mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-03-26 02:53:53 +08:00
fix(security): enforce whitelist with blacklist and harden wildcard rules
This commit is contained in:
@@ -146,11 +146,15 @@ trust.host = *
|
|||||||
|
|
||||||
### Q4: 如何允许子域名?
|
### Q4: 如何允许子域名?
|
||||||
|
|
||||||
目前不支持通配符域名匹配,需要明确列出每个子域名:
|
已支持通配符域名匹配,可使用 `*.example.com`:
|
||||||
```properties
|
```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 地址,不匹配域名
|
||||||
|
|
||||||
## 🚨 安全事件响应
|
## 🚨 安全事件响应
|
||||||
|
|
||||||
如果发现可疑的预览请求:
|
如果发现可疑的预览请求:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import jakarta.servlet.FilterConfig;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -51,6 +52,9 @@ public class TrustHostFilter implements Filter {
|
|||||||
String host = WebUtils.getHost(url);
|
String host = WebUtils.getHost(url);
|
||||||
if (isNotTrustHost(host)) {
|
if (isNotTrustHost(host)) {
|
||||||
String currentHost = host == null ? "UNKNOWN" : host;
|
String currentHost = host == null ? "UNKNOWN" : host;
|
||||||
|
if (response instanceof HttpServletResponse httpServletResponse) {
|
||||||
|
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
}
|
||||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
String html = this.notTrustHostHtmlView == null
|
String html = this.notTrustHostHtmlView == null
|
||||||
@@ -70,8 +74,9 @@ public class TrustHostFilter implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果配置了黑名单,优先检查黑名单
|
// 如果配置了黑名单,优先检查黑名单
|
||||||
if (CollectionUtils.isNotEmpty(ConfigConstants.getNotTrustHostSet())) {
|
if (CollectionUtils.isNotEmpty(ConfigConstants.getNotTrustHostSet())
|
||||||
return matchAnyPattern(host, ConfigConstants.getNotTrustHostSet());
|
&& matchAnyPattern(host, ConfigConstants.getNotTrustHostSet())) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果配置了白名单,检查是否在白名单中
|
// 如果配置了白名单,检查是否在白名单中
|
||||||
@@ -121,6 +126,9 @@ public class TrustHostFilter implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pattern.contains("*")) {
|
if (pattern.contains("*")) {
|
||||||
|
if (isIpv4WildcardPattern(pattern)) {
|
||||||
|
return matchIpv4Wildcard(host, pattern);
|
||||||
|
}
|
||||||
Pattern compiledPattern = wildcardPatternCache.computeIfAbsent(pattern, key -> Pattern.compile(wildcardToRegex(key)));
|
Pattern compiledPattern = wildcardPatternCache.computeIfAbsent(pattern, key -> Pattern.compile(wildcardToRegex(key)));
|
||||||
return compiledPattern.matcher(host).matches();
|
return compiledPattern.matcher(host).matches();
|
||||||
}
|
}
|
||||||
@@ -128,6 +136,31 @@ public class TrustHostFilter implements Filter {
|
|||||||
return host.equals(pattern);
|
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) {
|
private String wildcardToRegex(String wildcard) {
|
||||||
StringBuilder regexBuilder = new StringBuilder("^");
|
StringBuilder regexBuilder = new StringBuilder("^");
|
||||||
String[] parts = wildcard.split("\\*", -1);
|
String[] parts = wildcard.split("\\*", -1);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class TrustHostFilterTests {
|
|||||||
|
|
||||||
assert trustHostFilter.isNotTrustHost("192.168.1.10");
|
assert trustHostFilter.isNotTrustHost("192.168.1.10");
|
||||||
assert !trustHostFilter.isNotTrustHost("8.8.8.8");
|
assert !trustHostFilter.isNotTrustHost("8.8.8.8");
|
||||||
|
assert !trustHostFilter.isNotTrustHost("192.168.evil.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -79,4 +80,13 @@ public class TrustHostFilterTests {
|
|||||||
assert trustHostFilter.isNotTrustHost("10.1.2.3");
|
assert trustHostFilter.isNotTrustHost("10.1.2.3");
|
||||||
assert !trustHostFilter.isNotTrustHost("8.8.8.8");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user