前回および前々回で、
一部のタグを許容してHTMLを組み立てる3つの場面
JavaScriptを使用して複雑なHTMLの操作を行うようなWebアプリケーションにおいては、
- サーバ側でHTML断片となる文字列を生成し、
ブラウザ上でHTML内に流し込む  - あらかじめ定まった構造のHTMLをJavaScriptにて生成し、
その一部にデータを当てはめる  - ユーザーからの入力に基づき、
自由にHTMLを生成する  
それぞれの状況について、
サーバ側でHTML断片となる文字列を生成し、ブラウザ上でHTML内に流し込む 
「XMLHttpRequestを用いて、
var xhr = new XMLHttpRequest();
var url = "/news-update";
xhr.open("GET", url, true);
xhr.onload = function () {
    // サーバ上では「<div><a href="/news/20150101/">新製品のお知らせ</a></div>」のような文字列を返す
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);
このような場合、
- サーバ上で生成されるHTML断片文字列にXSSが存在する
 - XMLHttpRequestの接続先が攻撃者によってコントロール可能
 
ブラウザ上のJavaScriptでは、
また、
たとえば次のコードは、
// bad code
// URL内の#より後ろの部分をXMLHttpRequestの接続先として使用
// http://example.jp/#/newsであれば/newsをXMLHttpRequestで取得する
// 攻撃者はhttp://example.jp/#//attacker.example.com/のようなURLへ誘導することでXSSが発生する
var url = location.hash.substring(1);
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true); // XMLHttpRequestの接続先はhttp://attacker.example.com/となる
xhr.onload = function () {
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);
この対策として、
// URL内の#より後ろの部分でXMLHttpRequestの接続先を識別
// http://example.jp/#newsであれば/newsを、http://example.jp/#updateであれば/updateをXMLHttpRequestで取得する
// 接続先候補としては/news、/update、/infoがあるとする
var pages = {
    news : "/news",
    update : "/update",
    info : "/info"
};
var target = location.hash.substring(1);
var url = pages[target];
if (url === undefined || !pages.hasOwnProperty(target) ) {
    return; // リストに存在しない場合は関数を抜ける
}
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(){
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);
XMLHttpRequestを使用する際のセキュリティ上の注意点については、
あらかじめ定まった構造のHTMLをJavaScriptにて生成し、その一部にデータを当てはめる 
たとえば
具体例として、
// bad code
/* 
    変数friendsには以下のような配列が格納されている
    [
        {
            "name" : "山田太郎",
            "mail" : "yamada@example1.jp",
            "birthday" : "1980-05-19"
        },
        {
            "name" : "鈴木一郎",
            "mail" : "suzuki@example2.jp",
            "birthday" : "1991-10-22"
        },
        {
            "name" : "John Smith",
            "mail" : "john@example3.jp",
            "birthday" : "1993-3-27"
        }
    ]
*/
function expandTemplate (template, friends) {
    // テンプレート文字列中の「%name%」「%birthday%」「%mail%」をそれぞれ変数に置換する関数
    var i, s, html = "";
    for (i = 0; i < friends.length; i++) {
        s = template.replace(/%(\w+)%/g, function (s, param) {
            if (param === "name") return friends[ i ].name;
            else if (param === "birthday") return friends[ i ].birthday;
            else if (param === "mail") return friends[ i ].mail;
            else return "%" + param + "%";
        });
        html += s;
    }
    return html;
}
var template = '<div>%name% さん<br>メールアドレス:<a href="mailto:%mail%">%mail%</a> 誕生日:%birthday%</div>';
var elm = document.getElementById("contacts");
var html = expandTemplate(template, friends);
elm.innerHTML = html;
このようなコードでは、
{
    "name" : "<img src=# onerror=alert(1)>",
    "mail" : "\"onmouseover=alert(2)//\"@example.jp",
    "birthday" : "2000-01-01"
}
攻撃者のこのようなコンタクト先が、
<div><img src=# onerror=alert(1)> さん<br>メールアドレス:<a href="mailto:"onmouseover=alert(2)//"@example.jp">"onmouseover=alert(2)//"@example.jp</a> 誕生日:2000-01-01</div>
このような処理でのDOM-basedの発生を防ぐ最もかんたんな方法は、
function htmlEscape (s) {
    s = s.replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">")
        .replace(/"/g, """)
        .replace(/'/g, "'");
    return s;
}
function expandTemplate (template, friends){
    var i, s, html = "";
    for (i = 0; i < friends.length; i++){
        s = template.replace( /%(\w+)%/g, function(s, param){
            if (param === "name") return htmlEscape(friends[ i ].name);
            else if (param === "birthday") return htmlEscape( friends[ i ].birthday);
            else if (param === "mail") return htmlEscape(friends[ i ].mail);
            else return "%" + param + "%";
        });
        html += s;
    }
    return html;
}
このように、
先ほどの例と同様、
{
    "name" : "<img src=# onerror=alert(1)>",
    "mail" : "\"onmouseover=alert(2)//\"@example.jp",
    "birthday" : "2000-01-01"
}
この場合であっても、
<div><img src=# onerror=alert(1)> さん<br>メールアドレス:<a href="mailto:"onmouseover=alert(2)//"example.jp">"onmouseover=alert(2)//"example.jp</a> 誕生日:2000-01-01</div>
先にも述べたように、
- 自動的にエスケープされた値が埋め込まれるのか
 - 明示的に指定した場合にのみエスケープされるのか
 
