从滥用HTTP hop by hop请求头看CVE-2022-1388
source link: https://y4er.com/post/from-hop-by-hop-to-cve-2022-1388/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
最近爆出来的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中的请求头,其他的要单独测试了。
看了太多资料了,用到了但是没贴上来的请原作者见谅。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK