java xss 防护
Posted on 2020-08-25 18:00 in Java
XSS 的类型
存储型 XSS
数据库中存有的存在XSS攻击的数据,返回给客户端。若数据未经过任何转义,被浏览器渲染,就可能导致XSS攻击。
反射型 XSS
将用户输入的存在XSS攻击的数据,发送给后台,后台并未对数据进行存储,也未经过任何过滤,直接返回给客户端。被浏览器渲染,就可能导致XSS攻击。
防护
输入过滤
https://github.com/OWASP/java-html-sanitizer
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20200713.1</version>
</dependency>
PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHTML = policy.sanitize(untrustedHTML);
输出过滤
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.2</version>
</dependency>
<%@ taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>
<p>Exception: ${e:forHtml(exception.toString())}</p>
实践
在 spring 的 controller 中添加 String 的转换,在这个过程中过滤非法数据,所有继承 BaseController 的都会拥有过滤能力。(PS.如果子类已经有 @InitBinder, 需要调用 Super.initBinder(dataBinder))
public class BaseController {
// xss protection
@InitBinder
protected void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(String.class, new StringAntiXssConverter());
}
}
public class StringAntiXssConverter extends PropertyEditorSupport {
private static final PolicyFactory policy = Sanitizers.BLOCKS.and(Sanitizers.FORMATTING).and(Sanitizers.STYLES)
.and(Sanitizers.LINKS)
.and(new HtmlPolicyBuilder().allowElements("p").allowAttributes("class").onElements("p").toFactory())
.and(new HtmlPolicyBuilder().allowElements("span").allowAttributes("lang").onElements("span").toFactory());
@Override
public String getAsText() {
Object value = getValue();
return (value != null ? value.toString() : "");
}
@Override
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
if (JsonUtils.isJsonValid(text)){ // 先判断是不是json字符串
setValue(text);
} else if (StringUtils.isHtml(text)){ // 然后 html sanitizer
String value = policy.sanitize(text);
setValue(value);
} else {
setValue(text);
}
}
}
}
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class JsonUtils {
public static boolean isJsonValid(final String json) {
try {
return isJsonValid(new StringReader(json));
} catch (IOException e) {
return false;
}
}
public static boolean isJsonValid(final Reader reader)
throws IOException {
return isJsonValid(new JsonReader(reader));
}
public static boolean isJsonValid(final JsonReader jsonReader)
throws IOException {
try {
JsonToken token;
loop:
while ((token = jsonReader.peek()) != JsonToken.END_DOCUMENT && token != null) {
switch (token) {
case BEGIN_ARRAY:
jsonReader.beginArray();
break;
case END_ARRAY:
jsonReader.endArray();
break;
case BEGIN_OBJECT:
jsonReader.beginObject();
break;
case END_OBJECT:
jsonReader.endObject();
break;
case NAME:
jsonReader.nextName();
break;
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
jsonReader.skipValue();
break;
case END_DOCUMENT:
break loop;
default:
throw new AssertionError(token);
}
}
return true;
} catch (final MalformedJsonException ignored) {
return false;
}
}
}
import org.springframework.web.util.HtmlUtils;
public class StringUtils {
public static boolean isHtml(String str) {
boolean isHtml = false;
if (str != null) {
if (!str.equals(HtmlUtils.htmlEscape(str))) {
isHtml = true;
}
}
return isHtml;
}
}
更新
上面的方法存在一个问题,owasp-java-html-sanitizer 会导致很多符号被转义,以后使用 jsoup
<dependency>
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
判断是否是字符串是否是 html
import org.springframework.web.util.HtmlUtils;
public class StringUtils {
private static Pattern htmlPattern = Pattern.compile(".*\\<[^>]+>.*", Pattern.DOTALL);
public static boolean isHtml(String str) {
boolean isHtml = false;
if (str != null) {
return htmlPattern.matcher(str).matches();
}
return isHtml;
}
}
public class JsoupUtils {
private static final Whitelist whitelist = Whitelist.relaxed();
/*
* 配置过滤化参数,不对代码进行格式化
*/
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
/*
* 富文本编辑时一些样式是使用style来进行实现的 比如红色字体 style="color:red;" 所以需要给所有标签添加style属性
*/
whitelist.addAttributes(":all", "style");
whitelist.addAttributes(":all", "class");
whitelist.addAttributes(":all", "lang");
whitelist.removeTags("img");
}
public static String clean(String content) {
return Jsoup.clean(content, "", whitelist, outputSettings);
}
}