サービスも人も月日と共に老いていくのは自然の摂理です。人の場合は人間ドックがあったり健康保険があったり会社休めたりと割りとメンテナンスは充実してますが、サービスの場合はどうでしょう?24時間365日フル稼働で、しかも人気コンテンツだったり業務に欠かせないイントラだったりすると、おいそれと止める事が出来ず、放置してご老体になるケースが多いかと思います。サーバーの減価償却が終わった途端にDiskがやられて泣きを見た方は多いのではないでしょうか。また、使っているミドルウェアのサポートが終了し、セキュリティホールが晒される可能性も増えていきます。高品質のサービスを運営していくには、
・常に最新のセキュリティホール動向をチェック
・こまめにOS/ミドルウェアのバージョンアップを行う
・サーバーの耐障害性を高める(クラウド化または冗長化)
・定期的にセキュリティ診断を行う
というのを常に意識しなければいけません。
OS/ミドルウェアのバージョンUPをしよう! → 出来ねえ
といいつつも会社という組織に属していると中々そう簡単に出来ないのが現状です。ものすごく売上のあるサービスなら別ですが、中途半端に売れてるけどそんなになあ、という「なあなあ」サービスの場合、ほぼ放置の運命にあるのでは無いでしょうか。放置される原因は様々ですが、上記の売上もさりながら、他にも
・やる人がいない
・担当者がやめてて充分な引き継ぎがされていない
・お金が無い
・やっても見返りが少ない
・やる気が出ない
・平社員で権限がない
・既にサービスを閉じたと勘違いしている
など、様々な阻害要因がございます。あと、もう事業としては別に閉じてもいいんだけどお客様で熱狂的なファンがいらっしゃると中々閉じられないのが人情というもので、放置の悪循環が進行します。
放置駄目!ゼッタイ!
結論から言うと、どんなに売れてないサービスでも、それをお客様に提供している以上、放置する事はいけません。理由は前述しましたが、セキュリティホール増大のリスクです。映画や漫画でも、古くなって誰も顧みない箇所からハッキングされるのが定番ですね。また昨今のアタックの流行など、ネットは常に脅威にさらされています。至極当然の事ですが、商品の品質管理を蔑ろにすると、後で手痛いツケが返ってくるのです。
予算と現実の間で
とは言っても金は出ないし人も居ない。とりあえずバージョンアップだけしたらアプリケーションが動かなくなった。どうしたらいいのでしょうか。最低限、セキュリティホールだけは潰しましょう。
・常に最新のセキュリティホール動向をチェック
・定期的にセキュリティ診断を行う
上記のこの2項目ですね。では実際にセキュリティホールが見つかった場合どう対処するか具体例を見てみましょう。
DWR
DWR(Direct Web Remoting)は、Ajaxをお手軽に実装出来るJavaのライブラリです。クロスドメイン対応やサーバーとの連携が簡単に出来ます。弊社のとあるサービスでバージョン1.1(かなり古い)を使用してたところ、以下のクロスサイドスクリプティングの脆弱性が発生していました。
リクエスト時のとあるパラメーター
...
c0-id=1499_1404795522775
...
通常のレスポンス時
...
DWREngine._handleResponse('1499_1404795522775',s0);
...
プロキシツールで以下の様にいじると
...
c0-id=1499_1404795522775',s0);alert('test')//
...
レスポンスがこうなる
...
DWREngine._handleResponse('1499_1404795522775',s0);alert('test')//',s0);
...
上記のレスポンスはJSとして動作する事を前提としている為、サービス上でXSSが実行されてしまいます。また、ライブラリそれ自体が制御している箇所なので、当方のアプリケーション側からエスケープ処理を行う事が出来ませんでした。(方法はあるかもしれませんが)
調べたところ、バージョン2.xからXSS対応したとの事なので、バージョンアップしたところ、今度は互換性の問題でサービスが動かなくなってしまいました。
サービスでDWRを使っている箇所は広範囲に及び、全体を改修するのは容易ではありません。そこでDWR自体がオープンソースなのをいいことに、バージョン1.1自体の改修を試みました。
まずはソースのダウンロードです。幸い古いバージョンも残っていました。
$> mkdir dwr-1.1
$> cd dwr-1.1
$> wget https://java.net/downloads/dwr/Version%201.1/dwr-1.1-src.zip
$> unzip dwr-1.1-src.zip
次に出力箇所の特定です。uk.ltd.getahead.dwr.impl.DefaultExecProcessorクラスにそれらしき記述が見られます。
79 Call call = calls.getCall(i);
80 if (call.getThrowable() != null)
81 {
82 OutboundVariable ov = call.getThrowable();
83
84 buffer.append(ov.getInitCode());
85 buffer.append('\n');
86 buffer.append(prefix);
87 buffer.append("DWREngine._handleServerError('"); //$NON-NLS-1$
88 buffer.append(call.getId());
89 buffer.append("', "); //$NON-NLS-1$
90 buffer.append(ov.getAssignCode());
91 buffer.append(");\n"); //$NON-NLS-1$
92 }
93 else
94 {
95 OutboundVariable ov = call.getReply();
96
97 buffer.append(ov.getInitCode());
98 buffer.append('\n');
99 buffer.append(prefix);
100 buffer.append("DWREngine._handleResponse('"); //$NON-NLS-1$
101 buffer.append(call.getId());
102 buffer.append("', "); //$NON-NLS-1$
103 buffer.append(ov.getAssignCode());
104 buffer.append(");\n"); //$NON-NLS-1$
105 }
ここでエスケープ処理をしても良いのですが、影響範囲が不明なので、元のcall.getIdを修正します。call.getIdはuk.ltd.getahead.dwr.CallのgetIdメソッドです。
33 /**
34 * @return Returns the id.
35 */
36 public String getId()
37 {
38 return id;
39 }
idに使われる文字列は半角数値とアンダーバーのみっぽいので、思い切ってそれ以外の文字列は削除するよう、修正します。
33 /**
34 * @return Returns the id.
35 */
36 public String getId()
37 {
38 if (id != null) {
39 id = id.replaceAll("[^0-9_]+", "");
40 }
41
42 return id;
43 }
あとはbuild → jarにしてサービスに投入します。build.xmlがあるのでantで簡単に出来ます。解凍ディレクトリのtarget/ant直下にjarが生成されます。
$> ant clean
$> ant compile
$> ant jar
以上で対応完了です。半角数値とアンダーバーのみ許可しているのでXSSは発生しなくなりました。要した時間は調査も含め、おおよそ2~3時間です。アプリケーション側の改修なら軽く5人日は吹っ飛んでたでしょう。
まとめ
まとめというほどではありませんが、予算も時間も無かったら、脆弱性が発覚した場合、サービスに影響が無い最短のコースを探すのがベストでしょう。(当たり前ですが)
その為にはオープンソースをいじるのもやむ無しです。ただし、後で絶対忘れますのでオープンソースをいじったらメンバーに必ず共有しましょう。繰り返しになりますが、
・常に最新のセキュリティホール動向をチェック
・定期的にセキュリティ診断を行う
この2点は必ず遵守しましょう。忘れがちなサービスにも愛情を。