防御Xss攻击的几种方法
关于xss是什么,以及它带来的危害,这里不多赘述
宁杀错,不放过。对用户输入的内容进行HTML
编码
使用Spring提供的工具类 org.springframework.web.util.HtmlUtils
String result = HtmlUtils.htmlEscape("<script>alert('springboot中文社区');</script>");
System.out.println(result);
// <script>alert('springboot中文社区');</script>
很简单粗暴,直接对所有内容进行Html编码。
编码的时机可以选择,在存储到数据库的时候编码。或者模版引擎在渲染页面的时候进行编码。
HtmlUtils
还有很多有用的静态方法,可以通过阅读源码或者文档学习
不能妄杀忠良。使用Jsoup
过滤非法的HTML
有时候也不能把用户输入的所有html都给编码了。典型的场景就是 富文本编辑器。需要完整的保留富文本编辑器生成的html标签以及样式。但是又必须要防止坏人插入恶意代码
jsoup
一款html解析器,也可以用来防止xss。它可以通过配置来设定白名单的html代码。通俗的说,就是,可以设置,哪些标签,属性是系统允许的。然后通过clean
方法来清除那些不符合白名单要求的标签,属性。
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
我封装了一个工具类
通过
classpath
下的一个json
文件来描述Html白名单
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class JsoupUtils {
private static final Whitelist NONE_WHITELIST = Whitelist.none();
private static final Logger LOGGER = LoggerFactory.getLogger(JsoupUtils.class);
// 过滤配置
private static final Document.OutputSettings outputsettings = new Document.OutputSettings();
/**
* 过滤XSS字符
* @param content
* @return
*/
public static String clean(String content) {
return content == null ? null : Jsoup.clean(content, "", NONE_WHITELIST, outputsettings);
}
static {
//过滤配置参数
outputsettings.prettyPrint(false);
outputsettings.charset(StandardCharsets.UTF_8);
//读取配置JSON文件
Resource resource = new ClassPathResource("allow-html.json");
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))){
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = bufferedReader.readLine()) != null){
stringBuilder.append(line.trim());
}
JSONObject jsonObject = JSON.parseObject(stringBuilder.toString());
//允许标签
JSONArray tags = jsonObject.getJSONArray("allow_tags");
NONE_WHITELIST.addTags(tags.toArray(new String[tags.size()]));
LOGGER.debug("允许标签:{}", tags);
//允许属性
JSONArray properties = jsonObject.getJSONArray("allow_properties");
NONE_WHITELIST.addAttributes(":all",properties.toArray(new String[properties.size()]));
LOGGER.debug("允许属性:{}",properties);
//允许特殊属性
JSONObject specialProperties = jsonObject.getJSONObject("special_properties");
specialProperties.keySet().stream().forEach(tag -> {
JSONArray attributes = specialProperties.getJSONArray(tag);
NONE_WHITELIST.addAttributes(tag,attributes.toArray(new String[attributes.size()]));
LOGGER.debug("允许特殊属性:标签={},属性={}",tag,attributes);
});
//允许特殊协议
JSONObject protocols = jsonObject.getJSONObject("protocols");
protocols.keySet().stream().forEach(tag -> {
JSONObject protoObject = protocols.getJSONObject(tag);
protoObject.keySet().stream().forEach(attr -> {
JSONArray protocolValues = protoObject.getJSONArray(attr);
NONE_WHITELIST.addProtocols(tag,attr,protocolValues.toArray(new String[protocolValues.size()]));
LOGGER.debug("允许特殊协议:标签={},属性={},协议={}",tag,attr,protocolValues);
});
});
//固定属性值,非必须的
JSONObject fixedProperties = jsonObject.getJSONObject("fixed_properties");
if(fixedProperties != null && !fixedProperties.isEmpty()) {
fixedProperties.keySet().stream().forEach(tag -> {
JSONObject property = fixedProperties.getJSONObject(tag);
if(property != null && !property.isEmpty()) {
property.keySet().stream().forEach(attr -> {
String value = property.getString(attr);
NONE_WHITELIST.addEnforcedAttribute(tag, attr, value);
LOGGER.debug("强制属性:标签={},属性={},值={}",tag,attr,value);
});
}
});
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("加载XSS过滤白名单异常,请检查文件 allow-html.json");
}
}
}
allow-html.json
{
"allow_tags": [
"a","abbr","acronym","address","area","article","aside","audio",
"b","bdi","big","blockquote","br",
"caption","cite","code","col","colgroup",
"datalist","dd","del","details","div","dl","dt",
"em",
"fieldset","figcaption","figure","footer",
"h1","h2","h3","h4","h5","h6","hr",
"i","img","li","ins",
"ol",
"p","pre",
"q",
"ul",
"small","span"
],
"allow_properties":[
"style","title"
],
"special_properties": {
"a":["href"],
"img":["src"]
},
"protocols": {
"a": {
"href":["#","http","https","ftp","mailto"]
},
"blockquote":{
"cite":["http","https"]
},
"cite":{
"cite":["http","https"]
},
"q":{
"cite":["http","https"]
}
},
"fixed_properties":{
"tag":{
"attr":"value"
}
},
"desc":{
"allow_tags":"仅仅允许使用的标签",
"allow_properties":"所有标签都允许使用的属性",
"special_properties":"特殊标签允许使用的特殊属性",
"protocols":"特殊标签,特殊属性,仅仅允许使用指定的协议",
"fixed":"特殊标签,必须会添加的固定属性与值(会覆盖原来的)"
}
}
演示:清除非法属性
String content = "Hello <span onclick='alert('Hello');'>KevinBlandy</span>";
content = JsoupUtils.clean(content);
System.out.println(content);
// Hello <span>KevinBlandy</span>
最后的防线。浏览器CSP策略
如果很不幸,系统被攻破,已经被注入了XSS代码。那么浏览器的CSP
策略将是保证系统安全的最后一道防线
参考我以前的帖子
最后。一些建议
- 永远也别相信用户的输入
- 一般采用
cookie
作为凭证,那么必须要设置httpOnly=true
。禁止js代码读取cookie