見出し画像

プログラムをプログラムで組み立てるときの注意点とは 〜パラメータ化クエリ, SQLインジェクション, エスケープ〜

プログラム自学案内の35回目です。今回は、パラメータ化クエリと、エスケープの考え方を案内します。前回までの記事はこちら。


パラメータ化クエリ

今回の記事は、前回チラ見せしたコードのうち、ここについての話をします。

models/family.js

const result = await client.query(
    'DELETE FROM isono_family WHERE full_name=$1 AND age=$2',
    [name, age]
);

ここで full_name=$1 とか age=$2 とか書かれているSQL文は、 パラメータ化クエリ (Parameterized query) と呼ばれます。SQLの中の値を、プログラムで、いろいろ変えて実行したいときに必須で、定石の方法です。 node-postgresの取説 の "Parameterized query" の項で紹介されています。

とりあえずは、「SQLの条件句などに色んな値を入れて動かしたいときには、パラメータ化クエリを使いましょう」、とだけ覚えておけばいいのですが、では、なぜこれが必須で定石なのでしょうか?

逆に、一般的に禁じ手とされる方法もあります。パラメータ化クエリを使わずに、SQLを組み立てる方法です。

const result = await client.query(
    "DELETE FROM isono_family WHERE full_name=' " + name + "' AND age=" + age
);

もしくは

const result = await client.query(
    `DELETE FROM isono_family WHERE full_name='${name}' AND age=${age}`
);

という感じです。

こっちの方がコードとしてシンプルだし、同じように動くはずなのに、なぜ禁じ手なのでしょうか?

禁じ手が禁じ手である理由は、何と3つもあります。

  • じつは正しくSQL文を組み立てられない、 機能的欠陥 がある

  • 正しくSQL文を組み立てられないことによる、 脆弱性 がある

  • つど異なるSQLが発行されることにより、性能的に不利 になる

ことに2つ目の脆弱性はマジでヤバいんですね。どれくらいヤバいかの説明は、この記事では割愛しますが、かわりに、SQLインジェクション というキーワードで、ぜひそのヤバさを調べてみてください。

ところで、2度も出てきた「正しくSQL文を組み立てられない」とはどういうことなのでしょうか。このことは、SQLの学習としてではなく、あらゆる言語が持つ、 エスケープ という考え方の学習のなかで、理解したほうが良いでしょう。この記事では、ゆっくりめに説明します。

エスケープの話

あらゆる言語とは?

さきにあらゆる言語はエスケープの考え方を持つ、と言いましたが、あらゆる言語とはどういうことでしょうか? ここでは、コンピュータに指示を与える記法のすべて、ととらえてください。この連載では既に次の7つの記法を紹介していますが、これらには、すべてエスケープの仕組みが備わっています。

  • HTML

  • CSS

  • JavaScript

  • シェル言語

  • HTTP

  • mustacheテンプレート言語 ※

  • SQL

※ 正確には、mustacheテンプレート言語には狭義のエスケープ構文はありません。ですが、代わりに Set Delimiter という仕組みが備わっています。

ほとんどの言語は、別の言語で書かれたメッセージを扱う

上述の7言語はすべて、文字列を扱おうとします。そして、扱われようとしている文字列の内容は、「その言語以外の言語でかかれたメッセージ」です。

  • 自然言語(日本語や英語など)で書かれた、人間向けのメッセージ

  • コンピュータ向けの記法(HTMLやSQLなど)で書かれた、コンピュータ向けのメッセージ

ややこしいので、例を見てみましょう。

const result = await client.query(
    "DELETE FROM isono_family WHERE full_name='磯野波平' AND age=54"
);

これは全体としてJavaScript言語で書かれていますが、2行目の部分の 文字列 の内容は、JavaScriptではなくSQL言語の文です。そして、SQL文の中にも 文字列 があり、その内容である「磯野波平」は日本語です。ややこしいと思いませんか?

もう一つ例を見てみます。

<a href="https://www.google.com/?gl=us&hl=en">
    Google検索 英語版
</a>

これは全体としてHTML言語で書かれていますが、「"」 で囲まれたgoogleのアドレスの部分の 文字列 は HTTPで用いられるURL、「Google検索 英語版」の部分の 文字列 は日本語ですね。

文字列の終わりをハミ出る問題

いままで見てきた例で分かると思いますが、文字列の開始、終了はだいたい、「"」「'」「>」「<」なんていう文字で区切られることが多いです。ですが、この決まりだけだと、文字列の中でこれらの文字を扱いたいときに困ります。

たとえば、元NBA選手のシャキール・オニールが磯野家にやってきたとしますね。

