ホンモノのエンジニアになりたい

ITやビジネス、テクノロジーの話を中心とした雑記ブログです。

【writeupスタディー】DEFCON sbva (web)

writeupスタディーです。
人様が公開しているCTFのwriteupを読んで勉強しよう、そしてその内容を記録しておこうというエントリです。

私自身CTFは初級者レベルなので、アウトプットを通じて理解を深めたいというのが目的です。あと初心者が書くものなので、ある意味ほかの初心者の方もわかりやすくなる部分があるのではないかと思います。

問題概要

CTF:DEF CON CTF Qualifier 2018
問題:sbva
分野:Web

writeupページ:https://ctftime.org/task/6095

1問以上の問題に解答できたチームが586チームでこの問題を解けたチームが99チーム、正答チーム数でみると6番目に多くのチームが解いている問題です。前回のwriteupスタディーと同様に簡単な方の問題です。(難問はそもそもわからんのでレベルアップするまで触れない)

まずは問題文を確認。

We offer extensive website protection that stops attackers even when the admin's credentials are leaked!

Try our demo page http://0da57cd5.quals2018.oooverflow.io with username:password admin@oooverflow.io:admin to see for yourself.

管理者アカウントが漏洩した時でさえ攻撃を止めるWebサイト防御を提供する。
デモページでやってみな。

という問題。 デモページはこんな具合。

f:id:kwnflog:20180515181310p:plain

問題文に書いてあるアカウントでログインを試行する。 f:id:kwnflog:20180515181410p:plain

こんなページに遷移する。 f:id:kwnflog:20180515181430p:plain

当然ながら、エラーページに飛ばされた模様。これを何とかしてエラーページではない正解のページにアクセスする問題となっています。

最初に考えたこと、アプローチ

問題文からして提供されたログインアカウントは正しいものと考えた。このアカウントでどうにかして正規のページにアクセスする。

①とりあえずログインページとエラーページのソースを見てみる

特に怪しいところやヒントは発見できず。
/login.phpにログイン情報を投げているようですが、ブラウザ画面ではwrongbrowser.phpのページが表示されているので、リダイレクトされている模様。

②リクエストとレスポンスを覗く

まずこれが「Sign in」を押したときのリクエスト f:id:kwnflog:20180515183445p:plain

これがレスポンス f:id:kwnflog:20180515183552p:plain

最初のlogin.htmlの画面でログイン情報を入力させて、login.phpへリクエスト要求を投げる。

サーバ側のlogin.phpで何かしてwrongbrowser.phpへリダイレクトしろというレスポンスを返してきているらしい。レスポンスの中にhtmlがあるけど、これは何なんだろう。

とりあえずwrongbrowser.phpのリクエスト、レスポンスのヘッダに怪しいところは見つからなかった。

フラグを取るための方針を立て、何とか頑張る

まぁここまでの文章ではふれませんでしたが、wrongbrowser.phpのbodyに「Incompatible browser detected.」とあるので、違うブラウザでアクセスするんだろうなということは考えていました。

「Incompatible browser detected.」(非対応なブラウザが検出されました)

ということで、それまでChromeでアクセスしていたのを、Firefox、IE、Edge、Android Chrome、Android標準ブラウザと手元にあるブラウザでそれぞれアクセスしてみようという作戦をまず立てる。

・・・が、うんともすんとも状況は変わらず。



つーか、ブラウザ変えなくてもUser-Agent変えればいいんじゃないの、とここで気づく。久しぶりにAndroidタブレットを起動したが、速攻でお役御免。

以降、ChromeでUser-Agentを偽装してアクセスを試みることに。 偽装にはChromeアドオンの「User-Agent Switcher for Chrome」を使いました。

偽装はアドオンで標準装備されている、Chrome、IE6~10、iPhone6、iPad、Android KitKat、Windows Phone8、Firefox v33、Opera 12、Safari7、をそれぞれ使ってアクセスしてみました。

