zentinelproxy/zentinel-modsec
GitHub: zentinelproxy/zentinel-modsec
纯 Rust 实现的 ModSecurity 规则引擎,兼容 OWASP CRS,为 Rust Web 应用提供零 C 依赖、高性能的 Web 应用防火墙(WAF)功能。
Stars: 15 | Forks: 5
# zentinel-modsec
[](https://crates.io/crates/zentinel-modsec)
[](https://docs.rs/zentinel-modsec)
[](LICENSE)
**纯 Rust 实现的 ModSecurity,完全兼容 OWASP CRS。**
一个完全使用 Rust 编写的 ModSecurity 规则引擎,零 C/C++ 依赖。加载并执行 OWASP Core Rule Set (CRS) 规则,在任何 Rust 应用程序中实现 Web 应用防火墙 (WAF) 功能。
## 性能:比 libmodsecurity 快 10-30 倍
| Benchmark | zentinel-modsec | libmodsecurity (C++) | 提速 |
|-----------|-----------------|----------------------|---------|
| Clean request | 161 ns | 4,831 ns | **快 30 倍** |
| SQLi detection | 295 ns | 5,545 ns | **快 19 倍** |
| Body processing | 1.24 µs | 12.93 µs | **快 10 倍** |
| Rule parsing | 2.75 µs | 10.07 µs | **快 3.6 倍** |
| **吞吐量** | **6.2M req/s** | 207K req/s | **高出 30 倍** |
## 特性
- **完全兼容 OWASP CRS** - 解析并执行 800 多条 CRS 规则
- **纯 Rust** - 无 libmodsecurity,无 C/C++ 依赖,无 FFI
- **支持 SecLang** - 加载标准的 ModSecurity `.conf` 规则文件
- **内置检测** - 原生 `@detectSQLi` 和 `@detectXSS` 操作符(纯 Rust libinjection)
- **所有操作符** - `@rx`、`@pm`、`@pmFromFile`、`@contains`、`@streq`、`@ipMatch` 等 30 多种
- **所有转换** - `t:lowercase`、`t:urlDecode`、`t:base64Decode`、`t:htmlEntityDecode` 等 30 多种
- **线程安全** - 支持 `Send + Sync`,可安全用于并发请求处理
- **支持异步** - 适用于 tokio、async-std 或任何异步 runtime
- **零 Unsafe** - `#![deny(unsafe_code)]`
## 快速开始
将其添加到您的 `Cargo.toml` 中:
```
[dependencies]
zentinel-modsec = "0.1"
```
### 基础用法
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
// Compile rules once; reuse the engine for all requests.
let modsec = ModSecurity::from_string(r#"
SecRuleEngine On
SecRule REQUEST_URI "@contains /admin" \
"id:1,phase:1,deny,status:403,msg:'Admin access blocked'"
"#)?;
// Process a request
let mut tx = modsec.new_transaction();
tx.process_uri("/admin/dashboard", "GET", "HTTP/1.1")?;
tx.add_request_header("Host", "example.com")?;
tx.add_request_header("User-Agent", "Mozilla/5.0")?;
tx.process_request_headers()?;
// Check for intervention (block/redirect/etc)
if let Some(intervention) = tx.intervention() {
println!("Blocked: status={}, rules={:?}",
intervention.status,
intervention.rule_ids);
}
Ok(())
}
```
### 加载 OWASP CRS 规则
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
// Point at an entry file that `Include`s crs-setup.conf and the rule files
// (CRS ships such a layout), or a single combined ruleset file.
let modsec = ModSecurity::from_file("/etc/modsecurity/main.conf")?;
println!("Loaded {} rules", modsec.rule_count());
Ok(())
}
```
### SQL 注入检测
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
let modsec = ModSecurity::from_string(r#"
SecRuleEngine On
SecRule ARGS "@detectSQLi" \
"id:942100,phase:2,deny,status:403,msg:'SQL Injection detected'"
"#)?;
let mut tx = modsec.new_transaction();
// Simulate a request with SQLi payload
tx.process_uri("/search?q=' OR 1=1--", "GET", "HTTP/1.1")?;
tx.process_request_headers()?;
assert!(tx.has_intervention());
println!("SQLi attack blocked!");
Ok(())
}
```
### XSS 检测
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
let modsec = ModSecurity::from_string(r#"
SecRuleEngine On
SecRule ARGS "@detectXSS" \
"id:941100,phase:2,deny,status:403,msg:'XSS detected'"
"#)?;
let mut tx = modsec.new_transaction();
tx.process_uri("/comment?text=", "GET", "HTTP/1.1")?;
tx.process_request_headers()?;
assert!(tx.has_intervention());
println!("XSS attack blocked!");
Ok(())
}
```
### 请求 Body 检查
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
let modsec = ModSecurity::from_string(r#"
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@detectSQLi" \
"id:942110,phase:2,deny,status:403,msg:'SQLi in body'"
"#)?;
let mut tx = modsec.new_transaction();
tx.process_uri("/api/login", "POST", "HTTP/1.1")?;
tx.add_request_header("Content-Type", "application/x-www-form-urlencoded")?;
tx.process_request_headers()?;
// Add request body
tx.append_request_body(b"username=admin&password=' OR 1=1--")?;
tx.process_request_body()?;
assert!(tx.has_intervention());
Ok(())
}
```
### 仅检测模式
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
let modsec = ModSecurity::from_string(r#"
SecRuleEngine DetectionOnly
SecRule REQUEST_URI "@contains /admin" "id:1,phase:1,deny"
"#)?;
let mut tx = modsec.new_transaction();
tx.process_uri("/admin", "GET", "HTTP/1.1")?;
tx.process_request_headers()?;
// Rule matched but no intervention (detection only)
assert!(!tx.has_intervention());
assert!(tx.matched_rules().contains(&"1".to_string()));
println!("Detected but not blocked: {:?}", tx.matched_rules());
Ok(())
}
```
### 异常评分
```
use zentinel_modsec::ModSecurity;
fn main() -> zentinel_modsec::Result<()> {
let modsec = ModSecurity::from_string(r#"
SecRuleEngine On
# Increment score for suspicious patterns
SecRule REQUEST_URI "@contains /admin" \
"id:1,phase:1,pass,setvar:'TX.anomaly_score=+5'"
SecRule REQUEST_HEADERS:User-Agent "@contains sqlmap" \
"id:2,phase:1,pass,setvar:'TX.anomaly_score=+10'"
# Block if score exceeds threshold
SecRule TX:anomaly_score "@ge 10" \
"id:100,phase:1,deny,status:403,msg:'Anomaly score exceeded'"
"#)?;
let mut tx = modsec.new_transaction();
tx.process_uri("/admin", "GET", "HTTP/1.1")?;
tx.add_request_header("User-Agent", "sqlmap/1.0")?;
tx.process_request_headers()?;
println!("Anomaly score: {}", tx.anomaly_score());
assert!(tx.has_intervention());
Ok(())
}
```
## 框架集成
### Axum
```
use axum::{
body::Body,
extract::State,
http::{Request, StatusCode},
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
use zentinel_modsec::ModSecurity;
use std::sync::Arc;
async fn waf_middleware(
State(modsec): State>,
request: Request,
next: Next,
) -> Result {
let mut tx = modsec.new_transaction();
// Process request
tx.process_uri(
request.uri().path_and_query().map(|pq| pq.as_str()).unwrap_or("/"),
request.method().as_str(),
"HTTP/1.1",
).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
for (name, value) in request.headers() {
if let Ok(v) = value.to_str() {
let _ = tx.add_request_header(name.as_str(), v);
}
}
tx.process_request_headers()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Check for intervention
if let Some(intervention) = tx.intervention() {
return Err(StatusCode::from_u16(intervention.status).unwrap_or(StatusCode::FORBIDDEN));
}
Ok(next.run(request).await)
}
#[tokio::main]
async fn main() {
let modsec = Arc::new(ModSecurity::from_file("/etc/modsecurity/main.conf").unwrap());
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.layer(middleware::from_fn_with_state(modsec.clone(), waf_middleware))
.with_state(modsec);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
```
### Actix-web
```
use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, middleware};
use zentinel_modsec::ModSecurity;
use std::sync::Arc;
async fn waf_check(
req: HttpRequest,
modsec: web::Data>,
) -> Option {
let mut tx = modsec.new_transaction();
tx.process_uri(req.uri().path_and_query().map(|pq| pq.as_str()).unwrap_or("/"),
req.method().as_str(),
"HTTP/1.1").ok()?;
for (name, value) in req.headers() {
if let Ok(v) = value.to_str() {
let _ = tx.add_request_header(name.as_str(), v);
}
}
tx.process_request_headers().ok()?;
tx.intervention().map(|i| {
HttpResponse::build(actix_web::http::StatusCode::from_u16(i.status).unwrap())
.body(format!("Blocked by rule: {:?}", i.rule_ids))
})
}
```
## 支持的 SecLang 指令
### 指令
| 指令 | 状态 | 描述 |
|-----------|--------|-------------|
| `SecRule` | ✅ | 主规则指令 |
| `SecAction` | ✅ | 无条件操作 |
| `SecMarker` | ✅ | 用于 skipAfter 的命名标记 |
| `SecRuleEngine` | ✅ | On/Off/DetectionOnly |
| `SecRequestBodyAccess` | ✅ | 开启 body 检查 |
| `SecResponseBodyAccess` | ✅ | 开启响应检查 |
| `Include` | ✅ | 包含其他规则文件 |
### 操作符
| 操作符 | 状态 | 描述 |
|----------|--------|-------------|
| `@rx` | ✅ | 正则表达式 |
| `@pm` | ✅ | 短语匹配 (Aho-Corasick) |
| `@pmFromFile` | ✅ | 从文件进行短语匹配 |
| `@contains` | ✅ | 字符串包含 |
| `@streq` | ✅ | 字符串相等 |
| `@beginsWith` | ✅ | 字符串以某项开头 |
| `@endsWith` | ✅ | 字符串以某项结尾 |
| `@within` | ✅ | 列表中的值 |
| `@eq`, `@ne`, `@gt`, `@ge`, `@lt`, `@le` | ✅ | 数值比较 |
| `@detectSQLi` | ✅ | SQL 注入检测 |
| `@detectXSS` | ✅ | XSS 检测 |
| `@ipMatch` | ✅ | IP/CIDR 匹配 |
| `@validateUrlEncoding` | ✅ | URL 编码验证 |
| `@validateUtf8Encoding` | ✅ | UTF-8 验证 |
### 转换
| 转换 | 状态 | 描述 |
|----------------|--------|-------------|
| `t:lowercase` | ✅ | 转换为小写 |
| `t:uppercase` | ✅ | 转换为大写 |
| `t:urlDecode` | ✅ | URL 解码 |
| `t:urlDecodeUni` | ✅ | URL 解码 (Unicode) |
| `t:base64Decode` | ✅ | Base64 解码 |
| `t:base64Encode` | ✅ | Base64 编码 |
| `t:htmlEntityDecode` | ✅ | HTML 实体解码 |
| `t:removeWhitespace` | ✅ | 移除空白字符 |
| `t:compressWhitespace` | ✅ | 压缩空白字符 |
| `t:normalizePath` | ✅ | 规范化路径 |
| `t:normalizePathWin` | ✅ | 规范化 Windows 路径 |
| `t:cmdLine` | ✅ | 命令行规范化 |
| `t:md5` | ✅ | MD5 哈希 |
| `t:sha1` | ✅ | SHA1 哈希 |
| `t:hexEncode` | ✅ | 十六进制编码 |
| `t:hexDecode` | ✅ | 十六进制解码 |
### 动作
| 动作 | 状态 | 描述 |
|--------|--------|-------------|
| `deny` | ✅ | 阻断请求 |
| `block` | ✅ | 以默认状态阻断 |
| `pass` | ✅ | 继续处理 |
| `allow` | ✅ | 跳过剩余规则 |
| `redirect` | ✅ | 重定向至 URL |
| `drop` | ✅ | 断开连接 |
| `chain` | ✅ | 链接至下一条规则 |
| `skip` | ✅ | 跳过 N 条规则 |
| `skipAfter` | ✅ | 跳转至标记 |
| `setvar` | ✅ | 设置变量 |
| `capture` | ✅ | 捕获 regex 分组 |
| `id` | ✅ | 规则 ID |
| `phase` | ✅ | 处理阶段 |
| `severity` | ✅ | 严重级别 |
| `msg` | ✅ | 日志消息 |
| `tag` | ✅ | 规则标签 |
## 为什么选择纯 Rust?
1. **性能** - 比 C++ libmodsecurity 快 10-30 倍
2. **安全性** - 保证内存安全,无缓冲区溢出
3. **可移植性** - 可在 Rust 编译的任何地方运行(包括 WASM)
4. **便捷性** - `cargo add zentinel-modsec`,无系统依赖
5. **可审计性** - 单一语言代码库,更容易进行安全审查
### 技术优化
- **PHF (Perfect Hash Functions)** - O(1) 操作符/变量查找
- **延迟 Regex 编译** - 将编译推迟到首次使用时
- **Aho-Corasick** - 用于 `@pm` 的 O(n) 多模式匹配
- **RegexSet** - 用于 XSS 检测的单次遍历多 regex 评估
- **零拷贝解析** - `Cow` 尽可能避免内存分配
- **无 FFI 开销** - 纯 Rust 实现,无跨语言调用
## OWASP CRS 设置
```
# 下载 OWASP Core Rule Set
git clone https://github.com/coreruleset/coreruleset /etc/modsecurity/crs
cp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf
# 创建引入 setup 和 rule 文件的入口文件
cat > /etc/modsecurity/main.conf <<'EOF'
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/rules/*.conf
EOF
```
```
// Then load the entry file from your application:
let modsec = zentinel_modsec::ModSecurity::from_file("/etc/modsecurity/main.conf")?;
```
## 对比
| 特性 | zentinel-modsec | libmodsecurity | mod_security |
|---------|-----------------|----------------|--------------|
| 语言 | 纯 Rust | C++ | C |
| 依赖 | 无 | PCRE, libxml2 等 | Apache/nginx |
| 性能 | 6.2M req/s | 207K req/s | ~200K req/s |
| 兼容 CRS | ✅ | ✅ | ✅ |
| 支持 WASM | ✅ | ❌ | ❌ |
| 内存安全 | ✅ 有保证 | ❌ 手动 | ❌ 手动 |
## 许可证
Apache-2.0
## 贡献
欢迎贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解指南。
## 相关项目
- [Zentinel](https://zentinelproxy.io) - 使用此引擎的可扩展反向代理
- [OWASP CRS](https://coreruleset.org) - 用于 ModSecurity 的 Core Rule Set
- [libmodsecurity](https://github.com/SpiderLabs/ModSecurity) - 原始的 C++ 实现
标签:AppImage, CISA项目, DOE合作, ModSecurity, OWASP CRS, Rust, WAF, Web应用防火墙, 可视化界面, 网络安全, 网络流量审计, 通知系统, 隐私保护