HarmonyOS开发:安全代码扫描
HarmonyOS开发:安全代码扫描
📌 核心要点:安全漏洞不是"以后再修"的问题——硬编码密钥、SQL注入、未加密传输,每一个都是等着被利用的定时炸弹。
背景与动机
你有没有在代码里写过这种东西?
const API_KEY = 'sk-abc123def456'; // 先这么写,上线前再改
const DB_PASSWORD = 'admin123'; // 测试环境密码,无所谓
“上线前再改”——这句话你信吗?上线前永远有更紧急的事,密码就这么明晃晃地上了生产环境。然后某天代码仓库泄露了,或者反编译APK就能看到这些字符串,你的系统就裸奔了。
安全漏洞跟普通Bug不一样。普通Bug影响的是用户体验,安全漏洞影响的是用户数据和企业声誉。一个SQL注入漏洞,可能让整个数据库被拖走;一个硬编码密钥,可能让所有加密形同虚设。
鸿蒙应用的安全要求更严格。华为应用市场有安全审核,发现硬编码密钥、未加密传输等问题直接拒绝上架。你辛辛苦苦开发了几个月,因为安全问题被拒,值得吗?
安全扫描的核心价值:在代码合入之前发现安全漏洞,而不是在安全事件之后打补丁。
核心原理
安全扫描的检查维度
graph TD
A[安全代码扫描] --> B[敏感信息泄露]
A --> C[注入攻击]
A --> D[数据安全]
A --> E[通信安全]
A --> F[权限安全]
B --> B1[硬编码密钥/密码]
B --> B2[日志打印敏感数据]
B --> B3[调试信息未清理]
C --> C1[SQL注入]
C --> C2[命令注入]
C --> C3[路径遍历]
D --> D1[明文存储敏感数据]
D --> D2[加密算法不安全]
D --> D3[密钥管理不当]
E --> E1[HTTP明文传输]
E --> E2[证书校验缺失]
E --> E3[中间人攻击风险]
F --> F1[权限过度申请]
F --> F2[权限绕过]
F --> F3[敏感操作无鉴权]
classDef mainStyle fill:#FF6B6B,stroke:#C0392B,color:#fff,font-weight:bold
classDef subStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
classDef detailStyle fill:#FFE66D,stroke:#F39C12,color:#333
class A mainStyle
class B,C,D,E,F subStyle
class B1,B2,B3,C1,C2,C3,D1,D2,D3,E1,E2,E3,F1,F2,F3 detailStyle
安全扫描流程
graph LR
A[源代码] --> B[模式匹配扫描]
A --> C[数据流分析]
A --> D[依赖漏洞扫描]
B --> B1[正则匹配敏感信息]
B --> B2[危险函数调用检测]
C --> C1[污点追踪]
C --> C2[数据流向分析]
D --> D1[已知CVE检查]
D --> D2[许可证合规]
B1 --> E[安全报告]
B2 --> E
C1 --> E
C2 --> E
D1 --> E
D2 --> E
E --> F{是否通过?}
F -->|否| G[阻断合并/构建]
F -->|是| H[继续流程]
classDef sourceStyle fill:#A8E6CF,stroke:#2ECC71,color:#333
classDef scanStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
classDef resultStyle fill:#6C5CE7,stroke:#8E44AD,color:#fff
classDef decisionStyle fill:#FF6B6B,stroke:#E74C3C,color:#fff
class A sourceStyle
class B,C,D,B1,B2,C1,C2,D1,D2 scanStyle
class E resultStyle
class F,G,H decisionStyle
代码实战
基础用法:敏感信息泄露检测
最常见、最危险的安全问题——硬编码敏感信息:
// security-scanner.ts
// 敏感信息泄露检测器
interface SecurityFinding {
file: string;
line: number;
severity: 'critical' | 'high' | 'medium' | 'low';
category: string;
ruleId: string;
description: string;
recommendation: string;
}
class SensitiveDataScanner {
private findings: SecurityFinding[] = [];
// 敏感信息检测规则
private readonly patterns: Array<{
name: string;
pattern: RegExp;
severity: SecurityFinding['severity'];
category: string;
ruleId: string;
}> = [
// API密钥
{
name: 'API密钥',
pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][A-Za-z0-9]{20,}['"]/i,
severity: 'critical',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-001',
},
// 密码
{
name: '密码',
pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/i,
severity: 'critical',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-002',
},
// Token
{
name: '令牌',
pattern: /(?:token|access[_-]?token|secret)\s*[:=]\s*['"][A-Za-z0-9._-]{20,}['"]/i,
severity: 'critical',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-003',
},
// 私钥
{
name: '私钥',
pattern: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
severity: 'critical',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-004',
},
// 数据库连接串
{
name: '数据库连接串',
pattern: /(?:mysql|postgres|mongodb|redis):\/\/[^\s'"]+/i,
severity: 'high',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-005',
},
// AWS密钥
{
name: 'AWS密钥',
pattern: /AKIA[0-9A-Z]{16}/,
severity: 'critical',
category: '敏感信息泄露',
ruleId: 'SEC-KEY-006',
},
];
// 扫描文件
scanFile(content: string, filePath: string): SecurityFinding[] {
this.findings = [];
const lines = content.split('\n');
lines.forEach((line, index) => {
for (const rule of this.patterns) {
if (rule.pattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: rule.severity,
category: rule.category,
ruleId: rule.ruleId,
description: `检测到硬编码的${rule.name}`,
recommendation: `将${rule.name}移至环境变量或安全存储中`,
});
}
}
});
return this.findings;
}
}
// ==========================================
// 正确做法:使用安全的方式存储敏感信息
// ==========================================
// ❌ 错误:硬编码
const API_KEY_BAD = 'sk-abc123def456';
// ✅ 正确:使用环境变量或安全存储
import { huks } from '@kit.UniversalKeystore';
// 方案1:通过构建配置注入
// build-profile.json5中配置:
// "buildOption": {
// "arkOptions": {
// "compilerOptions": {
// "plugins": [
// {
// "name": "env-plugin",
// "options": {
// "API_KEY": "$(API_KEY)" // 从环境变量读取
// }
// }
// ]
// }
// }
// }
// 方案2:使用HUKS(通用密钥存储)保护密钥
async function secureKeyManagement(): Promise<void> {
// 生成密钥别名
const keyAlias = 'my_api_key';
// 初始化密钥(首次运行时)
const properties: huks.HuksOptions = {
properties: [
{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES },
{ tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 },
{ tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT |
huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT },
{ tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_PKCS7 },
{ tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_CBC },
],
inData: new Uint8Array(0),
};
// 生成密钥
await huks.generateKeyItem(keyAlias, properties);
}
进阶用法:注入攻击检测与数据安全检查
// injection-scanner.ts
// 注入攻击检测器
class InjectionScanner {
private findings: SecurityFinding[] = [];
// ==========================================
// SQL注入检测
// ==========================================
checkSqlInjection(content: string, filePath: string): void {
const lines = content.split('\n');
lines.forEach((line, index) => {
// 检测字符串拼接SQL
const sqlConcatPattern = /(?:SELECT|INSERT|UPDATE|DELETE|DROP).*\+\s*\w+/i;
if (sqlConcatPattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'critical',
category: 'SQL注入',
ruleId: 'SEC-INJ-001',
description: '检测到字符串拼接SQL,存在SQL注入风险',
recommendation: '使用参数化查询替代字符串拼接',
});
}
// 检测模板字符串SQL
const sqlTemplatePattern = /(?:SELECT|INSERT|UPDATE|DELETE|DROP).*\$\{/i;
if (sqlTemplatePattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'critical',
category: 'SQL注入',
ruleId: 'SEC-INJ-002',
description: '检测到模板字符串拼接SQL,存在SQL注入风险',
recommendation: '使用参数化查询替代模板字符串',
});
}
});
}
// ==========================================
// 路径遍历检测
// ==========================================
checkPathTraversal(content: string, filePath: string): void {
const lines = content.split('\n');
lines.forEach((line, index) => {
// 检测用户输入直接用于文件路径
const pathTraversalPattern = /(?:open|read|write|delete|stat)\s*\(\s*.*(?:\+|\$\{)/i;
if (pathTraversalPattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'high',
category: '路径遍历',
ruleId: 'SEC-INJ-003',
description: '用户输入可能直接用于文件路径,存在路径遍历风险',
recommendation: '对用户输入进行路径校验,禁止包含..和绝对路径',
});
}
});
}
// ==========================================
// 数据安全检查
// ==========================================
checkDataSecurity(content: string, filePath: string): void {
const lines = content.split('\n');
lines.forEach((line, index) => {
// 检测Preferences明文存储敏感数据
const prefsSensitivePattern = /preferences\.(put|set)\s*\(\s*['"]*(?:password|token|key|secret)/i;
if (prefsSensitivePattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'high',
category: '数据安全',
ruleId: 'SEC-DATA-001',
description: '检测到使用Preferences存储敏感数据,存在明文泄露风险',
recommendation: '敏感数据应加密后存储,或使用HUKS安全存储',
});
}
// 检测弱加密算法
const weakCryptoPattern = /(?:DES|RC4|MD5|SHA1)\s*\(/i;
if (weakCryptoPattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'high',
category: '数据安全',
ruleId: 'SEC-DATA-002',
description: '检测到使用弱加密/哈希算法',
recommendation: '使用AES-256-GCM替代DES/RC4,使用SHA-256+替代MD5/SHA1',
});
}
// 检测hilog打印敏感数据
const logSensitivePattern = /hilog\.(info|debug)\s*\([^)]*(?:password|token|key|secret)/i;
if (logSensitivePattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'medium',
category: '数据安全',
ruleId: 'SEC-DATA-003',
description: '检测到日志中打印敏感数据',
recommendation: '禁止在日志中输出密码、令牌等敏感信息',
});
}
});
}
// ==========================================
// 通信安全检查
// ==========================================
checkCommunicationSecurity(content: string, filePath: string): void {
const lines = content.split('\n');
lines.forEach((line, index) => {
// 检测HTTP明文请求
const httpPattern = /http:\/\/(?!localhost|127\.0\.0\.1)/i;
if (httpPattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'high',
category: '通信安全',
ruleId: 'SEC-NET-001',
description: '检测到HTTP明文请求,数据可能被窃听',
recommendation: '使用HTTPS加密传输,禁止HTTP明文通信',
});
}
// 检测证书校验绕过
const certBypassPattern = /(?:onSslErrorReceive|onCertificateError)\s*\(\s*\)\s*\{/;
if (certBypassPattern.test(line)) {
this.findings.push({
file: filePath,
line: index + 1,
severity: 'critical',
category: '通信安全',
ruleId: 'SEC-NET-002',
description: '检测到SSL证书校验回调,可能绕过证书验证',
recommendation: '不要绕过SSL证书校验,生产环境必须验证证书',
});
}
});
}
// 执行所有检查
scanAll(content: string, filePath: string): SecurityFinding[] {
this.findings = [];
this.checkSqlInjection(content, filePath);
this.checkPathTraversal(content, filePath);
this.checkDataSecurity(content, filePath);
this.checkCommunicationSecurity(content, filePath);
return this.findings;
}
}
完整示例:鸿蒙安全编码规范与自动化扫描
// harmony-security-audit.ts
// 鸿蒙安全编码规范审计工具
class HarmonySecurityAuditor {
private allFindings: SecurityFinding[] = [];
// 鸿蒙特有的安全检查
checkHarmonySpecificSecurity(content: string, filePath: string): void {
const lines = content.split('\n');
lines.forEach((line, index) => {
// 检查权限声明
this.checkPermissionUsage(line, filePath, index);
// 检查数据备份安全
this.checkBackupSecurity(line, filePath, index);
// 检查Web组件安全
this.checkWebSecurity(line, filePath, index);
// 检查Ability导出安全
this.checkAbilityExport(line, filePath, index);
});
}
// 检查权限使用
private checkPermissionUsage(
line: string, filePath: string, lineIndex: number
): void {
// 检测过度权限申请
const dangerousPermissions = [
'ohos.permission.READ_CALENDAR',
'ohos.permission.READ_CALL_LOG',
'ohos.permission.READ_CONTACTS',
'ohos.permission.LOCATION',
'ohos.permission.CAMERA',
'ohos.permission.MICROPHONE',
];
for (const perm of dangerousPermissions) {
if (line.includes(perm)) {
// 检查是否有对应的权限使用说明
if (!line.includes('reason')) {
this.allFindings.push({
file: filePath,
line: lineIndex + 1,
severity: 'medium',
category: '权限安全',
ruleId: 'SEC-PERM-001',
description: `申请了敏感权限${perm}但未提供使用说明`,
recommendation: '敏感权限必须在module.json5中声明reason',
});
}
}
}
}
// 检查数据备份安全
private checkBackupSecurity(
line: string, filePath: string, lineIndex: number
): void {
// 检测备份配置不当
if (line.includes('allowBackup') && line.includes('true')) {
this.allFindings.push({
file: filePath,
line: lineIndex + 1,
severity: 'high',
category: '数据安全',
ruleId: 'SEC-BACKUP-001',
description: '应用允许备份,敏感数据可能通过备份泄露',
recommendation: '包含敏感数据的应用应设置allowBackup为false',
});
}
}
// 检查Web组件安全
private checkWebSecurity(
line: string, filePath: string, lineIndex: number
): void {
// 检测Web组件的fileFromProxyAccessEnabled
if (line.includes('fileFromProxyAccessEnabled') && line.includes('true')) {
this.allFindings.push({
file: filePath,
line: lineIndex + 1,
severity: 'high',
category: 'Web安全',
ruleId: 'SEC-WEB-001',
description: 'Web组件允许代理访问文件,可能被恶意网页利用',
recommendation: '设置fileFromProxyAccessEnabled为false',
});
}
// 检测JavaScript自动执行
if (line.includes('javaScriptAccess') && line.includes('true')) {
this.allFindings.push({
file: filePath,
line: lineIndex + 1,
severity: 'medium',
category: 'Web安全',
ruleId: 'SEC-WEB-002',
description: 'Web组件启用了JavaScript,需确保加载的网页可信',
recommendation: '仅对可信网页启用JavaScript',
});
}
}
// 检查Ability导出安全
private checkAbilityExport(
line: string, filePath: string, lineIndex: number
): void {
// 检测导出的Ability没有权限保护
if (line.includes('exported') && line.includes('true')) {
// 检查后续几行是否有权限声明
this.allFindings.push({
file: filePath,
line: lineIndex + 1,
severity: 'high',
category: '组件安全',
ruleId: 'SEC-ABILITY-001',
description: 'Ability被导出但可能缺少权限保护',
recommendation: '导出的Ability应添加权限保护,防止被恶意调用',
});
}
}
// 生成安全审计报告
generateSecurityReport(): string {
let report = '╔══════════════════════════════════════════╗\n';
report += '║ 安全代码扫描报告 ║\n';
report += '╚══════════════════════════════════════════╝\n\n';
// 按严重程度统计
const critical = this.allFindings.filter(f => f.severity === 'critical');
const high = this.allFindings.filter(f => f.severity === 'high');
const medium = this.allFindings.filter(f => f.severity === 'medium');
const low = this.allFindings.filter(f => f.severity === 'low');
report += `📊 问题统计\n`;
report += ` 🔴 严重: ${critical.length}\n`;
report += ` 🟠 高危: ${high.length}\n`;
report += ` 🟡 中危: ${medium.length}\n`;
report += ` 🟢 低危: ${low.length}\n\n`;
// 按类别分组
const categories = new Map<string, SecurityFinding[]>();
for (const finding of this.allFindings) {
if (!categories.has(finding.category)) {
categories.set(finding.category, []);
}
categories.get(finding.category)!.push(finding);
}
for (const [category, findings] of categories) {
report += `📋 ${category} (${findings.length}个)\n`;
for (const f of findings) {
const icon = f.severity === 'critical' ? '🔴' :
f.severity === 'high' ? '🟠' :
f.severity === 'medium' ? '🟡' : '🟢';
report += ` ${icon} [${f.ruleId}] ${f.file}:${f.line}\n`;
report += ` ${f.description}\n`;
report += ` 建议: ${f.recommendation}\n`;
}
report += '\n';
}
// 安全评分
const score = Math.max(0, 100 - critical.length * 20 - high.length * 10 -
medium.length * 5 - low.length * 2);
report += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
report += `🔒 安全评分: ${score}/100\n`;
if (critical.length > 0) {
report += `🚫 存在严重安全问题,必须修复后才能发布\n`;
} else if (high.length > 0) {
report += `⚠️ 存在高危安全问题,强烈建议修复\n`;
} else {
report += `✅ 未发现严重安全问题\n`;
}
return report;
}
// 执行完整扫描
runFullScan(files: Map<string, string>): string {
this.allFindings = [];
const sensitiveScanner = new SensitiveDataScanner();
const injectionScanner = new InjectionScanner();
for (const [filePath, content] of files) {
// 敏感信息扫描
const sensitiveFindings = sensitiveScanner.scanFile(content, filePath);
this.allFindings.push(...sensitiveFindings);
// 注入攻击扫描
const injectionFindings = injectionScanner.scanAll(content, filePath);
this.allFindings.push(...injectionFindings);
// 鸿蒙特有安全检查
this.checkHarmonySpecificSecurity(content, filePath);
}
return this.generateSecurityReport();
}
}
踩坑与注意事项
坑1:误报太多,团队开始忽视
安全扫描一跑,几百个问题,其中80%是误报。团队看都不看就关掉了。
解决方案:
- 调整规则敏感度:先跑一遍,把误报多的规则调低优先级
- 白名单机制:确认是误报的可以加入白名单,但必须注释原因
- 分级处理:critical必须修,high必须review,medium选择性修
坑2:只扫描源代码,忽略配置文件
module.json5里的权限声明、build-profile.json5里的签名配置——这些配置文件也有安全问题,但经常被忽略。
解决方案:
- 扫描范围包括.ets/.ts/.json/.json5文件
- 特别检查module.json5中的权限声明和导出配置
- 检查oh-package.json5中的依赖来源
坑3:修复安全问题引入新Bug
修了一个安全漏洞,结果功能挂了。比如把HTTP改成HTTPS,结果服务端不支持HTTPS。
解决方案:
- 安全修复也要走测试流程
- 优先修复不影响功能的问题(如日志脱敏)
- 影响功能的修复需要与后端协同
坑4:忽视运行时安全
静态扫描只能发现代码层面的问题,运行时的安全问题(如内存中的敏感数据、进程间通信安全)检测不到。
解决方案:
- 敏感数据用完立即清零
- 使用HUKS保护密钥,不要在内存中明文保存
- 进程间通信使用系统提供的IPC安全机制
坑5:安全扫描不进CI
开发阶段不跑安全扫描,上线前才跑一次,发现一堆问题来不及修。
解决方案:
- 安全扫描集成到CI,每次PR都跑
- Pre-commit钩子检查硬编码密钥
- 夜间构建跑全量安全扫描
HarmonyOS 6适配说明
HarmonyOS 6在安全扫描方面的改进:
-
应用安全审核工具:华为提供了官方的安全审核工具
appscanner,可以自动检测鸿蒙应用的常见安全问题,包括硬编码密钥、权限滥用、数据泄露等。 -
HUKS增强:HarmonyOS 6的HUKS新增了安全密钥导入功能,可以从服务端安全地导入密钥,不需要在客户端代码中出现明文密钥。
-
证书钉扎(Certificate Pinning):新增了证书钉扎API,可以防止中间人攻击。不再需要手动实现证书校验逻辑。
-
安全编码规范文档:华为发布了官方的鸿蒙安全编码规范,涵盖了数据安全、通信安全、权限管理等各方面,安全扫描规则可以对照这个规范制定。
-
DevEco Studio安全提示:IDE内置了安全编码提示,编写不安全的代码时会实时提醒。比如写HTTP URL会提示改为HTTPS。
总结
安全漏洞不是小事,一个硬编码密钥可能让整个系统的安全防线崩溃。安全扫描的核心价值是在问题发生之前发现它。
核心要点:
- 硬编码密钥零容忍:密钥必须走安全存储,代码里不能出现
- 注入攻击要防范:参数化查询、输入校验、路径检查
- 数据安全要重视:加密存储、安全传输、日志脱敏
- 鸿蒙特有安全:权限声明、Ability导出、Web组件安全
- 安全扫描进CI:不要等到上线前才检查
| 维度 | 评分 | 说明 |
|---|---|---|
| 学习难度 | ⭐⭐⭐ | 安全知识需要积累,规则配置需要经验 |
| 使用频率 | ⭐⭐⭐⭐ | CI每次构建都跑,日常开发需要关注 |
| 重要程度 | ⭐⭐⭐⭐⭐ | 安全问题可能导致数据泄露,不可忽视 |
- 点赞
- 收藏
- 关注作者
评论(0)