Code 有點長 一題 XSS 題,關鍵是要繞掉這個 html sanitizer (NodeJS)
1 2 3 4 5 6 7 8 9 10 11 12 13
// js translation of python's html.escape because // this isn't in nodejs stdlib i think // src - https://github.com/python/cpython/blob/18347417b06d09fa7eeac68f88eb1e720e925c65/Lib/html/__init__.py#L12 functionhtmlEscape(s, quote=true) { s = s.replace("&", "&"); // Must be done first! s = s.replace("<", "<"); s = s.replace(">", ">"); if (quote) { s = s.replace('"', """); s = s.replace('\'', "'"); } return s; }
from random import randrange from Crypto.Cipher import AES from Crypto.Util.Padding import pad from hashlib import md5 p = defclockadd(P1,P2): x1,y1 = P1 x2,y2 = P2 x3 = x1*y2+y1*x2 y3 = y1*y2-x1*x2 return x3,y3
defF(p): # caveat: caller must ensure that p is prime classF: def__init__(self,x): self.int = x % p def__str__(self): returnstr(self.int) __repr__ = __str__ def__eq__(a,b): return a.int == b.int def__ne__(a,b): return a.int != b.int def__add__(a,b): return F(a.int + b.int) def__sub__(a,b): return F(a.int - b.int) def__mul__(a,b): return F(a.int * b.int) def__div__(a,b): # caveat: caller must ensure that b is nonzero return a*F(pow(b.int,p-2,p)) return F
defscalarmult(P, n): # caveat: caller must ensure that n is nonnegative # caveat: n is limited by python's recursion limit if n == 0: return (Fp(0),Fp(1)) if n == 1: return P Q = scalarmult(P, n//2) Q = clockadd(Q,Q) if n % 2: Q = clockadd(P,Q) return Q
defsolve(): print(f"[*] 使用模數 p: {p}") if p % 4 == 3: print("[*] p = 3 mod 4,構造 GF(p^2)") K.<i> = GF(p^2, modulus=x^2+1) else: print("[*] p = 1 mod 4,構造 GF(p)") u = GF(p)(-1).sqrt() K = GF(p) i = u
defto_element(pt): if p % 4 == 3: return K(pt[1]) + K(pt[0]) * i else: return K(pt[1]) + K(pt[0]) * i
G = to_element(base_point) A = to_element(alice_pub) B = to_element(bob_pub)
defscalarmult(P, n): res = (0, 1) # Identity element (x=0, y=1) -> 0i + 1 base = P while n > 0: if n % 2 == 1: res = clockadd(res, base) base = clockadd(base, base) n //= 2 return res
n = 2013173213309365625933566305130017869328316462881720251221055320304369815259410105951666181699459238383495162630863039217321546600903821213864673379819904794609979913390359136621570364138898797760597408456993438476109504849386080887264587106986309928978035085609864102499542187443021506824921026077779331204235369048429915385420897226473011503923523354591918132188886957608103418222891533803352966915259 c = 149386296161039450793419173696749564837096923819919322225924017787080033546963446814313759533158518854393955392890924635019955685701507100445213480979815012711722239756098145647780423351033795835914264538987805182519568292807894318702107730592387995058190313827162008258766268153383080316590950833153983988393039613499608862905845016467473282719322342438431262088746070306399215054104531050151987590326 e = 65537
defcreate_shares(secret, threshold, num_shares, p): rng = RNG(secret, p) coefficients = [secret] for i inrange(threshold - 1): coefficients.append(rng.next())
shares = [] for x inrange(1, num_shares + 1): y = 0 for power, coeff inenumerate(coefficients): term = (coeff * pow(x, power, p)) % p y = (y + term) % p shares.append((x, y))
import cypari2 from Crypto.Util.number import long_to_bytes
try: pari.allocatemem(4096 * 1024 * 1024) print("[*] PARI stack size increased to 4GB") except: print("[!] Failed to increase memory, trying to proceed anyway...")
p = 12670098302188507742440574100120556372985016944156009521523684257469947870807586552014769435979834701674318132454810503226645543995288281801918123674138911 y_target = 6435837956013280115905597517488571345655611296436677708042037032302040770233786701092776352064370211838708484430835996068916818951183247574887417224511655
a = 4378187236568178488156374902954033554168817612809876836185687985356955098509507459200406211027348332345207938363733672019865513005277165462577884966531159 b = 5998166089683146776473147900393246465728273146407202321254637450343601143170006002385750343013383427197663710513197549189847700541599566914287390375415919 c = 4686793799228153029935979752698557491405526130735717565192889910432631294797555886472384740255952748527852713105925980690986384345817550367242929172758571 d = 4434206240071905077800829033789797199713643458206586525895301388157719638163994101476076768832337473337639479654350629169805328840025579672685071683035027
R = GF(p) P.<x> = PolynomialRing(R)
defrng_next(val): return a * val^3 + b * val^2 + c * val + d
current_coeff = x poly_sum = current_coeff
for i inrange(9): current_coeff = rng_next(current_coeff) poly_sum += current_coeff
if gcd_poly.degree() > 0: roots = gcd_poly.roots(multiplicities=False)
for r in roots: try: val = int(r) flag = long_to_bytes(val) print(f"FLAG: {flag.decode()}") except: print(f"[-] Root {r} is valid int but decode failed") else: print("[-] 未在 GF(p) 內找到整數根。")
# generate keys and set up some initial data # this is time-consuming defsetup(): global NTRUKeys, current_count, zero_signature, signature_cache, ready_status
current_count = 0
NTRUKeys = KeyGenerator.KeyPair(gen=True, B=1)
# Sign the initial count (0) so that client can use it (_, r, s) = NTRU.Signing(NTRUKeys, long_to_bytes(current_count), N_BOUND) zero_signature = NTRU.export_signature(r, s, N_BOUND, False)
# limit to 4 count if current_count < 4and client_count == current_count: if count_sig in signature_cache and signature_cache[count_sig] == client_count: verif = True else: try: r, s = NTRU.import_signature(count_sig) verif = NTRU.Verifying( long_to_bytes(client_count), r, s, N_BOUND, NTRUKeys ) except Exception: verif = False if verif: current_count += 1
# sign the new number ready_status["status"] = False (_, r, s) = NTRU.Signing(NTRUKeys, long_to_bytes(current_count), N_BOUND) ready_status["status"] = True new_count_sig = NTRU.export_signature(r, s, N_BOUND, False) signature_cache[new_count_sig] = current_count if current_count >= 4: return jsonify( { "msg": f"Snake has grown to length {current_count}. It is too long and does not have any more food.", "count": current_count, "signature": new_count_sig, } ) return jsonify( { "msg": f"Snake has grown to length {current_count}", "count": current_count, "signature": new_count_sig, } ) else: return jsonify( { "msg": "Invalid signature!", "count": current_count, "signature": "null", } )
return jsonify( { "msg": "Snake does not have enough food to grow!", "count": current_count, "signature": "null", } )
@app.post("/flag") defget_flag(): global current_count, ready_status
網路上蠻多 NTRUSign 相關的 archive,想看可以去找下 XD 大概講一下 NTRUSign 的流程是透過在一個多項式 ring $\textbf{Z}[x]/(x^N-1)$ 下做運算,取兩個短的正交多項式當私鑰 (f, g),接下來的運算都是在它們相乘張出來的空間下進行 另外會再取兩個小的 G, F 滿足 $f \cdot G - g \cdot F = q$,他們當然也是私鑰不過更多是為了滿足一些餘數的性質 公開公鑰是 $h = f^{-1}*g$ 我們有個私鑰矩陣 $B = \begin{pmatrix} f & g \ F & G \end{pmatrix}$
要簽章訊息的人會先把訊息取 H(m) 變成座標 $(I_1, I_2)$ $(a, b) = (I_1, I_2) \cdot B^{-1}$ 注意到這時候 a, b 其實都不太會是整數,所以我們取 A, B 作為他們四捨五入的結果
最後再把四捨五入的結果乘回去 B $s = (A, B) \cdot B = (A \cdot f + B \cdot F, A \cdot g + B \cdot G)$ 最後簽章結果是這樣做完 Babai 以後攤出來的 error $e = (I_1, I_2) - s = (s_1, s_2)$
驗證簽章首先要確認代數結構是正確的:
$s_1 \cdot h \equiv s_2 - H(m) \pmod q$ 另外就是計算 e 的 norm 要夠小 (小於 N_BOUND)
這機制的安全性在於不知道基底的人雖然可以很好的透過計算 h 的 inverse 去滿足代數結構,但是會忽略掉大小問題 只有知道私鑰的人才能在空間裡面完成 Babai
deflong_to_bytes(n): if n == 0: returnb'' return n.to_bytes((n.bit_length() + 7) // 8, 'big')
classPolynomial: def__init__(self, N=251): self.coeff = np.zeros(N, dtype=int) self.N = N defstar_multiply(self, other): conv = np.convolve(self.coeff, other.coeff) res_coeff = np.zeros(self.N, dtype=int) for i inrange(len(conv)): res_coeff[i % self.N] += conv[i] res = Polynomial(self.N) res.coeff = res_coeff return res defmod(self, q): self.coeff = self.coeff % q return self def__sub__(self, other): res = Polynomial(self.N); res.coeff = self.coeff - other.coeff; return res def__add__(self, other): res = Polynomial(self.N); res.coeff = self.coeff + other.coeff; return res
defH(s_bytes, N): h_hash = hashlib.sha1() i = 0 m_hex = "" # 模擬 Server 端的 Bug: i 永遠為 0,且 hash 狀態累積 whilelen(m_hex) < N: h_hash.update(s_bytes + str(i).encode("ascii")) m_hex += h_hash.hexdigest() p = Polynomial(N) # 模擬 Server 端的 Wrap-around for j inrange(len(m_hex)): p.coeff[j % N] += ord(m_hex[j]) return p
definvert_poly_mod2(poly_coeffs, N): modulus = np.zeros(N + 1, dtype=int); modulus[0] = 1; modulus[-1] = 1 target = np.array(poly_coeffs, dtype=int) % 2 defdeg(p): d = len(p) - 1 while d >= 0and p[d] == 0: d -= 1 return d defpoly_div(num, den): num = np.trim_zeros(num, 'b'); den = np.trim_zeros(den, 'b') iflen(den) == 0: raise ValueError("Div 0") quot = np.zeros(max(0, len(num) - len(den) + 1), dtype=int) rem = np.copy(num) while deg(rem) >= deg(den): shift = deg(rem) - deg(den) quot[shift] = 1 for i inrange(len(den)): rem[shift + i] ^= den[i] return quot, rem defpoly_mul(a, b): return np.convolve(a, b) % 2 defpoly_add(a, b): l = max(len(a), len(b)); return (np.pad(a, (0, l-len(a))) + np.pad(b, (0, l-len(b)))) % 2 r0, r1 = modulus, target; t0, t1 = np.array([0]), np.array([1]) while deg(r1) >= 0: q_poly, r_new = poly_div(r0, r1); t_new = poly_add(t0, poly_mul(q_poly, t1)) r0, r1 = r1, r_new; t0, t1 = t1, t_new if deg(r0) > 0: returnNone inv = np.zeros(N, dtype=int) for i inrange(min(N, len(t0))): inv[i] = t0[i] return inv
definvert_poly_n(h_poly, N, q): inv_2 = invert_poly_mod2(h_poly.coeff, N) if inv_2 isNone: returnNone c = Polynomial(N); c.coeff = inv_2 cur_p = 2; two = Polynomial(N); two.coeff[0] = 2 while cur_p < q: hc = h_poly.star_multiply(c).mod(cur_p**2) term = two - hc c = c.star_multiply(term).mod(cur_p**2) cur_p *= 2 c.mod(q) return c
defparse_pub_key(pub_key_str): lines = pub_key_str.split('\n'); coeff_line = "" for line in lines: if"|"in line and"=="notin line and"PUBLIC"notin line: coeff_line += line return [int(x) for x in coeff_line.split('|') if x]
defformat_signature(r, s_poly): sig = "-----BEGIN NTRU SIGNATURE BLOCK-----\n" for c in s_poly.coeff: sig += str(c) + "|" sig = sig[:-1] + "\n==" + str(r) + "\n-----END NTRU SIGNATURE BLOCK-----\n" return sig
threads = [] for i inrange(len(sockets)): t = threading.Thread(target=worker, args=(sockets[i], payloads[i])) t.start() threads.append(t)
print("[*] Waiting for barrier...") # 確保所有線程都已經執行到 barrier.wait() time.sleep(1) # 釋放所有請求 barrier.wait() print("[*] GO!!! Payload sent.") for t in threads: t.join() for s in sockets: s.close()
# 5. Check Result time.sleep(2) try: final = requests.get(f"{TARGET_URL}/current-count").json()['count'] print(f"[*] Final Count: {final}") if final >= 14: print(f"\n>>> FLAG: {requests.post(FLAG_URL).json().get('msg')} <<<\n") else: print("[FAIL] Count < 14. Try running again.") except: print("[!] Error checking status.")