はじめに
― 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 文を組み立てていないので、
UNIONやOR 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 の危険性を理解して設計しないと、
「攻撃者に裏管理画面をプレゼントするのと同じ」 状態になります。