はじめに
2024年7月号のSoftware Designを読み、セキュリティの対策と強化のコーナーがありましたので、第4章の「CTFに挑戦」の例をそのまま上げさせて頂きます。
最初に掲載されていたのは、初歩的な例でクエリパラメータを使用したGETリクエストの例でした。
フレームワークを使用すると配慮されているためあまり気にしない箇所ではありますが、今回の掲載例にて試してみました。
第4章の「CTFに挑戦」の例
urlからの情報を直接SQLクエリに埋め込んでいる例を取り上げました。
const express = require("express");
const { conn } = require("./connect");
const app = express();
app.set("view engine", "ejs");
app.get("/", (req, res) => {
res.redirect("/shopping");
});
app.get("/shopping", (req, res) => {
const q = req.query.q ?? "";
let query = "SELECT category,product,price FROM clothes ";
if (q !== "") {
query += `WHERE category = '${q}' AND sale = 0;`;
} else {
query += "WHERE sale = 0;";
}
conn.query(query, (e, results) => {
if (e) return res.send(e);
return res.render("shopping.ejs", { items: results });
});
});
app.listen(3000);
ユーザーからの入力を直接SQLクエリに埋め込んでいます。
このアプローチでは、ユーザー入力が正しくエスケープされず、SQLインジェクション攻撃のリスクが高くなります。また、入力にシングルクォートなどの特殊文字が含まれていると、SQL構文エラーが発生します。
問題ない例
入力にミスがなければ特に問題はありません。
http://localhost:8081/?q=ワンピース
脆弱性の例
urlで間違ったり、意図してsql文を入力した場合、SQLインジェクションされます。
間違ったsqlの場合表示されてしまいます。
1.http://localhost:8081/shopping?q=test'
{
"code": "ER_PARSE_ERROR",
"errno": 1064,
"sqlMessage": "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''test'' AND sale = 0' at line 1",
"sqlState": "42000",
"index": 0,
"sql": "SELECT category,product,price FROM clothes WHERE category = 'test'' AND sale = 0;"
}
2.http://localhost:8081/shopping?q=ワンピース' ORDER BY 4--%20
カラム数がわかってしまいます
{
"code": "ER_BAD_FIELD_ERROR",
"errno": 1054,
"sqlMessage": "Unknown column '4' in 'order clause'",
"sqlState": "42S22",
"index": 0,
"sql": "SELECT category,product,price FROM clothes WHERE category = 'ワンピース' ORDER BY 4-- ' AND sale = 0;"
}
SELECT category,product,price FROM clothes WHERE category = 'ワンピース' ORDER BY 4-- ' AND sale = 0;
4つ目のカラムが存在しないというエラーが出ています。
500系のエラーを発生する対策が必要です。
3 . http://localhost:8081/shopping?q=' UNION SELECT 'a' 'b',null,null;--%20
MySQLだと判明してしまいます。
SELECT category,product,price FROM clothes WHERE category = '' UNION SELECT 'a' 'b',null,null;-- ' AND sale = 0
MySQLでのSQLインジェクションの詳細
上記SQLの説明を記載しておきます。
元のクエリ:
SELECT category, product, price FROM clothes WHERE category = '' AND sale = 0
このクエリは、clothes
テーブルからcategory
が空文字列で、かつsale
が0
であるレコードを選択するものです。
SQLインジェクションペイロード:
まずSQLインジェクションペイロードとは、攻撃者がデータベースに対するSQLクエリの構造を意図的に変更するために使用する悪意のある入力のことを指します。
このペイロードを使用することで、攻撃者はデータベースに不正なコマンドを実行させることができます。
' UNION SELECT 'a' 'b',null,null;--
これを元のクエリに挿入すると、以下のようなクエリになります:
SELECT category, product, price FROM clothes WHERE category = '' UNION SELECT 'a' 'b',null,null;-- ' AND sale = 0
説明:
-
category = ''
:これは元のクエリの条件です。 -
UNION SELECT 'a' 'b',null,null;--
:UNION
キーワードにより、前のクエリの結果にこの新しいクエリの結果を追加します。'a b'
および2つのnull
を選択します。
--以下はコメント化されます。
SELECT category, product, price FROM clothes WHERE category = ''
UNION
SELECT 'a b',null, null;-- ' AND sale = 0
category | product | price
---------|---------|------
| T-shirt | 1000
| Jeans | 2000
a b | null | null
-
--
:これはコメントアウトを示し、これ以降の部分(AND sale = 0
)を無効にします。
これにより、クエリの結果には以下のデータが含まれます:
- 元のクエリに一致するレコード(
category = ''
かつsale = 0
) - UNIONによって追加された
'a'
、'b'
、およびnull
の行
この結果、結果セットには画像のように'a'
と'b'
が表示されます。
MySQLに特有のポイント:
- MySQLは、
UNION
による結果の結合をサポートしています。 - MySQLのコメントアウトは
--
で開始し、後続のすべてのテキストを無効にします。 - MySQLでは、
null
は有効な値として使用されます。
対策例
以下では、プレースホルダーを使用してユーザー入力を安全にSQLクエリにバインドしています。
要は必ずプレースホルダ、バインドが必要だということです。
const express = require("express");
const { conn } = require("./connect");
const app = express();
app.set("view engine", "ejs");
app.get("/", (req, res) => {
res.redirect("/shopping");
});
app.get("/shopping", (req, res) => {
const q = req.query.q ?? "";
let query = "SELECT category, product, price FROM clothes WHERE sale = 0";
let queryParams = [];
if (q !== "") {
query += " AND category = ?";
queryParams.push(q);
}
// SQLクエリとパラメータをログに出力
console.log("Executing SQL query:", query);
console.log("With parameters:", queryParams);
conn.query(query, queryParams, (e, results) => {
if (e) {
console.error('SQLエラー:', e); // エラーログをコンソールに出力
return res.send(e);
}
return res.render("shopping.ejs", { items: results });
});
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
おわりに
脆弱性の例として、Web開発で最初に学習することだと思いますが、油断してはいけない例として記載しました。
参考資料
ソフトウェアデザイン 2024年7月号 第4章