1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】Exploiting Stored Procedures(ストアドプロシージャを悪用した SQL Injection)

1
Last updated at Posted at 2025-11-27

はじめに

― SQL が「関数化」されても安全とは限らない ―

多くの企業システム、レガシーな Web アプリケーション、
そしていまだ根強い「とりあえず安全そうだから SP 使おう」文化。

残念ながら── Stored Procedure(以下 SP)=安全 ではありません。

むしろ SP の設計次第では、
通常の SQL Injection より危険な状態が生まれる ことすらあります。

この記事では SP を悪用して SQL Injection を成立させる手法を、
攻撃者視点と防御指針の両方から徹底解説します。


1. そもそも Stored Procedure は何をするものか?

SP は簡単に言えば:

  • SQL 文の まとまり
  • サーバ側で実行される 関数
  • ユーザー入力を受け取り DB 内部で SQL を実行する

という“DB 内部のロジック”。

見た目の例(MySQL):

CREATE PROCEDURE getUser(IN uname VARCHAR(50))
BEGIN
    SET @sql = CONCAT('SELECT * FROM users WHERE username = ''', uname, ''';');
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
END;

Yes, これもはや爆弾。
uname' OR 1=1 -- だった場合?

完全に SQL Injection

正しい例(Dynamic SQL をやめて、パラメータをそのまま使う)

-- 安全な例:動的 SQL を使わない
DROP PROCEDURE IF EXISTS getUser;
DELIMITER $$

CREATE PROCEDURE getUser(IN uname VARCHAR(50))
BEGIN
    SELECT id, username, email
    FROM users
    WHERE username = uname;
END$$

DELIMITER ;
  • 文字列連結していない

  • ユーザ入力は SQL のパラメータ(変数) として扱われている
    → uname に何を突っ込まれても SQL 構造は変わらない ので安全。


2. Stored Procedure が危険になるパターンTOP3

パターン①:SP 内で動的 SQL(Dynamic SQL)を使用

以下が典型的な事故コード:

SET @query = CONCAT('SELECT * FROM products WHERE id = ', productId);
PREPARE stmt FROM @query;
EXECUTE stmt;

productId = 1 OR 1=1 のように入力された瞬間:

SELECT * FROM products WHERE id = 1 OR 1=1;

→ 全件取得。
→ 管理画面レベルのデータ漏洩が一撃。

❗最悪の誤解:「Stored Procedure を使えば SQL Injection は防げる」

現実は逆で、
動的 SQL(CONCAT × EXECUTE)=Injection 製造装置
です。


パターン②:SP が高権限(SUPER / DBA)で動く

Web アプリのユーザーは低権限でも、
SP だけは DEFINER='root' のような超権限で実行されるケースがあります。

すると…

  • テーブル作成
  • ユーザー管理
  • ファイル読み込み(LOAD_FILE()
  • ファイル書き込み(SELECT ... INTO OUTFILE
  • OS コマンド実行(MSSQL: xp_cmdshell

など、上位権限の機能まで攻撃者が触れる。

攻撃者視点では:

「SP に侵入できれば、アプリの制限を一気にバイパスできる“裏管理者入口”」
という認識です。


パターン③:複数の SQL 文を内部で連結

典型的にはこう:

SET @q = CONCAT(
  'UPDATE orders SET status="', status, '" WHERE id=', orderId, ';',
  'INSERT INTO logs(action) VALUES("status change");'
);

攻撃:

status = shipped"; DROP TABLE users; --

生成される SQL:

UPDATE orders SET status="shipped"; DROP TABLE users; --" WHERE id=10;

複数ステートメント注入による攻撃が完全成立

正しい書き方(動的 SQL をやめる)
-- 安全なストアドプロシージャ例(MySQL想定)
DELIMITER $$

CREATE PROCEDURE update_order_status(
    IN p_order_id INT,
    IN p_status   VARCHAR(50)
)
BEGIN
    -- 1. 注文のステータスを更新
    UPDATE orders
    SET status = p_status
    WHERE id = p_order_id;

    -- 2. ログテーブルに記録
    INSERT INTO logs(action)
    VALUES (CONCAT('status change to ', p_status));
END$$

DELIMITER ;

3. 実際の Exploiting の例(DB別)


3.1 MySQL の場合:Dynamic SQL + Prepared Statement が爆心地

脆弱 SP:

CREATE PROCEDURE searchPosts(IN keyword VARCHAR(100))
BEGIN
    SET @sql = CONCAT('SELECT * FROM posts WHERE title LIKE "%', keyword, '%"');
    PREPARE s FROM @sql;
    EXECUTE s;
END;

攻撃ペイロード:

keyword = %"; UNION SELECT username, password FROM users; --

生成された SQL:

SELECT * FROM posts WHERE title LIKE "%%";
UNION SELECT username, password FROM users;
--%"

→ クエリ乗っ取り完成。

正しい例(CONCAT は SQL 内で完結させる)
DROP PROCEDURE IF EXISTS searchPosts;
DELIMITER $$

CREATE PROCEDURE searchPosts(IN keyword VARCHAR(100))
BEGIN
    SELECT id, title, created_at
    FROM posts
    WHERE title LIKE CONCAT('%', keyword, '%');
END$$

DELIMITER ;
  • CONCAT('%', keyword, '%')SQL の式 であり、
    keyword 自体はパラメータとして使われている

  • 文字列として SQL 文を組み立てていないので、
    UNIONOR 1=1 を入れられても ただの検索文字列 になる


3.2 Microsoft SQL Server(MSSQL):xp_cmdshell が本気で危険

MSSQL の SP がひどいとこうなる:

DECLARE @cmd NVARCHAR(200)
SET @cmd = 'EXEC xp_cmdshell ''ping ' + @ip + ''''
EXEC(@cmd)

攻撃:

ip = 8.8.8.8 & net user pwned P@ss! /add

生成:

EXEC xp_cmdshell 'ping 8.8.8.8 & net user pwned P@ss! /add'

→ Windows ユーザー追加
→ RDP/SMB 乗っ取り
→ 企業システムが爆散


3.3 Oracle:SQL Injection from PL/SQL(#REAL)

Oracle の PL/SQL も同じ地雷を持つ。

脆弱 PL/SQL:

EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = ''' || v_name || '''';

攻撃:

v_name = '|| (SELECT password FROM admin WHERE rownum=1) ||'

関数内部で UNION が成立
→ Oracle でも普通に SQL Injection できる

正しい例: 動的 SQL を使わない(最も安全)

PL/SQL は本来、WHERE 句に パラメータバインドできる ので、
実は EXECUTE IMMEDIATE すら不要。

--  SAFE: 動的SQLを使わない
CREATE OR REPLACE PROCEDURE get_emp(p_name IN VARCHAR2)
AS
BEGIN
    FOR rec IN (
        SELECT empno, ename, job
        FROM emp
        WHERE ename = p_name
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.empno || ' ' || rec.ename);
    END LOOP;
END;
  • SQL 文が固定
  • パラメータは安全にバインドされる
  • 文字列連結なし → 攻撃者の入力で SQL が崩れない

動的 SQL が不要なら これが最強の安全策


4. SP が危険な理由(要点まとめ)

危険ポイント 説明
動的 SQL CONCAT + EXECUTE が injection の温床
高権限で動く アプリ権限を超えて DB レベルで攻撃可能
内部ロジックの複雑化 バリデーション抜け漏れが多発
ログ・管理系 SP 機密情報がセットで取れる
エラーメッセージが表示されやすい OSCP では爆発しがちなポイント

SP は「安全化のため」ではなく、
“危険を内部に押し込んだだけ” になることが非常に多い。


5. 防御:どうすれば安全な SP になるのか?

🚫 絶対にやってはいけないこと

  • Dynamic SQL をユーザー入力で組み立てる
  • EXECUTE, xp_cmdshell などを内部で呼び出す
  • SP に root / SUPER 権限を持たせる
  • SELECT/UPDATE を文字列結合で作る
  • エスケープ処理のみで防ごうとする

やるべきこと

① Prepared Statement を使う

動的 SQL が必要でも、パラメータ化可能な部分は必ずパラメータ化。

② SP の権限を最低限にする

下記は絶対に SP に付けてはいけない権限:

  • FILE
  • SUPER
  • PROCESS
  • SHUTDOWN
  • 全テーブルへの SELECT / INSERT
  • xp_cmdshell 使用権限(MSSQL)
③ 動的 SQL からの脱却

WHERE column = ? で済むなら SP に組み込む必要はない。

④ DBアカウントも分離
  • Web 用:SELECT / INSERT のみ
  • 管理画面用:追加権限
  • root:SP 作成のみ

まとめ

  • SP が安全というのは 大きな誤解
  • 特に Dynamic SQL は SQL Injection の大温床
  • 権限が高い SP ほど攻撃者が喜ぶ
  • SP を突破されると DB 内部のロジックを丸ごと乗っ取られる
  • 防御は「パラメータ化」+「権限最小化」+「動的 SQL 排除」

SP の危険性を理解して設計しないと、
「攻撃者に裏管理画面をプレゼントするのと同じ」 状態になります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?