配布型掲示板に潜むバグや脆弱性を実際のコードを使って解説する (修正情報あり)
投稿日: 最終更新日:2017/01/15 投稿者:nk.
色々とWEBサービスの検証記事を書いてきましたが、脆弱性があるのはそういったサービスだけではありません。
一昔前はCGIやPHP等のスクリプト自体を配布するというものが主流だったように思います。
今でもそういった配布サイトは残っていますね。
それらにも脆弱性が含まれる可能性があるという記事です。
目次
基礎編 – 入力値の検査
そもそも検査していない – レインボーNote v.1.51
Falcon World – CGI スクリプトで配布されている掲示板(レインボーNote)を例にとります。
こちらの掲示板、見た目はすごくよいというか在りし日のインターネッツを思い起こさせるような哀愁漂う感じでとても好きなのですが、残念な点があります。
一部の入力値にHTMLタグが含まれていないかの検査がされていません。
$FORM{'name'} =~ s/</</g; $FORM{'name'} =~ s/>/>/g; $FORM{'name'} =~ s/'/'/g;$FORM{'name'} =~ s/"/"/g; $FORM{'email'} =~ s/</</g; $FORM{'email'} =~ s/>/>/g; $FORM{'email'} =~ s/'/'/g;$FORM{'email'} =~ s/"/"/g; $FORM{'url'} =~ s/</</g; $FORM{'url'} =~ s/>/>/g;$FORM{'url'} =~ s/'/'/g;$FORM{'url'} =~ s/"/"/g;
検査されているのはこれらの項目だけなので、それ以外の項目にHTMLタグを埋め込むと簡単にXSSが可能となります。
この脆弱性をつぶすには、前述の検査をすべての項目に施す必要があります。
ただしこの掲示板の脆弱性をつぶすにはそれだけでは完璧ではありません。次の例を見てみましょう。
2016年02月21日 追記
配布サイトの方から連絡があり、順次修正していくとのことです。
2016年02月29日 追記
v.1.62(脆弱性修正済)が公開されています。
スポンサーリンク
検査の仕方が甘い – Land BBS v.1.10
最近はそうでもないのかもしれませんが、一般的にselectタグやhidden項目のデータは閲覧者が改竄できないと思われがちです。
ところが最近のブラウザには標準で開発者ツールというものが付属しており、例えばChromeやfirefoxではF12キーを押すことでそのメニューが表示されます。
そして開発者ツールからダイレクトに描画されているHTMLの変更が可能になっています。
先ほどと同じFalcon World – CGI スクリプトの、Land BBSを例にとって説明します。
こちらのスクリプトではすべての入力値に対してタグの検査がされています。
foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $value =~ s/\r/\n/g; $value =~ s/\n\n/\n/g; $value =~ s/&/&/g; $value =~ s/"/"/g; $value =~ s/</</g; $value =~ s/>/>/g; $value =~ s/'/'/g; $value =~ s/\n/<br>/g; &jcode'convert(*value,'sjis'); push(@DEL,$value) if ($name eq 'num'); $FORM{$name} = $value if ($value ne ''); }
しかし、アイコン項目でこれ以上の検査がされていません。
たとえば、アイコンの入力値を次のようにしてみましょう。
landbbs.cgi?mode=regist&func=log&subj=hello&name=yeah&comment=ThisBBSisHacked&color=R
すると、閲覧者は意図せずにこの掲示板に書き込んでしまうことになります。
&が&にエスケープされていますがimgタグのsrc属性の中では&に復元されてしまうため、上記のエスケープは全く無意味です。実質的にimgタグは外部ファイルの呼び出しを行っているため、注意して検査する必要があります。
例えば、次のようなコードを追加するとよいと思われます。
$func = $FORM{'func'}; $icon = $FORM{'icon'}; grep{$_ eq $icon}@{[@icon_f, 'admin']} or &error('アイコンの指定が不正です!!'); $resno = $FORM{'resno'};
前述のレインボーNoteにも同様の脆弱性があります。
同様の処理を追加することで回避できるはずです。
2016年02月21日 追記
配布サイトの方に連絡したところ返信をいただきました。
修正するとのことです。
2016年02月29日 追記
v.1.22(脆弱性修正済)が公開されています。
そもそも検査していないその2 – Lively BBS v.1.01
検査していないシリーズが続きます。血眼になって脆弱性のある掲示板を探したわけでなくこの記事を書こうと適当に検索して出てきたスクリプトを見たら脆弱性が残ったりしているので書いている筆者も驚いています。
さて今度はCGI Scripts for Mobile – Tor WorldにあるLively BBSです。
この掲示板には画像ファイルのアップロード機能があるのですが、なぜかファイルのアップロードをすると入力値にHTMLタグが含まれているかの検査がされません。意味がわかりません。
ファイルのアップロードをするにはデータのエンコード形式をmultipart/form-dataにする必要があり、Perlでは通常のapplication/x-www-form-urlencodedで送られてきたデータと別に処理する必要があります。このあたりが原因なのでしょうか。というか公開されて6年たつのによく今まで誰も見つけなかったな。
elsif ($name) { $FORM{$name} = substr($read_data, $pos2, $size); $value=$FORM{$name}; &jcode'convert(*value,'sjis'); $value =~ s/\r\n/<br>/g; $value =~ s/\r|\n/<br>/g; $value =~ s/\t//g; if ($name eq 'cat') { push(@CATE,$value); } $FORM{$name} = $value; }
511行目あたりに次のコードを追加すればよさそうです。
elsif ($name) { $FORM{$name} = substr($read_data, $pos2, $size); $value=$FORM{$name}; &jcode'convert(*value,'sjis'); $value =~ s/\r\n/<br>/g; $value =~ s/\r|\n/<br>/g; $value =~ s/\t//g; $value =~ s/</</g; $value =~ s/>/>/g; $value =~ s/"/"/g; $value =~ s/'/'/g; if ($name eq 'cat') { push(@CATE,$value); } $FORM{$name} = $value; }
面倒なのでこれで大丈夫かのチェックはしてませんが。
あとSPAM対策なのか、本文中にURLが含まれると投稿できないようにしたいみたいですが、なぜかその検査も甘いです。
if ($spam2) { if($FORM{'comment'} =~ /(http\:\/\/[!#!-9A-\~\=\?]+)/) { &error("入力エラー","本文に「URL」を記入することはできません。"); } }
HTTPSだと通ってしまいますし、そもそもiオプションがないのでHTTP://という書き方をするだけでも通ってしまいます。
if ($spam2) { if($FORM{'comment'} =~ /(https?\:\/\/[!#!-9A-\~\=\?]+)/i) { &error("入力エラー","本文に「URL」を記入することはできません。"); } }
単純なifが入れ子になっているので変態のみなさんは次のように書きたくなるでしょうね。
$span2 and $FORM{'comment'} =~ m!https?://!i and &error("入力エラー", "本文にURLを記入することはできません。");
実はこの記事を書く一週間前にこのLivelyBBSの脆弱性を見つけたのですぐに配布者にメールで連絡したのですが、一向に返事が来る気配がありません。どうしても誰かを思い出してしまいます。誰かどうにかしてくれ。
2016年02月21日 追記
返信がありました。
早急に対応なさるとのことです。
2016年02月22日 追記
v.1.02が公開されていました。
中身は確認していませんが、すでにこの問題は解決済みだと思われます。よかった。
そして実はTorWorldのCGIにはもっとヤバい脆弱性がありました。
応用編でそのことに触れます。
応用編
ダイレクトOS コマンドインジェクション – Prosperous BBS v.1.11
Perlでファイルを開くときには注意が必要であることはみなさんご存じでしょう。
しかしファイルのオープンのときだけ気をつければよいわけでもないのです。
先ほども登場したCGI Scripts for Mobile – Tor WorldにあるProsperous BBSを見てみましょう。
TorWorldの掲示板には「匿名メール機能」というものがついています。
これは投稿者にメールを送信したいとき、サーバーが代理でメールを送信してくれるというもので、投稿者は自身のメールアドレスを掲示板上で公開しなくてもよいという便利な機能です。
が!
メールアドレスのvalidityチェックが甘い!
if ($FORM{'email'} =~ /^090/) { $error .= "「E-MAIL」の入力に不備があります。<br><br>セキュリティー上の問題から電話番号のままのメールアドレスは受け付けられません。$br"; } if ($FORM{'email'} ne "" && $FORM{'email'} !~ /(.*)\@(.*)\.(.*)/) { $error .= "「メールアドレス」の形式に誤りがあります。$br"; }
たったこれだけです。
そして、メールを送信する関数を見てみましょう。
sub sendmail { if (open (MAIL,"| $sendmail $_[0]")) {
マジカヨ。
入力値検査で>は>にエスケープされていますが、パイプを使えばそんなことお構いなしです。
メールアドレス欄を次のようにして投稿します。
hoge@example.com; cat cond.cgi | tee count.txt
そして、edit.cgi?m=sd&no=記事番号&id=noにアクセスすると匿名メールフォームが表示されます。設定で匿名メールフォームを使わないようにしていても、です。
そして匿名メールフォームで適当な値を入力して実行をすると、先ほどのコマンドが満を持して実行されてしまいます。
上記の例では、この掲示板の設定ファイルであるcond.cgiの中身が書き込み可能ファイルであるcount.txtにコピーされて閲覧可能になってしまいます。
管理パスを思う存分鑑賞した後は該当の記事を削除してしまえば証拠も(ほとんど)残りません。あな恐ろしや。
この問題を修正するには色々面倒なので、応急処置としてとりあえずsendmailが使えないようにします。
## --- 匿名メール送信 処理実行 sub sended { unless (-e $sendmail) { &error("システムエラー","sendmailのパスが不正です。"); }
とあるので、まず$sendmailを存在しないパスにしてしまいましょう。
;# sendmail $sendmail = '/unko';
次に、すでに存在している書き込みにこの攻撃がされていてはシャレにならないため、実行されないようにします。
if ($mailto eq "") { &error("入力エラー","送信先が不明です。"); } $mailto !~ /^[\da-zA-Z\._\-]+@[\da-zA-Z\._\-]+$/ and &error('入力エラー', '保存されているメールアドレスが不正です。'); if ($cntr =~ /(.+)◆(.+)/) { $cntr = $1; }
そして仕上げとして、新たな書き込みにおける対策をします。
if ($FORM{'email'} ne "" && $FORM{'email'} !~ /^[\da-zA-Z\._\-]+@[\da-zA-Z\._\-]+$/) { $error .= "「メールアドレス」の形式に誤りがあります。$br"; }
あとは好みで最初の$sendmailの設定を戻せばよいでしょう。
この脆弱性はやばすぎるので記事を公開する前に開発者に連絡していますが、果たして反応はあるのでしょうか…。
数日様子を見て返事がなければIPA経由で報告することにします。
2016年02月21日 追記
IPAは未公開の脆弱性しか受け付けてないらしいので、もういいや。
2016年02月21日 追記
前述しましたが、返信がありまして早急に対応なさるとのことです。
また、この脆弱性はTorWorldのほかの掲示板にも存在していると予想されます。
上記と同様の行を探し出して修正してください。
なお、この項目だけ記事を書いている最中に脆弱性を発見したのでテンションが高めになっていました。
載せてる順番は最後ではないんですが、書いた順番は最後です。
2016年02月21日 追記
このメールアドレスのvalidityチェックは厳密なものではなく、とりあえずコマンドインジェクションがされないためのものです。
例えば上記のチェックでは「hoge@aaaaa」でも通ってしまいますが、これはメールが送信されないだけでコマンドインジェクションは起こらず、管理上の問題がない(管理者が連絡する必要のあるサービスでもない)ため弾いていません。
厳密なvalidityチェックを望む場合はRFC 5321をご覧になってください。
2016年02月22日 追記
こちらもv.1.12が公開されました。
よかったです。
アップロードしたファイルの検査が甘い – JB掲示板 v.0.30
最後はジューベー株式会社のJB掲示板です。こちらも一言でいえば入力値の検査が甘いのが原因なのですが、ちょっとこみいっているので応用編にしました。
今までのスクリプトはすべてオープンソースでしたが、こちらの掲示板はソースコードが暗号化されています。また、リバースエンジニアリングや変更は規約で禁止されており、攻撃者が脆弱性を見つけるのも大変な一方で問題があった場合に利用者が修正するのも大変です。
RMSに見せたらどんな反応するんでしょうね。
さて、こちらの掲示板ではみなさん大好きなシリアライズデータがサーバーに送信されています。しかしリバースエンジニアリングは禁止されている(笑)のでPHPオブジェクトインジェクションは不可能です(爆笑)。
そのシリアライズされたデータなのですが、これの検証がどうやら甘いようです。
このデータはサーバー側が吐き出したものをhidden項目に埋め込んでいるので安心しているのでしょうか。ともかく、ファイル名だった箇所にレコードの区切り文字である<>を入れると見事に表示がバグってしまいました。
また、処理の流れとして投稿の確認ページが挟まれるのですが、その時点でアップロードされたファイルのリソースがサーバー上にすでに存在しています。アップロードされたファイルには一意なファイル名というかIDのようなものが割り振られており、それも確認画面のHTMLソース内に存在するシリアライズされたデータに含まれています。
ほかにも、アップロードできるファイルの種類は制限されていますが、ファイルのヘッダを見ているわけではなくただ拡張子を見ているだけです。
そして、画像ファイルをアップロードするとサムネイルが作成され、それのIDもシリアライズされたデータに含まれています。
そのうえファイルをDLさせるときに追加される拡張子もシリアライズされたデータに含まれています。
さて、ここまで確認してきましたが次のようなことをしてみたらどうなるでしょうか。
- サムネを作成させるためだけに画像ファイルをアップロードする
- 投稿確認画面より先に進まず、そのページ内にあるシリアライズされたデータからサムネイルのIDを取得する
- exeやlnkなどの悪意あるファイルの拡張子を許可されている拡張子に偽装してアップロードし、アップロードされたファイルのIDを取得する
- DL先のファイルのIDには悪意のあるファイルのIDを指定し、サムネには画像ファイルのIDを指定する。また、DL用の拡張子にはexeやlnkなどの本来のものが出るよう指定して投稿する
実際にこれらのことをしてみると、表示上はどう見てもただの画像ファイルがアップロードされているだけなのに、画像に張られているリンクをクリックすると本来の拡張子で保存が促されるようになります。
exeファイルのアイコンがサムネ用画像に差し替えられていたり、利用者がローカルで拡張子を非表示にしていたらどうなるでしょうか。怖いですね。
さて問題はわかりました。ではどうやってこの問題に立ち向かいましょうか。
規約でbbs.phpの変更は禁止されています。
実はこのJB掲示板には以前にも同様のバグがありましたがその際はこのブログのメンバーであるktm@sが修正プログラムを公開しました。
同様の手段で入力値の検査をしようとしたのですが、どうやら外部ファイルからbbs.phpを呼び出すと実行できないようにプログラムを変更したようです。
というかそもそも検証用にサーバーにこのJB掲示板を入れただけで「このURLでは使えません」といったエラーがでる始末でした。リバースProxyを導入していたのでドメインを書き換えることで実行できるようになりましたが。
開発者はホワイトハッカーに親でも殺されたんでしょうか。
そんな処理を書く暇があるなら本質的な処理を優先してもらいたいものです。
話を戻しますが、規約をよく読むと同梱されているconfig.phpを変更してはいけないとはどこにも書かれていません(2016/02/21時点)。設定用ファイルなので当然っちゃ当然ですが、ここに入力値検査用のコードを書くことにします。
以下のコードを、config.phpの最後に書き足してください。
// バグfix用クラス // ここから class FuckinRuby{ function __construct() { ob_start(); } final public function check(){ if(isset($_POST['n'])){ // 普通の投稿ではこの領域に<は含まれ得ない。無慈悲なdie。 if(strpos($_POST['n'], '<') !== FALSE){ die('不正な投稿が疑われます。'); } // アップロード可能な拡張子の指定がある場合 if(defined('JbBbsConf::FILEUP_EXT')){ $pos = 0; while(strpos($_POST['n'], 'suffix', $pos) !== FALSE){ $pos = strpos($_POST['n'], 'suffix', $pos) + 8; $sl_pos = strpos($_POST['n'], '"', $pos); if($sl_pos !== FALSE){ $sr_pos = strpos($_POST['n'], '"', $sl_pos+1); if($sr_pos !== FALSE){ $suffix_str = strtolower(substr($_POST['n'], $sl_pos+1, ($sr_pos - $sl_pos)-1)); $accept = explode(' ', strtolower(JbBbsConf::FILEUP_EXT)); $suffix; $flag = 0; foreach($accept as $suffix){ if($suffix === $suffix_str){ $flag++; break; } } if($flag === 0){ die('不正な拡張子です。'); } } } } } } } function __destruct() { $out = ob_get_clean(); print str_replace('jubei</div>','jubei / <a href="https://vivibit.net/bbs-bugs/">vulnerability fixed by vivibit.net</a></div>',$out); } } $fuckmyruby = new FuckinRuby(); $fuckmyruby->check(); // ここまで
シリアライズされたデータなのでunserialize関数を使ってもよいのでしょうが、PHPオブジェクトインジェクションの問題があってはいけないので手動でparseしています。
さてこのコードですが、次の2点の処理を行っています。
- シリアライズされたデータに<が含まれていないかチェック。本来はファイル情報が含まれる箇所であり通常なら含まれることはない。そのため含まれていた場合は攻撃とみなす。
- DL用の拡張子が許可されていないものの場合、危険なファイルを危険な拡張子でDLさせようとしている可能性が高いのではじく。
つまり、そもそもサムネと違う画像を登録しようとするだけではこのコードは何もしません。
それに関してはせいぜいグロ画像が張られる程度で危険性は低いと判断しました。
EXEファイルのDLが促される記事が上記のコードで作成されなくなるかの検証用のPerlスクリプトを公開します。
あくまでも自身が管理するJB掲示板の検証用に使ってください。
“JB掲示板検証コード” をダウンロード jubeicheck.7z – 612 回のダウンロード – 261 KB
悪用されにくくするため、このファイルにはパスワードが設定されています。
パスワードの入力が求められた場合はscrapehumanと入力してください。すべて小文字です。
開発者はどうすればよかったのか
アップロードされたデータとそのサムネイル、あるいはほかの投稿情報を紐づけてサーバー側で管理しておくべきだったでしょう。
例えばファイルがアップロードされた時点でpresubmit.txtあたりに情報を記録しておいて本投稿時に照会し、一定時間が経過しても本投稿されない場合は関連するデータを削除するだとかの処理をしておけばよかったのではないでしょうか。
まとめ
入力値検査は大事。
ブラウザ上の攻撃につながるXSSをはじめ、サーバーに危害を加えかねないOSコマンドインジェクションや、閲覧者の行動を誘導しなくてはならないが任意のファイルを実行してしまいかねない脆弱性まで、この記事で見てきただけでもその危険性は多岐にわたりましたね。
みなさんも一度、ご自身が使ってらっしゃるスクリプトの入力値検査が妥当かチェックしてみるべきです。
なお、この記事は上述の掲示板スクリプトにほかの脆弱性が存在しないことを保証するものではありません。
また、上述の配布サイトが公開しているほかのスクリプトに脆弱性が存在しないことを保証するものでもありません。