端口扫描系统实践心得

端口扫描系统实践心得

from:https://www.freebuf.com/articles/es/201210.html

端口扫描对任何一名网络安全从业者来说都不陌生,但作为一名小白,在甲方做扫描系统时踩了不少坑,在网络上找相关资料时没有发现太多相关的文章,于是想写下这篇文章和大家分享一下代码,顺便讨教一下主机存活判断和指纹识别的问题,欢迎大佬们批评和指正。

0×00 目的

对于外网,能够监控对外开放端口情况,并及时的发现向外暴露的高危端口,以便安全人员进行响应处理。对于内网,日常 的端口扫描以及指纹识别,不仅能够帮助梳理公司资产,并且能够帮助进行后续内网的漏洞扫描。

0×01 存活主机判断

开始做端口扫描时,所考虑的第一步便是存活主机判断。最初的设想便是使用nmap的-sP参数,对IP地址进行存活判断。

代码如下:

def ip_alive_check(ip_str):
   cmd = "/usr/bin/nmap -sP "+ip_str output = os.popen(cmd).readlines() flag = False for line in list(output): if not line: continue if str(line).lower().find("1 host up") >= 0: flag = True break return flag 

但随后便发现这个办法存在问题,引用Nmap官方文档如下:

-sP选项在默认情况下, 发送一个ICMP回声请求和一个TCP报文到80端口。  
  如果非特权用户执行,就发送一个SYN报文 (用connect()系统调用)到目标机的80端口。 
  当特权用户扫描局域网上的目标机时,会发送ARP请求(-PR), ,除非使用了--send-ip选项。 
-sP选项可以和除-P0)之外的任何发现探测类型-P* 选项结合使用以达到更大的灵活性。   
  一旦使用了任何探测类型和端口选项,默认的探测(ACK和回应请求)就被覆盖了。 
  当防守严密的防火墙位于运行Nmap的源主机和目标网络之间时, 推荐使用那些高级选项。  
  否则,当防火墙捕获并丢弃探测包或者响应包时,一些主机就不能被探测到。

抓包如下:

局域网环境:

非root用户

image.pngimage.png

nmap通过向目标IP的80端口和443端口分别发送SYN包来判断主机是否存活,由于目标主机的80和443端口均未开放,所以均返回RST包

nmap扫描结果:0 hosts up

root用户

image.pngimage.png

nmap发送ARP请求并得到响应

nmap扫描结果:1 hosts up

非局域网环境:

非root用户

image.pngimage.png

同样的,nmap向目标主机的80和443端口发送SYN包,通过返回的确认包得到目标主机存活。所以扫描结果为:1 hosts up。

root用户

image.png

image.png

这次,nmap不仅向目标主机的80和443端口发送了SYN包,还向目标主机发送了ICMP Echo请求以及Timestamp请求,nmap会综合这四种方式的响应情况来判断目标主机是否存活。显然这次的扫描结果为:1 hosts up。

通过对nmap -sP参数的分析便可得知,实际上对存活主机的判断并不准确。许多主机的防火墙会过滤掉ICMP包,而且80和443端口也不一定会保证对外开放。

而nmap官方文档中提到的高级选项在实际的使用中也都不能保证准确性,所以对于存活主机的判断,一直没有找到比较好的解决办法,在实际的扫描中便没有用上这一步骤,还请各路大佬指点指点有没有什么成本比较低的解决方案。

0×02 Masscan扫描端口

直接对全端口使用nmap进行扫描速度较慢,所以选择使用号称,三分钟扫遍全网的masscan。

masscan采用的是无状态的扫描技术即无需关心TCP状态,不占用系统TCP/IP协议栈资源,忘记syn,ack,fin,timewait ,不进行会话组包,而nmap则是需要记录TCP/IP的状态,并且OS能够处理的TCP/IP连接数存在上限,这就导致了nmap扫描的速度不如masscan。

代码如下:

class Masscan(object):
    def __init__(self, args): self.masscan_bin = config.MASSCAN_BIN # Masscan路径 例如:/usr/bin/masscan self.result_xml = '/tmp/masscan/'+args['hosts'] # 暂存的masscan扫描结果名称 self.rate = config.MASSCAN_RATE # 发包速率,例如:10000 self.retries = config.MASSCAN_RETRIES # 发送重试的次数 例如:3 self.wait = config.MASSCAN_WAIT # 指定发送完包之后的等待时间,例如:5 self.ports = args['ports'] # 端口 self.hosts = args['hosts'] # IP def scan(self): cmd = 'mkdir -p /tmp/masscan/' os.system(cmd) command = ( '{masscan_bin} -oX {result_xml} --rate={rate} --retries={retries} --wait={wait} -p {ports} {hosts}' ).format( masscan_bin=self.masscan_bin, result_xml=self.result_xml, rate=self.rate, retries=self.retries, wait=self.wait, hosts=self.hosts, ports=self.ports ) process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) logger.info(b'\nStarting masscan,the command is '+str(command)) try: _, stderr = process.communicate() if not stderr.startswith(b'\nStarting masscan'): logger.failure('Masscan Error\n{}'.format(stderr)) os._exit(1) except KeyboardInterrupt: logger.failure('User aborted') os._exit(1) def parse_result_xml(self, d_ip): result = {} try: xml_size = os.path.getsize(self.result_xml) if xml_size > 0 and xml_size < 10000: tree = ET.parse(self.result_xml) root = tree.getroot() for host in root.iter('host'): ip = host.find('address').attrib['addr'] port = host.find('ports').find('port').attrib['portid'] if result.setdefault(ip): result[ip].append(port) else: result[ip] = [port] elif xml_size >= 10000: result = {d_ip: ['1-65535']} else: result = {} except Exception as e: logger.info('----------') logger.info(str(e)) logger.info('ParseError!!!') logger.info('----------') logger.info(self.result_xml) pass cmd_rm = 'rm -rf ' + self.result_xml os.system(cmd_rm) return result 

