この記事は Qt Blog の "Security considerations regarding QLabel and friends" を翻訳したものです。
執筆: Peter Hartmann, 2011年10月4日
この記事は [qt QLabel] およびその類似クラスへの入力を常にサニタイズすることに関する注意喚起です。
文字列による情報を表示するために [qt QLabel] を作成する場合、そのクラスではフォーマットがプレインテキストかリッチテキストかを推測しようとすることに注意してください。特に Web から読み込んだデータなど、外部から取得した文字列をプレインテキストとして表示したい場合、[qt '' setTextFormat l=qlabel p=textFormat] で明示的にフォーマットを指定したり、[qt Qt escape()] で入力をエスケープすることを推奨します。
QLabel(や [qt QMessageBox] 等の類似クラス)へ入力する文字列は巧妙に作成することで、そのすべてを表示させないようにすることができます。
この記事を書くきっかけとなったのはいくつかの Qt / KDE アプリケーションでの脆弱性です。
それらの脆弱性ではラベルに偽の情報を表示させて欺くことができます。
ブラウザで SSL を介してサーバと接続することを想像してください。ユーザが SSL 接続の詳細を(SSL エラーの確認や単なる興味から)確認したい場合、ブラウザは SSL 証明書の最も有用な情報が記述されているフィールド群をダイアログに表示しようとします。その中で最も重要なものの一つが、証明書が対応するホスト名を意味するサブジェクトフィールドのコモンネーム属性です。たとえば、https://qt.nokia.com で使われている SSL 証明書には以下に示すサブジェクトの行で強調してあるようにコモンネーム(略: CN)が含まれています。
Subject: C=NO, ST=Oslo, L=Oslo, O=Nokia, OU=ASF, CN=qt.nokia.com
それでは、下記のような異常なコモンネーム属性を持つサーバを想像してみてください。
Subject: C=NO, ST=myState, O=myCompany, CN=qt.nokia.com<table>.evilhost.com
setText() を用いて QLabel にこのコモンネームを表示する場合、QLabel クラスはこのテキストをリッチテキストとして解釈し、そのため "<table>.evilhost.com" の部分は表示されないでしょう。この場合の表示は以下のようになります。
これは明らかに QLabel が表示すべきものとは異なります。この場合の解決策は QLabel に文字列のフォーマットを明示的に指定することです。
label->setTextFormat(Qt::PlainText);
そうすることで、ラベルに情報の全体を正しく表示されます。
読者の皆さんは次のようにおっしゃるかもしれません:「確かに。その通り。しかし、認証局がコモンネームに HTML タグを含むような証明書を承認するはずが無い。」しかし、「NUL 文字を含む証明書に認証局(CA)が署名」した事例があり、それほど大きな差があるとは思えません。(とはいえ、NUL 文字のケースは SSL の実装が証明書が指し示すのとは異なるホスト名を受け入れるようにだまそうとするもので、より悪質であると言えます。この記事で書いている問題は不正なホスト名が表示されるということに過ぎません。)
信頼できない入力をリッチテキストとして使用する場合には、以下のコードのようにエスケープすることを推奨します。
label->setText(QString::fromAscii("<b>Common Name:</b> %1").arg(Qt::escape(commonName)));
QLabel や QMessageBox 等のクラスへの入力は常に注意して扱ってください。それらのクラスで表示させる前に、文字列のフォーマットを明示的に指定したり入力をエスケープするのが良いでしょう。
この問題を報告してくれた Nth Dimension の Tim Brown と KDE の Jeff Mitchell に、またこの記事のクロスチェックをしてくれた KDE の Rich Moore に感謝します。