├── .DS_Store ├── entry ├── .gitignore ├── src │ ├── main │ │ ├── resources │ │ │ ├── base │ │ │ │ ├── profile │ │ │ │ │ ├── backup_config.json │ │ │ │ │ └── main_pages.json │ │ │ │ ├── media │ │ │ │ │ ├── icon.png │ │ │ │ │ ├── startIcon.png │ │ │ │ │ ├── background.png │ │ │ │ │ ├── foreground.png │ │ │ │ │ └── layered_image.json │ │ │ │ └── element │ │ │ │ │ ├── float.json │ │ │ │ │ ├── color.json │ │ │ │ │ └── string.json │ │ │ ├── dark │ │ │ │ └── element │ │ │ │ │ └── color.json │ │ │ └── rawfile │ │ │ │ └── vuln.html │ │ ├── cpp │ │ │ ├── types │ │ │ │ └── libentry │ │ │ │ │ ├── oh-package.json5 │ │ │ │ │ └── Index.d.ts │ │ │ ├── CMakeLists.txt │ │ │ └── vuln_jni.cpp │ │ ├── ets │ │ │ ├── entrybackupability │ │ │ │ └── EntryBackupAbility.ets │ │ │ ├── entryability │ │ │ │ ├── LeakyImplicitAbility.ets │ │ │ │ ├── SecretAbility.ets │ │ │ │ └── EntryAbility.ets │ │ │ └── pages │ │ │ │ ├── ImplicitHijackReceiver.ets │ │ │ │ ├── HardcodedKeyPage.ets │ │ │ │ ├── SecurePaymentPage.ets │ │ │ │ ├── InsecureNetworkPage.ets │ │ │ │ ├── InsecureStoragePage.ets │ │ │ │ ├── WebViewPage.ets │ │ │ │ ├── Index.ets │ │ │ │ ├── ImplicitHijackPage.ets │ │ │ │ ├── ClipboardLeakPage.ets │ │ │ │ ├── SqlInjectionPage.ets │ │ │ │ └── NdkPage.ets │ │ └── module.json5 │ ├── mock │ │ ├── mock-config.json5 │ │ └── Libentry.mock.ets │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── hvigorfile.ts ├── oh-package.json5 ├── oh-package-lock.json5 ├── build-profile.json5 └── obfuscation-rules.txt ├── AppScope ├── resources │ └── base │ │ ├── media │ │ ├── background.png │ │ ├── foreground.png │ │ └── layered_image.json │ │ └── element │ │ └── string.json └── app.json5 ├── hvigorfile.ts ├── oh-package.json5 ├── .gitignore ├── docs └── signing-setup.md ├── code-linter.json5 ├── oh-package-lock.json5 ├── LICENSE ├── LICENSE.txt ├── hvigor └── hvigor-config.json5 └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/.DS_Store -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/backup_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowToBackupRestore": true 3 | } -------------------------------------------------------------------------------- /entry/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "libentry.so": { 3 | "source": "src/mock/Libentry.mock.ets" 4 | } 5 | } -------------------------------------------------------------------------------- /AppScope/resources/base/media/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/AppScope/resources/base/media/background.png -------------------------------------------------------------------------------- /AppScope/resources/base/media/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/AppScope/resources/base/media/foreground.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/entry/src/main/resources/base/media/icon.png -------------------------------------------------------------------------------- /entry/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/startIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/entry/src/main/resources/base/media/startIcon.png -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/entry/src/main/resources/base/media/background.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistrainn/harmonyTarget/HEAD/entry/src/main/resources/base/media/foreground.png -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "VulnerableAppDemo" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/float.json: -------------------------------------------------------------------------------- 1 | { 2 | "float": [ 3 | { 4 | "name": "page_text_font_size", 5 | "value": "50fp" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /entry/src/main/resources/dark/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#000000" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /AppScope/resources/base/media/layered_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:background", 5 | "foreground" : "$media:foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/layered_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:background", 5 | "foreground" : "$media:foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /entry/src/mock/Libentry.mock.ets: -------------------------------------------------------------------------------- 1 | const NativeMock: Record = { 2 | 'add': (a: number, b: number) => { 3 | return a + b; 4 | }, 5 | }; 6 | 7 | export default NativeMock; -------------------------------------------------------------------------------- /entry/src/main/cpp/types/libentry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libentry.so", 3 | "types": "./Index.d.ts", 4 | "version": "1.0.0", 5 | "description": "Please describe the basic information." 6 | } -------------------------------------------------------------------------------- /entry/src/main/cpp/types/libentry/Index.d.ts: -------------------------------------------------------------------------------- 1 | export const add: (a: number, b: number) => number; 2 | 3 | export function getSecretKeyFromNative(): string { 4 | throw new Error('Function not implemented.'); 5 | } 6 | -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "phone" 7 | ], 8 | "deliveryWithInstall": true, 9 | "installationFree": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { appTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.5", 3 | "description": "Please describe the basic information.", 4 | "dependencies": { 5 | }, 6 | "devDependencies": { 7 | "@ohos/hypium": "1.0.21", 8 | "@ohos/hamock": "1.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.example.vulnerableappdemo", 4 | "vendor": "example", 5 | "versionCode": 1001000, 6 | "versionName": "1.1.0", 7 | "icon": "$media:layered_image", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "entry", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "", 6 | "author": "", 7 | "license": "", 8 | "dependencies": { 9 | "libentry.so": "file:./src/main/cpp/types/libentry" 10 | } 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /local.properties 4 | /.idea 5 | **/build 6 | /.hvigor 7 | .cxx 8 | /.clangd 9 | /.clang-format 10 | /.clang-tidy 11 | **/.test 12 | /.appanalyzer 13 | # Local signing material 14 | build-profile.local.json5 15 | build-profile.json5 16 | AGENTS.md 17 | -------------------------------------------------------------------------------- /entry/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # the minimum version of CMake. 2 | cmake_minimum_required(VERSION 3.5.0) 3 | project(entry) 4 | 5 | set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 6 | 7 | add_library( 8 | entry 9 | SHARED 10 | vuln_jni.cpp) 11 | 12 | target_link_libraries( 13 | entry 14 | ace_napi.z 15 | hilog_ndk.z) -------------------------------------------------------------------------------- /docs/signing-setup.md: -------------------------------------------------------------------------------- 1 | # 签名配置指引 2 | 3 | 项目不包含任何真实签名材料,构建/发布需使用你的本地证书与 profile。 4 | 5 | ## 配置步骤 6 | 7 | 1. 将根目录的 `build-profile.json5` 复制为 `build-profile.local.json5`,在副本中填写你的证书、密钥库、profile 的绝对路径和密码。 8 | 2. `build-profile.local.json5` 已加入 `.gitignore`,确保不要提交到版本库。 9 | 3. 构建前的常见做法: 10 | - 直接在 `build-profile.local.json5` 中维护个人配置,并在本地脚本中引用;或 11 | - 用脚本在构建前临时覆盖 `build-profile.json5`,构建后恢复。 12 | 4. 仅在本地机器存放证书,避免通过仓库、IM、邮件等渠道分发。 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index", 4 | "pages/InsecureStoragePage", 5 | "pages/InsecureNetworkPage", 6 | "pages/HardcodedKeyPage", 7 | "pages/WebViewPage", 8 | "pages/NdkPage", 9 | "pages/SqlInjectionPage", 10 | "pages/SecurePaymentPage", 11 | "pages/ImplicitHijackPage", 12 | "pages/ClipboardLeakPage", 13 | "pages/ImplicitHijackReceiver" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /entry/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true, 4 | "enableUnifiedLockfile": false 5 | }, 6 | "lockfileVersion": 3, 7 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 8 | "specifiers": { 9 | "libentry.so@src/main/cpp/types/libentry": "libentry.so@src/main/cpp/types/libentry" 10 | }, 11 | "packages": { 12 | "libentry.so@src/main/cpp/types/libentry": { 13 | "name": "libentry.so", 14 | "version": "1.0.0", 15 | "resolved": "src/main/cpp/types/libentry", 16 | "registryType": "local" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /entry/src/main/ets/entrybackupability/EntryBackupAbility.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; 3 | 4 | const DOMAIN = 0x0000; 5 | 6 | export default class EntryBackupAbility extends BackupExtensionAbility { 7 | async onBackup() { 8 | hilog.info(DOMAIN, 'testTag', 'onBackup ok'); 9 | await Promise.resolve(); 10 | } 11 | 12 | async onRestore(bundleVersion: BundleVersion) { 13 | hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); 14 | await Promise.resolve(); 15 | } 16 | } -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/LeakyImplicitAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import window from '@ohos.window'; 3 | import { Want } from '@kit.AbilityKit'; 4 | 5 | let latestLeakyWant: Want | undefined; 6 | 7 | export default class LeakyImplicitAbility extends UIAbility { 8 | onCreate(want: Want): void { 9 | latestLeakyWant = want; 10 | } 11 | 12 | onWindowStageCreate(windowStage: window.WindowStage): void { 13 | windowStage.loadContent('pages/ImplicitHijackReceiver', (err) => { 14 | if (err && err.code) { 15 | console.error(`Failed to load hijack receiver page: ${JSON.stringify(err)}`); 16 | } 17 | }); 18 | } 19 | } 20 | 21 | export function getLeakedWant(): Want | undefined { 22 | return latestLeakyWant; 23 | } 24 | -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | "externalNativeOptions": { 5 | "path": "./src/main/cpp/CMakeLists.txt", 6 | "arguments": "", 7 | "cppFlags": "", 8 | "abiFilters": [ 9 | "arm64-v8a", 10 | "x86_64" 11 | ] 12 | } 13 | }, 14 | "buildOptionSet": [ 15 | { 16 | "name": "release", 17 | "arkOptions": { 18 | "obfuscation": { 19 | "ruleOptions": { 20 | "enable": false, 21 | "files": [ 22 | "./obfuscation-rules.txt" 23 | ] 24 | } 25 | } 26 | }, 27 | "nativeLib": { 28 | "debugSymbol": { 29 | "strip": true, 30 | "exclude": [] 31 | } 32 | } 33 | }, 34 | ], 35 | "targets": [ 36 | { 37 | "name": "default" 38 | }, 39 | { 40 | "name": "ohosTest", 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/SecretAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import window from '@ohos.window'; 3 | 4 | export default class SecretAbility extends UIAbility { 5 | onWindowStageCreate(windowStage: window.WindowStage) { 6 | windowStage.loadContent('pages/SecretPage', (err) => { 7 | if (err?.code) { 8 | console.error(`Failed to load secret page: ${JSON.stringify(err)}`); 9 | } 10 | }); 11 | } 12 | } 13 | 14 | @Component 15 | struct SecretPage { 16 | build() { 17 | Column({ space: 20 }) { 18 | Text('秘密页面') 19 | .fontSize(30) 20 | .fontWeight(FontWeight.Bold) 21 | Image($r('app.media.icon')) 22 | .width(100) 23 | Text('这是一个被导出的秘密页面!') 24 | .fontSize(20) 25 | .fontColor(Color.Red) 26 | Text('在正常流程下,你不应该能看到我。') 27 | } 28 | .width('100%') 29 | .height('100%') 30 | .justifyContent(FlexAlign.Center) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /code-linter.json5: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "**/*.ets" 4 | ], 5 | "ignore": [ 6 | "**/src/ohosTest/**/*", 7 | "**/src/test/**/*", 8 | "**/src/mock/**/*", 9 | "**/node_modules/**/*", 10 | "**/oh_modules/**/*", 11 | "**/build/**/*", 12 | "**/.preview/**/*" 13 | ], 14 | "ruleSet": [ 15 | "plugin:@performance/recommended", 16 | "plugin:@typescript-eslint/recommended" 17 | ], 18 | "rules": { 19 | "@security/no-unsafe-aes": "error", 20 | "@security/no-unsafe-hash": "error", 21 | "@security/no-unsafe-mac": "warn", 22 | "@security/no-unsafe-dh": "error", 23 | "@security/no-unsafe-dsa": "error", 24 | "@security/no-unsafe-ecdsa": "error", 25 | "@security/no-unsafe-rsa-encrypt": "error", 26 | "@security/no-unsafe-rsa-sign": "error", 27 | "@security/no-unsafe-rsa-key": "error", 28 | "@security/no-unsafe-dsa-key": "error", 29 | "@security/no-unsafe-dh-key": "error", 30 | "@security/no-unsafe-3des": "error" 31 | } 32 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "module description" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "应用描述" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "NsfocusVuln" 14 | }, 15 | { 16 | "name": "permission_read_pasteboard_reason", 17 | "value": "演示剪贴板被动窃取风险,需要读取剪贴板内容用于教学" 18 | }, 19 | { 20 | "name": "secret_ability_label", 21 | "value": "Secret Page" 22 | }, 23 | { 24 | "name": "sensitive_action_label", 25 | "value": "执行敏感操作" 26 | }, 27 | { 28 | "name": "sensitive_action_desc", 29 | "value": "允许应用执行内部的敏感操作" 30 | }, 31 | { 32 | "name": "secret_Unprotected_Ability_label", 33 | "value": "Unprotected Ability" 34 | }, 35 | { 36 | "name": "secret_protected_Ability_label", 37 | "value": "Protected Ability" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true, 4 | "enableUnifiedLockfile": false 5 | }, 6 | "lockfileVersion": 3, 7 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 8 | "specifiers": { 9 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", 10 | "@ohos/hypium@1.0.21": "@ohos/hypium@1.0.21" 11 | }, 12 | "packages": { 13 | "@ohos/hamock@1.0.0": { 14 | "name": "@ohos/hamock", 15 | "version": "1.0.0", 16 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", 17 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", 18 | "registryType": "ohpm" 19 | }, 20 | "@ohos/hypium@1.0.21": { 21 | "name": "@ohos/hypium", 22 | "version": "1.0.21", 23 | "integrity": "sha512-iyKGMXxE+9PpCkqEwu0VykN/7hNpb+QOeIuHwkmZnxOpI+dFZt6yhPB7k89EgV1MiSK/ieV/hMjr5Z2mWwRfMQ==", 24 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.21.har", 25 | "registryType": "ohpm" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /entry/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 LuMingFei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 LuMingFei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/ImplicitHijackReceiver.ets: -------------------------------------------------------------------------------- 1 | import { Want } from '@kit.AbilityKit'; 2 | import { getLeakedWant } from '../entryability/LeakyImplicitAbility'; 3 | 4 | @Entry 5 | @Component 6 | struct ImplicitHijackReceiver { 7 | private readonly wantSnapshot: Want | undefined = getLeakedWant(); 8 | 9 | build() { 10 | Column({ space: 10 }) { 11 | Text('LeakyImplicitAbility 已截获隐式 Want') 12 | .fontSize(22) 13 | .fontWeight(FontWeight.Bold) 14 | .padding({ top: 20, bottom: 10 }) 15 | 16 | Text(`action: ${this.wantSnapshot?.action ?? '未知'}`) 17 | .fontSize(16) 18 | .fontColor(Color.Red) 19 | 20 | Text('收到的 parameters:') 21 | .fontSize(14) 22 | .fontColor(Color.Grey) 23 | 24 | Text(JSON.stringify(this.wantSnapshot?.parameters ?? {}, null, 2)) 25 | .fontSize(12) 26 | .backgroundColor('#f6f6f6') 27 | .padding(12) 28 | .borderRadius(10) 29 | .width('90%') 30 | 31 | Text('漏洞说明:隐式 Want 未指定 bundle/ability,任意导出组件都能拦截并读取敏感数据。') 32 | .fontSize(14) 33 | .fontColor(Color.Gray) 34 | .padding(15) 35 | .backgroundColor('#f0f0f0') 36 | .borderRadius(10) 37 | .width('90%') 38 | } 39 | .width('100%') 40 | .height('100%') 41 | .justifyContent(FlexAlign.Start) 42 | .alignItems(HorizontalAlign.Center) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.5", 3 | "dependencies": { 4 | }, 5 | "execution": { 6 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ 7 | // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ 8 | // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ 9 | // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ 10 | // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ 11 | // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ 12 | }, 13 | "logging": { 14 | // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ 15 | }, 16 | "debugging": { 17 | // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ 18 | }, 19 | "nodeOptions": { 20 | // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ 21 | // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { hilog } from '@kit.PerformanceAnalysisKit'; 3 | import { window } from '@kit.ArkUI'; 4 | 5 | const DOMAIN = 0x0000; 6 | 7 | export default class EntryAbility extends UIAbility { 8 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 9 | this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); 10 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); 11 | } 12 | 13 | onDestroy(): void { 14 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); 15 | } 16 | 17 | onWindowStageCreate(windowStage: window.WindowStage): void { 18 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 19 | 20 | windowStage.loadContent('pages/Index', (err) => { 21 | if (err.code) { 22 | hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); 23 | return; 24 | } 25 | hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); 26 | }); 27 | } 28 | 29 | onWindowStageDestroy(): void { 30 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 31 | } 32 | 33 | onForeground(): void { 34 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); 35 | } 36 | 37 | onBackground(): void { 38 | hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/vuln.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vulnerable H5 5 | 6 | 11 | 12 | 13 |

WebView JS Interface 测试

14 | 15 |
点击按钮后,原生代码返回的信息将显示在这里...
16 | 17 | 18 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /entry/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/HardcodedKeyPage.ets: -------------------------------------------------------------------------------- 1 | import promptAction from '@ohos.promptAction'; 2 | 3 | @Entry 4 | @Component 5 | struct HardcodedKeyPage { 6 | // VULNERABILITY: 密钥被硬编码在代码中 7 | private readonly SECRET_KEY = "this_is_a_very_secret_key_123"; 8 | private readonly MAP_API_KEY = "AKfj-s93f_thisIsAFakeBaiduMapKey"; 9 | @State plainText: string = 'Hello HarmonyOS'; 10 | @State encryptedText: string = ''; 11 | // Secure version (示例): 将密钥存入 Keystore 或后端下发,代码不直接存储明文 key 12 | // private async getKeyFromKeystore(): Promise { return await keyStore.get('MAP_API_KEY'); } 13 | 14 | // 简单的异或加密算法,用于演示 15 | xorEncrypt(input: string, key: string): string { 16 | let output = ''; 17 | for (let i = 0; i < input.length; i++) { 18 | output += String.fromCharCode(input.charCodeAt(i) ^ key.charCodeAt(i % key.length)); 19 | } 20 | return output; 21 | } 22 | 23 | build() { 24 | Column({ space: 20 }) { 25 | Text('硬编码敏感信息 (ArkTS)').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 26 | 27 | TextInput({ placeholder: '输入要加密的文本', text: this.plainText }) 28 | .onChange(val => this.plainText = val) 29 | 30 | Button('使用硬编码密钥加密') 31 | .onClick(() => { 32 | this.encryptedText = this.xorEncrypt(this.plainText, this.SECRET_KEY); 33 | promptAction.showToast({ message: '加密完成' }); 34 | }) 35 | // Secure version (示例): 使用系统加密框架 + 动态密钥 36 | // .onClick(async () => { 37 | // const safeKey = await this.getKeyFromKeystore(); 38 | // this.encryptedText = await systemCrypto.encrypt(this.plainText, safeKey); 39 | // }) 40 | 41 | Text('加密结果:') 42 | Text(this.encryptedText).fontColor(Color.Red) 43 | 44 | Text('操作指引:\n请反编译本应用的HAP包,在ArkTS代码中搜索字符串,即可找到硬编码的密钥 "this_is_a_very_secret_key_123"。') 45 | .padding(15) 46 | .backgroundColor('#f0f0f0') 47 | .borderRadius(10) 48 | .margin({ top: 30 }) 49 | } 50 | .padding(20) 51 | .width('100%') 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/SecurePaymentPage.ets: -------------------------------------------------------------------------------- 1 | import window from '@ohos.window'; 2 | import promptAction from '@ohos.promptAction'; 3 | 4 | @Entry 5 | @Component 6 | struct SecurePaymentPage { 7 | private win: window.Window | undefined = undefined; 8 | 9 | async aboutToAppear() { 10 | try { 11 | let windowClass = await window.getTopWindow(getContext(this)); 12 | this.win = windowClass; 13 | // VULNERABILITY (from a feature perspective): 开启防截屏 14 | await this.win.setWindowPrivacyMode(true); 15 | promptAction.showToast({ message: '防截屏模式已开启' }); 16 | } catch (exception) { 17 | console.error('Failed to set privacy mode. Cause: ' + JSON.stringify(exception)); 18 | } 19 | } 20 | 21 | async aboutToDisappear() { 22 | // 离开页面时恢复,避免影响其他页面 23 | if (this.win) { 24 | try { 25 | await this.win.setWindowPrivacyMode(false); 26 | } catch (exception) { 27 | console.error('Failed to reset privacy mode. Cause: ' + JSON.stringify(exception)); 28 | } 29 | } 30 | } 31 | 32 | build() { 33 | Column({ space: 20 }) { 34 | Text('模拟支付 (安全特性)').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 35 | 36 | Text('支付金额').width('100%').textAlign(TextAlign.Start) 37 | TextInput({ text: '100.00' }) 38 | .type(InputType.Number) // 使用数字安全键盘 39 | 40 | Text('支付密码').width('100%').textAlign(TextAlign.Start) 41 | TextInput({ placeholder: '请输入6位密码' }) 42 | .type(InputType.Password) // 使用密码安全键盘 43 | .maxLength(6) 44 | 45 | Button('确认支付') 46 | .width('80%') 47 | .onClick(() => { 48 | promptAction.showToast({ message: '模拟支付成功' }); 49 | }) 50 | 51 | Text("安全特性演示:\n1. 防截屏/录屏: 当前页面已开启防截屏,请尝试截图,预期会失败或得到黑屏。\n2. 安全键盘: 点击密码框,系统会弹出安全键盘。\n3. 后台安全提示: 将App滑到后台,在多任务卡片上应有安全提示(此特性依赖系统实现)。") 52 | .padding(15) 53 | .backgroundColor('#f0f0f0') 54 | .borderRadius(10) 55 | .margin({ top: 30 }) 56 | } 57 | .padding(20).width('100%') 58 | } 59 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/InsecureNetworkPage.ets: -------------------------------------------------------------------------------- 1 | import http from '@ohos.net.http'; 2 | import promptAction from '@ohos.promptAction'; 3 | 4 | @Entry 5 | @Component 6 | struct InsecureNetworkPage { 7 | @State responseText: string = '等待请求...'; 8 | 9 | fetchDataOverHttp() { 10 | this.responseText = '请求中...'; 11 | // VULNERABILITY: 使用 HTTP 明文传输 12 | const url = 'http://httpbin.org/get?param=sensitive_data'; // 若设备无法直连,可改成 http://example.com 13 | // Secure version (示例): const url = 'https://api.example.com/secure'; 并配置证书校验/Pinning 14 | const httpRequest = http.createHttp(); 15 | httpRequest.request(url, { method: http.RequestMethod.GET, readTimeout: 5000 }, (err, data) => { 16 | if (err) { 17 | this.responseText = `请求失败: ${JSON.stringify(err)}`; 18 | httpRequest.destroy(); 19 | return; 20 | } 21 | let body = ''; 22 | try { 23 | if (typeof data.result === 'string') { 24 | body = data.result; 25 | } else if (data.result instanceof ArrayBuffer) { 26 | body = String.fromCharCode(...new Uint8Array(data.result)); 27 | } else { 28 | body = JSON.stringify(data.result); 29 | } 30 | } catch (e) { 31 | body = `解析响应失败: ${e}`; 32 | } 33 | this.responseText = `请求成功!HTTP ${data.responseCode}\n响应内容:\n${body}`; 34 | httpRequest.destroy(); 35 | }); 36 | } 37 | 38 | build() { 39 | Column({ space: 20 }) { 40 | Text('不安全的网络通信').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 41 | 42 | Button('通过HTTP获取数据') 43 | .onClick(() => this.fetchDataOverHttp()) 44 | 45 | Scroll() { 46 | Text(this.responseText) 47 | .width('100%') 48 | .padding(10) 49 | .backgroundColor('#f0f0f0') 50 | .borderRadius(10) 51 | } 52 | .layoutWeight(1) 53 | 54 | Text('操作指引:\n请使用抓包工具 (如 Burp Suite, Charles) 监控本机的网络流量,你将能看到明文传输的HTTP请求和响应。') 55 | .padding(15) 56 | .backgroundColor('#f0f0f0') 57 | .borderRadius(10) 58 | .margin({ top: 20 }) 59 | } 60 | .padding(20) 61 | .width('100%') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /entry/src/main/cpp/vuln_jni.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created on 2025/9/8. 3 | // 4 | // Node APIs are not fully supported. To solve the compilation error of the interface cannot be found, 5 | // please include "napi/native_api.h". 6 | #include "napi/native_api.h" // 引入 NAPI 框架头文件,这是与 ArkTS 通信的桥梁 7 | #include 8 | 9 | // ======================= 漏洞点 ======================= 10 | // 将一个敏感的密钥字符串直接定义为 C++ 的常量。 11 | // 当编译成 .so 文件后,这个字符串会以明文形式存在于二进制文件中。 12 | static const char* SECRET_KEY = "key_from_native_lib_in_so_file"; 13 | // ===================================================== 14 | 15 | /** 16 | * @brief 这是我们要暴露给 ArkTS 的原生函数。 17 | * @param env NAPI 的环境句柄,可以理解为当前的 JS/ArkTS 运行环境。 18 | * @param info 回调信息,包含函数的入参等。 19 | * @return 返回一个 napi_value,这是可以在 ArkTS 中使用的数据类型。 20 | */ 21 | static napi_value GetSecretKeyFromNative(napi_env env, napi_callback_info info) { 22 | napi_value result; 23 | // 使用 napi_create_string_utf8 函数,将 C++ 的字符串 (const char*) 转换为 ArkTS 可以识别的字符串 (napi_value)。 24 | napi_create_string_utf8(env, SECRET_KEY, std::string(SECRET_KEY).length(), &result); 25 | return result; 26 | } 27 | 28 | // NAPI 模块初始化函数。当 ArkTS `import` 这个模块时,此函数会被调用。 29 | EXTERN_C_START 30 | static napi_value Init(napi_env env, napi_value exports) { 31 | // 定义一个属性描述符,用于描述我们要暴露的函数 32 | napi_property_descriptor desc[] = { 33 | // "getSecretKeyFromNative" 是暴露给 ArkTS 的函数名 34 | // GetSecretKeyFromNative 是上面我们实现的 C++ 函数指针 35 | { "getSecretKeyFromNative", nullptr, GetSecretKeyFromNative, nullptr, nullptr, nullptr, napi_default, nullptr } 36 | }; 37 | // 将这个属性定义到 `exports` 对象上,这样 ArkTS 就能通过 `exports.getSecretKeyFromNative` 调用了 38 | napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 39 | return exports; 40 | } 41 | EXTERN_C_END 42 | 43 | // 定义 NAPI 模块本身 44 | static napi_module demoModule = { 45 | .nm_version = 1, 46 | .nm_flags = 0, 47 | .nm_filename = nullptr, 48 | .nm_register_func = Init, // 注册初始化函数 49 | .nm_modname = "entry", 50 | .nm_priv = ((void*)0), 51 | .reserved = { 0 }, 52 | }; 53 | 54 | // 在模块被加载时,调用 napi_module_register 注册我们的模块 55 | extern "C" __attribute__((constructor)) void RegisterModule(void) { 56 | napi_module_register(&demoModule); 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # harmonyTarget 2 | 3 | 一个专为安全培训打造的 HarmonyOS NEXT 示范应用。项目刻意实现了多种常见的移动端安全漏洞,用来演示在 ArkTS 代码中漏洞是如何产生、如何被发现以及潜在的风险点。 4 | 5 | > ⚠️ **仅供教学与实验使用。** 请勿将此代码用于生产环境或任何恶意活动。 6 | 7 | ## 漏洞场景速览 8 | 9 | - 明文存储与日志泄露:账号密码直接写入 `Preferences` 10 | - 不安全的网络通信:使用 HTTP 明文请求,缺少 TLS 保护 11 | - ArkTS 代码中的硬编码密钥 12 | - WebView 误用:任意 URL 加载、暴露 JS Bridge 13 | - `.so` 原生库内嵌密钥(NDK 演示) 14 | - 字符串拼接导致的 SQLite SQL 注入 15 | - 模拟安全支付流程:截屏、防窥、输入保护等场景演练 16 | - 导出的 `SecretAbility` 页面可被外部组件直接拉起 17 | - 隐式 Want 泄露/劫持:未绑定包名组件被任意 App 截获 18 | - 剪贴板后台轮询窃取(权限因等级限制默认不申请,保留 UI 演示) 19 | 20 | ## 项目结构 21 | 22 | ``` 23 | ├── AppScope/app.json5 # 全局应用信息 24 | ├── entry/ # 主模块 25 | │ ├── src/main/ets/pages/ # 各漏洞示例页面(HTTP/SQL注入/硬编码/隐式Want/剪贴板等) 26 | │ ├── src/main/resources/ # UI 资源 27 | │ ├── src/main/ets/entryability/ # 主/导出/隐式 Want 能力 28 | │ ├── build-profile.json5 # (模板)构建配置 29 | │ └── hvigorfile.ts # 模块级构建脚本 30 | ├── docs/signing-setup.md # 本地签名材料操作指南 31 | └── hvigorfile.ts # 应用级构建脚本 32 | ``` 33 | 34 | ## 快速开始 35 | 36 | ### 环境要求 37 | 38 | - DevEco Studio 5.0 及以上(HarmonyOS NEXT 工具链) 39 | - DevEco Studio 自带的 Node.js / Hvigor 工具 40 | - HarmonyOS NEXT 真机或模拟器,API ≥ 17(5.0.5) 41 | 42 | ## 签名说明 43 | 44 | 仓库不会保存任何真实证书或密码。若需发布专用 HAP,请参考 [`docs/signing-setup.md`](docs/signing-setup.md),将个人签名信息写入本地的 `build-profile.local.json5`。 45 | 46 | ## 学习与实战建议 47 | 48 | - 反编译生成的 HAP,尝试定位 ArkTS 和 `.so` 里的硬编码密钥。 49 | - 通过代理劫持 HTTP 流量,观察未加密通信的风险。 50 | - 在 SQLite 演示页面输入 SQL 注入 payload,体验攻击链。 51 | - 使用 `aa start -b com.example.vulnerableappdemo -a SecretAbility` 手动拉起导出的能力,理解组件暴露问题。 52 | - 隐式 Want 演示:若无第三方匹配组件,会降级显式拉起本应用;安装自定义“攻击者” HAP 可真实劫持。 53 | - 剪贴板演示:界面保留轮询逻辑,READ_PASTEBOARD 属 system_basic(需 ACL),默认不申请以便 normal APL 安装。 54 | 55 | ## 演示截图 56 | - 主界面: image 57 | 58 | - 隐式 Want 劫持:image 59 | 60 | - so原生密钥+SM4 加密:image 61 | 62 | - sql 注入漏洞:image 63 | 64 | 65 | ## 免责声明 66 | 67 | 本项目面向实验室、培训和 CTF 等学习场景。作者不对滥用带来的任何后果负责。在测试第三方软件时请遵循负责任的披露原则。 68 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/InsecureStoragePage.ets: -------------------------------------------------------------------------------- 1 | import dataPreferences from '@ohos.data.preferences'; 2 | import hilog from '@ohos.hilog'; 3 | import promptAction from '@ohos.promptAction'; 4 | 5 | @Entry 6 | @Component 7 | struct InsecureStoragePage { 8 | @State username: string = 'testuser'; 9 | @State password: string = 'Password123'; 10 | private context = getContext(this); 11 | 12 | async onSaveClick() { 13 | try { 14 | const pref = await dataPreferences.getPreferences(this.context, 'vuln_credential'); 15 | 16 | // VULNERABILITY: 明文存储用户名和密码 17 | await pref.put('username', this.username); 18 | await pref.put('password', this.password); 19 | await pref.flush(); 20 | 21 | // VULNERABILITY: 在日志中明文打印敏感信息 22 | hilog.error(0x0001, 'VULN_APP_LOG', 'Insecure Logging: User %{public}s password is %{public}s', this.username, this.password); 23 | const fakeSessionToken = 'Bearer_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InRlc3R1c2VyIn0.fake_signature'; 24 | hilog.error(0x0001, 'VULN_APP_LOG', 'Login success for %{public}s, password: %{public}s, session: %{public}s', this.username, this.password, fakeSessionToken); 25 | 26 | // Secure version (示例): 使用加密存储 + 不打印敏感信息 27 | // const encPref = await dataPreferences.getPreferences(this.context, 'secure_credential'); 28 | // await encPref.put('username', encrypt(this.username)); 29 | // await encPref.put('password', encrypt(this.password)); 30 | // await encPref.flush(); 31 | // hilog.info(0x0001, 'SAFE_LOG', 'Login success for %{private}s', this.username); 32 | 33 | promptAction.showToast({ message: '凭据已明文保存,并已打印到日志' }); 34 | } catch (e) { 35 | promptAction.showToast({ message: `保存失败: ${e}` }); 36 | } 37 | } 38 | 39 | build() { 40 | Column({ space: 20 }) { 41 | Text('不安全的数据存储 & 日志').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 42 | 43 | TextInput({ placeholder: '用户名', text: this.username }).onChange(val => this.username = val) 44 | TextInput({ 45 | placeholder: '密码', 46 | text: this.password 47 | }) 48 | .type(InputType.Password) 49 | .onChange(val => this.password = val) 50 | 51 | Button('保存凭据 (明文)') 52 | .onClick(() => this.onSaveClick()) 53 | 54 | Text('操作指引:\n1. 点击保存后,请进入设备shell环境。\n2. 找到应用沙箱目录:/data/storage/el2/base/haps/entry/preferences。\n3. 查看 vuln_credential.xml 文件内容。\n4. 同时使用 hilog 命令查看日志输出。') 55 | .padding(15) 56 | .backgroundColor('#f0f0f0') 57 | .borderRadius(10) 58 | .margin({ top: 30 }) 59 | } 60 | .padding(20) 61 | .width('100%') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /entry/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone" 9 | ], 10 | "deliveryWithInstall": true, 11 | "installationFree": false, 12 | "pages": "$profile:main_pages", 13 | "abilities": [ 14 | { 15 | "name": "EntryAbility", 16 | "srcEntry": "./ets/entryability/EntryAbility.ets", 17 | "description": "$string:EntryAbility_desc", 18 | "icon": "$media:icon", 19 | "label": "$string:EntryAbility_label", 20 | "startWindowIcon": "$media:icon", 21 | "startWindowBackground": "$color:start_window_background", 22 | "exported": true, 23 | "skills": [ 24 | { 25 | "entities": [ 26 | "entity.system.home" 27 | ], 28 | "actions": [ 29 | "ohos.want.action.home" 30 | ] 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "SecretAbility", 36 | "srcEntry": "./ets/entryability/SecretAbility.ets", 37 | "description": "Exported Secret Ability", 38 | "label": "$string:secret_ability_label", 39 | "exported": true, // 漏洞:组件被导出 40 | // Secure version (示例): "exported": false, 或限制 skills/权限验证后再暴露 41 | "launchType": "standard", 42 | "startWindowIcon": "$media:icon", 43 | "startWindowBackground": "$color:start_window_background" 44 | }, 45 | { 46 | "name": "LeakyImplicitAbility", 47 | "srcEntry": "./ets/entryability/LeakyImplicitAbility.ets", 48 | "description": "Handles implicit Want and leaks extras", 49 | "label": "$string:EntryAbility_label", 50 | "exported": true, 51 | // Secure version (示例): 指定权限或校验调用方,或设置 exported 为 false 并仅内部调用 52 | "launchType": "standard", 53 | "startWindowIcon": "$media:icon", 54 | "startWindowBackground": "$color:start_window_background", 55 | "skills": [ 56 | { 57 | "actions": [ 58 | "com.example.SECRET_SHARE" 59 | ], 60 | "entities": [ 61 | "entity.system.default" 62 | ] 63 | } 64 | ] 65 | } 66 | ], 67 | "requestPermissions": [ 68 | { 69 | "name": "ohos.permission.INTERNET" 70 | } 71 | // 剪贴板窃取演示原计划需要 READ_PASTEBOARD(system_basic 级别,user_grant,需 ACL 申请)。 72 | // 为避免 normal 等级安装失败,权限暂注释,功能仅保留界面演示。 73 | // , 74 | // { 75 | // "name": "ohos.permission.READ_PASTEBOARD", 76 | // "reason": "$string:permission_read_pasteboard_reason", 77 | // "usedScene": { 78 | // "abilities": [ 79 | // "EntryAbility" 80 | // ], 81 | // "when": "inuse" 82 | // } 83 | // } 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/WebViewPage.ets: -------------------------------------------------------------------------------- 1 | import web from '@ohos.web.webview'; 2 | import { BusinessError } from '@kit.BasicServicesKit'; 3 | 4 | // VULNERABILITY: 暴露给JS的原生对象 5 | class JsBridge { 6 | getSensitiveInfo(): string { 7 | // 模拟返回敏感信息 8 | return `设备SN: MOCK_SN_1234567890, API_Level: ${globalThis.ohos_api_version}`; 9 | } 10 | } 11 | 12 | @Entry 13 | @Component 14 | struct WebViewPage { 15 | @State urlInput: string = 'https://www.bing.com'; 16 | webviewController: web.WebviewController = new web.WebviewController(); 17 | 18 | aboutToAppear() { 19 | try { 20 | // VULNERABILITY: 向全部页面暴露原生对象 21 | this.webviewController.registerJavaScriptProxy(new JsBridge(), 'nativeBridge', ['*']); 22 | // Secure version (示例): 仅在可信域名时注册,并限制方法白名单 23 | // if (this.urlInput.startsWith('https://trusted.example.com')) { 24 | // this.webviewController.registerJavaScriptProxy(new JsBridge(), 'nativeBridge', ['getSensitiveInfo']); 25 | // } 26 | } catch (error) { 27 | const err = error as BusinessError; 28 | console.error(`registerJavaScriptProxy failed, code is ${err.code}, message is ${err.message}`); 29 | } 30 | } 31 | 32 | build() { 33 | Column() { 34 | Text('WebView 漏洞集合').fontSize(24).fontWeight(FontWeight.Bold).padding({ top: 20, bottom: 10 }) 35 | 36 | Row({ space: 10 }) { 37 | TextInput({ placeholder: '输入任意URL并加载', text: this.urlInput }) 38 | .onChange(val => this.urlInput = val) 39 | .layoutWeight(1) 40 | Button('加载') 41 | .onClick(() => { 42 | // VULNERABILITY: 未校验 URL,直接加载 43 | this.webviewController.loadUrl(this.urlInput); 44 | // Secure version (示例): 验证协议/域名,仅允许 https 白名单 45 | // if (this.urlInput.startsWith('https://trusted.example.com')) { 46 | // this.webviewController.loadUrl(this.urlInput); 47 | // } 48 | }) 49 | }.width('90%').padding({ bottom: 10 }) 50 | 51 | Web({ src: $rawfile('vuln.html'), controller: this.webviewController }) 52 | .javaScriptAccess(true) 53 | .onControllerAttached(() => { 54 | // VULNERABILITY: 注册 JS 桥接供任意页面调用 55 | this.webviewController.registerJavaScriptProxy( 56 | new JsBridge(), 57 | 'nativeBridge', 58 | ['getSensitiveInfo'] // 方法名写全 59 | ); 60 | // Secure version (示例): 仅对受信页面开放或使用域名校验 61 | // if (this.webviewController.getHitTest() === 'https://trusted.example.com') { ... } 62 | }) 63 | .layoutWeight(1) 64 | 65 | Text('操作指引:\n1. H5页面已加载。\n2. 点击H5页面的按钮,它会通过JS调用原生代码获取信息。\n3. 尝试使用Chrome DevTools远程调试,或通过注入JS代码来调用 `nativeBridge.getSensitiveInfo()` 方法。\n4. 任意URL加载: 在上方输入框输入恶意网址或file://协议路径进行测试。') 66 | .padding(15) 67 | .backgroundColor('#f0f0f0') 68 | .borderRadius(10) 69 | .margin(20) 70 | } 71 | .width('100%').height('100%') 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import router from '@ohos.router'; 2 | import promptAction from '@ohos.promptAction'; 3 | import { BusinessError } from '@kit.BasicServicesKit'; 4 | 5 | interface VulnerabilityItem { 6 | title: string; 7 | description: string; 8 | targetPage: string; 9 | } 10 | 11 | @Entry 12 | @Component 13 | struct Index { 14 | // 更新漏洞列表 15 | private vulnerabilityList: VulnerabilityItem[] = [ 16 | { title: '1. 不安全的数据存储 & 日志', description: '明文存储账号密码到Preferences', targetPage: 'pages/InsecureStoragePage' }, 17 | { title: '2. 不安全的网络通信', description: '使用HTTP明文传输数据', targetPage: 'pages/InsecureNetworkPage' }, 18 | { title: '3. 硬编码敏感信息', description: '密钥硬编码在ArkTS代码中', targetPage: 'pages/HardcodedKeyPage' }, 19 | { title: '4. WebView 漏洞集合', description: 'JS-Bridge漏洞 & 任意URL加载', targetPage: 'pages/WebViewPage' }, 20 | { title: '5. SO原生库漏洞', description: '密钥硬编码在.so文件中', targetPage: 'pages/NdkPage' }, 21 | { title: '6. SQLite SQL注入', description: 'SQL查询语句拼接用户输入', targetPage: 'pages/SqlInjectionPage' }, 22 | { title: '7. 模拟支付', description: '防截屏、安全键盘等', targetPage: 'pages/SecurePaymentPage' }, 23 | { title: '8. 导出的组件 (页面劫持)', description: '一个包含敏感信息的页面被导出', targetPage: '#' }, 24 | { title: '9. 隐式 Want 泄露与劫持', description: '未绑定包名的隐式Want被任意App截获', targetPage: 'pages/ImplicitHijackPage' }, 25 | { title: '10. 剪贴板劫持', description: '后台轮询剪贴板窃取敏感信息', targetPage: 'pages/ClipboardLeakPage' } 26 | ]; 27 | 28 | build() { 29 | Row() { 30 | Column() { 31 | Text('HarmonyOS 安全靶场') 32 | .fontSize(30) 33 | .fontWeight(FontWeight.Bold) 34 | .padding({ top: 20, bottom: 20 }) 35 | 36 | List({ space: 10 }) { 37 | ForEach(this.vulnerabilityList, (item: VulnerabilityItem) => { 38 | ListItem() { 39 | this.buildListItem(item.title, item.description) 40 | } 41 | .onClick(() => { 42 | if (item.targetPage === '#') { 43 | this.showDialog('组件暴露漏洞说明', '本应用内有一个名为 SecretAbility 的隐藏页面已被导出。请使用 hdc 命令尝试拉起它:\n\naa start -b com.example.vulnerableappdemo -a SecretAbility'); 44 | } else { 45 | router.pushUrl({ url: item.targetPage }); 46 | } 47 | }) 48 | }) 49 | } 50 | .width('90%') 51 | .layoutWeight(1) 52 | } 53 | .width('100%') 54 | } 55 | .height('100%') 56 | } 57 | 58 | // 抽离出Dialog显示函数 59 | showDialog(title: string, message: string) { 60 | try { 61 | promptAction.showDialog({ 62 | title: title, 63 | message: message, 64 | buttons: [{ text: '好的', color: '#007DFF' }] 65 | }); 66 | } catch (error) { 67 | const err = error as BusinessError; 68 | console.error(`showDialog failed, code is ${err.code}, message is ${err.message}`); 69 | } 70 | } 71 | 72 | @Builder 73 | buildListItem(title: string, description: string) { 74 | Column() { 75 | Text(title).fontSize(18).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Start) 76 | Text(description).fontSize(14).fontColor(Color.Gray).width('100%').textAlign(TextAlign.Start).margin({ top: 5 }) 77 | } 78 | .padding(15).backgroundColor(Color.White).borderRadius(10).shadow({ radius: 5, color: '#1F000000' }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/ImplicitHijackPage.ets: -------------------------------------------------------------------------------- 1 | import { BusinessError } from '@kit.BasicServicesKit'; 2 | import { Want, common } from '@kit.AbilityKit'; 3 | 4 | const IMPLICIT_ACTION = 'com.example.SECRET_SHARE'; 5 | 6 | @Entry 7 | @Component 8 | struct ImplicitHijackPage { 9 | @State sendLog: string = '未发送'; 10 | 11 | private buildLeakWant(): Want { 12 | return { 13 | action: IMPLICIT_ACTION, 14 | type: 'text/plain', 15 | // VULNERABILITY: 不指定 bundle/ability,敏感数据随隐式 Want 外泄 16 | parameters: { 17 | authToken: 'demo-token-77e1-45c8-90cc', 18 | location: '31.2304,121.4737', 19 | note: '私有通道消息,将被任意 App 劫持' 20 | } 21 | }; 22 | } 23 | // Secure version (示例): 显式指定 bundle/ability 或使用权限校验 24 | // private buildSecureWant(): Want { 25 | // return { 26 | // bundleName: 'com.example.vulnerableappdemo', 27 | // abilityName: 'SecureReceiverAbility', 28 | // action: IMPLICIT_ACTION, 29 | // parameters: { ... } 30 | // }; 31 | // } 32 | 33 | private startImplicitShare() { 34 | const ctx = getContext(this) as common.UIAbilityContext; 35 | const want = this.buildLeakWant(); 36 | ctx.startAbility(want).then(() => { 37 | this.sendLog = `已发送隐式 Want(action=${IMPLICIT_ACTION}),若有同 action 的第三方将被劫持`; 38 | }).catch((error: BusinessError) => { 39 | // 若无匹配组件,退化为显式拉起自身能力以便演示 40 | if (error.code === 16000019) { 41 | const fallback: Want = { 42 | bundleName: 'com.example.vulnerableappdemo', 43 | abilityName: 'LeakyImplicitAbility', 44 | action: IMPLICIT_ACTION, 45 | parameters: want.parameters 46 | }; 47 | ctx.startAbility(fallback).then(() => { 48 | this.sendLog = '隐式匹配失败,已用显式方式拉起本应用演示页面(正常情况下应被其它 App 劫持)'; 49 | }).catch((inner: BusinessError) => { 50 | this.sendLog = `隐式+显式均失败: ${inner.code} ${inner.message}`; 51 | }); 52 | } else { 53 | this.sendLog = `发送失败: ${error.code} ${error.message}`; 54 | } 55 | }); 56 | } 57 | 58 | build() { 59 | Column() { 60 | Text('隐式 Want 泄露 / 劫持') 61 | .fontSize(24) 62 | .fontWeight(FontWeight.Bold) 63 | .padding({ top: 20, bottom: 10 }) 64 | 65 | Column({ space: 8 }) { 66 | Text('敏感数据样例(未绑定包名/组件,默认走隐式路由):') 67 | .fontSize(14) 68 | .fontColor(Color.Grey) 69 | Text(`action: ${IMPLICIT_ACTION}`) 70 | .fontSize(14) 71 | .fontColor(Color.Red) 72 | Text(JSON.stringify(this.buildLeakWant().parameters, null, 2)) 73 | .fontSize(12) 74 | .backgroundColor('#f6f6f6') 75 | .padding(10) 76 | .borderRadius(8) 77 | } 78 | .width('90%') 79 | .padding({ bottom: 12 }) 80 | 81 | Button('发送隐式 Want(可被劫持)') 82 | .backgroundColor('#d9534f') 83 | .fontColor(Color.White) 84 | .onClick(() => this.startImplicitShare()) 85 | .width('90%') 86 | .padding({ top: 10, bottom: 10 }) 87 | 88 | Text(this.sendLog) 89 | .fontSize(14) 90 | .fontColor('#d9534f') 91 | .width('90%') 92 | .padding({ top: 10, bottom: 10 }) 93 | 94 | Text('复现提示:\n1)当前 App 内置导出的 LeakyImplicitAbility 会截获该 Want 并显示参数;\n2)若系统提示“暂无可用打开方式”,会自动降级为显式拉起本应用;\n3)安装“攻击者” HAP 或用 aa start -a com.example.SECRET_SHARE -b <恶意包名> 劫持,观察敏感数据外泄。') 95 | .fontSize(14) 96 | .fontColor(Color.Gray) 97 | .padding(15) 98 | .backgroundColor('#f0f0f0') 99 | .borderRadius(10) 100 | .width('90%') 101 | .margin({ top: 10 }) 102 | } 103 | .width('100%') 104 | .height('100%') 105 | .justifyContent(FlexAlign.Start) 106 | .alignItems(HorizontalAlign.Center) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/ClipboardLeakPage.ets: -------------------------------------------------------------------------------- 1 | import { BusinessError, pasteboard } from '@kit.BasicServicesKit'; 2 | 3 | @Entry 4 | @Component 5 | struct ClipboardLeakPage { 6 | @State sensitiveText: string = '卡号 4111 1111 1111 1111, CVV 888, OTP 123456'; 7 | @State hijackLog: string[] = ['未监听剪贴板']; 8 | @State lastSeen: string = '空'; 9 | private pollTimer: number = -1; 10 | private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); 11 | 12 | aboutToDisappear() { 13 | this.stopSniff(); 14 | } 15 | 16 | private pushLog(msg: string) { 17 | const next = [...this.hijackLog, msg]; 18 | this.hijackLog = next.slice(-6); 19 | } 20 | 21 | private async copySensitive() { 22 | try { 23 | const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.sensitiveText); 24 | await this.systemPasteboard.setData(data); 25 | this.pushLog('已将敏感信息写入剪贴板(未做清理)。'); 26 | } catch (error) { 27 | const err = error as BusinessError; 28 | this.pushLog(`写入失败: ${err.code} ${err.message}`); 29 | } 30 | } 31 | 32 | private async readClipboardOnce() { 33 | try { 34 | const data = await this.systemPasteboard.getData(); 35 | const record = data.getRecord(0); 36 | const text = record ? record.toPlainText() : '<空>'; 37 | this.lastSeen = text ?? '<空>'; 38 | this.pushLog(`被动窃取剪贴板: ${this.lastSeen}`); 39 | } catch (error) { 40 | const err = error as BusinessError; 41 | this.pushLog(`读取失败: ${err.code} ${err.message}`); 42 | } 43 | } 44 | 45 | private startSniff() { 46 | if (this.pollTimer !== -1) { 47 | return; 48 | } 49 | // VULNERABILITY: 后台轮询剪贴板,未经用户授权 50 | this.pushLog('开始后台轮询剪贴板(无用户感知)...'); 51 | this.pollTimer = setInterval(() => { 52 | this.readClipboardOnce(); 53 | }, 2500); 54 | } 55 | 56 | private stopSniff() { 57 | if (this.pollTimer !== -1) { 58 | clearInterval(this.pollTimer); 59 | this.pollTimer = -1; 60 | this.pushLog('停止监听。'); 61 | } 62 | } 63 | 64 | build() { 65 | Column() { 66 | Text('剪贴板劫持与敏感信息泄露') 67 | .fontSize(24) 68 | .fontWeight(FontWeight.Bold) 69 | .padding({ top: 20, bottom: 10 }) 70 | 71 | Text('步骤:复制敏感信息 → 驻后台轮询读取 → 日志/上传(这里用日志模拟)。') 72 | .fontSize(14) 73 | .fontColor(Color.Gray) 74 | .width('90%') 75 | .padding({ bottom: 10 }) 76 | 77 | Column({ space: 8 }) { 78 | Text('敏感文本样例(可编辑):').fontSize(14).fontColor(Color.Grey) 79 | TextInput({ text: this.sensitiveText, placeholder: '输入要复制的敏感数据' }) 80 | .width('100%') 81 | .onChange(val => this.sensitiveText = val) 82 | .padding(10) 83 | .backgroundColor('#f6f6f6') 84 | .borderRadius(8) 85 | }.width('90%') 86 | 87 | Row({ space: 12 }) { 88 | Button('复制到剪贴板') 89 | .onClick(() => this.copySensitive()) 90 | .backgroundColor('#d9534f') 91 | .fontColor(Color.White) 92 | Button('开始后台监听') 93 | .onClick(() => this.startSniff()) 94 | Button('停止监听') 95 | .onClick(() => this.stopSniff()) 96 | }.padding({ top: 12, bottom: 12 }) 97 | // Secure version (示例): 仅在用户明确触发时读取剪贴板,并及时清空 98 | // Button('读取一次并清空') 99 | // .onClick(async () => { 100 | // const data = await this.systemPasteboard.getData(); 101 | // await this.systemPasteboard.clear(); 102 | // }) 103 | 104 | Column({ space: 6 }) { 105 | Text(`最近一次读取:${this.lastSeen}`).fontSize(14).fontColor(Color.Red) 106 | Text('日志(模拟外传记录):').fontSize(14).fontColor(Color.Grey) 107 | ForEach(this.hijackLog, (line: string) => { 108 | Text(line).fontSize(12).width('100%') 109 | }) 110 | } 111 | .width('90%') 112 | .padding(12) 113 | .backgroundColor('#f0f0f0') 114 | .borderRadius(10) 115 | } 116 | .width('100%') 117 | .height('100%') 118 | .justifyContent(FlexAlign.Start) 119 | .alignItems(HorizontalAlign.Center) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/SqlInjectionPage.ets: -------------------------------------------------------------------------------- 1 | import relationalStore from '@ohos.data.relationalStore'; 2 | import promptAction from '@ohos.promptAction'; 3 | import hilog from '@ohos.hilog'; 4 | 5 | const DB_NAME = 'vuln_user.db'; 6 | const TABLE_NAME = 'users'; 7 | const TAG = '[SQL_INJECTION]'; 8 | 9 | @Entry 10 | @Component 11 | struct SqlInjectionPage { 12 | @State searchName: string = ''; 13 | @State searchResult: string = '尚未查询'; 14 | private store: relationalStore.RdbStore | null = null; 15 | 16 | async aboutToAppear() { 17 | // 初始化数据库并插入数据 18 | try { 19 | const config: relationalStore.StoreConfig = { 20 | name: DB_NAME, 21 | securityLevel: relationalStore.SecurityLevel.S1 // S1表示设备级加密 22 | }; 23 | this.store = await relationalStore.getRdbStore(getContext(this), config); 24 | hilog.info(0x0001, TAG, '数据库获取成功'); 25 | 26 | // 创建表 27 | const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT)`; 28 | await this.store.executeSql(CREATE_TABLE_SQL); 29 | hilog.info(0x0001, TAG, '表创建成功'); 30 | 31 | // 清空并插入预置数据 32 | await this.store.executeSql(`DELETE FROM ${TABLE_NAME}`); 33 | await this.store.executeSql(`INSERT INTO ${TABLE_NAME}(name, description) VALUES ('admin', '系统管理员, 掌握最高权限')`); 34 | await this.store.executeSql(`INSERT INTO ${TABLE_NAME}(name, description) VALUES ('alice', '普通用户, 财务部门')`); 35 | await this.store.executeSql(`INSERT INTO ${TABLE_NAME}(name, description) VALUES ('bob', '普通用户, 技术部门')`); 36 | hilog.info(0x0001, TAG, '预置数据插入成功'); 37 | 38 | } catch (e) { 39 | this.searchResult = `数据库初始化失败: ${JSON.stringify(e)}`; 40 | hilog.error(0x0001, TAG, `数据库初始化失败: ${JSON.stringify(e)}`); 41 | } 42 | } 43 | 44 | async onSearch() { 45 | if (!this.store) { 46 | promptAction.showToast({ message: '数据库未准备好' }); 47 | return; 48 | } 49 | try { 50 | // VULNERABILITY: 直接将用户输入拼接到SQL语句中 51 | const vulnerableSql = `SELECT * FROM ${TABLE_NAME} WHERE name = '${this.searchName}'`; 52 | hilog.info(0x0001, TAG, `Executing vulnerable SQL: ${vulnerableSql}`); 53 | 54 | const resultSet = await this.store.querySql(vulnerableSql); 55 | let resultStr = `查询到 ${resultSet.rowCount} 条结果:\n`; 56 | if (resultSet.rowCount > 0) { 57 | resultSet.goToFirstRow(); 58 | do { 59 | const id = resultSet.getLong(resultSet.getColumnIndex('id')); 60 | const name = resultSet.getString(resultSet.getColumnIndex('name')); 61 | const desc = resultSet.getString(resultSet.getColumnIndex('description')); 62 | resultStr += `ID: ${id}, Name: ${name}, Desc: ${desc}\n`; 63 | } while (resultSet.goToNextRow()); 64 | } 65 | this.searchResult = resultStr; 66 | resultSet.close(); 67 | 68 | // Secure version (示例): 使用参数化查询避免注入 69 | // const stmt = await this.store.prepare(`SELECT * FROM ${TABLE_NAME} WHERE name = ?`); 70 | // const safeResult = await stmt.executeQuery([this.searchName]); 71 | // stmt.close(); 72 | } catch (e) { 73 | this.searchResult = `查询失败: ${JSON.stringify(e)}`; 74 | hilog.error(0x0001, TAG, `查询失败: ${JSON.stringify(e)}`); 75 | } 76 | } 77 | 78 | build() { 79 | Column({ space: 20 }) { 80 | Text('SQLite SQL注入漏洞').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 81 | 82 | TextInput({ placeholder: '输入用户名搜索', text: this.searchName }) 83 | .onChange(val => this.searchName = val) 84 | 85 | Button('搜索') 86 | .onClick(() => this.onSearch()) 87 | 88 | Scroll() { 89 | Text(this.searchResult) 90 | .width('100%') 91 | .padding(10) 92 | .backgroundColor('#f0f0f0') 93 | .borderRadius(10) 94 | }.layoutWeight(1) 95 | 96 | Text("操作指引:\n在输入框中尝试输入 `' OR '1'='1` 并点击搜索,观察是否能查询出所有用户信息。") 97 | .padding(15) 98 | .backgroundColor('#f0f0f0') 99 | .borderRadius(10) 100 | .margin({ top: 20 }) 101 | } 102 | .padding(20).width('100%') 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/NdkPage.ets: -------------------------------------------------------------------------------- 1 | import entry from 'libentry.so'; 2 | import cryptoFramework from '@ohos.security.cryptoFramework'; 3 | 4 | @Entry 5 | @Component 6 | struct NdkPage { 7 | @State nativeKey: string = '点击按钮获取'; 8 | @State errorMessage: string = ''; 9 | @State inputText: string = ''; 10 | @State encryptedText: string = ''; 11 | 12 | // 使用 HarmonyOS 加密框架 SM4(ECB/PKCS7) 加密并返回 HEX 字符串 13 | private encryptWithKey(plain: string, key: string): string { 14 | if (!key) { 15 | return ''; 16 | } 17 | const keyBytes = this.normalizeKeyTo16Bytes(this.stringToBytes(key)); 18 | const plainBytes = this.stringToBytes(plain); 19 | try { 20 | let symGen: cryptoFramework.SymKeyGenerator; 21 | try { 22 | symGen = cryptoFramework.createSymKeyGenerator('SM4_128'); 23 | } catch (e1) { 24 | try { 25 | symGen = cryptoFramework.createSymKeyGenerator('SM4'); 26 | } catch (e2) { 27 | this.errorMessage = `SM4算法不可用: ${e2}`; 28 | return ''; 29 | } 30 | } 31 | const symKey = symGen.convertKeySync({ data: new Uint8Array(keyBytes) }); 32 | let cipher: cryptoFramework.Cipher; 33 | try { 34 | cipher = cryptoFramework.createCipher('SM4|ECB|PKCS7'); 35 | } catch (e) { 36 | // 兼容部分版本仅支持 PKCS5 的命名 37 | cipher = cryptoFramework.createCipher('SM4|ECB|PKCS5'); 38 | } 39 | cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, null); 40 | const outBlob: cryptoFramework.DataBlob = cipher.doFinalSync({ data: new Uint8Array(plainBytes) }); 41 | return this.bytesToHex(Array.from(outBlob.data)); 42 | } catch (e) { 43 | this.errorMessage = `SM4加密失败: ${e}`; 44 | return ''; 45 | } 46 | } 47 | 48 | private stringToBytes(str: string): number[] { 49 | const bytes: number[] = []; 50 | for (let i = 0; i < str.length; i++) { 51 | // 简化处理:仅取低8位,适用于演示 52 | bytes.push(str.charCodeAt(i) & 0xff); 53 | } 54 | return bytes; 55 | } 56 | 57 | private bytesToHex(bytes: number[]): string { 58 | const hex = '0123456789abcdef'; 59 | let out = ''; 60 | for (let i = 0; i < bytes.length; i++) { 61 | const b = bytes[i] & 0xff; 62 | out += hex.charAt((b >>> 4) & 0x0f) + hex.charAt(b & 0x0f); 63 | } 64 | return out; 65 | } 66 | 67 | private normalizeKeyTo16Bytes(src: number[]): number[] { 68 | const out = new Array(16).fill(0); 69 | for (let i = 0; i < 16; i++) { 70 | out[i] = src[i % src.length] & 0xff; 71 | } 72 | return out; 73 | } 74 | 75 | build() { 76 | Column({ space: 20 }) { 77 | Text('SO原生库漏洞').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 }) 78 | 79 | Button('从.so文件获取密钥') 80 | .onClick(() => { 81 | try { 82 | // VULNERABILITY: 密钥硬编码在 .so 中 83 | this.nativeKey = entry.getSecretKeyFromNative(); 84 | this.errorMessage = ''; 85 | } catch (error) { 86 | console.error('调用native方法失败:', error); 87 | this.errorMessage = `错误: ${error}`; 88 | } 89 | }) 90 | // Secure version (示例): 将密钥存入系统 KeyStore,并按需解密/拉取 91 | // .onClick(async () => { 92 | // this.nativeKey = await keyStore.get('secure_key_alias'); 93 | // }) 94 | 95 | Text('从原生库获取到的密钥是:') 96 | Text(this.nativeKey) 97 | .fontColor(Color.Red) 98 | .fontSize(18) 99 | 100 | // 明文输入与加密操作 101 | Text('输入要加密的内容:') 102 | TextInput({ placeholder: '请输入明文...' }) 103 | .width('100%') 104 | .onChange((val: string) => { 105 | this.inputText = val; 106 | }) 107 | 108 | Button('使用SM4加密') 109 | .onClick(() => { 110 | try { 111 | if (!this.nativeKey || this.nativeKey === '点击按钮获取') { 112 | this.errorMessage = '请先获取密钥'; 113 | return; 114 | } 115 | if (!this.inputText || this.inputText.length === 0) { 116 | this.errorMessage = '请输入明文'; 117 | return; 118 | } 119 | const result: string = this.encryptWithKey(this.inputText, this.nativeKey); 120 | if (result && result.length > 0) { 121 | this.encryptedText = result; 122 | this.errorMessage = ''; 123 | } else { 124 | // encryptWithKey 内部已设置错误信息,这里不清空,给出兜底提示 125 | if (!this.errorMessage) { 126 | this.errorMessage = '加密失败'; 127 | } 128 | } 129 | } catch (e) { 130 | console.error('加密失败:', e); 131 | this.errorMessage = `加密错误: ${e}`; 132 | } 133 | }) 134 | 135 | if (this.encryptedText) { 136 | Text('加密后的密文(HEX):') 137 | Text(this.encryptedText) 138 | .fontColor(Color.Blue) 139 | .fontSize(16) 140 | } 141 | 142 | if (this.errorMessage) { 143 | Text(this.errorMessage) 144 | .fontColor(Color.Orange) 145 | .fontSize(14) 146 | } 147 | 148 | Text('操作指引:\n请反编译本应用的HAP包,找到 lib/arm64-v8a/libentry.so 文件。使用IDA, Ghidra等工具分析该文件,即可找到硬编码的密钥字符串。\n\n注意:在HarmonyOS Next中,需要在oh-package.json5中正确配置native模块。') 149 | .padding(15) 150 | .backgroundColor('#f0f0f0') 151 | .borderRadius(10) 152 | .margin({ top: 30 }) 153 | } 154 | .padding(20) 155 | .width('100%') 156 | } 157 | } 158 | --------------------------------------------------------------------------------