一、概述
没有 Web 服务器配置,手里的日志和文件就是一堆没有坐标的碎片。
站点目录在哪、日志写到哪、请求转到哪、某个 location 是不是压根不记日志——四个问题只在配置文件里有答案。
跳过这一层直接分析 access.log,你连自己在读哪台机器的哪一段时间都说不清,更解释不了为什么关键时段「什么都没有」。
要做的是一张「路由图」:把散落的工件归位到具体的站点、路径和日志文件上,顺带把配置本身当证据读。
在取证流程中的位置:这一层自己几乎不产出结论,却决定后面所有分析是否成立。站点映射错了,第 02 篇的日志分析跟着全错;access_log 路径找错了,「无访问记录」就是个伪结论。上游依赖 取证工件地图(Linux 侧) 的目录清单与 系统日志深度分析 的轮转认知;下游支撑网站目录取证(第 2 步)、访问日志分析(第 4 步)和报告里的归因表述。
涉及的证据形态——具体到文件,不是「配置文件」这种大类:
| 形态 |
位置 |
取证价值 |
| Nginx 主配置 |
/etc/nginx/nginx.conf、/www/server/nginx/conf/nginx.conf(宝塔) |
★★★★★ 生效配置的入口 |
| 站点配置 |
conf.d/*.conf、conf/vhost/nginx/*.conf、sites-available/ + sites-enabled/ 软链 |
★★★★★ 站点与日志映射 |
| Apache 主配置 |
/etc/apache2/apache2.conf、/etc/httpd/conf/httpd.conf |
★★★★ |
log_format 定义 |
主配置内,可多个 |
★★★★★ 决定字段顺序 |
.htaccess |
站点目录逐级向上查找 |
★★★★ 隐藏重写规则 |
mods-enabled/ |
Apache 模块加载状态 |
★★★ 决定 .htaccess 是否生效 |
| 访问/错误日志 |
/var/log/nginx/、/www/wwwlogs/(面板环境) |
★★★★★ |
| 在线状态页 |
/server-status?auto、/server-info |
★★★★★ 活证据(需授权窗口) |
⚠️ 本篇最容易被忽略的一点:面板环境的路径与源包完全不同。 源包装在 /etc/nginx、日志在 /var/log/nginx,宝塔装在 /www/server/nginx/conf、日志在 /www/wwwlogs。「在 /var/log/nginx/ 没找到日志」和「这个站点没有日志」是两件完全不同的事,见 4.2 字段与路径判读。
哪个配置文件真正生效;每个站点的 root / DocumentRoot 实际指向哪里;哪个 location 的日志被关掉了、日志文件在哪;proxy_pass 背后的内部拓扑,以及真实客户端 IP 能不能采信;alias 错配、auto_prepend_file、.htaccess 构成的配置层攻击面;某时段日志缺失是轮转、删除还是压根没记。
注意配置层与 PHP 层的分界:篡改机制的核心可能就写在 .user.ini 里,而它不是 Nginx 配置——只读 nginx.conf 会把最关键的证据整个漏掉。
请求体内容、文件上传内容、命令执行结果,配置层看不到这些。攻击者实际改了哪个文件,要靠文件时间戳与内容去比。.htaccess 里写了什么不等于生效的行为,它受 AllowOverride 和模块加载约束。至于在生产中的被入侵主机上执行 nginx -T 拿全量配置——它会实际加载配置,见 4.1 定位与生效。
读者前提:HTTP 请求/响应结构、Linux 目录树与权限模型、进程与端口的基本认知,以及 取证流程与原则 里的哈希固定与只读挂载要求。Windows 侧的等价工件(W3C 字段顺序、IIS 站点配置)见 IIS 配置与日志,两边的字段顺序可以对照着记。
二、核心原理
2.1 配置文件的组织方式
Nginx 的配置是层级化的:main → http → server → location。取证时必须理解「上下文」,同一个指令在不同层级含义完全不同。
| 发行版/安装方式 |
主配置 |
站点配置目录 |
站点根目录 |
日志目录 |
| apt / yum 安装 |
/etc/nginx/nginx.conf |
conf.d/*.conf |
/usr/share/nginx/html 或自建 |
/var/log/nginx/ |
| 源码编译(官方 tar 包) |
/usr/local/nginx/conf/nginx.conf |
conf/extra/、conf/ 下直接写 |
编译时指定 |
同上 |
| 宝塔生成 |
/www/server/nginx/conf/nginx.conf |
conf/vhost/*.conf |
/www/wwwroot/<site>/ |
** /www/wwwlogs/** |
| 宝麒麟等面板 |
路径随版本 |
conf/vhost/ |
/www/wwwroot/ |
/www/wwwlogs/ |
| Debian 惯例 |
/etc/nginx/nginx.conf |
sites-available/ + sites-enabled/ 软链 |
/var/www/html |
/var/log/nginx/ |
| Apache (Debian) |
/etc/apache2/apache2.conf |
sites-available/ + sites-enabled/ |
/var/www/html |
/var/log/apache2/ |
| Apache (RHEL/CentOS) |
/etc/httpd/conf/httpd.conf |
conf.d/*.conf |
/usr/share/httpd/html |
/var/log/httpd/ |
⚠️ sites-enabled/ 只是软链接目录。 取证时要看软链接指向谁,而不是只看 sites-enabled/ 里有什么——攻击者或运维可能直接改了 sites-available/ 下的原文件,也可能有孤儿配置(available 里有、enabled 里没链,属未生效)。
最关键的一步:拿到真正生效的完整配置。
方式一:从镜像里静态找(不执行任何东西,最安全)
find /mnt/df -maxdepth 5 -name 'nginx.conf' -o -name 'httpd.conf'
-o -name 'apache2.conf' 2>/dev/null
方式二:确认主配置里 include 了什么(读文件,不执行)
grep -nE '^\s*include' /mnt/df/etc/nginx/nginx.conf
方式三:在目标副本上执行,输出全量生效配置(生产主机上禁止)
nginx -T 2>&1 | head -100
apachectl -S 2>&1 | head -40 # 列出 vhost 与 DocumentRoot 对应关系
这是本篇最容易被搞错、也最影响后续分析的部分。 Nginx 的 combined 格式定义如下:
log_format combined '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent"';
逐字段拆解(注意 $remote_addr 之后是 -,再之后是 $time_local,$remote_user 在方括号内):
| 序 |
字段 |
含义 |
取证用途 |
| 1 |
$remote_addr |
客户端 IP(TCP 对端)。若前置了反向代理,这里是代理 IP |
★★★★★ 定位攻击源 |
| 2 |
- |
字面连字符,不是字段 |
固定占位 |
| 3 |
$remote_user |
HTTP Basic/Digest 认证用户名,未认证时为 - |
区分认证访问 |
| 4 |
$time_local |
本地时区时间,格式 [18/Mar/2024:02:14:33 +0800] |
★★★★★ 时间线基准 |
| 5 |
$request |
完整请求行:"GET /a.php?x=1 HTTP/1.1" |
★★★★★ 还原行为 |
| 6 |
$status |
响应状态码 |
200 成功、403 禁目录、404 不存在、500 脚本错误 |
| 7 |
$body_bytes_sent |
响应体字节数(不含响应头) |
估算上传/下载量 |
| 8 |
$http_referer |
Referer 头,无则 - |
判断是否同源;WebShell 请求常无 Referer |
| 9 |
$http_user_agent |
UA,无则 - |
工具指纹 |
🔴 高频错误提醒:$remote_addr 之后是字面 -,再之后是 $time_local。很多解析脚本错写成「IP → 时间 → 用户名」的顺序,或者把方括号里的时间当成第 3 字段。解析 combined 日志必须按上面的完整结构来做,第 7 篇和本节的脚本都严格遵守此顺序。
其他需要认识的格式:
| 格式名 |
字段 |
特点 |
main |
$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent |
无 referer/UA |
combined |
上表 9 项 |
默认,最常用 |
自定义 dlog |
常见加 $request_time、$upstream_response_time、$server_protocol |
自定义格式可能没有 UA/referer,也可能含 cookie——务必逐台核对 |
⚠️ 取证时必须核对 log_format 的实际定义,不能假设一定是 combined。一台机器可能有多个 log_format 定义,多个 access_log 引用不同格式。用 grep -n 'log_format' nginx.conf 逐一列出。
2.3 关键指令的取证意义
root vs alias(最危险的一类配置错误)
两者的路径拼接规则不同,这是很多「任意文件读取」漏洞的根因:
location /static/ { alias /data/files/; }
请求 /static/../etc/passwd → /data/files/../etc/passwd → /data/etc/passwd
若写成 root /data/files;(无尾斜杠),/static/x → /data/files/static/x,语义完全不同
| 指令 |
语义 |
取证关注点 |
root |
替换 location 前缀 |
拼错会导致访问到计划外目录 |
alias |
整段替换 location 前缀 |
少写尾斜杠或写错路径极易造成目录穿越,务必逐个核对 |
try_files |
依次尝试文件/目录 |
$uri$uri/ 落到目录可能触发索引列表泄露 |
access_log off —— 痕迹消失开关
access_log off 本身不是恶意证据(很多站点用它降压),但它的取证意义是:该 location 下的行为没有 Nginx 侧记录。此时必须转向 PHP 层(/www/wwwlogs/ 面板日志、PHP-FPM 日志)或数据库审计。** proxy_pass —— 看清真实拓扑**
location /api/ { proxy_pass http://127.0.0.1:8080/; } # 末尾有斜杠:会剥掉 /api/ 前缀
location /api/ { proxy_pass http://127.0.0.1:8080; } # 末尾无斜杠:路径传递方式不同!
| 观察 |
意义 |
proxy_pass 指向 127.0.0.1:某端口 |
有内部服务,可查该端口的进程与日志 |
| 指向内网 IP |
存在跨主机调用,内网主机可能也在检材范围内 |
上游转发明文 Authorization 或 Cookie |
凭据可能被写入下游日志 |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for |
后端日志里的是真实 IP,取证时可直接采信 |
| 未设置该头 |
后端日志里全是代理 IP,不能直接当作客户端 IP |
sub_filter / add_header —— 内容与响应头被程序改写
sub_filter_types text/html;
sub_filter 'https://cdn.old.com' 'https://cdn.new.com'; # 内容替换
add_header 'X-Powered-By' 'PHP/5.6.40'; # 伪造版本号,干扰技术栈判断
add_header 伪造版本号在真实入侵中出现过——它让基于响应头的技术栈判断失效,取证时要意识到这一点。
Apache 侧:mod_rewrite 与 .htaccess
全局重写规则
RewriteRule ^/old/(.*)$ /$1 [R=301,L]
.htaccess 隐藏规则(放在任意目录,逐级向上查找)
RewriteEngine On
RewriteRule ^.(git|svn|env) - [F] # 禁访问版本库
RedirectMatch 404 ^/backups?/ # 隐藏备份目录
[R=301,L] 通常是管理员主动做的跳转;[R=302] 出现在备份路径上,可能是后门在给自己打掩护。
.htaccess 是逐级向上查找的:请求 /a/b/c.php 时 Nginx 不读 .htaccess,但 Apache 会依次检查 /a/b/、/a/、/。必须逐层找,不能只查站点根目录。
mod_rewrite 生效需要 AllowOverride All;若为 None,站点目录里的 .htaccess 完全无效——此时「配置看起来在禁某目录」实际是失效的假象。
2.4 Apache 特有工件的取证价值
| 文件/接口 |
内容 |
价值 |
/var/log/apache2/access.log |
访问日志(格式同 Nginx) |
★★★★★ |
/var/log/apache2/error.log |
错误日志,含 PHP 警告与栈 |
★★★★ |
/var/log/apache2/other_vhosts_access.log |
多 vhost 时自动追加,容易被忽略 |
★★★★ |
/var/log/apache2/access.log.*.gz |
轮转历史 |
★★★★ |
server-status(/server-status?auto) |
活动连接、正在处理的请求、子进程 PID |
★★★★★ 在线时价值极高 |
server-info |
模块列表与完整配置 |
★★★★ |
/etc/apache2/mods-enabled/ |
启用了哪些模块 |
判断 mod_rewrite/mod_php 是否真能用 |
/etc/apache2/sites-enabled/ |
软链接,要 ls -l 看指向 |
★★★★★ |
server-status 在取证中的特殊价值:它实时列出当前正在处理的请求 URL、来源 IP、子进程 PID。若取证窗口内还能访问,可以直接抓到攻击者正在进行的操作——这是任何离线日志都给不了的活证据。但这必须在委托方明确授权的在线取证窗口内进行,且要立即截图固定。
Windows 侧的同类配置(站点配置、日志目录、W3C 字段顺序)见第 02 篇,字段语义可以对照着看。
三、操作步骤
3.1 第 1 步:定位并固定配置
1) 找到所有候选主配置
find /mnt/df -maxdepth 5 ( -name nginx.conf -o -name httpd.conf
-o -name apache2.conf ) 2>/dev/null | tee /evidence/ws-conf-list.txt
2) 完整复制并固定哈希(先复制,再分析)
mkdir -p /evidence/nginx-conf /evidence/apache-conf
cp -rp /mnt/df/etc/nginx /evidence/nginx-conf/ 2>/dev/null
cp -rp /mnt/df/etc/apache2 /evidence/apache-conf/ 2>/dev/null
cp -rp /mnt/df/etc/httpd /evidence/apache-conf/ 2>/dev/null
find /evidence/nginx-conf /evidence/apache-conf -type f
-exec sha256sum {} + > /evidence/ws-conf-hashes.txt
3) 看主配置的 include 关系
grep -nE '^\s*include' /evidence/nginx-conf/etc/nginx/nginx.conf 2>/dev/null
4) 配置文件的修改时间(判断是否被改过的关键)
find /evidence/nginx-conf /evidence/apache-conf -type f
-printf '%T+ %p
' 2>/dev/null | sort | tail -20
或: find ... -name '*.conf' -newermt '2024-03-01' -ls
3.2 第 2 步:提取站点与日志映射
所有 server 块、root、access_log、error_log 一次看全
grep -rnE 'server_name|root |alias |access_log|error_log|proxy_pass'
/evidence/nginx-conf/etc/nginx/ 2>/dev/null
Apache 侧
grep -rnE 'ServerName|ServerAlias|DocumentRoot|ErrorLog|CustomLog'
/evidence/apache-conf/ 2>/dev/null
ls -l /evidence/apache-conf/etc/apache2/sites-enabled/ 2>/dev/null # 看软链接指向
把结果整理成映射表,这是后续所有分析的基准:
| 域名 |
root 实际路径 |
access_log 路径 |
是否 off |
备注 |
digiforensics-site |
/www/wwwroot/digiforensics-site/ |
/www/wwwlogs/digiforensics-site.log |
否 |
主站 |
cdn.digiforensics |
/data/cdn/ |
off |
是 |
★ 无访问记录 |
3.3 第 3 步:查隐藏规则、危险配置与站外目录
.htaccess 逐级找(Apache 才会生效)
find /mnt/df/www/wwwroot /mnt/df/var/www -name '.htaccess' 2>/dev/null
find /mnt/df/www/wwwroot -name '.htaccess' -exec grep -HnE 'Rewrite|Redirect|php_value|auto_prepend' {} + 2>/dev/null
grep -rn 'AllowOverride' /evidence/apache-conf/ 2>/dev/null # 决定 .htaccess 是否真生效
Nginx 侧的隐藏规则
grep -rnE 'Redirect|return 40[13]|deny' /evidence/nginx-conf/ 2>/dev/null | head -30
.user.ini(PHP 层面劫持,详见第 06 篇)
find /mnt/df/www/wwwroot -name '.user.ini' -exec grep -Hn . {} + 2>/dev/null
站外目录:面板装的站点有时把路径指到 /www/wwwroot 之外,
这类"计划外目录"是篡改网站改 JS 的常见落点
grep -rnE '^\s*(root|alias) ' /evidence/nginx-conf/etc/nginx/ 2>/dev/null
| grep -v '/www/wwwroot' | grep -v '/usr/share/nginx'
ls -la /mnt/df/data/ 2>/dev/null # 结果需逐个核实:是否该存在?内容是否像正常业务?
3.4 第 4 步:解析访问日志与核查轮转
awk -F'"' 是 combined 日志的实用技巧:把行按双引号切成 3 段,$1 是「IP - user [time]」,$2 是请求行,$3 是 referer + UA。不需要写正则就能可靠地拿到请求行。
Z=/mnt/df/www/wwwlogs/digiforensics-site.log
awk -F'"' '{split($1,a," "); print a[1], $2}' "$Z" | sort | uniq -c | sort -rn | head -20
关键:按 IP + 路径组合看,能发现"只对某个 URL 反复访问"的扫描/后门行为
服务还在跑的话,日志随时会被轮转覆盖——这是第 01 篇强调的抢时间理由,在 Web 取证里要具体到文件层面:
ls -l /mnt/df/www/wwwlogs/ /mnt/df/var/log/nginx/ 2>/dev/null
digiforensics-site.log 当前
digiforensics-site.log.1 昨天
digiforensics-site.log-20240317.gz 已压缩
cat /mnt/df/etc/logrotate.d/ngi