実践
第1回 DOM Based XSSとは

フロントエンド実装者が注意したい脆弱の問題のひとつに、XSSがあります。今回は、その原理を理解してみましょう。

2018年04月19日発行

目次

脆弱性トップ10に数えられるXSS

「脆弱性(ぜいじゃくせい)」という言葉を聞いたことはありますでしょうか。サービスにセキュリティ上の問題があり、情報の漏えいや改竄などにつながる状態を「脆弱」であると言い、その原因となる問題を「脆弱性」と呼びます。つまり、脆弱性があると、攻撃を受けて情報が漏洩したり、改竄されたりといった問題につながる可能性があるということです。

脆弱性には、さまざまなものがあります。Webアプリケーションのセキュリティに関する課題解決を考えるコミュニティであるOWASP(Open Web Application Security Project)は、Webアプリケーションにおけるセキュリティ上の問題のうち、重要と考えられる10点を「OWASP Top 10 Application Security Risks」として発表しています。このOWASP TOP10の2017年版は、以下で見ることができます。

OWASP Top 10 Application Security Risks - 2017

このOWASP TOP 10を、次に挙げてみましょう。

  • A1:2017-Injection
  • A2:2017-Broken Authentication
  • A3:2017-Sensitive Data Exposure
  • A4:2017-XML External Entities (XXE)
  • A5:2017-Broken Access Control
  • A6:2017-Security Misconfiguration
  • A7:2017-Cross-Site Scripting (XSS)
  • A8:2017-Insecure Deserialization
  • A9:2017-Using Components with Known Vulnerabilities
  • A10:2017-Insufficient Logging&Monitoring

インジェクション、認証の不備、機微な情報の露出、といった深刻な問題が上位にランクされていますが、フロントエンド実装者の立場でもっとも注意が必要なのは、7位にランクされているクロスサイトスクリプティング(XSS)でしょう。OWASP TOP 10の2013年版では、XSSは4位に入っており、2017年版では少し順位を下げていますが、トップ10に入る重要な問題であることには変わりありません。

XSSは、Webの脆弱性の中でも、特に出現頻度が高い脆弱性です。IPA(独立行政法人 情報処理推進機構)が発表している「ソフトウェア等の脆弱性関連情報に関する届出状況」によれば、Webアプリケーションの脆弱性として届け出られた内容のうち56%がXSSであったとされています。なお、筆者も数百件のXSSを届出しています。

なお、クロスサイトスクリプティングはCross-Site Scriptingと綴りますが、XSSと略されます。これは単純に、CSSという略ではおなじみのCascading Style Sheetsと紛らわしかったからだと言われています。クロスサイトスクリプティングの問題が話題になり始めたころにはほかにもさまざまな呼ばれ方がありました。たとえば2000年のCERTのアドバイザリでは"Malicious HTML Tags Embedded in Client Web Requests"と呼ばれていましたが、結局のところ「XSS」で定着しました。

XSSの原理

ここでXSSの原理についておさらいしておきましょう。前節では、CERTアドバイザリではクロスサイトスクリプティングを"Malicious HTML Tags Embedded"と呼んでいたこともあったとお伝えしましたが、これは「悪意あるHTMLタグの埋め込み」という意味です。それでは、悪意あるHTMLの埋め込みが可能になるのはどんなときでしょうか。

ユーザーの入力でHTMLが壊れる

多くの場合、ユーザーがWebサイトにHTMLタグを書き込むことはできません。しかし、ユーザーが入力したテキストがWeb上に表示されるのはよくあることです。たとえば、Amazonで「デザイニングWebアクセシビリティ」という単語を検索すると、結果ページには次のように表示されます。

5件の結果 本 : "デザイニングWebアクセシビリティ"

これによって、ユーザーはどの言葉で検索したのかわかるようになっています。また、検索フォームが再表示され、そこにはユーザーが入力した単語があらかじめ埋められています。このような仕様は一般的で、Amazonに限らず多くのサイトが似たような機能を備えています。

さて、このとき、ユーザーが次のような単語を検索したらどうなるでしょうか。

<s>test

もちろん、Amazonではこのような単語でも問題なく検索でき、意図どおりに動作します。

検索結果 234 のうち 1-24件 本 : "<s>test"

これは問題のない例で、ユーザーはHTMLタグを埋め込むことができません。HTMLソースを見ると、該当箇所は次のようになっています。

<span class="a-color-state a-text-bold">&#034;&lt;s&gt;test&#034;</span>

