「ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」と「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践」(通称:徳丸本)を参考に、セキュリティの勉強を進めています。
前回は、セッション管理の理解と、実際になりすましを試したのと、PHP のセッションID を URL に埋め込まない対策を行いました。
今回は、前々回に行った OWASP ZAP の自動脆弱性スキャンの結果の「パストラバーサル」について、分析と可能なら対策までやっていきたいと思います。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル ← 今回
徳丸本の環境構築については、以下の第9回でやりました。
daisuke20240310.hatenablog.com
また、徳丸本が用意してくれている、脆弱なアプリケーション Bad Todo の準備については、以下の第12回でやりました。今回は、この環境を使ってやっていきます。
daisuke20240310.hatenablog.com
パストラバーサルの検出結果の確認(GET)
今回は、書籍でも解説されているように、パストラバーサルのアラートを見てみます。
GETリクエストで任意のファイルを読み出せる脆弱性の分析
GET の方を選択します。どうやら、/etc/passwd を読み出すことが出来る脆弱性のようです。それは「リスク:High」ですね(笑)。
Bad Todo のソースコードを確認してみました。
マイページにアクセスすると、自分の情報を更新したり、退会できたりします。
このページにアクセスしたとき、登録したアイコンが表示されるのですが、ここで以下のソースコードのように、登録した画像ファイルをリサイズする別のページにアクセスしていて、ここで画像ファイルのパスを URL に埋め込んでしまっています。
ソースコードは、mypage.php の L46 です。$icon
に、画像ファイルのパスが入っていると思います。
<td>アイコン</td><td><imgsrc="resize.php?path=icons&basename=<?php e($icon); ?>&size=64"><ahref="changeicon.php?id=<?php e($reqid); ?>">変更</a></td>
GETリクエストで任意のファイルを読み出せる脆弱性の再現
/etc/passwd が読み出せる脆弱性を再現してみたいと思います。
OWASP ZAP でいろいろやってみたのですが、今回いくつか設定した影響なのか、うまく動かせなかったです。そこで、Burp Suite を使って、再現をしてみたいと思います。
Burp Suite を起動して、Chromium も起動します。Bad Todo にログインして、マイページに移動します。このとき、Chromium はマイページに表示する登録されたアイコンを読みだそうとサーバにリクエストします。そのアイコンのパスを /etc/passwd に変更することで、読み出せるという脆弱性だったようです。
では、マイページに移動する直前に Intercept を有効にします。最初の GETリクエストは、マイページのページ自体なので、Forward を押して、スルーします。
次のアイコンの GETリクエストで、パスを /etc/passwd のパスに置き換えて、Forward をクリックします。
HTTP history で結果を確認します。左側が GETリクエストで、パスを置き換える前の内容が書かれていますが、上の一覧では Edited(編集したということです)にチェックが入っています。
右側がレスポンスで、/etc/passwd の内容が読み出せています。
実際に見つかった脆弱性を確認して、その再現までやってみました。ログインできることが前提になりますが、おそらくどんなファイルでも読み出せるんだと思います。
GETリクエストで任意のファイルを読み出せる脆弱性の対策
対策方法としては、簡単に思いつくのは、そもそも GETリクエストを使う必要はないと思うので、関数呼び出しに変える、とかですかね。
もしくは、アイコンの画像ファイルのパスを渡すのではなく、ユーザ名を渡して、resize.php の方で、データベースからアイコンの画像ファイルのパスを取得する、でしょうか。
前者の方が適切ですが修正量が多そうです。後者はデータベースへのアクセスが2回になって、あまり良くない修正です。
ソースコードを検索すると、この resize.php は、他に1か所(todo.php)から使われています。
PHP言語の勉強と考えて、前者の方法で修正します。
resize.php はそのままとして、新しく、resize_func.php を作ります。
<?phpfunction resize($path, $basename, $size){$file="$path/$basename"; $xfile="$path/_${size}_$basename"; error_log("file={$file}/".gettype($file).", xfile={$xfile}/".gettype($xfile)); if(!file_exists($xfile)){copy($file, $xfile); // 当初ImageMagicを使っていたがあまりにサイズが大きいのでimgpに変更// error_log("imgp -x {$size}x{$size} -w {$xfile}");exec("imgp -x {$size}x{$size} -w {$xfile}"); }error_log("{$xfile}"); return$xfile; }?>
呼び出し側の mypage.php の修正点は以下です。
--- todo.org/mypage.php 2018-08-15 10:51:05.000000000 +0900+++ todo.change/mypage.php 2024-08-14 14:40:06.000000000 +0900@@ -1,5 +1,6 @@<?php require_once('./common.php'); + require_once('./resize_func.php'); $id = $user->get_id(); $reqid = filter_input(INPUT_GET, 'id'); $ok = $user->is_super() || $id === $reqid; @@ -16,6 +17,8 @@ $email = $result['email']; $pwd = $result['pwd']; $icon = $result['icon']; + $icon_resize = resize('icons', $icon, '64');+ error_log("icon=${icon}/" . gettype($icon) . ", icon_resize=${icon_resize}"); } } catch (PDOException $e) { $logger->add('クエリに失敗しました: ' . $e->getMessage()); @@ -43,7 +46,7 @@<td>パスワード</td><td>****** <a href="changepwd.php?id=<?php e($reqid); ?>">変更</a></td> </tr> <tr> - <td>アイコン</td><td><img src="resize.php?path=icons&basename=<?php e($icon); ?>&size=64"><a href="changeicon.php?id=<?php e($reqid); ?>">変更</a></td>+ <td>アイコン</td><td><img src=<?php e($icon_resize); ?>><a href="changeicon.php?id=<?php e($reqid); ?>">変更</a></td></tr> </table> <a href="quit.php?id=<?php e($reqid); ?>">退会する</a>
もう1つの呼び出し側の todo.php の修正点は以下です。
--- todo.org/todo.php 2018-08-18 16:37:29.000000000 +0900+++ todo.change/todo.php 2024-08-13 20:52:59.000000000 +0900@@ -1,5 +1,6 @@<?php require_once('./common.php'); + require_once('./resize_func.php'); $item = $_GET['item']; $id = $user->get_id(); @@ -14,6 +15,7 @@ $logger->add('クエリに失敗しました: ' . $e->getMessage()); die('只今サイトが大変混雑しています。もうしばらく経ってからアクセスしてください'); } + $icon_resize = resize('icons', $result['icon'], '64'); ?><html> <head> <link rel="stylesheet" type="text/css" href="css/common.css"> @@ -26,7 +28,7 @@<?php if (! empty($result)): ?> <table style="width: 70%;"> <tr> - <td>ID</td><td><?php e($result['userid']); ?><img src="resize.php?path=icons&basename=<?php e($result['icon']); ?>&size=64"></td>+ <td>ID</td><td><?php e($result['userid']); ?><img src=<?php e($icon_resize); ?>></td></tr> <tr> <td>todo</td><td><?php
これで動きました。だいぶ苦労しました。
修正と動作確認をする上で必要になった内容(PHP で開発する基礎的な内容)などを後述します。
パストラバーサルの検出結果の確認(POST)
パストラバーサルで、もう1点の検出された脆弱性です。
POSTリクエストの脆弱性の分析
以下のキャプチャ以外に、特に情報がありません。証拠のところも空欄ですし。
レスポンスのところに、エラー(Notice)が出てて、logindo.php のパスの情報が出てしまっていることが脆弱性と指摘されたのでしょうか。
エラーになっているソースコードです。あと、POST で渡しているデータは、userid=daisuke&pwd=useruser&url=logindo.php
です。
<?phprequire_once'./common.php'; try{$dbh= dblogin(); $userid=filter_input(INPUT_POST, 'userid'); $pwd=substr($_POST['pwd'], 0, 6); $url=filter_input(INPUT_POST, 'url');
POSTリクエストの脆弱性の再現
とりあえず、再現させてみます。
普通にログインできますね、、、あ、よく見たら、通常のPOSTデータは、url=todolist.php
で、url=logindo.php
ではないです。
自動診断では、パラメータを変更して試していたんですね、違うパラメータを入れると、変な応答が返ってきたので、診断で指摘したということでしょうか。
Burp Suite で、ログインするときに intercept して、POSTデータを、url=todolist.php
から url=logindo.php
に変更して、POSTリクエストしてみます。
再現しました。
POSTリクエストの後、通常なら、todolist.php(urlパラメータで指定した URL)にリダイレクトされるところが、logindo.php にリダイレクトされます。しかし、userid、pwd、url のパラメータが設定されてないので、pwd の一部を取り出すところでエラーが出たということだと思います。
再現できたので、エラーが発生しないように修正してみます。
POSTリクエストの脆弱性の再現
修正したソースコードの差分です。POSTデータが格納されているかをチェックして、入っていなかったら、exit するだけの簡単な対策です。
--- todo.org/logindo.php 2018-08-15 15:29:23.000000000 +0900+++ todo.change/logindo.php 2024-08-13 21:34:15.000000000 +0900@@ -1,5 +1,8 @@<?php require_once './common.php'; + if (! isset($_POST['userid']) || ! isset($_POST['pwd']) || ! isset($_POST['url'])) {+ exit;+ } try { $dbh = dblogin(); $userid = filter_input(INPUT_POST, 'userid');
修正した後、さっきの再現手順を実施すると、真っ白なページになるだけでしたが、エラーは出ませんでした。とりあえず、これで、もう一度、自動診断してみることにします。
自動脆弱性スキャンの再実行
再診断の結果が以下です。25件が23件になりました。GETリクエストの指摘は無くなりましたが、POSTリクエストの方は残っています。
それ以外で1件減っているのは、前回の「徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す - 土日の勉強ノート」で、PHP の設定を見直したことで、XSS の項目が減ったんだと思います。
POSTリクエストの脆弱性は対策できませんでした。Notice(エラー)が出てることが原因だと思ったのですが、違う原因で指摘されているのかもしれません。
今後は、他のアラートの内容を対策して、どうしてもここだけが分からなかったら、徳丸本のサポートML に質問してみたいと思います。もし、原因が分かったら、この記事に追記したいと思います。
wasbookのPHPを変更するときのメモ
- 変更したコードは、Apache を再起動しなくても、すぐに反映された
- デバッグログを入れたい場合、PHPコード(
<?php から ?>
の中)に、error_log("icon=${icon}");
を入れる - デバッグログを見るには、root になって(
$ sudo su
)、# tail -f /var/log/apache2/error.log
でモニタしておく
おわりに
今回は、パストラバーサルの脆弱性について、再現と対策を行いました。残念ながら、POSTリクエストの指摘については原因が分かりませんでしたが、めげずに次の指摘を進めたいと思います。
今回は、PHP のロゴを使わせていただきました。ありがとうございます。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。