mcpField
S
sast-sqli

sast-sqli

github.com/utkusenセキュリティ1.3k70 回インストール5 日前に更新
リポジトリ

sast-sqli について

sast-sqliは、コードベースのSQLインジェクションを検出する安全性評価スキルです。動的に構築されたSQLを特定し、入力元をバッチ単位で追跡して、結果をレポートにまとめます。

主な機能

  1. 01動的なSQL構築箇所の特定
  2. 02入力元の並列バッチ追跡
  3. 03検証結果レポートの統合
  4. 04事前のアーキテクチャ概要が必要

活用シーン

  1. 01生SQLの構築方法の確認
  2. 02ユーザー入力がSQLに届くかの調査
  3. 03動的なソート項目の検証確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/utkusen/sast-skills --skill sast-sqli

インストール後、Claude Code で/sast-sqliを入力して読み込まれたか確認します。

詳細

SQL インジェクション(SQLi)の検出

コードベース内の SQL インジェクションの脆弱性を見つけるため、対象を絞ったセキュリティ評価を実施します。このスキルでは、サブエージェントを使った 3 段階のアプローチを採用します。偵察(脆弱な SQL 構築箇所の特定)、バッチ検証(3 件ずつ並行してテイント分析)、統合(バッチごとのレポートを 1 つのファイルに統合)です。

前提条件:sast/architecture.md が存在している必要があります。存在しない場合は、最初に分析スキルを実行してください。


SQL インジェクションとは

SQL インジェクションは、ユーザーが入力した値を、パラメーター化されたバインドではなく、文字列の連結や埋め込みによって SQL クエリに組み込むことで発生します。これにより、攻撃者はクエリのロジックを変更したり、認証を回避したり、機密データを抽出したり、レコードを変更または削除したりできます。また、構成によっては OS コマンドを実行できる場合もあります。

基本的なパターン:検証もパラメーター化もされていないユーザー入力が、SQL クエリの実行呼び出しに到達する。

SQLi に該当するもの
  • ユーザー入力を SQL 文字列に直接連結する:"SELECT * FROM users WHERE name = '" + username + "'"
  • 文字列のフォーマット機能を使ってクエリを構築する:f"SELECT * FROM orders WHERE id = {order_id}"
  • ユーザー入力を使って動的に ORDER BY/GROUP BY/テーブル名/列名を指定し、許可リストによる検証を行わない
  • サニタイズされていない入力を使う ORM の生クエリメソッド:User.objects.raw(f"SELECT * WHERE id={id}")、$queryRawUnsafe(input)
  • 二次 SQL インジェクション:入力がデータベースに保存され、その後、再サニタイズされずに生クエリで使用される
SQLi に該当しないもの

以下を SQLi として報告しないでください。

  • IDOR:別のユーザーのデータにアクセスするために ?id=1 を ?id=2 に変更すること。これは不適切な直接オブジェクト参照であり、別の脆弱性クラスです
  • 一括代入:ユーザー入力から ORM モデルの追加フィールドを設定すること。これは別の脆弱性です
  • データベース経由の XSS:<script> タグをデータベースに保存し、後でエスケープせずに表示すること。これは XSS であり、SQLi ではありません
  • NoSQL インジェクション:MongoDB の演算子に値を注入すること。類似の概念ですが、別の脆弱性クラスです
  • 安全な ORM クエリ:User.objects.filter(id=user_id) や User.find(params[:id]) のようなパラメーター化された ORM 検索。これらは報告しないでください
SQLi を防ぐパターン

以下のパターンが見つかった場合、そのコードは脆弱ではない可能性が高いです。

1. パラメーター化クエリ/プリペアドステートメント(最も一般的な修正方法)

# Python — cursor.execute with tuple binding
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# Node.js — mysql2 / pg placeholder binding
db.query("SELECT * FROM users WHERE id = ?", [userId])
pool.query("SELECT * FROM users WHERE id = $1", [userId])

# Java — PreparedStatement
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);

# Go — database/sql placeholder
db.QueryRow("SELECT * FROM users WHERE id = $1", userID)

# PHP — PDO with named params
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $userId]);

# C# — SqlCommand with parameters
cmd.CommandText = "SELECT * FROM users WHERE id = @id";
cmd.Parameters.AddWithValue("@id", userId);

2. ORM クエリビルダー(デフォルトで安全)

# Django ORM
User.objects.filter(id=user_id)

