Advantech WebAccess Unpatched RCE

Author: Chris Lyne

Chris Lyne
Tenable TechBlog

--

Summary

Tenable Research has discovered that Advantech WebAccess remains unprotected against a public exploit several months after a patch was said to be released. Vulnerable WebAccess instances are susceptible to an unauthenticated remote code execution attack. This post discusses the vulnerability and relevant events in great detail.

Background

On January 4th, 2018 ICS-CERT released ICSA-18–004–02A to detail several vulnerabilities reported for Advantech WebAccess. One of the vulnerabilities, CVE-2017–16720, which was also disclosed by the Zero Day Initiative (ZDI), allows an unauthenticated remote attacker to execute arbitrary system commands.

The mitigation section of the ICS-CERT advisory states, “Advantech has released WebAccess Version 8.3 to address the reported vulnerabilities.” In March, two months after the release of the ICS-CERT advisory, a public exploit leveraging CVE-2017–16720 was published to the Exploit Database.

In July, Tenable discovered that WebAccess version 8.3 is affected by this vulnerability. The public exploit works out-of-the-box without modification. Furthermore, the exploit also works against versions 8.3.1 and 8.3.2. According to the WebAccess Support & Download page, 8.3.2 was released on August 17, 2018. It appears there was never a patch for this vulnerability.

At the time of writing, a basic Shodan.io search reveals that there are 38 Internet-facing instances of WebAccess. These results certainly don’t represent the total number of WebAccess installations across the globe, but it is clear that the product is being used in at least five countries. Given that this software is used in critical infrastructure sectors (such as critical manufacturing, energy, water and wastewater systems), this type of cyber exposure creates the potential for significant impact.

Analysis

While this is not a new vulnerability, and a proof of concept exploit has been around for a few months, it is still valuable to understand the root cause. On the surface, this vulnerability allows for remote command execution via the Remote Procedure Call (RPC) protocol over TCP port 4592. Since an in-depth ZDI blog post was written about another RPC bug in WebAccess, I won’t go into detail about reversing the protocol itself. However, we will talk about the Interactive Disassembler (IDA) Pro plugin, mIDA, as it proved to be quite useful in the reversing process. In case you didn’t know, mIDA was created by Tenable’s own, Nicolas Pouvesle. Great work, Nicolas! Finally, I will show that version 8.3 is still vulnerable to CVE-2017–16720, using BinDiff to compare versions 8.2_20170817 and 8.3.

Proof of Concept

Let’s first take a look at the vulnerability and the public exploit. In order to demonstrate proof of concept, I have set up a target virtual machine running Windows Server 2008 R2 x64 with an IP address of 192.168.1.194. Out of the box, the exploit code will simply launch calc.exe. The screenshot below shows Advantech WebAccess 8.2_20170817 (though, for some reason it says 2017.08.18).

WebAccess 8.2_20170817

Below is a screenshot showing Advantech WebAccess 8.3.0 being exploited by the exact same code. The version string can be seen at the bottom of the web interface. Keep in mind that version 8.3.0 is supposed to contain a fix for the vulnerability. Clearly, this is not the case.

WebAccess 8.3.0

Now that I have your attention, let’s take a look at what is going on under the hood. Before we starting diffing binaries, it is necessary to understand how the vulnerability is triggered.

Reversing RPC Interfaces with mIDA

As stated earlier, the vulnerable service (webvrpcs.exe) utilizes an RPC protocol over TCP port 4592. For this reason, the IDA Pro plugin, mIDA, was chosen to help facilitate the reverse engineering process.

mIDA is an IDA plugin which extracts RPC interfaces and recreates the associated Interface Definition Language (IDL) file. It supports inline, interpreted and fully interpreted server stubs. Let’s see this in action! The following example will be performed against WebAccess 8.2_20170817 using the IDA Pro i386 compatibility build (7.0.171130).

First, we will load webvrpcs.exe in IDA. Next, the mIDA plugin must be launched. This creates a new tab which shows a table of each RPC interface. The following table is generated.

Opcode  Address     Function Name
---------------------------------
0x00 0x00401000 sub_401000
0x01 0x00401260 sub_401260
0x02 0x00401420 sub_401420
0x03 0x00401630 sub_401630
0x04 0x004017A0 sub_4017A0
0x05 0x00401970 sub_401970
0x06 0x00401A80 sub_401A80
0x07 0x00401BA0 sub_401BA0

