Webアプリは基本的にテキストインターフェースを利用して構築します。HTML、JSON、SQL、XML、XPath/LDAPクエリ、HTTP、SMTP、これら全てテキストインターフェースです。
今日はインターフェースとテキストインターフェースの基本を紹介します。
インターフェース
インターフェースとは異なる境界で物事をやり取りする場合の決まりなど含む手順(プロトコル)です。コンピュータで利用するインターフェースにはハードウェアインターフェース、ソフトウェアインターフェース、ユーザーインターフェースがあります。Web開発者がWebアプリケーションから利用するインターフェースの多くは、前述の通りテキストで記述された手順を採用しています。
ソフトウェアインターフェースの種類
プログラムが命令やデータをやり取りするソフトウェアインターフェースにはバイナリとテキスト、固定長と可変長があります。固定長インターフェースの場合、命令やデータは記述された位置によって意味を持ちます。固定長と可変長が組み合わさったインターフェースもあります。
固定長インターフェース
利点
- 位置に意味があるため、命令やデータを取得する場合にパース(解析)が必要ない(高速)
欠点
- 位置で意味が決まるため、拡張性が低い
可変長インターフェース
利点
- 文字で意味が変わる為、柔軟に構造を変えることができ拡張性が高い
欠点
- 文字列の意味は解析(パース)しないと分からない(低速)
固定長インターフェースにもメリットはありますが、現在では速度より柔軟性の方が重視され可変長のインターフェースが広く利用されています。
バイナリインターフェース
例えば、UDPプロトコルはバイナリインターフェースを持ち、固定長と可変長を組み合わせて利用しています。バイナリインターフェースを持つ、とは送信元ポート番号やチェックサムなどがテキストの数値ではなく、バイナリ形式の数値を持っているという意味です。
オフセット(ビット) | 0 – 15 | 16 – 31 | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 送信元ポート番号 | 宛先ポート番号 | ||||||||||||||||||||||||||||||
32 | データ長 | チェックサム | ||||||||||||||||||||||||||||||
64+ | データ |
出典:http://ja.wikipedia.org/wiki/User_Datagram_Protocol
UDPのデータはヘッダー長フィールドの値によって変わります。ヘッダー部分は固定長のデータです。
バイナリインターフェースの利点は効率が良い事です。例えば、ポート番号がテキストの数値ならテキストからバイナリ値への変換が必要になります。元々バイナリなら変換は必要ありません。(実際にはエンディアンの違いから変換が必要になる場合があります)データサイズも小さくなります。
テキストインターフェース
現在ではテキストインターフェースの多くが可変長のインターフェースを持っています。可変長インターフェースを持つテキストインターフェースは、テキスト情報に意味を持たせるため特定の形式や特別な意味を持つ文字が定義されています。
ここでは「可変長」の「テキストインターフェース」を解説します。
HTTPプロトコル
HTTPプロトコルは非常にシンプルなルールを持ったテキストインターフェースです。基本的な構造は次のようになっています。
ヘッダー名: 値 ヘッダー名: 値 <ボディ>
様々な情報や命令を含んだヘッダー部分は「ヘッダー名: 値」の形式を持ちます。各ヘッダーは改行文字で区切られ、改行文字が2回連続で現れたあとが「ボディ」になります。
HTTPプロトコルの解析
プログラムがHTTPデータを利用するには、HTTPプロトコルのルールに従って解析しなければなりません。HTTPプロトコルを解析(パース)するプログラムは「ボディ」が現れるまで(つまり改行が2つ連続で現れるまで)
"ヘッダー名" + ":" + "値"
の形式を持つテキストをスキャンし、ヘッダーとその値を保存していきます。HTTPプロトコルパーサーは複数あるHTTPヘッダー(「ヘッダー名」と「値」)と「ボディ」に分割する処理を行うと考えて良いです。
HTTPプロトコルの解析(パース)とは、そのままでは意味のないテキストを意味のある物として処理することです。
SQL文の解析
もう少し複雑なSQL文のパースがどのように行われるか、簡単に解説します。
SELECT * FROM mytable;
SQLパーサーがこのSQLを受け取った場合、まずSQL構文を形成する語句に分割します。つまり、このSQL文は
- SELECT
- *
- FROM
- mytable
- ;
に分割します。分割の後にSQL構文に適合する構文木を作成します。SQL構文に適合しない語句があった場合、構文エラーになります。例えば、
- SELECT文の後には必ず1つ以上の値(式)または識別子がある
という構文ルールがあります。このルールに反して
SELECT FROM mytable;
等とすると構文エラーになります。
SQLパーサーは基本的にはSQLを「SQL語句(命令)」「識別子」「パラメーター」の3つに分解します。
テキストインターフェースとセキュリティ
セキュリティ的に重要な点は「テキストインターフェース」は何らかの解析(パース)処理が行われ、ルールに従って文字列に意味が持たされることです。文脈、つまりコンテクストによって意味が変わり、適切な無害化方法も変わります。
HTTPプロトコル
例えば、HTTPヘッダーの値として以下の様にプログラムから設定されたとします。
Location: $new_location
$new_locationには絶対パスのURLが入ることになりますが、攻撃者が任意の文字列を設定可能な脆弱性がある場合、$new_locationに
http://example.com\r\nCache-Contole: public
が設定されていたらどうなるでしょうか?プログラマが想定しない”Cache-Control”ヘッダーがインジェクションされ、キャッシュされてならないコンテンツがキャッシュされるかも知れません。
(注:PHPの場合、HTTPヘッダーの直接出力は許可されていないのでこの様な事は行えない)
HTTPヘッダーのみでなくボディにもインジェクションできる事が解ると思います。ボディにインジェクションした場合、簡単にJavaScriptがインジェクションできます。
ここで解説したHTTPヘッダーインジェクションはHTTPレスポンススプリッティングと呼ばれる攻撃手法です。
SQL文
SQLの場合も、 テーブル名が変数の場合、
SELECT * FROM $mytable;
$tableに”mytable; DROP mytable;”が設定されると
SELECT * FROM mytable; DROP mytable;
となりmytableを削除されてしまいます。
他の可変長テキストインターフェース
テキストインターフェースのパーサーがどのようにテキストを解釈するのか、全てを解説することは困難です。プログラミングを始めたばかりの開発者に理解を求めることは現実的ではありません。
しかし、「パーサーが命令とデータ(識別子・リテラルなど)を解析し」、「命令とデータの分離」がセキュリティ上重要である、と理解した上でそれぞれの処理系のコンテクストに対して適切な「エスケープ処理」および「バリデーションが必要な箇所」を理解するようにすれば、開発を始めたばかりの開発者でも自分自身で安全な処理はどのような処理か自分自身で理解できるようになると思います。
HTTPプロトコルは単純ですが、SQLやJavaScript、シェルはプログラミング言語なので複雑な仕様を持っています。複雑な仕様を持っている場合でもエスケープ処理・バリデーション処理は比較的単純な場合も多いです。エスケープ処理・バリデーション処理が必要ないAPIが用意されている事もあります。
例えば、PHPのHTTPヘッダーを送信するheader関数はパラメーターの中の改行を許可しません。このため、header関数はここで紹介したHTTPレスポンススプリッティングが行えない仕様になっています。
別の意味に変えさせない
ここまでで、テキストインターフェースが解析(パース)した際に”別の意味に変えさせない”ことがセキュリティ上重要であることが解りました。
インジェクション対策、基礎の基礎では「命令とデータの分離」が、パーサーに「データの中に命令を埋め込ませない」ために重要ですと解説しました。
プログラマがセキュアなコードを書くためには、「命令とデータの分離」の前にパーサー(つまりテキストインターフェース)が「どの部分を命令」「どの部分をデータ」として取り扱うのか理解していなければならないという事です。
つまり出力を行う場合、開発者は出力のコンテクストを理解していなければならない、です。
インジェクション対策、基礎の基礎で解説した事を「基礎の基礎」とするなら、この理解は「基礎の基礎の基礎」と言えるでしょう。
一部の例外を除き、命令にユーザーが指定できる変数を利用する事はありません。命令を変数にする場合には細心の注意が必要です。変数を命令に利用する=直接命令を指定する、だからです。eval関数に注意が必要であるのはこの為です。
<?php $function = $_GET['api_name']; eval("\$return_value = ${function}();");
このコードが任意コード実行に利用できる事は、ここまでの説明で理解できると思います。
PHPの場合、文字列変数の内容を関数名として呼び出す$function_name()形式の関数呼び出しが可能です。任意の関数・メソッドの呼び出しは許しますが、こちらの方が任意コードの実行を仕組み的に許さないのでより安全です。
インターフェースとAPI
可変長テキストインターフェース用に限らず、インターフェースにアクセスするAPIが用意されている事も多いです。APIもインターフェースの一種です。
一般に「セキュアなAPI」と呼ばれているAPIは、「命令とデータを分離またはデータに命令を混入されないAPI」です。代表例はプリペアードクエリです。プリペアードクエリはクエリパラメーターの分離が可能です。仕組み的にクエリパラメータ−が命令と誤解釈される余地がありません。しかし、プリペアードクエリには仕組みとして識別子を分離できない、命令の書き込みを防げない、という欠点があるためセキュリティ対策的に理想のAPIとは言い難いです。
全てのインターフェースに「セキュアなAPI」用意され、「セキュアなAPI」以外からのアクセスが禁止(仕組み的に不可能)で、「セキュアなAPI」が完全であれば良いのですが残念ながらこのような開発環境を提供しつつ、十分に柔軟なアプリケーション開発環境は知りません。セキュリティ的にはこのような開発環境があれば理想的と言えます。仮にこのような環境を作ったとして、セキュリティ的に理想的でも通常は「安全性」と「利便性」がトレードオフ関係にあり、開発者のニーズに耐えうる物になるのか?は疑問です。
少なくとも当分の間は、基礎・基本を押さえつつ現在の柔軟な開発環境を安全に使っていくしかありません。理想的な環境の構築はチャレンジングなプロジェクトだと思います。もし、挑戦しているプロジェクトがあったら教えてください。
まとめ
ツイッターで「シェルのコマンドラインでコマンドと引数のエスケープを区別する事の意味が理解できない」と問い合わせを頂きました。そこで「命令とデータの分離」の前に、パーサーがどのように解析するのか、簡単な説明がないと混乱するかも知れない事が分かりました。
シェルのコマンドラインではコマンドも引数も同じルールでエスケープできます。例えば、”this is my great command”というスペースを含むコマンドがあった場合、これをコマンドとして実行するには”this\ is\ my\ great\ command”とエスケープすれば”this”がコマンドとして解釈されず、”this is my great command”がコマンドとして実行されます。
シェルエスケープはコマンド名/引数にもシェルコマンドを分離する特殊文字などにも利用できますが、セキュリティ的には「命令とデータの分離」が重要です。攻撃者がコマンド名にインジェクションを行える場合、エスケープしてもあまり意味がありません。「データが命令として誤解釈されないエスケープ」がセキュリティ上意味を持つエスケープ処理になります。
コマンドとその引数は明らかに異なるコンテクスト、命令と引数、です。
命令を変数にする場合、ユーザー入力が含まれないか、含まれない事が前提であっても、意図しない命令が入り込む余地がないようバリデーションすると良いです。シェルコマンドの場合、コマンド名をエスケープして全く意味がない訳ではありませんが、任意コマンドを指定できる状態で安全である、とは言い難いです。
(備考:コマンド名をシェルエスケープすると、任意のコマンドを”任意のオプション付き”で実行することは防げる。コマンド名が変数の場合、フェイルセーフ対策としてエスケープすることはセキュリティ対策として意味があります)
固定長のインターフェースの解説はあまり行いませんでした。固定長の場合、オーバーフロー/アンダーフローが発生すれば命令となる部分を書き換える事ができます。(命令となる部分がある場合)命令が無い場合でも、データ部分を書き換えられてしまうと困る場合も多いです。固定長のインターフェースで処理する場合、データの大きさに十分注意してください。
Leave a Comment