# ActiveRecord (Rails)
User.find(params[:id])
User.where(name: params[:name])

# Prisma (tagged template literal form of $queryRaw)
await prisma.$queryRaw`SELECT * FROM users WHERE id = ${userId}`

# Laravel Eloquent (non-raw)
User::find($id)

3. 動的な識別子に対する許可リスト検証

# Dynamic ORDER BY — validate column name against a hardcoded set before interpolating
ALLOWED_COLUMNS = {'name', 'created_at', 'price'}
if sort_col not in ALLOWED_COLUMNS:
    raise ValueError("Invalid column")
query = f"SELECT * FROM products ORDER BY {sort_col}"  # safe only after allowlist check

脆弱な例と安全な例

Python — Django(生 SQL)
# VULNERABLE: f-string interpolation in raw()
def search_users(request):
    username = request.GET.get('username')
    users = User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")
    return JsonResponse(list(users.values()), safe=False)

# SECURE: parameterized raw()
def search_users(request):
    username = request.GET.get('username')
    users = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])
    return JsonResponse(list(users.values()), safe=False)
Python — Flask/SQLAlchemy
# VULNERABLE: f-string into text()
@app.route('/search')
def search():
    name = request.args.get('name')
    result = db.session.execute(text(f"SELECT * FROM products WHERE name = '{name}'"))
    return jsonify(result.fetchall())

# SECURE: named bound parameter
@app.route('/search')
def search():
    name = request.args.get('name')
    result = db.session.execute(
        text("SELECT * FROM products WHERE name = :name"), {"name": name}
    )
    return jsonify(result.fetchall())
Python — sqlite3/psycopg2
# VULNERABLE
def get_user(username):
    cursor.execute("SELECT * FROM users WHERE username = '" + username + "'")
    return cursor.fetchone()

# SECURE
def get_user(username):
    cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
    return cursor.fetchone()
Node.js — mysql2
// VULNERABLE: template literal in query string
app.get('/user', async (req, res) => {
  const { id } = req.query;
  const [rows] = await db.query(`SELECT * FROM users WHERE id = ${id}`);
  res.json(rows);
});

// SECURE: placeholder binding
app.get('/user', async (req, res) => {
  const { id } = req.query;
  const [rows] = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  res.json(rows);
});
Node.js — pg(PostgreSQL)
// VULNERABLE
app.get('/orders', async (req, res) => {
  const status = req.query.status;
  const result = await pool.query(`SELECT * FROM orders WHERE status = '${status}'`);
  res.json(result.rows);
});

// SECURE
app.get('/orders', async (req, res) => {
  const status = req.query.status;
  const result = await pool.query('SELECT * FROM orders WHERE status = $1', [status]);
  res.json(result.rows);
});
Ruby on Rails
# VULNERABLE: string interpolation in where()
def search
  @users = User.where("name = '#{params[:name]}'")
end

# VULNERABLE: find_by_sql with interpolation
def find_user
  @user = User.find_by_sql("SELECT * FROM users WHERE email = '#{params[:email]}'")
end

# SECURE: parameterized where()
def search
  @users = User.where("name = ?", params[:name])
  # or using hash form: User.where(name: params[:name])
end
Java — Spring JDBC
// VULNERABLE: string concatenation
public User findUser(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(sql, userRowMapper);
}

// SECURE: parameterized query
public User findUser(String username) {
    return jdbcTemplate.queryForObject(
        "SELECT * FROM users WHERE username = ?", userRowMapper, username
    );
}
Go — database/sql
// VULNERABLE: fmt.Sprintf to build query
func GetUserByName(name string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)
    row := db.QueryRow(query)
    // ...
}

// SECURE: parameterized query
func GetUserByName(name string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE name = $1", name)
    // ...
}
PHP — PDO
// VULNERABLE: string concatenation
function getUser($id) {
    $stmt = $pdo->query("SELECT * FROM users WHERE id = " . $id);
    return $stmt->fetch();
}

// SECURE: prepared statement
function getUser($id) {
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
    $stmt->execute(['id' => $id]);
    return $stmt->fetch();
}
C# — ADO.NET
// VULNERABLE: string concatenation
public User GetUser(string username) {
    using var cmd = new SqlCommand(
        "SELECT * FROM Users WHERE Username = '" + username + "'", conn);
    return ReadUser(cmd.ExecuteReader());
}

