跳转到帖子

授权内网测试中发现“可写目录+高权限服务”,如何定位、验证、留痕与修复?

精选回复

发布于

背景

在授权内网渗透测试中,一个比较常见、也容易被误判的问题是:普通域用户能够修改某个目录或服务程序文件,而对应的 Windows 服务以 LocalSystem、域管理员或其他高权限账户运行。这里的风险不在于“发现了一个可写目录”本身,而在于该目录是否处于高权限执行链路中。

这类问题应当按照“权限边界确认—执行关系确认—最小化验证—日志核对—修复复测”的顺序处理。不要直接替换生产服务二进制、重启关键服务或投递真实后门来证明影响,这些操作既可能造成中断,也会让后续取证变得困难。

问题定位:先确认写权限,再确认执行主体

假设测试范围为 APP01,发现普通测试账户对 C:\Program Files\Acme Agent\bin 具有修改权限。第一步不是立即利用,而是确认该目录中的文件是否被 Windows 服务加载。

可以在获得授权的远程管理会话中执行以下检查。命令只读取服务配置和目录 ACL,不会停止服务或修改文件。

$result = Invoke-Command -ComputerName 'APP01' -ScriptBlock {
    $services = Get-CimInstance Win32_Service
    $services |
        Where-Object { $_.StartName -match 'LocalSystem|Administrator|svc_' } |
        Select-Object Name, StartName, State, PathName

    Write-Output '--- ACL ---'
    icacls 'C:\Program Files\Acme Agent\bin'
    Write-Output '--- Files ---'
    Get-ChildItem 'C:\Program Files\Acme Agent\bin' -File |
        Select-Object FullName, Length, LastWriteTime
}
$result

重点关注以下三项是否同时成立:

  • PathName 指向可被测试账户写入的目录,或者引用了该目录下的 DLL、脚本、配置文件。
  • 服务的 StartName 为 LocalSystem、LocalService、NetworkService、管理员账户或高权限域服务账户。
  • 普通用户或其所属组在目录或文件 ACL 中拥有 Write、Modify、CreateFiles 或删除权限。

需要区分“目录可写”和“目标文件可替换”。如果服务文件本身属于 TrustedInstaller 或管理员,且目录只允许创建新文件,风险可能与 DLL 搜索顺序、配置加载或临时文件覆盖有关;如果服务二进制及其依赖文件均可直接修改,则通常属于更明确的权限边界问题。

技术分析:确认是否存在高权限执行链路

服务路径不能只看字符串。例如以下几种情况需要分别判断:

  • 服务路径包含空格但未使用引号,例如 C:\Program Files\Acme Agent\agent.exe。这种配置可能引入路径解析风险,但是否可利用还取决于根目录和中间目录的写权限。
  • 服务加载的 DLL 使用相对路径,或通过配置文件、环境变量指定插件目录。此时需要确认加载顺序和服务实际工作目录。
  • 服务启动账户是高权限账户,但服务只有系统启动时执行一次。影响窗口可能较小,仍应记录为权限边界问题,并避免在未经批准的情况下重启服务。
  • 目录权限来自继承 ACL、组嵌套或软件部署策略。不能只根据界面上的“用户具有写入权限”下结论,应记录具体 ACE 来源。

对于可疑服务,建议收集服务配置、文件哈希、签名状态和 ACL 快照。文件哈希用于后续确认测试前后是否发生意外变化,不能把哈希变化本身当作漏洞证据。

Invoke-Command -ComputerName 'APP01' -ScriptBlock {
    $p = 'C:\Program Files\Acme Agent\bin\agent.exe'
    Get-Item $p | Select-Object FullName, Length, LastWriteTime
    Get-FileHash $p -Algorithm SHA256
    Get-AuthenticodeSignature $p |
        Select-Object Status, SignerCertificate
    icacls $p
    sc.exe qc 'AcmeAgent'
}

最小化验证方法

