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 regex
xss:
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<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()));
}
}
}
寫完收工,可以愉快地摸魚了??!

浙公網安備 33010602011771號