// SECURE: parameterized command
public User GetUser(string username) {
    using var cmd = new SqlCommand(
        "SELECT * FROM Users WHERE Username = @username", conn);
    cmd.Parameters.AddWithValue("@username", username);
    return ReadUser(cmd.ExecuteReader());
}
動的な ORDER BY/列名(すべてのスタック)
# VULNERABLE: unsanitized user input as column name (parameterization can't help here)
sort_col = request.args.get('sort', 'name')
cursor.execute(f"SELECT * FROM products ORDER BY {sort_col}")

# SECURE: allowlist validation before interpolation
ALLOWED_SORT_COLS = {'name', 'price', 'created_at'}
sort_col = request.args.get('sort', 'name')
if sort_col not in ALLOWED_SORT_COLS:
    return abort(400)
cursor.execute(f"SELECT * FROM products ORDER BY {sort_col}")

実行

このスキルは、サブエージェントを使って 3 段階で実行されます。sast/architecture.md の内容を、すべてのサブエージェントにコンテキストとして渡してください。

フェーズ 1:偵察 — 脆弱な SQL 構築箇所の特定

以下の指示をサブエージェントに渡して起動してください。

目的:コードベース内で、文字列連結、補間、または任意の変数を使ったフォーマットによって脆弱な方法で SQL クエリが構築されている箇所をすべて見つけてください(変数の出所は問いません)。結果を sast/sqli-recon.md に書き込んでください。

背景:プロジェクトのアーキテクチャ概要が提供されます。これを使って、技術スタック、データベース層、ORM のパターン、クエリの実行方法を把握してください。

検索対象 — 脆弱なクエリ構築パターン:

クエリ文字列が、プレースホルダー付きの静的文字列ではなく動的に組み立てられている SQL クエリ実行呼び出しを探してください。クエリに埋め込まれた動的変数はすべて検出してください。変数がユーザー制御可能かどうかはまだ追跡しません。それはフェーズ 2 の作業です。

  1. SQL 実行呼び出しでの文字列連結:
    • cursor.execute("SELECT ... WHERE id = " + var)
    • $pdo->query("SELECT * FROM users WHERE id = " . $var)
    • jdbcTemplate.query("SELECT * WHERE username = '" + var + "'")
  2. クエリ引数として使われる f-string/テンプレートリテラル:
    • cursor.execute(f"SELECT * WHERE name = '{var}'")
    • db.query(`SELECT * WHERE id = ${var}`)
    • db.QueryRow(fmt.Sprintf("SELECT * WHERE id = '%s'", var))
  3. クエリの構築に使われる文字列フォーマット関数:
    • cursor.execute("SELECT * WHERE id = %s" % var)(注意:パラメーターのバインドではなく、% によるフォーマットです)
    • cursor.execute("SELECT * WHERE id = {}".format(var))
    • String.format("SELECT * WHERE id = '%s'", var)(Java)
    • sprintf("SELECT * WHERE id = %s", $var)(PHP)
  4. 動的に構築された文字列を引数に取る ORM の raw/unsafe メソッド(バインドパラメーター付きの静的テンプレートは除く):
    • Django:Model.objects.raw(f"...")、RawSQL(f"...")、extra(where=[f"..."])
    • ActiveRecord:where("col = '#{var}'")(文字列引数内での Ruby の補間)
    • Sequelize:sequelize.query(`...${var}...`)、literal(var)
    • TypeORM:createQueryBuilder().where(`col = '${var}'`)、.query("..." + var)
    • Prisma:$queryRawUnsafe(...)、$executeRawUnsafe(...)
    • Entity Framework:FromSqlRaw("..." + var)、ExecuteSqlRaw("..." + var)
  5. 動的な識別子 — クエリ文字列内で、列名、テーブル名、ORDER BY/GROUP BY の値として使われる変数。パラメーター化では識別子を保護できません。保護できるのは許可リストによる検証のみです。
    • f"SELECT * FROM {table_var}"
    • `SELECT * FROM ${tableVar}`
    • f"SELECT * ORDER BY {sort_col}"

対象外(これらは安全な構築パターンです。検出しないでください):

  • 動的な部分を含まない静的クエリ文字列:cursor.execute("SELECT * FROM users WHERE id = %s", (val,))
  • ORM の安全なクエリビルダーメソッド:.filter()、.where(col: val)、.findOne()、.findUnique()、タグ付きテンプレートリテラルを使った prisma.$queryRaw
  • クエリ文字列自体が静的で、値が別の引数リストで渡される、適切にパラメーター化された raw クエリ:execute("SELECT * WHERE id = %s", (val,))、query("SELECT * WHERE id = ?", [val])

