nginxでreverse proxyしてslowlorisを防いでみる

Slowloris HTTP DoSというスクリプトが最近話題で、だれでもかんたんに、そんなに帯域も使わず、おもに apache に DOS をかませます。

ためしに(自分のサーバに)かましてみたらカンタンでした。いにしえの SYN flood attack っぽい芸風な感じがします。

残念ながら apache 系では、設定を追い込むとかのアプローチでは、なるべく被害を小さめにおさえるぐらいのことしかできないぽいです。猛烈ないたずら電話の被害に遭っているときに、かかってきたいたずら電話はなるべくすぐ切るようにしましょう! ぐらいの負け戦ですね。

リクエストをどう処理するかのツクリを狙われてる感じで、いまの apache を使うかぎり対処はなかなか厳しいのですが、最近人気のロシアの nginx だと大丈夫みたいということで、組み合わせをためしてみました。

nginx (えんじん・えっくす) は web サーバのソフトウェアですが、pop3 だの smtp だののプロキシもできたり、そもそも http server として軽かったり、https もしゃべれるし、Empty GIF Module - serve a 1x1 image from memory とか GEO Module - set config variables using key/value pairs of IP addresses とか、Memcached Module とか、ダメな、いや面白そうな機能がいろいろあって、以前からいじってみたくてむらむらしていたのですが、面倒で試してないままでした。

今回は nginx を apache2 の前段に reverse proxy としてかませてみます。

まず slowloris で自分のホストを攻撃してみます。

僕はあらゆる deb, dpkg, apt-get な感じで環境がパッケージ管理されていないと気持ち悪くて邪悪な気分なのですが、いま手元にあるのはさらに堕落した OS X マシンなので、手で cpan とか打っちゃいます。ああ邪悪。

sudo cpan install Net::SSLeay

sudo cpan install IO::Socket::SSL

/usr/bin/perl slowloris -dns example.com

対象のapacheは簡単に応答不能になりました。apache 側ではレスポンスどうしよっかなーというフェーズで寸止めされちゃってるので、ログにも残りません。

サーバ側に nginx 入れてみます。alternatives とかひっかかるかと思いましたが、apache2 と無事共存するかたちでインストールできます。

sudo apt-get install nginx

ちょいちょい設定します。Debian GNU/Linux 流には /etc/nginx/sites-available 以下に VirtualHost ごとに proxy 定義をファイルにばらけて書いていくのが美しいと思いますが、めんどくさいのでとりあえず /etc/nginx/sites-available/default に全部書いちゃう。

proxy_set_header  X-Forwarded-For  $remote_addr;

server {

listen 80;

server_name example.org;

location / { proxy_pass http://example.org:8000; }

}

server {

listen 80;

server_name blog.example.org;

location / { proxy_pass http://blog.example.org:8000; }

}

# and so on..

すでにお分かりと思いますが、これからは port 80 は apache じゃなくて nginx で受けて、それを「中の人」の apache にこれからは port 8000 で listen させてるところに proxy しましょうと。

apache2 がわは、/etc/apache2/ports.confListen 8000 とか、/etc/apache2/apache2.confNameVirtualHost XXX.XXX.XXX.XXX:8000 とか、あとは気合いで /etc/apache2/sites-available/ 以下にごっそりたまった VirtualHost 定義ファイル群も port を 8000 にしていきます。

ああこんなの、仕事だったら m4 マクロで書いておいて一気にドンですが。

ついでに、/etc/apache2/conf.d/proxy-log とか作って

LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy-combined

とか書いておき、ログ書き出しは CustomLog うんぬんの proxy-combined としておくと、nginx が受けた source ip が中の人 apache のログにも出るので素敵です。

(さらに mod_proxy で多段に proxy 仕込みいれてたり、そもそもその前にどっかでプロキシされてたりすると %{X-Forwarded-For}i がカンマ区切りで増えちゃってログ解析的にイヤとか、そもそも変な HTTP_X_FORWARDED_FOR を偽装してきたらどうすんのとかありますが、面倒くさいので今はスルーします)

あとは、中の人 apache が listen してる port 8000 に直に slowloris くらったら元も子もないので、外からの 8000 を蹴っておきます。

apache 設定でやったほうがエレガントですが、L7 処理はコスト高いだろ、という以前に、これ以上 apache 設定をあれこれ書き換えるのも面倒くさいし、明日も仕事あるので、めんどうだから iptables で。

iptables -A INPUT -p tcp -s xxx.xxx.xxx.xxx --dport 8000 -j ACCEPT

iptables -A INPUT -p tcp --dport 8000 -j DROP

そもそも xxx.xxx.xxx.xxx (外側ネットワーク) とかしないで、ちゃんとやるなら name based virtual host じゃなくて中の人は port based virtual host に切り替えて local network でやっとけよ そこんとこ。と思いますが、さらにこれ以上 apache 設定をあれこれ書き換えるのも面倒くさいし切り戻しできないし、明日も仕事あるしそういえばまだお風呂入ってなかったので、これでいいです。

そんな感じで、slowloris へっちゃらになりました。