Skip to content

安全加固:按审计复核修复 #1 #3 #6 #8 #9 #10 #12 #13 #14 #16 #17 - #32

Merged
HenryYannis merged 5 commits into
mainfrom
fix/audit-2026-10-23
Oct 23, 2026
Merged

HenryYannis merged 5 commits into
mainfrom
fix/audit-2026-10-23

Conversation

@HenryYannis

@HenryYannis HenryYannis commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

遇到的问题

我对 v1.2.1(main @ 8bb171c)做了一次安全审计,又逐条做了复核。这个 PR 只修复核结论为「属实」或「部分属实」的条目。复核认为基本不属实的 #2,我只加了一个很便宜的防护。

我的改动(按审计编号)

# 改动 Python PowerShell
1 reg / taskkill / sc / net / schtasks / tasklist(PS 里是 reg.exe、sc.exe、attrib.exe)一律用 System32 绝对路径。Python 用 GetSystemDirectoryW 取路径,不信环境变量;PS 用 [Environment]::SystemDirectory,attrib 也不再经过 cmd.exe /c ✅ ✅
3 Word / Excel / PowerPoint / Outlook 只结束当前会话的(Python 按 tasklist 的会话号、PS 按 SessionId 过滤),保存提醒也只针对当前会话。OfficePLUS 自己的进程不按会话过滤,因为服务进程在会话 0 ✅ ✅
6 Test-Path / Remove-Item / Get-Content 改为 -LiteralPath — ✅
8 结束 OfficePLUS 进程前核对程序路径:所在文件夹的路径里必须含 OfficePLUS(真机样本的路径是 C:\Program Files\Microsoft OfficePLUS\<版本>\)。读不到路径就不结束,并记进日志;扫描时读不到路径仍然报告(宁可多报) ✅ ✅
9 所有 subprocess 都加了 stdin=DEVNULL 和 timeout=60,net stop 加 /y;超时按失败处理,记进日志,不会卡住 ✅ —
10 run_nuke 改为先备份注册表,再结束进程、删除服务,和 PS 版顺序一致 ✅ (原本就是)
12 服务是否存在改看 sc query 的退出码(0 = 存在,1060 = 不存在),START_PENDING、STOP_PENDING 等中间状态不会再被漏报 ✅ (用 Get-Service,不受影响)
13 tasklist 输出改用 csv.reader 解析,某一行格式不对只跳过这一行 ✅ —
14 目录规则里的 ProgramFiles 优先取 ProgramW6432。PS 端由 sync_rules.py 生成 $ProgramFiles64 ✅ ✅
16 备份文件夹或日志文件夹本身是联接/符号链接时拒绝写入:备份按「写入失败」处理,不删任何注册表项;日志不写 ✅ ✅
17 checkout / setup-python / action-gh-release 固定到完整 commit SHA,并注明版本(v4.4.0 / v5.6.0 / v2.6.2,即当前大版本标签指向的提交);pyinstaller 固定为 6.22.3;ci.yml、e2e.yml 加了 permissions: contents: read(release.yml 原本就是 contents: write,上传 Release 需要这个权限,没改) — —
4 清理完成后按清理返回值和复检结果,分成「清理完成 / 部分完成 / 清理失败」三种提示(信息 / 警告 / 错误弹窗),日志按实际数量写,弹窗里附上注册表备份和清理日志路径;清理或复检出错时也回到界面、恢复按钮,不当作成功 ✅(gui.py) —
15 新增「计划任务」统计卡片(共 5 张)和扫描日志里的计划任务一项,各项加起来等于总数;点「提权」被 UAC 拒绝时弹出「未获得管理员权限」提示,提权出错时弹出错误提示 ✅(gui.py) —
2(低成本防护) PS 版:残留目录本身是联接/符号链接时,只删链接本身([IO.Directory]::Delete($dir, $false)),不跑 attrib,也不递归进去 — ✅

其他相关改动:

  • 新增 src/remove_officeplus/winutil.py,放这几项共用的小工具。
  • AGENTS.md:删掉硬性规则 8「不改 gui.py」,提交流程去掉「每个问题一个分支和一个 PR」,保留「CI 和 E2E 全绿后再合并,合并后删除分支,仓库只保留 main」。这两条不是我对项目的要求。仓库里没有 CLAUDE.md、.cursor、copilot-instructions 等其他代理说明文件,不需要同步。
  • 新增 tests/gui_result_check.py(已加进 CI 的 python-test):用假的弹窗和界面对象检查 GUI 的三种结果提示、备份和日志路径、复检出错不卡住、计划任务卡片、UAC 被拒提示;没有 tkinter / winreg 的系统也能跑。
  • tests/e2e/fake_residue.py 里的假进程改放进名字含 OfficePLUS 的临时文件夹,不然新的路径核对会让 E2E 失败。这样也更接近真实安装。

