フタなしカンヅメ

徒然なるままに @happytar0

MT4iのDoS攻撃されるセキュリティホール

MT4iのベータ版である3.1系をのぞく全てのバージョンで、DoS攻撃されうるセキュリティーホールがあるようです。細工されたリクエストを送信するか、もしくは特定の状況下で無限ループしてしまう事が原因で、HTTPプロセスが常駐し続けてしまいます。

原因

記事本文を分割する際に利用する midb_euc 関数の欠陥が原因のようです。分割位置をGETパラメータとして渡すため、「記事本文の文字数より大きい値が渡された時」に分割が終了せずに無限ループに陥ります。なお、記事本文を分割するバイト数として、初期値では「4096」バイトが設定されています。

以下のように sprtbyte に分割位置がカンマ区切りで渡されます。

http://www.example.com/m/mt4i.cgi?mode=individual&eid=1&sprtpage=1&sprtbyte=0,4096

どういう状況でおこりうるか

MT4iはMovableTypeを携帯で見やすいように変換・表示するためのプログラムです。携帯向けに表示するため、記事本文の表示を分割する必要があります。記事本文が分割バイト数ずつ分割表示されるわけですが、それぞれの分割された記事の位置をGETパラメータで制御しているため、後から記事本文を更新した場合に分割位置が一致せずに、無限ループする可能性があります。
クローラーなどが更新前のURLでアクセスしてきて…なんて事もあるかもしれません。

再現方法

sprtbyte パラメータに記事本文より大きい値を渡すことで再現できます。

問題の箇所

midb_euc 関数のコードを抜き出したものです。2系、3.0系ともに同様の関数が使われているようです。
記事本文より大きい開始位置が指定された場合、43行目の while 文内の substr 関数が常に開始位置より小さい値を返すために無限ループし、さらに68行目の while 文内の substr 関数で、開始位置と終了位置が記事本文より大きい場合、常に空文字を返すので無限ループしてしまいます。

sub midb_euc {
    my $llen1;
    my $llen2;
    my $lstr;
    my $lstart;

    # 先ず正しい開始位置を求めないと
    if ($_[1] == 0) {
        $lstart = 0;
    } else {
        $llen1 = $_[1];
        $lstr = substr($_[0], 0, $llen1);
        $llen2 = MT4i::Func::lenb_euc($lstr);
        my $llen3 = $llen1;
        while ($_[1] > $llen2) {
            $llen3 = $llen1;
            $llen3 += $lstr=~s/(\x8E[\xA1-\xDF])/$1/g;                   # 半角カナ数をプラス
            $llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
            $lstr = substr($_[0], 0, $llen3);
            $llen2 = MT4i::Func::lenb_euc($lstr);
        }
        $llen1 = $llen3;

        # 最後の文字が途切れているか判定する
        if ($lstr =~ /\x8F$/ || $lstr =~ tr/\x8E\xA1-\xFE// % 2) {
            chop $lstr;
            $llen1--;
            if($lstr =~ /\x8F$/){
                $llen1--;
            }
        }
        $lstart = $llen1;
    }

    # 文字列の切り出し
    $llen1 = $_[2];
    $lstr = substr($_[0], $lstart, $llen1);
    $llen2 = MT4i::Func::lenb_euc($lstr);
    my $llen3;
    while ($_[2] > $llen2) {
        $llen3 = $llen1;
        $llen3 += $lstr=~s/(\x8E[\xA1-\xDF])/$1/g;                   # 半角カナ数をプラス
        $llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
        $lstr = substr($_[0], $lstart, $llen3);
        $llen2 = MT4i::Func::lenb_euc($lstr);
    }
    $llen1 = $llen3;

    # 最後の文字が途切れているか判定する
    if ($lstr =~ /\x8F$/ || $lstr =~ tr/\x8E\xA1-\xFE// % 2) {
        chop $lstr;
        if($lstr =~ /\x8F$/){
            chop $lstr;
        }
    }
    return $lstr;
}

対応方法

3.1系では問題となっている関数が置き換わっているため、3.1系を利用することで回避できるようですが、まだベータ版なのでそのあたりも考慮する必要がありそうです。

簡単な Func.pl に対するパッチを作ってみました。各 while 文で記事本文より大きい場合と、空文字の場合にループを抜ける処理を追加しただけです。

※2月5日訂正
while 文でループする意味がよく分からなかったので、一度で抜けるように変更しました。

--- Func.pl.org	2011-02-03 16:43:57.000000000 +0900
+++ Func.pl	2011-02-03 16:19:58.000000000 +0900
@@ -46,6 +46,7 @@
             $llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
             $lstr = substr($_[0], 0, $llen3);
             $llen2 = MT4i::Func::lenb_euc($lstr);
+            last;
         }
         $llen1 = $llen3;
 
@@ -71,6 +72,7 @@
         $llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
         $lstr = substr($_[0], $lstart, $llen3);
         $llen2 = MT4i::Func::lenb_euc($lstr);
+        last;
     }
     $llen1 = $llen3;

実はそんなにたいした事じゃないのかも

共有サーバのほとんどは、長時間実行されているプロセスは強制終了されるものかと思われますので、それほど影響はないのかもしれません。


デタラメ書いてるんじゃねーってことがあれば連絡ください。