现象
当我A字段输入 '【】、=-·!@#~¥%......&*()------+{}|":》?《,./;'\][=-`~!@#$%^&*()_+{}|":>????????????????<
B字段输入'【】、=-·!@#~¥%......&*()------+{}|":》?《,./;'\][=-`~!@#$%^&*()_+{}|":>????????????????/>
发现A字段被截断,B字段为空
原因
原因在于XSSFilter中对PUT和POST请求XSS过滤,重新包装了请求头,防止XML攻击,
弊端在于,他传入的content是整段json,也就是A字段和B字段合并起来产生了HTML匹配,所以修改方案是,单个字段校验
java
public static String cleanHtmlTag(String content) {
return content.replaceAll("(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)", "");
}
原XssHttpServletRequestWrapper过滤器的getInputStream
java
@Override
public ServletInputStream getInputStream() throws IOException {
// 非json类型,直接返回
if (!isJsonRequest()) {
return super.getInputStream();
}
// 为空,直接返回
String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);
if (StringUtils.isEmpty(json)) {
return super.getInputStream();
}
// xss过滤
json = HtmlUtil.cleanHtmlTag(json).trim();
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public int available() throws IOException {
return jsonBytes.length;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
修改后的
java
@Override
public ServletInputStream getInputStream() throws IOException {
// 非 JSON 类型,直接返回
if (!isJsonRequest()) {
return super.getInputStream();
}
// 读取原始 JSON
String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);
if (StringUtils.isEmpty(json)) {
return super.getInputStream();
}
// ===== 新逻辑:针对 JSON 的每个 value 做 XSS 清理 =====
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map;
try {
map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
// JSON 解析失败,保守返回原始流
return super.getInputStream();
}
// 遍历 Map,把每个 String 类型的 value 做清理
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof String) {
entry.setValue(HtmlUtil.cleanHtmlTag((String) entry.getValue()).trim());
}
}
// 序列化回 JSON
byte[] jsonBytes = mapper.writeValueAsBytes(map);
final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);
// 返回新的 ServletInputStream
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {}
@Override
public int available() throws IOException {
return jsonBytes.length;
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}