10.9. 转换操作

转换关键字用于将粘性缓冲区中的数据转换为其他形式。部分转换操作支持选项参数,以便更精细地控制转换过程。

示例:

alert http any any -> any any (file_data; strip_whitespace; \
    content:"window.navigate("; sid:1;)

此示例即使在 navigate( 之间存在一个或多个空格时也能匹配流量。

转换操作可以链式调用。它们按照规则中出现的顺序依次处理,每个转换的输出将作为下一个转换的输入。

示例:

alert http any any -> any any (http_request_line; compress_whitespace; to_sha256; \
    content:"|54A9 7A8A B09C 1B81 3725 2214 51D3 F997 F015 9DD7 049E E5AD CED3 945A FC79 7401|"; sid:1;)

10.9.1. dotprefix

在缓冲区内容前添加 . 字符,便于精简域名检查。例如,输入字符串 hello.google.com 会被修改为 .hello.google.com。添加点号后,google.com 能匹配 content:".google.com"

示例:

alert dns any any -> any any (dns.query; dotprefix; \
    content:".microsoft.com"; sid:1;)

此示例将匹配 windows.update.microsoft.commaps.microsoft.com.au,但不匹配 windows.update.fakemicrosoft.com

此规则可仅匹配域名:示例:

alert dns any any -> any any (dns.query; dotprefix; \
    content:".microsoft.com"; endswith; sid:1;)

此示例将匹配 windows.update.microsoft.com 但不匹配 windows.update.microsoft.com.au

最后,此规则可仅匹配顶级域名:示例:

alert dns any any -> any any (dns.query; dotprefix; \
    content:".co.uk"; endswith; sid:1;)

此示例将匹配 maps.google.co.uk 但不匹配 maps.google.co.nl

10.9.2. domain

从缓冲区提取域名。域名定义基于 Mozilla公共后缀列表,这意味着既包含传统顶级域名如 .com,也包含特定域名如 airport.aero``execute-api.cn-north-1.amazonaws.com.cn``(这些域名允许用户在下方声明子域名)。

示例:

alert tls any any -> any any (tls.sni; domain; \
    dataset:isset,domains,type string,load domains.lst; sid:1;)

此示例将匹配文件 domains.lst 中的所有域名。例如若文件包含 oisf.net,则 webshop.oisf.net 将被匹配。

10.9.3. tld

从缓冲区提取顶级域名(TLD)。TLD定义基于 Mozilla公共后缀列表,包含传统TLD如 com 和特定域名如 airport.aero``execute-api.cn-north-1.amazonaws.com.cn``(这些域名允许用户在下方声明子域名)。

示例:

alert tls any any -> any any (tls.sni; tld; \
    dataset:isset,tlds,type string,load tlds.lst; sid:1;)

此示例将匹配文件 tlds.lst 中的所有TLD。例如若文件包含 net,则 oisf.net 将被匹配。

10.9.4. strip_whitespace

移除所有C语言 isspace() 函数认定的空白字符。

示例:

alert http any any -> any any (file_data; strip_whitespace; \
    content:"window.navigate("; sid:1;)

10.9.5. compress_whitespace

将所有连续空白字符压缩为单个空格。

10.9.6. to_lowercase

将缓冲区内容转为小写后传递。

此示例在 http.uri 包含 this text has been converted to lowercase 时告警:

示例:

alert http any any -> any any (http.uri; to_lowercase; \
    content:"this text has been converted to lowercase"; sid:1;)

10.9.7. to_md5

计算缓冲区的MD5哈希值并传递原始哈希值。

示例:

alert http any any -> any any (http_request_line; to_md5; \
    content:"|54 A9 7A 8A B0 9C 1B 81 37 25 22 14 51 D3 F9 97|"; sid:1;)

10.9.8. to_uppercase

将缓冲区内容转为大写后传递。

此示例在 http.uri 包含 THIS TEXT HAS BEEN CONVERTED TO UPPERCASE 时告警:

示例:

alert http any any -> any any (http.uri; to_uppercase; \
    content:"THIS TEXT HAS BEEN CONVERTED TO UPPERCASE"; sid:1;)

10.9.9. to_sha1

计算缓冲区的SHA-1哈希值并传递原始哈希值。

示例:

alert http any any -> any any (http_request_line; to_sha1; \
    content:"|54A9 7A8A B09C 1B81 3725 2214 51D3 F997 F015 9DD7|"; sid:1;)

10.9.10. to_sha256

计算缓冲区的SHA-256哈希值并传递原始哈希值。

示例:

alert http any any -> any any (http_request_line; to_sha256; \
    content:"|54A9 7A8A B09C 1B81 3725 2214 51D3 F997 F015 9DD7 049E E5AD CED3 945A FC79 7401|"; sid:1;)

10.9.11. pcrexform

对缓冲区应用正则表达式,输出*第一个捕获组*。

此示例在 http.request_line 包含 /dropper.php 时告警: 示例:

alert http any any -> any any (msg:"HTTP with pcrexform"; http.request_line; \
    pcrexform:"[a-zA-Z]+\s+(.*)\s+HTTP"; content:"/dropper.php"; sid:1;)

10.9.12. url_decode

解码URL编码数据(将'+'替换为空格,'%HH'替换为对应值),不支持Unicode '%uZZZZ'编码解码。

10.9.13. xor

对缓冲区应用异或解码。

此示例在 http.uri 包含用4字节密钥 0d0ac8ff 异或编码的 password= 时告警: 示例:

alert http any any -> any any (msg:"HTTP with xor"; http.uri; \
    xor:"0d0ac8ff"; content:"password="; sid:1;)

10.9.14. header_lowercase

用于HTTP/1和HTTP/2头部名称规范化。将头部名称转为小写,同时保持头部值不变。

实现采用状态机: - 在找到 : 前转为小写 - 在找到换行符前保持原状,之后切换回初始状态

此示例对带有authorization头的HTTP/1和HTTP/2流量告警: 示例:

alert http any any -> any any (msg:"HTTP authorization"; http.header_names; \
    header_lowercase; content:"authorization:"; sid:1;)

10.9.15. strip_pseudo_headers

用于HTTP/1和HTTP/2头部名称规范化。移除HTTP2伪头部(名称和值)。

实现方式为移除所有以 : 开头的行。

此示例对仅含User-Agent的HTTP/1和HTTP/2流量告警: 示例:

alert http any any -> any any (msg:"HTTP ua only"; http.header_names; \
   bsize:16; content:"|0d 0a|User-Agent|0d 0a 0d 0a|"; nocase; sid:1;)

10.9.16. from_base64

此转换类似关键字 base64_decode:使用可选的 modeoffsetbytes 参数解码缓冲区,解码后的数据可用于匹配。

转换完成后,缓冲区仅包含可被base64解码的字节。若解码过程遇到无效字节,这些字节不会包含在缓冲区中。

选项值必须以 , 分隔且可任意排序。

格式:

from_base64: [[bytes <value>] [, offset <offset_value> [, mode: strict|rfc4648|rfc2045]]]

各选项默认值: - bytes 默认为输入缓冲区长度 - offset 默认为 0 且必须小于 65536 - mode 默认为 rfc4648

注意:未来版本中 bytesoffset 可能支持来自 byte_extract 和/或 byte_math 的变量,当前暂不支持。

模式 rfc4648 应用RFC 4648解码逻辑,适用于可安全通过电子邮件发送、用于URL或包含在HTTP POST请求中的二进制数据编码。

模式 rfc2045 应用RFC 2045解码逻辑,支持包含空格、换行符等非base64字母表字符的字符串。

模式 strict 在编码字节中发现无效字符时将失败。

以下示例将在缓冲区内容匹配时告警(参见最后一个 content 值的预期字符串):

此示例使用默认参数将 "VGhpcyBpcyBTdXJpY2F0YQ==" 转换为 "This is Suricata":

content: "VGhpcyBpcyBTdXJpY2F0YQ=="; from_base64; content:"This is Suricata";

此示例将 "dGhpc2lzYXRlc3QK" 转换为 "thisisatest":

content:"/?arg=dGhpc2lzYXRlc3QK"; from_base64: offset 6, mode rfc4648; \
content:"thisisatest";

此示例将 "Zm 9v Ym Fy" 转换为 "foobar":

content:"/?arg=Zm 9v Ym Fy"; from_base64: offset 6, mode rfc2045; \
content:"foobar";

10.9.17. luaxform

此转换允许Lua脚本对缓冲区应用转换。

用于转换的Lua脚本*必须*包含名为 transform 的函数。

Lua转换可接收可选参数(参见下方示例),但非必需。参数以逗号分隔。

若缓冲区为空或Lua框架不可用(罕见情况),则不会调用Lua转换函数。

Lua转换函数必须返回两个值(见下文),否则缓冲区不会被修改。

注意参数和值未经验证或解释直接传递,最多支持10个参数。

Lua转换函数调用参数:
  • input 提供给转换的缓冲区

  • arguments 参数列表

Lua转换函数必须返回两个值[Lua数据类型如下]:
  • buffer [Lua字符串] 返回包含原始输入缓冲区或经转换修改后的缓冲区

  • bytes [Lua整数] 返回缓冲区的字节数

此示例将HTTP数据提供给Lua转换,并用 content 检查转换结果:

示例:

alert http any any -> any any (msg:"Lua Xform example"; flow:established;  \
        file.data; luaxform:./lua/lua-transform.lua; content: "abc"; sid: 2;)

此示例向Lua转换提供HTTP数据及指定转换偏移量和字节数的参数,随后用 content 匹配结果缓冲区:

示例:

alert http any any -> any any (msg:"Lua Xform example"; flow:established; \
        file.data; luaxform:./lua/lua-transform.lua, bytes 12, offset 13; content: "abc"; sid: 1;)

以下Lua脚本展示了一个处理 bytesoffset 参数的转换,使用这些值(若无参数则使用默认值)对缓冲区应用大写转换:

function init (args)
     local needs = {}
     return needs
end

local function get_value(item, key)
    if string.find(item, key) then
        local _, value = string.match(item, "(%a+)%s*(%d*)")
        if value ~= "" then
            return tonumber(value)
        end
    end

    return nil
end

-- 支持的参数
local bytes_key = "bytes"
local offset_key = "offset"
function transform(input_len, input, argc, args)
    local bytes = #input
    local offset = 0

    -- 查找可选的bytes和offset参数
    for i, item in ipairs(args) do
        local value = get_value(item, bytes_key)
        if value ~= nil then
            bytes = value
        else
            local value = get_value(item, offset_key)
            if value ~= nil then
                offset = value
            end
        end
    end
    local str_len = #input
    if offset < 0 or offset > str_len then
        print("offset is out of bounds: " .. offset)
        return nil
    end
    str_len = str_len - offset
    if bytes < 0 or bytes > str_len then
        print("invalid bytes " ..  bytes .. " or bytes > length " .. bytes .. " length " .. str_len)
        return nil
    end
    local sub = string.sub(input, offset + 1, offset + bytes)
    return string.upper(sub), bytes
end