mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/mobile-pentesting/android-app-pentesting/android-task-h
This commit is contained in:
parent
5273e78448
commit
1edab4a222
@ -8,36 +8,42 @@ Android에서 **작업**은 사용자가 특정 작업을 완료하기 위해
|
||||
|
||||
활동 전환에 대한 간단한 설명은 다음과 같습니다:
|
||||
|
||||
- **활동 1**은 포그라운드에서 유일한 활동으로 시작합니다.
|
||||
- **활동 1**은 포그라운드에서 유일한 활동으로 시작됩니다.
|
||||
- **활동 2**를 시작하면 **활동 1**이 백 스택으로 밀려나고, **활동 2**가 포그라운드로 옵니다.
|
||||
- **활동 3**을 시작하면 **활동 1**과 **활동 2**가 스택에서 더 뒤로 밀리고, **활동 3**이 이제 앞에 위치합니다.
|
||||
- **활동 3**을 시작하면 **활동 1**과 **활동 2**가 스택에서 더 뒤로 밀려나고, **활동 3**이 앞에 오게 됩니다.
|
||||
- **활동 3**을 닫으면 **활동 2**가 다시 포그라운드로 돌아와 Android의 간소화된 작업 탐색 메커니즘을 보여줍니다.
|
||||
|
||||
.png>)
|
||||
|
||||
## 작업 친화성 공격
|
||||
|
||||
### 작업 친화성 및 실행 모드 개요
|
||||
### 작업 친화성 및 시작 모드 개요
|
||||
|
||||
Android 애플리케이션에서 **작업 친화성**은 활동의 선호 작업을 지정하며, 일반적으로 앱의 패키지 이름과 일치합니다. 이 설정은 공격을 시연하기 위한 개념 증명(PoC) 앱을 만드는 데 중요합니다.
|
||||
|
||||
### 실행 모드
|
||||
### 시작 모드
|
||||
|
||||
`launchMode` 속성은 작업 내에서 활동 인스턴스의 처리를 지시합니다. **singleTask** 모드는 이 공격에 중요한 역할을 하며, 기존 활동 인스턴스와 작업 친화성 일치에 따라 세 가지 시나리오를 지시합니다. 이 취약점은 공격자의 앱이 대상 앱의 작업 친화성을 모방할 수 있는 능력에 의존하여, Android 시스템이 의도된 대상을 대신하여 공격자의 앱을 실행하도록 오도합니다.
|
||||
`launchMode` 속성은 작업 내에서 활동 인스턴스의 처리를 지시합니다. **singleTask** 모드는 이 공격에 중요한 역할을 하며, 기존 활동 인스턴스와 작업 친화성 일치에 따라 세 가지 시나리오를 지시합니다. 이 익스플로잇은 공격자의 앱이 대상 앱의 작업 친화성을 모방할 수 있는 능력에 의존하여, Android 시스템이 의도된 대상을 대신하여 공격자의 앱을 시작하도록 오도합니다.
|
||||
|
||||
### 상세 공격 단계
|
||||
|
||||
1. **악성 앱 설치**: 피해자가 자신의 장치에 공격자의 앱을 설치합니다.
|
||||
2. **초기 활성화**: 피해자가 악성 앱을 처음 열어 공격을 위한 장치를 설정합니다.
|
||||
1. **악성 앱 설치**: 피해자가 공격자의 앱을 자신의 장치에 설치합니다.
|
||||
2. **초기 활성화**: 피해자가 처음으로 악성 앱을 열어 공격을 위한 장치를 설정합니다.
|
||||
3. **대상 앱 실행 시도**: 피해자가 대상 앱을 열려고 시도합니다.
|
||||
4. **하이재킹 실행**: 일치하는 작업 친화성으로 인해 악성 앱이 대상 앱 대신 실행됩니다.
|
||||
4. **하이재킹 실행**: 어느 시점에서 앱이 **singleTask** 뷰를 열려고 합니다. 일치하는 작업 친화성으로 인해 악성 앱이 대상 앱 대신 실행됩니다.
|
||||
5. **기만**: 악성 앱이 대상 앱과 유사한 가짜 로그인 화면을 표시하여 사용자가 민감한 정보를 입력하도록 속입니다.
|
||||
|
||||
이 공격의 실제 구현에 대한 내용은 GitHub의 Task Hijacking Strandhogg 리포지토리를 참조하십시오: [Task Hijacking Strandhogg](https://github.com/az0mb13/Task_Hijacking_Strandhogg).
|
||||
> [!TIP]
|
||||
> 이 공격이 작동하려면 취약한 뷰가 **exported to true**일 필요는 없으며, 메인 활동일 필요도 없습니다.
|
||||
|
||||
이 공격의 실용적인 구현에 대해서는 GitHub의 Task Hijacking Strandhogg 리포지토리를 참조하십시오: [Task Hijacking Strandhogg](https://github.com/az0mb13/Task_Hijacking_Strandhogg).
|
||||
|
||||
### 예방 조치
|
||||
|
||||
이러한 공격을 방지하기 위해 개발자는 `taskAffinity`를 빈 문자열로 설정하고 `singleInstance` 실행 모드를 선택하여 자신의 앱이 다른 앱과 격리되도록 할 수 있습니다. `onBackPressed()` 함수를 사용자 정의하면 작업 하이재킹에 대한 추가 보호를 제공합니다.
|
||||
이러한 공격을 방지하기 위해 개발자는 다음을 수행할 수 있습니다:
|
||||
- **`**taskAffinity`**를 **singleTask** 뷰에 대해 빈 문자열로 설정 (`android:taskAffinity=""`)
|
||||
- **`singleInstance`** 시작 모드를 선택하여 자신의 앱이 다른 앱과 격리되도록 합니다.
|
||||
- **`onBackPressed()`** 함수를 사용자 정의하여 작업 하이재킹에 대한 추가 보호를 제공합니다.
|
||||
|
||||
## **참고 문헌**
|
||||
|
||||
|
@ -6,34 +6,76 @@
|
||||
|
||||
1. APK 파일의 이름을 zip 확장자로 변경하고 `cp com.example.apk example-apk.zip` 및 `unzip -qq example-apk.zip -d ReactNative` 명령을 사용하여 새 폴더에 추출합니다.
|
||||
|
||||
2. 새로 생성된 ReactNative 폴더로 이동하여 assets 폴더를 찾습니다. 이 폴더 안에는 minified 형식의 React JavaScript가 포함된 `index.android.bundle` 파일이 있어야 합니다.
|
||||
2. 새로 생성된 ReactNative 폴더로 이동하여 assets 폴더를 찾습니다. 이 폴더 안에는 React JavaScript가 축소된 형식으로 포함된 `index.android.bundle` 파일이 있어야 합니다.
|
||||
|
||||
3. `find . -print | grep -i ".bundle$"` 명령을 사용하여 JavaScript 파일을 검색합니다.
|
||||
|
||||
JavaScript 코드를 추가로 분석하려면 동일한 디렉토리에 `index.html`이라는 파일을 생성하고 다음 코드를 입력합니다:
|
||||
## Javascript 코드
|
||||
|
||||
`index.android.bundle`의 내용을 확인할 때 애플리케이션의 JavaScript 코드(축소된 경우에도)를 찾으면 **민감한 정보와 취약점을 찾기 위해 분석할 수 있습니다**.
|
||||
|
||||
번들은 실제로 애플리케이션의 모든 JS 코드를 포함하고 있으므로 **다양한 파일로 나눌 수 있습니다** (역공학을 더 쉽게 만들 수 있습니다) **[react-native-decompiler](https://github.com/numandev1/react-native-decompiler)** 도구를 사용하여.
|
||||
|
||||
### Webpack
|
||||
|
||||
JavaScript 코드를 더 분석하려면 파일을 [https://spaceraccoon.github.io/webpack-exploder/](https://spaceraccoon.github.io/webpack-exploder/)에 업로드하거나 다음 단계를 따르세요:
|
||||
|
||||
1. 동일한 디렉토리에 `index.html`이라는 이름의 파일을 생성하고 다음 코드를 입력합니다:
|
||||
```html
|
||||
<script src="./index.android.bundle"></script>
|
||||
```
|
||||
파일을 [https://spaceraccoon.github.io/webpack-exploder/](https://spaceraccoon.github.io/webpack-exploder/)에 업로드하거나 다음 단계를 따르세요:
|
||||
2. `index.html` 파일을 Google Chrome에서 엽니다.
|
||||
|
||||
1. Google Chrome에서 `index.html` 파일을 엽니다.
|
||||
3. **OS X의 경우 Command+Option+J** 또는 **Windows의 경우 Control+Shift+J**를 눌러 개발자 도구를 엽니다.
|
||||
|
||||
2. **OS X의 경우 Command+Option+J** 또는 **Windows의 경우 Control+Shift+J**를 눌러 개발자 도구를 엽니다.
|
||||
4. 개발자 도구에서 "Sources"를 클릭합니다. 폴더와 파일로 나뉘어진 JavaScript 파일이 보일 것입니다. 이는 주요 번들을 구성합니다.
|
||||
|
||||
3. 개발자 도구에서 "Sources"를 클릭합니다. 폴더와 파일로 나뉘어진 JavaScript 파일이 보이며, 이는 주요 번들을 구성합니다.
|
||||
|
||||
`index.android.bundle.map`이라는 파일을 찾으면, 비축소 형식으로 소스 코드를 분석할 수 있습니다. 맵 파일은 소스 매핑을 포함하고 있어 축소된 식별자를 매핑할 수 있습니다.
|
||||
`index.android.bundle.map`이라는 파일을 찾으면, 비축소 형식으로 소스 코드를 분석할 수 있습니다. 맵 파일은 소스 매핑을 포함하고 있어, 축소된 식별자를 매핑할 수 있게 해줍니다.
|
||||
|
||||
민감한 자격 증명 및 엔드포인트를 검색하려면 다음 단계를 따르세요:
|
||||
|
||||
1. JavaScript 코드를 분석하기 위해 민감한 키워드를 식별합니다. React Native 애플리케이션은 종종 Firebase, AWS S3 서비스 엔드포인트, 개인 키 등과 같은 서드파티 서비스를 사용합니다.
|
||||
|
||||
2. 이 특정 경우에 애플리케이션이 Dialogflow 서비스를 사용하고 있는 것으로 관찰되었습니다. 해당 구성과 관련된 패턴을 검색합니다.
|
||||
2. 이 특정 경우에 애플리케이션이 Dialogflow 서비스를 사용하고 있는 것으로 관찰되었습니다. 그 구성과 관련된 패턴을 검색합니다.
|
||||
|
||||
3. 재조사 과정에서 JavaScript 코드에서 민감한 하드코딩된 자격 증명이 발견된 것은 다행이었습니다.
|
||||
|
||||
## References
|
||||
### JS 코드 변경 및 재빌드
|
||||
|
||||
이 경우 코드를 변경하는 것은 쉽습니다. 앱의 이름을 `.zip` 확장자로 변경하고 압축을 풀기만 하면 됩니다. 그런 다음 **이 번들 안의 JS 코드를 수정하고 앱을 재빌드**할 수 있습니다. 이는 테스트 목적으로 앱에 **코드를 주입**할 수 있게 해줄 것입니다.
|
||||
|
||||
## Hermes 바이트코드
|
||||
|
||||
번들에 **Hermes 바이트코드**가 포함되어 있다면, 앱의 JavaScript 코드에 **접근할 수 없습니다** (축소된 버전조차도).
|
||||
|
||||
번들에 Hermes 바이트코드가 포함되어 있는지 확인하려면 다음 명령을 실행하세요:
|
||||
```bash
|
||||
file index.android.bundle
|
||||
index.android.bundle: Hermes JavaScript bytecode, version 96
|
||||
```
|
||||
그러나 **[hbctool](https://github.com/bongtrop/hbctool)**, **[hermes-dec](https://github.com/P1sec/hermes-dec)** 또는 **[hermes_rs](https://github.com/Pilfer/hermes_rs)** 도구를 사용하여 **바이트코드를 분해**하고 **일부 의사 JS 코드로 디컴파일**할 수 있습니다. 이를 위해 예를 들어 다음 명령어를 사용합니다:
|
||||
```bash
|
||||
hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
|
||||
hbc-decompiler ./index.android.bundle /tmp/my_output_file.js
|
||||
```
|
||||
### 코드 변경 및 재빌드
|
||||
|
||||
이상적으로는 디스어셈블된 코드를 수정할 수 있어야 합니다(비교를 변경하거나 값 또는 수정해야 할 기타 사항). 그런 다음 **바이트코드를 재빌드**하고 앱을 재빌드해야 합니다.
|
||||
|
||||
도구 **[hbctool](https://github.com/bongtrop/hbctool)**은 번들을 디스어셈블하고 변경 후 다시 빌드하는 것을 지원하지만, **구버전**의 Hermes 바이트코드만 지원합니다.
|
||||
|
||||
도구 **[hermes-dec](https://github.com/P1sec/hermes-dec)**는 바이트코드를 재빌드하는 것을 지원하지 않습니다.
|
||||
|
||||
도구 **[hermes_rs](https://github.com/Pilfer/hermes_rs)**는 바이트코드를 재빌드하는 것을 지원하지만, 실제로는 라이브러리이며 CLI 도구가 아닙니다.
|
||||
|
||||
## 동적 분석
|
||||
|
||||
앱을 동적으로 분석하려면 Frida를 사용하여 React 앱의 개발자 모드를 활성화하고 **`react-native-debugger`**를 사용하여 연결할 수 있습니다. 그러나 이를 위해서는 앱의 소스 코드가 필요합니다. 이에 대한 자세한 정보는 [https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/](https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/)에서 확인할 수 있습니다.
|
||||
|
||||
## 참고 문헌
|
||||
|
||||
- [https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7](https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7)
|
||||
- [https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications](https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications)
|
||||
- [https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf](https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -5,17 +5,19 @@
|
||||
## **기본 정보**
|
||||
|
||||
**Tapjacking**은 **악의적인** **애플리케이션**이 실행되어 **희생 애플리케이션 위에 위치하는 공격**입니다. 희생 앱을 가시적으로 가리면, 사용자 인터페이스는 사용자가 상호작용하도록 속이도록 설계되어 있으며, 이 상호작용은 희생 앱으로 전달됩니다.\
|
||||
결과적으로, 이는 **사용자가 실제로 희생 앱에서 작업을 수행하고 있다는 것을 알지 못하게 합니다**.
|
||||
결과적으로, 이는 **사용자가 실제로 희생 앱에서 작업을 수행하고 있다는 사실을 알지 못하게 만듭니다**.
|
||||
|
||||
### 탐지
|
||||
|
||||
이 공격에 취약한 앱을 탐지하기 위해서는 안드로이드 매니페스트에서 **내보낸 활동**을 검색해야 합니다 (인텐트 필터가 있는 활동은 기본적으로 자동으로 내보내집니다). 내보낸 활동을 찾으면, **권한이 필요한지 확인하십시오**. 이는 **악의적인 애플리케이션도 해당 권한이 필요하기 때문입니다**.
|
||||
이 공격에 취약한 앱을 탐지하기 위해서는 안드로이드 매니페스트에서 **내보낸 활동**을 검색해야 합니다(인텐트 필터가 있는 활동은 기본적으로 자동으로 내보내집니다). 내보낸 활동을 찾은 후, **해당 활동이 어떤 권한을 요구하는지 확인하십시오**. 이는 **악의적인 애플리케이션도 해당 권한이 필요하기 때문입니다**.
|
||||
|
||||
앱의 최소 SDK 버전도 확인할 수 있으며, **`android:minSdkVersion`**의 값을 **`AndroidManifest.xml`** 파일에서 확인하십시오. 값이 **30 미만**이면, 앱은 Tapjacking에 취약합니다.
|
||||
|
||||
### 보호
|
||||
|
||||
#### Android 12 (API 31,32) 및 그 이상
|
||||
|
||||
[**이 출처에 따르면**](https://www.geeksforgeeks.org/tapjacking-in-android/)**,** tapjacking 공격은 Android 12 (API 31 & 30) 및 그 이상에서 자동으로 방지됩니다. 따라서 애플리케이션이 취약하더라도 **악용할 수 없습니다**.
|
||||
[**이 출처에 따르면**](https://www.geeksforgeeks.org/tapjacking-in-android/)**,** Tapjacking 공격은 Android 12 (API 31 & 30) 이상에서 자동으로 방지됩니다. 따라서 애플리케이션이 취약하더라도 **악용할 수 없습니다**.
|
||||
|
||||
#### `filterTouchesWhenObscured`
|
||||
|
||||
@ -23,7 +25,7 @@
|
||||
|
||||
#### **`setFilterTouchesWhenObscured`**
|
||||
|
||||
**`setFilterTouchesWhenObscured`** 속성이 true로 설정되면, 안드로이드 버전이 낮더라도 이 취약점의 악용을 방지할 수 있습니다.\
|
||||
**`setFilterTouchesWhenObscured`** 속성이 true로 설정되면, Android 버전이 낮더라도 이 취약점의 악용을 방지할 수 있습니다.\
|
||||
예를 들어, **`true`**로 설정하면 버튼이 가려질 경우 자동으로 **비활성화될 수 있습니다**:
|
||||
```xml
|
||||
<Button android:text="Button"
|
||||
@ -37,26 +39,26 @@ android:filterTouchesWhenObscured="true">
|
||||
|
||||
### Tapjacking-ExportedActivity
|
||||
|
||||
가장 **최근의 Android 애플리케이션**으로 Tapjacking 공격을 수행하는 애플리케이션(+ 공격받는 애플리케이션의 내보낸 활동 이전에 호출) 은 다음에서 찾을 수 있습니다: [**https://github.com/carlospolop/Tapjacking-ExportedActivity**](https://github.com/carlospolop/Tapjacking-ExportedActivity).
|
||||
가장 **최근의 Android 애플리케이션**으로 Tapjacking 공격을 수행하는 (+ 공격받는 애플리케이션의 내보낸 활동 이전에 호출) 애플리케이션은 다음에서 찾을 수 있습니다: [**https://github.com/carlospolop/Tapjacking-ExportedActivity**](https://github.com/carlospolop/Tapjacking-ExportedActivity).
|
||||
|
||||
**README 지침에 따라 사용하세요**.
|
||||
**사용을 위한 README 지침을 따르세요**.
|
||||
|
||||
### FloatingWindowApp
|
||||
|
||||
**FloatingWindowApp**을 구현한 예제 프로젝트는 클릭재킹 공격을 수행하기 위해 다른 활동 위에 올릴 수 있는 프로젝트로, [**FloatingWindowApp**](https://github.com/aminography/FloatingWindowApp)에서 찾을 수 있습니다 (조금 오래된 프로젝트로, apk 빌드에 행운을 빕니다).
|
||||
클릭재킹 공격을 수행하기 위해 다른 활동 위에 올릴 수 있는 **FloatingWindowApp**을 구현한 예제 프로젝트는 [**FloatingWindowApp**](https://github.com/aminography/FloatingWindowApp)에서 찾을 수 있습니다 (조금 오래된 프로젝트로, apk 빌드에 행운을 빕니다).
|
||||
|
||||
### Qark
|
||||
|
||||
> [!CAUTION]
|
||||
> 이 프로젝트는 현재 유지 관리되지 않는 것 같으며 이 기능이 더 이상 제대로 작동하지 않습니다.
|
||||
> 이 프로젝트는 현재 유지 관리되지 않는 것 같으며 이 기능이 제대로 작동하지 않습니다.
|
||||
|
||||
`--exploit-apk` --sdk-path `/Users/username/Library/Android/sdk` 매개변수와 함께 [**qark**](https://github.com/linkedin/qark)를 사용하여 가능한 **Tapjacking** 취약점을 테스트하기 위한 악성 애플리케이션을 생성할 수 있습니다.\
|
||||
`--exploit-apk` --sdk-path `/Users/username/Library/Android/sdk` 매개변수를 사용하여 [**qark**](https://github.com/linkedin/qark)를 사용하여 가능한 **Tapjacking** 취약점을 테스트하기 위한 악성 애플리케이션을 생성할 수 있습니다.\
|
||||
|
||||
완화 방법은 상대적으로 간단합니다. 개발자는 다른 뷰에 의해 가려질 때 터치 이벤트를 수신하지 않도록 선택할 수 있습니다. [Android 개발자 참조](https://developer.android.com/reference/android/view/View#security)를 사용하여:
|
||||
|
||||
> 때때로 애플리케이션이 사용자의 완전한 지식과 동의 하에 작업이 수행되고 있는지 확인하는 것이 필수적입니다. 예를 들어 권한 요청을 승인하거나, 구매를 하거나, 광고를 클릭하는 경우입니다. 불행히도, 악성 애플리케이션은 사용자가 의도된 뷰의 목적을 알지 못한 채 이러한 작업을 수행하도록 속이려고 할 수 있습니다. 이를 해결하기 위해 프레임워크는 민감한 기능에 대한 접근을 개선하기 위해 사용할 수 있는 터치 필터링 메커니즘을 제공합니다.
|
||||
> 때때로 애플리케이션이 사용자의 완전한 지식과 동의 하에 작업이 수행되고 있는지 확인하는 것이 필수적입니다. 예를 들어 권한 요청을 승인하거나, 구매를 하거나, 광고를 클릭하는 경우입니다. 불행히도, 악성 애플리케이션은 사용자가 의도한 목적을 숨김으로써 이러한 작업을 수행하도록 속이려고 할 수 있습니다. 이를 해결하기 위해 프레임워크는 민감한 기능에 대한 접근을 개선하기 위해 사용할 수 있는 터치 필터링 메커니즘을 제공합니다.
|
||||
>
|
||||
> 터치 필터링을 활성화하려면 [`setFilterTouchesWhenObscured(boolean)`](https://developer.android.com/reference/android/view/View#setFilterTouchesWhenObscured%28boolean%29)를 호출하거나 android:filterTouchesWhenObscured 레이아웃 속성을 true로 설정하세요. 활성화되면 프레임워크는 뷰의 창이 다른 보이는 창에 의해 가려질 때 수신된 터치를 무시합니다. 결과적으로, 뷰의 창 위에 토스트, 대화상자 또는 다른 창이 나타날 때 뷰는 터치를 수신하지 않습니다.
|
||||
> 터치 필터링을 활성화하려면 [`setFilterTouchesWhenObscured(boolean)`](https://developer.android.com/reference/android/view/View#setFilterTouchesWhenObscured%28boolean%29)를 호출하거나 android:filterTouchesWhenObscured 레이아웃 속성을 true로 설정합니다. 활성화되면 프레임워크는 뷰의 창이 다른 보이는 창에 의해 가려질 때 수신된 터치를 무시합니다. 결과적으로, 뷰의 창 위에 토스트, 대화 상자 또는 다른 창이 나타날 때 뷰는 터치를 수신하지 않습니다.
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -471,12 +471,13 @@ window.search = window.search || {};
|
||||
showResults(true);
|
||||
}
|
||||
|
||||
fetch(path_to_root + 'searchindex.json')
|
||||
var branch = lang === "en" ? "master" : lang
|
||||
fetch(`https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.json`)
|
||||
.then(response => response.json())
|
||||
.then(json => init(json))
|
||||
.catch(error => { // Try to load searchindex.js if fetch failed
|
||||
var script = document.createElement('script');
|
||||
script.src = path_to_root + 'searchindex.js';
|
||||
script.src = `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.js`;
|
||||
script.onload = () => init(window.search);
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
@ -55,6 +55,7 @@
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var lang = "{{ language }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user