といった点は確認しておく必要があります。
また、
ユーザーからの入力に基づき、自由にHTMLを生成する 
テキスト装飾用のHTMLを埋め込むためのリッチテキストエディタや、
通常、
<script src="lib/marked.js"></script>
...
<div id="content"></div>
<script>
    var markdownText = "# title\n\n-list 1\n-list 2\n";
    document.getElementById("content").innerHTML = marked(markdownText);
</script>
このとき、
じつは、
// 自由なHTMLの生成を抑制
var markdownText = "# title\n\n<img src=# onerror=alert(1)>\n<img src='/img/fig.png'>";
document.getElementById("content").innerHTML = marked(markdownText,{sanitize:true}); // <img>はどちらも生成されない
ただし、
そこで、
このような場合に採れる方法として、
DOMParser APIやcreateHTMLDocument APIは、
DOMParserを使ってDOMツリーを構築し、
function safeHtml (htmlString) {
    var parser, doc, body, i, newNode, parentNode, buildNode;
    parser = new DOMParser();
    doc = parser.parseFromString(htmlString, "text/html");
    body = doc.body;
    parentNode = document.createElement("div");
    buildNode = function (node) {
        var i, elm, childNode, attrName, attrValue;
        switch (node.nodeType) {
        case 1: // ELEMENT_NODE
            if (node.tagName === "DIV" || node.tagName === "IMG") {
                elm = document.createElement(node.tagName); 
                if (node.tagName === "IMG") {
                    for (i=0; i<node.attributes.length; i++) {
                        attrName = node.attributes[i].name;
                        attrValue = node.attributes[i].value;
                        if (attrName === "src" || attrName === "title" || attrName === "alt") {
                            elm.setAttribute( attrName, attrValue );
                        }
                    }
                }
                for (i=0; i<node.childNodes.length; i++) {
                    console.log(node.childNodes[i]);
                    childNode = buildNode(node.childNodes[i]);
                    if (childNode !== undefined) {
                        elm.appendChild( childNode );
                    }
                }
            }
            break;
        case 3: // TEXT_NODE
            elm = document.createTextNode(node.textContent);
            break;
        }
        return elm;
    };
    for (i=0; i <body.childNodes.length; i++) {
        newNode = buildNode(body.childNodes[i]);
        if (newNode !== undefined) {
            parentNode.appendChild(newNode);
        }
    }
    return parentNode.innerHTML;
}
unsafeHtml = '<div>Hello, Sanitize!<img src=# alt="incorrect image" onerror=alert(1)></div>';
sanitizedHtml = safeHtml(unsafeHtml);
elm.innerHTML = sanitizedHtml; // <div>Hello, Sanitize!<img src="#" alt="incorrect image"></div>
この例では、
たとえば、
<div>Hello, Sanitize!<img src=# alt="incorrect image" onerror=alert(1)></div>
結果として、
<div>Hello, Sanitize!<img src="#" alt="incorrect image"></div>
実際にこのような方法で安全なHTMLを組み立てるためのライブラリの実装としては、
