一、使用
将fscan扫描后的结果文件进行信息识别、汇总,输出生成excel表格文档。
用法举例:
简单使用:python fscan_result_merge_toExcel.py -i ./result.txt -o ./内网资产信息.xlsx
屏蔽特定IP:python fscan_result_merge_toExcel.py -i ./result.txt -n “10.217.35.158,10.23.32.0/24”
只整合特定IP:python fscan_result_merge_toExcel.py -i ./result.txt -a “192.168.0.0/16”
本脚本用于整合fscan探测结果,生成excel表格文档。(仅作合法用途)
optional arguments:
-h, --help show this help message and exit
-i filename, --input filename
fscan生成的探测结果 文本文件(.txt)
-o filename, --output filename
整合信息后所生成的表格文档 保存路径(含文件名)
-n IP地址(段), --neglect IP地址(段)
传入需要排除的IP地址(段),如: -n 192.168.1.1,192.168.23.0/24
-nf filename, --neglect_file filename
包含需要排除的IP地址(段)的可读文件,其中每个IP地址(段)独占一行
-a IP地址, --allow IP地址
限制程序只处理 该参数所传入的IP地址(段)相关信息。
二、代码
#! /usr/bin/env python3
# -*- coding: UTF-8 -*-
# File: fscan_result_merge_toExcel.py
# Author: learning
# History: 2023/7/21
"""fscan结果整合成excel表格"""
import argparse
import os
import re
import ipaddress
import pandas as pd
from colorama import init, Fore, Style
init(autoreset=True)
def color_text(text, color):
"""
终端字体颜色调整
:param text:
:param color:
:return:
"""
return f"{color}{text}{Style.RESET_ALL}"
def readfile(filename: str) -> list:
"""
读取(文本)文件
"""
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.strip(' ') for line in f
if 'Unsolicited response received' not in line
and '[->]' not in line]
return lines
def is_valid_ipv4(ip):
"""
判断是否为合法的IP地址(段)
:param ip:
:return:
"""
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$')
match = pattern.match(ip.strip())
return match is not None
def get_black_ip_range(ip_string='', ip_file=''):
ip_list = []
if len(ip_string) > 0:
ip_string_list = ip_string.split(',')
for ip in ip_string_list:
ip = ip.strip()
if is_valid_ipv4(ip):
ip_list.append(ip)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
if ip_list:
return ip_list
if ip_file == '':
return []
if not os.path.isfile(ip_file):
raise FileExistsError('传入的用于屏蔽相关IP地址(段)的文件路径不存在!')
with open(ip_file, 'r') as f:
for line in f:
line = line.strip()
if is_valid_ipv4(line):
ip_list.append(line)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
return ip_list
def get_black_ip_network(ip_string, ip_file):
ip_list = get_black_ip_range(ip_string, ip_file)
black_networks = [ipaddress.ip_network(ip) for ip in ip_list]
return black_networks
def check_ip_in_black_network(ip, black_networks):
ip_obj = ipaddress.ip_address(ip)
for n in black_networks:
if ip_obj in n:
return True
return False
def get_white_ip_range(ip_string):
ip_list = []
if len(ip_string) > 0:
ip_string_list = ip_string.split(',')
for ip in ip_string_list:
ip = ip.strip()
if is_valid_ipv4(ip):
ip_list.append(ip)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
if ip_list:
return ip_list
return ip_list
def check_ip_in_white_network(ip, black_networks):
ip_obj = ipaddress.ip_address(ip)
for n in black_networks:
if ip_obj in n:
return True
return False
def get_white_ip_network(ip_string):
ip_list = get_white_ip_range(ip_string)
white_networks = [ipaddress.ip_network(ip) for ip in ip_list]
return white_networks
def get_ip_ports(content) -> dict:
"""
获取:url, code, length, title
:param content:
:return: dict{ ip: [port1, port2, ...] }
"""
ip_port_dict = {}
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)', line)
if match:
ip, port = match.group(1), match.group(2)
if ip not in ip_port_dict:
ip_port_dict[ip] = []
if port in ip_port_dict[ip]:
continue
ip_port_dict[ip].append(port)
return ip_port_dict
def get_url_info(content) -> dict:
"""
获取:url, code, length, title, finger
:param content:
:return: dict{ url: [title, code, length, finger] }
"""
url_info_dict = {}
text = '[INFO] 对可能存在【多次访问但数据不一致】的情况' \
' (title, 响应码, 数据长度, 指纹),将会在下方打印输出,以供比较...'
message = color_text(text, Fore.YELLOW)
print(message)
for line in content:
match = re.search(
r'WebTitle: (http.*?)\s+code:(\d+)\s+len:(.*?)\s+title:(.*?)\n',
line)
if match:
url, code, length, title = \
match.group(1), match.group(2), \
match.group(3), match.group(4)
finger = ''
if url not in url_info_dict:
url_info_dict[url] = [title, code, length, finger]
continue
if [title, code, length, finger] == url_info_dict[url]:
continue
# 通过比较,去除title出现的异常空格
count1 = url_info_dict[url][0].count(' ')
count2 = title.count(' ')
if count1 > count2:
url_info_dict[url][0] = title
if [code, length, finger] == url_info_dict[url][1:] \
and abs(len(title) - len(url_info_dict[url][0])) < 2:
continue
if [title, code, length] == url_info_dict[url][:-1] \
and len(finger) <= len(url_info_dict[url][-1]):
continue
print(url, url_info_dict[url], '|', [title, code, length, finger])
else:
match = re.search(r'InfoScan:\s*(http.*?)\s+\[(.*?)]', line)
if match:
url, finger = match.group(1), match.group(2)
if url in url_info_dict:
url_info_dict[url][-1] = finger
return url_info_dict
def get_ip_exp(content):
"""
获取:IP、对应的主机系统漏洞
:param content:
:return: list[[ip, exp]]
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(
r'\[\+]\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(.*)',
line.strip('\n '))
if match:
ip, exp = match.group(1), match.group(2)
exp = re.sub(r'\s+', ' ', exp)
ip_exp_dict = {'IP': ip, '主机漏洞': exp}
if ip_exp_dict in data:
continue
data.append(ip_exp_dict)
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
data = sorted(data, key=lambda _: _['主机漏洞'])
return data
def get_url_poc(content):
"""
获取:url、对应poc
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'(http.*?) (poc-.*)', line)
if match:
url, poc = match.group(1), match.group(2)
url_poc_dict = {'URL': url, 'POC': poc.strip()}
if url_poc_dict in data:
continue
data.append(url_poc_dict)
return data
def format_url_dict(url: str, url_info: list):
if url == '':
return {
'url': '',
'title': '',
'code': '',
'length': '',
'finger': ''
}
title, code, length, finger = url_info
url_dict = {
'url': url,
'title': title,
'code': int(code),
'length': int(length),
'finger': finger
}
return url_dict
def search_url(ip, port, url_info_dict):
if str(port) == '80':
pattern = re.compile(fr"^http://{ip}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
elif str(port) == '443':
pattern = re.compile(fr"^https://{ip}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
pattern = re.compile(fr"^https?://{ip}:{port}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
return format_url_dict('', [])
def get_weak_auth(content):
"""
获取:弱口令、无认证等信息
(ip, port, service, username, password, more_info)
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
if 'handshake failed' in line:
continue
info_dict = {'IP': '', '端口': '', '服务': '',
'用户名': '', '密码': '', '附加信息': ''}
# mysql, mssql...
match = re.search(
r'(\S+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+):(\S+)\s+(\S+)',
line)
if match:
service, ip, port, username, password = \
match.group(1), match.group(2), match.group(3), \
match.group(4), match.group(5)
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = username
info_dict['密码'] = password
info_dict['附加信息'] = ''
data.append(info_dict)
continue
# ftp...
match = re.search(
r'(\S+)://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+):(\S+)(.*)',
line
)
if match:
service, ip, port, username, password = \
match.group(1), match.group(2), match.group(3), \
match.group(4), match.group(5)
if len(password) >= 1:
password = password.strip()
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = username
info_dict['密码'] = password
info_dict['附加信息'] = ''
data.append(info_dict)
continue
# redis...
match = re.search(
r'(\S+)[:\s](\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)\s+(.*)',
line
)
if match:
service, ip, port, more_info = \
match.group(1), match.group(2), \
match.group(3), match.group(4)
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = ''
info_dict['密码'] = ''
info_dict['附加信息'] = more_info
data.append(info_dict)
continue
data = [i for index, i in enumerate(data) if i not in data[:index]]
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
data = sorted(data, key=lambda _: int(_['端口']))
return data
def clean_content(content, black_networks, white_networks):
if len(black_networks) == 0 and len(white_networks) == 0:
return content
new_content = []
if len(white_networks) == 0:
for line in content:
if '[*] LiveTop ' in line:
continue
ip = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\D', line)
if ip:
ip = ip.group(1)
if check_ip_in_black_network(ip, black_networks):
continue
new_content.append(line)
return new_content
else:
for line in content:
if '[*] LiveTop ' in line:
continue
ip = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\D', line)
if ip:
ip = ip.group(1)
if not check_ip_in_white_network(ip, white_networks):
continue
if check_ip_in_black_network(ip, black_networks):
continue
new_content.append(line)
return new_content
def get_domain_os(content):
"""
获取:各域内大体的关系
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'NetBios: (.*?)\s+(.*?\S)\s+(.*)', line)
if match:
ip, domain, OS = match.group(1), match.group(2), match.group(3)
if len(OS) > 1 and not OS[0].isalnum():
OS = OS[1:]
info_dict = {'IP': ip, '域名': domain, '操作系统': OS.strip()}
data.append(info_dict)
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
return data
def get_collated_info(input_filename='./result.txt',
output_filename='内网资产探测信息表.xlsx',
black_networks=(),
white_networks=()):
"""
获取各类信息,并合并成一个表格文档。
IP 端口 协议 服务 URL Title 指纹 返回码
[IP, port, protocol(默认tcp), server(无法识别:""), url, title, finger, code]
:return: list[[IP, port, protocol, server, url, title, finger, code]]
"""
content = readfile(input_filename)
content = list(dict.fromkeys(content))
content = clean_content(content, black_networks, white_networks)
# 常规资产信息
ip_ports = get_ip_ports(content)
url_info_dict = get_url_info(content)
domain_os_list = get_domain_os(content)
# 可能性高的脆弱资产
weak_auth_list = get_weak_auth(content)
ip_exp_list = get_ip_exp(content)
url_poc_list = get_url_poc(content)
data = []
ip_data = []
# IP排序(小 --> 大)
ip_ports = sorted(ip_ports.items(),
key=lambda _: ipaddress.ip_address(_[0]))
for ip, ports in ip_ports:
# 端口排序(小 --> 大)
ports.sort(key=int)
ip_data.append({'IP': ip, '端口': ' '.join(ports)})
for p in ports:
d_row = {'IP': '', '端口': '', '协议': 'tcp', '服务': '',
'URL': '', 'Title': '', '指纹': '', '返回码': ''}
url_info = search_url(ip, p, url_info_dict)
d_row['IP'] = ip
d_row['端口'] = int(p)
d_row['协议'] = 'tcp'
d_row['URL'] = url_info['url']
d_row['Title'] = url_info['title']
d_row['指纹'] = url_info['finger']
d_row['返回码'] = url_info['code']
data.append(d_row)
data = sorted(data, key=lambda _: int(_['端口']))
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
df_web = pd.DataFrame(data)
df_domain = pd.DataFrame(domain_os_list)
df_ip = pd.DataFrame(ip_data)
df_weak = pd.DataFrame(weak_auth_list)
df_poc = pd.DataFrame(url_poc_list)
df_exp = pd.DataFrame(ip_exp_list)
name, suffix = os.path.splitext(output_filename)
weak_filename = name + '_脆弱资产' + suffix
with pd.ExcelWriter(output_filename) as writer:
df_web.to_excel(writer, sheet_name='资产信息', index=False)
df_domain.to_excel(writer, sheet_name='域信息', index=False)
df_ip.to_excel(writer, sheet_name='存活IP', index=False)
with pd.ExcelWriter(name + '_脆弱资产' + suffix) as writer:
df_weak.to_excel(writer, sheet_name='脆弱认证', index=False)
df_poc.to_excel(writer, sheet_name='POC', index=False)
df_exp.to_excel(writer, sheet_name='主机漏洞', index=False)
text = f'[INFO] 已导出:{os.path.realpath(output_filename)}'
message = color_text(text, Fore.GREEN)
print(message)
text = f'[INFO] 已导出:{os.path.realpath(weak_filename)}'
message = color_text(text, Fore.GREEN)
print(message + '\n')
def main():
parser = argparse.ArgumentParser()
parser.description = "本脚本用于整合fscan探测结果,生成excel表格文档。" \
"(仅作合法用途)\t\t--DJH"
parser.usage = "\n用法举例:\n" \
"\t简单使用:python fscan_result_merge_toExcel.py " \
"-i ./result.txt -o ./内网资产信息.xlsx\n" \
"\t屏蔽特定IP:python fscan_result_merge_toExcel.py " \
"-i ./result.txt " \
"-n \"10.217.35.158,10.23.32.0/24\"\n" \
"\t只整合特定IP:python fscan_result_merge_toExcel.py " \
"-i ./result.txt -a \"192.168.0.0/16\""
parser.add_argument('-i', '--input', default='./result.txt',
metavar="filename",
help='fscan生成的探测结果 文本文件(.txt)')
parser.add_argument('-o', '--output', default='./内网资产信息.xlsx',
metavar="filename",
help='整合信息后所生成的表格文档 保存路径(含文件名)')
parser.add_argument('-n', '--neglect', default='',
metavar='IP地址(段)',
help='传入需要排除的IP地址(段),'
'如: -n 192.168.1.1,192.168.23.0/24')
parser.add_argument('-nf', '--neglect_file', default='',
metavar='filename',
help='包含需要排除的IP地址(段)的可读文件,'
'其中每个IP地址(段)独占一行')
parser.add_argument('-a', '--allow', default='',
metavar='IP地址',
help='限制程序只处理 该参数所传入的IP地址(段)相关信息。')
args = parser.parse_args()
if not os.path.isfile(args.input):
print("[ERROR] 输入的fscan结果文件路径不存在,请检查。")
return
if ' ' in args.neglect:
print("[ERROR] 输入的(用于屏蔽的)IP地址(段)参数格式不合法:不能含有空格。")
white_networks = get_white_ip_network(
ip_string=args.allow
)
black_networks = get_black_ip_network(
ip_string=args.neglect, ip_file=args.neglect_file
)
text = "[INFO] 程序开始运行..."
message = color_text(text, Fore.GREEN)
print(message)
get_collated_info(input_filename=args.input,
output_filename=args.output,
black_networks=black_networks,
white_networks=white_networks)
if __name__ == '__main__':
main()
一、使用
将fscan扫描后的结果文件进行信息识别、汇总,输出生成excel表格文档。
用法举例:
简单使用:python fscan_result_merge_toExcel.py -i ./result.txt -o ./内网资产信息.xlsx
屏蔽特定IP:python fscan_result_merge_toExcel.py -i ./result.txt -n "10.217.35.158,10.23.32.0/24"
只整合特定IP:python fscan_result_merge_toExcel.py -i ./result.txt -a "192.168.0.0/16"
本脚本用于整合fscan探测结果,生成excel表格文档。(仅作合法用途)
optional arguments:
-h, --help show this help message and exit
-i filename, --input filename
fscan生成的探测结果 文本文件(.txt)
-o filename, --output filename
整合信息后所生成的表格文档 保存路径(含文件名)
-n IP地址(段), --neglect IP地址(段)
传入需要排除的IP地址(段),如: -n 192.168.1.1,192.168.23.0/24
-nf filename, --neglect_file filename
包含需要排除的IP地址(段)的可读文件,其中每个IP地址(段)独占一行
-a IP地址, --allow IP地址
限制程序只处理 该参数所传入的IP地址(段)相关信息。
二、代码
#! /usr/bin/env python3
# -*- coding: UTF-8 -*-
# File: fscan_result_merge_toExcel.py
# Author: DJH
# History: 2023/7/21
"""fscan结果整合成excel表格"""
import argparse
import os
import re
import ipaddress
import pandas as pd
from colorama import init, Fore, Style
init(autoreset=True)
def color_text(text, color):
"""
终端字体颜色调整
:param text:
:param color:
:return:
"""
return f"{color}{text}{Style.RESET_ALL}"
def readfile(filename: str) -> list:
"""
读取(文本)文件
"""
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.strip(' ') for line in f
if 'Unsolicited response received' not in line
and '[->]' not in line]
return lines
def is_valid_ipv4(ip):
"""
判断是否为合法的IP地址(段)
:param ip:
:return:
"""
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$')
match = pattern.match(ip.strip())
return match is not None
def get_black_ip_range(ip_string='', ip_file=''):
ip_list = []
if len(ip_string) > 0:
ip_string_list = ip_string.split(',')
for ip in ip_string_list:
ip = ip.strip()
if is_valid_ipv4(ip):
ip_list.append(ip)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
if ip_list:
return ip_list
if ip_file == '':
return []
if not os.path.isfile(ip_file):
raise FileExistsError('传入的用于屏蔽相关IP地址(段)的文件路径不存在!')
with open(ip_file, 'r') as f:
for line in f:
line = line.strip()
if is_valid_ipv4(line):
ip_list.append(line)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
return ip_list
def get_black_ip_network(ip_string, ip_file):
ip_list = get_black_ip_range(ip_string, ip_file)
black_networks = [ipaddress.ip_network(ip) for ip in ip_list]
return black_networks
def check_ip_in_black_network(ip, black_networks):
ip_obj = ipaddress.ip_address(ip)
for n in black_networks:
if ip_obj in n:
return True
return False
def get_white_ip_range(ip_string):
ip_list = []
if len(ip_string) > 0:
ip_string_list = ip_string.split(',')
for ip in ip_string_list:
ip = ip.strip()
if is_valid_ipv4(ip):
ip_list.append(ip)
else:
raise ValueError('传入的(用于屏蔽的)IP地址(段)格式存在问题。')
if ip_list:
return ip_list
return ip_list
def check_ip_in_white_network(ip, black_networks):
ip_obj = ipaddress.ip_address(ip)
for n in black_networks:
if ip_obj in n:
return True
return False
def get_white_ip_network(ip_string):
ip_list = get_white_ip_range(ip_string)
white_networks = [ipaddress.ip_network(ip) for ip in ip_list]
return white_networks
def get_ip_ports(content) -> dict:
"""
获取:url, code, length, title
:param content:
:return: dict{ ip: [port1, port2, ...] }
"""
ip_port_dict = {}
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)', line)
if match:
ip, port = match.group(1), match.group(2)
if ip not in ip_port_dict:
ip_port_dict[ip] = []
if port in ip_port_dict[ip]:
continue
ip_port_dict[ip].append(port)
return ip_port_dict
def get_url_info(content) -> dict:
"""
获取:url, code, length, title, finger
:param content:
:return: dict{ url: [title, code, length, finger] }
"""
url_info_dict = {}
text = '[INFO] 对可能存在【多次访问但数据不一致】的情况' \
' (title, 响应码, 数据长度, 指纹),将会在下方打印输出,以供比较...'
message = color_text(text, Fore.YELLOW)
print(message)
for line in content:
match = re.search(
r'WebTitle: (http.*?)\s+code:(\d+)\s+len:(.*?)\s+title:(.*?)\n',
line)
if match:
url, code, length, title = \
match.group(1), match.group(2), \
match.group(3), match.group(4)
finger = ''
if url not in url_info_dict:
url_info_dict[url] = [title, code, length, finger]
continue
if [title, code, length, finger] == url_info_dict[url]:
continue
# 通过比较,去除title出现的异常空格
count1 = url_info_dict[url][0].count(' ')
count2 = title.count(' ')
if count1 > count2:
url_info_dict[url][0] = title
if [code, length, finger] == url_info_dict[url][1:] \
and abs(len(title) - len(url_info_dict[url][0])) < 2:
continue
if [title, code, length] == url_info_dict[url][:-1] \
and len(finger) <= len(url_info_dict[url][-1]):
continue
print(url, url_info_dict[url], '|', [title, code, length, finger])
else:
match = re.search(r'InfoScan:\s*(http.*?)\s+\[(.*?)]', line)
if match:
url, finger = match.group(1), match.group(2)
if url in url_info_dict:
url_info_dict[url][-1] = finger
return url_info_dict
def get_ip_exp(content):
"""
获取:IP、对应的主机系统漏洞
:param content:
:return: list[[ip, exp]]
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(
r'\[\+]\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(.*)',
line.strip('\n '))
if match:
ip, exp = match.group(1), match.group(2)
exp = re.sub(r'\s+', ' ', exp)
ip_exp_dict = {'IP': ip, '主机漏洞': exp}
if ip_exp_dict in data:
continue
data.append(ip_exp_dict)
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
data = sorted(data, key=lambda _: _['主机漏洞'])
return data
def get_url_poc(content):
"""
获取:url、对应poc
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'(http.*?) (poc-.*)', line)
if match:
url, poc = match.group(1), match.group(2)
url_poc_dict = {'URL': url, 'POC': poc.strip()}
if url_poc_dict in data:
continue
data.append(url_poc_dict)
return data
def format_url_dict(url: str, url_info: list):
if url == '':
return {
'url': '',
'title': '',
'code': '',
'length': '',
'finger': ''
}
title, code, length, finger = url_info
url_dict = {
'url': url,
'title': title,
'code': int(code),
'length': int(length),
'finger': finger
}
return url_dict
def search_url(ip, port, url_info_dict):
if str(port) == '80':
pattern = re.compile(fr"^http://{ip}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
elif str(port) == '443':
pattern = re.compile(fr"^https://{ip}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
pattern = re.compile(fr"^https?://{ip}:{port}[\D]*$")
for url in url_info_dict.keys():
if pattern.match(url):
return format_url_dict(url, url_info_dict[url])
return format_url_dict('', [])
def get_weak_auth(content):
"""
获取:弱口令、无认证等信息
(ip, port, service, username, password, more_info)
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
if 'handshake failed' in line:
continue
info_dict = {'IP': '', '端口': '', '服务': '',
'用户名': '', '密码': '', '附加信息': ''}
# mysql, mssql...
match = re.search(
r'(\S+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+):(\S+)\s+(\S+)',
line)
if match:
service, ip, port, username, password = \
match.group(1), match.group(2), match.group(3), \
match.group(4), match.group(5)
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = username
info_dict['密码'] = password
info_dict['附加信息'] = ''
data.append(info_dict)
continue
# ftp...
match = re.search(
r'(\S+)://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+):(\S+)(.*)',
line
)
if match:
service, ip, port, username, password = \
match.group(1), match.group(2), match.group(3), \
match.group(4), match.group(5)
if len(password) >= 1:
password = password.strip()
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = username
info_dict['密码'] = password
info_dict['附加信息'] = ''
data.append(info_dict)
continue
# redis...
match = re.search(
r'(\S+)[:\s](\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)\s+(.*)',
line
)
if match:
service, ip, port, more_info = \
match.group(1), match.group(2), \
match.group(3), match.group(4)
info_dict['IP'] = ip
info_dict['端口'] = int(port)
info_dict['服务'] = service
info_dict['用户名'] = ''
info_dict['密码'] = ''
info_dict['附加信息'] = more_info
data.append(info_dict)
continue
data = [i for index, i in enumerate(data) if i not in data[:index]]
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
data = sorted(data, key=lambda _: int(_['端口']))
return data
def clean_content(content, black_networks, white_networks):
if len(black_networks) == 0 and len(white_networks) == 0:
return content
new_content = []
if len(white_networks) == 0:
for line in content:
if '[*] LiveTop ' in line:
continue
ip = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\D', line)
if ip:
ip = ip.group(1)
if check_ip_in_black_network(ip, black_networks):
continue
new_content.append(line)
return new_content
else:
for line in content:
if '[*] LiveTop ' in line:
continue
ip = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\D', line)
if ip:
ip = ip.group(1)
if not check_ip_in_white_network(ip, white_networks):
continue
if check_ip_in_black_network(ip, black_networks):
continue
new_content.append(line)
return new_content
def get_domain_os(content):
"""
获取:各域内大体的关系
"""
data = []
# 使用正则表达式匹配IP和端口信息
for line in content:
match = re.search(r'NetBios: (.*?)\s+(.*?\S)\s+(.*)', line)
if match:
ip, domain, OS = match.group(1), match.group(2), match.group(3)
if len(OS) > 1 and not OS[0].isalnum():
OS = OS[1:]
info_dict = {'IP': ip, '域名': domain, '操作系统': OS.strip()}
data.append(info_dict)
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
return data
def get_collated_info(input_filename='./result.txt',
output_filename='内网资产探测信息表.xlsx',
black_networks=(),
white_networks=()):
"""
获取各类信息,并合并成一个表格文档。
IP 端口 协议 服务 URL Title 指纹 返回码
[IP, port, protocol(默认tcp), server(无法识别:""), url, title, finger, code]
:return: list[[IP, port, protocol, server, url, title, finger, code]]
"""
content = readfile(input_filename)
content = list(dict.fromkeys(content))
content = clean_content(content, black_networks, white_networks)
# 常规资产信息
ip_ports = get_ip_ports(content)
url_info_dict = get_url_info(content)
domain_os_list = get_domain_os(content)
# 可能性高的脆弱资产
weak_auth_list = get_weak_auth(content)
ip_exp_list = get_ip_exp(content)
url_poc_list = get_url_poc(content)
data = []
ip_data = []
# IP排序(小 --> 大)
ip_ports = sorted(ip_ports.items(),
key=lambda _: ipaddress.ip_address(_[0]))
for ip, ports in ip_ports:
# 端口排序(小 --> 大)
ports.sort(key=int)
ip_data.append({'IP': ip, '端口': ' '.join(ports)})
for p in ports:
d_row = {'IP': '', '端口': '', '协议': 'tcp', '服务': '',
'URL': '', 'Title': '', '指纹': '', '返回码': ''}
url_info = search_url(ip, p, url_info_dict)
d_row['IP'] = ip
d_row['端口'] = int(p)
d_row['协议'] = 'tcp'
d_row['URL'] = url_info['url']
d_row['Title'] = url_info['title']
d_row['指纹'] = url_info['finger']
d_row['返回码'] = url_info['code']
data.append(d_row)
data = sorted(data, key=lambda _: int(_['端口']))
data = sorted(data, key=lambda _: ipaddress.ip_address(_['IP']))
df_web = pd.DataFrame(data)
df_domain = pd.DataFrame(domain_os_list)
df_ip = pd.DataFrame(ip_data)
df_weak = pd.DataFrame(weak_auth_list)
df_poc = pd.DataFrame(url_poc_list)
df_exp = pd.DataFrame(ip_exp_list)
name, suffix = os.path.splitext(output_filename)
weak_filename = name + '_脆弱资产' + suffix
with pd.ExcelWriter(output_filename) as writer:
df_web.to_excel(writer, sheet_name='资产信息', index=False)
df_domain.to_excel(writer, sheet_name='域信息', index=False)
df_ip.to_excel(writer, sheet_name='存活IP', index=False)
with pd.ExcelWriter(name + '_脆弱资产' + suffix) as writer:
df_weak.to_excel(writer, sheet_name='脆弱认证', index=False)
df_poc.to_excel(writer, sheet_name='POC', index=False)
df_exp.to_excel(writer, sheet_name='主机漏洞', index=False)
text = f'[INFO] 已导出:{os.path.realpath(output_filename)}'
message = color_text(text, Fore.GREEN)
print(message)
text = f'[INFO] 已导出:{os.path.realpath(weak_filename)}'
message = color_text(text, Fore.GREEN)
print(message + '\n')
def main():
parser = argparse.ArgumentParser()
parser.description = "本脚本用于整合fscan探测结果,生成excel表格文档。" \
"(仅作合法用途)"
parser.usage = "\n用法举例:\n" \
"\t简单使用:python fscan_result_merge_toExcel.py " \
"-i ./result.txt -o ./内网资产信息.xlsx\n" \
"\t屏蔽特定IP:python fscan_result_merge_toExcel.py " \
"-i ./result.txt " \
"-n \"10.217.35.158,10.23.32.0/24\"\n" \
"\t只整合特定IP:python fscan_result_merge_toExcel.py " \
"-i ./result.txt -a \"192.168.0.0/16\""
parser.add_argument('-i', '--input', default='./result.txt',
metavar="filename",
help='fscan生成的探测结果 文本文件(.txt)')
parser.add_argument('-o', '--output', default='./内网资产信息.xlsx',
metavar="filename",
help='整合信息后所生成的表格文档 保存路径(含文件名)')
parser.add_argument('-n', '--neglect', default='',
metavar='IP地址(段)',
help='传入需要排除的IP地址(段),'
'如: -n 192.168.1.1,192.168.23.0/24')
parser.add_argument('-nf', '--neglect_file', default='',
metavar='filename',
help='包含需要排除的IP地址(段)的可读文件,'
'其中每个IP地址(段)独占一行')
parser.add_argument('-a', '--allow', default='',
metavar='IP地址',
help='限制程序只处理 该参数所传入的IP地址(段)相关信息。')
args = parser.parse_args()
if not os.path.isfile(args.input):
print("[ERROR] 输入的fscan结果文件路径不存在,请检查。")
return
if ' ' in args.neglect:
print("[ERROR] 输入的(用于屏蔽的)IP地址(段)参数格式不合法:不能含有空格。")
white_networks = get_white_ip_network(
ip_string=args.allow
)
black_networks = get_black_ip_network(
ip_string=args.neglect, ip_file=args.neglect_file
)
text = "[INFO] 程序开始运行..."
message = color_text(text, Fore.GREEN)
print(message)
get_collated_info(input_filename=args.input,
output_filename=args.output,
black_networks=black_networks,
white_networks=white_networks)
if __name__ == '__main__':
main()