[分享] Nginx 参考实现 0day漏洞分析及解决方案

Nginx 参考实现 0day漏洞分析及解决方案

官方原文:

漏洞出现的背景是nginx用户需要使用nginx和ldap来认证应用里的用户,因此官方提供了一个参考实现ldap-auth来演示这种实现方式。

原理

Nginx官方提供的方案使用了nginx和nginx Plus里的ngx_http_auth_request_module模块,这个模块会重定向认证请求到一个外部服务。在参考实现里,这个服务就是ldap-auth,以守护进程的方式运行的,即我们今天要讨论的nginx-ldap-auth。这个实现是以python编写,能够使用LDAP协议和后端的LDAP服务器进行通信。
这个守护进程可以作为客户自己实现的连接器app(客户自己开发的)的模块来使用。

这里要注意:

  • 参考实现不是给生产环境使用的,而是仅作为你自己实现的认证框架的一个模块使用。官方提供的ldap-auth模块只是提供参考。
  • 后面内容的前提条件是预编译的nginx二进制文件里需要有http_auth_request模块,可以使用nginx -V命令查看。

在参考实现里认证流程是怎么工作的?

要执行认证操作,http_auth_request模块会创建一个HTTP子请求发送给ldap-auth守护进程,ldap-auth守护进程会立即将它解析并发送给后端的LDAP服务器。这中间:

  • http_auth_request到ldap-auth模块,使用http协议
  • ldap-auth模块到LDAP服务器,使用LDAP的api接口。

整个请求逻辑如下所示:

client
:arrow_up_down:
nginx(http_auth_request模块) :left_right_arrow: 后端守护进程
:arrow_up_down:
ldap-auth守护进程
:arrow_up_down:
LDAP服务端

整个认证的详细流程如下:

  1. 客户端给nginx做反向代理的后端服务器中的保护资源发送一个HTTP请求
  2. nginx重定向请求到ldap-auth守护进程,守护进程会返回401 状态码,因为这个时候没有提供任何凭据信息;
  3. nginx重定向请求到后端的http://backend/login页面,对应的是后端守护进程。它会将原始请求URL写入到重定向请求的X-Target头部;
  4. 后端守护进程发送给客户端登录表单,nginx会设置登录表单的状态码为200
  5. 用户填写表单里的用户名和密码字段,然后点击登录按钮。客户端会生成生成一个到/login的POST请求,nginx会把这个请求重定向到后端守护进程。
  6. 后端守护进程构建一个字符串,格式是:username:password,然后做base64编码。生成一个叫做nginxauth的cookie,对应的值是base64加密后的字符串。然后把cookie发送给客户端。同时会发送httponly标记来阻止使用js读取或者操作cookie(避免跨站脚本攻击)
  7. 客户端重传它的原始请求(步骤1里的),此时在HTTP头部的Cookie字段里包含了上一步设置的cookie。nginx会把这个请求再次重定向到ldap-auth守护进程。
  8. ldap-auth守护进程解码cookie,然后在认证请求里发送用户名和密码给ldap服务器
  9. 下一步动作取决于LDAP服务器是否成功认证用户:
  • 如果认证成功,ldap-auth会发送200状态码给nginx,nginx会想后端请求客户端需要的资源。在参考实现的测试用例里,后端守护进程会返回:Hello, world! Requested URL: URL。此外在nginx-ldap-auth.conf文件里还可以配置是否缓存请求结果。
  • 如果认证失败,则ldap-auth守护进程会发送状态码401给nginx,nginx会再次重定向请求给后端守护进程(步骤3)上面的流程重复执行;

ldap-auth实现请求转发的机制

ldap-auth这个守护进程它转发请求时,是通过HTTP头部来设置LDAP服务器所需要的参数,配置文件示例如下所示:

# URL and port for connecting to the LDAP server
proxy_set_header X-Ldap-URL "ldaps://example.com:636";
 
