使用jsoup解决xss(跨站脚本攻击)威胁

1. 在介绍jsoup之前,首先来详细介绍一下关于xss的信息。

1.1 什么是xss 

Cross-Site Scripting(XSS)是一类注入问题,恶意脚本被注入到健康的、可信任的网站。当一个攻击者通过一个网站应用程序,
    以浏览器端脚本的形式,给另一端的用户发送恶意代码时,XSS攻击就发生了。允许这种攻击成功的缺陷广泛存在于各个大小网站,
    只要这个网站某个页面将用户的输入包含在它生成的动态输出页面中并且未经验证或编码转义,这个缺陷就存在。
            攻击者使用XSS发送恶意脚本给一个不持怀疑态度的用户,用户端的浏览器没法知道脚本可不可信,从而执行该js脚本。因为浏览器
    认为该脚本来自于一个可信赖的网站,导致恶意脚本可以访问任何cookies信息、会话令牌、或者其他由浏览器保存的但由那个网站
    使用的敏感信息,甚至可以修改当前网页内容。
            存储型XSS:存储在数据库,是永久的,除非数据库被重置或者恶意语句被人工删除。攻击者引导用户到一个特定的页面。
            反射型XSS:恶意脚本没有存储在远端的网站应用中,需要社会工程学配合,比如通过邮件或聊天软件发送链接。主要用来窃取cookie

1.2 xss发生在什么地方

   数据通过一个不可信的源,大多数时是一个页面请求,进入网站应用。
        -   数据未经验证是否含有恶意内容,就包含在动态内容中发送给网站用户。
        发送到浏览器的恶意内容通常以一段js脚本的形式存在,但也可能是html、flash或者任何其他可能被浏览器执行的代码。基于xss的攻击是
        多样化没有限制的,常见的有传输私密数据,像cookies或其他会话信息,对攻击者而言,重定向或引诱受害者到由攻击者所控制的页面,或者
        伪装成可信赖网站,直接在用户机器上执行恶意操作

1.3 xss分为哪几类

XSS攻击通常被分为两类:存储型和反射型。还有第三类,不那么知名的,基于DOM的xss。
        -   存储型
            注入的脚本被永久的存储在了目标服务器中,比如数据库、论坛帖子、访问日志、留言评论等。受害者向服务器请求获取存储的信息时,
            就获得了这些恶意脚本。存储型XSS也被称为持久型或I-型XSS。
        -   反射型
            注入脚本从网站服务器被反弹回来,比如错误消息、搜索结果、或者任何其他响应(这些响应完全或部分包含了用户在浏览器输入的内容)。
            反射型攻击通过其他途径传递到受害者,比如邮件、或其他网站。当用户被引诱点击恶意链接,提交一个特别构造的表单、或浏览一个恶意
            站点,注入脚本传送到了脆弱站点并反射给用户的浏览器,浏览器认为该链接来自一个可信的服务器就执行了它。反射型XSS也称为非持久型
            或II-型XSS。
        -   DOM型
            DOM型XSS又称0-型xss。攻击者提交的恶意数据并未显式的包含在web服务器的响应页面中,但会被页面中的js脚本以变量的形式来访问到,
            导致浏览器在渲染页面执行js脚本的过程中,通过DOM操作执行了变量所代表的恶意脚本。这种也被归类为‘client-side xss’。
            前两类xss攻击中,服务器的响应页面中显式的包含了恶意内容,被归类为‘server-side xss’。

上面的描述可能太过于抽象,如果读者阅读完还是不能理解xss,那么请阅读下面具体的介绍:

其实xss(跨站脚本攻击)就是恶意攻击者为了达到某种目的,恶意在web网页页面中插入一些html代码,当访问者访问这些页面时,这些恶意的html代码就会被执行,从而达到目的。插入html代码通常发生在输入框内,例如评论,留言,注册等等。

2. 解决xss攻击的方案-jsoup

   2.1 pom.xml集成到pom.xml中

<jsoup.version>1.8.3</jsoup.version>
<!-- jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>${jsoup.version}</version>
</dependency>

 2.2 Jsoup 类介绍

package org.jsoup;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.jsoup.helper.DataUtil;
import org.jsoup.helper.HttpConnection;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.parser.Parser;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;

public class Jsoup {
    private Jsoup() {
    }

    public static Document parse(String html, String baseUri) {
        return Parser.parse(html, baseUri);
    }

    public static Document parse(String html, String baseUri, Parser parser) {
        return parser.parseInput(html, baseUri);
    }

    public static Document parse(String html) {
        return Parser.parse(html, "");
    }

    public static Connection connect(String url) {
        return HttpConnection.connect(url);
    }

    public static Document parse(File in, String charsetName, String baseUri) throws IOException {
        return DataUtil.load(in, charsetName, baseUri);
    }

    public static Document parse(File in, String charsetName) throws IOException {
        return DataUtil.load(in, charsetName, in.getAbsolutePath());
    }

    public static Document parse(InputStream in, String charsetName, String baseUri) throws IOException {
        return DataUtil.load(in, charsetName, baseUri);
    }

    public static Document parse(InputStream in, String charsetName, String baseUri, Parser parser) throws IOException {
        return DataUtil.load(in, charsetName, baseUri, parser);
    }

    public static Document parseBodyFragment(String bodyHtml, String baseUri) {
        return Parser.parseBodyFragment(bodyHtml, baseUri);
    }

    public static Document parseBodyFragment(String bodyHtml) {
        return Parser.parseBodyFragment(bodyHtml, "");
    }

    public static Document parse(URL url, int timeoutMillis) throws IOException {
        Connection con = HttpConnection.connect(url);
        con.timeout(timeoutMillis);
        return con.get();
    }

