Jsoup代碼解讀之八-防御XSS攻擊

DesRizzo 8年前發布 | 13K 次閱讀 XSS漏洞 Java開發

來自: http://www.importnew.com/17893.html

防御XSS攻擊的一般原理

cleaner是Jsoup的重要功能之一,我們常用它來進行富文本輸入中的XSS防御。

我們知道,XSS攻擊的一般方式是,通過在頁面輸入中嵌入一段惡意腳本,對輸出時的DOM結構進行修改,從而達到執行這段腳本的目的。對于純文本輸入,過濾/轉義HTML特殊字符<,>,”,’是行之有效的辦法,但是如果本身用戶輸入的就是一段HTML文本(例如博客文章),這種方式就不太有效了。這個時候,就是Jsoup大顯身手的時候了。

在前面,我們已經知道了,Jsoup里怎么將HTML變成一棵DOM樹,怎么對DOM樹進行遍歷,怎么對DOM文檔進行輸出,那么其實cleaner的實現方式,也能猜出大概了。使用Jsoup進行XSS防御,大致分為三個步驟:

  • 將HTML解析為DOM樹

這一步可以過濾掉一些企圖搞破壞的非閉合標簽、非正常語法等。例如一些輸入,會嘗試用</textarea>閉合當前Tag,然后寫入攻擊腳本。而根據前面對Jsoup的parser的分析,這種時候,這些非閉合標簽會被當做錯誤并丟棄。

  • 過濾高風險標簽/屬性/屬性值

高風險標簽是指<script>以及類似標簽,對屬性/屬性值進行過濾是因為某些屬性值里也可以寫入javascript腳本,例如onclick=’alert(“xss!”)’。

  • 重新將DOM樹輸出為HTML文本

DOM樹的輸出,在前面(Jsoup代碼解讀之三)已經提到過了。

Cleaner與Whitelist

對于上述的兩個步驟,1、3都已經分別在parser和輸出中完成,現在只剩下步驟 2:過濾高風險標簽等。

Jsoup給出的答案是白名單。下面是Whitelist的部分代碼。

public class Whitelist {
 private Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span]
 private Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag.
 private Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values
 private Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes
 private boolean preserveRelativeLinks; // option to preserve relative links
}

這里定義了標簽名/屬性名/屬性值的白名單。

而Cleaner是過濾的執行者。不出所料,Cleaner內部定義了CleaningVisitor來進行標簽的過濾。CleaningVisitor的過濾過程并不改變原始DOM樹的值,而是將符合條件的屬性,加入到Element destination里去。

private final class CleaningVisitor implements NodeVisitor {
 private int numDiscarded = 0;
 private final Element root;
 private Element destination; // current element to append nodes to
private CleaningVisitor(Element root, Element destination) {
 this.root = root;
 this.destination = destination;
 }
public void head(Node source, int depth) {
 if (source instanceof Element) {
 Element sourceEl = (Element) source;
if (whitelist.isSafeTag(sourceEl.tagName())) { // safe, clone and copy safe attrs
 ElementMeta meta = createSafeElement(sourceEl);
 Element destChild = meta.el;
 destination.appendChild(destChild);
numDiscarded += meta.numAttribsDiscarded;
 destination = destChild;
 } else if (source != root) { // not a safe tag, so don't add. don't count root against discarded.
 numDiscarded++;
 }
 } else if (source instanceof TextNode) {
 TextNode sourceText = (TextNode) source;
 TextNode destText = new TextNode(sourceText.getWholeText(), source.baseUri());
 destination.appendChild(destText);
 } else { // else, we don't care about comments, xml proc instructions, etc
 numDiscarded++;
 }
 }
public void tail(Node source, int depth) {
 if (source instanceof Element && whitelist.isSafeTag(source.nodeName())) {
 destination = destination.parent(); // would have descended, so pop destination stack
 }
 }
}

結束語

至此,Jsoup的全部模塊都已經寫完了。Jsoup源碼并不多,只有14000多行,但是實現非常精巧,在讀代碼的過程中,除了相關知識,還驗證幾個很重要的思想:

  • 最好的代碼抽象,是對現實概念的映射。

這句話在看《 代碼大全 》的時候印象很深刻。在Jsoup里,只要有相關知識,每個類的作用都能第一時間明白其作用。

  • 不要過度抽象

在Jsoup里,只用到了兩個接口,一個是NodeVisitor,一個是Connection,其他都是用抽象類或者直接用實現類代替。記得有次面試的時候被問到我們開發中每逢一個功能,都要先定義一個接口的做法是否必要?現在的答案是沒有必要,過度的抽象反而會降低代碼質量。

另外,Jsoup的代碼內聚性都很高,每個類的功能基本都定義在類的內部,這是一個典型的充血模型。同時有大量的facade使用,而避免了Factory、Configure等類的出現,個人感覺這點是非常好的。

最后繼續貼上Jsoup解讀系列的github地址:https://github.com/code4craft/jsoup-learning/

本系列:

</div>

 本文由用戶 DesRizzo 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!