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为例

yaml 复制代码
# xss regex
xss:
  enable: true
  regexes:
    # UTF-7编码绕过
    - "(?i)(\\+[A-Za-z0-9+/=,]+\\-|\\+(?:ADw|AD4|AC8|ACI|AHs|AH0)-)"
java 复制代码
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<String> regexes = new ArrayList<>();
    private static List<Pattern> 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<String, String[]> params = request.getParameterMap();
        for (Map.Entry<String, String[]> 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<Boolean, ?> 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 小时前
SpringAI让Java会画画?用Azure OpenAI生成AI图片太惊艳了!
后端·spring·openai
ZhengEnCi3 小时前
统一认证平台完全指南-从单点登录到企业级安全访问的数字化利器
后端
9号达人3 小时前
if-else 优化的折中思考:不是消灭分支,而是控制风险
java·后端·面试
回家路上绕了弯3 小时前
高并发后台系统设计要点:从流量削峰到低延迟的实战指南
分布式·后端
Yefimov4 小时前
3. DPDK:更好的压榨cpu--并行计算
后端
两万五千个小时4 小时前
LangChain 入门教程:06LangGraph工作流编排
人工智能·后端
oak隔壁找我4 小时前
MyBatis的MapperFactoryBean详解
后端
王道长AWS_服务器4 小时前
AWS Elastic Load Balancing(ELB)—— 多站点负载均衡的正确打开方式
后端·程序员·aws
oak隔壁找我4 小时前
Spring BeanFactory 和 FactoryBean 详解
后端