fix(security): enforce whitelist with blacklist and harden wildcard rules

This commit is contained in:
kl
2026-03-03 14:35:56 +08:00
parent a20606bf33
commit 2abedc34b4
3 changed files with 51 additions and 4 deletions

View File

@@ -146,11 +146,15 @@ trust.host = *
### Q4: 如何允许子域名
目前不支持通配符域名匹配需要明确列出每个子域名
支持通配符域名匹配可使用 `*.example.com`
```properties
trust.host = cdn.example.com,api.example.com,storage.example.com
trust.host = *.example.com
```
说明
- `*.example.com` 会匹配 `cdn.example.com``api.internal.example.com`但不匹配根域 `example.com`
- 对于 IP 风格通配 `192.168.*``10.*`仅匹配字面量 IPv4 地址不匹配域名
## 🚨 安全事件响应
如果发现可疑的预览请求

View File

@@ -16,6 +16,7 @@ import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
@@ -51,6 +52,9 @@ public class TrustHostFilter implements Filter {
String host = WebUtils.getHost(url);
if (isNotTrustHost(host)) {
String currentHost = host == null ? "UNKNOWN" : host;
if (response instanceof HttpServletResponse httpServletResponse) {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("text/html;charset=UTF-8");
String html = this.notTrustHostHtmlView == null
@@ -70,8 +74,9 @@ public class TrustHostFilter implements Filter {
}
// 如果配置了黑名单,优先检查黑名单
if (CollectionUtils.isNotEmpty(ConfigConstants.getNotTrustHostSet())) {
return matchAnyPattern(host, ConfigConstants.getNotTrustHostSet());
if (CollectionUtils.isNotEmpty(ConfigConstants.getNotTrustHostSet())
&& matchAnyPattern(host, ConfigConstants.getNotTrustHostSet())) {
return true;
}
// 如果配置了白名单,检查是否在白名单中
@@ -121,6 +126,9 @@ public class TrustHostFilter implements Filter {
}
if (pattern.contains("*")) {
if (isIpv4WildcardPattern(pattern)) {
return matchIpv4Wildcard(host, pattern);
}
Pattern compiledPattern = wildcardPatternCache.computeIfAbsent(pattern, key -> Pattern.compile(wildcardToRegex(key)));
return compiledPattern.matcher(host).matches();
}
@@ -128,6 +136,31 @@ public class TrustHostFilter implements Filter {
return host.equals(pattern);
}
private boolean isIpv4WildcardPattern(String pattern) {
return pattern.matches("^[0-9.*]+$") && pattern.contains(".");
}
private boolean matchIpv4Wildcard(String host, String pattern) {
if (parseLiteralIpv4(host) == null) {
return false;
}
String[] hostParts = host.split("\\.");
String[] patternParts = pattern.split("\\.");
if (hostParts.length != 4 || patternParts.length < 1 || patternParts.length > 4) {
return false;
}
for (int i = 0; i < patternParts.length; i++) {
String p = patternParts[i];
if ("*".equals(p)) {
continue;
}
if (!p.equals(hostParts[i])) {
return false;
}
}
return true;
}
private String wildcardToRegex(String wildcard) {
StringBuilder regexBuilder = new StringBuilder("^");
String[] parts = wildcard.split("\\*", -1);

View File

@@ -21,6 +21,7 @@ public class TrustHostFilterTests {
assert trustHostFilter.isNotTrustHost("192.168.1.10");
assert !trustHostFilter.isNotTrustHost("8.8.8.8");
assert !trustHostFilter.isNotTrustHost("192.168.evil.com");
}
@Test
@@ -79,4 +80,13 @@ public class TrustHostFilterTests {
assert trustHostFilter.isNotTrustHost("10.1.2.3");
assert !trustHostFilter.isNotTrustHost("8.8.8.8");
}
@Test
void shouldStillEnforceWhitelistWhenBlacklistConfigured() {
ConfigConstants.setTrustHostValue("internal.example.com");
ConfigConstants.setNotTrustHostValue("127.0.0.1");
assert !trustHostFilter.isNotTrustHost("internal.example.com");
assert trustHostFilter.isNotTrustHost("8.8.8.8");
}
}