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

hamayanhamayan's blog

DEF CON CTF Qualifier 2020 Web Writeups

https://ctftime.org/event/994

uploooadit110pt104/1399
pooot177pt18/1399
dogooos151pt26/1399
ooonline-class162pt22/1399
ooonline-gradclass416pt3/1399

えー、もたもたしてたら全部復習する前にサーバが落ちちゃいました。

uploooadit

https://uploooadit.oooverflow.io/
Files:
app.py
store.py

調査

app.pyとstore.pyも与えられる。
ソースから得られる情報は以下の通り。

  • /files/に対してファイルをGUID付きでアップロードできる
  • /files/[GUID]でアップロードしたファイルを確認できる
  • アップロード先はAmazon S3っぽいが、テスト用にローカルに保存するコードもコメントアウトで書かれている
    • 今、アップロードされているものはどっちになっているかは分からないが、たぶんテスト用にローカル保存になっているのだろう
    • えー、あとで分かるのですが、これは全く関係ないです(白目)

HTTP Desync Attacks

去年どっかで見ましたねぇ…
この攻撃が通りそうというのはどこから探るんだろう。
改めて調べると、素晴らしい記事があるので、改めて読んで思い出す。

ふむふむ。
どっからこれを持ってくるんやという感じだが、HAProxy Gunicorn vulnerability
HAProxy HTTP request smuggling - nathandavison.comこれが出てくる。
はー。

これを投げる。

POST /files/ HTTP/1.1
Host: uploooadit.oooverflow.io
Content-Length: 177
Content-type: text/plain
Connection: keep-alive
X-guid: f2d5c6af-5de9-4728-8b0f-c320b6677ab5
Transfer-Encoding:chunked

0

POST /files/ HTTP/1.1
Host: uploooadit.oooverflow.io
Connection: close
x-guid: f2d5c6af-5de9-4728-8b0f-c320b6677ab5
Content-Type: text/plain
Content-Length: 387

A

ここを見ると、Transfer-Encoding: chunkedが認識されたら、Content-Lengthは省略されるらしい。
よく見ると、chunkedの前に何かついている。
これは\x0bである。
普通にchunkedを書いてしまうと、リバースプロキシであるHAProxyがchunkedを認識してしまい、後半を捨ててしまうからである。
こう書くことで、chunkedでないと思わせて、アプリの方Gunicornにリクエスト全体を送ることができる。
アプリでは、前半と後半は別リクエストになるが、それより重要なのが、後ろのリクエストのContent-Lengthが与えられているものより長いということだ。
長いとどうなるかというとGunicornはまだデータが来ていないという風に解釈して、リクエストを待ち合わせる状態になる。
この状態で「誰かの」リクエストが来ると、そのリクエストをデータと誤って判断してしまう。
今回のアプリではそれが保存されるので、他の人のリクエストを自分の指定したGUIDの所に入れることができる。

参考

pooot

The web is becoming more and more dangerous everyday. Our secure pooot proxy allows you to continue your browsing securely and hide your IP address from your visited websites! Give it a swing here: pooot.challenges.ooo

調査

例えばhttps://www.hamayanhamayan.com/entry/2020/05/17/023558を入力すると、URLがhttps://pooot.challenges.ooo/www.hamayanhamayan.com/entry/2020/05/17/023558となり、サイトが表示される。

ふむ。ソースコードを見てみると、pythonコードへのリンクがある。
エンドポイントを調査する。

  • GET /
  • POST /
  • /<string:domain> /<string:domain>/<path:path>
  • /source
  • GET /feedback
  • POST /feedback

feedback機能がついている。feedback機能といえば、XSSでクッキー情報を抜く方針。
まずはそっち方針で考えよう。

/feedback
f:id:hamayanhamayan:20200521214246p:plain

ふむ。問題とURLを聞く感じね。
問題には適当な値を入れておいて、RequestBinでリクエストが来るか確認してみる。

f:id:hamayanhamayan:20200521214254p:plain

爆速で返事が来た。

うーん、わかんないですねぇ

Service workers

さて、これを見つけてくるのはさすがに難しいと思うのだけれど、どの糸口からこれに至るんだろ。
exploitコードはここを見てもらうことにして、概念を説明する。

Service workersとは何かというと、あるオリジンに対してjsコードを実行できるようにするブラウザ機能である。
まあ、ざっくり過ぎて違うまであるので、詳しくは下を見てほしい。
サービスワーカー API - Web API | MDN
Service Worker の紹介 | Web Fundamentals | Google Developers
さて、なぜこれが出てくるかという話だが、今回のプロキシサイトを経由してサイトを閲覧すると、必ず同じ「オリジン」になる。
なので、Service workersを登録するページをプロキシサイト経由で閲覧したときに、閲覧したURLをどこかに送るjsを登録する。
そのjsがここのpooot_sw.jsである。
プロキシサイト経由でService workerを登録すると、オリジンはプロキシサイトのオリジンになるので、
プロキシサイト経由でそれ以外のサイトを見たときにもこのjsが実行されることになる。
つまりは、対象のブラウザにロガーを仕込むことができるのだ。

