인트라넷 침투를 수행하고 수많은 호스트와 서비스를 만날 때마다 나는 항상 자동화된 방법을 사용하여 nmap 스캔 결과에서 정보를 추출합니다. 이를 통해 웹 서비스의 경로 폭발, SSL/TLS 서비스에서 사용되는 키 또는 프로토콜 테스트, 기타 대상 테스트와 같은 다양한 유형의 서비스를 자동으로 감지할 수 있습니다.
저도 침투 테스트에 IPthon이나 *nix Shell을 자주 사용하는데, 이것들은 Python을 통해 접근할 수 있고, 스크립트에서 직접 사용하든, REPL 환경에서 사용하든, 코드를 작성하는데 매우 유용합니다. 디스크에 저장한 다음 셸 명령을 통해 액세스합니다.
구성
nmap 스캔 결과를 구문 분석하는 첫 번째 단계는 nmap 스캔을 수행하는 것입니다. 여기서는 너무 자세히 설명하지는 않겠지만, 이 기사의 코드를 직접 사용하려면 스캔 구조를 xml 파일(-oX 또는 -oA)에 저장하고 다음에서 서비스 감지를 수행해야 합니다. 포트를 열고(-sV) 관련 스크립트를 실행합니다(-sC).
이 문서의 명령은 IPython과 같은 Python REPL 환경에서 실행 중이고 libnmap 모듈이 설치되어 있다고 가정합니다(easy_install 또는 pip를 사용하여 설치할 수 있음).
시작하기 전에 먼저 해당 환경을 설정해야 합니다. 먼저 NmapParser 모듈을 가져오고 xml 스캔 결과 파일을 읽어옵니다. (예제의 이름은 현재 작업 디렉터리에 있는 "up_hosts_all_ports_fullscan.xml"입니다.)
from libnmap.parser import NmapParser nmap_report = NmapParser.parse_fromfile('up_hosts_all_ports_fullscan.xml')
이 글의 나머지 부분에는 다양한 유용한 정보를 한 줄로 추출한 일련의 코드가 포함되어 있습니다. 모든 예제에서는 위와 같이 nmap 스캔 결과가 파일에 저장된다고 가정합니다. 다음은 몇 가지 기본 샘플 코드를 제공합니다. IPython에서 직접 실행하려면 위 코드를 먼저 실행하여 콘솔에 직접 출력되도록 하세요. 나는 일반적으로 출력 데이터가 예상한 대로인지 확인하기 위해 이 작업을 먼저 수행합니다.
그런 다음 변수 이름을 선택하고 "="를 사용하여 변수에 데이터를 할당하면 후속 코드에서 직접 호출하거나 셸 명령에서 사용할 수 있도록 디스크에 쓸 수 있습니다. 여러 번 사용하고 싶은 것이 있으면 Python 스크립트에 일부 코드 조각을 붙여넣거나 좀 더 복잡한 로직을 추가하고 싶지만 이로 인해 REPL 환경이 처리하기 어려울 수 있습니다. 이를 빠르게 실행하는 방법을 설명하겠습니다. 마지막 섹션에서 작동합니다.
포트 정보
지정된 포트 번호가 열려 있는 호스트
지정된 포트 번호가 열려 있는 모든 호스트를 표시합니다. 호스트 주소(문자열)가 포함된 목록을 생성합니다. 다음은 포트 443을 예로 들어 필요한 값으로 수정할 수 있습니다.
[ a.address for a in nmap_report.hosts if (a.get_open_ports()) and 443 in [b[0] for b in a.get_open_ports()] ]
열린 포트 수
일련의 호스트에 대해 열린 포트 수를 표시합니다. 포트 수(int)가 포함된 목록을 생성하고 정렬합니다.
sorted(set([ b[0] for a in nmap_report.hosts for b in a.get_open_ports()]), key=int)
호스트의 열려 있는 포트에 해당하는 서비스를 포트 번호별로 그룹화
모든 호스트가 열려 있는 포트 번호를 포트 번호별로 그룹화하여 표시합니다. 여러 목록을 포함하는 목록(즉, 목록의 각 요소도 목록임)을 생성합니다. 여기서 각 구성원 목록의 첫 번째 요소는 포트 번호(int)이고 두 번째 요소는 열려 있는 호스트 IP 주소입니다. 해당 포트(문자열) 목록입니다.
[ [a, [ b.address for b in nmap_report.hosts for c in b.get_open_ports() if a==c[0] ] ] for a in sorted(set([ b[0] for a in nmap_report.hosts for b in a.get_open_ports()]),key=int) ] SSL/TLS 和 HTTP/HTTPS
SSL을 사용하는 호스트 및 포트
SSL을 사용하는 모든 호스트와 포트를 표시합니다. 이는 "SSL" 채널을 사용하는 서비스가 있는지 또는 관련 스크립트가 결과에서 pem 인증서를 감지하는지 확인하여 수행됩니다. 일련의 목록을 포함하는 목록을 생성합니다. 각 구성원 목록에는 호스트 주소(문자열)와 포트 번호(int)가 포함됩니다.
[ [a.address, b.port] for a in nmap_report.hosts for b in a.services if b.tunnel=='ssl' or "'pem'" in str(b.scripts_results) ]
다음 내용은 위와 동일한 내용을 담고 있으나 목록의 목록 대신 조인 기능을 사용하여 "호스트:포트 번호"(문자열)가 포함된 목록을 생성합니다.
[ ':'.join([a.address, str(b.port)]) for a in nmap_report.hosts for b in a.services if b.tunnel=='ssl' or "'pem'" in str(b.scripts_results) ]
웹 서비스의 호스트와 포트를 포함합니다.
모든 웹 서비스와 해당 포트 번호 및 프로토콜(http 또는 https)을 표시합니다. 이렇게 하면 각 구성원 목록에 프로토콜(문자열), 주소(문자열) 및 포트 번호(int)가 포함된 여러 목록이 포함된 목록이 생성됩니다. 하지만 여기에는 몇 가지 문제가 있습니다. nmap이 https를 사용하는 웹사이트를 보고할 때 서비스가 "https"로 표시되는 경우도 있고, "ssl" 채널을 사용하여 "http"로 표시되는 경우도 있으므로 데이터 형식을 조정했습니다. 균일한 출력을 위해.
[ [(b.service + b.tunnel).replace('sl',''), a.address, b.port] for a in nmap_report.hosts for b in a.services if b.open() and b.service.startswith('http') ]
동일한 정보가 여기에 있지만 프로토콜, 호스트 및 포트 번호의 원래 목록에 URL(문자열)이 추가됩니다.
[ (b.service + b.tunnel).replace('sl','') + '://' + a.address + ':' + str(b.port) + '/' for a in nmap_report.hosts for b in a.services if b.open() and b.service.startswith('http') ]
기타 서비스 정보
알 수 없는 서비스
nmap에서 인식하지 못하는 서비스를 모두 보여줍니다. 각 구성원 목록에는 주소(문자열), 포트 번호(int) 및 nmap에서 검색한 포트 지문(문자열)이 포함된 여러 목록이 포함된 목록을 생성합니다. 이 정보는 주로 해당 특정 서비스에 대한 후속 수동 검토를 용이하게 하기 위해 생성되며 자동화된 프로세스에는 참여하지 않습니다.
[ [ a.address, b.port, b.servicefp ] for a in nmap_report.hosts for b in a.services if (b.service =='unknown' or b.servicefp) and b.port in [c[0] for c in a.get_open_ports()] ]
Nmap 식별 소프트웨어
Nmap 스캔으로 식별된 모든 소프트웨어를 표시합니다. 제품의 알파벳순 목록을 생성합니다.
sorted(set([ b.banner for a in nmap_report.hosts for b in a.services if 'product' in b.banner]))
软件对应的主机和端口号,按产品分组
显示扫描出软件对应的主机和端口,按产品分组。生成一个包含多个列表的列表,其中每个成员列表的第一个元素为软件的名称(string),随后是另一个列表包含地址(string)和端口号(int)。
[ [ a, [ [b.address, c.port] for b in nmap_report.hosts for c in b.services if c.banner==a] ] for a in sorted(set([ b.banner for a in nmap_report.hosts for b in a.services if 'product' in b.banner])) ]
同上相同的信息,只是输出略有不同。同样还是生成一个包含多个列表的列表,成员列表的第一个元素还是软件的名称(string),但第二个是一个包含 “主机:端口号” 的列表。
[ [ a, [ ':'.join([b.address, str(c.port)]) for b in nmap_report.hosts for c in b.services if c.banner==a] ] for a in sorted(set([ b.banner for a in nmap_report.hosts for b in a.services if 'product' in b.banner])) ]
搜索指定关键词相关的主机和端口
显示所有与给定关键词相关联的主机和端口,从 nmap 扫描结果的原始文本中查找包含产品名称、服务名称等等。下面以 “Oracle” 为例。生成一个包含多个列表的列表,其中每个成员列表包含主机地址(string)和端口号(int)。
[ [a.address, b.port] for a in nmap_report.hosts for b in a.services if b.open() and 'Oracle' in str(b.get_dict()) + str(b.scripts_results) ]
同上一样的方法,只是将存储的信息修改后一律使用小写进行搜索(下面示例为小写的 “oracle”),输出格式还是跟上面一样。
[ [a.address, b.port] for a in nmap_report.hosts for b in a.services if b.open() and 'oracle' in (str(b.get_dict()) + str(b.scripts_results)).lower() ]
其他的事情
相同的证书名称
显示找到的 SSL 证书和使用 nmap 脚本解析后得到证书名称相同的部分。这样在当你从一个 IP 地址开始扫描且反向 DNS 失效的时候,可以帮助确定系统的主机名。生成一个包含多个列表的列表,其中每个成员列表包含 IP 地址(string)和提取出的主机名(string)。
[ [a.address, c['elements']['subject']['commonName'] ] for a in nmap_report.hosts for b in a.services for c in b.scripts_results if c.has_key('elements') and c['elements'].has_key('subject') ]
处理以上结果的方法
正向前面所说,上述的例子,当你直接粘贴进 IPython REPL 时只是将输出打印在屏幕上。这的确不错,因为这样你可以随时查看到自己感兴趣的信息,但你可能还会想做更多的事情。之所以去生成上述信息,一大好处就在于你可以根据结果轻松执行一些自动化的操作。
如果你已经很熟悉 Python,应当可以很容易完成这些工作,那么你可以跳过这一节。但如果你不熟悉,那么本节会讲述一些很基本的知识,告诉你如何使用上述的代码段。
保存到磁盘
如果你想将上述代码段的输出结果保存到磁盘上的文本文件中,你需要将输出的列表转换为适当的字符串格式(具体取决于你的需求),然后在将这个字符串写入文件。在 Python 中,你可以使用 join 函数来整合这些列表并将其写入文件,这里只是一个示例。
我们想要从生成的列表中提取出支持 SSL 的主机和端口,并将它们保存到一个新的文件中,这样可以在 bash 中使用循环来完成并使用命令行工具来进行测试。
我通常会在 IPython 中使用一行代码来完成这些,虽然一行代码会比较方便,但这里为了方便阅读和理解,我会将代码拆分出来说。
让我们来解析之前生成了一个包含 “主机:端口” 的列表,请注意我们使用了 str 函数将端口号从整数类型装换为了字符类型,这样使得它也能够使用 join 函数与其他字符串拼接在一起。
[ ':'.join([a.address, str(b.port)]) for a in nmap_report.hosts for b in a.services if b.tunnel=='ssl' or "'pem'" in str(b.scripts_results) ]
让我们来给上面这段代码的结果分配名为 “ssl_services” 变量,以方便后续的调用。
ssl_services = [ ':'.join([a.address, str(b.port)]) for a in nmap_report.hosts for b in a.services if b.tunnel=='ssl' or "'pem'" in str(b.scripts_results) ]
现在,让我们来使用 join 函数将列表的每一个元素拼接起来并使用 (‘\n') 进行换行,然后给它分配一个名为 “ssl_services_text” 的变量。
ssl_services_text = '\n'.join(ssl_services)
随后,我们就可以在当前工作目录下创建一个名为 “ssl_services_file.txt” 的新文建,并将 “ssl_services_text” 变量的内容写入其中。
open('ssl_services_file.txt','w').write(ssl_services_text)
就这么简单,后续你可以根据自己的需要来使用文件内容了。
使用其他 Python 代码
也许你还会想用其他的 Python 代码来完成上述工作?同样很简单,下面就是另一个示例,这里我们遍历每一个 nmap 识别出的 web 服务及其网页的请求结果。
下面会生成一个包含 URLs 的列表,我们分配一个名为 “urls” 的变量给它。
urls = [ (b.service + b.tunnel).replace('sl','') + '://' + a.address + ':' + str(b.port) + '/' for a in nmap_report.hosts for b in a.services if b.open() and b.service.startswith('http') ]
下一步,我们先进行一些准备工作,导入 requests 模块,然后设置一个简单的 getAndSave 函数进行 web 请求并将返回结果保存到磁盘上,文件名按 url 自动生成。你可能会注意到下面代码中,在 get 请求中使用了 “verify=False” 选项,这会在发送请求时忽略证书验证的错误,这个选项经常在测试内部机器时使用,因为内部机器基本不会有可信的证书颁发机构颁发的 SSL 证书。
import requests def getAndSave(url): r = requests.get(url, verify=False) open('_'.join(url.split('/')[2:]).replace(':',''),'wb').write(r.text.encode('utf8'))
现在,让我们增加一些代码来遍历每一个 url,请求每个站点的 robots.txt 文件,并将其保存到本地以供后续使用。
for a in urls: getAndSave(a + 'robots.txt')
这样就会将每一个站点的 robots.txt 文件爬取到当前工作目录下。这只是一个很简单的例子。
总结
希望你在阅读完本文后,可以自己灵活的使用 Python 解析 nmap 扫描结果。
更多巧用python和libnmapd,提取Nmap扫描结果相关文章请关注PHP中文网!