Index
generate_password(char **password) がローカル変数 secret[32] のアドレスを呼び出し元へ返しており、関数を抜けた時点で password はスタック上の壊れた領域を指す。次の auth() では input[256] が同じスタック領域を再利用するので、password が実質的に input の後半を指す形になる。
先頭16バイトに任意のパスワード、直後にNUL、さらに password が指すオフセットにも同じ16バイトを置く。strlen(input) == 16 を満たしつつ strcmp(input, password) も一致するため認証を通過できる。
半年分のDaily AlpacaHackを振り返る記念問題でフラグが明示されている。
Flaskの /flag は初回だけダミーを返し、その後は本物のフラグを返す。一方で前段のNginxが200応答を長期間キャッシュしており、素直に /flag へアクセスすると初回のダミーが固定されてしまう。
キャッシュキーはURI全体なので、/flag?a=1 のように未使用のクエリを付けると別キーになる。バックエンド側ではすでに初回フラグが折れているため、この新しいキーで本物のフラグが返る。
Node.jsの vm.runInNewContext をサンドボックスとして使っているが、node:vm はセキュリティ境界ではない。グローバルオブジェクトからconstructor chainをたどるとホスト側の Function を作れてしまう。
this.constructor.constructor('return process.env.FLAG')() のように実行し、ホストプロセスの環境変数を直接読む。
RSAで e = 5、かつ平文が短くパディングもない。平文整数を m とすると、今回のサイズでは m^5 が法 n を超えないため、暗号文は剰余ではなく単なる5乗になっている。
したがって暗号文 c の整数5乗根を取れば m が復元できる。
shuffle(items) が sorted(items, key=lambda _: random.getrandbits(1)) で実装されている。キーが0/1しかなく安定ソートなので、先頭要素が先頭に残る確率が高く、完全なシャッフルにはなっていない。
相手の手のリストはラウンド間で引き継がれるため、前回見えた先頭手に勝つ手を次に出し続ければ勝率が600/1000を超える。初手だけ初期配置の先頭に勝つ手を選び、以降は観測結果を追従する。
localhost