
相信有不少同学跟我们一样使用AWVS(Acunetix Web Vulnerability Scanner)来进行web业务上线前的渗透测试和已上线站点的安全扫描,不知大家在使用这个工具的时候是否遇到过需要用命令行来实现对多个疑似URL进行批量自动扫描的问题?今天我们就来分享一下我们使用这个神器在结合python实现自动扫描的一些经验以及结合OSSEC Web扫描日志进行有安全隐患URL复查的思路。
1.1 先决条件
AWVS本身自带定时任务功能(Scheduler),启动服务后可以在网页配置需要扫描的站点、时间等等,不过之前测试的时候发现在图形界面里面添加任务和结果查看比较麻烦,而且只能看内置的一些规则进行扫描,并不非常智能,所以并没有采用这个天然的方案。而AWVS提供了命令行工具wvs_console.exe,给了喜欢“脚本改变世界”的我们一个可以通过命令行操纵AWVS的机会。
wvs_console扫描时会将过程存为CSV,我们可以通过分析CSV的内容来判断本次扫描是否存在漏洞,然后决定是否发告警给安全巡检人员。调用系统命令、分析CSV内容、发送告警这样的需求在windows系统进行,显然Python是一个合适的工具。
那么我们需要用于发起扫描的设备是一个安装了AWVS(9.5)和Python(2.7)的Windows(win7)机器。
2.1 参数解析
wvs_console有很多配置选项,可以执行wvs_console查看,更具体的说明则在官网(http://www.acunetix.com/blog/docs/acunetix-wvs-cli-operation/)查阅。
下面介绍下几个必要的参数:
/Scan:扫描一个站点
语法:/Scan [链接]
示例:/Scan http://www.abc.com
/profile :使用指定的配置来进行扫描
语法:/profile [配置名]
示例:/profile default
/SaveFolder :指定扫描结果和其他相关文件存放的目录
语法:/SaveFolder [目录名]
示例:/SaveFolder D:\Scans
完成我们自动化扫描的小目标需要的参数可以只用以上3个。那么转换为命令则是
C:\Program Files (x86)\Acunetix\Web Vulnerability Scanner 9.5>wvs_console.exe Scan http://www.abc.com profile Default /SaveFolder D:\Scans
PS: 如果你不清楚 profile 选什么,那么选用default即可;当然你也可以根据需要选择Sql_Injection,XSS等配置或者自定义的配置来进行针对性的扫描。
2.2 结果分析
使用这个命令完成扫描后,我们可以在C:\Scans目录下看到2个文件:
scan-results.wvs:扫描后生成的结果,跟直接用AWVS扫描保存的结果文件一致
wvs_log_2017xxx.csv:执行命令时生成的扫描日志,记录了扫描情况,以及是否存在漏洞,譬如[high] Cross site scripting 或 [medium] Slow HTTP Denial of Service Attack "Web Server"
实际业务中,我们只关心AWVS定义的“高危”、“中危”这2种漏洞或安全隐患,那么我们可以分析csv文件是否含有相应的关键词。如果有,那么就把对应的信息取出来作为正文内容的一部分,把scan-results.wvs作为附件,发告警邮件给指定的安全巡检人员,安全巡检人员通过告警邮件来验证、通报跟进处理;如果没有则认为本次扫描没有异常,可以发个扫描完成的邮件进行闭环提示。
通过以上环节,我们已经能够调用AWVS来进行扫描并判断扫描结果了,那么如何在此基础上进行自动扫描和结合OSSEC web扫描日志进行安全隐患核查呢?
如果我们知道怎么自动化复核web扫描,那么自动化扫描自然也就知道如何进行了,所以此处我们直接讲如何进行web扫描复核。
我们在OSSEC服务器定义一个rootcheck规则来检查web日志,规则里面定义SQL注入、XSS、Webshell相关的一些关键词,并把规则推送到各个OSSEC客户端上。然后每天凌晨在OSSEC服务器找出前一天匹配到web扫描日志,按约定的格式拼接成URL,存到某个地方,AWVS扫描机再想办法拿到这些需要扫描的URL进行扫描。如果某URL确实存在漏洞,则发送告警给安全巡检人员,并走相关流程进行修复,避免由于已存在的漏洞被利用从而导致线上业务受影响。
下面我们介绍上述扫描过程的自动化方案。
3.1 OSSEC自定义规则
用过OSSEC的同学应该知道,OSSEC可以自定义rootcheck规则来检查系统文件的安全情况(如/var/log/message 、/etc/ssh/sshd_config等)。那么如果我们定义规则用来检查web访问日志呢?
比如我们定义如下规则
<rootcheck> <frequency>86400</frequency> <system_audit>/var/ossec/etc/system_audit_rcl.txt</system_audit>
</rootcheck>
部分配置示例如下
[root@ossecsvr ]# cat var/ossec/etc/system_audit_rcl.txt
#SQL_Inject$web_logs=/logs; [ webscan1 ] [any] [] d:$web_logs -> .com.log$ -> r:information_schema && r:\s200\s; d:$web_logs -> .cn.log$ -> r:information_schema && r:\s200\s;
#XSS[ webscan2 ] [any] [] d:$web_logs -> .com.log$ -> r:alert\( && r:\s200\s; d:$web_logs -> .cn.log$ -> r:alert\( && r:\s200\s; …
匹配到web日志中含有SQL注入、XSS、Webshell相关的一些比如script、alert、select等关键词作为告警规则,OSSEC自动上报到服务端,那么我们就可以通过分析OSSEC每天的告警日志来取出web扫描的部分并按约定的格式拼接成那种非常可疑的URL,存到某个地方,AWVS扫描机再想办法拿到这些需要扫描的URL进行扫描。如果某URL确实存在漏洞,则发送告警给安全巡检人员,并走相关流程进行修复,避免由于已存在的漏洞被利用从而导致线上业务受影响。
下面我们介绍上述扫描过程的自动化方案。
3.2 框架图

3.3 扫描流程
OSSEC服务器每天凌晨定时分析前一天的web扫描日志,按照约定的格式拼接出URL列表,而办公网安全机(win7)则每天早晨拉取待复核的URL进行预处理后丢到队列里,然后调用wvs_console对URL逐一进行扫描,出现告警则把扫描结果通过邮件发送给安全巡检人员进行通知;未发现异常则发汇总邮件进行闭环,确保当天的复核是确实进行过的。
3.4 需要注意的细节
复检环节中最难的是URL的生成,URL的生成依赖于nginx日志内容格式的统一(如字段顺序、分隔符设置)和日志文件命名符合一定规范(如 logs/${DOMAIN}.log),只有按照约定的规则,才能得到真实的URL,从而更好地扫描复核。
为了让AWVS复检不进入死循环(即昨天复检的扫描又生成了今天需要扫描的URL),可以排除掉扫描机的IP,或者进行一些特殊的头部设定,当ELK服务器生成URL列表时,匹配到这些特殊的标记则不把这部分URL生成到列表里。
由于复检的时候业务属于对外服务期,我们总不能为了绩效凭个优秀而拿业务开玩笑是吧?需要尽可能不重复地只扫描需要检查的URL,一方面是URL去重,另一方面是加上--GetFirstOnly参数只扫描这个链接,还有就是同一时间的并发数控制,最简单的是遍历顺序执行,这样就不用考虑并发导致的问题,编码也容易实现。
从以上流程来看,扫描需要用到的代码其实很少,下面列出几个关键的步骤给些参考代码。
4.1 扫描结果入库
def load_data(save_path): ''' 将扫描结果导入SQLite3通过SQL来找自己要的信息,其实生成的CSV是纯文本文件,我们也可以分析文本就好 ''' save_path = r'%s'%save_path save_db = r'%s\wvs_svae.db'%save_path save_csv = r'%s\wvs_log.tmpcsv'%save_path
for result in os.listdir(save_path):
if result.endswith('.csv'): _result = r'%s\%s'%(save_path,result) _result_rename = _result + '.bak' lines = open(_result).readlines()
# 删除格式不对的第2行 del(lines[1]) open(save_csv,'w').writelines(lines)
# 删除此次结果 os.rename(_result, _result_rename) conn = sqlite3.connect(save_db) cursor = conn.cursor() cursor.executescript(''' DROP TABLE IF EXISTS "wvs_test"; CREATE TABLE "wvs_test" ( "TimeStamp" varchar(20) NOT NULL DEFAULT '', " Source" varchar(20) NOT NULL DEFAULT '', "LogType" varchar(20) NOT NULL DEFAULT '', "Description" TEXT(5000) NOT NULL DEFAULT '');''' )
# CLI 版本 #cursor.execute('.separator ","') #cursor.execute('.import %s wvs_test'%save_db) #cursor.execute('select Description from wvs_test where
Description like "%[high]%";') with open(save_csv,'rb') as csv_fin: dr = csv.DictReader(csv_fin) to_import = []
for i in dr: f1 = i["TimeStamp"] if i["TimeStamp"] else "" f2 = i[" Source"] if i[" Source"] else "" f3 = i["LogType"] if i["LogType"] else "" f4 = i["Description"] if i["Description"] else "" to_import.append((f1, f2, f3, f4)) cursor.executemany('INSERT INTO wvs_test VALUES (?,?,?,?);',to_import) conn.commit() conn.close()
4.2 分析入库结果
def analyse_result(save_path,logtype): ''' 读出指定日志类型的信息 ''' save_path = r'%s'%save_path save_db = r'%s\wvs_svae.db'%save_path
##print save_db conn = sqlite3.connect(save_db) cursor = conn.cursor() query_sql = r'select Description from wvs_test where Description like "%[{0}]%";'.format(logtype) cursor.execute(query_sql) result = cursor.fetchall() conn.close()
return result
4.3 扫描并判断
def scan(url): ''' 扫描函数,返回1代表发现异常,返回0代表未发现异常 ''' domain = get_domain(url) root_url = 'http://%s/'%domain
if root_url == url : #不扫描主域名 return save_path = '%s\%s' % (save_time_path,domain) command = '%s\wvs_console.exe Scan %s profile Default SaveFolder %s SavetoDatabase' % (awvs_bin_path, url, save_path) result = call(command) result_flie = '%s\scan-results.wvs' % save_path
# 将结果导入sqlite load_data(save_path)
# 根据官方文档由返回值入手 ## http://www.acunetix.com/blog/docs/acunetix-wvs-cli-operation/ if result == 3: _alert_info_high = analyse_result(save_path,'high') _alert_info_medium = analyse_result(save_path,'medium') alert_info = '<br>' for info in _alert_info_high: alert_info += '%s'%info[0] + '<br>' for info in _alert_info_medium: alert_info += '%s'%info[0] + '<br>' alert_info = alert_info.replace('[high]',r'<font color="red">[high]</b></font>') alert_info = alert_info.replace('[medium]',r'<font color="orange">[medium]</b></font>') logging.warning('发现高危漏洞') msg = '扫描 %s 时,至少发现1个高危告警,概况如下<br>%s<br><br>详情请下载附件用AWVS查看 '% ( url,alert_info.encode('utf-8')) send_mail(contact, '%s AWVS 复检 %s 时发现高危漏洞 !'%(today,domain), msg,result_flie, '%s.%s.wvs' % (domain,time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(time.time()))), )
return 1 elif result == 666: send_mail(contact, 'AWVS HIDS ! %s'%(url), 'The WVS Scanner stopped unexpectedly' )
return 0 elif result == 777: send_mail(contact, 'AWVS HIDS ! %s'%(url), 'Scan cannot start, since the number of licensed instances has been reached' )
return 0 else:
return 0
有同学可能留意到我们用的是AWVS 9.5而不是用新版10.x,并不是因为我们盲目守旧,之前也尝试过更新到10.x,但是测试的时候发现10.x扫描的时候会卡在99%,而我们是顺序执行的,这样就会导致永远执行不完,所以我们又回退到了9.5。
工具本身是比较强大的,重点在于我们怎么去使用Ta,目前我们做到每天获取队列进行自动扫描,发现异常则发告警,扫描完成则发通知。如果我们想更深入使用这个工具来扫描,还可以想办法如何在安全后台添加或终止扫描任务等配置。
整个方案属于“被动式地精确扫描”方式,比如某个web被人xss之后,我们会定时自动取web日志放入AWVS进行精确地扫描,但这样基本不会错过漏洞点,更不会出现以前被人XSS还不知道的情况。
有其他思路的同学也可以通过留言告知,让我们一起探讨交流共同进步。
END
全中国只有不到1% 的人关注了运维军团
你是个有眼光的人!
(由于交流群人数已超100人,需要进群的小伙伴可以添加运维小编的微信:qq834775039)

如果你喜欢我们的文章,请转发到朋友圈

公众号
ywjtshare
运维军团
专注运维技术与传承,分享丰富原创干货




