写点什么

XSS 检测绕过(UTF-7 编码绕过)

作者:BugShare
  • 2025-10-29
    山东
  • 本文字数:5157 字

    阅读完需:约 17 分钟

XSS检测绕过(UTF-7编码绕过)

📢 叮咚,现场运维来消息了,说项目被检测到有高危漏洞,要求修复,以为就是 jar 安全漏洞,升级就完事了,就让发过来看看👀,亚麻袋住了,“XSS 检测绕过(UTF-7 编码绕过)”,从没见过啊,还是 UTF-7。




怎么搞?我电脑上的编辑器都没找到有支持 UTF-7 编码的,首先想到的,把这些信息丢给 DeepSeek 帮我分析看看,问 Ai 怎么防御?结果没有我想要的方案。


然后去网络搜索下吧,看看大家前辈们有没解决过,果然有相关文件,但是都没给出具体解决方案,不过也有所收获,得到了一段 UTF-7 编码的 XSS 注入参数(如果 Get 参数请求,记得对参数 URL 编码)


+ADw-script+AD4-alert('UTF-7 XSS')+ADw-/script+AD4-
复制代码


进入正题,结合项目代码,想到可以用 Filter 过滤器对参数拦截,那就动手来吧,以项目 SpringCloud Zuul 为例


# xss regexxss:  enable: true  regexes:    # UTF-7编码绕过    - "(?i)(\\+[A-Za-z0-9+/=,]+\\-|\\+(?:ADw|AD4|AC8|ACI|AHs|AH0)-)"
复制代码