永続的なXSS:ServiceWorkersに支配されることへの恐怖
このサイトでは永続的なXSSと紹介されており、確かに永続的だ…と感じている。

自分はここを参考に攻撃を試みたが、うまくいかず、もたもたしてたらサーバが落ちてしまった。

よーく探すと、以下のようなフレームワークもある。
Shadow Workers
これを使って脆弱性診断して、フラグを抜き取るのもいいだろう。
誰か環境を作って(できればdockerで)チュートリアル記事を書いてほしい。

参考

dogooos

DogOOOs is a new website where members can rate pictures of dogs. We think there might still be a few bugs, can you check it out? In this challenge, the flag is located in the root directory of the server at /flag.
http://dogooos.challenges.ooo:37453

f:id:hamayanhamayan:20200521214310p:plain

大量の犬画像がgoogledされてくる。

調査

Webサイトの探索と、与えられているソースコードから分かることをまとめておく。

  • /dogooo/show
    • 犬の一覧
  • /dogooo/deets/<id>
    • 犬の個別サイトに飛べる
  • /dogooo/login
    • ログインで使用される。POST限定
    • get_login関数で判定
      • cursorで入れてるしインジェクションは厳しそう
      • とりあえずsafeかな
  • /dogooo/runcmd
    • やってみると、502エラーとなる
    • 外部からアクセスするとWAFかなにかで弾く仕様なのだろう
    • プログラム的には特に目立ったところはないので、うまくやればRCEできるかもしれない
  • /dogooo/user/create
    • ログインしていないとユーザーは作れないみたい

コード中に@postforkとあるが、これはuWSGIの構文の1つであり、アプリを立ち上げたら最初に実行されるコード。
中身は使えるシステムコールを定義したもの。

わかんねぇ。

どこが弱いか

ほとんどのエンドポイントはログインを要求しているが、していないところがある。
/dogooo/deets/<postid>ここである。
ここでのソースコードを探ってみると、明らかに変な所がある。
get_commentsメソッドの
fmt_cmt = cmt.comment.format(rating=self.__dict__)
である。まあ、どう見ても変。
formatの引数として、rating=??となっているので、{rating}を与えてみる。

f:id:hamayanhamayan:20200521214324p:plain

SSTIできている。
しかし、実はこれ以上のことができる。

{rating[comments][0].__class__.__init__.__globals__}

これでグローバルの変数を全部ぶっこ抜ける。

DBの認証情報も抜けるし、

 'jdata': {'db_user': 'dogooo', 'db_pass': 'dogZgoneWild'},

よーく見ると、攻撃を行った投稿の投稿者情報も抜けている。

'post_results': ((20, "This is Duffy. If you look closely, you can see exactly where the mud attacked him. Fortunately for the mud, there were no witnesses. 12/10 let's get you cleaned pup", 2, 12, 'images/img_20.jpg', 2, 'demidog', 'princesses_password'),)

これはget_posting関数のSQL文を見ると、対応するpostとその投稿者のuserレコードをSELECT *で全部持ってきているからである。
ユーザーの情報も抜けてしまっている。

ログイン後

さて、やっとログインもできた。
やれることが増えてまた大変。
次の怪しい部分は、ログイン時に呼ばれるget_user_infoである。
fという関数を通しているが、fはfstringである。
別に通す必要性を感じない。
実はpythonコードをここにインジェクションすることができる。

例えば、

>>> a = "x"
>>> b = "{a}"
>>> print(f"{b}")
{b}

となるが、

>>> from fstring import fstring
>>> a = "x"
>>> b = "{a}"
>>> print(fstring(b))
x

こんな感じである。

後はエスパーして{open('flag').read()}を投げるとflagが出てくる。

あれっ、runcmdは??

Although there was a promising route in the script for executing local commands /runcmd. It would error because of the seccomp filter which prevented execve.
o-o-overflow/dc2020q-dogooos-public: Cute DOOOGz

あっ…

参考

ooonline-class

Ugh, everything's moving to online classes now.
There's one assignment that I can't solve (and chegg is no help).
I'm about to yeet my laptop.
http://ooonline-class.challenges.ooo:5000/

ooonline-gradclass

Wow, grad classes are so much harder (yet seem hauntingly familiar)
http://ooonline-gradclass.challenges.ooo:5000/