INSERT INTO isono_family VALUES ('Shaquille O'Neal', 51);

このSQLは上手くいきません。英語であるはずの「Shaquille O'Neal」に「'」が含まれるせいで、文字列の枠を「'Neal」が越境し、本来英語としての意味のみを持たせたいはずの「'Neal」が、SQL文としての意味を持ってしまうからです。

では、どうすれば良いでしょうか?

エスケープとは

PostgreSQLでは、「''」(シングルクォート2つ)と書くことで、文字列の終端であるというSQL上の意味が取り除かれた、文字列のなかの「'」文字を表現することができます。

INSERT INTO isono_family VALUES ('Shaquille O''Neal', 51);

このことを、「「'」を エスケープ(escape) する」と言います。英語で escape は 逃亡、脱出という意味です。なぜ逃亡? 「文字列の終端」という意味付けから逃れさせるというニュアンスで、エスケープというようです。

じつは、エスケープはこの連載の「はじめてのJavaScript」でも行われています。

console.log('Hello, World. m(__)m I\'m sorry to be born.');

「コンピュータに対する指示」に、文字列を埋め込むときには、必ずエスケープ処理を!

さきほど述べた通り、プログラムが扱う文字列は大きく、次の二つに大別できます。

  • 人間に向けたメッセージ(日本語、英語、中国語、、、)

  • コンピュータに対する指示(SQL、JavaScript、HTML、JSON、正規表現、、、)

プログラミングでは、前者と後者双方の文字列を扱いますが、後者、コンピュータに対する指示の文字列をプログラムで組み立てるときには、必ず エスケープを考えなければいけません。ヤッカイですね。

この記事の最初の方に、禁じ手として示したコードを再掲します。

const result = await client.query(
    "DELETE FROM isono_family WHERE full_name=' " + name + "' AND age=" + age
);

このコードでは、コンピュータに対する指示であるSQL文字列を組み立てているのに、エスケープ処理がなされていません。このようなコードは、かならず、機能的な欠陥があるコードとみられるのです。具体的には、「変数のnameが "Shaquille O'Neal" を指すとき、SQL文が実行できない」という欠陥を持つのですね。

この欠陥を回避するには、「nameの中の文字列に ' が入っていたら、全部 '' に置き換える 」というようなひと手間(エスケープ処理)を加えなければいけません。面倒ですが、SQL文をプログラムで組み立てようとする限り、この面倒さを避けるわけにはいかないのです。

一方、パラメータ化クエリは、このような欠陥を回避するために、「SQL文字列の組み立て」そのものを回避する方法だったというわけなのでした。

他のエスケープの例を考えてみましょう

これまでの連載では、mustacheテンプレートエンジン をつかって、HTML言語で書かれた画面に、日本語を埋め込んでいました。たとえば、下記のコードでは {{.}} の部分に日本語が埋め込まれます。すなわち、mustacheエンジンによって、HTML言語の文字列が組み立てられます。

list_view.html(抜粋)

    あなたが いままでに おだてた人たち
    <ul>
        {{#history}}
        <li>{{.}}さん</li>
        {{/history}}
    </ul>

では、HTML言語で文字列の終端を示す文字が、{{.}} さんの部分に入ってきたらどうなるでしょうか?

つぎのことを試してみましょう。

  • おだてる人の名前に、HTMLタグを埋めてみる(例:「滝沢</li>カレン<li>チャン」)

  • 表示された画面で、ブラウザの「ページのソースを表示する」コマンドを実行する

手元に mustacheテンプレートエンジン を使ったアプリが無い方は、mustacheテンプレートエンジンのデモ画面 で、nameに「滝沢</li>カレン<li>チャン」と入れるとどうなるか確かめてみましょう。

{
  "header": "Colors",
  "items": [
      {"name": "滝沢</li>カレン<li>チャン", "first": true, "url": "#Red"},
      {"name": "green", "link": true, "url": "#Green"},
      {"name": "blue", "link": true, "url": "#Blue"}
  ],
  "empty": false
}

その結果がなぜ起きるのかの理解は、読者の課題とします。納得いくまで、周りの人や google、ChatGPTなんかに訊いてみてください。

まとめと次回予告

今回の記事では、「エスケープ」について紹介しました。小ネタのようで、じつは意外と大切な大ネタだと、思っております。何せ、ややこしい話です。

次回予告です。前回例題としてお見せした家族の追加・削除プログラムの実装例を紹介する予定です。

余談

エスケープという言葉で思い出すのが「大脱走 (The Great Escape)」という映画です。1963年公開の映画だそうなので、もう60年も前の古典ですが、見たことあります? あれは見どころの多い、面白い映画ですよね。見たことない人にはおススメします。ジュディマリの曲にもそういう名前の曲がありますが、有名なのは映画のほうだと思います。

#コラム #プログラミング #JavaScript #SQLインジェクション #エスケープ

この記事が気に入ったらサポートをしてみませんか?