use std::sync::OnceLock;
use regex::RegexSet;
static DESER_PATTERNS: OnceLock<RegexSet> = OnceLock::new();
static DESER_DESCRIPTIONS: &[&str] = &[
"Java serialized object (rO0AB / aced0005)",
"PHP serialized object (O:N:\"ClassName\")",
"Python pickle header (\\x80\\x03/\\x80\\x04/\\x80\\x05)",
".NET BinaryFormatter (AAEAAAD)",
];
fn patterns() -> &'static RegexSet {
DESER_PATTERNS.get_or_init(|| {
RegexSet::new([
r"(?i)(rO0AB|aced\s*0005)",
r#"(?i)[OaCsS]:\d+:"[^"]*":\d+:\{"#,
r"(?i)(\\x80\\x0[345]|\\x80\\x02)",
r"(?i)AAEAAAD",
])
.expect("deserialization regex patterns must compile")
})
}
pub fn check_deserialization(input: &str) -> Option<String> {
let set = patterns();
let matches: Vec<_> = set.matches(input).into_iter().collect();
if matches.is_empty() {
None
} else {
let idx = matches[0];
Some(
DESER_DESCRIPTIONS
.get(idx)
.unwrap_or(&"deserialization attack")
.to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_java_serialized() {
assert!(check_deserialization("rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA").is_some());
assert!(check_deserialization("aced0005").is_some());
}
#[test]
fn detects_php_serialized() {
assert!(check_deserialization(r#"O:4:"User":1:{s:4:"name";s:5:"admin";}"#).is_some());
}
#[test]
fn detects_dotnet_formatter() {
assert!(check_deserialization("AAEAAAD/////AQAAAA").is_some());
}
#[test]
fn allows_normal_text() {
assert!(check_deserialization("Hello world").is_none());
assert!(check_deserialization(r#"{"name":"John"}"#).is_none());
}
}