Zenn
🐶

SQLインジェクションおさらいする[セキュリティ対策]

2025/03/27に公開

背景

XSSの記事を書いた背景と同様です。

この前急に「XSSって何?」と聞かれた時に100%正しいと思える回答ができませんでした。
そこで主要なセキュリティ攻撃は言語化できるようにしようと思い、アウトプットすることにしました。

今回はSQLインジェクションについてアウトプットします。

SQLインジェクションとは

Webアプリの脆弱性を意図的に利用し、アプリケーションに悪意のあるSQL文を実行させる方法です。
例えば、脆弱性のあるWebアプリケーションのSQL文を外部から改竄するようなイメージです。
ORMなどを使わずに、生SQLを書いているとSQLインジェクションを受けやすく思います。

攻撃方法

例えば、以下のようなAPIの実装があるとする。

以下はNode.js(express)の実装です。

app.get('/user', async (req, res) => {
    const username = req.query.username;
    const query = `SELECT * FROM users WHERE username = '${username}'`;
    
    const result = await db.query(query);  // SQLクエリを直接実行
    res.json(result);
});

この実装には脆弱性があります。クライアントからリクエストされたクエリ usernameの値をそのままSQL文に入れています。

usernameに悪意のあるSQL文を入れ込めてしまいます。

具体的にどのようなSQL文を入れるかというと、以下です。

GET /user?username=' OR '1'='1

上記は、「' OR '1'='1」がusernameになります。
「SELECT * FROM users WHERE username = '${username}'」に繋げると以下になります。

SELECT * FROM users WHERE username = '' OR '1'='1';

1=1は常にtrueなので、全ユーザーのデータが取れてしまいます!

対策

エスケープ処理をする

記号など特別な意味を持つ記号を、エスケープします。
例えば、シングルクォート1つ(')をシングルクォート2つ('')にエスケープするなど。

プレースホルダーを使う

SQL文に、変数を入れたいですよね。
例えば、以下のような感じで。

SELECT * FROM users WHERE id = ${変数}

このとき、変数部分に悪意のあるユーザーがSQL文を埋め込むと、SQL文が改竄される恐れがあります。

そこでプレースホルダーを使います。
プレースホルダーを使うと、SQL文としてでなく値として扱ってくれます。

使い方としては、ORMのprismaなんかを使うと、以下のように書けます。
変数部分をプレースホルダーとして処理してくれます。

await prisma.$queryRaw`SELECT * FROM users WHERE id = ${変数}`;

ORMを使う

ORMを使うと、生SQL文を書かずに、ORMのライブラリが良しなに処理してくれるので、そこで大体SQLインジェクションの対策がされているようです。

ただ、前述にもありますが、ORMでも生SQLを書くことがあります。例えばprismaのqueryrawです。
prismaで生SQLを書く際の注意点は以下に書いてありました。
複雑なSQL文を書きたい時は生SQLを書きたかったりすると思うので、気をつけましょう。
https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#sql-injection-prevention

エラーメッセージに気をつける

DB・テーブル構造がわかるようなエラーメッセージをクライアントに出さないように気をつけます。
存在するテーブルを攻撃者が知ってしまうと、そのテーブルに対して操作するSQL文を書かれてしまいます。

その他にもいろいろありますがこれくらいで、、

まとめ

SQLインジェクションも1年前くらいに本で読んでなんとなく知っているという感じだったので、アウトプットできてよかったです。

今時で言うと、ORMを使ったDB操作をするようになってきましたが、ORMの中身もちゃんと見ておくと良いですね。

株式会社ソニックムーブ

Discussion

ログインするとコメントできます