XSS 检测绕过(UTF-7 编码绕过)
 作者:BugShare
- 2025-10-29  山东
- 本文字数:5157 字 - 阅读完需:约 17 分钟 

📢 叮咚,现场运维来消息了,说项目被检测到有高危漏洞,要求修复,以为就是 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
版权声明: 本文为 InfoQ 作者【BugShare】的原创文章。
原文链接:【http://xie.infoq.cn/article/f15bba49a733aa8f1cf31a3c7】。文章转载请联系作者。

BugShare
关注
还未添加个人签名 2025-10-18 加入
还未添加个人简介







 
    
评论