はまやんはまやんはまやん

hamayanhamayan's blog

Angular of the Universe [TokyoWesterns CTF 6th 2020]

Angular of the Universe
You know, everything has the angular.
A bread, you, me and even the universe.
Do you know the answer?
http://universe.chal.ctf.westerns.tokyo
universal-angular.zip

ソースコードが大きいので、とりあえずflagで検索

PS> rg -i flag
docker-compose.yml
11:      - FLAG=FLAG{xxxxx}
12:      - FLAG2=FLAG{yyyyy}

app\src\polyfills.ts
33: * user can disable parts of macroTask/DomEvents patch by setting following flags
34: * because those flags need to be set before `zone.js` being loaded, and webpack
36: * in this directory (for example: zone-flags.ts), and put the following flags
38: * import './zone-flags';
40: * The flags allowed in zone-flags.ts are listed here.
42: * The following flags will work for all browsers.
49: *  with the following flag, it will bypass `zone.js` patch for IE/Edge

app\src\app\debug\answer\answer.component.ts
13:  public flag: string;
19:      this.flag = process?.env?.FLAG

app\src\app\debug\answer\answer.component.html
3:<p class="lead">You want this? {{flag}}</p>

app\package-lock.json
2475:        "has-flag": {
2477:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
2487:            "has-flag": "^4.0.0"
5123:        "regexp.prototype.flags": "^1.2.0"
6710:    "has-flag": {
6712:      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
7358:        "has-flag": {
7360:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
7396:            "has-flag": "^4.0.0"
7794:        "has-flag": {
7796:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
7821:            "has-flag": "^4.0.0"
7922:        "has-flag": {
7924:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
7934:            "has-flag": "^4.0.0"
8777:        "has-flag": {
8779:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
8789:            "has-flag": "^4.0.0"
9916:        "has-flag": {
9918:          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
9937:            "has-flag": "^4.0.0"
10459:            "has-flag": "^3.0.0"
10801:            "has-flag": "^3.0.0"
11813:    "regexp.prototype.flags": {
11815:      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
12043:            "has-flag": "^3.0.0"
13574:        "has-flag": "^3.0.0"
15195:            "has-flag": "^3.0.0"

app\server.ts
37:      res.json(`hello admin, this is true answer: ${process.env.FLAG2}`)
45:    if (process.env.FLAG && req.path.includes('debug')) {

フラグは2つあるようだ。

  • /debug/answerにアクセス
  • /api/true-answer127.0.0.1でアクセス

Writeup

とりあえず何をすべきかは分かったが、それから手も足も出なかった。

TokyoWesterns CTF 2020 | writeups by @terjanq

うーん、なるほどというべきか、どうやってこの辺身に着けていけばいいんかな。
nginxのバックスラッシュバイパスは見たことあるし、パーセントエンコーディングバイパスも見たことあるけど、
ぱっと引き出せないな…

フラグ1

/debug/answerにアクセスできれば勝ち。だが、以下に阻まれてアクセスできない。

  • nginxのWAF
    • location /debugでDENYになっている
  • expressのエントリーポイントでのチェック
    • if (process.env.FLAG && req.path.includes('debug')) {

どちらもバイパスできるいい方法を探そう。

nginxのWAFをバイパスする

/debug/answerにアクセスしようとすると、location /debugに阻まれてダメになる。
なので、バックスラッシュによるバイパスを使おう。
/\debug/answerとする。こうすると、locationは/\debugとなるので引っかからなくなる。
これで今後の処理は問題ないかという話であるが、このURLは最終的にはAngularに渡される。
Angularでは\はスラッシュ扱いになるので、//debug/answerと解釈される。
URLではスラッシュ続きは無視されるだけなので、問題なく動作する。

これで403 Forbidden nginx/1.19.0からdebug page is disabled in production envに応答が変化する。
一歩前進。

expressのエントリーポイントでのチェック

パーセントエンコーディングでバイパスする。
dを%64に変換して送る。これでフラグが出てくる。
/\%64ebug/answer
これはnginxではパーセントエンコーディングはデコードされるけど、expressではパーセントエンコーディングはデコードされなくて、Angularではパーセントエンコーディングはデコードされるってこと?
たぶんそういう事だよね。
ちゃんと確認したいけれど、wslがwinupしたらぶっこわれたから環境作るのめんどくさかった。

フラグ2

解説を読んだ。
ライブラリのこういう特性を使うとHostに適当なやつ入れておけばSSRFできるパターンあるんだな…

Angularの仕様

AngularからHTTPリクエストを飛ばすときは、Hostヘッダーを使うらしい。
Angularでthis.http.get('/api/answer')のようにリクエストを飛ばしている箇所があれば、PROTOCOL + HOST + / PATHでアクセスする。
なので、適当なサイトに誘導してリダイレクトで127.0.0.1/api/true-answerへ飛ばせば、SSRFを達成できる。

https://repl.it/repls/BaggyStrongPaint
こんな感じに受けてを作って、リクエストさせればいい。
repl.it便利すぎる。

非想定解解法

https://twitter.com/tyage/status/1307856413386047488