今回Qualysが公開した脆弱性情報とPoCでは、前章で説明した概略に沿って攻略する際に多くのパターンとOSが含まれており、Linuxのものだけでも下記などが存在します。
今回は、この中で「ld.soとSUIDされた任意のバイナリを用いて攻略する」という方法に関して(特にamd64:64bitの場合を例に)詳しく見ていきます。
この方法では、前述した一連の脆弱性の中から下記の3つを使用して、スタックガードページにアクセスすることなく、飛び越してしまうことが可能になっています。
以下、前章と同じ【ステップ1〜4】に沿って追っていきます。
スタックの開始アドレスをAnonymous mmap()に到達させるために、以下のことを行います。
最初の128KBのスタック拡張を消費させるために、128KBのargv[]とenvp[]ポインタをexecve()に引き渡します(図6)。
ld.soは、LD_LIBRARY_PATH環境変数のスタックベースのコピーであるllp_tmpを割り当てます。このllp_tmpは128KB(MAX_ARG_STRLEN)より大きく、スタックガードページを飛び越して直接マップされたメモリ領域を突き破ることができます(図7)。
スタックガードページを飛び越してしまい、重なったメモリ領域を好きなように書き換えてコードを実行します。この箇所は実際に攻撃につながってしまう可能性があるため詳細は書きませんが、ld.so中のhwcap(hwcap-pointersとhwcap-strings)とLD_LIBRARY_PATHを用います。これとSUID/SGIDが設定されたプログラムを用いて、root権限の取得が可能になります。
64bitの場合は、対象となるメモリ領域が大きいため、【ステップ1】のようにmmap-stack間が2GBどころではなく、とんでもない広さになる可能性があります。
しかし、CVE-2017-1000379によって、mmap-stack間の距離は最小で128MBになることが分かります(※計算参照)。
※(計算)
mmapによってマッピングされるAnonymous mmapなどの終わりの位置(mmap_end)は、Linux Kernelのarch/x86/mm/mmap.c中のarch_pick_mmap_layout()の実装により、下記のような式で求められます。
mmap_end = TASK_SIZE - MIN_GAP - arch_mmap_rnd()
スタックの上端(stack_end)は、fs/binfmt_elf.c中のrandomize_stack_top()により、下記のような式になります。
stack_end = TASK_SIZE - [0〜stack_maxrandom_size()]
従って、mmap_endとstack_end間の距離は下記のようになります。
stack_end - mmap_end = MIN_GAP + arch_mmap_rnd() - "stack_rand" = 128MB + stack_maxrandom_size() - "stack_rand" + arch_mmap_rnd() = 128MB + StackRand + MmapRand
さらに、全てのカーネルでPIEやld.soがスタックの直下にマッピングされる確率は(計算は省略しますが)、約1万7331分の1になります。つまり、PIEやld.soのread-writeセグメントがスタックガードページの開始アドレスと同じになるわけです。
これらの条件により、ld.soとSUIDされた任意のバイナリを用いて攻略した場合には、平均5時間程度で攻撃のPoCが成功するといわれています。
筆者も実際にPoCを行いましたが、条件がなかなか厳しいので(実際にはmmap-stack間も最小で128MBなので実際にはもっと広い)、成功までにかなりの時間がかかってしまいました。しかし、だいたい3000回ぐらいの試行を数十回繰り返して行い、攻撃が成功しています。また、この脆弱性の特徴上、SELinuxが有効(Enforcing)になっていても攻撃は成功しています(図8)。
今回の一連の脆弱性を受けて、glibc、カーネルなどに修正が加えられています。ディストリビューションのカーネルでは、緩和策としてスタックガードページのサイズを1MiB(1024KB)に増やして、突破される可能性を低くするようにしました。
しかし、根本的な修正になっていないことがやや懸念されます。スタックガードページのサイズを例えば1MiBにしたとして、今回のStack Clashの脆弱性は防げるかもしれませんが、将来にわたって、その1MiBで安全だとは言い切れないからです。
そのため、Linux Kernel 4.11.7からは、スタックガードページのサイズを、ブート時に「stack_guard_gap=[MM]」で引き渡すことにより、ユーザーがサイズを変更できるようにしました。
今回の件は、10以上年前から懸念されていたことが実際に発覚したということと、根本的には確率を下げる以外にないという方法でしか修正ができないため、根が深い問題になっています。今回の問題も64bitに関しては発生確率は低いため、深刻度もそれほどではないと思われるかもしれません。しかし、現実に特権を取れる脆弱性であるため、なるべく早い対処が望まれます。
また今回の件は、関係するパッケージが多く、かつ根が深い問題だったため、脆弱性の公開自体も異例に遅れました。意図的に公開を遅らせたことについては、OSS-Security MLのSolar Designer氏が謝罪しており、今後の対応を早くするために各ディストリビューションのセキュリティ担当と脆弱性に関して緊密に連携を取るようにしています(参考)。
今後も、脆弱性情報については最新のものをウォッチして対処していく姿勢が必要となるでしょう。
略歴:OSSのセキュリティ専門家として20年近くの経験があり、主にOS系のセキュリティに関しての執筆や講演を行う。大手ベンダーや外資系、ユーザー企業などでさまざまな立場を経験。2015年からサイオステクノロジーのOSS/セキュリティエバンジェリストとして活躍し、同社でSIOSセキュリティブログを連載中。
CISSP:#366942
近著:『Linuxセキュリティ標準教科書』(LPI-Japan)」
Copyright © ITmedia, Inc. All Rights Reserved.