土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル

ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」と「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践」(通称:徳丸本)を参考に、セキュリティの勉強を進めています。

前回 は、セッション管理の理解と、実際になりすましを試したのと、PHP のセッションID を URL に埋め込まない対策を行いました。

今回は、前々回 に行った 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>
  <img src="resize.php?path=icons&basename=<?php e($icon); ?>&size=64">
  <a href="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 をクリックします。

アイコンのパスを/etc/passwdのパスに置き換えたところ
アイコンのパスを/etc/passwdのパスに置き換えたところ

HTTP history で結果を確認します。左側が GETリクエストで、パスを置き換える前の内容が書かれていますが、上の一覧では Edited(編集したということです)にチェックが入っています。

右側がレスポンスで、/etc/passwd の内容が読み出せています。

/etc/passwdファイルが取得できた
/etc/passwdファイルが取得できた

実際に見つかった脆弱性を確認して、その再現までやってみました。ログインできることが前提になりますが、おそらくどんなファイルでも読み出せるんだと思います。

GETリクエストで任意のファイルを読み出せる脆弱性の対策

対策方法としては、簡単に思いつくのは、そもそも GETリクエストを使う必要はないと思うので、関数呼び出しに変える、とかですかね。

もしくは、アイコンの画像ファイルのパスを渡すのではなく、ユーザ名を渡して、resize.php の方で、データベースからアイコンの画像ファイルのパスを取得する、でしょうか。

前者の方が適切ですが修正量が多そうです。後者はデータベースへのアクセスが2回になって、あまり良くない修正です。

ソースコードを検索すると、この resize.php は、他に1か所(todo.php)から使われています。

PHP言語の勉強と考えて、前者の方法で修正します。

resize.php はそのままとして、新しく、resize_func.php を作ります。

<?php
function 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)
パストラバーサルの検出結果(POST)

エラーになっているソースコードです。あと、POST で渡しているデータは、userid=daisuke&pwd=useruser&url=logindo.php です。

<?php
  require_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 の一部を取り出すところでエラーが出たということだと思います。

Burp Suiteで再現させた
Burp Suiteで再現させた

再現できたので、エラーが発生しないように修正してみます。

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 のロゴを使わせていただきました。ありがとうございます。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。