1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Burp Suite入門:リクエスト改ざんで理解するSQLインジェクションの危険性

Posted at

はじめに

今回はWebアプリケーションのセキュリティ検証ツールであるBurp Suiteを使用して、SQLインジェクション脆弱性の検証方法と、その防御策の重要性について解説します。
そこまで深い話はしていませんが、本記事は防御目的です。悪用は厳禁です。

準備

1. Burp Suiteの導入

下記リンクからBurp Suiteをダウンロードする。
(メールアドレスの入力が必要です)
https://portswigger.net/burp/communitydownload

スクリーンショット 2025-10-21 001508.png

無償のCommunity Edition を選択します。
OSに関しては、ご自身の環境に合ったものを選択してください。
スクリーンショット 2025-10-20 021956.png

特に変更せず、Next連打でOK
スクリーンショット 2025-10-20 022045.png

スクリーンショット 2025-10-20 022100.png

スクリーンショット 2025-10-20 022105.png

Finishで完了
スクリーンショット 2025-10-20 022129.png

セットアップで指定したところにburpが入る。
スクリーンショット 2025-10-20 022308.png

起動するとこの画面になるので、Accept
スクリーンショット 2025-10-20 022323.png

Next
スクリーンショット 2025-10-20 022342.png

Start Burp
スクリーンショット 2025-10-20 022348.png

これで使えるようになりました。
スクリーンショット 2025-10-20 022409.png

2. テスト用サイトの準備

フロント: React
バックエンド: Express
DB: PostgreSQL
で構築
(参考: https://qiita.com/cho-tehu/items/371802e4c0bf0f7d29cc)

frontend/src/App.tsx
import VulnerableSearch from './components/VulnerableSearch'
import SafeSearch from './components/SafeSearch'
import VulnerablePullDown from './components/VulnerablePullDown'
import SafePullDown from './components/SafePullDown'


export default function App() {
  return (
    <div style={{ padding: 20 }}>
      <h1>Burp Test Frontend</h1>
      <section>
        <h2>SQLi (Item lookup)</h2>
        <VulnerablePullDown />
        <SafePullDown />
      </section>
    </div>
  )
}
frontend/src/components/VulnerablePullDown.tsx
import React, { useState } from 'react'
import axios from 'axios'

interface Item {
    id: number
    name: string
}

const VulnerablePullDown: React.FC = () => {
    const [query, setQuery] = useState<string>('1')
    const [result, setResult] = useState<Item[]>([])

    const search = async () => {
        try {
            // 脆弱:文字列連結で SQL に渡す(SQLi 再現用)
            const r = await axios.get<{ rows: any }>(
                `http://localhost:4000/vuln/item?id=${query}`,
                { withCredentials: true }
            )
            console.log(r.data.rows.rows)
            setResult(r.data.rows.rows)
        } catch (e) {
            console.error(e)
            setResult([])
        }
    }

    return (
        <div style={{ border: '1px solid #f99', padding: 8, marginBottom: 8 }}>
            <h3>Vulnerable PullDown (SQLi)</h3>
            <select value={query} onChange={e => setQuery(e.target.value)} style={{ marginRight: 8 }}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            <button onClick={search}>Search</button>
            <pre style={{ marginTop: 8 }}>{JSON.stringify(result, null, 2)}</pre>
        </div>
    )
}

export default VulnerablePullDown

frontend/src/components/SafePullDown.tsx
import React, { useState } from 'react'
import axios from 'axios'

interface Item {
    id: number
    name: string
}

const SafePullDown: React.FC = () => {
    const [query, setQuery] = useState<string>('1')
    const [result, setResult] = useState<Item[]>([])

    const search = async () => {
        try {
            // 安全:パラメータ化クエリをサーバ側で処理している前提
            const r = await axios.get<{ rows: Item[] }>(
                `http://localhost:4000/safe/item?id=${query}`,
                { withCredentials: true }
            )
            setResult(r.data.rows)
        } catch (e) {
            console.error(e)
            setResult([])
        }
    }

    return (
        <div style={{ border: '1px solid #9f9', padding: 8, marginBottom: 8 }}>
            <h3>Safe PullDown (param query)</h3>
            <select value={query} onChange={e => setQuery(e.target.value)} style={{ marginRight: 8 }}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            <button onClick={search}>Search</button>
            <pre style={{ marginTop: 8 }}>{JSON.stringify(result, null, 2)}</pre>
        </div>
    )
}

export default SafePullDown

backend/src/index.ts
import express, { type Request, type Response } from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import csrf from 'csurf';
import cors from 'cors';
import { Pool } from 'pg';

// -----------------------
// DB Pool(グローバルに 1 回だけ作成)
const db = new Pool({
    host: "db",
    port: 5432,
    user: "user",
    password: "pass",
    database: "appdb",
});

// -----------------------
// ヘルパー: クエリ実行
async function query(text: string, params: any[] | undefined) {
    const client = await db.connect();
    try {
        const res = await client.query(text, params);
        return res;
    } finally {
        client.release();
    }
}

// -----------------------
const app = express();
app.use(cors({ origin: 'http://localhost:5173', credentials: true }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieParser());

// CSRF 用
const csrfProtection = csrf({ cookie: true });

// -----------------------
// 脆弱:SQLi を示すエンドポイント
// GET /vuln/item?id=1
app.get('/vuln/item', async (req: Request, res: Response) => {
    const id = req.query.id || '';
    // 危険:直接文字列連結(SQLi の例)
    const sql = `SELECT * FROM items WHERE id = ${id}`;
    try {
        const r = await query(sql, undefined);
        console.log(r)
        res.json({ rows: r });
    } catch (err) {
        console.log(err)
        res.status(500).send(err instanceof Error ? err.message : String(err));
    }
});


// 安全:パラメータ化クエリ
// GET /safe/item?id=1
app.get('/safe/item', async (req: Request, res: Response) => {
    const id = req.query.id || '';
    try {
        const r = await query('SELECT * FROM items WHERE id = $1', [id]);
        res.json({ rows: r.rows });
    } catch (err) {
        console.log(err)
        res.status(500).send('internal error');
    }
});

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`backend listening on ${PORT}`);
});
db/init.sql
CREATE TABLE IF NOT EXISTS items (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
balance INTEGER NOT NULL DEFAULT 0
);