出力形式 — sast/sqli-recon.md に書き込んでください:

# SQLi Recon: [Project Name]

## Summary
脆弱な方法で SQL クエリが構築されている箇所が [N] 件見つかりました。

## Vulnerable Construction Sites

### 1. [説明的な名前 — 例:「get_user クエリ内の文字列連結」]
- **File**: `path/to/file.ext`(行 X-Y)
- **Function / endpoint**: [関数名またはルート]
- **Query execution method**: [cursor.execute / db.query / raw / など]
- **Construction pattern**: [文字列連結 / f-string / テンプレートリテラル / % format / .format() / fmt.Sprintf / ORM raw]
- **Interpolated variable(s)**: `var_name` — [何を表しているように見えるかの簡単な説明。例:「ソート列と思われる」または「出所不明」]
- **Code snippet**:

[脆弱なクエリ構築と実行呼び出し]


[各箇所について繰り返す]
フェーズ 1 の後:次に進む前に該当箇所があるか確認する

フェーズ 1 が完了したら、sast/sqli-recon.md を読みます。調査で脆弱な構築箇所が 0 件見つかった場合(概要に「Found 0」と記載されている場合、または「Vulnerable Construction Sites」セクションが空か存在しない場合)は、フェーズ 2 を完全にスキップします。代わりに、以下の内容を sast/sqli-results.md に書き込み、終了します。

# SQLi Analysis Results

No vulnerabilities found.

フェーズ 2 に進むのは、フェーズ 1 で脆弱な構築箇所が 1 件以上見つかった場合のみです。

フェーズ 2:検証 — 汚染解析(バッチ処理)

フェーズ 1 が完了したら、sast/sqli-recon.md を読み、構築箇所を各最大 3 件のバッチに分割します。バッチごとに 1 つのサブエージェントを並行して起動します。各サブエージェントは、割り当てられた箇所のみについてユーザー入力を追跡し、その結果を専用のバッチファイルに書き込みます。