が、これも全部だめ。


ここら辺でUser-Agentの変更じゃだめなのかしら、つーかそもそも方針が違うのかしら、といつも通り不安になる。
一緒に問題を解いていたお友達から「login.phpのレスポンスに現行廃止されてる機能が使われてる」と情報提供・アドヴァイスをいただく。

<html>
    <style scoped>
        h1 {color:red;}
        p {color:blue;} 
    </style>
    <video id="v" autoplay> </video>
    <script>
        if (navigator.battery.charging) {
            console.log("Device is charging.")
        }
    </script>
</html>

調べてみると、style scoped、video autoplay、navigator.battery.charging、の3つが既に廃止されてる模様。とりあえずこれを動かせればいいんじゃないという話になった。

というわけでそれぞれの動作バージョンを確認したのが以下。

Firefox Chrome
scoped 21~54
autoplay 3.5~62 4~69
battery 10~51 38~69

WEBの世界でブラウザが対応している機能かバージョンごとに教えてくれるサイト「Can I Use」で調べた結果です。(これはwriteupを見て知ったWebサイトでした。開催中は似たようなもう少しショボいサイトを見てました)

Can I use... Support tables for HTML5, CSS3, etc

さてこの段階ですべてに対応しているブラウザが存在しなかったら手詰まりだなぁと思っていたんですが、Firefoxの21~51なら共通でサポートされているようなので、「もろた!」と思いました。

まずはUser-Agentを偽装してリクエストを送って今までと違う動きをしないか確認してみることに。

ユーザエージェントは適当にググって↓のサイトから取得した。

User Agents - Parser and API - Easily decode any user agent

そいじゃ早速やってみませう。

# Python3 , User-Agent :Firefox 21
import requests

url='http://0da57cd5.quals2018.oooverflow.io/login.php'

payload={'username':'admin@oooverflow.io','password':'admin'}
headers={"User-Agent":"Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130517 Firefox/21.0"}

r=requests.post(url,headers=headers,data=payload)

r.text

# 'Incompatible browser detected.'

あれ・・・? v21は境界で含まないってことかしら?

# User-Agent:Firefox 30
headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20130401 Firefox/30.0"}
r=requests.post(url,headers=headers,data=payload)
r.text

# 'Incompatible browser detected.'

あれれ~~~??(江戸川コナン風に)

# User-Agent:Firefox 40
headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/40.0"}
r=requests.post(url,headers=headers,data=payload)
r.text

# 'Incompatible browser detected.'


# User-Agent:Firefox 50
headers={"User-Agent":"Mozilla/5.0 (Windows 3.0; rv:45.0) Gecko/20100101 Firefox/50.0"}
r=requests.post(url,headers=headers,data=payload)
r.text

# 'Incompatible browser detected.'

ハズレ。

やっぱり素のブラウザでないといけないのか。しかししばし探せど正規の過去バージョンFirefoxが見つからない。仕方なく大分怪しいアーカイブサイトからインストーラをダウンロード。評価用に立てていた仮想環境のサーバにぶっこんだが起動と同時に最新版へアップロードしてくれる親切仕様により過去バージョンとして動作させることができない。

該当バージョンのスタンドアローンインストーラを別に探してきて、オフライン状態でインストール、自動更新を無効化した上でアクセスしてみるか...

いやいやCTFだぞこれ、しかも名門(?)のDEFCONなわけだし、わざわざ古くて脆弱な可能性のあるブラウザを入れさせたりしないでしょ。DEFCONの問題解くためにFirefoxをダウングレードするなんて悪問もいいとこだし(素人見解)、きっとそういうことじゃない。そういうことじゃないよね?違うよね?(超不安)

それは無い。無いはず。きっと無い。多分ない。


結局その後は最後のあがきとして、インターネッツから見つけてきたUser-Agentのリストを手当たり次第ポストするというお行儀の悪いアプローチをかましてみた。が、何を投げても「Incompatible browser detected.」が返ってくる。