在使用masscan得注意速率问题,在带宽有限的情况下,速率过高则会导致丢包的情况发生从而导致扫描结果漏报。具体的速率根据实际的带宽情况慢慢调教即可。

在缓存masscan的扫描结果时,选择了直接写在tmp目录下,解析完后再删除。也可以使用redis进行缓存。

在实际的测试中发现了一个问题,masscan在扫描时,可能是因为目标主机防火墙的抗DDos功能,对masscan所发送的SYN包均会回复ACK包,所以masscan会误报部分IP开放特别大量端口的情况。选择了对xml文件的大小加了个判断,如果过大,直接将结果置为1-65535,扔给nmap重新扫一下。

0×03 Nmap扫描识别指纹

Masscan虽然扫描速度够快,但是在指纹识别这一块却是远远比不了nmap,于是在masscan扫描完成后,使用nmap对端口进行指纹识别,以及确认结果以防止masscan误报(实际上masscan的误报好像挺少的)。

代码如下:

class Nmap(object):
    def __init__(self, masscan_result): self.nm = nmap.PortScanner(nmap_search_path=(config.NMAP_BIN,)) # config.NMAP_BIN:nmap的路径,例如:/usr/bin/nmap self.nmap_args = config.NMAP_ARGS # nmap 扫描时的参数 例如:-Pn -sV -sS --host-timeout 1200 self.targets = [] self.result = [] for host, ports in masscan_result.items(): self.targets.append({host: ','.join(ports)}) def scan(self, args): for target in self.targets: for host, ports in target.items(): try: self.nm.scan(host, ports, self.nmap_args) if host not in self.nm.all_hosts(): continue if self.nm[host].has_key('tcp'): for port, data in self.nm[host]['tcp'].items(): state = data['state'] product = data['product'] name = data['name'] ip = args['hosts'] address = args['address'] if product: service = product else: service = name version = data['version'] if state == "open": x = save_it(ip, port, address, service, version) x.detect_new_port() # 储存结果时,进行一下判断,看是否是新增端口 if self.nm[host].has_key('udp'): for port, data in self.nm[host]['udp'].items(): state = data['state'] product = data['product'] name = data['name'] ip = args['hosts'] address = args['address'] if product: service = product else: service = name version = data['version'] if state == "open": x = save_it(ip, port, address, service, version) x.detect_new_port() # 储存结果时,进行一下判断,看是否是新增端口 except Exception as e: logger.info('the exception i nmap.scan is ' + str(e)) continue 

在设置nmap的扫描参数时,别忘了带上-Pn或者-P0跳过判断主机存活的步骤,因为nmap默认是会先对主机进行存活判断再进行扫描,可能会因为误判而导致漏扫。

0×04 告警

告警部分则是和扫描一样,使用django的celery进行定时任务,所以扫描和告警存在一定的时间间隔,于是在告警前便使用socket对库中的扫描结果进行一下验证。

def detect_port(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1) try: s.connect((ip, int(port))) s.shutdown(2) return True except Exception as e: return False 

0×05 后续

多进程则是由celery所依赖的billiard库实现

from billiard import Pool

p = Pool(5)
for ip in ip_data: p.apply_async(port_scan, args=(ip, log_name,)) p.close() p.join() 

如果IP数量较大,可以将端口扫描、资产发现、漏洞扫描等集成起来,做成agent,搭配rabbitmq实现分布式的扫描系统。

扫描时别忘了避开交换机和打印机等比较容易脆弱的设备,在实际进行扫描时就曾遇见过某型号打印机存在缺陷,一扫就自己疯狂打印(都吓到了晚上正在加班的同事),最后无奈只能避开。

经过一段时间的使用,带宽足够,速率合适的情况下,masscan扫描的准确性还挺不错的。但即使nmap的指纹库已经较为丰富,在识别web应用程序、中间件这些时,还是有些不够用,不便于后续的漏洞扫描。

猜你喜欢

转载自www.cnblogs.com/bonelee/p/10735137.html