import cn.hutool.core.util.BooleanUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.json.JSON;import cn.hutool.json.JSONUtil;import com.framework.util.RegexUtil;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import lombok.Data;import lombok.EqualsAndHashCode;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.springframework.util.StreamUtils;
import javax.annotation.PostConstruct;import javax.servlet.ReadListener;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.*;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;
@Data@Slf4j@Component@Configuration@EqualsAndHashCode(callSuper = true)@ConfigurationProperties(prefix = "xss")@ConditionalOnProperty(prefix = "xss", name = "enable", havingValue = "true")public class XssZuulFilter extends ZuulFilter {
/** * 正则 */ private List regexes = new ArrayList<>(); private static List injectionPatterns = new ArrayList<>();
@PostConstruct public void init() { log.debug("XssZuulFilter#init-regexes: {}", this.regexes); this.regexes.forEach(regex -> { injectionPatterns.add( Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) ); }); }
@Override public String filterType() { return "pre"; // 在路由之前执行 }
@Override public int filterOrder() { return 0; // 高优先级执行 }
@Override public boolean shouldFilter() { return true; // 对所有请求进行过滤 }
@Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest();
String requestURI = request.getRequestURI(); // 排除ureport预览时“过期”报错 if (!RegexUtil.pathAnyMatch(requestURI, "/api-config/ureport/designer/savePreviewData")) { // 1. 检查URL参数 checkUrlParameters(request, ctx);
// 2. 检查请求体 if (isJsonRequest(request)) { checkJsonRequestBody(request, ctx); } }
return null; }
/** * 检查请求参数 * * @param request HttpServletRequest * @param ctx RequestContext */ private void checkUrlParameters(HttpServletRequest request, RequestContext ctx) { Map params = request.getParameterMap(); for (Map.Entry entry : params.entrySet()) { for (String value : entry.getValue()) { String result = containsInjection(value); if (result != null) { log.debug("XssZuulFilter#checkUrlParameters-result: {}", result); blockRequest(ctx, String.format("请求参数包含敏感内容: 『%s』,请修正后再次请求。", result)); return; } } } }
/** * 检查请求JSON体 * * @param request HttpServletRequest * @param ctx RequestContext */ private void checkJsonRequestBody(HttpServletRequest request, RequestContext ctx) { try (InputStream in = request.getInputStream()) { if (in != null) { String result = null; String body = StreamUtils.copyToString(in, StandardCharsets.UTF_8); log.debug("XssZuulFilter#checkJsonRequestBody-body: {}", body);
// 先解析JSON,然后检查每个值 if (StrUtil.isNotEmpty(body)) { if (JSONUtil.isJson(body)) { JSON json = JSONUtil.parse(body); log.debug("XssZuulFilter#checkJsonRequestBody-json: {}", json); result = checkJsonObject(json); } else { // 不是有效JSON,直接检查原始内容 result = containsInjection(body); } }
if (result != null) { log.debug("XssZuulFilter#checkJsonRequestBody-result: {}", result); blockRequest(ctx, String.format("请求数据包含敏感内容: 『%s』,请修正后再次提交。", result)); return; }
// 重新写入请求体 ctx.setRequest(new CustomHttpServletRequestWrapper(request, body)); } } catch (IOException e) { e.printStackTrace(); blockRequest(ctx, "敏感内容检查失败: " + e.getMessage()); } }
/** * 检查json体 * * @param json * @return */ private String checkJsonObject(Object json) { String result = null; // 处理JSON对象 if (json instanceof Map) { Map map = (Map) json; log.debug("XssZuulFilter#checkJsonObject-map: {}", map); for (Map.Entry entry : map.entrySet()) { if (entry.getValue() instanceof String) { result = containsInjection((String) entry.getValue()); } else if (entry.getValue() != null) { result = checkJsonObject(entry.getValue()); } if (result != null) { return result; } } } else if (json instanceof List) { // 处理JSON数组 List list = (List) json; log.debug("XssZuulFilter#checkJsonObject-list: {}", list); for (Object item : list) { if (item instanceof String) { result = containsInjection((String) item); } else if (item != null) { result = checkJsonObject(item); } if (result != null) { return result; } } }
return result; }
/** * 是否JSON请求 * * @param request HttpServletRequest * @return true/false */ private boolean isJsonRequest(HttpServletRequest request) { String contentType = request.getContentType(); return contentType != null && contentType.contains("application/json"); }
/** * 匹配正则 * * @param input 输入内容 * @return null / 匹配的group(0) */ private String containsInjection(String input) { if (StrUtil.isEmpty(input)) { return null; }
// 跳过纯数字和布尔值 if ( BooleanUtil.or( input.matches("^\\d+\\.?\\d*$"), input.equalsIgnoreCase("true"), input.equalsIgnoreCase("false") ) ) { return null; }
for (Pattern pattern : injectionPatterns) { Matcher matcher = pattern.matcher(input); if (matcher.find()) { log.debug("XssZuulFilter#containsInjection-input: {}", input); return matcher.group(0); } }
return null; }
/** * 输入消息提示 * * @param ctx RequestContext * @param message 消息内容 */ private void blockRequest(RequestContext ctx, String message) { ResponseWrapper responseWrapper = new ResponseWrapper<>(); responseWrapper.setStatus(false) .setMessage(message); ctx.setSendZuulResponse(false); // 不进行路由 ctx.setResponseStatusCode(HttpStatus.OK.value()); // Bad Request ctx.setResponseBody(JSONUtil.toJsonStr(responseWrapper)); ctx.getResponse().setContentType("application/json;charset=UTF-8"); }
/** * 重新写入请求体 对于请求流 */ private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;
CustomHttpServletRequestWrapper(HttpServletRequest request, String body) { super(request); this.body = body; }
@Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; }
@Override public boolean isReady() { return true; }
@Override public void setReadListener(ReadListener readListener) { throw new UnsupportedOperationException(); }
@Override public int read() throws IOException { return byteArrayInputStream.read(); } }; }
@Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }}
复制代码


写完收工,可以愉快地摸鱼了🎉!

发布于: 刚刚阅读数: 3
用户头像

BugShare

关注

还未添加个人签名 2025-10-18 加入

还未添加个人简介

评论

发布
暂无评论
XSS检测绕过(UTF-7编码绕过)_spring-cloud_BugShare_InfoQ写作社区