ぶるーたるごぶりん

UI, UX, セキュリティとか😘

SQL Injection を探せ! 文字列テンプレートリテラル と Raw Query によるやらかし探し

はじめに

失敗記事です(SQLI 一個も見つかりませんでした)。

前回の記事では GitHub に漏れ出たコードを GitHub Code Search を使って検索しました。

brutalgoblin.hatenablog.jp

今回は少し特殊な SQL Injection を前回同様に GitHub Code Search の力を借りて探そうと思います。
特殊な SQLI とは、一般的な文字列結合ではなく、 文字列テンプレートリテラルを使った結合による SQLI です(後述)

失敗記事ではあるのですが、観点的には結構面白いと思うので、軽くまとめてみます。

また、今回は ORM に限定して探しましたが、限定しなければ結構見つかるのかもなーと思ってます。

見つけようとしたもの

ご存知の通り、SQL Injection の一番良くあるミスは、 文字列結合した SQL を発行し、そのまま Injection が成立してしまうというものです。

例えば以下のようなものです。

conn.raw_query("SELECT * FROM users WHERE id = " + id);

このような初歩的な脆弱性は、人類史の中で何十回、何万回と世界中で見つかってきてきました。 そのため、いくつかのセキュリティ SAST (静的コード解析)ツールなどでは、これらのコードを検出する機能が備わっていることがあります。

例えば Java / Scala のセキュリティ SAST ツールである Find Security Bugs には、 いくつかの文字列結合が原因となる SQLI の検知ルールがあります。

find-sec-bugs.github.io

ただ、これらの検知ルールを見ていた時にふと疑問が湧いてきました。

疑問というのは以下です。

  • 文字列テンプレートリテラル を使った SQLI のケースは SAST で検知されるのだろうか?
  • エンジニアはこれまでの経験の中から、シンプルな文字列結合には過敏に反応するが、 文字列テンプレートリテラルになると反応が鈍くなる(見逃す / 誤って書いてしまう)のではないか?

つまり、以下のような SQLI コードを、SASTは検知できなかったり、人間は割とミスって書いてしまうのではないか?という推測です。

conn.raw_query(f"SELECT * FROM users WHERE id = ${id}");

そこで、今回は以下を探してみようと思います。

  • ORM の Raw SQL (Plain SQL) 機能で、よくある文字列結合をしてしまっている SQL Injection
  • ただ、文字列結合は「言語の提供する文字列テンプレートリテラル」を使って行われている

つまり、人間がやらかして文字列テンプレートリテラルを使って文字列結合し、 SQLI を埋め込んでいるケースを探してみようというものです。


ちなみに、ORM の Raw SQL 機能を探す理由としては、過度に検知しすぎて調査が大変にならないようにするためです。
また、この着想を得たときに見ていたのが先ほど貼った Find Security Bugs の ORM 上で発生する SQLI のルールだったため、 「ORM 以外でこのケースは発生しない」という謎の前提を自分の中で勝手に作っちゃったというのも理由としてあります。

それと SAST は大変そうなので調査しませんでした。 調査しても SAST のプロダクト毎に検出する・しないが分かれるだけな気がしたというのもあります。

文字列テンプレートリテラル と言語と ORM 探し

言語選定

さて、調査をするには各言語での文字列テンプレートリテラルの書式を調べる必要があります。 そのためには調べる言語とかも選定する必要があります。

そこで今回は以下を調べることにしました。

Python も調べようかなと思いましたが、 Python で ORM 使ってる層、全くいなさそ〜という偏見を持っていたので調査しませんでした。
Ruby は文法すらわからないので調査しませんでした。
Go は文法テンプレートリテラルがないので調査しませんでした。
Java は JDK20 か 21 くらいで文字列テンプレートリテラルが入ると風の噂で聞いたのですが、新しすぎるため調査しませんでした。 (※ Java の文字列テンプレートリテラルですが、裏どりしてないので嘘かもしれません)

その他も色々理由をつけて調べてないです。

文字列テンプレートリテラルの調査

言語が決まったのでお次は文字列テンプレートリテラルを洗い出します。

