zentinelproxy/zentinel-modsec

GitHub: zentinelproxy/zentinel-modsec

纯 Rust 实现的 ModSecurity 规则引擎,兼容 OWASP CRS,为 Rust Web 应用提供零 C 依赖、高性能的 Web 应用防火墙(WAF)功能。

Stars: 15 | Forks: 5

# zentinel-modsec [![Crates.io](https://img.shields.io/crates/v/zentinel-modsec.svg)](https://crates.io/crates/zentinel-modsec) [![Documentation](https://docs.rs/zentinel-modsec/badge.svg)](https://docs.rs/zentinel-modsec) [![License](https://img.shields.io/crates/l/zentinel-modsec.svg)](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应用防火墙, 可视化界面, 网络安全, 网络流量审计, 通知系统, 隐私保护