バッチ分割の手順(サブエージェントではなく、オーケストレーターが行います):

  1. sast/sqli-recon.md を読み、「Vulnerable Construction Sites」配下の番号付き箇所セクション(### 1.、### 2. など)の数を数えます。
  2. 最大 3 件ずつのバッチに分割します。例:8 件の場合は 3 つのバッチ(1~3、4~6、7~8)に分けます。
  3. 各バッチについて、該当する箇所セクションの全文を調査ファイルから抽出します。
  4. 各バッチのサブエージェントをすべて並行して起動し、それぞれに割り当てられた箇所のみを渡します。
  5. 各サブエージェントは sast/sqli-batch-N.md(N は 1 から始まるバッチ番号)に書き込みます。
  6. sast/architecture.md からプロジェクトの主要な言語/フレームワークを特定し、「Vulnerable vs. Secure Examples」セクションから該当する例のみを選びます。たとえば、プロジェクトで Node.js と pg を使用している場合は、「Node.js — pg (PostgreSQL)」と関連する Node の例を含めます。以下の [TECH-STACK EXAMPLES] で示す箇所に、選択した例を各サブエージェントへの指示に含めます。

各バッチのサブエージェントには、次の指示を与えます(バッチごとの値に置き換えてください):

目的:割り当てられた脆弱な SQL 構築箇所ごとに、ユーザーが指定した値が補間される変数に到達するかどうかを判断します。SQL インジェクションの脆弱性を見つけることが目的です。結果を sast/sqli-batch-[N].md に記録してください。

割り当てられた構築箇所(調査フェーズから):

[割り当てられた箇所のセクション全文を、元の番号を保ったまま貼り付けてください]

コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使って、リクエストのエントリーポイント、ミドルウェア、アプリケーション内のデータの流れを把握してください。

SQLi の参考情報 — 補間される変数をその発生源まで遡って追跡してください:

  1. 直接的なユーザー入力 — 変換されずに、リクエスト元から変数に直接代入されています:
    • HTTP クエリパラメーター:request.GET.get(...)、req.query.x、params[:x]、$_GET['x']、c.Query("x")
    • パスパラメーター:request.path_params['id']、req.params.id、params[:id]
    • リクエスト本文/フォームフィールド:request.POST.get(...)、req.body.x、params[:x]、$_POST['x']
    • HTTP ヘッダー:request.headers.get(...)、req.headers['x']
    • Cookie:request.COOKIES.get(...)、req.cookies.x
  2. 間接的なユーザー入力 — 変換、関数呼び出し、中間代入を経て、ユーザー入力から変数が導出されています。経路全体を追跡してください:
    • 関数の戻り値から変数に代入されている → その関数の引数の発生元を確認する
    • 変数が関数の引数として渡されている → 呼び出し箇所を確認する
    • クラス属性または共有状態から変数が読み込まれている → それを設定する箇所を探す
    • 条件によって変数が代入されている → すべての分岐を確認する
  3. 二次入力 — 変数はデータベースから読み込まれますが、保存された値の元はユーザー入力です:
    • この値をデータベースに書き込んだ箇所を探す — ユーザーが指定したフィールドの値が保存されていましたか?
    • 書き込み時にサニタイズまたはパラメーター化されていましたか?
  4. サーバー側/ハードコードされた値 — 変数の値が設定、環境変数、ハードコードされた定数、またはユーザーの影響を受けないサーバー側のロジックに由来する場合、この箇所は悪用できません。

緩和策(ユーザー入力が変数に到達する可能性がある場合も確認してください):

  • 使用前の許可リストによる検証(特に動的な識別子 — 列名/テーブル名、ORDER BY)
  • コンテキスト内で値を確実に制約する型変換(例:数値のみの SQL フラグメントにおける int(val))
  • 独自のエスケープ処理(mysql_real_escape_string、addslashes、自作のサニタイザー)はパラメーター化と同等ではありません — 汚染がある場合は引き続き「脆弱性の可能性あり」と分類してください

このプロジェクトの技術スタックにおける脆弱/安全な例:

[TECH-STACK EXAMPLES]

分類:

  • 脆弱:有効な緩和策がないまま、ユーザー入力が補間される変数に到達することが明確に確認できる。
  • 脆弱性の可能性あり:ユーザー入力が変数に到達する可能性が高い(間接的なデータフロー)、または脆弱な緩和策(独自のエスケープ処理)のみが使われている。
  • 脆弱ではない:変数がサーバー側の値のみである、または有効なパラメーター化/許可リスト検証が行われている。
  • 手動レビューが必要:変数の発生元を確実に特定できない(不透明なヘルパー、複雑なデータフロー、外部ライブラリ)。

出力形式 — sast/sqli-batch-[N].md に記録してください:

# SQLi Batch [N] の結果

## 検出結果

### [VULNERABLE] 説明的な名前
- **ファイル**:`path/to/file.ext`(行 X-Y)
- **エンドポイント/関数**:[ルートまたは関数名]
- **問題**:[例:「HTTP クエリパラメーター `username` が f-string の SELECT クエリに直接流れ込む」]
- **汚染経路**:[エントリーポイントから構築箇所までの手順]
- **影響**:[攻撃者が実行できること — レコードの抽出、認証の回避、データの削除など]
- **修正方法**:[パラメーター化クエリ、ORM の同等機能、または識別子の許可リスト]
- **動的テスト**:

[sqlmap コマンドまたは手動の curl ペイロード。パラメーター、ペイロード、期待される応答の兆候を示してください。 例:sqlmap -u "https://app.example.com/search?q=test" -p q --dbs]


### [LIKELY VULNERABLE] 説明的な名前
- **ファイル**:`path/to/file.ext`(行 X-Y)
- **エンドポイント/関数**:[ルートまたは関数名]
- **問題**:[例:「間接的なデータフロー、または独自のエスケープ処理のみ」]
- **汚染経路**:[可能な範囲で追跡し、不確かな手順には印を付けてください]
- **懸念事項**:[リスクが残る理由]
- **修正方法**:[パラメーター化クエリに置き換える]
- **動的テスト**:

[回避を試みるペイロード]


### [NOT VULNERABLE] 説明的な名前
- **ファイル**:`path/to/file.ext`(行 X-Y)
- **エンドポイント/関数**:[ルートまたは関数名]
- **理由**:[例:「サーバー側の定数」または「許可リストでソート列を制限」]

### [NEEDS MANUAL REVIEW] 説明的な名前
- **ファイル**:`path/to/file.ext`(行 X-Y)
- **エンドポイント/関数**:[ルートまたは関数名]
- **不確実性**:[発生元を特定できなかった理由]
- **提案**:[手動で追跡する内容]
フェーズ 3:マージ — バッチ結果の統合

フェーズ 2 のすべてのバッチサブエージェントが完了したら、すべての sast/sqli-batch-*.md ファイルを読み込み、内容を統合して 1 つの sast/sqli-results.md にまとめます。オーケストレーターであるあなたが直接実行してください。サブエージェントは必要ありません。

マージ手順:

  1. sast/sqli-batch-1.md、sast/sqli-batch-2.md、... の各ファイルを読み込みます。
  2. 各バッチファイルのすべての検出結果を集め、元の分類とすべての詳細フィールドを保ったまま、1 つのリストにまとめます。
  3. エグゼクティブサマリー用に、全バッチの合計数を集計します(解析した構築箇所の数 = バッチに割り当てられた調査結果の合計箇所数、つまり各バッチの箇所数の合計です)。
  4. 次の形式で、統合レポートを sast/sqli-results.md に書き込みます。
# SQLi Analysis Results: [Project Name]

## Executive Summary
- Construction sites analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]