ユーザーが入力した<s>&lt;s&gt;に変換されて出力されていることがわかります。もし、この変換が行われず、<s>という文字列がそのまま出力されていたらどうなるでしょうか。

<span class="a-color-state a-text-bold"><s>test</span>

この場合、<s>という文字列はs要素の開始タグとして機能し、testという文字列に打ち消し線が引かれることになります。ユーザーが入力した<s>という文字列が表示できていない上に、HTMLが壊れ、コンテンツの他の部分にまで打ち消し線が表示される結果になります*。

*注:延々と打ち消し線が引かれる

s要素の終了タグ</s>がないため、HTML5の構文解析ルールによって後続の要素にもs要素が適用され、延々と打ち消し線が引かれることになります。表示の崩れがわかりやすいので、筆者がXSSの調査をするときは終了タグなしの<s>testという文字列をよく使います。

参考: HTML Standard 12.2.9.2 Misnested tags:

なぜHTMLは崩れたのか

HTMLが崩れ、打ち消し線が表示されてしまったのは、<s>という文字列が、テキストではなくHTMLのタグとして解釈されたためです。HTMLでは、タグを書ける場所(*注1)に&lt;という文字に続いて英文字が出現した場合(*注2)、それを開始タグと判断します。

*注1:

HTML5のパーサーのアルゴリズムで"Data state"と呼ばれる文脈です。昔のHTML(SGML)を知っている方なら、#PCDATAと呼ばれていた文脈だと思えば近いでしょう。

参考:HTML Standard 12.2.5.1 Data state

*注2:

HTML5のパーサーのアルゴリズムでは、&lt;の直後に英大文字・小文字が来たときのみ開始タグとみなします。数字で始まる名前が来てもタグ扱いされません。なお、2文字目以降は数字でもかまいません。

参考:HTML Standard 12.2.5.6 Tag open state

言い方を変えると、<s>の文字はHTMLにおいて特別の意味を持ち、テキストではないと解釈されるということになります。このように、データの解釈を誤らせるような特別な意味を持つ文字を入れることを、「インジェクション(注入)」と呼んでいます。この例では、ユーザーが<s>という文字列をインジェクションし、それがHTMLタグとして解釈されたためにHTMLが壊れ、表示が乱れたということになります。

HTML以外のインジェクション

インジェクションの攻撃ができるものはHTML以外にも多数あります。特に有名なのはSQL文として特別な意味を持つ文字を挿入する「SQLインジェクション」という攻撃で、HTMLのインジェクションよりもはるかに強力です。冒頭で紹介したOWASP TOP10のナンバーワンは「インジェクション」となっていましたが、これはSQLインジェクションやLDAPインジェクションといった強力な攻撃を想定したものです。

HTMLのインジェクションで何が起きるのか

先の例では表示がおかしくなっただけで、大きな害はないと言えるかもしれません。しかし、HTMLというのは非常にパワフルな言語で、単に表示を変えるだけにとどまらず、さまざまなものを実現することができます。

たとえば、次のような文字列がそのまま出力されれば、サイト上にはパスワード入力欄(のように見えるもの)が出現することになります。

<form action="http://example.com/">
パスワード: <input type="password" name="p">
</form>

ユーザーがパスワードを入力してしまうと、パスワードが example.com に対して送信されることになります。この送信先が攻撃者のサイトになっていれば、攻撃者にパスワードを読み取られることになってしまうでしょう。

また、次のような文字列が入れられるとどうなるでしょうか。

<script>alert('XSS')</script>

環境にもよりますが、スクリプトが実行されて「XSS」というダイアログが表示されることになるでしょう。この例では、サイト上に本来なかったはずのスクリプトが注入され、実行されることになりました。

HTMLがインジシェクションできるとさまざまなことができますが、中でも特にスクリプトが動作するというのは衝撃的なことで、これによってありとあらゆることができてしまいます。"XSS"の"Scripting"とは、まさにこのスクリプトが実行されるというシチュエーションを指しています。

エスケープでXSSを回避する

さて、このような問題を避けるためにはどうすれば良いのでしょうか。

問題の原因は、テキストであるべきものがタグとして認識されることでした。それであれば、タグとして認識されないようにすればよい、ということになります。HTMLでは、文字参照にするか、CDATA区間を使用すれば、<s>のような文字列をテキストとして書くことができます。次のような書き方が可能です。

  • &lt;s>
  • &#60;s>
  • &#x3c;s>
  • <![CDATA[<s>]]>