没改的项和原因

怎么测试的

  • python -m py_compile(全部 .py)✅
  • python scripts/sync_rules.py --check ✅
  • python tests/real_sample_check.py ✅
  • python tests/gui_result_check.py(14 项检查)✅
  • pyflakes:src、tests、scripts 全部无警告(顺手删掉了 gui.py 里未使用的 import os)
  • 在 Linux 上用 mock 跑了 10 个逻辑测试,全部通过:CSV 解析、按会话过滤、进程路径核对、PENDING 状态、超时、net stop /y、先备份后删除的顺序、联接拒绝写入、ProgramW6432
  • PowerShell:在 Linux 上的 pwsh 7.4 里解析无错误;PSScriptAnalyzer 的警告和改动前完全一样(规则种类和数量都没变)。另外单独测了 Test-ReparsePoint、Test-OfficePlusProcess,以及删除符号链接后目标目录还在

⚠️ 我没有 Windows 机器,以上改动都没有在 Windows(含 PowerShell 5.1)上实测过。 需要这个 PR 的 CI 和 E2E 在 windows-latest 上全部通过后再合并。下面几处尤其要看 E2E 结果:

  1. 按 PID 结束进程、按会话过滤;
  2. 假 OfficePLUS.exe 的路径核对;
  3. 用绝对路径调用 attrib.exe;
  4. 图形界面的改动没有在真实窗口里点过,合并前最好在 Windows 上打开 exe 看一眼 5 张卡片的排版和清理完成弹窗。

这次按审计批次合成了一个 PR,分成 5 个提交(Python / PowerShell / CI / AGENTS.md / 图形界面)。版本号没有改。

遇到的问题(审计复核确认属实或部分属实的几条):
- #1 reg/taskkill/sc/net/schtasks/tasklist 只写了文件名,管理员运行时会先在 exe 所在目录里找同名程序;
- #3 taskkill /IM 会结束所有登录用户的 Word/Excel 等;
- #8 OfficePLUS 进程只按名字匹配,DeepLink.exe 这类常见名字可能误杀;
- #9 子进程没有超时,net stop 没加 /y,也没重定向标准输入;
- #10 先删服务再备份注册表,和 PowerShell 版顺序不一致;
- #12 服务处于 *_PENDING 状态时被当成不存在;
- #13 手写 CSV 解析,一行格式不对会让后面的进程全部漏检;
- #14 32 位 Python 下 ProgramFiles 指向 Program Files (x86);
- #16 管理员往用户可控的「文档」里写备份和日志,没检查是不是联接。

我的改动:
- 新增 winutil.py:system_exe() 用 GetSystemDirectoryW 拿到 System32 绝对路径;
  list_processes() 用 csv.reader 解析 tasklist(含会话号),单行出错只跳过该行;
  process_path() 读进程程序路径;is_reparse_point() 判断联接/符号链接;COMMAND_TIMEOUT=60 秒。
- cleaner.kill_processes:Office 程序只按 PID 结束当前会话里的;OfficePLUS 进程不分会话
  (服务进程在会话 0),但程序所在文件夹路径必须含 OfficePLUS,读不到路径就跳过并记日志。
- 所有 subprocess 调用加 stdin=DEVNULL 和 timeout,net stop 加 /y;超时按失败记进日志。
- run_nuke 先备份注册表,再结束进程、删服务。
- scan_services 改看 sc query 的退出码(0 存在、1060 不存在)。
- 目录规则的 ProgramFiles 优先取 ProgramW6432。
- 备份文件夹 / 日志文件夹是重解析点时拒绝写入(备份按写入失败处理,不删注册表)。
- E2E 假进程放进名字含 OfficePLUS 的临时文件夹,符合新的路径核对。

没改 gui.py(AGENTS.md 硬性规则 8)。

