diff --git a/ruleSet.json b/ruleSet.json index ed90188d..cbb9c560 100644 --- a/ruleSet.json +++ b/ruleSet.json @@ -254,6 +254,11 @@ "@stability/image-sync-blur-check": 3 }, "plugin:@software-sec/all": { - "@software-sec/checker19241042/command-execution-check": 1 + "@software-sec/checker19241042/command-execution-check": 1, + "@software-sec/checker22373030/sql-injection-check": 2, + "@software-sec/checker22373030/xss-attack-check": 2, + "@software-sec/checker22373030/hardcoded-password-check": 2, + "@software-sec/checker22373030/unsafe-file-operation-check": 2, + "@software-sec/checker22373030/unsafe-network-request-check": 2 } } \ No newline at end of file diff --git a/sample/Sample22373030/Issue1/projectConfig.json b/sample/Sample22373030/Issue1/projectConfig.json new file mode 100644 index 00000000..cf4a1ba2 --- /dev/null +++ b/sample/Sample22373030/Issue1/projectConfig.json @@ -0,0 +1,16 @@ +{ + "projectName": "Issue1", + "projectPath": "./sample/Sample22373030/Issue1", + "logPath": "./HomeCheck.log", + "ohosSdkPath": "./resources/sdk/openharmony/ets", + "hmsSdkPath": "./resources/sdk/hms/ets", + "checkPath": "", + "sdkVersion": 14, + "fix": "false", + "npmPath": "", + "npmInstallDir": "./", + "reportDir": "./report", + "arkCheckPath": "./", + "product": "default", + "sdksThirdParty": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue1/ruleConfig.json b/sample/Sample22373030/Issue1/ruleConfig.json new file mode 100644 index 00000000..2c15d644 --- /dev/null +++ b/sample/Sample22373030/Issue1/ruleConfig.json @@ -0,0 +1,26 @@ +{ + "files": [ + "**/*.ets", + "**/*.ts" + ], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/build/**/*", + "**/hvigorfile/**/*", + "**/oh_modules/**/*", + "**/.preview/**/*" + ], + "rules": { + "@software-sec/checker22373030/sql-injection-check": 2 + }, + "ruleSet": [ + "plugin:@correctness/all", + "plugin:@performance/all", + "plugin:@cross-device-app-dev/all", + "plugin:@security/all", + "plugin:@stability/all" + ], + "overrides": [], + "extRuleSet": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue1/sample1.ts b/sample/Sample22373030/Issue1/sample1.ts new file mode 100644 index 00000000..84368ebe --- /dev/null +++ b/sample/Sample22373030/Issue1/sample1.ts @@ -0,0 +1,24 @@ +import { rdb } from '@ohos.data.relationalStore'; + +class DatabaseManager { + private rdbStore: rdb.RdbStore; + + async queryUser(userId: string) { + // 不安全的SQL查询 - 直接拼接用户输入 + const sql = `SELECT * FROM users WHERE id = '${userId}'`; + const resultSet = await this.rdbStore.querySql(sql); + return resultSet; + } + + async searchUsers(searchTerm: string) { + // 另一个SQL注入示例 + const query = "SELECT * FROM users WHERE name LIKE '%" + searchTerm + "%'"; + return await this.rdbStore.querySql(query); + } + + async deleteUser(userId: string) { + // 危险的删除操作 + const deleteSql = `DELETE FROM users WHERE id = ${userId}`; + await this.rdbStore.executeSql(deleteSql); + } +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue2/projectConfig.json b/sample/Sample22373030/Issue2/projectConfig.json new file mode 100644 index 00000000..67ead7e8 --- /dev/null +++ b/sample/Sample22373030/Issue2/projectConfig.json @@ -0,0 +1,16 @@ +{ + "projectName": "Issue2", + "projectPath": "./sample/Sample22373030/Issue2", + "logPath": "./HomeCheck.log", + "ohosSdkPath": "./resources/sdk/openharmony/ets", + "hmsSdkPath": "./resources/sdk/hms/ets", + "checkPath": "", + "sdkVersion": 14, + "fix": "false", + "npmPath": "", + "npmInstallDir": "./", + "reportDir": "./report", + "arkCheckPath": "./", + "product": "default", + "sdksThirdParty": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue2/ruleConfig.json b/sample/Sample22373030/Issue2/ruleConfig.json new file mode 100644 index 00000000..732b5cff --- /dev/null +++ b/sample/Sample22373030/Issue2/ruleConfig.json @@ -0,0 +1,26 @@ +{ + "files": [ + "**/*.ets", + "**/*.ts" + ], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/build/**/*", + "**/hvigorfile/**/*", + "**/oh_modules/**/*", + "**/.preview/**/*" + ], + "rules": { + "@software-sec/checker22373030/xss-attack-check": 2 + }, + "ruleSet": [ + "plugin:@correctness/all", + "plugin:@performance/all", + "plugin:@cross-device-app-dev/all", + "plugin:@security/all", + "plugin:@stability/all" + ], + "overrides": [], + "extRuleSet": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue2/sample2.ts b/sample/Sample22373030/Issue2/sample2.ts new file mode 100644 index 00000000..d0c68375 --- /dev/null +++ b/sample/Sample22373030/Issue2/sample2.ts @@ -0,0 +1,23 @@ +import { web } from '@ohos.web.webview'; + +class WebViewManager { + private webView: web.WebviewController; + + displayUserContent(userContent: string) { + // 不安全的HTML内容渲染 - 直接插入用户输入 + const html = `
${userContent}
`; + this.webView.loadData(html, 'text/html', 'UTF-8'); + } + + renderComment(comment: string) { + // 另一个XSS示例 - 直接拼接HTML + const commentHtml = `

${comment}

`; + this.webView.loadData(commentHtml, 'text/html', 'UTF-8'); + } + + showNotification(message: string) { + // 危险的JavaScript执行 + const script = ``; + this.webView.loadData(script, 'text/html', 'UTF-8'); + } +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue3/projectConfig.json b/sample/Sample22373030/Issue3/projectConfig.json new file mode 100644 index 00000000..6a2bb5c7 --- /dev/null +++ b/sample/Sample22373030/Issue3/projectConfig.json @@ -0,0 +1,16 @@ +{ + "projectName": "Issue3", + "projectPath": "./sample/Sample22373030/Issue3", + "logPath": "./HomeCheck.log", + "ohosSdkPath": "./resources/sdk/openharmony/ets", + "hmsSdkPath": "./resources/sdk/hms/ets", + "checkPath": "", + "sdkVersion": 14, + "fix": "false", + "npmPath": "", + "npmInstallDir": "./", + "reportDir": "./report", + "arkCheckPath": "./", + "product": "default", + "sdksThirdParty": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue3/ruleConfig.json b/sample/Sample22373030/Issue3/ruleConfig.json new file mode 100644 index 00000000..5a677d02 --- /dev/null +++ b/sample/Sample22373030/Issue3/ruleConfig.json @@ -0,0 +1,26 @@ +{ + "files": [ + "**/*.ets", + "**/*.ts" + ], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/build/**/*", + "**/hvigorfile/**/*", + "**/oh_modules/**/*", + "**/.preview/**/*" + ], + "rules": { + "@software-sec/checker22373030/hardcoded-password-check": 2 + }, + "ruleSet": [ + "plugin:@correctness/all", + "plugin:@performance/all", + "plugin:@cross-device-app-dev/all", + "plugin:@security/all", + "plugin:@stability/all" + ], + "overrides": [], + "extRuleSet": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue3/sample3.ts b/sample/Sample22373030/Issue3/sample3.ts new file mode 100644 index 00000000..6e155873 --- /dev/null +++ b/sample/Sample22373030/Issue3/sample3.ts @@ -0,0 +1,24 @@ +class DatabaseConnection { + // 硬编码的数据库密码 + private dbPassword = "admin123"; + private apiKey = "sk-1234567890abcdef"; + + connect() { + const connectionString = `mongodb://admin:${this.dbPassword}@localhost:27017`; + console.log("Connecting to database..."); + } + + authenticate() { + // 硬编码的认证信息 + const credentials = { + username: "admin", + password: "password123" + }; + return credentials; + } + + getApiKey() { + // 硬编码的API密钥 + return "sk-abcdefghijklmnopqrstuvwxyz123456"; + } + } \ No newline at end of file diff --git a/sample/Sample22373030/Issue4/projectConfig.json b/sample/Sample22373030/Issue4/projectConfig.json new file mode 100644 index 00000000..cf4a1ba2 --- /dev/null +++ b/sample/Sample22373030/Issue4/projectConfig.json @@ -0,0 +1,16 @@ +{ + "projectName": "Issue1", + "projectPath": "./sample/Sample22373030/Issue1", + "logPath": "./HomeCheck.log", + "ohosSdkPath": "./resources/sdk/openharmony/ets", + "hmsSdkPath": "./resources/sdk/hms/ets", + "checkPath": "", + "sdkVersion": 14, + "fix": "false", + "npmPath": "", + "npmInstallDir": "./", + "reportDir": "./report", + "arkCheckPath": "./", + "product": "default", + "sdksThirdParty": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue4/ruleConfig.json b/sample/Sample22373030/Issue4/ruleConfig.json new file mode 100644 index 00000000..45de0afd --- /dev/null +++ b/sample/Sample22373030/Issue4/ruleConfig.json @@ -0,0 +1,26 @@ +{ + "files": [ + "**/*.ets", + "**/*.ts" + ], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/build/**/*", + "**/hvigorfile/**/*", + "**/oh_modules/**/*", + "**/.preview/**/*" + ], + "rules": { + "@software-sec/checker22373030/unsafe-file-operation-check": 2 + }, + "ruleSet": [ + "plugin:@correctness/all", + "plugin:@performance/all", + "plugin:@cross-device-app-dev/all", + "plugin:@security/all", + "plugin:@stability/all" + ], + "overrides": [], + "extRuleSet": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue4/sample4.ts b/sample/Sample22373030/Issue4/sample4.ts new file mode 100644 index 00000000..cc646383 --- /dev/null +++ b/sample/Sample22373030/Issue4/sample4.ts @@ -0,0 +1,26 @@ +import { fileio } from '@ohos.fileio'; + +class FileManager { + readFile(filePath: string) { + // 不安全的文件操作 - 直接使用用户输入 + const content = fileio.readTextSync(filePath); + return content; + } + + deleteFile(fileName: string) { + // 路径遍历攻击风险 + const fullPath = `/data/storage/el2/base/files/${fileName}`; + fileio.unlinkSync(fullPath); + } + + writeFile(fileName: string, content: string) { + // 不安全的文件写入 + const path = `/data/storage/el2/base/files/${fileName}`; + fileio.writeTextSync(path, content); + } + + copyFile(source: string, destination: string) { + // 直接使用用户输入的文件路径 + fileio.copyFileSync(source, destination); + } +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue5/projectConfig.json b/sample/Sample22373030/Issue5/projectConfig.json new file mode 100644 index 00000000..cf4a1ba2 --- /dev/null +++ b/sample/Sample22373030/Issue5/projectConfig.json @@ -0,0 +1,16 @@ +{ + "projectName": "Issue1", + "projectPath": "./sample/Sample22373030/Issue1", + "logPath": "./HomeCheck.log", + "ohosSdkPath": "./resources/sdk/openharmony/ets", + "hmsSdkPath": "./resources/sdk/hms/ets", + "checkPath": "", + "sdkVersion": 14, + "fix": "false", + "npmPath": "", + "npmInstallDir": "./", + "reportDir": "./report", + "arkCheckPath": "./", + "product": "default", + "sdksThirdParty": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue5/ruleConfig.json b/sample/Sample22373030/Issue5/ruleConfig.json new file mode 100644 index 00000000..92e6d491 --- /dev/null +++ b/sample/Sample22373030/Issue5/ruleConfig.json @@ -0,0 +1,26 @@ +{ + "files": [ + "**/*.ets", + "**/*.ts" + ], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/build/**/*", + "**/hvigorfile/**/*", + "**/oh_modules/**/*", + "**/.preview/**/*" + ], + "rules": { + "@software-sec/checker22373030/unsafe-network-request-check": 2 + }, + "ruleSet": [ + "plugin:@correctness/all", + "plugin:@performance/all", + "plugin:@cross-device-app-dev/all", + "plugin:@security/all", + "plugin:@stability/all" + ], + "overrides": [], + "extRuleSet": [] +} \ No newline at end of file diff --git a/sample/Sample22373030/Issue5/sample5.ts b/sample/Sample22373030/Issue5/sample5.ts new file mode 100644 index 00000000..50e572d2 --- /dev/null +++ b/sample/Sample22373030/Issue5/sample5.ts @@ -0,0 +1,46 @@ +import { http } from '@ohos.net.http'; + +class NetworkManager { + async fetchUserData(userId: string) { + // 不安全的HTTP请求 + const httpRequest = http.createHttp(); + const response = await httpRequest.request( + `http://api.example.com/users/${userId}`, + { + method: http.RequestMethod.GET, + header: { + 'Content-Type': 'application/json' + } + } + ); + return response; + } + + async sendLoginData(credentials: any) { + // 使用HTTP发送敏感数据 + const httpRequest = http.createHttp(); + const response = await httpRequest.request( + "http://login.example.com/auth", + { + method: http.RequestMethod.POST, + header: { + 'Content-Type': 'application/json' + }, + extraData: JSON.stringify(credentials) + } + ); + return response; + } + + async downloadFile(fileUrl: string) { + // 不安全的文件下载 + const httpRequest = http.createHttp(); + const response = await httpRequest.request( + `http://download.example.com/files/${fileUrl}`, + { + method: http.RequestMethod.GET + } + ); + return response; + } +} \ No newline at end of file diff --git a/src/checker/SoftwareSecurity25/Checker22373030/HardcodedPasswordCheck.ts b/src/checker/SoftwareSecurity25/Checker22373030/HardcodedPasswordCheck.ts new file mode 100644 index 00000000..938b98d4 --- /dev/null +++ b/src/checker/SoftwareSecurity25/Checker22373030/HardcodedPasswordCheck.ts @@ -0,0 +1,93 @@ +import {ArkFile, Stmt} from 'arkanalyzer'; +import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger'; +import {BaseChecker, BaseMetaData} from '../../BaseChecker'; +import {Defects} from '../../../Index'; +import {FileMatcher, MatcherCallback, MatcherTypes} from '../../../Index'; +import {Rule} from '../../../Index'; +import {IssueReport} from '../../../model/Defects'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'HardcodedPasswordCheck'); + +const gMetaData: BaseMetaData = { + severity: 2, + ruleDocPath: '', + description: 'Detects hardcoded passwords and sensitive credentials in source code' +}; + +export class HardcodedPasswordCheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + + private fileMatcher: FileMatcher = { + matcherType: MatcherTypes.FILE + }; + + public registerMatchers(): MatcherCallback[] { + const fileMatchBuildCb: MatcherCallback = { + matcher: this.fileMatcher, + callback: this.check + } + return [fileMatchBuildCb]; + } + + public check = (targetFile: ArkFile) => { + for (const arkClass of targetFile.getClasses()) { + for (const arkMethod of arkClass.getMethods()) { + const cfg = arkMethod.getCfg(); + if (cfg == undefined) { + continue; + } + for (const stmt of cfg.getStmts()) { + this.checkForHardcodedPassword(targetFile, stmt, arkMethod.getName()); + } + } + } + } + + private checkForHardcodedPassword(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + + // 检测硬编码密码模式 + const passwordPatterns = [ + /password\s*=\s*["'][^"']{6,}["']/, // password = "xxx" + /password\s*:\s*["'][^"']{6,}["']/, // password: "xxx" + /passwd\s*=\s*["'][^"']{6,}["']/, // passwd = "xxx" + /pass\s*=\s*["'][^"']{6,}["']/, // pass = "xxx" + /pwd\s*=\s*["'][^"']{6,}["']/, // pwd = "xxx" + /apiKey\s*=\s*["'][^"']{10,}["']/, // apiKey = "xxx" + /secret\s*=\s*["'][^"']{6,}["']/, // secret = "xxx" + /token\s*=\s*["'][^"']{10,}["']/, // token = "xxx" + /sk-[a-zA-Z0-9]{20,}/, // OpenAI API key pattern + /mongodb:\/\/[^:]+:[^@]+@/, // MongoDB connection string with password + ]; + + for (const pattern of passwordPatterns) { + if (pattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + break; + } + } + } + + public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const severity = this.rule.alert ?? this.metaData.severity; + const filePath = arkFile.getFilePath(); + const originPositionInfo = stmt.getOriginPositionInfo(); + const lineNum = originPositionInfo.getLineNo(); + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + const startColumn = originPositionInfo.getColNo(); + const endColumn = startColumn + text.length; + let defects = new Defects(lineNum, startColumn, endColumn, + "Hardcoded password or sensitive credential detected. Use environment variables or secure storage instead.", + severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false); + this.issues.push(new IssueReport(defects, undefined)); + } +} \ No newline at end of file diff --git a/src/checker/SoftwareSecurity25/Checker22373030/SqlInjectionCheck.ts b/src/checker/SoftwareSecurity25/Checker22373030/SqlInjectionCheck.ts new file mode 100644 index 00000000..3f47e6d0 --- /dev/null +++ b/src/checker/SoftwareSecurity25/Checker22373030/SqlInjectionCheck.ts @@ -0,0 +1,89 @@ +import {ArkFile, Stmt} from 'arkanalyzer'; +import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger'; +import {BaseChecker, BaseMetaData} from '../../BaseChecker'; +import {Defects} from '../../../Index'; +import {FileMatcher, MatcherCallback, MatcherTypes} from '../../../Index'; +import {Rule} from '../../../Index'; +import {IssueReport} from '../../../model/Defects'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'SqlInjectionCheck'); + +const gMetaData: BaseMetaData = { + severity: 2, + ruleDocPath: '', + description: 'Detects potential SQL injection vulnerabilities in database queries' +}; + +export class SqlInjectionCheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + + private fileMatcher: FileMatcher = { + matcherType: MatcherTypes.FILE + }; + + public registerMatchers(): MatcherCallback[] { + const fileMatchBuildCb: MatcherCallback = { + matcher: this.fileMatcher, + callback: this.check + } + return [fileMatchBuildCb]; + } + + public check = (targetFile: ArkFile) => { + for (const arkClass of targetFile.getClasses()) { + for (const arkMethod of arkClass.getMethods()) { + const cfg = arkMethod.getCfg(); + if (cfg == undefined) { + continue; + } + for (const stmt of cfg.getStmts()) { + this.checkForSqlInjection(targetFile, stmt, arkMethod.getName()); + } + } + } + } + + private checkForSqlInjection(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + + // 检测SQL注入模式 + const sqlInjectionPatterns = [ + /\$\{[^}]+\}/, // 模板字符串中的变量 + /'\+[^+]+'/, // 字符串拼接 + /"\+[^+]+"/, // 字符串拼接 + /`[^`]*\$\{[^}]+\}[^`]*`/, // 模板字符串 + /querySql\s*\(\s*[^)]*\+[^)]*\)/, // querySql方法中的拼接 + /executeSql\s*\(\s*[^)]*\+[^)]*\)/ // executeSql方法中的拼接 + ]; + + for (const pattern of sqlInjectionPatterns) { + if (pattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + break; + } + } + } + + public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const severity = this.rule.alert ?? this.metaData.severity; + const filePath = arkFile.getFilePath(); + const originPositionInfo = stmt.getOriginPositionInfo(); + const lineNum = originPositionInfo.getLineNo(); + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + const startColumn = originPositionInfo.getColNo(); + const endColumn = startColumn + text.length; + let defects = new Defects(lineNum, startColumn, endColumn, + "Potential SQL injection vulnerability detected. Use parameterized queries instead of string concatenation.", + severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false); + this.issues.push(new IssueReport(defects, undefined)); + } +} \ No newline at end of file diff --git a/src/checker/SoftwareSecurity25/Checker22373030/UnsafeFileOperationCheck.ts b/src/checker/SoftwareSecurity25/Checker22373030/UnsafeFileOperationCheck.ts new file mode 100644 index 00000000..e934a534 --- /dev/null +++ b/src/checker/SoftwareSecurity25/Checker22373030/UnsafeFileOperationCheck.ts @@ -0,0 +1,108 @@ +import {ArkFile, Stmt} from 'arkanalyzer'; +import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger'; +import {BaseChecker, BaseMetaData} from '../../BaseChecker'; +import {Defects} from '../../../Index'; +import {FileMatcher, MatcherCallback, MatcherTypes} from '../../../Index'; +import {Rule} from '../../../Index'; +import {IssueReport} from '../../../model/Defects'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'UnsafeFileOperationCheck'); + +const gMetaData: BaseMetaData = { + severity: 2, + ruleDocPath: '', + description: 'Detects unsafe file operations that may lead to path traversal attacks' +}; + +export class UnsafeFileOperationCheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + + private fileMatcher: FileMatcher = { + matcherType: MatcherTypes.FILE + }; + + public registerMatchers(): MatcherCallback[] { + const fileMatchBuildCb: MatcherCallback = { + matcher: this.fileMatcher, + callback: this.check + } + return [fileMatchBuildCb]; + } + + public check = (targetFile: ArkFile) => { + for (const arkClass of targetFile.getClasses()) { + for (const arkMethod of arkClass.getMethods()) { + const cfg = arkMethod.getCfg(); + if (cfg == undefined) { + continue; + } + for (const stmt of cfg.getStmts()) { + this.checkForUnsafeFileOperation(targetFile, stmt, arkMethod.getName()); + } + } + } + } + + private checkForUnsafeFileOperation(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + + // 检测不安全的文件操作模式 + const unsafeFilePatterns = [ + /readTextSync\s*\(\s*[^)]+\)/, // readTextSync + /writeTextSync\s*\(\s*[^)]+\)/, // writeTextSync + /unlinkSync\s*\(\s*[^)]+\)/, // unlinkSync + /copyFileSync\s*\(\s*[^)]+\)/, // copyFileSync + /readFile\s*\(\s*[^)]+\)/, // readFile + /writeFile\s*\(\s*[^)]+\)/, // writeFile + /deleteFile\s*\(\s*[^)]+\)/, // deleteFile + /moveFile\s*\(\s*[^)]+\)/, // moveFile + ]; + + // 检测路径遍历模式 + const pathTraversalPatterns = [ + /\.\./, // 包含 .. + /\/\.\.\//, // 包含 /../ + /\.\.\/\.\./, // 包含 ../../ + ]; + + for (const pattern of unsafeFilePatterns) { + if (pattern.test(text)) { + // 检查是否包含路径遍历 + for (const traversalPattern of pathTraversalPatterns) { + if (traversalPattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + return; + } + } + // 检查是否直接使用用户输入 + if (text.includes('+') || text.includes('${')) { + this.reportIssue(arkFile, stmt, methodName); + return; + } + } + } + } + + public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const severity = this.rule.alert ?? this.metaData.severity; + const filePath = arkFile.getFilePath(); + const originPositionInfo = stmt.getOriginPositionInfo(); + const lineNum = originPositionInfo.getLineNo(); + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + const startColumn = originPositionInfo.getColNo(); + const endColumn = startColumn + text.length; + let defects = new Defects(lineNum, startColumn, endColumn, + "Unsafe file operation detected. Validate and sanitize file paths to prevent path traversal attacks.", + severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false); + this.issues.push(new IssueReport(defects, undefined)); + } +} \ No newline at end of file diff --git a/src/checker/SoftwareSecurity25/Checker22373030/UnsafeNetworkRequestCheck.ts b/src/checker/SoftwareSecurity25/Checker22373030/UnsafeNetworkRequestCheck.ts new file mode 100644 index 00000000..8c66f87f --- /dev/null +++ b/src/checker/SoftwareSecurity25/Checker22373030/UnsafeNetworkRequestCheck.ts @@ -0,0 +1,102 @@ +import {ArkFile, Stmt} from 'arkanalyzer'; +import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger'; +import {BaseChecker, BaseMetaData} from '../../BaseChecker'; +import {Defects} from '../../../Index'; +import {FileMatcher, MatcherCallback, MatcherTypes} from '../../../Index'; +import {Rule} from '../../../Index'; +import {IssueReport} from '../../../model/Defects'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'UnsafeNetworkRequestCheck'); + +const gMetaData: BaseMetaData = { + severity: 2, + ruleDocPath: '', + description: 'Detects unsafe HTTP requests that should use HTTPS instead' +}; + +export class UnsafeNetworkRequestCheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + + private fileMatcher: FileMatcher = { + matcherType: MatcherTypes.FILE + }; + + public registerMatchers(): MatcherCallback[] { + const fileMatchBuildCb: MatcherCallback = { + matcher: this.fileMatcher, + callback: this.check + } + return [fileMatchBuildCb]; + } + + public check = (targetFile: ArkFile) => { + for (const arkClass of targetFile.getClasses()) { + for (const arkMethod of arkClass.getMethods()) { + const cfg = arkMethod.getCfg(); + if (cfg == undefined) { + continue; + } + for (const stmt of cfg.getStmts()) { + this.checkForUnsafeNetworkRequest(targetFile, stmt, arkMethod.getName()); + } + } + } + } + + private checkForUnsafeNetworkRequest(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + + // 检测不安全的HTTP请求模式 + const unsafeHttpPatterns = [ + /http:\/\/[^"'\s]+/, // http:// 开头的URL + /"http:\/\/[^"]+"/, // 双引号中的http:// + /'http:\/\/[^']+'/, // 单引号中的http:// + /`http:\/\/[^`]+`/, // 反引号中的http:// + /http:\/\/[^"'\s]+[^"'\s]*/, // 更宽松的http://匹配 + ]; + + // 检测网络请求相关的方法调用 + const networkRequestMethods = [ + /request\s*\(\s*[^)]*http:\/\/[^)]*\)/, // request方法中的http + /fetch\s*\(\s*[^)]*http:\/\/[^)]*\)/, // fetch方法中的http + /loadUrl\s*\(\s*[^)]*http:\/\/[^)]*\)/, // loadUrl方法中的http + ]; + + for (const pattern of unsafeHttpPatterns) { + if (pattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + break; + } + } + + for (const pattern of networkRequestMethods) { + if (pattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + break; + } + } + } + + public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const severity = this.rule.alert ?? this.metaData.severity; + const filePath = arkFile.getFilePath(); + const originPositionInfo = stmt.getOriginPositionInfo(); + const lineNum = originPositionInfo.getLineNo(); + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + const startColumn = originPositionInfo.getColNo(); + const endColumn = startColumn + text.length; + let defects = new Defects(lineNum, startColumn, endColumn, + "Unsafe HTTP request detected. Use HTTPS instead to ensure secure communication.", + severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false); + this.issues.push(new IssueReport(defects, undefined)); + } +} \ No newline at end of file diff --git a/src/checker/SoftwareSecurity25/Checker22373030/XssAttackCheck.ts b/src/checker/SoftwareSecurity25/Checker22373030/XssAttackCheck.ts new file mode 100644 index 00000000..28e5cc22 --- /dev/null +++ b/src/checker/SoftwareSecurity25/Checker22373030/XssAttackCheck.ts @@ -0,0 +1,90 @@ +import {ArkFile, Stmt} from 'arkanalyzer'; +import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger'; +import {BaseChecker, BaseMetaData} from '../../BaseChecker'; +import {Defects} from '../../../Index'; +import {FileMatcher, MatcherCallback, MatcherTypes} from '../../../Index'; +import {Rule} from '../../../Index'; +import {IssueReport} from '../../../model/Defects'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'XssAttackCheck'); + +const gMetaData: BaseMetaData = { + severity: 2, + ruleDocPath: '', + description: 'Detects potential XSS vulnerabilities in HTML content rendering' +}; + +export class XssAttackCheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + + private fileMatcher: FileMatcher = { + matcherType: MatcherTypes.FILE + }; + + public registerMatchers(): MatcherCallback[] { + const fileMatchBuildCb: MatcherCallback = { + matcher: this.fileMatcher, + callback: this.check + } + return [fileMatchBuildCb]; + } + + public check = (targetFile: ArkFile) => { + for (const arkClass of targetFile.getClasses()) { + for (const arkMethod of arkClass.getMethods()) { + const cfg = arkMethod.getCfg(); + if (cfg == undefined) { + continue; + } + for (const stmt of cfg.getStmts()) { + this.checkForXss(targetFile, stmt, arkMethod.getName()); + } + } + } + } + + private checkForXss(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + + // 检测XSS攻击模式 + const xssPatterns = [ + /`[^`]*\$\{[^}]+\}[^`]*`/, // 模板字符串中的变量插入HTML + /'\+[^+]+'/, // 字符串拼接 + /"\+[^+]+"/, // 字符串拼接 + /loadData\s*\(\s*[^)]*\+[^)]*\)/, // loadData方法中的拼接 + /]*>/, // 直接包含script标签 + /javascript:/, // javascript协议 + /on\w+\s*=/, // 事件处理器 + ]; + + for (const pattern of xssPatterns) { + if (pattern.test(text)) { + this.reportIssue(arkFile, stmt, methodName); + break; + } + } + } + + public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void { + const severity = this.rule.alert ?? this.metaData.severity; + const filePath = arkFile.getFilePath(); + const originPositionInfo = stmt.getOriginPositionInfo(); + const lineNum = originPositionInfo.getLineNo(); + const text = stmt.getOriginalText(); + if (!text || text.length === 0) { + return; + } + const startColumn = originPositionInfo.getColNo(); + const endColumn = startColumn + text.length; + let defects = new Defects(lineNum, startColumn, endColumn, + "Potential XSS vulnerability detected. Sanitize user input before rendering HTML content.", + severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false); + this.issues.push(new IssueReport(defects, undefined)); + } +} \ No newline at end of file diff --git a/src/utils/common/CheckerIndex.ts b/src/utils/common/CheckerIndex.ts index 8ffe01ca..97232f33 100644 --- a/src/utils/common/CheckerIndex.ts +++ b/src/utils/common/CheckerIndex.ts @@ -245,6 +245,11 @@ import { StreamUsageApiCheck } from '../../checker/performance/StreamUsageApiChe import { AvoidMemoryLeakInAnimator } from '../../checker/performance/AvoidMemoryLeakInAnimator'; import { AvoidMemoryLeakInDisplaysync } from '../../checker/performance/AvoidMemoryLeakInDisplaysync'; import { CommandExecutionCheck } from '../../checker/SoftwareSecurity25/Checker19241042/CommandExecutionCheck'; +import { SqlInjectionCheck } from '../../checker/SoftwareSecurity25/Checker22373030/SqlInjectionCheck'; +import { XssAttackCheck } from '../../checker/SoftwareSecurity25/Checker22373030/XssAttackCheck'; +import { HardcodedPasswordCheck } from '../../checker/SoftwareSecurity25/Checker22373030/HardcodedPasswordCheck'; +import { UnsafeFileOperationCheck } from '../../checker/SoftwareSecurity25/Checker22373030/UnsafeFileOperationCheck'; +import { UnsafeNetworkRequestCheck } from '../../checker/SoftwareSecurity25/Checker22373030/UnsafeNetworkRequestCheck'; const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'CheckerIndex'); @@ -460,7 +465,12 @@ export const fileRules = { "@stability/call-addInput-before-addOutput-check": CallAddInputBeforeAddOutputCheck, "@stability/camera-input-open-check": CameraInputOpenCheck, //software-security2025 start - "@software-sec/checker19241042/command-execution-check":CommandExecutionCheck + "@software-sec/checker19241042/command-execution-check":CommandExecutionCheck, + "@software-sec/checker22373030/sql-injection-check":SqlInjectionCheck, + "@software-sec/checker22373030/xss-attack-check":XssAttackCheck, + "@software-sec/checker22373030/hardcoded-password-check":HardcodedPasswordCheck, + "@software-sec/checker22373030/unsafe-file-operation-check":UnsafeFileOperationCheck, + "@software-sec/checker22373030/unsafe-network-request-check":UnsafeNetworkRequestCheck //software-security2025 end }; diff --git a/test/SoftwareSecurity25/Test22373030.ts b/test/SoftwareSecurity25/Test22373030.ts new file mode 100644 index 00000000..6b99ad5e --- /dev/null +++ b/test/SoftwareSecurity25/Test22373030.ts @@ -0,0 +1,61 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { run } from '../../src/Main'; +import { HomeSecReport } from '../../src/utils/common/HomeSecReport'; + +// Run tests for a specific issue directory +async function runTestForIssue(issueDir: string): Promise { + const projectConfigPath = path.join(issueDir, 'projectConfig.json'); + const ruleConfigPath = path.join(issueDir, 'ruleConfig.json'); + + // Check if configuration files exist + if (!fs.existsSync(projectConfigPath) || !fs.existsSync(ruleConfigPath)) { + console.log(`Missing configuration files in: ${issueDir}`); + return false; + } + + console.log(`Running test for: ${path.basename(issueDir)}`); + try { + await run(projectConfigPath, ruleConfigPath); + console.log(`Test finished for: ${path.basename(issueDir)}`); + return true; + } catch (error) { + console.log(`Test failed for: ${path.basename(issueDir)}, Error: ${error}`); + return false; + } +} + +// Main function to iterate and run tests for all issue directories +async function main(): Promise { + const sampleDir = path.join(__dirname, '../../sample/Sample22373030'); + + // Check if the sample directory exists + if (!fs.existsSync(sampleDir)) { + console.log(`Directory does not exist: ${sampleDir}`); + return; + } + + // Get all issue directories + const issueDirs = fs.readdirSync(sampleDir) + .map(dir => path.join(sampleDir, dir)) + .filter(dir => fs.statSync(dir).isDirectory()); + + if (issueDirs.length === 0) { + console.log('No issue directories found.'); + return; + } + + // Run tests for each issue directory + for (const issueDir of issueDirs) { + await runTestForIssue(issueDir); + } + + HomeSecReport.getInstance().generateReport(); + console.log('All tests completed.'); +} + +// Execute the main function and handle errors +main().catch(error => { + console.log(`Test execution failed: ${error}`); + process.exit(1); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 29c940a8..f20c260c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "src/**/*" ], "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "lib": ["es2022", "dom"] } }