在测试报告中,通常不需要通过替换服务文件来证明提权。可采用以下较低风险的验证方式:

  • 使用授权测试账户执行 ACL 读取,保存命令输出和时间戳,证明该账户确实拥有写入或修改权限。
  • 由系统负责人在维护窗口创建一个无害的测试文件,确认权限是否如报告所述。测试文件应放在单独的测试目录,不应覆盖现有二进制或 DLL。
  • 由运维人员在变更审批后执行一次受控服务重启,并由测试人员观察服务是否从该目录读取文件。没有审批时不要自行重启。
  • 如果服务支持自带的诊断或插件加载日志,优先使用诊断模式验证加载路径,而不是投递可执行载荷。

验证过程应明确记录测试账户、目标主机、目录、原始 ACL、文件哈希、操作时间、变更单号以及回滚结果。若涉及域服务账户,还应避免读取或导出凭据,不需要凭据内容就可以完成权限链路判断。

日志留痕与关联分析

这类问题的留痕通常分散在文件系统、服务控制管理器和进程创建日志中。建议在测试前确认日志策略已经开启,并提前通知蓝队或系统负责人,避免把授权动作误判为未授权入侵。

日志位置事件或内容用途
Security4688关联服务启动后创建的进程及命令行
Security4697、7045确认服务安装或变更
Security5140、5145确认 SMB 共享访问及访问权限检查
Sysmon1、7、11观察进程创建、映像加载和文件创建
System7036、7040观察服务状态和启动类型变化

如果启用了对象访问审核,文件修改事件通常还需要配合 SACL 才会产生,单纯开启“成功审核”并不会自动记录所有文件写入。测试结束后应将时间窗口、源地址、账户和目标文件与日志进行交叉核对,而不是只截取一条事件作为证据。

修复建议

  • 将程序目录和服务文件的所有者设置为 TrustedInstaller 或受控管理员组,普通用户只保留读取和执行权限。
  • 移除对 Users、Authenticated Users、Domain Users 等宽泛主体的 Modify、Write、Delete 权限。若软件确实需要写入,应把数据目录与程序目录分离。
  • 服务运行账户遵循最小权限原则,优先使用 gMSA 或专用服务账户,禁止复用域管理员账户。
  • 对服务路径、插件目录和配置文件执行周期性 ACL 检查,重点检查继承权限、组嵌套和软件部署脚本是否会重新放开写权限。
  • 开启适当的进程创建、服务变更和文件访问审计,并将高权限服务目录的异常写入纳入告警。
  • 修复后重新采集 icacls、服务配置和文件哈希,使用普通测试账户进行一次只读验证,确认无法创建、覆盖或删除程序目录中的文件。

报告结论建议

报告中不要只写“目录权限过大,可提权”。更准确的表述应包含:受影响主机、测试账户、目录和文件、具体 ACL、服务名称、运行账户、加载关系、是否完成最小化验证、相关日志以及修复后的复测结果。

判定这类问题的关键是执行关系,而不是单独的写权限。只有当低权限主体能够影响高权限服务实际加载或执行的内容时,权限边界风险才成立;如果只是普通业务缓存目录,应结合实际数据敏感性和其他控制措施单独评估。

总结

授权内网测试遇到“可写目录+高权限服务”时,最稳妥的处理方式是先收集证据,再确认服务加载链路,最后在变更审批下进行最小化验证。避免直接替换二进制或重启生产服务,既能降低业务风险,也能让日志、复测和修复建议更加可信。

日志取证与事件时间线示意图
日志证据、时间线与事件还原示意

这类问题建议把“目录可写”和“服务是否实际使用该目录”分开证明。单独发现一个低权限可写目录,不能直接判定为提权风险;关键是确认高权限服务是否会从该目录加载可执行文件、插件、配置或脚本,以及服务是否会在重启、定时任务或特定请求下访问它。

1. 先确认服务身份、启动方式和实际文件路径

Linux 上不要只看 unit 文件,最好同时核对主进程、子进程和打开的文件:

# 服务身份、启动命令和状态
systemctl cat example.service
systemctl show example.service \
  -p User -p Group -p ExecStart -p WorkingDirectory \
  -p Environment -p FragmentPath

# 进程树及运行身份
systemctl status example.service
ps -eo user,group,pid,ppid,lstart,args --forest | grep -E 'example|PID'

# 服务当前打开的文件和目录
pid=$(systemctl show -p MainPID --value example.service)
lsof -p "$pid"
readlink -f /proc/"$pid"/exe
tr '\0' '\n' < /proc/"$pid"/environ