なるほど。無事、敗北 (゜ロ゜)

writeupスタディー

さぁそれではフラグは取れませんでしたが、writeupをみていきましょう。

①'Incompatible browser detected.'のメッセージからUser-Agentに目をつける
②login.phpアクセス時のレスポンス内容からアクセス可能なブラウザの範囲を絞り込む
③User-Agentの全組み合わせでアクセスを試みる



・・・ちょっと待て。ま? そういうことなの?


Balsnというチームのwriteupでは以下のようなスクリプトが書かれている。(私の環境で動作するよう一部改変)

元はここ。 ctf_writeup/20180512-defconctfqual at master · balsn/ctf_writeup · GitHub

import requests
from itertools import product

s = requests.session()
for i, j in product(range(5, 6), range(0, 51)):
    agent = 'Mozilla/{}.0 (Windows NT 10.0; WOW64; rv:{}.0) Gecko/20100101 Firefox/{}.0'.format(i,j,j)
    headers={'User-Agent': agent}
    r = s.post('http://0da57cd5.quals2018.oooverflow.io/login.php', data=dict(username='admin@oooverflow.io', password='admin'), headers=headers)
    print(r.text, i, j)

# ここより前は省略
# 
# Incompatible browser detected. 5 41
# OOO{0ld@dm1nbr0wser1sth30nlyw@y}         ←これがフラグ
# <html>
#     <style scoped>
#         h1 {color:red;}
#         p {color:blue;}
#     </style>
#     <video id="v" autoplay> </video>
#     <script>
#         if (navigator.battery.charging) {
#             console.log("Device is charging.")
#         }
#     </script>
# </html> 5 42
# Incompatible browser detected. 5 43
# 
# ここより後も省略

User-Agentのバージョンを表す部分に数字を組み合わせで入れてPOSTしているだけです。
出力内容からFirefox42の時のUser-Agentでフラグを吐いたと。こんな文字列。

Mozilla/5.0 (Windows NT 10.0; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0


いやーこれは取れたな。取れなかったけど取れた。もう取れたでいいと思う。

私も手当たり次第のUser-Agentを投げたから、同じようなことはしてるんですけど、ちょっとそれは無くない?という感想です。

オフィシャルのRulesを見返すとこう書いてある。

No Denial of Service—DoS is super lame, don't do it or you will be banned
No automated scanning—For these challenges, do better

確かにブルートフォースすんなとは書いてない。DoSもしてないし、自動スキャンとも違うけど、CTFってもうちょっとインテリジェントなものじゃないかしら(素人見解再)。

いや違うか。writeupを書いたチームはFirefoxのUser-Agentを全パターン試行しているだけで、他の情報からFirefox42に絞れるのかもしれない。開催中は気付かなかったけど、サーバ側ではbrowsertest.phpというプログラムも動作しているようだったので、そのPHPを何とか引っこ抜くのが正しいアプローチなのかも。かも。

この問題から得られたノウハウ

  • ブラウザバージョンと機能を整理しているサイト:Can I User
    Can I use... Support tables for HTML5, CSS3, etc

  • ユーザエージェントまとめサイト(UA一覧のソートが使いにくい)
    User Agents - Parser and API - Easily decode any user agent

  • 思いつきで一部だけやるんじゃなくて、やるなら全パターン網羅して抜け漏れ無くやる。この問題ではFIrefox21~51が範囲なんだから、その中の”どれか”が答えに繋がる道であることは推測し得た。21~51の範囲内で適当に選んで数個だけ試行してダメだこりゃ、と考えてしまったのはよくなかった。

  • Pythonのproduct関数(単純に知らなかったので)

from itertools  import product

A = ('a', 'b')
B = ('c', 'd')

list(product(A, B))

# [('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd')]

for i,j in product(A,B):
     print(i,j)

#a c
#a d
#b c
#b d



おわり