いつの間にか MacPorts で R13B が提供されていたので、port upgrade erlang してみた。すると X を含む大量のパッケージのアップデートが始まってしまい、予想外の大仕事になってしまった。
途中、tiffパッケージのアップグレードで次のようなエラーが出た。
---> Extracting tiff
Error: Target org.macports.extract returned: can't read "macosx_version": no such variable
Error: Status 1 encountered during processing.
検索してもズバリな情報は出てこなかったのだが、お手入れ tiff 3.8.2の場合 – 逃走航路@hatena を参考に XCode のバージョンを 3.1.2 に上げたら解消された。
Ubuntu で散々苦労した wxWidgets もあっさりインストールされた。素晴らしい…。またこのバージョンからは標準で smp オプションが入るようだ。
erl を起動して Unicode 対応をチェック。日本語を入力してダイレクトに Unicode コードポイントが返される様はちょっと感動的だ。
1> "日本語".
[26085,26412,35486]
io:format 等で Unicode 文字列を表示する場合は、修飾子の前に t を付ければうまく処理される。
4> io:format("~s~n", ["日本語"]).
** exception exit: {badarg,[{io,format,[<0.26.0>,"~s~n",[[26085,26412,35486]]]},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}
in function io:o_request/3
5> io:format("~ts~n", ["日本語"]).
日本語
ok
久しぶりにまた Erlang で遊ぼうかなあ。
mod_dosdetectorの改造は、先日の共有メモリ処理の改善をもって一段落。今後は実際に運用しながらバグを取りつつ、ドキュメントでも書きつつ、またオリジナルの mod_dosdetector に対するフィードバックも考えていこうと思う。
やり残したこと。
ディレクティブ有効範囲の矛盾の解消
mod_dosdetector はクライアント情報を管理するのに共有メモリを使用している。共有メモリ・セグメントはサーバ中に唯一つしか存在しないので、DoS判定の閾値や継続時間などの設定値も当然サーバ中で唯一になっているべきである。ところが実際には、ディレクティブはどこにでも(.htaccessにでも)何度でも書くことができる。
この矛盾した状況を解消するには、設定は全て httpd.conf のグローバルスコープにしか書けないように変更すれば良い。しかし「設定は一箇所にまとめておいてね!」という紳士規定によって得られる「どこにでも設定を書くことができる」というメリットを捨ててまで、そうする価値があるかと言われると…よく分からない。
Apache 2.0 対応
参考:mod_dosdetector を Apache 2.0 系で動かすパッチ
こちらは技術的には特に難しくない。しかしきちんとした配布物としてまとめようとすると、途端に面倒くさくなる。というのも、2.0 に足りていない一部の機能を 2.2 のソースコードからバックポートしてくる必要があるために、ライセンス/ファイル構成/アーキテクチャ判定の面で一気に複雑になってしまうからだ。機能の本質とは関係のない部分で、そういった複雑さを持ち込むのは、やはり気が進まない。
(この点、mod_fcgid は Apache License に違反しているのではないだろうか…)
これらやり残したことは、いつか良いアイデアが浮かんだら、取り組むつもり。
mod_dosdetector 改造版。共有メモリの初期化処理を改善した。かなり試行錯誤を繰り返すことになったが、自分なりに理解をした上で修正できたと思う。
共有メモリ機構においては、セグメントの「名前」が共有の鍵となる。名前の衝突にはくれぐれも注意しなければならない。
- 新たに共有メモリ・セグメントを作成する際、既にその名前が使用されていると、作成に失敗する
- 同じ名前を指定して attach すれば、どんなプロセスでも共有に参加できる。異なるソフトウェアに属する異なるプロセスが、異なる意図を持って同じメモリにアクセスすれば、メモリ内容は破壊されプロセスはクラッシュする
名前の衝突を防ぐための一つの工夫として、共有メモリ・セグメントの名前を”捨てて”しまう方法がある。名前を捨てることで、新たに別のプロセスが共有に参加することはできなくなる(既に共有に参加しているプロセスからは変わらずアクセスできる)。また名前を捨てた後なら、新たに同じ名前で共有メモリを作ることができるようになる。
APR では apr_shm_remove を使うことで名前を捨てることができる。上に挙げた2つの問題は、それぞれ次のようにして回避できる:
- apr_shm_create を実行する前に、その名前で apr_shm_remove を実行する
- 共有メモリの作成と初期化が終わった時点で apr_shm_remove を実行する
(もちろん厳密に言えば remove と create の間で他のプロセスが create する可能性とか、remove を実行する前にプロセスがクラッシュする可能性とかも考えられるわけだが、大部分のトラブルは回避できるはずである)。
mod_dosdetector ではバージョン0.2からこれらの対策が施されたのだが、(多分古いコードの名残で?)remove/create を実行する前に、まず既存の共有メモリ・セグメントに attach を試みるコードが入っていた。これだと偶然に同じ名前のセグメントが存在した場合にクラッシュする危険性がある。
多分もともとは、Apache が不正終了するなどして共有メモリが破棄されなかった → 再起動の際の apr_shm_create が名前の衝突でコケる、という事態に備えてこのような手順を導入したのではないかと思う。しかし対策1と2によって名前衝突の危険性はほとんどなくなったはずなので、この処理はもう役目を終えたのではないか、と考える。
参考:
- 共有メモリをAPRで使用するには
- コンパクトに必要な情報がまとめられている
- apr_shm_attach() and APR_EEXIST
- apr_shm_remove が追加されるに至った議論
tkyk’s mod_dosdetector-fork at master – GitHub
オリジナルに対して最小限の変更のみ加えたバージョンはcompat-2タグ
まずバグ修正。前回エントリ時点でのmasterブランチおよびcompat-1タグには実にしょぼいバグがあった。「フラグの逆転」という初歩的なミスによって、DoSIgnoreContentType の効果が逆転する状態になっていた……。C言語に不慣れなのがこういう形で露呈するとは。どうやら脳内で ! 演算子を論理 false のチェックだと見なしてしまうらしい。C言語の場合、! はあくまでも「ゼロかどうかを調べる」演算子だと肝に銘じておこう。
その後masterブランチでは、クライアントの管理処理を全部クリティカルセクションに押し込む、という修正を行った。それなりに大きな規模の修正になった。これで共有メモリに対する更新処理は全てロックの中に収まったはず。動作は全く変わっていないはず。「DoS攻撃の検出」と「検出結果に応じた処理」が綺麗に分離されて、コードの見通しはよくなったはず、である。
mod_dosdetector の改造版をgithubで公開した。元のライセンスに従ってMITライセンスとなっている。
今のところ、最大の違いは次の2点である:
- 無視するアクセスを環境変数「NoCheckDoS」で指定できるようになった
- DoSIgnoreContentTypeが指定されていない場合、サブリクエストの生成を行わないようにした
例えば画像/js/cssファイルに対するアクセスを無視するには、次のように SetEnvIf で指定する。DoSIgnoreContentType は指定しなくて良い。
SetEnvIf Request_URI "\.(gif|jpe?g|ico|js|css|png)$" NoCheckDoS
これで各アクセスごとのサブリクエストが行われなくなるので、パフォーマンスも改善されるはず。abを使った単純なテストでは、20%程度は効果があるようだ(並列20、合計10000アクセスで実験)。
Requests per second: 1638.03 [#/sec] (mean)
Time per request: 12.210 [ms] (mean)
Time per request: 0.610 [ms] (mean, across all concurrent requests)
Requests per second: 2058.26 [#/sec] (mean)
Time per request: 9.717 [ms] (mean)
Time per request: 0.486 [ms] (mean, across all concurrent requests)
もし上記の変更点「だけ」が欲しいという方は、compat-1タグ compat-2 タグをチェックアウトしてほしい。今後masterブランチでは(既に取り掛かっているものも含め)
- 共有メモリ処理と排他処理の整理
- 設定ディレクティブまわりの整理
- Apache 2.0系への対応
- 諸々のコードのクリーンアップ
といった課題にチャレンジしていくつもりである。
とあるソフトウェア製品での出来事。
あるバグフィックス・アップデートを適用して以来、それまでは問題なかった入力値でValidation Errorが出るようになった。丸一日調べ回った結果、以前は
^[a-z0-9_\-][a-z0-9_\.\-]*$
だった入力チェックの正規表現が、
^[a-z0-9_\-][a-z0-9_\.\-]*[^x][^y][^z]$
に変わったため、と判明した(xyzは架空)。どうやら「末尾がxyzで終わってはいけない」という条件を追加したかったようだが、この正規表現は完全に誤りだといえる。これだと最低でも4文字以上ないとマッチしないし、’boxes’のような正当なはずの文字列にもマッチしないし、逆に’a+:^’のような不正であるべき文字列にマッチしてしまう。
では「末尾がxyzで終わってはいけない」を表す正しい正規表現とはどんなものだろうか。戻り読みが使える環境なら、否定戻り読みを使うのが簡単だと思う。
irb> my_answer = /^[-a-z0-9_][-a-z0-9_.]*(?<!xyz)$/
=> /^[-a-z0-9_][-a-z0-9_.]*(?<!xyz)$/
irb> my_answer =~ "a_xyz"
=> nil
irb> my_answer =~ "xyz"
=> nil
irb> my_answer =~ "a"
=> 0
irb> my_answer =~ "abcdef"
=> 0
irb> my_answer =~ "abc_xyz_def"
=> 0
戻り読みが使えないとなると……ちょっと思いつかない。条件式を2つに分けるしかないだろうか。
ひっくり返して「xyzで始まってはいけない」にすると、否定先読みを使うことになる。例えば xyz で始まらない単語文字列なら
^(?!xyz)\w+$
さらに厳しくして「xyzではない」にするとどうだろう。…これは先読み/戻り読みだけでは無理かな?長さの条件も加えて
^\w{4,}$|^(?!xyz)\w+$
といったところか。まあいずれにせよ、実際に使う機会は稀だろうが。
ちなみに件の製品は戻り読みが使えない glibc の regex を使っていたので、諦めてアップデート前の形に戻した。
先日 mod_dosdetector について調べて以来、Apache モジュールについて俄然興味が湧いてきた。もともとは mod_dosdetector の正確な動作を確かめたかっただけなのだが、処理の流れを追うために Apache のソースコードにまで手を出して読みふけっているうちに、夢中になってしまった。普段から馴染み深いソフトウェアだけに、動作の裏側が分かってくるとすごく面白い。勢い、この分野で定本と言われる『The Apache Modules Book』も買ってしまった。
とりあえず mod_dosdetector で気になっているのは
- 無視するアクセスを Content-Type で指定するのは適当か?Content-Type を取得するにはサブリクエストの生成を行う必要があるので、オーバーヘッドが大きい。URLを対象にした方が良いのではないか?
- あるいは、無視するアクセスの選別自体は mod_setenvif などに任せて、mod_dosdetecotr 側では環境変数をチェックすれば済むのではないか?
- 共有メモリに対してロックの外で更新操作を行っている箇所があるが、問題は無いのか?
C言語の勉強を兼ねてこれらの改造にチャレンジしてみよう。
『サーバ/インフラを支える技術』で知ったDoS攻撃判定モジュール mod_dosdetector の使い方。mod_dosdetecotr のバージョンは 0.2、環境は Apache 2.2.3 @ CentOS 5。
※改造版も公開しています。
インストール
wget http://ncu.dl.sourceforge.net/sourceforge/moddosdetector/mod_dosdetector-0.2.tar.gz
tar xvzf mod_dosdetector-0.2.tar.gz
cd mod_dosdetector-0.2
make
sudo make install
httpd.conf 設定例
# 実際にはこの一行は make install で追加される
LoadModule dosdetector_module modules/mod_dosdetector.so
DoSDetection on
DoSPeriod 5
DoSThreshold 10
DoSHardThreshold 25
DoSBanPeriod 30
DoSTableSize 100
DoSIgnoreContentType image|javascript|css
DoS判定の肝となる数値は DoSPeriod, DoSThreshold, DoSHardThreshold, DoSBanPeriod の4つで、上の例ではだいたい次のような意味になる:
- 同一IPアドレスから5秒間に10回以上のアクセスがあった場合「DoS攻撃の疑いあり」と見なし、その後30秒間のアクセスに対しては環境変数 SuspectDoS をセットする(値は”1″)
- さらに5秒間のアクセス回数が25回に達した場合、「激しいDoS攻撃の疑い」と見なし、環境変数 SuspectHardDoS もセットする(値は”1″)
- 初めに「DoS攻撃の疑いあり」と判定してから30秒が経過したら、次のアクセスでもう一度判定をやり直す。直近5秒のアクセスが10回を下回っていれば、疑いは晴れる
アクセス数の計測はファイルの種類・有無を問わずに行われるため、デフォルトではページ内の画像やCSSなども計測対象となってしまう。それらを除外したい場合は、DoSIgnoreContentType ディレクティブで除外したいファイルの MIME type を正規表現で指定しておく(ファイル名(URL)ではないので注意)。各種画像ファイル・JavaScript・CSSファイルを除外したい場合は上例のように設定しておけば良い。
ただし多くの環境では拡張子 ico に対する MIME type の指定は行われていないはずなので、そのままだと favicon.ico が text/plain 等と見なされて除外されない(.ico に限らず、未設定の場合は httpd.conf の DefaultType になる)。次のように AddType を追加しておけば image と判定される。
AddType image/vnd.microsoft.icon .ico
ちなみにこれがアイコンファイルの正式な MIME type である。
DoS攻撃に対する防御設定
mod_dosdetector が行うのはあくまでも攻撃の”検出”だけなので、防御する方法はまた別に設定する必要がある。多くの場合は mod_rewrite と併用することになるだろう。例えば「激しいDoS攻撃の疑い」なアクセスに対してステータスコード503 + 静的なHTMLを返す場合は次のように設定する(Apache 2.2 only)。
RewriteEngine On
RewriteCond %{ENV:SuspectHardDoS} =1
RewriteRule .* - [R=503,L]
ErrorDocument 503 /server_is_busy.html
しかし現実には、攻撃の判定とその後の防御は、匙加減が難しい。どの程度のアクセスを攻撃と見なすかはサイトによって千差万別だろうし、あまりに頻繁・過剰な反応をしてしまうとユーザが離れてしまう。
そこで個人的には、まず厳し目の設定を行って、ログを取ってみることをおすすめする。「DoS攻撃の疑いあり」なアクセスを普通のログと分けて記録するには次のように設定する。
LogFormat "%{SuspectHardDoS}e %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" dos_suspect
CustomLog logs/dos_suspect_log dos_suspect env=SuspectDoS
ログの行頭に環境変数 SuspectHardDoS が記録されるため、どのような経過をたどって「疑いあり」から「激しい攻撃の疑い」へと移行したかも分かる。
後はこの記録を見つつ、通常のアクセスが引っかからないような適当な設定を探ることになる。もしサーバリソースの都合上、どうしても通常のアクセスが引っかてしまう可能性があるのなら、その時は防御方法をより穏当なものに設定することもできる(このあたりの柔軟性は mod_dosdetector の設計の妙だと思う)。
その他
上で触れなかったディレクティブについて少々:
- DoSDetection
- DoS判定機能の有効・無効を設定。on で有効
- DoSTableSize
- クライアントの追跡記録の最大保存数。この数値を大きくすればより多くのクライアントを同時に追跡できるが、メモリ使用量と負荷が上昇する
- DoSShmemName
- クライアントの追跡記録を保存しておくための共有メモリの名前。普通は設定する必要は無いはず
最後に
素晴らしいソフトウェアをオープンソースとして公開してくださった田中慎司さん(そして株式会社はてな)に感謝!
昔Apache 2.0系で試したときは無理だったのに、2.2系ではできるようになっていたのか。
mod_rewriteで503 – Do You PHP はてな
上エントリーで引用されているドキュメントは古くて、現在は次のような記述に置き換わっている。
While this is typically used for redirects, any valid status code can be given here. If the status code is outside the redirect range (300-399), then the Substitution string is dropped and rewriting is stopped as if the L flag was used.
mod_rewrite – Apache HTTP Server
『このフラグは通常リダイレクトのために用いられるが、任意の妥当なステータスコードを指定することができる。もしリダイレクト以外のステータスコード(すなわち300〜399以外)が指定された場合、置換文字列は無視され、さらにLフラグが指定されたときと同じようにURLの書き換えが停止される。』
つまりリダイレクトもURLの書き換えも行われないので、デフォルトのエラー画面以外を表示するには ErrorDocument ディレクティブで指定しておく必要がある。
RewriteEngine On
RewriteCond %{some_condition} =1
RewriteRule .* - [R=503]
ErrorDocument 503 /server_is_busy.html
ソースに付属している文書(CHANGES)によれば、この仕様に変わったのはバージョン2.1.1。Bugzillaで以下のような議論も見つけた。
In the <300, >=400 cases, one can think of this as [R{esponse}=nnn} as opposed to [R{edirect}], and it really harms nothing to overload it.
Bug 45478 – R flag in RewriteRule does not honor 400 and 500 range error code
Redirect の ‘R’ ではなく Response の ‘R’ と見なしてくれ、と。なるほど…。
Authenticated encryption:機密性と完全性を同時に守るための暗号システム。対称暗号とMACとの組み合わせで実現するか、もしくは専用に設計された対称ブロック暗号のモードを使用する。最近になって活発に研究されている分野らしい。
そのような対称ブロック暗号のモードは Authenticated Encryption Modes といった名称で分類され、中でもNISTが標準化しているのは CCM と GCM モード。CCM モードは IEEE 802.11i 規格の一部として実用化されている。また他にNISTに提案されているものとしては CWC, EAX, OCB モードなどがある。
一般に使用可能な実装としては、Brian Gladman氏のサイトで CCM, GCM, EAX, CWC モードの実装がBSDライクなライセンスで公開されている。またパブリックドメインの暗号化ライブラリ libTomCrypt(工事中の新サイト) では EAX, OCB, CCM, GCM モードが利用できる。
『C/C++セキュアプログラミングクックブック〈VOLUME2〉対称鍵暗号の実装』でイチ押しされているのは CWC モードだが、世間的にはNIST標準である CCM モードや GCM モードの方が広く使われているようだ。
比較的新しい分野のためか、WEB上には日本語の情報は少ない(探し方が悪いのかも)。唯一CRYPTREC が2003年に公開した報告書(pdf)『ブロック暗号を使った秘匿、メッセージ認証、及び認証暗号を目的とした利用モードの技術調査報告』の中で「認証暗号に関するモード」として安全性や効率について詳しい検討を行っているが、ちょっと専門的過ぎる。一方でWikipedia英語版の充実振りは素晴らしい。