Satoshi Tajima

2018/10/13 追記

徳丸さん(@ockeghem) により、mod_cache利用時にXSSが成立してしまうケースが確認されました。
これにより環境によっては本脆弱性の緊急度合いが大きく変わってくるため、
タイトルと本文を一部修正しました。

詳細はこちらです。
https://blog.tokumaru.org/2018/09/cve-2018-17082-cache-poisoning.html


こんにちは。久しぶりのブログになりました。
最近はインフラの仕事に加えてセキュリティ周りも見るようになりました。
(元々まったくやってなかったわけではないけど)

今回はPHPの脆弱性の話題です。

3行まとめ

  • PHPにおいて、 リクエストヘッダの処理の不備により、XSSに結びつく可能性のある脆弱性が見つかった。
  • CVEは CVE-2018-17082、 PHPは Sec Bug #76582 として扱われている。
  • 詳細を確認したところ、現実的な脅威になるほどのものではなさそうという見解に落ち着いた。 環境によっては大きく影響を受けることが確認されています。

脆弱性の概要

PHP BTSのURLはこちらです。
https://bugs.php.net/bug.php?id=76582

該当のシステムに対して Transfer-Encoding: chunked を含んだ以下のようなリクエストを送信すると…

POST /lol.php HTTP/1.1  
Host: localhost  
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:61.0) Gecko/20100101 Firefox/61.0  
Accept-Language: en-US,en;q=0.5  
Content-Type: application/json  
Upgrade-Insecure-Requests: 1  
Cache-Control: max-age=0  
Transfer-Encoding: chunked  
Content-Length: 25  
 
<script>alert(1)</script>  

本来 "{'hack':'1'}" というレスポンスが返ってくるはずが、
リクエストのボディが含まれた "{'hack':'1'}"<script>alert(1)</script> というレスポンスになってしまう。

HTTP/1.1 200 OK
Date: Mon, 02 Jul 2018 05:23:16 GMT
Server: Apache/2.4.33 (Unix) PHP/7.1.17
X-Powered-By: PHP/7.1.17
Content-Length: 39
Connection: close
Content-Type: text/html; charset=UTF-8

"{'hack':'1'}"<script>alert(1)</script> 

よって同様のリクエストを他者に強要することで、XSSが成立するだろうという話のようです。

攻撃成立の難しさ

本脆弱性を利用してXSSを成立させるには、

  • 他者のブラウザに該当のリクエストの発生を強要する
  • 本脆弱性を持つ環境に該当のリクエストを到達させる

という必要があります。
この条件を満たすことにどれほどの現実性があるのかを考えてみます。

他者のブラウザに該当のリクエストの発生を強要することについて

いわゆる反射型のXSSなので、基本的には攻撃対象者にリンクを踏ませるところが攻撃の起点です。
今回はPOSTのリクエストが必要なので、罠ページを用意してそこから攻撃対象サイトにPOSTするような方法が考えられます。

まずはそんなPOSTができる罠ページを用意するために、攻撃用のリクエストをもう一度見てみましょう。

細かい話として、 Transfer-Encoding と Content-Length を同時に指定するのは、
以下のように RFC 7230 において禁止されているので、 Transfer-Encoding だけを指定することを考えましょう。

A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header field.

また、本脆弱性に関係ない他のヘッダも取り除いて考えます。

POST /lol.php HTTP/1.1  
Host: localhost  
Content-Type: application/json  
Transfer-Encoding: chunked  
 
<script>alert(1)</script>  

このリクエストでもレスポンスにリクエストのボディが含まれるため、XSSが成立しそうなことは確認できました。

罠ページでは、このリクエストを強要することができればよさそうです。
しかし、ここに1つめのハードルがあります。

Transfer-Encoding ヘッダは、 forbidden header name に含まれているため、
XHRの setRequestHeader() 等で、自由にリクエストに付与することはできません。
また、実はそもそもこのリクエストは、 HTTPの仕様として正しいものではありません。
Transfer-Encoding: chunked の場合の正しいボディ(chunked-body)は、

chunked-body   = *chunk
                 last-chunk
                 trailer-part
                 CRLF
chunk          = chunk-size [ chunk-ext ] CRLF
                 chunk-data CRLF
chunk-size     = 1*HEXDIG
last-chunk     = 1*("0") [ chunk-ext ] CRLF

となっていなければいけません。 chunk-size や last-chunk が抜けているのです。
よって、(ブラウザのバグ等がない限り)攻撃対象者を罠ページに誘導することができたとしても、
上記のようなリクエストをブラウザから自然に送信させることは難しいでしょう。

尚、このように、HTTP的に正しいリクエストにしてしまうと今回の攻撃は再現しません。

POST /lol.php HTTP/1.1
Host: localhost
Content-Type: application/json
Transfer-Encoding: chunked
 
19<CR><LF>
<script>alert(1)</script><CR><LF>
0<CR><LF>

本脆弱性を持つ環境に該当のリクエストを到達させることについて

運良く(悪く)、ブラウザから前述のような攻撃が成立するリクエストを投げる方法が見つかったとしましょう。
次のハードルは、そのリクエストをそのまま攻撃対象サイトのシステムに到達させるところです。

ロードバランサやCDNのようなホップが間に入ってしまうと、そのままリクエストを攻撃対象に到達させることは難しくなります。
例えばAWSのALBにおいては、このようにALBから400が返ってきてしまいます。

POST /lol.php HTTP/1.1  
Host: localhost  
Content-Type: application/json  
Transfer-Encoding: chunked  
 
<script>alert(1)</script>
HTTP/1.1 400 Bad Request
Server: awselb/2.0
Date: Fri, 21 Sep 2018 08:38:33 GMT
Content-Type: text/html
Content-Length: 138
Connection: close

<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
</body>
</html>

もちろんそもそもこのようなホップが間に入らない場合もあるでしょうし、 間に入る製品/サービスによって挙動が違うというのはあるので、その点は注意が必要です。

結論

このように、CVE-2018-17082を悪用してXSSを成立させるには、超えるのが難しいハードルが存在するため、
現実的にはそれほどの脅威にならないだろうと考えています。 冒頭に追記した通り、環境によってはXSSが容易に成立してしまうケースが確認されています。

もし見落としている観点などあれば、Twitter等でご指摘いただけると嬉しいです。

もちろん、この脆弱性が大きな脅威ではないからといってアップデートをしなくていいということにはなりません、
あくまでこの情報を優先順位や緊急度の参考とした上で、適切にアップデートは行いましょう。