Last week I wrote about how I semi-blindly produced an RCE exploit for the BMC Server Automation RSCD service without access to a test environment. Since then I’ve got my hands on a test environment where I’ve been able to improve the exploit in several ways through further analysis and fuzzing.
The tl;dr; is that I’ve fixed a few bugs through further analysis, debugging, and a little fuzzing/bruteforcing. The exploit can be found over on Github.
The first issue with the original exploit was that it took a significant time for command output to be returned whereas Nessus seemed to be getting a response much faster or immediately. The fact that there was a consistent delay in getting a response from my exploit, yet the response was correct (command output), implied that the server was waiting for more data and a timeout had occurred so the server simply responded to the request/command that it did receive. I figured there either had to be another length field in one of the payload packets, or that there was an “end of command” or “end of session” packet that the server was expecting. I couldn’t identify any signs of the former so I took a step back to further analyse the Nessus exploit.
I created two Python scripts: a TCP listener script, and a TCP replay script. One could have done the job but I figured two would put me more in control and allow me to better understand the exploit. I ran a minimal Nessus policy against the TCP listener script which output the received packets until Nessus appeared to expect a response. At that point I modified the TCP replay script to replay those packets and output the response from the real BMC RSCD service. Repeating this process a few times revealed that a total of eight packets (not four) were sent during a successful scan/exploitation. Replaying those packets resulted in the BMC RSCD service returning the command output immediately. Combining this with the code I already had to generate packets for arbitrary commands made the exploit much faster and far more usable!
Truncated Command Output
Testing the exploit further I noticed that longer command output was being truncated as demonstrated in the following example of running the “help” command:
My original code to extract command output from the server’s response was as follows:
#Read the response length
resLen = struct.unpack(">I", ssck.read(4))
#Read the entire response
resBody = ""
while len(resBody) < resLen:
resBody += ssck.read(4096)
#Check for a successful response
if int(resBody[8:16], 16) == 1:
#Success, print the command output
print "[+] Success, command output:"
#Failed, print error output
errOutLen = int(resBody[8:16], 16) - 1
print "[-] Command execution failed (does the command exist?), output:"
print resBody[17:17 + errOutLen]
Here I made the assumption that the first four bytes of the first output packet would contain the total length of the command output. The script then reads from the socket until at least that much data has been received before checking for success.
The check for success was based on comparing multiple success and failure response packets. Successful responses had an ASCII-HEX length field with a value of 1 as shown in the example below:
In unsuccessful responses the same field indicated the length of an error message as shown in the example below:
By adding a few more calls to SSLSocket.read() into the exploit script and outputting the resulting packets I found that the server was in fact returning all of the command output.
I ran this modified exploit a few more times using both valid and invalid commands and commands producing both long and short output in order to capture traffic for analysis. It was immediately obvious from doing so that the last packet containing command or error output was always followed by a packet containing one of the following two strings:
By reading data until one of the above “end of output” packets was received the exploit could consistently read the full command output back from the exploited BMC RSCD service.
The Disappearing Backslash
Further testing revealed some strange behavior with the backslash character. Running the command ‘cmd /c dir C:\Windows’ resulted in output as though the command ‘cmd /c dir C:\’ had been executed.
My immediate thought was that the backslash may need to be escaped, however various tests around this (escaping, double-escaping, forward slashes, quoting etc) all resulted in the same command output. A quick Google search and skim over the BMC documentation showed examples of command lines containing backslashes but apart from that didn’t lead to anything helpful.
Unfortunately my test environment didn’t give me access to the “nexec” command so I couldn’t use that to generate packets for analysis. Based on the documentation I knew that shell operators (such as the pipe symbol) were supported. Further to this I also knew that I could issue an “agentinfo” command to the BMC RSCD service to identify the target operating system. This allowed me to write a hacky workaround for the disappearing backslash issue under Windows as follows:
if "windows" in platform.lower():
thecmd = thecmd.replace("\\", "%windir:~2,1%")
This uses the Windows shell substring syntax to grab the backslash character from the %windir% environment variable, turning the command ‘cmd /c dir C:\Windows’ into the following (try it):
cmd /c dir C:%windir:~2,1%Windows
While this worked and made the exploit generally more usable, it was a dirty hack that would likely fail in some cases.
While this version of the exploit was providing footholds leading to DA on pentests, I decided to get stuck in to the RSCD process with a debugger. I checked over the imports looking for Windows APIs that would allow RSCD to spawn a process, such as WinExec() and CreateProcess(), and set breakpoints on those functions. Running my exploit again triggered a breakpoint on CreateProcessAsUserW().
Interestingly the command line I specified, ‘cmd /c dir C:\Windows’ had become ‘”cmd” /Q /c dir C:’. The RSCD process wasn’t just stripping out part of my command, it was inserting an additional parameter. Through a combination of stepping out of functions in the debugger and setting further breakpoints I eventually hit a breakpoint where my command string was intact in memory and I was able to step through the instructions to identify the point where it was truncated. It appeared as though the backslash was being treated as an escape character and the following two characters were being consumed/interpreted in the process. Some validation may have been going on leading to the string truncation but I didn’t get that far, instead I decided to drop out of the debugger and do some further testing of potential backslash encodings, unsuccessfully.
Having identified that RSCD supports some kind of escape sequences prefixed with the backslash character I decided to throw together a fuzzer/bruteforcer using my existing exploit script. If RSCD/nexec are encoding/decoding backslash characters then the encoded value is unlikely to be more than a few bytes long. I wrote a fuzzer that iterated over all combinations of byte values from 1 to 4 bytes in length and replaced the backslashes in “dir /c C:\Python27\Doc” with each byte sequence before sending that payload to the RSCD service. If the response contained the string “python2714.chm” then the payload was successful and the byte string was printed out.
It didn’t take long for the fuzzer to identify that the byte sequence 0xc1dc would be decoded to a backslash character by RSCD.
Before updating my exploit code I wanted to be sure that this was definitely the work of RSCD and that it wasn’t some buggy/quirky behavior in the way that Windows was handling strange characters in a path. I fired up my debugger again and set a breakpoint on CreateProcessAsUserW() and, sure enough, the correct command line had been passed in.