💣

ZIP爆弾はWebサイトの攻性防壁となり得るか

2024/01/23に公開

TL;DR

クライアント側の実装によっては効果がある。

Do not try this at home!

はじめに

先日、ウェブメディアに「ZIP爆弾を用いて、サイトを乗っ取ろうとする輩に一杯食わせる」という記事が上がっていた。

https://gigazine.net/news/20240120-how-to-defend-your-website-with-zip-bombs/

(元ネタの元ネタ)
https://blog.haschek.at/2017/how-to-defend-your-website-with-zip-bombs.html

気持ちとしては良く分かる…… でもこれって本当に有効なんだろうか?

疑問

文中では以下のように語られている。

そしてそのGZIPファイルを配信するコードを記述。一般的なブラウザでは、GZIPファイルを自動で解凍する設定になっているためこのGZIP爆弾が設置されたページを開くといきなり10GBのファイルが展開されることになります。

そして、実際にIEやChrome、Edge、Safariなどの主要なブラウザで踏むと動作不良を引き起こすことも報告されている。

しかし、実際の攻撃者がインターネット上にある膨大な数のサーバのドアをノックするのにGUIのブラウザを使うとは考えづらく、
初動では何らかの専用プログラム・スクリプト言語を用いて、侵入できる可能性のあるウェブサイトを機械的にスキャンしているものと推測できる。

そのようなプログラム相手に、果たしてどこまで有効なんだろうか?

実験

実際に有名どころのツールやスクリプト言語で試してみることにする。

環境

  • Windows 11 Pro
  • RAM 16GB

元ネタのBlogにテスト用のリンクが張られているが、CUIツールではJavaScriptによる確認ダイアログを突破できないため自前で環境を用意した。

GZipを吐き出すPHPスクリプトは、元ネタの元ネタの記事に記載のあるシンプルなものとした。
また、使用するGZipペイロードについても記載の通り生成した。
(10MBが10GBになるgzipファイル)

cURL

C:\>curl --version
curl 8.4.0 (Windows) libcurl/8.4.0 Schannel WinIDN
Release-Date: 2023-10-11
Protocols: dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps telnet tftp
Features: AsynchDNS HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile NTLM SPNEGO SSL SSPI threadsafe Unicode UnixSockets

C:\>curl --output C:\Users\kuma\デスクトップ\bomb.html http://999.999.999.999/zipbomb/execute.php
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  9.9M  100  9.9M    0     0  26.4M      0 --:--:-- --:--:-- --:--:-- 26.5M

C:\>

すんなりダウンロードすることができた。
(10MBのファイルが手元にできた)

php

C:\>php -v
PHP 8.3.2 (cli) (built: Jan 16 2024 20:47:57) (ZTS Visual C++ 2019 x64)
Copyright (c) The PHP Group
Zend Engine v4.3.2, Copyright (c) Zend Technologies

file_get_contents

<?php
$url = 'http://999.999.999.999/zipbomb/execute.php';
$outfile = 'outfile.tmp';
$content = file_get_contents($url);
file_put_contents($outfile, $content);

これも問題なくダウンロードすることができた。
(10MBのファイルが手元にできた)

curl

<?php

$url = 'http://999.999.999.999/zipbomb/execute.php';
$outfile = 'outfile.tmp';

$fp = fopen($outfile, 'w');

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FILE, $fp);

curl_exec($ch) or die('error ' . curl_error($ch));
if(curl_error($ch)) {
    fwrite($fp, curl_error($ch));
}
curl_close($ch);

同様に、問題なくダウンロードすることができた。
(10MBのファイルが手元にできた)

Python

PS C:\> python --version
Python 3.12.1

urllib.requests

Python標準のurllibモジュールを使った場合。

import urllib.request

url = 'http://999.999.999.999/zipbomb/execute.php'
outfile = 'outfile.tmp'

with urllib.request.urlopen(url) as response:
    with open(outfile, 'wb') as out_file:
        data = response.read()
        out_file.write(data)
        print(f"Status Code: {response.status}")
        print(f"Headers: {response.headers}")
PS C:\> python .\python_urllib.py
Status Code: 200
Headers: Date: Tue, 23 Jan 2024 07:12:36 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Encoding: gzip
Content-Length: 10420385
Connection: close
Content-Type: text/html; charset=UTF-8

問題なくダウンロードすることができた。
(10MBのファイルが手元にできた)

Requests

より高度なことができるRequestsモジュールを使った場合。

import requests

url = 'http://999.999.999.999/zipbomb/execute.php'
outfile = 'outfile.tmp'

response = requests.get(url)

print("Status Code:" + response.status_code)
print("Header: " + response.headers)

f = open(outfile, 'w')
f.write(response.text)
f.close

実行したところ、PythonプロセスがCPUリソースを食いつぶした。

タスクマネージャ

メモリ使用率がじわじわ上がり、消費量が11GBあたりを超えたあたりでようやく画面にヘッダ等が出力された。

PS C:\> python .\python_requests.py
Status Code:200
{'Date': 'Tue, 23 Jan 2024 06:38:01 GMT', 'Server': 'Apache/2.4.41 (Ubuntu)', 'Content-Encoding': 'gzip', 'Content-Length': '10420385', 'Keep-Alive': 'timeout=5, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=UTF-8'}

処理中はプロンプトに何も表示されなかったことから、どうやらrequests.getの段階でGZipをメモリ内で展開しているようだ。

結果、手元に 10GBのファイルが出来上がった

考察

いくつかのファイル取得系コマンド、およびプログラミング言語のHTTPリクエスト系命令を用いた検証を行った。

この手法は事前察知が困難である上、GZipを自動で展開するモダンな実装が相手であれば、動作遅延効果やメモリアロケーションエラーでプロセスを止めることもできるだろうと推測できる。

本来であれば、クライアントがリクエストを送る際に「圧縮(GZip, deflare, etc)を受け付けることができるか否か」をヘッダーで通知し、サーバーはそれをもって圧縮するか否かを決めている。
今回の検証では、それらを一切無視して強制的にGZip形式で送りつけているので、クライアント側の実装によって差異が出たものと考えられる。
逆に言えば、「GZipに対応していない実装に対する対抗策」を実装すればいい話になる(そんなものが存在すればの話だが)。

おことわり

これは実験であり、Bot等に対処するためにZIP爆弾を使うことを推奨するものではありません。
また、この記事の内容に起因する一切のトラブルについて、筆者は何ら責任を負うものではありません。

Discussion