このように、特殊な意味に解釈される文字をそのまま書かずに、違う書き方をして意味を失わせることを「エスケープ」と言います。HTMLタグをテキストとして書く場合にはエスケープが必要になります。上に挙げたように、エスケープの仕方にはさまざまなものがありますが、ほとんどの場合は&lt;という表記に置き換えられています。

なお、タグ終了の>のほうは、ほとんどの場合そのまま書いて問題ありません。ただ、引用符が省略された属性値の中で使われるときはエスケープしなければなりません。属性値の引用符を省略することはほとんどないでしょうが、多くの場合は&lt;と対応させる形で&gt;としてエスケープしています。

ここではHTMLタグを例に挙げていますが、このほか、HTMLの属性値の中では"をエスケープする必要があります。たとえば、再検索フォームの中に以下のような出力がされるとしましょう。

<input type="text" value="デザイニングWebアクセシビリティ">

このときは、<s>testが入力されても問題は起きません。

<input type="text" value="<s>test">

これはXML構文の場合には整形式でない(not well-formed)構文となりますが(*注1)、XMLでないHTMLとしては特に問題のない構文です(*注2)。

しかし、"><s>testという文字列が入れられた場合、文字列の中の"が属性値の終了と解釈されることになります。

<input type="text" value=""><s>test">

改行してみるとわかりやすいでしょう。

<input type="text" value="">
<s>test
">

これでタグが挿入できています。仮に<をエスケープしていても、属性値を挿入することでスクリプトの実行ができてしまうことがあります。たとえば、" onmouseover=alert('xss')のような文字列を入れると、次のようになります。

<input type="text" value="" onmouseover=alert('xss')">

ここにマウスポインタを重ねるとスクリプトが実行されることになります。

エスケープを必要としないDOM操作

ところで、このようなエスケープ操作が必要ないケースもあります。エスケープ処理が必要になるのは、HTMLの構文解析の際に特定の意味を持つ文字が存在し、それがテキストデータではなく特定の意味を持つものとして解釈されるからです。先に挙げた、&lt;"が該当する文字であり、これをエスケープする必要がありました。

これはHTMLの構文解析に伴うものですので、HTMLの構文解析とは関係のないシチュエーションでは、エスケープが必要ないことがあります。たとえば、以下はDOM操作によって要素に属性値とテキストを追加している例です。

<p id="target1">sample</p>
<input id="target2" value="sample"/>
<script>
const target_p = document.getElementById('target1');
target_p.appendChild(document.createTextNode('<s>test</s>'));
const target_input = document.getElementById('target2');
target_input.setAttribute('value', '"><s>test</s>');
</script>

実際にこんなコードを書くことはないと思いますが、ともあれ、コード中で特にエスケープ処理をしていないことに注目してください。にもかかわらず、<s>"を含む文字を問題なく扱うことができています。

これは、createTextNode()setAttribute()に渡したテキストがHTMLとして構文解析されることなく、DOMに直接追加されるからです。どのような文字を渡しても、HTMLが壊れることはありません。したがって、DOM操作でXSSが生じることは、通常ありません。

よく使われるライブラリなどにも、DOM操作と同じ感覚でテキストを処理できるものがあります。たとえばjQueryを使うと、上記の例のスクリプト部分は次のように書くことができます。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$('#target1').text('<s>test</s>');
$('#target2').attr('value', '"><s>test</s>');
</script>

この場合もやはり、問題が起きることはありません。

では、JavaScript の実装ではXSSに配慮する必要はないと言ってよいのでしょうか?

残念ながら、答えはNOです。実際にはJavaScript実装に伴うXSSもよくみられ、これは一般的にDOM Based XSSと呼ばれます。この連載では、DOM Based XSSについて、その危険性の説明と実例、回避方法などを紹介していきます。

次回では、基本に戻り、XSSの危険性についてお話しする予定です。すでにスクリプトが実行される危険性については簡単にお話ししましたが、「クロスサイト」という言葉の意味も含め、もう少し踏み込んだお話をしていきたいと思います。

太田 良典
太田 良典
アクセシビリティエンジニア

HTML仕様の翻訳や解説といった個人活動をしながら、2001年よりビジネス・アーキテクツで大規模企業サイトの制作や管理に従事。Web技術の分野で幅広い専門性を持ち、セキュリティ分野においては「第二回IPA賞(情報セキュリティ部門)」を受賞。アクセシビリティ分野では、ウェブアクセシビリティ基盤委員会(WAIC)の委員として活動している。2017年12月より、アクセシビリティエンジニアとして弁護士ドットコムに所属。著書に『デザイニングWebアクセシビリティ』(共著:ボーンデジタル)など。