# Base DN
proxy_set_header X-Ldap-BaseDN "cn=Users,dc=test,dc=local";
 
# Bind DN
proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local";
 
# Bind password
proxy_set_header X-Ldap-BindPass "secret";

而这个0day漏洞的出现,也和这种配置机制紧密关联。

出现漏洞的3种场景

  1. 守护进程启动时带命令行参数

    除了可以使用配置文件来设置对应的参数,守护进程在启动的时候,可以在启动脚本后面带上命令行参数。而当攻击者在前端发送请求的时候,如果在HTTP请求里携带了和ldap-auth相同名称的命令行参数头部,那么HTTP请求里的头部参数就会覆盖掉守护进程的命令行参数,此时攻击者就可以反复尝试直到认证成功。

  2. 当守护进程启动时,配置文件里有可选参数,但是可选参数并未使用

    配置文件里的参数分为必需参数和可选参数两类。例如上面示例配置里的参数就是必需参数。而像下面的参数就是可选参数:

    #proxy_set_header X-CookieName "nginxauth";
    #proxy_set_header Cookie nginxauth=$cookie_nginxauth;
    proxy_set_header X-Ldap-Template "(cn=%(username)s)";
    

    而部分可选参数的功能非常强大,例如上面的X-Ldap-Template头部,它就可以修改LDAP认证模板里对应的值。当管理员没有注释这个参数,且没有使用这个参数时,攻击者在前端就可以通过设置这个参数来间接修改认证参数,进而可以反复尝试直到认证成功。

  3. 认证时需要群组成员关系

    当你的认证用户必须是某个群组成员时才能认证通过,这是一种限制资源访问的方式。而ldap-auth在接收后端返回的用户名和密码时,不会对用户名和密码做任何操作,在解密出来后直接会传给LDAP服务器。因此攻击者可以构建出某些特殊的用户名,例如带- ( ) _ =等符号的用户名。此时就可以绕过LDAP后端的群组关系检查。因为这些符号在LDAP里是有特殊含义的。此时攻击者就可以访问没有授权的文件。

解决方案

针对上面的3种场景,对应解决方案有:

  1. 禁止前端传递ldap头部信息

    location = /auth-proxy {
        # ...
        proxy_pass_request_headers off;
        proxy_set_header Authorization $http_authorization; # If using Basic auth
        # ...
    }
    
  2. 将nginx里主动将对应头部的值设置为空

    location = /auth-proxy {
        ...
        proxy_set_header X-Ldap-URL      ""; # Empty value when using command-line config
        proxy_set_header X-Ldap-BaseDN   ""; # Empty value when using command-line config
        proxy_set_header X-Ldap-BindDN   ""; # Empty value when using command-line config
        proxy_set_header X-Ldap-BindPass ""; # Empty value when using command-line config
    
        proxy_set_header X-Ldap-Template ""; # Optional, but do not comment (use empty value)
        proxy_set_header X-CookieName    ""; # Optional, but do not comment (use empty value)
        proxy_set_header X-Ldap-Realm    ""; # Optional, but do not comment (use empty value)
        proxy_set_header X-Ldap-Starttls ""; # "True" or empty (do not comment)
        ...
    }
    
  3. 保证后端守护进程能够处理登录表单

    后端裁切掉用户名字段里的特殊字符,例如- ( ) - = ,这些特殊字符在LDAP里都是有特殊含义的。
    最后,如果你的环境里不使用ldap-auth这个参考实现来实现你的LDAP认证,那么nginx也不需要做任何配置。

对极狐GitLab产品的影响

目前极狐GitLab 15.x产品内嵌的nginx版本是1.20.1,但是没有启用http_auth_reqeust模块,且没有附带ldap-auth参考实现,因此不受该漏洞影响。

如果客户的现场环境有使用该参考实现为基础,开发出了自己内部的认证组件,可以将上面的参考内容和解决方案发送给客户。

1 个赞