ヒーロー背景(区切りなし)
指令

射出 - 軌道通過

パストラバーサルは、もう1つの比較的よく見られる注入型脆弱性です。これらは、URI(URL、ファイルパスなど)の構築において、完全に解決されたパスが意図したルートの外を指さないことを適切に保証していない場合に発生する傾向があります。

強調すべき点は、パス通過がパス *インジェクション* の脆弱性としても見なされる可能性があることです。

パストラバーサル脆弱性の影響は、その脆弱性が発生する状況や実施された全体的な強化策に大きく依存します。しかし本題に入る前に、この脆弱性の具体的な実例を簡単に見てみましょう。

簡単な概要

応募書類の最終提出先として、契約書や求人票などの書類を提供するポイントを検討してください。これらはPDFなどの静的なファイルであり、アプリケーション内で固定的に配置されます。

この場合、オンデマンドでファイルを取得するために、次のようなコードを使用できます:

let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename ;

renvoie file.read (chemin) ;

脆弱性の進化を示すためには、アプリケーションのルートディレクトリがどこにあるかを知る必要もあります。この例では、アプリケーションのルートが「/var/www/api/」にあるものと仮定します。

アプリケーションが「ファイル名」パラメータを受け取ることはわかっています。いくつかの入力例とその結果を見てみましょう:

ファイル名 未解決のパス 解決済みのパス
プライバシー.pdf /var/www/api/documents/Privacy.pdf /var/www/api/documents/Privacy.pdf
../config/prod.config /var/www/api/documents/.../config/prod.config /var/www/api/config/prod.config
.../.../.../etc/shadow /var/www/api/documents/.../.../.../etc/shadow /etc/shadow

「.. /」を使用してファイルシステムを移動できる点に注目してください。 通常PDFが保存されている「documents」フォルダから、Linuxではパスワードハッシュを格納する「/etc/」フォルダ内の「shadow」ファイルへ移動できます。ご想像の通り、これは決して理想的な状態ではありません。

URL内のトラバースを参照する

パスパスの一つのバリエーションは、APIとのやり取りを目的としたURL作成時に発生する可能性があります。例えば、以下のメソッドを持つAPIがあると仮定しましょう:

URLパターン 説明
/api/v1/order/get/{id} 指定されたIDの注文に関する詳細を取得する
/api/v1/order/delete/{id} 特定のIDの注文を削除する

別のアプリケーションがAPIと相互作用し、例えば注文情報を取得しようとする際にAPIを呼び出す可能性があります:

laissez APIBase = "https://my.api/api/v1 « ;
let OrderAPI = APIBase + « /order/get » ;

let apiURL = OrderAPI + request.params.OrderId ;

let response = http.get (ApiUrl) ;

ユーザーが提供した注文番号に基づいて、現在何が起こるのでしょうか?以下に、入力内容に応じて実際に呼び出されるURLを示します。

正規化は通常クライアント側では行われません(可能ではありますが)、ウェブサーバーはリクエストを以下の形式で正規化します。

注文ID番号 実際に呼び出されたURL
1 /api/v1/order/get/1
1/.../.../削除/1 /api/v1/order/delete/1

2つ目の例を入力すると、識別番号「1」を持つ注文を取得する代わりに、実際にはdeleteメソッドを呼び出しました。当然ながら、これにより注文が削除されます。

減衰

横断経路について言及する際、直接的な軽減策と間接的/防御的技術の両方が存在し、これらは可能な限り頻繁に適用できるだけでなく、適用すべきである。まず経路の管理方法について見ていこう。

直接減衰

経路を管理する際には、経路解決プロセス(または経路の正規化)とその重要性を理解する必要があります。

'/var/www/api/documents/../../.. /etc/shadow' のようなパスは非正規化パスです。ファイルシステムにこのパスを要求すると、システムはこれを「/etc/shadow」に正規化します。非正規化パスを開こうとしないことが極めて重要です。代わりに、まずパスを正規化し、目的のファイルまたはディレクトリのみを指していることを確認してから読み込む必要があります。

let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename ;

let resolvedPath = path.resolve(path) ;

si (!ResolvedPath.StartWith(BaseFolder)
return « J'ai essayé de lire en dehors du dossier de base » ;
autre
renvoie file.read(ResolvedPath) ;

アンチパターン - ファイル名の仮のクリーンアップ

こんなことをしたくなるかもしれません:


let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename.replace («../», « ») ;
...

ただし、このアプローチは使用すべきではありません。パス管理の鍵は、常に正規パスを参照することです。

正規化されたパスが規則に違反しない限り、最終的にパスがどのように構築されるかは本質的に問題ではない。このようなパスの消毒を試みることは非常にエラーが発生しやすく、安全であることは稀であり、むしろ決して安全ではない。

アクセスを制限する

これまでの例では、Linuxでパスワードのハッシュ値を格納するファイル「/etc/shadow」の読み取りを使用してきました。しかし、アプリケーションがそのルートディレクトリ外にあるこのファイルや他のファイルを読み取る必要がある理由は全くありません。

コンテナを使用している場合、多くのリスクは既に軽減されているでしょう。コンテナ環境を強化する対策(root権限での実行を避けるなど)が不可欠です。Webプロセスの特権をすべて削除し、ファイルシステムへの読み取り権限を厳密に必要なファイルのみに制限することが強く推奨されます。

それでは、動作中のものをより効果的に示すために、いくつかの例を異なる言語でご紹介します。

C# - 非セキュア

パス全体を解決しない場合、またはパスの一部としてファイル名のみを使用することを保証しない場合、コードはパストラバーサル攻撃に対して脆弱です。

var BaseFolder = « /var/www/app/documents/ » ;
var FileName = «../../../../.. /etc/mot de passe « ;

//非セキュア:/etc/passwdを読み取る
var FileContents = File.ReadAllText (Path.Combine (BaseFolder, FileName)) ;

C# - セキュア - カノニカル

この例では、完全な(絶対)パスを解決し、ファイルの解決後のパスがベースディレクトリ内にあることを確認することで、パストラバーサルから保護しています。

var BaseFolder = « /var/www/app/documents/ » ;
var FileName = «../../../../.. /etc/mot de passe « ;

var CanonicalPath = Path.getFullPath (Path.Combine (BaseFolder, FileName)) ;

//SECURE : Rejette toute tentative de lecture en dehors de la base spécifiée.
si (! CanonicalPath. 基幹フォルダから開始)
「基幹フォルダ外でファイルを読み込もうとしました」を返す ;

var FileContents = File.ReadAllText(CanonicalPath) ;

C# - セキュア - ファイル名

この例では、パストラバーサル攻撃を防ぐため、ファイル名を含むパス部分のみを取得し、指定されたフォルダから脱出できないようにしています。

var BaseFolder = « /var/www/app/documents/ » ;

//他のサブフォルダへのナビゲーションを許可しない場合のみ使用
var FileName = Path.getFileName («../../../../.. /etc/passwd «) ;

//安全:/var/www/app/documents/passwd を読み取る
var FileContents = File.ReadAllText (Path.Combine (BaseFolder, FileName)) ;

Java - 非セキュア

パス全体を解決しない場合、またはパスの一部としてファイル名のみを使用することを保証しない場合、コードはパストラバーサル攻撃に対して脆弱です。

String BaseFolder = « /var/www/app/documents/ » ;
Nom de chaîne = «../../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
Chemin FilePath = Paths.get (BaseFolder + FileName) ;
<String>Lignes de liste = Files.readAllLines (FilePath) ;

Java - セキュア - Canonical

この例では、完全な(絶対)パスを解決し、ファイルの解決後のパスがベースディレクトリ内にあることを確認することで、パストラバーサルから保護しています。

String BaseFolder = « /var/www/app/documents/ » ;
Nom de chaîne = «../../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
Path NormalizedPath = Paths.get (baseFolder + FileName) .normalize () ;
si (! Chemin normalisé. ToString (). Commence par (BaseFolder)
{
return « Essayer de lire le chemin en dehors de la racine » ;
}
autre
{
<String>Lignes de liste = Files.ReadAllLines (NormalizedPath) ;
}

Java - セキュア - ファイル名

この例では、パストラバーサル攻撃を防ぐため、ファイル名を含むパス部分のみを取得し、指定されたフォルダから脱出できないようにしています。

String BaseFolder = « /var/www/app/documents/ » ;

//À utiliser uniquement si vous n'autorisez pas la navigation dans d'autres sous-dossiers
Chaîne FileName = Paths.get («../../../../../.. /etc/passwd «) .getFileName () .toString () ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
Chemin FilePath = Paths.get (BaseFolder + FileName) ;
<String>Lignes de liste = Files.readAllLines (FilePath) ;

JavaScript - 非セキュア

パス全体を解決しない場合、またはパスの一部としてファイル名のみを使用することを保証しない場合、コードはパストラバーサル攻撃に対して脆弱です。

const fs = require('fs');

const BaseFolder = « /var/www/app/documents/ »;
const FileName = «../../../../.. /etc/mot de passe « ;

//非セキュア:/etc/passwdを読み込み
const data = fs.ReadFileSync (BaseFolder + FileName, 'utf8') ;

JavaScript - セキュア - カノニカル

この例では、完全な(絶対)パスを解決し、ファイルの解決後のパスがベースディレクトリ内にあることを確認することで、パストラバーサルから保護しています。

const fs = require (« fs ») ;
const path = require (« chemin ») ;

const BaseFolder = « /var/www/app/documents/ » ;
const FileName = «../../../../.. /etc/mot de passe « ;

const NormalizedPath = path.normalize (path.join (baseFolder, FileName)) ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
const data = fs.ReadFileSync (NormalizedPath, 'utf8') ;

JavaScript - セキュア - ファイル名

この例では、パストラバーサル攻撃を防ぐため、ファイル名を含むパス部分のみを取得し、指定されたフォルダから脱出できないようにしています。

const fs = require(« fs ») ;
const path = require(« chemin ») ;

const BaseFolder = « /var/www/app/documents/ » ;
const FileName = path.basename(«../../../../.. /etc/passwd «) ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
const data = fs.ReadFileSync (path.join (baseFolder, FileName), 'utf8') ;

Python - 非セキュア

パス全体を解決しない場合、またはパスの一部としてファイル名のみを使用することを保証しない場合、コードはパストラバーサル攻撃に対して脆弱です。

基本ディレクトリ = 「/var/www/app/documents/」
ファイル名 = 「../../../../.. /etc/パスワード」

# 非セキュア:/etc/passwdを読み込み
ファイル内容 = (基本ディレクトリ + ファイル名)を開く .read()

Python - セキュア - カノニカル

この例では、完全な(絶対)パスを解決し、ファイルの解決後のパスがベースディレクトリ内にあることを確認することで、パストラバーサルから保護しています。

importer os.path

Dossier de base = « /var/www/app/documents/ »
Nom du fichier = «../../../../.. /etc/パスワード」

標準化パス = os.path.normpath (ベースフォルダ + ファイル名)

# セキュリティ:指定されたベースフォルダ以外のファイル読み取りの試みをすべて拒否
もし NormalizedPath.StartsWith (BaseFolder) でない場合:
ベースフォルダからの読み取りを試みる 

# セキュア:/var/www/app/documents/passwd を読み込み
ファイルの内容 = open(path, mode=read)

Python - セキュア - ファイル名

この例では、パストラバーサル攻撃を防ぐため、ファイル名を含むパス部分のみを取得し、指定されたフォルダから脱出できないようにしています。




importer os.path

ベースディレクトリ = 「/var/www/app/documents/ 」
ファイル名 = os.path.basename(「../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../