use super::keyword::{
between_obfuscate, case_alternate, mysql_versioned_comment, percentage_prefix,
random_case_alternate, space_to_comment, space_to_dash, space_to_hash, space_to_plus,
space_to_random_blank, sql_comment_insert, unmagic_quotes, whitespace_insert,
};
use super::structural::{
base64_encode, base64_url_encode, chunked_split, deflate_encode, gzip_encode, hex_encode,
null_byte_inject, overlong_utf8, overlong_utf8_more, parameter_pollute, utf7_encode,
};
use super::unicode::{
fullwidth_encode, homoglyph_encode, html_entity_decimal_encode, html_entity_encode,
iis_unicode_encode, json_string_encode, unicode_encode,
};
use super::url::{double_url_encode, triple_url_encode, url_encode, url_encode_lower};
use crate::error::EncodeError;
pub const MAX_PAYLOAD_SIZE: usize = 8 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Strategy {
UrlEncode,
UrlEncodeLower,
DoubleUrlEncode,
TripleUrlEncode,
UnicodeEncode,
IisUnicodeEncode,
JsonEncode,
HtmlEntityEncode,
HtmlEntityDecimalEncode,
CaseAlternation,
RandomCase,
WhitespaceInsertion,
SqlCommentInsertion,
MysqlVersionedComment,
NullByte,
OverlongUtf8,
OverlongUtf8More,
ChunkedSplit,
ParameterPollution,
Base64Encode,
Base64UrlEncode,
HexEncode,
Utf7Encode,
GzipEncode,
DeflateEncode,
SpaceToComment,
SpaceToDash,
SpaceToHash,
SpaceToPlus,
SpaceToRandomBlank,
PercentagePrefix,
BetweenObfuscation,
UnmagicQuotes,
FullwidthEncode,
HomoglyphEncode,
}
impl Strategy {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::UrlEncode => "UrlEncode",
Self::UrlEncodeLower => "UrlEncodeLower",
Self::DoubleUrlEncode => "DoubleUrlEncode",
Self::TripleUrlEncode => "TripleUrlEncode",
Self::UnicodeEncode => "UnicodeEncode",
Self::IisUnicodeEncode => "IisUnicodeEncode",
Self::JsonEncode => "JsonEncode",
Self::HtmlEntityEncode => "HtmlEntityEncode",
Self::HtmlEntityDecimalEncode => "HtmlEntityDecimalEncode",
Self::CaseAlternation => "CaseAlternation",
Self::RandomCase => "RandomCase",
Self::WhitespaceInsertion => "WhitespaceInsertion",
Self::SqlCommentInsertion => "SqlCommentInsertion",
Self::MysqlVersionedComment => "MysqlVersionedComment",
Self::NullByte => "NullByte",
Self::OverlongUtf8 => "OverlongUtf8",
Self::OverlongUtf8More => "OverlongUtf8More",
Self::ChunkedSplit => "ChunkedSplit",
Self::ParameterPollution => "ParameterPollution",
Self::Base64Encode => "Base64Encode",
Self::Base64UrlEncode => "Base64UrlEncode",
Self::HexEncode => "HexEncode",
Self::Utf7Encode => "Utf7Encode",
Self::GzipEncode => "GzipEncode",
Self::DeflateEncode => "DeflateEncode",
Self::SpaceToComment => "SpaceToComment",
Self::SpaceToDash => "SpaceToDash",
Self::SpaceToHash => "SpaceToHash",
Self::SpaceToPlus => "SpaceToPlus",
Self::SpaceToRandomBlank => "SpaceToRandomBlank",
Self::PercentagePrefix => "PercentagePrefix",
Self::BetweenObfuscation => "BetweenObfuscation",
Self::UnmagicQuotes => "UnmagicQuotes",
Self::FullwidthEncode => "FullwidthEncode",
Self::HomoglyphEncode => "HomoglyphEncode",
}
}
#[must_use]
pub const fn contexts(&self) -> &'static [&'static str] {
match self {
Self::UrlEncode
| Self::UrlEncodeLower
| Self::DoubleUrlEncode
| Self::TripleUrlEncode
| Self::ParameterPollution => &[],
Self::UnicodeEncode => &["json", "javascript"],
Self::IisUnicodeEncode => &["iis", "asp"],
Self::JsonEncode => &["json"],
Self::HtmlEntityEncode | Self::HtmlEntityDecimalEncode => &["html"],
Self::CaseAlternation | Self::RandomCase | Self::WhitespaceInsertion => &[],
Self::SqlCommentInsertion
| Self::MysqlVersionedComment
| Self::SpaceToComment
| Self::SpaceToDash
| Self::SpaceToRandomBlank
| Self::BetweenObfuscation => &["sql"],
Self::SpaceToHash => &["sql", "mysql"],
Self::SpaceToPlus => &["url-encoded"],
Self::NullByte => &["php", "cgi"],
Self::OverlongUtf8 | Self::OverlongUtf8More => &["iis-6"],
Self::ChunkedSplit => &["http-request-body"],
Self::Base64Encode | Self::Base64UrlEncode | Self::HexEncode => &[],
Self::Utf7Encode => &["iis", "legacy-dotnet"],
Self::GzipEncode | Self::DeflateEncode => &["http-request-body"],
Self::PercentagePrefix => &[],
Self::UnmagicQuotes => &["php", "gbk", "big5", "shift-jis"],
Self::FullwidthEncode => &["nfkc", "java", "dotnet", "python3", "postgresql"],
Self::HomoglyphEncode => &[],
}
}
}
fn check_size(payload: &[u8]) -> Result<(), EncodeError> {
if payload.len() > MAX_PAYLOAD_SIZE {
Err(EncodeError::PayloadTooLarge {
max: MAX_PAYLOAD_SIZE,
actual: payload.len(),
})
} else {
Ok(())
}
}
pub fn encode(payload: impl AsRef<[u8]>, strategy: Strategy) -> Result<String, EncodeError> {
let payload = payload.as_ref();
check_size(payload)?;
match strategy {
Strategy::UrlEncode => Ok(url_encode(payload)),
Strategy::UrlEncodeLower => Ok(url_encode_lower(payload)),
Strategy::DoubleUrlEncode => Ok(double_url_encode(payload)),
Strategy::TripleUrlEncode => Ok(triple_url_encode(payload)),
Strategy::UnicodeEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(unicode_encode(text))
}
Strategy::IisUnicodeEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(iis_unicode_encode(text))
}
Strategy::JsonEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(json_string_encode(text))
}
Strategy::HtmlEntityEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(html_entity_encode(text))
}
Strategy::HtmlEntityDecimalEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(html_entity_decimal_encode(text))
}
Strategy::CaseAlternation => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(case_alternate(text))
}
Strategy::RandomCase => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(random_case_alternate(text))
}
Strategy::WhitespaceInsertion => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(whitespace_insert(text))
}
Strategy::SqlCommentInsertion => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(sql_comment_insert(text))
}
Strategy::MysqlVersionedComment => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(mysql_versioned_comment(text, 50_000))
}
Strategy::NullByte => Ok(null_byte_inject(payload)),
Strategy::OverlongUtf8 => Ok(overlong_utf8(payload)),
Strategy::OverlongUtf8More => Ok(overlong_utf8_more(payload)),
Strategy::ChunkedSplit => {
let body = chunked_split(payload, 1024)?.body;
Ok(String::from_utf8_lossy(&body).into_owned())
}
Strategy::ParameterPollution => Ok(parameter_pollute(payload)),
Strategy::Base64Encode => Ok(base64_encode(payload)),
Strategy::Base64UrlEncode => Ok(base64_url_encode(payload)),
Strategy::HexEncode => Ok(hex_encode(payload)),
Strategy::Utf7Encode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(utf7_encode(text))
}
Strategy::GzipEncode => Ok(gzip_encode(payload)?),
Strategy::DeflateEncode => Ok(deflate_encode(payload)?),
Strategy::SpaceToComment => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(space_to_comment(text))
}
Strategy::SpaceToDash => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(space_to_dash(text))
}
Strategy::SpaceToHash => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(space_to_hash(text))
}
Strategy::SpaceToPlus => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(space_to_plus(text))
}
Strategy::SpaceToRandomBlank => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(space_to_random_blank(text))
}
Strategy::PercentagePrefix => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(percentage_prefix(text))
}
Strategy::BetweenObfuscation => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(between_obfuscate(text))
}
Strategy::UnmagicQuotes => Ok(unmagic_quotes(payload)),
Strategy::FullwidthEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(fullwidth_encode(text))
}
Strategy::HomoglyphEncode => {
let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
Ok(homoglyph_encode(text))
}
}
}
#[must_use]
pub fn all_strategies() -> Vec<Strategy> {
let mut strategies = vec![
Strategy::CaseAlternation,
Strategy::RandomCase,
Strategy::WhitespaceInsertion,
Strategy::SqlCommentInsertion,
Strategy::SpaceToPlus,
Strategy::SpaceToRandomBlank,
Strategy::SpaceToComment,
Strategy::SpaceToDash,
Strategy::SpaceToHash,
Strategy::UrlEncode,
Strategy::UrlEncodeLower,
Strategy::DoubleUrlEncode,
Strategy::UnicodeEncode,
Strategy::IisUnicodeEncode,
Strategy::JsonEncode,
Strategy::HtmlEntityEncode,
Strategy::HtmlEntityDecimalEncode,
Strategy::NullByte,
Strategy::PercentagePrefix,
Strategy::TripleUrlEncode,
Strategy::ChunkedSplit,
Strategy::ParameterPollution,
Strategy::MysqlVersionedComment,
Strategy::Base64Encode,
Strategy::Base64UrlEncode,
Strategy::OverlongUtf8,
Strategy::OverlongUtf8More,
Strategy::HexEncode,
Strategy::Utf7Encode,
Strategy::BetweenObfuscation,
Strategy::UnmagicQuotes,
Strategy::FullwidthEncode,
Strategy::HomoglyphEncode,
Strategy::GzipEncode,
Strategy::DeflateEncode,
];
strategies.sort_by(|a, b| {
super::layered::aggressiveness(*a)
.partial_cmp(&super::layered::aggressiveness(*b))
.unwrap_or(std::cmp::Ordering::Equal)
});
strategies
}