ディレクトリトラバーサル
はじめに
こんにちは!
株式会社ユーザベース スピーダ事業 Product Team(以下 Product Team)の新熊・度會です。
ユーザベースの Product Team には、全社のセキュリティを担うチームとは別に、プロダクトセキュリティの底上げを担うセキュリティチーム、通称 Blue Team というチームがあります。
私たちはそのチームの一員として、日頃の開発業務に加えてユーザベースのプロダクトのセキュリティを横断的に向上するための活動を行なっています。
現在、 Blue Team の取り組みのひとつとして、脆弱性のリスクや対策方法について継続的に記事にまとめ、Product Team 内の各開発チームで読み合わせを行なってもらう施策を実施しています。
このテックブログが ユーザベースに興味を持っていただくきっかけになればと思っています!
前回の記事はこちら!
第 4 回目である今回は、ディレクトリトラバーサルの脆弱性について取り上げます! 実アプリケーションでの脆弱性の例を交えながら、ハンズオン形式で脆弱性のリスクや対策方法について解説していきます。
ディレクトリトラバーサル(Directory Traversal)の脆弱性とは?
ディレクトリトラバーサル攻撃及び、その脆弱性はWebアプリケーションが予期していないディレクトリへのアクセスを、URLの改変を通じて行うことで実現されます。 「../../etc/passwd」のように相対パスや絶対パス等を指定することで、機密情報などを窃取することが可能になる重大な脆弱性です。
直近では、GitLabの特定のバージョン(商用も含む)において、ディレクトリトラバーサルの脆弱性が発見されています。
前述の脆弱性では、GitLab CE/EE のバージョン16.0.0 のみに影響するものでした。 認証されていない悪意のあるユーザーが特定の条件下において、ディレクトリトラバーサルの脆弱性を利用してサーバー上の任意のファイルを読み取ることが可能になっていました。
今回は、前回同様に脆弱性のあるアプリケーションを作成しました。 これによって、どのように攻撃が実現するか手元で確認することができます。 前提としては、画像投稿サイトを想定しています。ユーザーが任意の画像を投稿できますが、今回は閲覧機能のみを実装しています。 この閲覧機能に脆弱性が埋め込まれている想定となります。URLを悪意のあるユーザーが、改変することによって攻撃が実現します。
では、デモを見ていきましょう!
デモ
以下のようなクエリーパラメーターでファイル名を指定し、画像を返すAPIがあります。
想定では、http://localhost:3000/images?fileName=image1.png
とアクセスされ、画像を返します。
悪意あるユーザーにファイル名に相対パスを指定し、curl "http://localhost:3000/images?fileName=../../../../../proc/self/environ" -o env.txt
にアクセスすると、設定している環境変数のTEST_ENV="TOP SECRET"
などを取得することができます。
例では/proc/self
を取得していますが、他にも/etc/hosts
などアプリケーション外の任意のファイルにアクセスすることが可能です。
悪意ある攻撃者は、ここを起点として攻撃の準備や実行が可能になってしまいます。
app.get("/images", (req, res) => { const fileName = req.query.fileName as string; // ファイルを読み込む const filePath = path.join(__dirname,"public", fileName); fs.stat(filePath, (err, stats) => { // 処理の記載を一部省いています。 const file = fs.readFileSync(filePath, "utf-8"); fs.readFile(filePath, (err, data) => { res.writeHead(200, { "Content-Type": "image/jpeg" }); res.end(data); }); });
大前提としてユーザーの入力を信用せず、そのまま扱わないということが重要です。 また、入力を扱う場合には、アローリスト形式で許可されたパスのみを扱うようにするなどが、緩和策としてあげられます。
改修版のコードは以下のようになります。
app.get("/images", (req, res) => { const fileName = req.query.fileName as string; // ファイルを読み込む const filePath = path.join(__dirname,"public", fileName); const parsedPath = path.parse(filePath); // 画像を取得するファイルパスが許可されたディレクトリへのアクセスかを確認する if (parsedPath.dir !== path.join(__dirname, "public")) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); return; } const file = fs.readFileSync(filePath, "utf-8"); fs.readFile(filePath, (err, data) => { if (err) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); return; } res.writeHead(200, { "Content-Type": "image/jpeg" }); res.end(data); }); });
まとめ
ディレクトリトラバーサルは、/etc/passwd
やアプリケーションのプロセスから環境変数などのクレデンシャル情報が取得される危険性があります。
そして、そこを足がかりにして、さらなる攻撃の準備や攻撃自体が実現する極めて危険な脆弱性だと言えます。
対策としては、IPAによれば根本的な解決と保険的な対策があります。
根本的には、前述のようにユーザーの入力を信用しないこと、利用しないことになります。 今回のデモアプリでは、画像を任意の入力から相対パスで指定し、ブラウザに返すというものでした。 可能であれば、この仕様自体を見直すべきです。
次の根本的解決では、リクエストパラメーターで受け取ったファイル名をそのまま扱うのではなく、ライブラリなどを用いてファイル名を取得し、アプリ内部で保持しているディレクトリと組み合わせてファイルを取得することで実現できます。 リクエストパラメーターからディレクトリを除くことで、相対パスなどでアプリケーション外のファイルへのアクセスさせないようにできます。
保険的な対策では、ユーザーからの入力は許可するものの、信頼せずに検証を行うというものがあります。
具体的には、事前にアプリケーション側のアクセス権限を適切に設定し、制御を行うことで攻撃のしやすさを緩和できます。 また、リクエストのファイル名に英数字のみを許容するなどのバリデーションが有効です。
We are hiring!!!
ブログを最後まで読んでくださりありがとうございました。 ユーザベースでは、Product Team のメンバーを募集しています! 本ブログが、ユーザベースへ関心を持っていただくきっかけになれば幸いです!