291 lines
8.6 KiB
Markdown
291 lines
8.6 KiB
Markdown
|
|
# セキュリティリンター クイックリファレンス
|
|||
|
|
|
|||
|
|
## XSS 検出パターン
|
|||
|
|
|
|||
|
|
### Blade テンプレート
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: CRITICAL
|
|||
|
|
├── <script>{!! $var !!}</script> # JS内の生出力
|
|||
|
|
└── onclick="{!! $var !!}" # イベントハンドラ内の生出力
|
|||
|
|
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── {!! $var !!} # 生出力
|
|||
|
|
├── {!! func($var) !!} # 関数がエスケープしない場合
|
|||
|
|
├── {!! html_entity_decode(...) !!} # エスケープ破壊
|
|||
|
|
├── {!! '<script>...' . $var !!} # 危険なハードコードHTML
|
|||
|
|
├── onclick="{{ $var }}" # イベントハンドラ
|
|||
|
|
└── @include($var) # テンプレートインジェクション
|
|||
|
|
|
|||
|
|
重大度: MEDIUM
|
|||
|
|
├── <script>{{ $var }}</script> # JS コンテキスト
|
|||
|
|
├── href="{{ $var }}" # URL コンテキスト
|
|||
|
|
├── style="{{ $var }}" # CSS インジェクション
|
|||
|
|
└── <svg>{{ $var }}</svg> # SVG コンテキスト
|
|||
|
|
|
|||
|
|
重大度: LOW
|
|||
|
|
├── data-x={{ $var }} # 引用符なし属性
|
|||
|
|
└── @json($var) in <script> # JSON ディレクティブ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン(検出されない)
|
|||
|
|
|
|||
|
|
```blade
|
|||
|
|
{!! htmlspecialchars($var) !!} ✓ エスケープ関数
|
|||
|
|
{!! e($var) !!} ✓ Laravel ヘルパー
|
|||
|
|
{!! '<div>' . e($var) . '</div>' !!} ✓ 安全な連結
|
|||
|
|
{!! csrf_field() !!} ✓ 組み込み関数
|
|||
|
|
{!! $errors !!} ✓ Laravel 組み込み
|
|||
|
|
{!! Markdown::parse($var) !!} ✓ Markdown プロセッサ
|
|||
|
|
{!! safeFunc($var) !!} ✓ エスケープを返す関数
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SQLインジェクション 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── DB::raw("... $var ...")
|
|||
|
|
├── DB::select("... $var ...")
|
|||
|
|
├── ->whereRaw("... $var ...")
|
|||
|
|
├── ->orderByRaw($var)
|
|||
|
|
├── $pdo->query("... $var ...")
|
|||
|
|
├── $mysqli->query("... $var ...")
|
|||
|
|
└── "SELECT * FROM t WHERE id = $var"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### SQLサニタイザー関数(これらは安全と判定)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
intval($var), (int)$var ✓ 型キャスト
|
|||
|
|
floatval($var), (float)$var ✓ 型キャスト
|
|||
|
|
mysqli_real_escape_string($conn, $var) ✓ エスケープ
|
|||
|
|
$pdo->quote($var) ✓ PDO エスケープ
|
|||
|
|
filter_var($var, FILTER_VALIDATE_INT) ✓ 検証
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### SQLサニタイザー破壊パターン(危険)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
stripslashes(mysqli_real_escape_string(...)) ✗
|
|||
|
|
urldecode(addslashes($var)) ✗
|
|||
|
|
html_entity_decode($pdo->quote($var)) ✗
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
DB::select('SELECT * FROM t WHERE id = ?', [$id]); ✓
|
|||
|
|
$query->where('column', '=', $value); ✓
|
|||
|
|
$query->whereRaw('col = ?', [$value]); ✓
|
|||
|
|
"SELECT * FROM t WHERE id = " . intval($id) ✓
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## コマンドインジェクション 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: CRITICAL
|
|||
|
|
├── exec($cmd . $input)
|
|||
|
|
├── shell_exec($cmd . $input)
|
|||
|
|
├── system($cmd . $input)
|
|||
|
|
├── `$cmd $input`
|
|||
|
|
├── eval($code)
|
|||
|
|
├── include($userPath)
|
|||
|
|
└── preg_replace('/.../e', ...)
|
|||
|
|
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── call_user_func($userCallback)
|
|||
|
|
├── Process::fromShellCommandline($cmd)
|
|||
|
|
└── Artisan::call($userCmd)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### コマンドサニタイザー関数(これらは安全と判定)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
escapeshellarg($arg) ✓ 引数エスケープ
|
|||
|
|
escapeshellcmd($cmd) ✓ コマンドエスケープ
|
|||
|
|
basename($path) ✓ ファイル名のみ
|
|||
|
|
intval($var) ✓ 型キャスト
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### コマンドサニタイザー破壊パターン(危険)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
urldecode(escapeshellarg($var)) ✗
|
|||
|
|
str_replace("'", "", escapeshellarg($var)) ✗
|
|||
|
|
stripslashes(escapeshellcmd($var)) ✗
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
$process = new Process(['cmd', $arg1, $arg2]); ✓
|
|||
|
|
exec(escapeshellcmd($cmd) . ' ' . escapeshellarg($a)); ✓
|
|||
|
|
exec('ls -la ' . escapeshellarg($path)); ✓
|
|||
|
|
exec('kill ' . intval($pid)); ✓
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## パストラバーサル 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── file_get_contents($userPath)
|
|||
|
|
├── fopen($userPath, 'r')
|
|||
|
|
├── unlink($userPath)
|
|||
|
|
├── move_uploaded_file($tmp, $userDest)
|
|||
|
|
└── response()->download($userPath)
|
|||
|
|
|
|||
|
|
重大度: MEDIUM
|
|||
|
|
├── Storage::get($userPath)
|
|||
|
|
└── Storage::put($userPath, $content)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### パスサニタイザー関数(これらは安全と判定)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
basename($path) ✓ ディレクトリ除去
|
|||
|
|
realpath($path) ✓ パス正規化
|
|||
|
|
pathinfo($path, PATHINFO_BASENAME) ✓ ファイル名抽出
|
|||
|
|
intval($id) ✓ 数値ID
|
|||
|
|
Str::random(40), Str::uuid() ✓ 安全なファイル名
|
|||
|
|
$file->hashName() ✓ ハッシュファイル名
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 危険なパストラバーサルパターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
.. ✗ ディレクトリトラバーサル
|
|||
|
|
../ ..\\ ✗ Unix/Windows
|
|||
|
|
%2e%2e ✗ URLエンコード
|
|||
|
|
%252e%252e ✗ ダブルエンコード
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### パスサニタイザー破壊パターン(危険)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
urldecode(basename($var)) ✗ %2e%2e -> ..
|
|||
|
|
base64_decode($var) ✗ トラバーサル隠蔽
|
|||
|
|
$file->getClientOriginalName() ✗ ユーザー制御
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
$safePath = basename($userInput); ✓
|
|||
|
|
$realPath = realpath($path); ✓
|
|||
|
|
if (str_starts_with($realPath, $allowedDir)) { ... } ✓
|
|||
|
|
'/docs/' . intval($id) . '.txt' ✓
|
|||
|
|
$request->file('doc')->hashName() ✓
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 認証 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── md5($password)
|
|||
|
|
├── sha1($password)
|
|||
|
|
├── $password = 'hardcoded'
|
|||
|
|
└── base64_encode($password)
|
|||
|
|
|
|||
|
|
重大度: MEDIUM
|
|||
|
|
├── $token == $userToken # タイミング攻撃
|
|||
|
|
└── password_hash($pw, ..., ['cost' => 4])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
password_hash($pw, PASSWORD_ARGON2ID); ✓
|
|||
|
|
password_verify($input, $hash); ✓
|
|||
|
|
hash_equals($expected, $actual); ✓
|
|||
|
|
Hash::make($password); ✓
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## CSRF/セッション 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: HIGH
|
|||
|
|
└── <form method="POST"> without @csrf
|
|||
|
|
|
|||
|
|
重大度: MEDIUM
|
|||
|
|
├── session_start() without secure options
|
|||
|
|
├── Cookie without httponly
|
|||
|
|
└── session_regenerate_id() without true
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全なパターン
|
|||
|
|
|
|||
|
|
```blade
|
|||
|
|
<form method="POST">
|
|||
|
|
@csrf ✓
|
|||
|
|
@method('PUT') ✓
|
|||
|
|
</form>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
session_start([
|
|||
|
|
'cookie_httponly' => true, ✓
|
|||
|
|
'cookie_secure' => true, ✓
|
|||
|
|
'cookie_samesite' => 'Strict', ✓
|
|||
|
|
]);
|
|||
|
|
session_regenerate_id(true); ✓
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 設定 検出パターン
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重大度: HIGH
|
|||
|
|
├── phpinfo()
|
|||
|
|
└── unserialize($data) without allowed_classes
|
|||
|
|
|
|||
|
|
重大度: MEDIUM
|
|||
|
|
├── dd($var), dump($var)
|
|||
|
|
├── var_dump($var), print_r($var)
|
|||
|
|
├── error_reporting(-1)
|
|||
|
|
├── ini_set('display_errors', '1')
|
|||
|
|
└── Log::info($password)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## CLI 使用方法
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 基本
|
|||
|
|
php bin/security-lint <path>
|
|||
|
|
|
|||
|
|
# オプション
|
|||
|
|
-f, --format 出力形式 (text|json|html|sarif|markdown)
|
|||
|
|
-s, --severity 最小重大度 (low|medium|high|critical)
|
|||
|
|
-o, --output ファイルに出力
|
|||
|
|
-l, --lang 言語 (ja|en)
|
|||
|
|
-e, --exclude 除外パターン
|
|||
|
|
-d, --recursive-depth 再帰深度 (default: 10)
|
|||
|
|
--verbose 詳細表示
|
|||
|
|
|
|||
|
|
# 例
|
|||
|
|
php bin/security-lint app/ -s high -f json -o report.json
|
|||
|
|
php bin/security-lint resources/views/ -l en
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 終了コード
|
|||
|
|
|
|||
|
|
| コード | 意味 |
|
|||
|
|
|--------|------|
|
|||
|
|
| 0 | 問題なし |
|
|||
|
|
| 1 | 中/低重大度の問題あり |
|
|||
|
|
| 2 | クリティカル/高重大度の問題あり |
|