From bab228e2b0b8c1e4b1cd0d655ef6310b8b9d1dd2 Mon Sep 17 00:00:00 2001 From: Kyle Westhaus Date: Tue, 14 Apr 2020 10:53:31 -0400 Subject: [PATCH] New features: service detection and comma-separated ports (#48) --- scanners/host_up.py | 4 +- .../service_detection/service_detection.py | 98 +++++++++++++++++++ scanners/tcp_connect.py | 2 +- scanners/tcp_privileged/syn.py | 2 +- scanners/tcp_privileged/window.py | 2 +- urban_rain.py | 36 +++++-- 6 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 scanners/service_detection/service_detection.py diff --git a/scanners/host_up.py b/scanners/host_up.py index b16febd..0c358ef 100644 --- a/scanners/host_up.py +++ b/scanners/host_up.py @@ -10,7 +10,7 @@ def run(targets, is_admin): # TCP ACK to port 80, and an ICMP timestamp request icmp_echo_targets_up = icmp_echo.run(targets, print_results=False) targets_up.update(icmp_echo_targets_up) - tcp_syn_targets_up = tcp_privileged.syn.run(targets, [443], options = [1, 2, 3, 4, 5], fragment_size=None, print_results=False) + tcp_syn_targets_up, tcp_syn_ports_open = tcp_privileged.syn.run(targets, [443], options = [1, 2, 3, 4, 5], fragment_size=None, print_results=False) targets_up.update(tcp_syn_targets_up) tcp_ack_targets_up = tcp_privileged.ack.run(targets, [80], options = [2, 3, 4, 5], fragment_size=None, print_results=False) targets_up.update(tcp_ack_targets_up) @@ -20,7 +20,7 @@ def run(targets, is_admin): # Unprivileged # Use TCP connect() scan on two common ports so as to not look suspicious check_tcp_ports = [80, 443] - tcp_connect_targets_up = tcp_connect.run(targets, check_tcp_ports, print_results=False) + tcp_connect_targets_up, tcp_connect_ports_open = tcp_connect.run(targets, check_tcp_ports, print_results=False) targets_up.update(tcp_connect_targets_up) ping_os_targets_up = ping_os.run(targets, print_results=False, verbose=False) targets_up.update(ping_os_targets_up) diff --git a/scanners/service_detection/service_detection.py b/scanners/service_detection/service_detection.py new file mode 100644 index 0000000..caf46f9 --- /dev/null +++ b/scanners/service_detection/service_detection.py @@ -0,0 +1,98 @@ +# Kyle Westhaus +from color import pcolor +import socket + +HTTP_MSG = b'GET / HTTP/1.0\r\n\r\n' + +SMB_MSG = b"\x00\x00\x00\xa4\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x08\x01\x40" \ +b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x06" \ +b"\x00\x00\x01\x00\x00\x81\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f" \ +b"\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02" \ +b"\x4d\x49\x43\x52\x4f\x53\x4f\x46\x54\x20\x4e\x45\x54\x57\x4f\x52" \ +b"\x4b\x53\x20\x31\x2e\x30\x33\x00\x02\x4d\x49\x43\x52\x4f\x53\x4f" \ +b"\x46\x54\x20\x4e\x45\x54\x57\x4f\x52\x4b\x53\x20\x33\x2e\x30\x00" \ +b"\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x4c\x4d\x31\x2e" \ +b"\x32\x58\x30\x30\x32\x00\x02\x53\x61\x6d\x62\x61\x00\x02\x4e\x54" \ +b"\x20\x4c\x41\x4e\x4d\x41\x4e\x20\x31\x2e\x30\x00\x02\x4e\x54\x20" \ +b"\x4c\x4d\x20\x30\x2e\x31\x32\x00" + +def run(ports_open): + for target, ports in ports_open.items(): + print(f'Service Results on Host {target}') + print(f'Port\tService') + for port in ports: + time.sleep(1) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) + s.settimeout(2) + s.connect((str(target), port)) + except: + # Port wasn't actually open or socket error, no service detected + print(f'{port}\tNo service detected') + s.close() + continue + + try: + ssh_resp = s.recv(1024) + ssh_resp = str(ssh_resp.rstrip(b'\r\n'), 'ascii') + except: + # No response, not SSH, run other detections + pass + else: + # SSH, test response + if 'SSH' in ssh_resp: + print(f'{port}\t{ssh_resp}') + else: + print(f'{port}\tUnknown') + s.close() + continue + + # Other detections + try: + s.send(HTTP_MSG) + except: + # Port wasn't actually open or socket error, no service detected + print(f'{port}\tNo service detected') + s.close() + continue + + try: + http_resp = s.recv(1024) + except: + # No response, not HTTP, run other detections + pass + else: + if b'HTTP' in http_resp: + print(f'{port}\tHTTP') + else: + print(f'{port}\tUnknown') + s.close() + continue + + # Other detections + try: + s.close() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) + s.connect((str(target), port)) + s.send(SMB_MSG) + except: + # Port wasn't actually open or socket error, no service detected + print(f'{port}\tNo service detected') + continue + + try: + smb_resp = s.recv(1024) + except: + # No response, all detections failed + pass + else: + if b'SMB' in smb_resp: + print(f'{port}\tSamba') + continue + + # Fallthrough, all detections failed + print(f'{port}\tUnknown') + s.close() + + print('Service detection complete.') + diff --git a/scanners/tcp_connect.py b/scanners/tcp_connect.py index b43f293..0d7c657 100644 --- a/scanners/tcp_connect.py +++ b/scanners/tcp_connect.py @@ -46,7 +46,7 @@ def run(targets, port_range, print_results=True): output_ports(open_targets, closed_targets, filtered_targets) else: log(open_targets, closed_targets, filtered_targets) - return up_hosts + return up_hosts, open_targets def get_port_status(s, host, port): diff --git a/scanners/tcp_privileged/syn.py b/scanners/tcp_privileged/syn.py index 82c23a3..980f642 100644 --- a/scanners/tcp_privileged/syn.py +++ b/scanners/tcp_privileged/syn.py @@ -95,7 +95,7 @@ def run(targets, port_range, options, fragment_size, src_ip=None, print_results= print_result(open_targets, closed_targets, filtered_targets, unexpected_targets) else: log(open_targets, closed_targets, filtered_targets, unexpected_targets) - return up_hosts + return up_hosts, open_targets def print_result(open_targets, closed, filtered, unexpected): diff --git a/scanners/tcp_privileged/window.py b/scanners/tcp_privileged/window.py index 68de9d9..6f4d1d8 100644 --- a/scanners/tcp_privileged/window.py +++ b/scanners/tcp_privileged/window.py @@ -85,7 +85,7 @@ def run(targets, ports, options, fragment_size, src_ip=None, print_results=True) print_result(open_targets, closed_targets, filtered_targets, unexpected_targets) else: log(open_targets, closed_targets, filtered_targets, unexpected_targets) - return + return open_targets def print_result(open_targets, closed, filtered, unexpected): diff --git a/urban_rain.py b/urban_rain.py index 5d03816..29b49cd 100755 --- a/urban_rain.py +++ b/urban_rain.py @@ -12,19 +12,26 @@ from scanners.util.host_parser import parse_hosts from attacks import syn_attack from scanners.os_detection import os_detection +from scanners.service_detection import service_detection import subprocess import socket import re import textwrap +import time # Parser for port ranges def parseNumRange(string): m = re.match(r'(\d+)(?:-(\d+))?$', string) if not m: - raise argparse.ArgumentTypeError(pcolor.color.ERROR + "'" + string + "' is not a range of numbers (e.g. '80-100')." + pcolor.color.CLEAR) - start = m.group(1) - end = m.group(2) or start - return list(range(int(start,10), int(end,10)+1)) + m = re.match(r'(\d+)(?:,(\d+))*$', string) + if not m: + raise argparse.ArgumentTypeError(pcolor.color.ERROR + "'" + string + "' is not a range of numbers (e.g. '80-100')." + pcolor.color.CLEAR) + else: + return [int(group) for group in string.split(',')] + else: + start = m.group(1) + end = m.group(2) or start + return list(range(int(start,10), int(end,10)+1)) # Check if fragment size is multiple of 8 def fragmentSize(string): @@ -105,6 +112,7 @@ def main(): parser.add_argument('-sF', action='store_true', help='run a privileged TCP FIN scan') parser.add_argument('-sM', action='store_true', help='run a privileged TCP Maimon scan') parser.add_argument('-sW', action='store_true', help='run a privileged TCP Window scan') + parser.add_argument('-sV', action='store_true', help='run service detection on open ports') parser.add_argument('-f', '--fragmenter', type=fragmentSize, help='fragment privileged TCP scan') @@ -144,6 +152,8 @@ def main(): else: unpacked_targets.add(str(target)) + ports_open = {} + # Run selected scan types (can be multiple) if args.discovery == 'host' or args.discovery == 'both': unpacked_targets = host_up.run(unpacked_targets, is_admin) @@ -153,14 +163,16 @@ def main(): scantype_provided = 0 if args.sT: scantype_provided = 1 - tcp_connect.run(unpacked_targets, args.port_range,args.log) + tcp_connect_targets_up, tcp_connect_ports_open = tcp_connect.run(unpacked_targets, args.port_range,args.log) + ports_open.update(tcp_connect_ports_open) if args.sU: scantype_provided = 1 udp_connect.run(unpacked_targets, args.port_range, args.log) if args.sS: scantype_provided = 1 if is_admin: - syn.run(unpacked_targets, args.port_range, optionList, args.fragmenter, args.src_addr, args.log) + tcp_syn_targets_up, tcp_syn_ports_open = syn.run(unpacked_targets, args.port_range, optionList, args.fragmenter, args.src_addr, args.log) + ports_open.update(tcp_syn_ports_open) else: print(pcolor.color.WARNING + 'TCP SYN scan requires privileges, skipping' + pcolor.color.CLEAR) if args.sA: @@ -208,9 +220,19 @@ def main(): if args.sW: scantype_provided = 1 if is_admin: - window.run(unpacked_targets, args.port_range, optionList, args.fragmenter, args.src_addr, args.log) + tcp_window_ports_open = window.run(unpacked_targets, args.port_range, optionList, args.fragmenter, args.src_addr, args.log) + ports_open.update(tcp_window_ports_open) else: print(pcolor.color.WARNING + 'TCP Window Scan requires privileges, skipping' + pcolor.color.CLEAR) + if args.sV: + scantype_provided = 1 + if not (args.sT or args.sS or args.sW): + print(pcolor.color.WARNING + 'Service detection must be paired with either a TCP Connect, TCP SYN, or TCP Window scan, skipping' + pcolor.color.CLEAR) + elif len(ports_open) == 0: + print(pcolor.color.WARNING + 'No open ports passed to service detection, skipping' + pcolor.color.CLEAR) + else: + time.sleep(1) + service_detection.run(ports_open) if scantype_provided == 0: print(pcolor.color.WARNING + 'Port scan requested but no scan type provided, skipping' + pcolor.color.CLEAR)