A static security analysis tool for PHP and Laravel applications with recursive taint analysis capabilities. Features: - Comprehensive vulnerability detection (XSS, SQL Injection, Command Injection, Path Traversal, CSRF, Authentication issues) - Recursive taint analysis across function calls - Blade template analysis with context-aware XSS detection - Smart escape detection and escape bypass detection - Syntax highlighting in terminal output - Multi-language support (Japanese/English) - Docker support for easy deployment - Multiple output formats (text, JSON, HTML, SARIF, Markdown) - CI/CD integration ready (GitHub Actions, GitLab CI) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
862 lines
24 KiB
Markdown
862 lines
24 KiB
Markdown
# PHP/Laravel セキュリティリンター 検出ルール一覧
|
|
|
|
このドキュメントでは、セキュリティリンターが検出できる脆弱性パターンを詳細に説明します。
|
|
|
|
---
|
|
|
|
## 目次
|
|
|
|
1. [XSS (クロスサイトスクリプティング)](#1-xss-クロスサイトスクリプティング)
|
|
2. [SQLインジェクション](#2-sqlインジェクション)
|
|
3. [コマンドインジェクション](#3-コマンドインジェクション)
|
|
4. [パストラバーサル](#4-パストラバーサル)
|
|
5. [認証セキュリティ](#5-認証セキュリティ)
|
|
6. [CSRF/セッションセキュリティ](#6-csrfセッションセキュリティ)
|
|
7. [設定セキュリティ](#7-設定セキュリティ)
|
|
|
|
---
|
|
|
|
## 1. XSS (クロスサイトスクリプティング)
|
|
|
|
### 1.1 Blade テンプレートの生出力
|
|
|
|
| パターン | 重大度 | 説明 |
|
|
|---------|--------|------|
|
|
| `{!! $var !!}` | HIGH | エスケープされていない変数の出力 |
|
|
| `{!! $array['key'] !!}` | HIGH | 配列アクセスの生出力 |
|
|
| `{!! $obj->prop !!}` | HIGH | プロパティアクセスの生出力 |
|
|
|
|
**安全と判定されるパターン:**
|
|
```blade
|
|
{{-- エスケープ関数でラップされている場合 --}}
|
|
{!! 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 スタイルのスラッシュを除去 |
|
|
|
|
**検出例:**
|
|
```blade
|
|
{{-- 危険: エスケープ後にデコードしている --}}
|
|
{!! 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 |
|
|
|
|
**検出例:**
|
|
```php
|
|
// 危険: ハードコードされた 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 ディレクティブの使用 |
|
|
|
|
**推奨される対策:**
|
|
```blade
|
|
{{-- 推奨: 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 |
|
|
|
|
**安全と判定されるパターン:**
|
|
```blade
|
|
{{-- 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 |
|
|
|
|
**推奨される対策:**
|
|
```blade
|
|
{{-- 危険 --}}
|
|
<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 ユーザー定義関数の再帰解析
|
|
|
|
リンターは、ユーザー定義関数を再帰的に解析して、戻り値がエスケープされているかを判定します。
|
|
|
|
```php
|
|
// 安全と判定: 戻り値が常にエスケープされている
|
|
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 |
|
|
|
|
**安全なパターン:**
|
|
```php
|
|
// パラメータバインディングを使用
|
|
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 | 検証フィルター |
|
|
|
|
**安全と判定されるパターン:**
|
|
```php
|
|
// 型キャストによるサニタイズ
|
|
$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()` | フォーマット文字列で迂回可能 |
|
|
|
|
**危険なパターン:**
|
|
```php
|
|
// 危険: サニタイズ後にデコード
|
|
$safe = mysqli_real_escape_string($conn, $input);
|
|
$unsafe = stripslashes($safe); // エスケープを解除
|
|
|
|
// 危険: urldecode でエスケープを迂回
|
|
$safe = addslashes($input);
|
|
$unsafe = urldecode($safe); // %27 -> ' に変換
|
|
```
|
|
|
|
### 2.7 ユーザー定義関数の再帰解析
|
|
|
|
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
|
|
|
|
```php
|
|
// 安全と判定: 戻り値が常にサニタイズされている
|
|
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 |
|
|
|
|
**安全なパターン:**
|
|
```php
|
|
// 配列引数を使用
|
|
$process = new Process(['command', $arg1, $arg2]);
|
|
```
|
|
|
|
### 3.6 Laravel Artisan
|
|
|
|
| パターン | 重大度 |
|
|
|---------|--------|
|
|
| `Artisan::call($userCommand)` | HIGH |
|
|
|
|
### 3.7 コマンドサニタイザー関数の検出
|
|
|
|
リンターは以下のサニタイザー関数を認識し、適切に使用されている場合は安全と判定します。
|
|
|
|
| 関数 | 説明 |
|
|
|------|------|
|
|
| `escapeshellarg()` | 単一の引数をエスケープ |
|
|
| `escapeshellcmd()` | コマンド全体をエスケープ |
|
|
| `basename()` | ファイル名部分のみ抽出 |
|
|
| `intval()`, `floatval()` | 型キャスト |
|
|
|
|
**安全と判定されるパターン:**
|
|
```php
|
|
// 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()` | フォーマット文字列で迂回可能 |
|
|
|
|
**危険なパターン:**
|
|
```php
|
|
// 危険: サニタイズ後にデコード
|
|
$safe = escapeshellarg($input);
|
|
$unsafe = urldecode($safe); // %20 -> スペースに変換
|
|
|
|
// 危険: サニタイズを壊す置換
|
|
$safe = escapeshellarg($input);
|
|
$unsafe = str_replace("'", "", $safe); // エスケープを除去
|
|
|
|
// 危険: sprintf でフォーマット文字列攻撃
|
|
$cmd = sprintf($userFormat, escapeshellarg($arg)); // $userFormat が制御可能
|
|
```
|
|
|
|
### 3.9 ユーザー定義関数の再帰解析
|
|
|
|
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
|
|
|
|
```php
|
|
// 安全と判定: 戻り値が常にサニタイズされている
|
|
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()` | ハッシュベースの安全なファイル名 |
|
|
|
|
**安全と判定されるパターン:**
|
|
```php
|
|
// 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()` | 文字を構築可能 |
|
|
|
|
**危険なパターン:**
|
|
```php
|
|
// 危険: サニタイズ後にデコード
|
|
$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 ユーザー定義関数の再帰解析
|
|
|
|
リンターはユーザー定義関数を再帰的に解析し、安全かどうかを判定します。
|
|
|
|
```php
|
|
// 安全と判定: 戻り値が常にサニタイズされている
|
|
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 |
|
|
|
|
**推奨:**
|
|
```php
|
|
// 定時間比較を使用
|
|
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 |
|
|
|
|
**安全なパターン:**
|
|
```php
|
|
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 |
|
|
|
|
---
|
|
|
|
## 再帰的なテイント解析
|
|
|
|
リンターは、関数呼び出しを通じてユーザー入力(テイントデータ)がどのように伝播するかを追跡します。
|
|
|
|
### テイントソース(汚染源)
|
|
|
|
```php
|
|
$_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')
|
|
```
|
|
|
|
### サニタイザー(浄化関数)
|
|
|
|
以下の関数を通過したデータは、対応する脆弱性に対して安全と判定されます:
|
|
|
|
```php
|
|
// 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レベルの関数呼び出しを追跡します。
|
|
|
|
```php
|
|
// 例: 3レベルの追跡
|
|
function level1($input) { return level2($input); }
|
|
function level2($input) { return level3($input); }
|
|
function level3($input) { return $input; } // テイント
|
|
|
|
echo level1($_GET['input']); // 検出される
|
|
```
|
|
|
|
---
|
|
|
|
## 使用例
|
|
|
|
```bash
|
|
# 単一ファイルの解析
|
|
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** | 改善推奨。ベストプラクティス違反 |
|
|
|
|
---
|
|
|
|
## 制限事項
|
|
|
|
1. **静的解析の限界**: 実行時の値は判定できません
|
|
2. **動的なメソッド呼び出し**: `$obj->$method()` は追跡困難
|
|
3. **外部ライブラリ**: vendor 内のコードは解析対象外
|
|
4. **フレームワーク固有の機能**: 一部のLaravel固有パターンは検出できない場合があります
|
|
5. **偽陽性**: 安全なコードが危険と判定される場合があります
|
|
|
|
---
|
|
|
|
## バージョン
|
|
|
|
- ドキュメント作成日: 2024
|
|
- 対応 PHP バージョン: 8.0+
|
|
- 対応 Laravel バージョン: 9.x, 10.x, 11.x
|