Looking back at the public exploit, there are a total of three Distributed Computing Environment / Remote Procedure Calls (DCERPC) calls made. After playing with the code a bit, I found that the first call is not necessary for the exploit to work. Only the second and third calls are required. This makes sense because the second call elicits a server response containing a context handle. This behavior enables state to be maintained between client and server. The final DCERPC call specifies this context handle, along with the calc.exe payload. In the snippet below, the context handle is contained in ‘res[2]’.

52 print "...2"
53 stubdata = struct.pack("<I", 0x02)
54 res = call(dce, 4, stubdata)
55 if res == -1:
56 print "Something went wrong"
57 sys.exit(1)
58 res = struct.unpack("III", res)
59
60 if (len(res) < 3):
61 print "Received unexpected length value"
62 sys.exit(1)
63
64 print "...3"
65 # ioctl 0x2711
66 stubdata = struct.pack("<IIII", res[2], 0x2711, 0x204, 0x204)
67 command = "..\\..\\windows\\system32\\calc.exe"
68 fmt = "<" + str(0x204) + "s"
69 stubdata += struct.pack(fmt, command)
70 call(dce, 1, stubdata)

The vulnerability of interest here is specifically related to IOCTL 0x2711. Notice that the last RPC call specifies this value as well as an opcode of 1. For this reason, it seems logical that opcode 1 should be examined. To further show the power of mIDA, let’s decompile the opcode 1 RPC interface. First, select the opcode 0x01 entry in the mIDA output table, right-click it, then click “Decompile.”

Decompilation with mIDA

Notice in the Output window that a function prototype is displayed.

{/* opcode: 0x01, address: 0x00401260 */void sub_401260 (
[in] handle_t arg_1,
[in] long arg_2,
[in] long arg_3,
[in] long arg_4,
[in][ref][size_is(arg_4)] char * arg_5,
[out][ref] long * arg_6
);
}

This interface takes five input arguments. Clearly this signature matches up with the ‘stubdata’ variable that is constructed in the Python PoC. Four integers are specified, followed by a string.

66 stubdata = struct.pack("<IIII", res[2], 0x2711, 0x204, 0x204)
67 command = "..\\..\\windows\\system32\\calc.exe"
68 fmt = "<" + str(0x204) + "s"
69 stubdata += struct.pack(fmt, command)
70 call(dce, 1, stubdata)

At this point, we can safely say the function that processes opcode 1 also handles the IOCTL in some way. In order to verify which functions are in the call chain before the calc.exe process is eventually created, a debugging session is required to analyze the exploit in action. Once the call chain is understood, BinDiff can be used to compare “patched” and “unpatched” function implementations.

By manually stepping through instructions with the debugger in IDA, it was found that the following functions are called. The preceding function calls the next:

  1. webvrpcs.exe:sub_401260
  2. webvrpcs.exe:sub_402c60
  3. webvrpcs.exe:sub_4046D0
  4. drawsrv.dll:DsDaqWebService
  5. drawsrv.dll:sub_2517B0

For reference, function 1 is the RPC interface. We know this because mIDA told us! Number 4 (DsDaqWebService) is the IOCTL handler (it invokes other functions based on the IOCTL value). This is made apparent when we examine DsDaqWebService.

Initially, a calculation is made that subtracts 0x2710 (10,000) from our IOCTL value, 0x2710 (10,001). The EAX register is populated with the value of EDX minus 0x2710. EDX contains the IOCTL argument. A comment has been added to highlight this instruction.

IOCTL Used in Calculation

If we zoom out a bit, it’s clear that there are many paths which can be taken from within this function. Conditional logic is being used heavily. After the subtraction takes place, the value of EAX (1) is used to decide which branches to follow.

10,000 Foot View

After a few conditional jumps, a final switch block is reached with 118 possible cases. The IOCTL argument value ultimately determines which branch will be taken. This logic causes execution to call function 5 (sub_2517B0).

118 Possible Cases

Most importantly, the last function creates a new process based on the “command line” passed to it. The disassembly of this function is listed below, with the most relevant pieces highlighted. For the sake of brevity, the tail end has been truncated.

