449 lines
18 KiB
Java
449 lines
18 KiB
Java
package com.aluer.security;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.time.LocalDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.regex.Pattern;
|
|
import java.util.regex.Matcher;
|
|
|
|
@Component
|
|
public class WebApplicationFirewall {
|
|
private static final Logger logger = LoggerFactory.getLogger(WebApplicationFirewall.class);
|
|
|
|
private final Map<String, WAFRule> rules = new ConcurrentHashMap<>();
|
|
private final Queue<WAFEvent> eventLog = new ConcurrentLinkedQueue<>();
|
|
private final Map<String, RequestStats> requestStats = new ConcurrentHashMap<>();
|
|
private final Map<String, ClientReputation> clientReputations = new ConcurrentHashMap<>();
|
|
private final AtomicLong totalRequests = new AtomicLong(0);
|
|
private final AtomicLong blockedRequests = new AtomicLong(0);
|
|
private final AtomicLong suspiciousRequests = new AtomicLong(0);
|
|
|
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
public WebApplicationFirewall() {
|
|
initializeDefaultRules();
|
|
}
|
|
|
|
private void initializeDefaultRules() {
|
|
addRule("SQL_INJECTION", "HIGH", Pattern.compile(
|
|
".*(union|select|insert|update|delete|drop|create|alter|exec|execute|script|<script).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "SQL注入攻击");
|
|
|
|
addRule("XSS_ATTACK", "HIGH", Pattern.compile(
|
|
".*(<script|javascript:|onerror=|onload=|alert\\(|eval\\(|document\\.|window\\.).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "跨站脚本攻击(XSS)");
|
|
|
|
addRule("PATH_TRAVERSAL", "MEDIUM", Pattern.compile(
|
|
".*(\\.\\./|\\.\\.\\\\|%2e%2e/|%2e%2e\\\\).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "路径遍历攻击");
|
|
|
|
addRule("COMMAND_INJECTION", "HIGH", Pattern.compile(
|
|
".*(;|\\||`|$|&&|\\|\\||>|<).*(ls|cat|rm|mv|cp|chmod|chown|wget|curl|nc|bash|sh).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "命令注入攻击");
|
|
|
|
addRule("LDAP_INJECTION", "MEDIUM", Pattern.compile(
|
|
".*(\\(|\\)|\\*|\\?|\\?|\\?|\\\\n|\\\\r).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "LDAP注入攻击");
|
|
|
|
addRule("XML_INJECTION", "MEDIUM", Pattern.compile(
|
|
".*(<!DOCTYPE|<!ENTITY|<!ELEMENT|CDATA|\\?xml).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "XML注入攻击");
|
|
|
|
addRule("FILE_INCLUDE", "MEDIUM", Pattern.compile(
|
|
".*(include|require|include_once|require_once|php://|file://).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "文件包含攻击");
|
|
|
|
addRule("SSRF_ATTACK", "HIGH", Pattern.compile(
|
|
".*(https?://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0|169\\.254\\.169\\.254|::1)|gopher://|dict://|ftp://).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "服务端请求伪造(SSRF)");
|
|
|
|
addRule("SSTI_ATTACK", "HIGH", Pattern.compile(
|
|
".*(\\{\\{.*\\}\\}|\\$\\{.*\\}|<%=.*%>|#\\{.*\\}).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "服务端模板注入(SSTI)");
|
|
|
|
addRule("JNDI_INJECTION", "HIGH", Pattern.compile(
|
|
".*\\$\\{jndi:(ldap|ldaps|rmi|dns|iiop):.*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "JNDI注入");
|
|
|
|
addRule("JAVA_DESERIALIZATION", "HIGH", Pattern.compile(
|
|
".*(rO0AB|ac ed 00 05|java\\.util\\.PriorityQueue|ysoserial).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "Java反序列化利用");
|
|
|
|
addRule("CRLF_INJECTION", "MEDIUM", Pattern.compile(
|
|
".*(%0d|%0a|\\\\r|\\\\n|\\r|\\n).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "CRLF注入");
|
|
|
|
addRule("OPEN_REDIRECT", "MEDIUM", Pattern.compile(
|
|
".*(redirect=|url=|next=|return=)https?://.*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "开放重定向");
|
|
|
|
addRule("VALIDATE_EMAIL", "LOW", Pattern.compile(
|
|
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
|
|
), "邮箱格式验证");
|
|
|
|
addRule("VALIDATE_IP", "LOW", Pattern.compile(
|
|
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
), "IP地址格式验证");
|
|
|
|
addRule("BLOCK_SENSITIVE", "HIGH", Pattern.compile(
|
|
".*(password|passwd|pwd|secret|token|auth|api_key|apikey|private_key).*",
|
|
Pattern.CASE_INSENSITIVE
|
|
), "敏感信息访问");
|
|
|
|
logger.info("Initialized {} WAF rules", rules.size());
|
|
}
|
|
|
|
public void addRule(String name, String severity, Pattern pattern, String description) {
|
|
WAFRule rule = new WAFRule(name, severity, pattern, description);
|
|
rules.put(name, rule);
|
|
logger.info("Added WAF rule: {} [{}]", name, severity);
|
|
}
|
|
|
|
public WAFResult checkRequest(String clientIP, String method, String uri, String queryString, Map<String, String> headers, String body) {
|
|
WAFResult result = new WAFResult();
|
|
result.setClientIP(clientIP);
|
|
result.setMethod(method);
|
|
result.setUri(uri);
|
|
result.setTimestamp(LocalDateTime.now());
|
|
|
|
totalRequests.incrementAndGet();
|
|
|
|
updateClientStats(clientIP);
|
|
|
|
if (isClientBlocked(clientIP)) {
|
|
result.setBlocked(true);
|
|
result.setReason("Client is blocked");
|
|
blockedRequests.incrementAndGet();
|
|
logEvent(clientIP, method, uri, "BLOCKED", "Client blocked");
|
|
return result;
|
|
}
|
|
|
|
if (isRateLimited(clientIP)) {
|
|
result.setBlocked(true);
|
|
result.setReason("Rate limit exceeded");
|
|
blockedRequests.incrementAndGet();
|
|
updateClientReputation(clientIP, -20);
|
|
logEvent(clientIP, method, uri, "BLOCKED", "Rate limit exceeded");
|
|
return result;
|
|
}
|
|
|
|
StringBuilder fullRequestBuilder = new StringBuilder();
|
|
fullRequestBuilder.append(method).append(" ").append(uri);
|
|
if (queryString != null) {
|
|
fullRequestBuilder.append("?").append(queryString);
|
|
}
|
|
for (Map.Entry<String, String> header : headers.entrySet()) {
|
|
if (header.getValue() != null) {
|
|
fullRequestBuilder.append("\n").append(header.getKey()).append(": ").append(header.getValue());
|
|
}
|
|
}
|
|
if (body != null) {
|
|
fullRequestBuilder.append("\n").append(body);
|
|
}
|
|
String fullRequest = fullRequestBuilder.toString();
|
|
|
|
for (WAFRule rule : rules.values()) {
|
|
if (!rule.isEnabled()) continue;
|
|
|
|
Matcher matcher = rule.getPattern().matcher(fullRequest);
|
|
if (matcher.find()) {
|
|
result.addMatchedRule(rule.getName());
|
|
|
|
if ("HIGH".equals(rule.getSeverity())) {
|
|
result.setBlocked(true);
|
|
result.setReason("High severity rule matched: " + rule.getDescription());
|
|
blockedRequests.incrementAndGet();
|
|
updateClientReputation(clientIP, -15);
|
|
logEvent(clientIP, method, uri, "BLOCKED", rule.getName() + " - " + rule.getDescription());
|
|
} else if ("MEDIUM".equals(rule.getSeverity())) {
|
|
result.setSuspicious(true);
|
|
result.addWarning("Medium severity rule matched: " + rule.getDescription());
|
|
suspiciousRequests.incrementAndGet();
|
|
updateClientReputation(clientIP, -5);
|
|
logEvent(clientIP, method, uri, "SUSPICIOUS", rule.getName());
|
|
} else {
|
|
result.addWarning("Low severity rule matched: " + rule.getDescription());
|
|
logEvent(clientIP, method, uri, "WARNING", rule.getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Map.Entry<String, String> header : headers.entrySet()) {
|
|
String headerValue = header.getValue();
|
|
if (headerValue != null && headerValue.length() > 1000) {
|
|
result.addWarning("Unusually long header: " + header.getKey());
|
|
suspiciousRequests.incrementAndGet();
|
|
}
|
|
if (headerValue != null && looksLikeHeaderSmuggling(header.getKey(), headerValue)) {
|
|
result.setSuspicious(true);
|
|
result.addWarning("Possible header smuggling: " + header.getKey());
|
|
suspiciousRequests.incrementAndGet();
|
|
}
|
|
}
|
|
|
|
if (!result.isBlocked()) {
|
|
logEvent(clientIP, method, uri, "ALLOWED", "Request passed all rules");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private boolean looksLikeHeaderSmuggling(String headerName, String headerValue) {
|
|
String normalizedName = headerName == null ? "" : headerName.toLowerCase(Locale.ROOT);
|
|
String normalizedValue = headerValue == null ? "" : headerValue.toLowerCase(Locale.ROOT);
|
|
if (normalizedValue.contains("\r") || normalizedValue.contains("\n")) {
|
|
return true;
|
|
}
|
|
return normalizedName.equals("host")
|
|
&& (normalizedValue.contains("127.0.0.1") || normalizedValue.contains("localhost"));
|
|
}
|
|
|
|
private void updateClientStats(String clientIP) {
|
|
RequestStats stats = requestStats.computeIfAbsent(clientIP, k -> new RequestStats(clientIP));
|
|
stats.incrementRequestCount();
|
|
|
|
long oneMinuteAgo = System.currentTimeMillis() - 60000;
|
|
if (stats.getWindowStart() < oneMinuteAgo) {
|
|
stats.setRequestCountInLastMinute(stats.getRequestCount() - stats.getRequestCountInLastMinute());
|
|
stats.setWindowStart(System.currentTimeMillis());
|
|
}
|
|
stats.incrementRequestCountInLastMinute();
|
|
}
|
|
|
|
private boolean isClientBlocked(String clientIP) {
|
|
ClientReputation rep = clientReputations.get(clientIP);
|
|
return rep != null && rep.getScore() < -100;
|
|
}
|
|
|
|
private boolean isRateLimited(String clientIP) {
|
|
RequestStats stats = requestStats.get(clientIP);
|
|
if (stats == null) return false;
|
|
|
|
return stats.getRequestCountInLastMinute() > 120;
|
|
}
|
|
|
|
private void updateClientReputation(String clientIP, int scoreChange) {
|
|
ClientReputation rep = clientReputations.computeIfAbsent(clientIP, k -> new ClientReputation(clientIP));
|
|
rep.adjustScore(scoreChange);
|
|
}
|
|
|
|
public void blockClient(String clientIP, String reason) {
|
|
ClientReputation rep = clientReputations.computeIfAbsent(clientIP, k -> new ClientReputation(clientIP));
|
|
rep.setScore(-200);
|
|
rep.setBlockReason(reason);
|
|
logger.info("Blocked client: {} - {}", clientIP, reason);
|
|
}
|
|
|
|
public void unblockClient(String clientIP) {
|
|
ClientReputation rep = clientReputations.get(clientIP);
|
|
if (rep != null) {
|
|
rep.setScore(0);
|
|
rep.setBlockReason(null);
|
|
logger.info("Unblocked client: {}", clientIP);
|
|
}
|
|
}
|
|
|
|
public Map<String, Object> getStats() {
|
|
Map<String, Object> stats = new HashMap<>();
|
|
stats.put("totalRequests", totalRequests.get());
|
|
stats.put("blockedRequests", blockedRequests.get());
|
|
stats.put("suspiciousRequests", suspiciousRequests.get());
|
|
stats.put("activeClients", requestStats.size());
|
|
stats.put("blockedClients", clientReputations.values().stream()
|
|
.filter(r -> r.getScore() < -100).count());
|
|
stats.put("rulesLoaded", rules.size());
|
|
|
|
return stats;
|
|
}
|
|
|
|
public List<WAFEvent> getRecentEvents(int limit) {
|
|
List<WAFEvent> events = new ArrayList<>();
|
|
int count = 0;
|
|
for (WAFEvent event : eventLog) {
|
|
if (count++ >= limit) break;
|
|
events.add(event);
|
|
}
|
|
return events;
|
|
}
|
|
|
|
public Map<String, ClientReputation> getBlockedClients() {
|
|
Map<String, ClientReputation> blocked = new HashMap<>();
|
|
for (Map.Entry<String, ClientReputation> entry : clientReputations.entrySet()) {
|
|
if (entry.getValue().getScore() < -100) {
|
|
blocked.put(entry.getKey(), entry.getValue());
|
|
}
|
|
}
|
|
return blocked;
|
|
}
|
|
|
|
private void logEvent(String clientIP, String method, String uri, String action, String details) {
|
|
WAFEvent event = new WAFEvent(clientIP, method, uri, action, details, LocalDateTime.now());
|
|
eventLog.offer(event);
|
|
|
|
if (eventLog.size() > 10000) {
|
|
eventLog.poll();
|
|
}
|
|
}
|
|
|
|
public void enableRule(String ruleName) {
|
|
WAFRule rule = rules.get(ruleName);
|
|
if (rule != null) {
|
|
rule.setEnabled(true);
|
|
logger.info("Enabled WAF rule: {}", ruleName);
|
|
}
|
|
}
|
|
|
|
public void disableRule(String ruleName) {
|
|
WAFRule rule = rules.get(ruleName);
|
|
if (rule != null) {
|
|
rule.setEnabled(false);
|
|
logger.info("Disabled WAF rule: {}", ruleName);
|
|
}
|
|
}
|
|
|
|
public static class WAFRule {
|
|
private final String name;
|
|
private final String severity;
|
|
private final Pattern pattern;
|
|
private final String description;
|
|
private volatile boolean enabled = true;
|
|
|
|
public WAFRule(String name, String severity, Pattern pattern, String description) {
|
|
this.name = name;
|
|
this.severity = severity;
|
|
this.pattern = pattern;
|
|
this.description = description;
|
|
}
|
|
|
|
public String getName() { return name; }
|
|
public String getSeverity() { return severity; }
|
|
public Pattern getPattern() { return pattern; }
|
|
public String getDescription() { return description; }
|
|
public boolean isEnabled() { return enabled; }
|
|
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
}
|
|
|
|
public static class WAFResult {
|
|
private String clientIP;
|
|
private String method;
|
|
private String uri;
|
|
private boolean blocked;
|
|
private boolean suspicious;
|
|
private String reason;
|
|
private List<String> matchedRules = new ArrayList<>();
|
|
private List<String> warnings = new ArrayList<>();
|
|
private LocalDateTime timestamp;
|
|
|
|
public String getClientIP() { return clientIP; }
|
|
public void setClientIP(String clientIP) { this.clientIP = clientIP; }
|
|
public String getMethod() { return method; }
|
|
public void setMethod(String method) { this.method = method; }
|
|
public String getUri() { return uri; }
|
|
public void setUri(String uri) { this.uri = uri; }
|
|
public boolean isBlocked() { return blocked; }
|
|
public void setBlocked(boolean blocked) { this.blocked = blocked; }
|
|
public boolean isSuspicious() { return suspicious; }
|
|
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
|
|
public String getReason() { return reason; }
|
|
public void setReason(String reason) { this.reason = reason; }
|
|
public List<String> getMatchedRules() { return matchedRules; }
|
|
public void addMatchedRule(String rule) { this.matchedRules.add(rule); }
|
|
public List<String> getWarnings() { return warnings; }
|
|
public void addWarning(String warning) { this.warnings.add(warning); }
|
|
public LocalDateTime getTimestamp() { return timestamp; }
|
|
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
|
}
|
|
|
|
public static class RequestStats {
|
|
private final String clientIP;
|
|
private volatile long requestCount;
|
|
private volatile long requestCountInLastMinute;
|
|
private volatile long windowStart;
|
|
|
|
public RequestStats(String clientIP) {
|
|
this.clientIP = clientIP;
|
|
this.windowStart = System.currentTimeMillis();
|
|
}
|
|
|
|
public void incrementRequestCount() { requestCount++; }
|
|
public void incrementRequestCountInLastMinute() { requestCountInLastMinute++; }
|
|
|
|
public String getClientIP() { return clientIP; }
|
|
public long getRequestCount() { return requestCount; }
|
|
public long getRequestCountInLastMinute() { return requestCountInLastMinute; }
|
|
public long getWindowStart() { return windowStart; }
|
|
public void setRequestCountInLastMinute(long count) { this.requestCountInLastMinute = count; }
|
|
public void setWindowStart(long windowStart) { this.windowStart = windowStart; }
|
|
}
|
|
|
|
public static class ClientReputation {
|
|
private final String clientIP;
|
|
private volatile int score;
|
|
private volatile String blockReason;
|
|
private volatile long lastUpdated;
|
|
|
|
public ClientReputation(String clientIP) {
|
|
this.clientIP = clientIP;
|
|
this.score = 0;
|
|
this.lastUpdated = System.currentTimeMillis();
|
|
}
|
|
|
|
public void adjustScore(int delta) {
|
|
this.score = Math.max(-200, Math.min(100, this.score + delta));
|
|
this.lastUpdated = System.currentTimeMillis();
|
|
}
|
|
|
|
public String getClientIP() { return clientIP; }
|
|
public int getScore() { return score; }
|
|
public void setScore(int score) { this.score = score; }
|
|
public String getBlockReason() { return blockReason; }
|
|
public void setBlockReason(String blockReason) { this.blockReason = blockReason; }
|
|
public long getLastUpdated() { return lastUpdated; }
|
|
}
|
|
|
|
public static class WAFEvent {
|
|
private final String clientIP;
|
|
private final String method;
|
|
private final String uri;
|
|
private final String action;
|
|
private final String details;
|
|
private final LocalDateTime timestamp;
|
|
|
|
public WAFEvent(String clientIP, String method, String uri, String action, String details, LocalDateTime timestamp) {
|
|
this.clientIP = clientIP;
|
|
this.method = method;
|
|
this.uri = uri;
|
|
this.action = action;
|
|
this.details = details;
|
|
this.timestamp = timestamp;
|
|
}
|
|
|
|
public String getClientIP() { return clientIP; }
|
|
public String getMethod() { return method; }
|
|
public String getUri() { return uri; }
|
|
public String getAction() { return action; }
|
|
public String getDetails() { return details; }
|
|
public LocalDateTime getTimestamp() { return timestamp; }
|
|
}
|
|
}
|