セキュリティキャンプ2015に応募した
毎年お盆の頃にやってるセキュリティキャンプに応募してみました。
一昨年も応募して落ちて、去年はなんやかんやで応募しなかったのですが、今年は頑張って問題を解きました。
既に他にも問題回答を出している方もいますが、せっかくなので記録がてら僕も載せておこうかと。ただ、何となくこうじゃないかと書いた回答で、あまり自信がないので間違ってるところがあるかも。そこはご了承ください。これが最も正しいかは分かりません
僕が回答したのは選択問題2,5,9,10,11ですが、他の方の回答を見て「なるほど、こうだったのか」という感想ばかり浮かんでしまったので、割と頑張ってた問題9だけ載せます……。
選択問題9
以下のコードは、与えられたテキスト内からURLらしき文字列を探して、それらを要素でリンクにしたHTMLを生成するJavaScriptの関数であるとします。攻撃者が引数 text の中身を自由に制御可能な場合、このコードにはどのような問題点があるか、またこのコードを修正するとすればどのようにすればよいか、自分なりに考察して書いてください。
function makeUrlLinks( text ){ var html = text.replace( /[\w]+:\/\/[\w\.\-]+\/[^\r\n \t<>"']*/g, function( url ){ return "<a href=" + url + ">" + url + "</a>"; } ); document.getElementById( "output" ).innerHTML = html; }
回答
この正規表現はhttp、https以外のスキーマも許容するため、httpまたはhttpsのみにマッチさせた方が良い。
var html = text.replace( /[\w]+:\/\/[\w.-]+\/[^\r\n \t<>"']/g, function( url ){ => var html = text.replace( /https?:\/\/[\w.-]+\/[^\r\n \t<>"']/g, function( url ){
また、textに対してエスケープ処理をせずにinnerHTMLを使っているため、スクリプトを埋め込むことができ、DOM based XSSの危険性がある。aタグでリンクを生成したいのでhtmlエスケープではなく、回避策としてinnerHTMLを使わずにDOM操作APIを使ってaタグを生成するようにした方が良い。
修正したjavascript
function makeUrlLinks( text ){ var reg = new RegExp(/https?:\/\/[\w\.\-]+\/[^\r\n \t<>"']*/g); var old_index = 0; var div = document.getElementById("output"); while(match = reg.exec(text)){ // aタグをDOM APIで生成 var elm = document.createElement("a"); elm.setAttribute("href", match); elm.appendChild(document.createTextNode(match)); // リンクの手前の部分文字列のノードを生成 text_node = document.createTextNode(text.substring(old_index, match.index)); div.appendChild(text_node); div.appendChild(elm); old_index = reg.lastIndex; } // 最後のリンクの後の部分文字列のノードを生成(リンクがなければtextそのまま) text_node = document.createTextNode(text.substring(old_index)); div.appendChild(text_node); }
回答までの経緯
「コードが短いしこれは狙い目だ!」と思って解くことにしました。結局調べ物してたら3日位かかったんですけど。
このコードは、「与えられたテキスト内からURLらしき文字列をリンクにしたHTMLを生成する」ということなので、例えば
「参考文献はhttp://hoge.com/ とhttps://fuga.co/piyo です」
みたいな文を入力したらurlのところをaタグで囲んで再表示するという動作をするのだと思います。
さて、コードが短いので問題点があるところは簡単に絞れそうです。それは
- 正規表現
- innerHTMLでHTML生成
ですかね。というかそれ以外コードが無いんですが。とりあえずXSSの脆弱性があるんだろうなーという気持ちでいました。
なにはともあれ、動かしてみないと具体的に分からないですし、修正出来ないのでHTMLを書いて動かしましょう。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <h2> JS Test <h2> <input type="text" id="input" value=""> <input type="button" value="output" onclick="makeUrlLinks(document.getElementById('input').value)"> <div id="output"></div> <script type="text/javascript" src="./q9.js"></script> </body> </html>
テキストエリアに入力してボタンを押すとjsが実行されます。こんな感じ。urlとか関係なしに入力に<img>とか<script>とかが普通に入れられるのでXSSできそう。
では問題点の話に戻ります。
- 正規表現
現状の正規表現だとhttpとhttps以外にもマッチしてしまいます。
これは意図しない動作だろう、ということで正規表現を
/https?:\/\/[\w.-]+\/[^\r\n \t<>"']*/g
と変えます。
- innerHTML
これについては調べていたら問題文に近い記事がありました。一番下のところですね。最初は<a>以外のタグをHTMLエスケープしとけばいいのかなーって思っていたのですが、これを見てDOM操作用APIを使うことにしました。
HTML5時代の「新しいセキュリティ・エチケット」(2):単純ではない、最新「クロスサイトスクリプティング」事情 (2/3) - @IT
そもそもinnerHTMLを使うとDOMツリーをいじるやすくなるので今はinnerHTMLは推奨されていないんですね。調べているときに知りました。
DOM操作APIを使うのはいいんですが、問題文の目的としてはリンク部分以外も残してリンク部分だけ<a>タグにして入れ替えるわけで、リンクに挟まれているテキストとかリンクの前後のテキストも取得しないといけないですね。
テキストノード <a>リンク</a>テキストノード<a>リンク</a>テキストノード
という形にしたい。そこで正規表現でループさせてString.substring()メソッドと正規表現にマッチした位置なんかを使って切り出してます。
function makeUrlLinks( text ){ var reg = new RegExp(/https?:\/\/[\w\.\-]+\/[^\r\n \t<>"']*/g); var old_index = 0; var div = document.getElementById("output"); while(match = reg.exec(text)){ // aタグをDOM APIで生成 var elm = document.createElement("a"); elm.setAttribute("href", match); elm.appendChild(document.createTextNode(match)); // リンクの手前の部分文字列のノードを生成 text_node = document.createTextNode(text.substring(old_index, match.index)); div.appendChild(text_node); div.appendChild(elm); old_index = reg.lastIndex; }
なんかここまでしなくてももっと簡単にできるんじゃないかと思うんですが、調査力の限界と時間の限界でこの形で出しました。
「参考文献はhttp://hoge.com/ と<script>alert(1)</script>https://fuga.co/piyo です」で表示したもの。httpとhttps以外はテキストとして扱っています。
実はhttp://~~の末尾に#javascript:alert(1)と入れると正規表現に通るので、これもなんとかしとかないとまずかったのかな?ここは対処してません
セキュリティキャンプの問題はほとんど一から調べて書いたのですが、かなり勉強になりました。バイナリ読んだりするのは初めてみたいなものだったので。結果的に選考に落ちていても問題に向かったことで成長できていると信じたいものです。