読者です 読者をやめる 読者になる 読者になる

吹き出しをだすのに超便利なjQueryプラグインjquery.balloon.jsのXSSでハマった話

↑で紹介されているjquery.balloon.js

吹き出しをだすのにとっても便利なスグレモノなのですが
吹き出し内に出す文言が動的に変わる場合のXSS対策でハマりました。

titleまたはaltに設定されている文言を自動取得して吹き出し内に表示してくれるのですが
メモアプリでユーザが任意の文字列を入力できて、メモの一覧ページでマウスポインタをフォーカスすると
そのメモの内容の冒頭部分を吹き出しで表示するというような実装をしていました。

<div class="hoge " title="&lt;script&gt;alert('fuga')&lt;/script&gt;">
<a href="hoge">foo</a></div>

上記のようにスクリプトタグが入力された場合に山括弧をエスケープして出力していても
ポインタをフォーカスするとスクリプトが動いてalertが表示されてしまいました。

jquery.balloon.js内で一度titleの文言を取得し整形して動的にdivを生成しているようなのですが
どうもその過程でエスケープ文字列が<>に戻ってしまっていたようです。

対応としては

 if (o) i = e("<div>").append(u);
 if (!t.url && (!i || i.html() == "")) return;
 if (!o && u && u != i.html()) i.empty().append(u);

↑192~194行目あたりで文言をappendしているところに
エスケープをかませました。

 if (o) i = e("<div>").append(escapeTag(u));
 if (!t.url && (!i || i.html() == "")) return;
 if (!o && u && u != i.html()) i.empty().append(escapeTag(u));

エスケープ用の関数は

function escapeTag(str){

  //山括弧だけを置換
  a = str.replace(/<+/g,'&lt;');
  b = a.replace(/>+/g,'&gt;');

  return b;

  //タグ削除していいならこっちの方が簡単
  return $('<div>').html(str).text());

}


これでなんとかなりました。

山括弧だけを置換なんて面倒なことをわざわざやっているのは上司に入力されている文言がそのままでないのはおかしいと言われたからです。
そもそも不正なタグを入力することがおかしいので除去でもいいと思うんですけどね・・・。
デザインもフラットデザインをしらず見辛いからボタン画像にしろといいだしてデザイナーが大変そうです。
以上SIあるあるでした。

上記ソースはjquery.balloon.min,jsを抜粋しているので通常のjsだと変数名等違うかもしれません。

参考
【jQuery】吹き出しホバー用プラグイン「jquery.balloon.js」 | web MEMOry
JavaScriptでHTMLタグを削除する正規表現 - Qiita