渗透技巧 -- 伪协议php://input命令执行的原理
文前漫谈
文章首发在Freebuf
研究几种webshell管理工具时遇见,发现几款常见的webshell管理工具都有对php://input的利用
本文记录我对php://input原理的学习和思考,如有疏漏失错,还请联系告知
$HTTP_RAW_POST_DATA的作用和php://input的作用相同,但是PHP 5.6.0. 被废弃了,后面的版本中就是用php://input替代。所有本文省略
官方文档的解释
$_POST数组
当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded
或 multipart/form-data
时,会将变量以关联数组形式传入当前脚本。
php://input 伪协议
php://input 是个可以访问请求的原始数据的只读流。当请求方式是post,并且Content-Type不等于"multipart/form-data"
时,可以使用php://input来获取原始请求的数据。
从POST的几种Content-Type看PHP的$_POST []与 php://input
通常的,在php中我们用$POST[]
数组来处理封装好的POST请求正文中的内容,下面从http post上传时几种不同的表单编码格式分析$POST[]数组和php://input伪协议在处理post内容时的差别
POST表单上传时常见有这几种编码:
- multipart/form-data
- application/x-www-data-urlencoded
- application/json
- text/plain
multipart/form-data (文件上传时的编码)
服务端代码
<html>
<form enctype="multipart/form-data" method="post">
<input type="text" name="name" />
<input type="file" name="upload_file" />
<button type="submit" name="submit" value="Submit">Submit</button>
</form>
<?php
echo "\$_POST[]: ";
var_dump($_POST);
echo "<br>";echo "<br>";
echo "php://input: ";
var_dump(file_get_contents("php://input"));
?>
</html>
上传 upload.txt , form表单如下,文件上传的post包就不多解释了
boundary=—-WebKitFormBoundaryHwjtGxAgxib9xeFe就是multipart分块传输的分界线
这个http包我精简过,注意Content-Type即可
POST /test/demo.php HTTP/1.1
Host: 192.168.1.136
Content-Length: 398
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.1.136
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHwjtGxAgxib9xeFe
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.1.136/test/demo.php
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Connection: close
------WebKitFormBoundaryHwjtGxAgxib9xeFe
Content-Disposition: form-data; name="name"
123456
------WebKitFormBoundaryHwjtGxAgxib9xeFe
Content-Disposition: form-data; name="upload_file"; filename="upload.txt"
Content-Type: text/plain
upload test
------WebKitFormBoundaryHwjtGxAgxib9xeFe
Content-Disposition: form-data; name="submit"
Submit
------WebKitFormBoundaryHwjtGxAgxib9xeFe--
看见后端只有$POST[] 数组接收了form表单上传的内容,正如上面说的,当Content-Type: multipart/form-data;,php://input并不起作用
application/x-www-data-urlencoded
服务端代码
<html>
<form enctype="application/x-www-data-urlencoded" method="post">
<input type="text" name="name" />
<button type="submit" name="submit" value="Submit">Submit</button>
</form>
<?php
echo "\$_POST[]: ";
var_dump($_POST);
echo "<br>";echo "<br>";
echo "php://input: ";
var_dump(file_get_contents("php://input"));
?>
</html>
form表单的内容
POST /test/demo.php HTTP/1.1
Host: 192.168.1.136
Content-Length: 23
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.1.136
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.1.136/test/demo.php
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Connection: close
name=demo&submit=Submit
$_POST[] 和 php://input都能接收到form表单的内容,也正如上面所说,php://input访问的时原始数据的只读流,后端接收到的和http传输的是相同的,但是解析的结果依然不同
这里我突发奇想看看试试如果url编码的结果是什么
把 ‘demo’ 进行再一次编码url全编码,相当于url二次编码了
demo
->urlencode
%31%32%37%2e%30%2e%30%2e%31
这里两者之间就有些差别了,具体原因就是浏览器发包时还会对post请求进行一次url编码,$_POST[]数组的内容是urldecode()之后的,而php://input访问的还是原始数据,也就没有解码
application/json
json格式通常在传输数据时使用,我直接在post包中发送json格式的数据了。其实理所应当的能想到$_POST[]数组是处理不了json格式的数据的,这种情况php只能用php://input读post的数据
总的来说
php://input会接收除了Content_Type: multipart/form-data情况下的内容
而$_POST[]数组只处理类型为
application/x-www-form-urlencoded
或multipart/form-data
的情况,因为只有这两种能被直接写成关联数组的形式(就是字典的形式)
php://input 命令执行的原理
请求头中的Content-Type字段告诉服务器后端用户交互上传的是什么类型的数据,$_POST能处理form表单上传的键值对格式(类似字典)的内容,但是处理如上的json格式或者其他格式内容并不会生效。何况php的Content-Type类型且不止这三种
上面的三个例子也更好的说明了php://input是怎样和为什么要从原始数据的只读流处理post请求包的内容,但这不是php://input能进行命令执行的关键
它能进行命令执行的关键还是看它和什么函数联动,它本身并不能进行RCE,是处理php://input内容的函数出了问题
比如经典的远程文件包含
<?php
include($_GET['file']);
?>
file接收php://input的内容,post body里就是完整的php文件内容,这里的关键就是include()函数,它会解析<?php标签,并把后面的代码当作php代码执行,直到读到?>
或者说直接是
<?php
eval(file_get_contents("php://input"));
?>
这种情况根据上面所说,直接在post body里输入php代码就行,连标签都不用加。(注意php://input和全局变量$_POST不同,它通常配合file_get_contents()函数使用)
比如冰蝎4的默认shell