.text:002517B0 ; int __cdecl sub_2517B0(LPSTR lpCommandLine, __int16)
.text:002517B0 sub_2517B0 proc near ; CODE XREF: .text:drawsrv_DsDaqWebService+86p
.text:002517B0
.text:002517B0 ProcessInformation= _PROCESS_INFORMATION ptr -54h
.text:002517B0 StartupInfo= _STARTUPINFOA ptr -44h
.text:002517B0 lpCommandLine= dword ptr 4
.text:002517B0 arg_4= word ptr 8
.text:002517B0
.text:002517B0 sub esp, 54h
.text:002517B3 push edi
.text:002517B4 xor eax, eax
.text:002517B6 mov ecx, 10h
.text:002517BB lea edi, [esp+58h+StartupInfo.lpReserved]
.text:002517BF rep stosd
.text:002517C1 lea eax, [esp+58h+StartupInfo]
.text:002517C5 push eax ; lpStartupInfo
.text:002517C6 mov [esp+5Ch+StartupInfo.cb], 44h
.text:002517CE call ds:GetStartupInfoA
.text:002517D4 mov cx, [esp+58h+arg_4]
.text:002517D9 lea edx, [esp+58h+ProcessInformation]
.text:002517DD push edx ; lpProcessInformation
.text:002517DE lea eax, [esp+5Ch+StartupInfo]
.text:002517E2 push eax ; lpStartupInfo
.text:002517E3 push 0 ; lpCurrentDirectory
.text:002517E5 push 0 ; lpEnvironment
.text:002517E7 push 0 ; dwCreationFlags
.text:002517E9 push 0 ; bInheritHandles
.text:002517EB push 0 ; lpThreadAttributes
.text:002517ED mov [esp+74h+StartupInfo.wShowWindow], cx
.text:002517F2 mov ecx, [esp+74h+lpCommandLine]
.text:002517F6 push 0 ; lpProcessAttributes
.text:002517F8 push ecx ; lpCommandLine
.text:002517F9 push 0 ; lpApplicationName
.text:002517FB call ds:CreateProcessA
[snip]
...

Note that the Windows API function CreateProcessA is called, accepting the raw (unmodified) value of lpCommandLine as an argument. Per the Microsoft Windows Desktop API documentation, the CreateProcessA function “creates a new process and its primary thread. The new process runs in the security context of the calling process.” The lpCommandLine parameter is defined as “the command line to be executed.” In the case of the exploit code, the value “..\\..\\windows\\system32\\calc.exe” is specified as the lpCommandLine, which is why a calculator pops up, with Administrator privileges.

Let’s get some facts straight.

The vulnerability allows for remote command execution with Administrator privileges (pretty severe). We also know that an RPC packet with opcode 0x01 and IOCTL 0x2711 will be processed by five separate functions. These function calls are handled within webvrpcs.exe and drawsrv.dll.

Binary Comparison with BinDiff

Now that we have a high-level understanding of how the vulnerability is exploited, we can proceed to compare the five functions using BinDiff. Since version 8.3 is supposed to be patched, we will compare this to 8.2_20170817. All we need are IDA databases for each version of webvrpcs.exe and drawsrv.dll, and BinDiff will handle the rest (that’s a total of four DBs). Below are flow graphs comparisons of each function.

BinDiff of Function 1
BinDiff of Function 2
BinDiff of Function 3
BinDiff of Function 4
BinDiff of Function 5

Conclusion

So what does this mean?

We found that five function calls process the RPC request payload. The flow graphs for each of these five functions are identical based on the output of BinDiff. This means that each of the five functions are unchanged. A patch was not implemented.

Let’s wrap up with a timeline review. In January, ICS-CERT released an advisory recommending that users update to WebAccess 8.3 to address multiple vulnerabilities, including CVE-2017–16720. A public exploit was then released in March. Two months later, in May, WebAccess 8.3.1 was made available for download. Tenable discovered that both versions 8.3 and 8.3.1 were still vulnerable in July. In the interest of coordinated disclosure, we reported this issue to both Advantech and ICS-CERT. WebAccess 8.3.2 was released in August, and it remains exploitable.

Summary of Events

--

--

Chris Lyne
Tenable TechBlog

Chris is a security researcher. He is a former developer and aims to make the cyber world more secure.