重点关注以下几类路径:

  • ExecStart 指向的程序及其父目录;
  • WorkingDirectory,部分程序会从当前目录加载配置、插件或脚本;
  • 配置中的插件目录、模块目录、脚本目录、临时目录;
  • 动态库搜索路径,例如 LD_LIBRARY_PATH、程序自定义的 plugin_dir;
  • 日志、缓存和上传目录是否会被服务再次解析或执行。

Windows 上建议同时看服务配置、服务进程和文件 ACL:

sc.exe qc ExampleSvc
sc.exe queryex ExampleSvc

# 查看服务路径、账户和启动参数
Get-CimInstance Win32_Service -Filter "Name='ExampleSvc'" |
  Select-Object Name,StartName,State,PathName,ProcessId

# 查看目录及父目录 ACL
icacls "C:\Program Files\Example"
icacls "C:\Program Files\Example\bin"
icacls "C:\ProgramData\Example"

Windows 上尤其要检查服务路径是否未加引号、服务目录或 DLL 搜索路径是否对普通用户可写,以及服务账户是否是 LocalSystem、域高权限账户或具有过宽本地权限的自定义账户。

2. 判断“可写”是否真实且与服务存在关联

建议记录完整权限链,而不是只记录目标目录的权限。Linux 可以这样检查:

# 查看目录每一级的权限、属主和 ACL
namei -l /opt/example/plugins
stat -c '%A %a %U:%G %n' /opt/example /opt/example/plugins
getfacl -p /opt/example/plugins

# 查找服务文件、插件和配置中的路径
grep -RInE 'plugin|module|script|include|load|WorkingDirectory' \
  /etc/systemd/system /usr/lib/systemd/system /etc/example 2>/dev/null

# 检查是否存在符号链接或异常属主
find -L /opt/example -maxdepth 3 -printf '%M %u:%g %p -> %l\n'

要特别注意“父目录可写但目标文件不可写”的情况。普通用户只要能替换目录项、创建符号链接或重命名文件,仍可能影响服务读取的对象。因此需要检查每一级目录的写入、删除和重命名权限,而不是只执行一次 ls -l。

Windows 可使用 PowerShell 展开 ACL,确认是否存在 Users、Authenticated Users、Everyone 等主体的写入、修改或完全控制权限:

$path = 'C:\Program Files\Example\plugins'
(Get-Acl $path).Access |
  Format-Table IdentityReference,FileSystemRights,AccessControlType,IsInherited

Get-ChildItem $path -Recurse -Force |
  ForEach-Object {
    $acl = Get-Acl $_.FullName
    $acl.Access |
      Where-Object {
        $_.IdentityReference -match 'Users|Everyone|Authenticated Users' -and
        $_.FileSystemRights.ToString() -match 'Write|Modify|FullControl'
      } |
      Select-Object @{N='Path';E={$_.Path}},
                    IdentityReference,FileSystemRights,AccessControlType
  }

3. 验证时不要直接替换生产程序

在授权范围内,建议采用“无害可观察验证”,避免直接覆盖服务二进制、替换 DLL 或植入反弹连接。可以按以下顺序做:

  1. 先在变更单或测试窗口中确认服务重启、文件写入和日志采集不会影响业务;
  2. 使用唯一随机标记文件,验证低权限账户确实能在目标目录创建、修改、重命名和删除文件;
  3. 通过文件监控或服务调试日志确认高权限服务是否访问该路径;
  4. 如果必须验证加载行为,使用经过编译和审核的无害测试模块,只记录加载时间、进程身份和固定标记,不执行系统命令、不建立外连;
  5. 验证结束后立即删除测试文件,恢复 ACL,并再次确认服务状态和业务功能。

例如 Linux 可以先只验证权限和审计事件:

testdir=/opt/example/plugins
marker="$testdir/.authorized-test-$(date +%s)"

touch "$marker"
printf 'authorized validation\n' > "$marker"
mv "$marker" "${marker}.renamed"
rm -f "${marker}.renamed"

# 测试期间另开终端观察文件访问
sudo auditctl -w /opt/example/plugins -p wa -k example-plugin-dir
sudo ausearch -k example-plugin-dir -i

如果目标是 systemd 服务,可在测试窗口内结合服务重启前后的审计记录判断是否读取了该目录:

