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へっちゃらになりました。