    public static String clean(String bodyHtml, String baseUri, Whitelist whitelist) {
        Document dirty = parseBodyFragment(bodyHtml, baseUri);
        Cleaner cleaner = new Cleaner(whitelist);
        Document clean = cleaner.clean(dirty);
        return clean.body().html();
    }

    public static String clean(String bodyHtml, Whitelist whitelist) {
        return clean(bodyHtml, "", whitelist);
    }

    public static String clean(String bodyHtml, String baseUri, Whitelist whitelist, OutputSettings outputSettings) {
        Document dirty = parseBodyFragment(bodyHtml, baseUri);
        Cleaner cleaner = new Cleaner(whitelist);
        Document clean = cleaner.clean(dirty);
        clean.outputSettings(outputSettings);
        return clean.body().html();
    }

    public static boolean isValid(String bodyHtml, Whitelist whitelist) {
        Document dirty = parseBodyFragment(bodyHtml, "");
        Cleaner cleaner = new Cleaner(whitelist);
        return cleaner.isValid(dirty);
    }
}
 

关于jsoup的使用,下面举几个例子:

 /**
     * 去除图片和HTML标签
     * @param content
     * @return
     */
    public static String replseHtml(String content){
        try {
            if(StringUtils.isBlank(content)){
                return "";
            }
            return Jsoup.clean(content, Whitelist.basicWithImages());
        }catch (Exception e){
            return content;
        }
    }

使用jsoup解决xss(跨站脚本攻击)威胁

/**
 * 去除所有的HTML 代码
 * @param content
 * @return
 */
public static String replseAllHtml(String content){
    try {
        if(StringUtils.isBlank(content)){
            return "";
        }
        return Jsoup.clean(content, Whitelist.none());
    }catch (Exception e){
        //LOGGER.error(e.getMessage());
        return content;
    }
}

使用jsoup解决xss(跨站脚本攻击)威胁

上面Jsoup类中红色标注的方法就是jsoup清除html标签的操作,这三个方法中都有共同的一个传参-Whitelist whitelist

我们看一下Whitelist白名单类的构造函数,默认提供了5种设置。

1. 该API会清除所有HTML标签,仅保留文本节点

public static Whitelist none() {
        return new Whitelist();
    }

2.  该API仅会保留b, em, i, strong, u 标签,除此之外的所有HTML标签都会被清除

    public static Whitelist simpleText() {
        return (new Whitelist()).addTags("b", "em", "i", "strong", "u");
    }

3. 该API会保留 a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul 和其适当的属性标签,除此之外的所有HTML标签都会被清除,且该API不允许出现图片(img tag)。另外该API中允许出现的超链接中可以允许其指定http, https, ftp, mailto 且在超链接中强制追加rel=nofollow属性。

    public static Whitelist basic() {
        return (new Whitelist()).addTags("a", "b", "blockquote", "br", "cite", "code", "dd", "dl", "dt", "em", "i", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", "sub", "sup", "u", "ul").addAttributes("a", "href").addAttributes("blockquote", "cite").addAttributes("q", "cite").addProtocols("a", "href", "ftp", "http", "https", "mailto").addProtocols("blockquote", "cite", "http", "https").addProtocols("cite", "cite", "http", "https").addEnforcedAttribute("a", "rel", "nofollow");
    }

4. 该API在保留basic()中允许出现的标签的同时也允许出现图片(img tag)和img的相关适当属性,且其src允许其指定 http 或 https

    public static Whitelist basicWithImages() {
        return basic().addTags("img").addAttributes("img", "align", "alt", "height", "src", "title", "width").addProtocols("img", "src", "http", "https");
    }

5. 该API仅会保留 a, b, blockquote, br, caption, cite, code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, u, ul 标签,除此之外的所有HTML标签都会被清除,且在超链接中不会强制追加rel=nofollow属性。

    public static Whitelist relaxed() {
        return (new Whitelist()).addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul").addAttributes("a", "href", "title").addAttributes("blockquote", "cite").addAttributes("col", "span", "width").addAttributes("colgroup", "span", "width").addAttributes("img", "align", "alt", "height", "src", "title", "width").addAttributes("ol", "start", "type").addAttributes("q", "cite").addAttributes("table", "summary", "width").addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width").addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width").addAttributes("ul", "type").addProtocols("a", "href", "ftp", "http", "https", "mailto").addProtocols("blockquote", "cite", "http", "https").addProtocols("cite", "cite", "http", "https").addProtocols("img", "src", "http", "https").addProtocols("q", "cite", "http", "https");
    }

当然我们也可以扩展jsoup中白名单Whitelist的设置,我们看下扩展的源代码,只要传入想要扩展的标签即可。

public Whitelist addTags(String... tags) {
        Validate.notNull(tags);
        String[] var2 = tags;
        int var3 = tags.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String tagName = var2[var4];
            Validate.notEmpty(tagName);
            this.tagNames.add(Whitelist.TagName.valueOf(tagName));
        }

        return this;
    }

Whitelist.none().addTags("div");

甚至也可以移除我们不想过滤的标签,如下:

public Whitelist removeTags(String... tags) {
        Validate.notNull(tags);
        String[] var2 = tags;
        int var3 = tags.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String tag = var2[var4];
            Validate.notEmpty(tag);
            Whitelist.TagName tagName = Whitelist.TagName.valueOf(tag);
            if (this.tagNames.remove(tagName)) {
                this.attributes.remove(tagName);
                this.enforcedAttributes.remove(tagName);
                this.protocols.remove(tagName);
            }
        }

        return this;
    }

Whitelist.basicWithImages().addTags("image");