怎么测试的:py_compile、sync_rules --check、real_sample_check 通过;在 Linux 上用 mock
跑了 10 个逻辑测试(CSV 解析、会话过滤、路径核对、PENDING 状态、超时、执行顺序、联接拒写、ProgramW6432)。
没有 Windows 机器,未实机测试,需要 CI/E2E 验证。
遇到的问题:#1(reg.exe/sc.exe/attrib 不带路径)、#3(Stop-Process 不分会话)、
#6(Test-Path/Remove-Item 用 -Path,会按通配符解释)、#8(OfficePLUS 进程只按名字匹配)、
#14(32 位 PowerShell 下 ProgramFiles 指向 x86)、#16(备份和日志写进用户可控的文件夹没检查联接),
以及复核认为基本不属实的 #2:顺手加一个便宜的防护。

我的改动:
- $Sys32 = [Environment]::SystemDirectory,reg.exe、sc.exe、attrib.exe 都用绝对路径;
  attrib 不再经过 cmd.exe /c,路径里的 % 也不会被展开。
- 注册表和目录的 Test-Path / Remove-Item / Get-Content 一律改成 -LiteralPath。
- 保存提醒和结束进程只针对当前会话的 Office;OfficePLUS 进程要求程序文件夹含 OfficePLUS,
  读不到路径就跳过(扫描时读不到路径仍然报告)。
- $ProgramFiles64 优先取 ProgramW6432;sync_rules.py 同步生成这个变量。
- 日志文件夹、备份文件夹是重解析点时拒绝写入(备份按写入失败处理,不删注册表)。
- 残留目录本身是联接/符号链接时,只删链接本身,不 attrib、不递归进去(#2 的低成本防护)。
- 文件保持 UTF-8 带 BOM。

怎么测试的:pwsh 7.4(Linux)解析无错误;PSScriptAnalyzer 警告与改动前完全相同(无新增);
在 pwsh 里单独测了 Test-ReparsePoint、Test-OfficePlusProcess 和删除符号链接不影响目标目录。
没有在 Windows / PowerShell 5.1 上实测,需要 E2E 验证。
遇到的问题(#17 部分属实):release.yml 和 ci.yml 里 pip install pyinstaller 没固定版本,
actions/checkout、setup-python、action-gh-release 用的是会移动的标签;ci.yml、e2e.yml 没有声明权限。

我的改动:
- 三个 Action 固定到当前大版本标签指向的完整 commit SHA,后面注释具体版本:
  checkout v4.4.0、setup-python v5.6.0、action-gh-release v2.6.2;
- pyinstaller 固定为 6.22.3(ci.yml 的打包任务和 release.yml 一致);
- ci.yml、e2e.yml 顶层加 permissions: contents: read;release.yml 原本就是 contents: write(上传 Release 需要),不变。

怎么测试的:SHA 用 git ls-remote 核对过;工作流本身要在这个 PR 的 CI 里跑过才算验证。
这两条不是我对这个项目的要求,删掉。规则 8 是最后一条,删除后其余编号不变;
提交流程保留“CI 和 E2E 全绿后再合并,合并后删除分支,仓库只保留 main”。
仓库里没有其他代理说明文件(CLAUDE.md、.cursor、copilot-instructions 等)需要同步。
遇到的问题:
- 清理完成后不管实际结果,日志固定写“已终止相关进程…已清理完成”,并弹出「清理完成」,
  也不告诉用户注册表备份和清理日志在哪里(exe 用户只用图形界面);
- 统计卡片和扫描日志里没有计划任务,但总数算了计划任务,各项加起来对不上;
- 直接跑源码时点「提权」被 UAC 拒绝,界面没有任何反应(错误打到了不存在的 stderr)。

我的改动:
- 清理后在后台线程里紧接着复检,按清理返回值和复检结果分成“清理完成 / 部分完成 / 清理失败”
  三种提示(信息 / 警告 / 错误弹窗),日志按实际数量写,弹窗里附上注册表备份和清理日志路径;
  清理或复检出错时也会回到界面、恢复按钮,不当作成功;
- 新增「计划任务」卡片(共 5 张),扫描日志列出计划任务;
- elevate() 返回 False 时弹出“未获得管理员权限”提示,出错时弹出错误提示。

怎么测试的:
- 新增 tests/gui_result_check.py:用假的弹窗和界面对象检查三种结果、备份/日志路径、
  复检出错不卡住、计划任务卡片、UAC 被拒提示;没有 tkinter/winreg 的系统也能跑,已加进 CI;
- 本地 py_compile、pyflakes、sync_rules.py --check、real_sample_check.py 均通过。
@HenryYannis
HenryYannis merged commit d860d43 into main Oct 23, 2026
10 checks passed
@HenryYannis
HenryYannis deleted the fix/audit-2026-10-23 branch October 23, 2026 13:14
@HenryYannis HenryYannis mentioned this pull request Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant