- Add section 8 to DETECTION_RULES.md covering: - Mass Assignment detection patterns - Raw SQL injection detection - CSRF protection checks - File upload validation rules - Route authentication middleware - Rate limiting detection - Update README.md with Laravel-specific security in detectable vulnerabilities section (ja/en) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
31 KiB
PHP/Laravel セキュリティリンター 検出ルール一覧
このドキュメントでは、セキュリティリンターが検出できる脆弱性パターンを詳細に説明します。
目次
- XSS (クロスサイトスクリプティング)
- SQLインジェクション
- コマンドインジェクション
- パストラバーサル
- 認証セキュリティ
- CSRF/セッションセキュリティ
- 設定セキュリティ
- Laravel特有のセキュリティ
1. XSS (クロスサイトスクリプティング)
1.1 Blade テンプレートの生出力
| パターン | 重大度 | 説明 |
|---|---|---|
{!! $var !!} |
HIGH | エスケープされていない変数の出力 |
{!! $array['key'] !!} |
HIGH | 配列アクセスの生出力 |
{!! $obj->prop !!} |
HIGH | プロパティアクセスの生出力 |
安全と判定されるパターン:
{{-- エスケープ関数でラップされている場合 --}}
{!! htmlspecialchars($var) !!}
{!! e($var) !!}
{!! htmlentities($var) !!}
{!! strip_tags($html) !!}
{{-- 文字列リテラルとエスケープ値の連結 --}}
{!! '<div>' . htmlspecialchars($name) . '</div>' !!}
{{-- Laravel 組み込みの安全な出力 --}}
{!! csrf_field() !!}
{!! method_field('PUT') !!}
{!! $errors !!}
{!! $slot !!}
{{-- Markdown プロセッサ --}}
{!! Markdown::parse($content) !!}
{!! Parsedown::instance()->text($content) !!}
{{-- サニタイズ関数 --}}
{!! clean($html) !!}
{!! sanitize($html) !!}
{!! purify($html) !!}
1.2 エスケープ破壊関数の検出
エスケープ処理を無効化する関数呼び出しを検出します。
| 関数 | 説明 |
|---|---|
html_entity_decode() |
HTMLエンティティをデコード |
htmlspecialchars_decode() |
htmlspecialchars の逆変換 |
urldecode() |
URLエンコードをデコード |
rawurldecode() |
rawurlencode の逆変換 |
base64_decode() |
Base64 デコード |
json_decode() |
JSON デコード |
stripslashes() |
スラッシュを除去 |
stripcslashes() |
C スタイルのスラッシュを除去 |
検出例:
{{-- 危険: エスケープ後にデコードしている --}}
{!! html_entity_decode(htmlspecialchars($data)) !!}
{!! urldecode(htmlspecialchars($url)) !!}
1.3 危険なハードコード HTML の検出
文字列リテラル内の危険な HTML を検出します。
| 検出パターン | 説明 |
|---|---|
<script> |
スクリプトタグ |
<iframe> |
iframe タグ |
<object>, <embed> |
プラグインタグ |
<meta>, <link>, <base> |
メタ情報タグ |
<form> |
フォームタグ(フィッシング) |
onclick=, onerror= 等 |
イベントハンドラ |
javascript: |
JavaScript プロトコル |
vbscript: |
VBScript プロトコル |
data:...;base64 |
Data URL |
検出例:
// 危険: ハードコードされた script タグ
function badFormatter($input) {
return '<script>alert("XSS")</script>' . htmlspecialchars($input);
}
// 安全: 危険なタグを含まない
function safeFormatter($input) {
return '<div class="wrapper">' . htmlspecialchars($input) . '</div>';
}
1.4 JavaScript コンテキスト
| パターン | 重大度 | 説明 |
|---|---|---|
<script>{{ $var }}</script> |
MEDIUM | script 内の Blade 出力 |
<script>{!! $var !!}</script> |
CRITICAL | script 内の生出力 |
@json($var) in <script> |
LOW | JSON ディレクティブの使用 |
推奨される対策:
{{-- 推奨: Js::from() を使用 --}}
<script>
var data = {{ Js::from($data) }};
</script>
1.5 URL コンテキスト
javascript: URL によるXSSを検出します。
| パターン | 重大度 |
|---|---|
href="{{ $url }}" |
MEDIUM |
src="{{ $url }}" |
MEDIUM |
action="{{ $url }}" |
MEDIUM |
formaction="{{ $url }}" |
MEDIUM |
安全と判定されるパターン:
{{-- route() や url() ヘルパーは安全 --}}
<a href="{{ route('home') }}">Home</a>
<a href="{{ url('/about') }}">About</a>
<form action="{{ action('Controller@method') }}">
1.6 イベントハンドラ
| パターン | 重大度 |
|---|---|
onclick="{{ $var }}" |
HIGH |
onerror="{{ $var }}" |
HIGH |
onload="{{ $var }}" |
HIGH |
onclick="{!! $var !!}" |
CRITICAL |
推奨される対策:
{{-- 危険 --}}
<button onclick="doAction('{{ $action }}')">Click</button>
{{-- 推奨: data 属性を使用 --}}
<button data-action="{{ $action }}" class="js-action">Click</button>
<script>
document.querySelector('.js-action').addEventListener('click', function() {
doAction(this.dataset.action);
});
</script>
1.7 Style インジェクション
| パターン | 重大度 |
|---|---|
style="{{ $css }}" |
MEDIUM |
style="color: {{ $color }}" |
MEDIUM |
1.8 引用符なし属性
| パターン | 重大度 |
|---|---|
data-value={{ $var }} |
LOW |
1.9 テンプレートインジェクション
| パターン | 重大度 |
|---|---|
@include($var) |
HIGH |
@extends($var) |
HIGH |
@component($var) |
HIGH |
@each($var, ...) |
HIGH |
1.10 SVG コンテキスト
| パターン | 重大度 |
|---|---|
<svg>{{ $var }}</svg> |
MEDIUM |
1.11 @php ブロック
| パターン | 重大度 |
|---|---|
@php echo $var; @endphp |
MEDIUM |
1.12 PHP の echo/print
| パターン | 重大度 |
|---|---|
echo $taintedVar; |
HIGH |
print $taintedVar; |
HIGH |
printf("%s", $taintedVar) |
HIGH |
1.13 ユーザー定義関数の再帰解析
リンターは、ユーザー定義関数を再帰的に解析して、戻り値がエスケープされているかを判定します。
// 安全と判定: 戻り値が常にエスケープされている
function safeFormatter($input) {
return htmlspecialchars(trim($input));
}
// 危険と判定: エスケープされていない戻り値がある
function unsafeFormatter($input) {
return trim($input); // エスケープなし
}
// 危険と判定: エスケープを壊している
function breakEscaping($escaped) {
return html_entity_decode($escaped);
}
2. SQLインジェクション
2.1 Laravel クエリビルダー
| パターン | 重大度 |
|---|---|
DB::raw($userInput) |
HIGH |
DB::select($query . $userInput) |
HIGH |
->whereRaw($userInput) |
HIGH |
->selectRaw($userInput) |
HIGH |
->orderByRaw($userInput) |
HIGH |
->havingRaw($userInput) |
HIGH |
->groupByRaw($userInput) |
HIGH |
安全なパターン:
// パラメータバインディングを使用
DB::select('SELECT * FROM users WHERE id = ?', [$id]);
$query->whereRaw('column = ?', [$value]);
2.2 PDO
| パターン | 重大度 |
|---|---|
$pdo->query($sql . $input) |
HIGH |
$pdo->exec($sql . $input) |
HIGH |
$pdo->prepare($sql . $input) |
HIGH |
2.3 MySQLi
| パターン | 重大度 |
|---|---|
$mysqli->query($sql . $input) |
HIGH |
mysqli_query($conn, $sql . $input) |
HIGH |
2.4 文字列連結によるクエリ構築
| パターン | 重大度 |
|---|---|
"SELECT * FROM users WHERE id = " . $id |
HIGH |
"SELECT * FROM users WHERE id = {$id}" |
HIGH |
2.5 SQLサニタイザー関数の検出
リンターは以下のサニタイザー関数を認識し、適切に使用されている場合は安全と判定します。
| 関数/メソッド | 説明 |
|---|---|
intval(), floatval() |
型キャスト(最も安全) |
(int), (float) |
型キャスト演算子 |
mysqli_real_escape_string() |
MySQLi エスケープ |
PDO::quote() |
PDO エスケープ |
pg_escape_string(), pg_escape_literal() |
PostgreSQL エスケープ |
addslashes() |
基本的なエスケープ(非推奨) |
filter_var() + FILTER_VALIDATE_INT |
検証フィルター |
安全と判定されるパターン:
// 型キャストによるサニタイズ
$query = "SELECT * FROM users WHERE id = " . intval($id);
$query = "SELECT * FROM users WHERE id = " . (int)$id;
// エスケープ関数
$query = "SELECT * FROM users WHERE name = '" . mysqli_real_escape_string($conn, $name) . "'";
$query = "SELECT * FROM users WHERE name = " . $pdo->quote($name);
// ユーザー定義サニタイザー関数
function sanitizeId($input) {
return intval($input);
}
$query = "SELECT * FROM users WHERE id = " . sanitizeId($userId);
2.6 SQLサニタイザー破壊パターン
サニタイズ後に以下の関数を使用すると、サニタイズが無効化されます。
| 関数 | 説明 |
|---|---|
stripslashes() |
エスケープを解除 |
urldecode() |
URLデコードで特殊文字を復活 |
html_entity_decode() |
HTMLエンティティをデコード |
base64_decode() |
Base64 デコード |
sprintf() |
フォーマット文字列で迂回可能 |
危険なパターン:
// 危険: サニタイズ後にデコード
$safe = mysqli_real_escape_string($conn, $input);
$unsafe = stripslashes($safe); // エスケープを解除
// 危険: urldecode でエスケープを迂回
$safe = addslashes($input);
$unsafe = urldecode($safe); // %27 -> ' に変換
2.7 ユーザー定義関数の再帰解析
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
// 安全と判定: 戻り値が常にサニタイズされている
function getIntParam($key) {
return intval($_GET[$key] ?? 0);
}
// 危険と判定: サニタイズを破壊している
function processInput($input) {
$escaped = mysqli_real_escape_string($this->conn, $input);
return stripslashes($escaped); // エスケープを解除!
}
3. コマンドインジェクション
3.1 シェル実行関数
| 関数 | 重大度 |
|---|---|
exec($cmd . $input) |
CRITICAL |
shell_exec($cmd . $input) |
CRITICAL |
system($cmd . $input) |
CRITICAL |
passthru($cmd . $input) |
CRITICAL |
proc_open($cmd . $input, ...) |
CRITICAL |
popen($cmd . $input, ...) |
CRITICAL |
`$cmd $input` (バッククォート) |
CRITICAL |
3.2 コード実行関数
| 関数 | 重大度 |
|---|---|
eval($code) |
CRITICAL |
create_function($args, $code) |
CRITICAL |
assert($expr) (文字列引数) |
CRITICAL |
preg_replace('/.../e', ...) |
CRITICAL |
3.3 コールバック関数
| 関数 | 重大度 |
|---|---|
call_user_func($userCallback) |
HIGH |
call_user_func_array($userCallback, ...) |
HIGH |
array_map($userCallback, ...) |
HIGH |
array_filter($arr, $userCallback) |
HIGH |
3.4 ファイルインクルード
| 関数 | 重大度 |
|---|---|
include($userPath) |
CRITICAL |
include_once($userPath) |
CRITICAL |
require($userPath) |
CRITICAL |
require_once($userPath) |
CRITICAL |
3.5 Symfony Process
| パターン | 重大度 |
|---|---|
Process::fromShellCommandline($cmd . $input) |
HIGH |
new Process($stringCmd . $input) |
HIGH |
安全なパターン:
// 配列引数を使用
$process = new Process(['command', $arg1, $arg2]);
3.6 Laravel Artisan
| パターン | 重大度 |
|---|---|
Artisan::call($userCommand) |
HIGH |
3.7 コマンドサニタイザー関数の検出
リンターは以下のサニタイザー関数を認識し、適切に使用されている場合は安全と判定します。
| 関数 | 説明 |
|---|---|
escapeshellarg() |
単一の引数をエスケープ |
escapeshellcmd() |
コマンド全体をエスケープ |
basename() |
ファイル名部分のみ抽出 |
intval(), floatval() |
型キャスト |
安全と判定されるパターン:
// escapeshellarg で引数をエスケープ
exec('ls -la ' . escapeshellarg($path));
// escapeshellcmd でコマンド全体をエスケープ
exec(escapeshellcmd($command) . ' ' . escapeshellarg($arg));
// 型キャストで数値に限定
exec('kill ' . intval($pid));
// 配列引数による安全な Process 使用
$process = new Process(['convert', $inputFile, $outputFile]);
3.8 コマンドサニタイザー破壊パターン
サニタイズ後に以下の関数を使用すると、サニタイズが無効化されます。
| 関数 | 説明 |
|---|---|
stripslashes() |
エスケープを解除 |
urldecode() |
URLデコードで特殊文字を復活 |
str_replace() |
エスケープ文字を削除可能 |
preg_replace() |
エスケープ文字を削除可能 |
sprintf() |
フォーマット文字列で迂回可能 |
危険なパターン:
// 危険: サニタイズ後にデコード
$safe = escapeshellarg($input);
$unsafe = urldecode($safe); // %20 -> スペースに変換
// 危険: サニタイズを壊す置換
$safe = escapeshellarg($input);
$unsafe = str_replace("'", "", $safe); // エスケープを除去
// 危険: sprintf でフォーマット文字列攻撃
$cmd = sprintf($userFormat, escapeshellarg($arg)); // $userFormat が制御可能
3.9 ユーザー定義関数の再帰解析
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
// 安全と判定: 戻り値が常にサニタイズされている
function safeCommand($input) {
return escapeshellarg(trim($input));
}
// 危険と判定: サニタイズを破壊している
function processCommand($input) {
$escaped = escapeshellarg($input);
return urldecode($escaped); // エスケープを解除!
}
4. パストラバーサル
4.1 ファイル操作関数
| 関数 | 重大度 |
|---|---|
file_get_contents($userPath) |
HIGH |
file_put_contents($userPath, ...) |
HIGH |
fopen($userPath, ...) |
HIGH |
readfile($userPath) |
HIGH |
unlink($userPath) |
HIGH |
copy($userPath, ...) |
HIGH |
rename($userPath, ...) |
HIGH |
4.2 ファイルアップロード
| パターン | 重大度 |
|---|---|
move_uploaded_file($tmp, $userDest) |
HIGH |
4.3 Laravel Storage
| パターン | 重大度 |
|---|---|
Storage::get($userPath) |
MEDIUM |
Storage::put($userPath, ...) |
MEDIUM |
Storage::delete($userPath) |
MEDIUM |
Storage::download($userPath) |
MEDIUM |
4.4 レスポンスダウンロード
| パターン | 重大度 |
|---|---|
response()->download($userPath) |
HIGH |
response()->file($userPath) |
HIGH |
4.5 パスサニタイザー関数の検出
リンターは以下のサニタイザー関数を認識し、適切に使用されている場合は安全と判定します。
| 関数 | 説明 |
|---|---|
basename() |
ディレクトリ成分を除去(最も効果的) |
realpath() |
パスを正規化し、存在を確認 |
pathinfo(..., PATHINFO_BASENAME) |
ファイル名部分のみ抽出 |
intval() |
数値IDに限定 |
Str::random(), Str::uuid() |
安全なファイル名生成 |
$file->hashName() |
ハッシュベースの安全なファイル名 |
安全と判定されるパターン:
// basename でディレクトリを除去
$filename = basename($userInput);
file_get_contents($basePath . '/' . $filename);
// realpath で検証
$path = realpath($basePath . '/' . $userInput);
if ($path && str_starts_with($path, $basePath)) {
file_get_contents($path);
}
// 数値IDに限定
$content = file_get_contents('/docs/' . intval($id) . '.txt');
// 安全なファイル名生成
$filename = Str::random(40) . '.pdf';
Storage::put($filename, $content);
// ハッシュベースのファイル名
$path = $request->file('upload')->hashName();
4.6 危険なパストラバーサルパターン
以下のパターンが文字列リテラルに含まれている場合、警告します。
| パターン | 説明 |
|---|---|
.. |
ディレクトリトラバーサル |
../, ..\\ |
Unix/Windows トラバーサル |
%2e%2e |
URLエンコードされた .. |
%252e%252e |
ダブルURLエンコード |
..%c0%af, ..%c1%9c |
オーバーロング UTF-8 エンコード |
4.7 パスサニタイザー破壊パターン
サニタイズ後に以下の関数を使用すると、サニタイズが無効化されます。
| 関数 | 説明 |
|---|---|
urldecode() |
%2e%2e → .. |
rawurldecode() |
URLデコード |
base64_decode() |
Base64 でトラバーサルを隠蔽可能 |
hex2bin() |
16進数でトラバーサルを隠蔽可能 |
html_entity_decode() |
HTMLエンティティをデコード |
chr() |
文字を構築可能 |
危険なパターン:
// 危険: サニタイズ後にデコード
$safe = basename($userInput);
$unsafe = urldecode($safe); // %2e%2e -> .. に変換
// 危険: base64 でトラバーサルを隠蔽
$path = base64_decode($userInput); // Li4vLi4vZXRjL3Bhc3N3ZA== -> ../../etc/passwd
// 危険: サニタイズ前に getClientOriginalName を使用
$filename = $file->getClientOriginalName(); // ユーザー制御!
Storage::put($filename, $content); // トラバーサル可能
4.8 ユーザー定義関数の再帰解析
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
// 安全と判定: 戻り値が常にサニタイズされている
function safePath($input) {
return basename(trim($input));
}
// 危険と判定: サニタイズを破壊している
function processPath($input) {
$safe = basename($input);
return urldecode($safe); // サニタイズを解除!
}
// 安全と判定: 検証ロジックを含む
function validatePath($input, $baseDir) {
$path = realpath($baseDir . '/' . $input);
if ($path && str_starts_with($path, $baseDir)) {
return $path;
}
return null;
}
5. 認証セキュリティ
5.1 弱いハッシュアルゴリズム
| 関数 | 重大度 |
|---|---|
md5($password) |
HIGH |
sha1($password) |
HIGH |
sha256($password) |
MEDIUM |
hash('md5', $password) |
HIGH |
5.2 不適切なパスワードハッシュ
| パターン | 重大度 |
|---|---|
password_hash($pw, PASSWORD_MD5) |
HIGH |
password_hash($pw, ..., ['cost' => 4]) |
MEDIUM |
5.3 ハードコードされた認証情報
| パターン | 重大度 |
|---|---|
$password = 'secret123' |
HIGH |
$apiKey = 'sk-xxx...' |
HIGH |
['password' => 'hardcoded'] |
HIGH |
5.4 タイミング攻撃
| パターン | 重大度 |
|---|---|
$token == $userToken |
MEDIUM |
strcmp($secret, $input) |
MEDIUM |
推奨:
// 定時間比較を使用
hash_equals($expected, $actual);
password_verify($password, $hash);
5.5 Base64 エンコード
| パターン | 重大度 |
|---|---|
base64_encode($password) |
HIGH |
6. CSRF/セッションセキュリティ
6.1 CSRF トークン
| パターン | 重大度 |
|---|---|
<form method="POST"> without @csrf |
HIGH |
Form missing csrf_field() |
HIGH |
6.2 メソッドスプーフィング
| パターン | 重大度 |
|---|---|
PUT/PATCH/DELETE form without @method |
LOW |
6.3 セッション設定
| パターン | 重大度 |
|---|---|
session_start() without options |
MEDIUM |
Missing cookie_httponly |
MEDIUM |
Missing cookie_secure |
MEDIUM |
Missing cookie_samesite |
MEDIUM |
6.4 セッション固定化
| パターン | 重大度 |
|---|---|
session_regenerate_id() without true |
MEDIUM |
session_regenerate_id(false) |
MEDIUM |
6.5 Cookie セキュリティ
| パターン | 重大度 |
|---|---|
Cookie without httponly |
MEDIUM |
Cookie without secure |
MEDIUM |
Cookie without samesite |
MEDIUM |
7. 設定セキュリティ
7.1 デバッグ出力
| 関数 | 重大度 |
|---|---|
phpinfo() |
HIGH |
var_dump($var) |
MEDIUM |
print_r($var) |
MEDIUM |
dd($var) |
MEDIUM |
dump($var) |
MEDIUM |
7.2 エラー表示
| パターン | 重大度 |
|---|---|
error_reporting(-1) |
MEDIUM |
ini_set('display_errors', '1') |
MEDIUM |
ini_set('display_startup_errors', '1') |
MEDIUM |
7.3 安全でないデシリアライズ
| パターン | 重大度 |
|---|---|
unserialize($data) |
HIGH |
unserialize($data, ['allowed_classes' => true]) |
HIGH |
安全なパターン:
unserialize($data, ['allowed_classes' => false]);
unserialize($data, ['allowed_classes' => [AllowedClass::class]]);
7.4 機密情報のログ出力
| パターン | 重大度 |
|---|---|
Log::info($password) |
MEDIUM |
logger($apiKey) |
MEDIUM |
7.5 ハードコードされた設定
| パターン | 重大度 |
|---|---|
'debug' => true |
MEDIUM |
'key' => 'hardcoded-secret' |
HIGH |
8. Laravel特有のセキュリティ
Laravel フレームワーク固有の脆弱性パターンを検出します。
8.1 Mass Assignment(一括代入)
Eloquent モデルで $fillable または $guarded プロパティが定義されていない場合を検出します。
| パターン | 重大度 |
|---|---|
Model without $fillable or $guarded |
HIGH |
Model::create($request->all()) |
HIGH |
$model->fill($request->all()) |
HIGH |
$model->update($request->all()) |
HIGH |
脆弱なコード:
// 危険: $fillable/$guarded がない
class User extends Model
{
// Mass Assignment 脆弱性
}
// 危険: $request->all() を直接使用
User::create($request->all());
$user->update($request->all());
安全なコード:
// $fillable を定義
class User extends Model
{
protected $fillable = ['name', 'email'];
}
// または $guarded を定義
class User extends Model
{
protected $guarded = ['id', 'is_admin'];
}
// 特定のフィールドのみ使用
User::create($request->only(['name', 'email']));
$user->update($request->validated());
8.2 Raw SQL インジェクション
Laravel のクエリビルダーで生SQLを使用し、パラメータバインディングがない場合を検出します。
| パターン | 重大度 |
|---|---|
DB::raw($variable) |
HIGH |
whereRaw($sql) (バインディングなし) |
HIGH |
selectRaw($sql) (バインディングなし) |
HIGH |
orderByRaw($sql) (バインディングなし) |
HIGH |
havingRaw($sql) (バインディングなし) |
HIGH |
groupByRaw($sql) (バインディングなし) |
HIGH |
脆弱なコード:
// 危険: 変数を含む DB::raw
$column = $request->input('column');
DB::table('users')->select(DB::raw($column))->get();
// 危険: バインディングなしの whereRaw
$name = $request->input('name');
User::whereRaw("name = '$name'")->get();
// 危険: バインディングなしの orderByRaw
$order = $request->input('order');
User::orderByRaw($order)->get();
安全なコード:
// パラメータバインディングを使用
User::whereRaw('name = ?', [$name])->get();
User::orderByRaw('FIELD(status, ?, ?, ?)', ['active', 'pending', 'inactive'])->get();
// クエリビルダーを使用(推奨)
User::where('name', $name)->get();
User::orderBy($allowedColumns[$request->input('column')])->get();
8.3 CSRF 保護
POST/PUT/PATCH/DELETE メソッドのフォームに CSRF トークンがない場合を検出します。
| パターン | 重大度 |
|---|---|
<form method="POST"> without @csrf |
HIGH |
<form method="post"> without csrf_field() |
HIGH |
<form method="PUT"> without CSRF |
HIGH |
<form method="DELETE"> without CSRF |
HIGH |
脆弱なコード:
{{-- 危険: @csrf がない --}}
<form method="POST" action="/submit">
<input type="text" name="data">
<button type="submit">送信</button>
</form>
安全なコード:
{{-- @csrf ディレクティブを使用 --}}
<form method="POST" action="/submit">
@csrf
<input type="text" name="data">
<button type="submit">送信</button>
</form>
{{-- または csrf_field() ヘルパー --}}
<form method="POST" action="/submit">
{{ csrf_field() }}
<input type="text" name="data">
<button type="submit">送信</button>
</form>
8.4 ファイルアップロード検証
ファイルアップロードで extensions ルールのみを使用し、mimes または mimetypes ルールがない場合を検出します。拡張子のみのチェックは簡単にバイパスできます。
| パターン | 重大度 |
|---|---|
'file' => 'extensions:jpg,png' (mimes なし) |
MEDIUM |
'file' => ['extensions:jpg', 'file'] (mimes なし) |
MEDIUM |
脆弱なコード:
// 危険: 拡張子のみで検証
public function rules()
{
return [
'avatar' => ['required', 'file', 'extensions:jpg,png'],
'document' => 'file|extensions:pdf,doc',
];
}
安全なコード:
// mimes または mimetypes を併用
public function rules()
{
return [
// mimes を使用(推奨)
'avatar' => ['required', 'file', 'mimes:jpeg,png'],
// extensions と mimes を両方使用
'document' => ['file', 'extensions:pdf,doc', 'mimes:pdf,msword'],
// mimetypes を使用
'video' => ['file', 'mimetypes:video/mp4,video/mpeg'],
];
}
8.5 ルート認証ミドルウェア
センシティブなルートに auth ミドルウェアがない場合を検出します。
| パターン | 重大度 |
|---|---|
Route::*('/admin/*') without auth |
LOW |
Route::*('/dashboard/*') without auth |
LOW |
Route::*('/user/*') without auth |
LOW |
Route::*('/account/*') without auth |
LOW |
Route::*('/settings/*') without auth |
LOW |
Route::*('/profile/*') without auth |
LOW |
検出対象のセンシティブなルートパターン:
/admin/,/dashboard/,/user/,/users//account/,/settings/,/profile//manage/,/management/,/billing/
脆弱なコード:
// 危険: 認証なしで管理画面にアクセス可能
Route::get('/admin/dashboard', [AdminController::class, 'index']);
Route::get('/users/manage', [UserController::class, 'manage']);
安全なコード:
// ルートにミドルウェアを追加
Route::get('/admin/dashboard', [AdminController::class, 'index'])
->middleware('auth');
// グループでミドルウェアを適用(推奨)
Route::middleware(['auth'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
Route::get('/users/manage', [UserController::class, 'manage']);
});
8.6 レート制限
認証関連のルートに throttle ミドルウェアがない場合を検出します。ブルートフォース攻撃を防ぐために重要です。
| パターン | 重大度 |
|---|---|
Route::post('/login') without throttle |
LOW |
Route::post('/register') without throttle |
LOW |
Route::post('/password/reset') without throttle |
LOW |
Route::post('/password/email') without throttle |
LOW |
Route::post('/forgot-password') without throttle |
LOW |
Route::post('/2fa/*') without throttle |
LOW |
脆弱なコード:
// 危険: レート制限なしでブルートフォース可能
Route::post('/login', [AuthController::class, 'login']);
Route::post('/password/reset', [PasswordController::class, 'reset']);
安全なコード:
// throttle ミドルウェアを追加
Route::post('/login', [AuthController::class, 'login'])
->middleware('throttle:5,1'); // 1分間に5回まで
// グループでレート制限を適用
Route::middleware(['throttle:60,1'])->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/password/reset', [PasswordController::class, 'reset']);
});
再帰的なテイント解析
リンターは、関数呼び出しを通じてユーザー入力(テイントデータ)がどのように伝播するかを追跡します。
テイントソース(汚染源)
$_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES, $_SERVER
$request->input()
$request->get()
$request->post()
$request->query()
$request->all()
$request->only()
$request->except()
$request->file()
$request->cookie()
request()->input()
Request::input()
file_get_contents('php://input')
サニタイザー(浄化関数)
以下の関数を通過したデータは、対応する脆弱性に対して安全と判定されます:
// XSS
htmlspecialchars(), htmlentities(), strip_tags(), e()
// SQL
addslashes(), mysqli_real_escape_string(), pg_escape_string()
// パス
basename(), realpath()
// 型キャスト
(int), (float), (bool), intval(), floatval(), boolval()
// バリデーション
validate(), validated()
解析の深度
デフォルトで最大10レベルの関数呼び出しを追跡します。
// 例: 3レベルの追跡
function level1($input) { return level2($input); }
function level2($input) { return level3($input); }
function level3($input) { return $input; } // テイント
echo level1($_GET['input']); // 検出される
使用例
# 単一ファイルの解析
php bin/security-lint path/to/file.php
# ディレクトリの解析(再帰的な関数解析が有効)
php bin/security-lint app/
# 高重大度のみ表示
php bin/security-lint app/ -s high
# JSON 形式で出力
php bin/security-lint app/ -f json -o report.json
# 英語で出力
php bin/security-lint app/ -l en
重大度レベル
| レベル | 説明 |
|---|---|
| CRITICAL | 即時対応が必要。直接的な攻撃が可能 |
| HIGH | 早急な対応が必要。重大な脆弱性 |
| MEDIUM | 計画的な対応が必要。潜在的なリスク |
| LOW | 改善推奨。ベストプラクティス違反 |
制限事項
- 静的解析の限界: 実行時の値は判定できません
- 動的なメソッド呼び出し:
$obj->$method()は追跡困難 - 外部ライブラリ: vendor 内のコードは解析対象外
- フレームワーク固有の機能: 一部のLaravel固有パターンは検出できない場合があります
- 偽陽性: 安全なコードが危険と判定される場合があります
バージョン
- ドキュメント作成日: 2024
- 対応 PHP バージョン: 8.0+
- 対応 Laravel バージョン: 9.x, 10.x, 11.x