-- ------------------------
-- items テーブル
-- ------------------------
INSERT INTO items (id, name) VALUES (1, 'Item A') ON CONFLICT (id) DO NOTHING;
INSERT INTO items (id, name) VALUES (2, 'Item B') ON CONFLICT (id) DO NOTHING;
INSERT INTO items (id, name) VALUES (3, 'Item C') ON CONFLICT (id) DO NOTHING;

-- ------------------------
-- users テーブル
-- ------------------------
INSERT INTO users (id, username, balance) VALUES (1, 'alice', 1000) ON CONFLICT (id) DO NOTHING;
INSERT INTO users (id, username, balance) VALUES (2, 'bob', 500) ON CONFLICT (id) DO NOTHING;
INSERT INTO users (id, username, balance) VALUES (3, 'carol', 2000) ON CONFLICT (id) DO NOTHING;

-- 2. items
SELECT setval('items_id_seq', COALESCE((SELECT MAX(id) FROM items), 0) + 1, false);

-- 3. users
SELECT setval('users_id_seq', COALESCE((SELECT MAX(id) FROM users), 0) + 1, false);

テスト実施

Burp Suiteを開き、Proxy > Intercept > Open Browser
スクリーンショット 2025-10-21 004726.png

Open Browserを押すと、Chromium(簡単に言うとChromeのオープンソース版)が開く
アドレスバーに検証するサイトのアドレスを入力して、開く
image.png

Intercept on にする。
こうすることでリクエストをキャプチャして、送る前に止めることができる。
スクリーンショット 2025-10-21 005334.png

Chromiumで開いている検証対象サイトで開発者ツールのNetWorkを開く。
リクエスト(今回はVulnerable PullDown で検証)を送ると、pendingの状態で止まる。
image.png

Burp Suite側でリクエストを見つけ、クリックすると、リクエストを編集できる画面が開く。
スクリーンショット 2025-10-21 005732.png

下記のように書き換えてみる。
※ %20 はURLエンコーディングで半角スペースを表す

1;%20SELECT%20*%20FROM%20users

スクリーンショット 2025-10-21 010349.png

対象のリクエストを右クリック、Forwardを選択し、リクエストを送信する。
スクリーンショット 2025-10-21 011512.png

すると、Responseでユーザ一覧が取得できてしまう。
(サーバ側でSQL文字列を結合しているため、ユーザ入力がSQL構文として実行されてしまう)
image.png

安全な方も試してみる。
image.png

500エラーで終了
image.png

まとめ:防御のために必要なこと

上記の検証から、以下のことに注意する必要があることが分かります。

  • 自サイトのフロントからのみAPIが叩ける設定にしていても関係ない
  • ユーザ入力の値をクエリに組み込む際は、プルダウンなど入力可能な文字を制御している場合でも、必ずサニタイズを行う
  • DB操作結果をバックエンドから返す際は、必ず最小限の内容を返す(rowsのみを返す。型定義を行い、想定する型になっているかバックエンド側でチェックを行う等)

最後に

このようにBurp Suiteを使うとリクエストの改ざんが簡単にできます。
プルダウンや、フロントで入力チェックを行っている場合も余裕で改ざんできるので、安心できません。
自サイトのフロントからのみAPIが叩ける設定にしていても、かなり自由にリクエストを編集できてしまうので、注意が必要です。
professional版にすると自動で脆弱性スキャンができるそうなので、ぜひ活用してみてください。
私もそこまで詳しくないので、こういう使い方もあるよ等あれば教えていただきたいです。

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?