こんにちは。20Bの@reyuです。普段はSysAd班・CTF班などで活動しています。
この記事は5/1に行われたCPCTF22の作問者writeupです。
writeup
- Web/Forbidden 1
- Web/Forbidden 2
- Web/5 quadrillion coins
の3問を作問しました。
Web/Forbidden 1 (newbie)
ヒントなし | あり |
---|---|
22 | 53 |
adminじゃないと取得できないフラグを取得する問題です。
cookieを見るとadmin: false
がそのまま入っているので、cookieを編集することで誰でもadminになることができます。
Webのnewbie問はdevtoolsを見たらそのままフラグが書いてあるみたいな問題になることが多いのですが、なんとなく味気なさを感じたので少し現実寄りな問題にしました。
そのぶんヒントはできるだけわかりやすくなるように意識しながら書きました。結果としては多くの方に解いてもらえたようで、少し安心しています。
Web/Forbidden 2 (easy)
ヒントなし | 1 | 2 | 3 |
---|---|---|---|
17 | 2 | 14 | 15 |
Forbidden 1と見た目は同じですが、今度はフラグに誰もアクセスできないようになっています。
添付ファイルを開いてみると、nginx.conf
というファイルが含まれています。
nginx.conf
はNginxというアプリケーションの設定ファイルで、読んでみると /private
以下へのアクセスが拒否されていることがわかります。
http {
server {
listen 80;
location /private {
return 403;
}
location /public {
alias /usr/share/nginx/public/;
}
location / {
alias /usr/share/nginx/html/;
}
}
}
events {}
ここで、alias
について調べてみると、locationに対して置換を行うディレクティブなことが書かれています。
http://nginx.org/en/docs/http/ngx_http_core_module.html#alias
よって今回の設定だと、/public/image.png
に対するリクエストは、/usr/share/nginx/public//image.png
に置換され、LinuxなどのOSではパスの中の //
は /
として扱われるため、結果として /usr/share/nginx/public/image.png
へのアクセスして解釈されます。
ここで、/public../hoge
へのリクエストについて考えてみると、/usr/share/nginx/public/../hoge
に置換され、..
は1つ上のディレクトリを指すため /usr/share/nginx/hoge
へのリクエストとなります。
よって、/public../private/flag.txt
にアクセスするとフラグを入手できます。
この脆弱性は、 location /public/
のように最後に /
をつけることで防ぐことができます。
余談ですが、この脆弱性はalias traversalなどと呼ばれているのですが、alias traversalはNginx自体の脆弱性としては扱われておらず、今後のアップデートで修正などが行われる可能性は低いです。
いかにもやってしまいそうな設定ミスが脆弱性となりうるのが怖いですねという問題でした。
Web/5 quadrillion coins (medium)
ヒントなし | 1 | 2 | 3 |
---|---|---|---|
6 | 1 | 0 | 5 |
5000兆枚コインを集める問題です。
コインは1枚ずつしか集めることができないため、別の方法を考える必要があります。
ユーザー情報はJWTで管理されており、ここの改ざんができればコインの枚数を自由に増やせます。
まず、JWTの中身を見てみましょう。
JWTの仕様について詳しくは触れませんが、データ自体は暗号化されていないのでjwt.ioなどで中身を見ることができます。
"alg": "RS256"
が見えます。
このように、JWTでは自分自身がどのように署名されているかをヘッダーに記載します。
この仕様を使った攻撃手法の1つに、"alg": "none"
とすることで署名の検証を回避するというものがあります。
ただ、src/jwt.ts
を見ると対策されているようです。
const algorithms: jwt.Algorithm[] = [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"PS256",
"PS384",
"PS512",
// "none"
];
次に、jwt.verify()
のTypeScriptでの型定義を見てみると
/**
* Synchronously verify given token using a secret or a public key to get a decoded token
* token - JWT string to verify
* secretOrPublicKey - Either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA.
* [options] - Options for the verification
* returns - The decoded token.
*/
export function verify(token: string, secretOrPublicKey: Secret, options: VerifyOptions & { complete: true }): Jwt;
export function verify(token: string, secretOrPublicKey: Secret, options?: VerifyOptions & { complete?: false }): JwtPayload | string;
export function verify(token: string, secretOrPublicKey: Secret, options?: VerifyOptions): Jwt | JwtPayload | string;
第二引数に secretOrPublicKey
と書いてあります。
Either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA.
と書いてあるように、ここにはHMACの共通鍵もしくは公開鍵暗号の公開鍵が入るようです。
ここまでの情報をまとめると
verifyJWT()
ではnone
以外のアルゴリズムなら許可されている- どのアルゴリズムを使うかはユーザーの入力で決められる
jwt.verify()
の第二引数はHMACの共通鍵または公開鍵暗号の公開鍵
よって、RS256の公開鍵をHMACの共通鍵としてJWTに署名することで、JWTの改ざんが可能となります。
攻撃用のtokenの作成はWeb上のサービスや自前のプログラムなどで行うことができます。
ただ、jwt.ioでは改行を含むsecretを貼り付けるとスペースに置換されるらしく、解くことができません。この罠のせいでヒントを全部開けてしまった人を見かけて申し訳ない気持ちになりました。
自分で解いた時点でそこが罠なことには気づいていたのでヒント1とかに書いておくとよかったかもしれないですね。
const jwt = require("jsonwebtoken");
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzlUYqKvBVup0Tv62RGUm
bsNIpY8JTZ3p5vPQdQqwzFjpm0mZGPnFo7xV/u2LA7anZI9gd/3zOqvbI0rwbLxm
YQXIViJX8l/RFi6/nMfRTOxZaGcBefZ/lNrnJY1vQOxaF/bRM2kx9wQvIJf+gVvq
WReix2vFplMhFTKPEAzK4EB6IzSOJ9s6yf7umUshOzxXI6LY6dIPYLGTvKm7PJdn
fi+YJlv0+OE2j25HsWGj3raeQJoVEL2tiEzLN+5ID7ddR2bN5xmmWkk3pfyOaZZj
2U/igL/Qz5e9bP6kYpBzjZIROsCpnxO5pooWjAb+13Aemb3d+ecsmf9RBJvU20KW
YQIDAQAB
-----END PUBLIC KEY-----`;
const obj = {
username: "a",
coins: 10000000000000000,
};
const token = jwt.sign(obj, publicKey, { algorithm: "HS256", noTimestamp: true });
console.log(token);
公開鍵を共通鍵として使うという手法が好きで問題にしたんですが、そのまますぎてCTF的な楽しさがあまりない問題になってしまったような気もしています。
いくつか要素を加えようとしたものの、どれも蛇足感が強くなってしまい結局そのまま出すことにしました。
おわりに
CPCTF22に参加してくださった皆様ありがとうございました。
スコアサーバーや問題環境はしばらく残っているらしいので、気になった方はぜひ解いてみてください。