※今更ですが、 文字列テンプレートリテラルと書いてますが、言語によっては String Interpolation とか呼ばれたり Template Literal, String Template, formatted string literal とか呼ばれてます。

調べた結果、以下のようなフォーマットのようでした。

  • Node.js
    • `${...}`
  • Python
    • f"${...}"
    • f"$..."
  • Kotlin
    • "$..."
    • "${...}"
  • Scala
    • s"${...}"
    • f"$..."

ORM 選び

次は ORM です。調べる対象の条件としては、 Raw SQL を発行できる機能の有無が条件としてあります。 (Raw SQL を発行できる機能がないと、文字列結合による SQLI は基本的に発生しないので)

これら言語 / ORM / 文字列テンプレートリテラルをまとめた結果が以下になります。

※ ORM の下の部分は、 Raw SQL を発行するメソッド(の一部)か、対象の ORM が使われていることを一意に特定できるキーワードになります。調査メモが雑だったので欠損してたり、検索時に使う箇所だけ残してたりしていて、正確性は全くないです。ただもう調べ直すの面倒なのでそのまま書いちゃいます。

  • Node.js

    • String Template Literal:

      • `${...}` (バッククォートで囲む必要がある。小文字の Back Quote をエスケープうまくできなかったので全角で...)
    • ORM:

      • Prisma

        • $queryRawUnsafe(
        • $executeRawUnsafe(
      • Objection.js

        • require('objection');
        • raw(${})
        • knex.raw(${})
      • mikro-orm

        • getConnection
        • execute('
      • typeorm

        • query(SELECT * FROM USERS)
        • typeorm
      • sequelize

        • sequelize
        • query( ${
  • Kotlin

    • String Template Literal:
      • "$..."
      • "${...}"
    • ORM
      • Exposed
        • TransactionManager
        • exec/
      • ktorm
        • prepareStatement
        • ktorm
      • Hibernate

      • Spring JDBC

        • queryForObject()
      • JDBC Injection

        • executeQuery()
  • Scala
    • String Template Literal:
      • s"${...}"
      • f"$..."
    • ORM

GitHub Code Search のクエリ

上記の情報をもとに、いくつかのクエリを実行しました。

例えば以下のようなクエリです。

.kt ファイルで、 prepareStatement の後に $ があるケース

path:*.kt /prepareStatement.+\$/ /ktorm/ 

SQL 文の後に 文字列テンプレートリテラルがあるケース

/(SELECT|UPDATE|DELETE|INSERT)\s?.*(INTO|FROM|SET).+\$\{?.+\}?/`

などなど。 基本的には拡張子を変えたり、 ORM 毎にメソッド名を変えたりといった微調整ばかりです。

これらを前回の記事でも使った Code Search の結果収集スクリプトに無理やり食わせて、 最終的に Star 1000 以上のものだけを抽出していきます。

https://gist.github.com/motoyasu-saburi/7a2a8ef9e3405a90016fcb26a70d012a

結果

結果は冒頭に書いた通り、 SQL Injection は発見できませんでした。残念。

理由はいくつか思い浮かびます

  • ORM を使っているような OSS はそもそも少ない可能性があるかも?
  • ORM を使うようなものの多くは、Web Service とかプロジェクト的に(会社として)作っているものが多く、 OSS になっていないものばかりなのかも?

逆に、 ORM のようなものに限定せず、 SQLI を探すとなるといくつか検知できる余地はあるかなと思いました。

終わり。


投稿後に思い出したこと追記:

  • GitHub Code Search では、複数行にまたがるコードサーチっぽいのが多分できないので、複数行にわたる SQL などは今回検索の対象外になっています。
  • GitHub CodeQL とかだと、文字列と文字列テンプレートリテラルが分かれていたので、多分 SAST によっては検知 (AST とかの検知ルールによっては)今回の研究対象は検知しないんじゃないかなーと思ってます。
  • ORM の Raw Query を選んだ理由として、書き手が 「 ${} 」みたいなフォーマットを ORM 独自の記法と勘違いしているケースってあるんじゃないか?みたいな推測をしていたのもあった。
  • GitHub で検索する際に、例として prepared statement を検索する例を出しちゃったけど、ミスリードを誘いそうなので、普通に raw query にすればよかった。あとで直す