作者:Y4er
原文链接:https://y4er.com/post/from-hop-by-hop-to-cve-2022-1388/
前言
最近爆出来的bigip的CVE-2022-1388漏洞,涉及到一个知识点就是hop by hop,对这个东西没了解过,所以有了此文。
回顾CVE-2021-22986
CVE-2021-22986原理是因为apache和jetty之间的鉴权不当导致的权限绕过。
当不存在Authorization basic认证头时,由apache做权限校验,判断basic认证头是否存在,此时response中的server头为apache
当给一个错误的basic认证头时仍返回apache的401认证,注意这里给的是一个admin:
空密码的admin用户。
当给一个空的X-F5-Auth-Token认证头时,由jetty处理返回401,报错信息为Authorization failed: no user authentication header or token detected.
当两个请求头都存在时,绕过了权限校验,rce。
由此得出结论,当存在X-F5-Auth-Token头时apache不检查basic认证头,而jetty只会判断用户名,而不判断密码是否正确。
思考为什么权限校验不起作用?
查看apache的配置文件/config/httpd/conf/httpd.conf发现
/mgmt/请求被转发到8100端口,并且启用了AuthPAM_Enabled,启用auth会调用/usr/lib/httpd/modules/mod_auth_pam.so判断鉴权,在这个so中
判断是否存在X-F5-Auth-Token头
然后接着拿一些其他的请求头
最终逻辑就是如果存在转发给jetty处理。
而在jetty中 f5.rest.jar
com.f5.rest.workers.authz.AuthzHelper#decodeBasicAuth
从header中拿到basic认证的用户名和密码,在com.f5.rest.common.RestOperationIdentifier#setIdentityFromBasicAuth
中设置用户身份
因为basic不为空,所以进入com.f5.rest.common.RestOperation#setIdentityData
因为 userName!=null && userReference==null
,所以处理完之后用户的身份变为
identityData.userName = 'admin';
identityData.userReference = 'http://localhost/mgmt/shared/authz/users/admin'
identityData.groupReference = null;
接着在鉴权的地方 com.f5.rest.workers.EvaluatePermissions#completeEvaluatePermission
setBasicAuthFromIdentity之后拿到userRef,此时userRef即上文处理完之后的用户身份。在判断AuthzHelper.isDefaultAdminRef(userRef)
时
先拿到默认的AdminReference和当前用户身份匹配,在getDefaultAdminReference()中拿到admin用户的身份new一个RestReference
UrlHelper.buildPublicUri(UrlHelper.buildUriPath(new String[]{WellKnownPorts.AUTHZ_USERS_WORKER_URI_PATH, DEFAULT_ADMIN_NAME}))`最终构建出来的url还是`http://localhost/mgmt/shared/authz/users/admin
所以此时我们在basic中将用户名设置为admin则可以满足defaultReference != null && defaultReference.equals(userReference)
至此绕过权限认证。
然后就是找一个命令执行的点
对应的就是util下的路由功能点
调用bash执行命令即可
另外此处文档中也有提到
https://f5-sdk.readthedocs.io/en/latest/_modules/f5/bigip/tm/util/bash.html
CVE-2021-22986的修复
在上文中,我们传递了一个X-F5-Auth-Token为空的header头,所以completeEvaluatePermission函数会赋给我们一个默认的用户身份。而修复补丁在mod_auth_pam.so判断当X-F5-Auth-Token为空直接返回401
所以我们无法传递给jetty一个空的X-F5-Auth-Token请求头。
那么CVE-2022-1388就是对其的绕过,这里引申出本文的重点hop by hop。
hop by hop
先解释下这是什么东西。根据RFC 2612,HTTP/1.1 规范默认将以下标头视为逐跳:Keep-Alive、Transfer-Encoding、TE、Connection、Trailer、Upgrade、Proxy-Authorization和Proxy-Authenticate。当在请求中遇到这些标头时,代理服务器会处理这些标头,并且不会将其转发到下一个节点。
以推特@jinonehk的一张图来看
第一次尝试导出用户时返回403,因为不是环路ip,而当加上Connection: close, X-Real-IP
时,导出用户成功,说明此时后端服务获取不到X-Real-IP请求头,认为是本地请求所以可以导出用户。
更具体一点,我在这里找到了一个ctf的题目 https://github.com/ritsec/RITSEC-CTF-2019/tree/master/Web/hop-by-hop
在verify函数中尝试获取xff头,如果获取不到则默认为direct。
而前置服务为apache,根据逐跳原则,当Connection中加了其他标头X-Forwarded-For,那么在apache转发给下一跳时,会移除X-Forwarded-For头,导致在verify函数中request.headers['X-Forwarded-For']
抛出异常,由此拿到flag。
可以自己本地搭一个反代试试,我这有一个springboot的项目,只有一个controller
apache 80端口反代springboot 9091端口
先开启反代功能
LoadModule proxy_module modules/mod_proxy.so
配置virtualhost
<VirtualHost *:80>
ProxyRequests Off
ProxyPreserveHost On
<Proxy>
Order deny,allow
Allow from all
</Proxy>
ProxyPass / http://localhost:9091
ProxyPassReverse / http://localhost:9091
</VirtualHost>
正常传token,springboot可以获取到token头
当connection加上Token时,springboot获取的token为null
由此可见CVE-2022-1388
CVE-2022-1388
在CVE-2022-1388中使用Connection加上X-F5-Auth-Token让jetty接收到的X-F5-Auth-Token为null以此来绕过权限认证。
另外需要注意的一个地方为host赋值为localhost,不然host为ip时报错
因为CVE-2021-22986之后,在com.f5.rest.common.RestOperationIdentifier#setIdentityFromBasicAuth
中
当host为localhost或者127.0.0.1时,会赋予用户身份。另外这里还可以赋值host为127.4.2.1然后basic用户名为f5hubblelcdadmin,或者通过Connection加上X-Forwarded-Host也可以rce,就不截图了。
hop by hop的适用面
我本地测试了apache、nginx、openresty、HAProxy,其中只有apache会消费掉Connection中的请求头,其他的要单独测试了。
参考链接
看了太多资料了,用到了但是没贴上来的请原作者见谅。
- 漏洞百出
- https://twitter.com/jinonehk/status/1420413477521301507
- https://nathandavison.com/blog/abusing-http-hop-by-hop-request-headers
- RFC 2616
- https://y4y.space/2021/03/19/cve-2021-22986-f5-rest-unauthenticated-rce-analysis/
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
- https://nosec.org/home/detail/4722.html
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1908/