sudo auditctl -w /opt/example/plugins -p r -k example-plugin-read
sudo systemctl restart example.service
sudo ausearch -k example-plugin-read -i
sudo auditctl -W /opt/example/plugins -p r -k example-plugin-read

生产环境不建议长期使用 auditctl 临时规则,验证完成后应移除;正式留痕应落到现有 auditd、Sysmon、EDR 或集中日志平台。

4. 留痕内容要能让别人复核

报告中最好同时保留以下证据:

  • 测试授权范围、主机、账户、时间窗口和变更单编号;
  • 服务名、服务账户、PID、启动命令、父子进程关系;
  • 目标目录及每一级父目录的属主、权限、ACL;
  • 文件原始哈希、大小、时间戳和签名状态;
  • 测试前后服务状态、业务探针结果和监控告警;
  • 文件创建、修改、重命名、删除以及服务读取的审计记录;
  • 验证使用的测试文件名、内容摘要、开始和结束时间;
  • 修复前后的 ACL、服务配置和复测结果。

例如可以对关键文件做基线记录:

sha256sum /usr/local/bin/example /opt/example/plugins/*
stat /usr/local/bin/example /opt/example/plugins/*
systemctl show example.service > /tmp/example.service.before.txt

截图可以作为辅助,但不应替代原始命令输出和集中日志。涉及域环境时,还应记录执行账户的 SID、主机名和时间同步状态,避免后续对不上日志时间线。

5. 修复建议应针对实际加载链路

如果服务只需要读取目录,通常不应让运行账户或普通用户拥有写权限。Linux 可按最小权限调整,例如:

# 示例,具体账户和路径按实际服务替换
sudo chown -R root:example /opt/example
sudo find /opt/example -type d -exec chmod 0750 {} \;
sudo find /opt/example -type f -exec chmod 0640 {} \;

# 可执行文件单独设置
sudo chown root:root /usr/local/bin/example
sudo chmod 0755 /usr/local/bin/example

# 确认没有额外 ACL
getfacl -p /opt/example /opt/example/plugins

如果确实需要运行时写入,应把写入目录与加载目录分离,禁止服务从可写目录加载程序或插件。systemd 还可以增加一定的隔离措施,例如:

[Service]
User=example
Group=example
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/example

这类硬化项需要结合程序实际行为逐项验证,不能直接套用;例如程序确实需要写入 /etc 或某个状态目录时,应改为明确授权特定路径,而不是整体关闭保护。

Windows 侧建议将程序目录和插件目录的写权限收紧到管理员或专用部署账户,服务运行账户只保留必要的读取和执行权限;同时修正未加引号的服务路径、移除不必要的 DLL 搜索目录,并通过应用白名单、代码签名校验或 EDR 规则限制未签名模块加载。修复后要用低权限测试账户重新执行 ACL 检查,并在服务重启、升级和回滚流程中各验证一次,防止安装程序再次把目录权限放宽。

最终风险判断可以按这条链路表述:低权限主体可写目录 → 高权限服务会读取或加载该目录内容 → 触发条件成立 → 可观察到高权限进程访问。其中任何一环没有证据,就应在报告中明确写成“潜在风险”而不是直接定性为已验证提权。

补充一个实际排查时比较好用的思路:不要急着“投放可执行文件验证”,先用审计和文件访问轨迹把证据链做实。这样报告里也更容易说明风险来源,而不是停留在“目录可写”。

1. 用审计确认服务是否真的访问该目录

Linux 上可以临时给可疑目录加 audit 规则,观察高权限进程是否读写、执行、遍历该目录:

# 假设可疑目录是 /opt/example/plugins
sudo auditctl -w /opt/example/plugins -p rwxa -k svc_writable_dir_test

# 重启服务或触发业务动作后查看记录
sudo ausearch -k svc_writable_dir_test -i

# 看具体进程、uid、exe、cwd、访问的文件
sudo ausearch -k svc_writable_dir_test -i | egrep 'type=SYSCALL|type=PATH|exe=|cwd='

如果能看到 uid=0 或服务高权限账号对应的进程访问了这个目录,证据会比单纯权限截图强很多。测试结束记得清理:

sudo auditctl -W /opt/example/plugins -p rwxa -k svc_writable_dir_test

如果环境没有 auditd,也可以用 inotifywait 做轻量观察,但它只能看到文件事件,不能直接证明是谁访问:

inotifywait -m -r /opt/example/plugins \
  -e open,access,modify,create,delete,move,attrib

2. 用 strace 做一次受控验证

如果允许短时间调试,可以对服务主进程挂 strace,只跟文件相关系统调用,噪音会小很多:

pid=$(systemctl show -p MainPID --value example.service)

sudo strace -f -p "$pid" \
  -e trace=open,openat,execve,stat,statx,access,chdir \
  -s 200 \
  -o /tmp/example-service-filetrace.log

然后触发服务重载、业务请求或定时任务,再过滤可疑目录:

grep -F "/opt/example/plugins" /tmp/example-service-filetrace.log

如果出现类似 openat(..., "/opt/example/plugins/xxx.so", ...)、execve("/opt/example/xxx.sh", ...),就基本能说明服务存在从可写路径加载/执行内容的行为。

3. 验证建议用“无害标记”,不要直接放 payload

授权测试里建议优先用可审计的无害标记文件证明加载链路,例如:

  • 如果是配置目录:放一个不会改变业务逻辑的注释或无效配置,观察服务是否读取并报错。
  • 如果是插件目录:优先确认服务日志是否扫描插件名,不建议直接放自定义 so/dll。
  • 如果是脚本目录:可以放只写入固定标记文件的脚本,但要事先确认不会被误执行影响业务。

例如仅用于证明高权限服务执行到了测试脚本:

#!/bin/sh
id > /tmp/example_service_marker.txt
date >> /tmp/example_service_marker.txt

报告里保留:

  • 目录权限:namei -l /opt/example/plugins、getfacl -p /opt/example/plugins
  • 服务身份:systemctl show、ps 输出
  • 访问证据:audit/strace 日志
  • 触发条件:重启服务、访问某接口、定时任务执行时间等
  • 验证结果:标记文件属主、内容、时间戳

4. 修复时别只 chmod,最好同时收紧 systemd 能力

目录权限上,一般原则是:服务运行账号只对必要的数据目录可写,代码、插件、配置、启动脚本目录不可写。

# 示例:代码/插件目录只允许 root 或发布账号写
chown -R root:root /opt/example
chmod -R go-w /opt/example

# 数据目录单独授权给服务账号
mkdir -p /var/lib/example
chown -R example:example /var/lib/example
chmod 750 /var/lib/example

如果服务由 systemd 管理,可以加一些硬化项,降低类似问题的影响面:

[Service]
User=example
Group=example

# 根文件系统尽量只读
ProtectSystem=strict

# 只开放必要写目录
ReadWritePaths=/var/lib/example /var/log/example

# 禁止写 home/tmp 视业务决定
ProtectHome=true
PrivateTmp=true

# 不需要提权能力时建议开启
NoNewPrivileges=true

# 限制可执行映射,可能影响 JIT/某些运行时,需测试
MemoryDenyWriteExecute=true

注意 ProtectSystem=strict、MemoryDenyWriteExecute 这类配置可能影响部分服务,建议先在测试环境验证,再灰度上线。

5. Windows 场景可以重点看服务二进制和 DLL/插件搜索路径

如果是 Windows,除了目录 ACL,还要看服务是否从可写目录加载 DLL、配置、脚本。几个常用命令:

sc qc ExampleService
sc queryex ExampleService

wmic service where "name='ExampleService'" get Name,StartName,PathName,State

icacls "C:\Program Files\Example"
icacls "C:\Program Files\Example\plugins"

验证访问链路可以用 Procmon 过滤:

  • Process Name is example.exe
  • Path begins with C:\Program Files\Example\plugins
  • Operation is CreateFile / Load Image / RegQueryValue

如果出现高权限服务进程对可写目录下 DLL、EXE、PS1、配置文件的 Load Image 或读取行为,再结合 ACL,就比较容易形成完整风险结论。

最后,报告结论建议写成条件式:低权限用户可写目录 + 高权限服务在某触发条件下加载/执行该目录内容 + 已用无害标记验证。这样比直接定性“可提权”更稳,也方便整改方复现和修。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。