毎秒1000リクエスト を捌く超高速CMS「adiary」
2010/04/20(火)ApacheのRLimitMEMが効かない
CGI開発中についうっかり無限ループを生成するなんて経験のある人も多いと思いますが、つい最近2回連続でサーバごと落としてしまったので*1本格的に対策しました。
RLimitMEMとRLimitCPU
ApacheにはもともとCGIのメモリやCPU使用量を制限する設定があります。それがRLimitMEMとRLimitCPUです。
それぞれ「メモリ(リソース)使用量」と「CPU時間使用量を制限する」もので、制限値を超えた瞬間にプロセスを殺してくれます便利なものです。*2
# 256MB limit(unit Byte) RLimitMem 268435456 RLimitCPU 30
もちろんこれは設定していたのですが……。
RLimitMemが効かない
top コマンドで監視していいたら普通に500MBのメモリを食ってます。どうやら、RLimitMemやRLimitCPUが効かないようです。
- mod_perl / mod_perl2
- FastCGI(mod_fastcgi) / mod_fcgid
では効きません。ちょうどmod_fcgidを使用していました。Perl自体は別プロセスで起動しているので効くのかと思ったのですが、どうやら無効なようです(汗)。ちなみに通常CGIの他、SpeedyCGIは効きます。*3
Apache自体に制限を掛ける
Apache自体のメモリ消費量に制限をかければ、万が一無限ループなどで暴走してもサーバ落ちを防ぐことができます。Apache自体は落ちてしまいますが、サーバが落ちるよりは100倍マシです。
Apacheの起動スクリプト(シェルスクリプト)に
# 1GB limit(unit KByte) ulimit -v 1048576
と記述することで制限を加えることができます。
Ubuntu/Debian系の場合は /etc/apache2/envvars に ulimit を書き加えればよいようです。これでApache自体が落ちると思ったのですが、mod_fcgid の場合 fcgi プロセスのみが落ちてくれました。
ちょっと謎は残るけど、とりあえず目的は達成したのでこれで。
2007/04/30(月)Microsoft-IIS がダメダメな件(303 See Otherを無視する件)
●対象 Microsoft-IIS/5.0(Windows 2000系付属)
IE で数々のダメを露呈している MS ですが、案の定IISもダメダメでした。adiaryは、IISサーバ + perlis.dll でも動作することになっているのですが、IISサーバ + perl.exe で動かないという報告がありまして、対応外なのですが調査してみました。どうも IIS の Cookie 処理が怪しいようです。
perlis.dll だと Image::Magick を使えない*1のだそうで。試しに動作させたらメモリを食う暴走して危険でした(汗)。
303 See Otherとは何であるか
原因を説明する前に、adiaryのログイン時の動きを確認しておきましょう。
(1) | adiary/?login | login フォーム |
(2) | adiary/ | パスワード認証をして set-cookie |
(3) | adiary/?login_auth | cookieがきちんとブラウザに保存されているか確認 |
(4) | adiary/ | ログイン後の画面 |
IISだと (3) の時点で「Cookieが有効になっていません」とエラーになります。
HTTPヘッダには 303 See Other というものがありまして、これはHTTP/1.1対応ならば使えます。HTTP/1.1対応かどうかというのは、HTTPクライアントとサーバが両方対応しているときに環境変数 SERVER_PROTOCOL=HTTP/1.1 が設定されます。
adiary(というより Satsuki-system)ではリクエストを redirect するとき、SERVER_PROTOCOL を参照して適切な HTTP ヘッダを返します。というのも、ログインのような POST をリダイレクトするとき、Location ヘッダを出せばいいと思っている cgi 作者もたくさんいらっしゃるようですが、事はそう単純ではありません。
Location ヘッダを同時に出せる(出力してもいい) HTTP/1.1ヘッダ には3種類あります。
ヘッダ | 移動後のメソッド | 意味 |
---|---|---|
301 Moved Permanently | そのまま | コンテンツが永久的に移動した |
302 Found | そのまま | コンテンツの一時的な移動 |
303 See Other | GETに変更 | コンテンツの一時的な移動 |
HTTP/1.1 においては、302で redirect してしまうと、フォームのデータを再度送信されてしまい永久にログインし続ける恐れがあります。クライアントの実装がマチマチですので難しいところですが、HTTP/1.1対応を名乗るクライアントはこのように実装されているハズです。
303 See Otherを無視するIIS
論より証拠、Microsoft-IIS/5.0 の実装を確認してみましょう。HTTP/1.0クライアント。
[console~]$ telnet 192.168.1.1 80
POST /adiary-1.30/adiary.cgi HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
action=login&id=test&pass=test
--------------------------------------------------
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 30 Apr 2007 06:17:02 GMT
Set-Cookie: session=%00%02%00sid%00aJXNNh2FJGd3%00id%00test; path=/adiary-1.30/;
Content-Type: text/html; charset=EUC-JP;
Locationヘッダは出ていません。これは正しい実装です。続いて HTTP/1.1 の接続です。この場合 SERVER_PROTOCOL=HTTP/1.1 が設定されていますので、cgiは303 See Other を返します。
[console~]$ telnet 192.168.1.1 80
POST /adiary-1.30/adiary.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Host: 192.168.1.1
action=login&id=test&pass=test
--------------------------------------------------
HTTP/1.1 100 Continue
Server: Microsoft-IIS/5.0
Date: Mon, 30 Apr 2007 06:18:47 GMT
HTTP/1.1 302 Object Moved
Location: http://192.168.17.104/adiary-1.30/adiary.cgi/nabe?login_auth
Server: Microsoft-IIS/5.0
Content-Type: text/html
Connection: close
Content-Length: 183
- 303 をなぜか 302 に書き換えている。
- Locationヘッダはきちんと出力されている。
- Set-Cookieヘッダを出力しない(cgi側ではきちんと出力している)
解決策は?
Microsoft-IIS で cgi 動作させるときは、SERVER_PROTOCOL を HTTP/1.0 と見せかけるしかないでしょう。*2
ほんとIEといい、IISといい、まったくデタラメなソフトばかりです。AN HTTPDの方が幸せなになるんじゃないかなぁ。perlis.dll でも普通に ImageMagick 使えましたし。
- [BUG] Location ヘッダーと組み合わせると CGI で Set-Cookie ヘッダーが無視される(Microsoft)
- RFC2616(日本語訳)
2006/08/23(水)CSSXSS
CSSの中でjavascript実行しちゃったりする問題
CSSXSSというのがあるんですが、もちろんIE専用。要するに「罠サイトからスタイルシートに見せかけることで好きなサイトのデータを(クライアントに)取得させ、それをJavaScriptで処理することで罠サイト側に情報漏洩出来る」セキュリティホールです。さすがIEやることが違う!
というわけなんですが、CSSXSSさせない(加害者にならない)対策は取れても、CSSXSSされない(被害者にならない)対策は取りようがない。漏洩したことろでセッションは盗めないのですが(でも機密とかダダ漏れ)、CSSXSS+CSRFされるともはや手の打ちようがない。
世の中のIE使ってる人、危険極まりないですよこれは(汗
2006/05/29(月)POSTしたものを保存させると……
Content-Type技、色々
データをブラウザで開くのではなく、直接ファイルに保存させたい場合は、
Content-Type: application/xxx-data
という風にしておけば、大抵は未登録のコンテントタイプなので保存指示ウィンドウが出るようになります。
しかし、POST時にこの方法を用いてファイルを保存させようとすると、ファイル名選択ダイアログが出てきません(Firefoxにて確認)。各種blogツールにおいて、エクスポート時のメソッドがなぜGETなのか今まで疑問だったのですが、ようやく謎が解けました。
教訓:POSTメソッド発行時にファイルに保存させようとしてはいけない
おまけ
cgiのファイルなどを実行させずに、そのままのファイル名でブラウザで表示させたい場合は
AddType text/plain .cgi RemoveHandler .cgi
というふうにします(Apache2用)。http://adiary.abk.nu/tools/tdiary2adiary.cgiで使ってる技です。
追記。mod_perlの影響か、RemoveHandler がうまく働かなくなってましたので、次のようにしました。
AddType text/plain .cgi AddHandler default-handler .cgi
Content-Typeを変更せずにファイルとして保存させる
cgiの場合、もっとスマートにファイルとして保存させる方法を発見しました。
Content-Disposition: attachment; filename=export_file_name.txt Content-Type: text/plain; charset=(文字コード);
とすれば良いようです。
2006/05/25(木)タグの認識を正規表現で
一昔前の掲示板などで利用出来るタグを制限できるものがありますが、それと同等のモジュール(自作)をadiaryでは使用しています。属性値を認めると途端にタグの解析が難しくなります。
#!/usr/local/bin/perl use strict; my $inp = join('', <>); while($inp =~ /(<\w([^>"']|[=\s\n]".*?"|[=\s\n]'.*?')*?>)/s) { $inp = $'; print "$1\n"; }
だいたいこれでタグを認識できますが、IE/Firefox/Operaなどで色々確認した結果、壊れた(不正な形式の)htmlタグの解釈は統一されておらず、結局、その辺の仕様まで完全にサポートすることは非常に難しく、そこをつつけば、この手のタグ制限ルーチンの解析を避けて、ブラウザに対し不正なタグを認識させる(XSSする)ことが可能なようです。