## Findings

[All findings from all batches, grouped by classification:
 VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
 Preserve every field from the batch results exactly as written.]
  1. sast/sqli-results.md の書き込み後、すべての中間バッチファイル(sast/sqli-batch-*.md)を削除します。

重要な注意事項

  • sast/architecture.md を読み込み、その内容をコンテキストとしてすべてのサブエージェントに渡してください。
  • フェーズ 2 はフェーズ 1 の完了後に実行する必要があります。フェーズ 2 は調査結果に依存します。
  • フェーズ 3 はフェーズ 2 のすべてのバッチの完了後に実行する必要があります。フェーズ 3 はすべてのバッチの出力に依存します。
  • バッチサイズはサブエージェントあたり構築箇所 3 つです。箇所が合計 1~3 個の場合は、サブエージェントを 1 つ使用します。10 個の場合は、サブエージェントを 4 つ使用します(3+3+3+1)。
  • すべてのバッチサブエージェントを並行して起動してください。順番に実行してはいけません。
  • 各バッチサブエージェントには、調査ファイル全体ではなく、割り当てられた箇所のテキストのみを渡してください。これにより、各サブエージェントのコンテキストを小さく、焦点を絞ったものにできます。
  • フェーズ 1 は純粋に構造的な分析です。SQL クエリ文字列に埋め込まれた動的変数は、その出所にかかわらずすべて指摘してください。フェーズ 1 ではユーザー入力を追跡しないでください。それはフェーズ 2 の役割です。
  • フェーズ 2 は純粋な汚染分析です。割り当てられた箇所ごとに、補間された変数をその起点まで追跡します。ユーザーが制御するソースに由来する場合、その箇所は実際の脆弱性です。
  • 生 SQL と ORM の raw/unsafe メソッドに焦点を当ててください。標準的な ORM クエリビルダーの呼び出し(.filter()、.where(col: val)、.find())はデフォルトで安全です。指摘しないでください。
  • 判断に迷う場合は、「脆弱性なし」ではなく「手動レビューが必要」に分類してください。セキュリティ評価では、偽陰性は偽陽性よりも深刻です。
  • 汚染は間接的に伝播することがあります。リクエストパラメーターがミドルウェアで抽出され、共有オブジェクトに保存され、複数のヘルパー関数を経由して、最終的にクエリ構築に到達する場合があります。経路全体を追跡してください。
  • カスタムのエスケープ処理(mysql_real_escape_string、addslashes、独自のサニタイザーを含む)はパラメーター化と同等ではありません。エスケープ処理が行われていても、「脆弱性の可能性が高い」として指摘してください。
  • 動的な識別子(列名/テーブル名)には、パラメーター化では対処できません。安全な修正方法は、許可リストによる検証のみです。ユーザーが制御しているかどうかにかかわらず、許可リストがない動的な識別子はすべて指摘してください。
  • 二次インジェクションは見落としやすいものです。ユーザー入力に由来し、DB に保存された値が、後にコードベース内の別の場所で読み込まれ、生クエリで安全でない方法で使われることがあります。フェーズ 2 では、DB から読み込まれた値も汚染されている可能性があるものとして扱い、どこで書き込まれたかまで追跡してください。
  • 中間ファイルをクリーンアップしてください。最終版の sast/sqli-results.md の書き込み後、sast/sqli-recon.md とすべての sast/sqli-batch-*.md ファイルを削除してください。

コメント