0x00 前言
周五快下班的时候,想着终于结束了一周挖坑种树的活,可以休息两天了。没想到,隔壁工地一朋友滴滴我,让我帮忙在一个脚本输出结果上增加两个字段,于是就有了本文。
注:学习别人的code编写思路,也是一种成长。
0x01 接收脚本
文件地址:[连接(原始和完善后均在)](链接: https://pan.baidu.com/s/1Xe496gG8fX2sPDp1CkO8MQ 提取码: p82b 复制这段内容后打开百度网盘手机App,操作更方便哦)
接收到脚本后,看到这是一个 nsfocus rsas V6版本的一个漏洞报告转存工具,也就是把HTML页面的漏洞转存为Excel形式。
简单的看了一下第三方库,核心库主要是 BeautifulSoup 和 openpyxl ,有点小蒙,毕竟近期都是试着用 pandas 来处理Excel,网页主要用 re 正则来匹配调试。
推荐一下这个正则工具,经常用,挺不错的。
下载地址:[百度云连接](链接: https://pan.baidu.com/s/1PPCJqJzA5IJ7cCsBISZQbQ 提取码: g8g6 复制这段内容后打开百度网盘手机App,操作更方便哦)
0x02 了解需求
需求其实在前面也提到了,在输出结果上增加两个字段,说起来简单,做起来,有点难啊,还是菜啊。。。
效果前:
效果后:
0x03 着手开始
当接收到一个陌生的工具代码时,快速的了解方式,莫过于从入口点开始回溯。再此之前,先整体看下这份代码。
1 2 3 4 5 from bs4 import BeautifulSoupimport reimport osfrom openpyxl import Workbookimport sys
可以看到使用了5个三方库,核心的为 bs4 和 openpyxl 。整体一共编写了6个函数模块,分别是:
1 2 3 4 5 6 1. getrisk() 2. gettype() 3. genfilelist() 4. extract_index() 5. extract_detail() 6. extract_info()
工具使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 目录架构: ├─测试文件夹 │ ├─v6.py │ ├─host │ │ ├─192.168 .3 .123 .html │ │ ├─192.168 .3 .125 .html │ │ └─192.168 .3 .125 .html └─ ─ ─ ─ ─ ─ ─ CMD下运行: C:\Users\shiyan>python3 v6.py ./
0x04 程序入口点
翻到code最下面,可以看到如下:
1 2 3 if __name__ == '__main__' : path = sys.argv[1 ] extract_info(path)
由上可知,那首先就先从核心函数模块开始看吧。
1 2 3 4 5 6 7 8 9 xls = u'漏洞汇总.xlsx' xlswb = Workbook() xlswb.save(filename=xls) xlsws = xlswb.active xlsws.append(['IP' , u'设备类型' , u'风险等级' , u'CVE编号' , u'漏洞名称' , u'漏洞描述' , u'整改意见' , u'端口' , u'服务' , u'协议' ]) volume = path + 'host/' filelist = genfilelist(volume)
可以看出,这几行主要是创建一个Excel文档,并且设置相应的字段名,这里,我直接在后面添加上“服务”、“协议”两个需新增的字段名。
1 2 3 4 5 6 7 8 9 10 11 12 for filename in filelist: file = volume + filename try : html = open (file, 'rb' ) except : continue soup = BeautifulSoup(html, 'lxml' , from_encoding="utf8" ) ip_node = soup.select('div#content div.report_content table tr td table.report_table.plumb tbody tr.even td' ) ip = ip_node[0 ].get_text() vul_node = soup.select('div.vul_summary' )
这几行比较好理解,获取路径中的html报告,并且加载进 bs4 创建的内存中。
第十二行,直接利用第十行的 CSS 选择器,选中存在 IP 的部分,然后筛选 IP 值,放入 ip 变量中。
第十三行,是通过选择器把所有 div 瞄点的 class 值为 vul_summary 放入 vul_node 变量中,这个便于下面循环中,取相应所需的值。
查看 html 源码中,发现 div.vul_summary 主要为漏洞名称、开放端口,和一些其它标签值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 for node in vul_node: attr = node.attrs port = attr["data-port" ] vulname_node = node.find('span' ) level = vulname_node['class' ][0 ] vul_info_index = vulname_node['onclick' ] vulname = vulname_node.get_text() vul_index = extract_index(vul_info_index) allvul = extract_detail(soup, index=vul_index) vultype = gettype(vulname) vulrisk = getrisk(level) if vulname == "Portable OpenSSH 'ssh-keysign'本地未授权访问漏洞" : vulcve = 'CVE-2011-4327' elif vulname == "Portable OpenSSH 'ssh-keysign'本地未授权访问漏洞" : vulcve = 'CVE-2011-4327' else : vulcve = allvul['cve' ] print (ip, vulname) list = [ip, vultype, vulrisk, vulcve, vulname, allvul['desc' ], allvul['advice' ], port, service, Agreement] if vulrisk == '低' : pass else : xlsws.append(list )
通过遍历 div.vul_summary 区域的部分,最终获取到了主机类型、风险等级、CVE编号、漏洞名称、漏洞描述、整改意见、开放端口。
但是,在输出结果时,把漏洞等级为低的,全部过滤了,不进行输出。
第二十一行,list 为存放漏洞所有信息的列表,并且写入Excel中,我们需要在这里加上 “服务”、“协议” 两个变量名,这样再最后就会写入到Excel中。
0x05 分析两个字段
既然前后输出的字段列表中,我们已经提前写上了字段名,那就开始构造相应的变量吧。
首先查看报告,可以看出,需求的两个字段,均在 2.1 漏洞概况中,其它地方均无可用的。
鼠标右键源码查看:
通过查看该部分,发现了一个问题,服务、协议,两个标签出,均没有属性,导致无法直接通过属性值来获取。
突然蒙蔽中,因为是用的 bs4 ,本身并没有用过,不是很熟悉,故此开始各种翻文档学习中。。。
一开始思考着,直接使用正则匹配 <td class="vul_port">--</td>
和 <div class="vul_summary"
之间的部分,然后利用正则直接匹配所需值。并且我还调试了半天,调试出来的正则:
1 <td class ="vul_port" >(\S+)</td>\s+<td>(.*?)</td>\s+<td>(.*?)</td>\s+<td>\s+<ul>\s+<li>\s+<div class ="vul_summary'#" \s+data-id '="{}"' .format ('50394' )
通过这个正则,可以看到,我还想着利用 data-id 这个值来对应相对应的漏洞,结果出现问题了。
没错!data-id 的值并不是对应着一个开放端口和协议的,而是汇总到一起的。
此时,我陷入沉思中。。。
0x06 柳暗花明
与其没思路,不如求助大佬,求助大佬中。。。。
按照上述的方法,再继续看HTML源码,发现了一个特点:
1、整个漏洞概况储存在 id 为的 vuln_list 的 table 中,且该id值是唯一的。
2、核心部分位于 tbody 标签中。
3、tr 的 class 值为 even 为灰色,class 值为 odd 为白色。
至此,一开始我想的是写一个函数,来获取服务和协议的值,现在为何不在原始的 for 循环外,嵌套两个循环来解决该问题。
1 2 3 4 5 6 vul_node = soup.select('div.vul_summary' )for node in vul_node: attr = node.attrs port = attr["data-port" ]
原始循环只是在 div.vul_summary 中循环,而 tr.even 和 tr.odd 都是包含 div.vul_summary 的,故此可以在原始循环外面嵌套几个大的循环。
为了验证这个想法是可行的,简单的写了一个dome来测试,看看。
dome code 就不贴了,太丑了。
至此,成功的获取到了 服务 和 协议 两个想要的值。
获取两个字段值的最终 code 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Vulnerability_List = soup.find_all('table' , id ='vuln_list' ) for Vulnerability_area in Vulnerability_List: v_area_tbody = Vulnerability_area.tbody va_tbody_tr = v_area_tbody.select('tr' ) for tr_even_odd in va_tbody_tr: atters_list = tr_even_odd.contents serve = str (atters_list[3 ]) zz1 = '<td>(.*?)</td>' serve1 = re.findall(zz1, serve) service = serve1[0 ] treaty = str (atters_list[5 ]) treaty1 = re.findall(zz1, treaty) Agreement = treaty1[0 ]
0x07 扩展学习①
刚刚把我的这部分写完,并且测试也没啥问题时,上文中的大佬也完成了他写的,我看了下,和我的方法不太一致,主要是按照他一开始说的那个思路写的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 x = 0 vul_service_sum = [] for vul_node1 in soup.find(id ="vuln_list" ).findAll("tr" ): x+=1 vul_service = [] if x >= 2 : num = len (vul_node1.findAll('span' )) for i in vul_node1: if isinstance (i, bs4.element.Tag): vul_service.append(str (i.string)) for z in range (num): vul_service_sum.append(vul_service) print (vul_service_sum) vul_node = soup.select('div.vul_summary' ) y = 0 for node in vul_node: attr = node.attrs port = attr["data-port" ] vulname_node = node.find('span' ) level = vulname_node['class' ][0 ] vul_info_index = vulname_node['onclick' ] vulname = vulname_node.get_text() vul_index = extract_index(vul_info_index) allvul = extract_detail(soup, index=vul_index) vultype = gettype(vulname) vulrisk = getrisk(level) if vulname == "Portable OpenSSH 'ssh-keysign'本地未授权访问漏洞" : vulcve = 'CVE-2011-4327' elif vulname == "Portable OpenSSH 'ssh-keysign'本地未授权访问漏洞" : vulcve = 'CVE-2011-4327' else : vulcve = allvul['cve' ] list = [ip, vultype, vulrisk, vulcve, vulname, allvul['desc' ], allvul['advice' ], port,vul_service_sum[y][2 ],vul_service_sum[y][1 ]] y +=1 if vulrisk == '低' : pass else : xlsws.append(list )
思路流程如下:
1、获取 table.vuln_list 中所有被 tr 包围的部分。
2、通过span标签来识别漏洞数量
3、对获取的 tr 包围的部分,如果数据类型为 bs4.element.Tag ,储存到 vul_service 列表中,该列表储存基本均是端口号、服务、协议。
4、通过获取的漏洞数,来重复放置识别出的值。
分割线上方为 vul_service 列表的值,下方为 vul_service_sum 的值。
5、 最后通过 node 每轮遍历的 y 值,来对应使用哪个服务和协议。
6、不好理解的话,可以拿code进行debug一下,能加深理解。
0x08 扩展学习②
把上述两个方法整合了一下,感觉下面的这个更简单。
1 2 3 4 5 6 7 8 9 10 Vulnerability_List = soup.find(id ="vuln_list" ).findAll("tr" ) for tr_even_odd in Vulnerability_List: vul_service=[] for x in tr_even_odd: if isinstance (x, bs4.element.Tag): vul_service.append(str (x.string)) service = vul_service[2 ] Agreement = vul_service[1 ]
0x09 后续
因标题只是针对新增的两个字段的,故没有对剩下的函数模块,进行分析。
其它函数,也是很不错的,比如 gettype() 函数模块,通过匹配漏洞名称里的关键词,来确认主机类型,因为报告中,确实没有显示该主机类型。