w9scan是一款全能型的网站漏洞扫描器,借鉴了各位前辈的优秀代码。内置1200+插件可对网站进行一次规模的检测,功能包括但不限于web指纹检测、端口指纹检测、网站结构分析、各种流行的漏洞检测、爬虫以及SQL注入检测、XSS检测等等,w9scan会自动生成精美HTML格式结果报告
工作流程
扫描器首先加载服务类型为www的插件,www插件中集合了信息搜集用到的各种脚本,对目标网站进行信息搜集,获取服务类型,端口,CMS等信息,加载对应的插件,进行漏洞扫描。
信息搜集
子域名扫描
w9scan中的子域名扫描实现
w9scan中子域名扫描以爆破的形式进行,利用已有的子域名字典进行爆破,若访问成功则子域名存在。但是子域名扫描时有可能会出现子域名泛解析的问题,这种情况下任何子域名均可访问到所指向的WEB地址。w9scan中首先设定了一个不可能存在的子域名进行访问,若访问成功则存在泛解析。
unable_pro = "sbsbsbsbforWebScannerunablepro"
hostnames = unable_pro + "." + domain
hostnames = hostnames.strip()
try:
l = socket.gethostbyname_ex(hostnames)
except socket.error:
l = 0
if l != 0:
security_info("域名存在泛解析 %s" % ("*." + domain), 'subdomain')
return
for pro in tlds:
hostnames = pro + "." + domain
hostnames = hostnames.strip()
try:
l = socket.gethostbyname_ex(hostnames)
security_info(str(l),'subdomain')
time.sleep(0.01)
except socket.error:
pass
泛解析绕过
我们可以利用泛解析时不存在的域名返回的页面是一样的这一原理来判断是否存在泛解析。首先访问一个不存在的子域名,对返回的html页面进行MD5加密获取泛解析特征。然后再进行子域名爆破,若返回html的MD5值与特征相同,则该子域名不存在。
unable_pro = “sbsbsbsbforw9scanunablepro”
hostnames = unable_pro + “.” + domain
hostnames = hostnames.strip()
try:
l = socket.gethostbyname_ex(hostnames)
except socket.error:
l = 0
if l != 0:
security_info(“域名存在泛解析 %s” % (“*.” + domain), ‘subdomain’)
m = get_html_md5(hostnames)
for pro in tlds:
hostnames = pro + “.” + domain
hostnames = hostnames.strip()
try:
if l==0:
security_info(str(l),'subdomain')
time.sleep(0.01)
else:
m1 =get_html_md5(hostnames)
if m==m1:
security_info(str(l), 'subdomain')
time.sleep(0.01)
except socket.error:
pass
cms指纹识别
Cms识别的原理为收集各种cms的指纹特征,如特殊文件的MD5值,请求头与响应头的特殊关键字等特征,将目标网站与指纹库的指纹进行对比,判断网站cms类别。全面的cms指纹库在cms识别过程中起重要作用。
插件加载
插件格式
该插件模板借鉴了bugscan的设计,将脚本分为assign,audit,main三部分,assign用于验证任务指纹,aduit用于审计漏洞,main用于本地测试。若指纹符合则执行aduit函数检测漏洞是否存在。
加载过程
插件调用时系统会new一个模块出来,用exec函数执行插件代码并将执行的字节码加载进新建的模块,可以通过调用模块中的方法使用插件。首先用assign方法验证插件类型,若插件的任务指纹符合需求,则将该插件的扫描函数作为新的任务加入线程池等待执行。
基于爬虫的漏洞扫描
对于网页的常见漏洞等漏洞的发现是借助爬虫扫描实现的,爬虫采取了深度优先遍历的思想,即对目标url进行访问,对返回的报文进行分析获取相关链接,将爬到的新链接存入new_url列表,并同时对获取的url进行漏洞扫描,这些漏洞扫描脚本集中在spider_file模块中。Spider_file模块中包括许多常见的漏洞扫描脚本,如xss漏洞扫描,sql注入扫描,文件包含漏洞,文件上传漏洞的扫描。
def craw(self):
self.urls.add_new_url(self.root)
while self.urls.has_new_url() and self.maxdeep>self.deep and self.maxdeep > 0:
new_url = self.urls.get_new_url()
logger.debug("craw:" + new_url)
try:
html = until.w9_get(new_url)
check(new_url,html)
except:
html = ''
new_urls = self._parse(new_url, html)
self.urls.add_new_urls(new_urls)
self.deep = self.deep + 1
def check(url,html = ''):
for k, v in w9_hash_pycode.iteritems():
try:
pluginObj = v["pluginObj"]
service = v["service"]
if(service == "spider_file"):
pluginObj.audit(url,html)
except Exception as errinfo:
logger.error("spider plugin:%s errinfo:%s url:%s"%(k,errinfo,url))
线程调度
该扫描器利用了python的threading,queue,sleep等包实现了线程池调度,每次执行扫描任务时初始化线程池,设定线程工作函数及同时并行的最大线程值。遍历插件库,若插件符合要求则将其加入队列。然后使用编写的run方法,利用threading模块并行执行扫描线程,若执行线程数小于最大并行数,则等待。
# coding:utf-8
# 模拟一个进城池 线程池,可以向里面添加任务,
import threading
import time
import traceback
from lib.core.data import logger
import Queue
import random
class w8_threadpool:
def __init__(self,threadnum,func_scan,Isjoin = False):
self.thread_count = self.thread_nums = threadnum
self.scan_count_lock = threading.Lock()
self.thread_count_lock = threading.Lock()
self.load_lock = threading.Lock()
self.scan_count = 0
self.isContinue = True
self.func_scan = func_scan
self.queue = Queue.Queue()
self.isjoin = Isjoin
def push(self,payload):
self.queue.put(payload)
def changeScanCount(self,num):
self.scan_count_lock.acquire()
self.scan_count += num
self.scan_count_lock.release()
def changeThreadCount(self,num):
self.thread_count_lock.acquire()
self.thread_count += num
self.thread_count_lock.release()
def run(self):
th = []
for i in range(self.thread_nums):
t = threading.Thread(target=self.scan)
t.setDaemon(True)
t.start()
th.append(t)
# It can quit with Ctrl-C
if self.isjoin:
for tt in th:
tt.join()
else:
while 1:
if self.thread_count > 0 and self.isContinue:
time.sleep(0.01)
else:
break
def stop(self):
self.load_lock.acquire()
self.isContinue = False
self.load_lock.release()
def scan(self):
while 1:
self.load_lock.acquire()
if self.queue.qsize() > 0 and self.isContinue:
payload = self.queue.get()
self.load_lock.release()
else:
self.load_lock.release()
break
try:
# POC在执行时报错如果不被处理,线程框架会停止并退出
self.func_scan(payload)
time.sleep(0.3)
except KeyboardInterrupt:
self.isContinue = False
raise KeyboardInterrupt
except Exception:
errmsg = traceback.format_exc()
self.isContinue = False
logger.error(errmsg)
# self.changeScanCount(-1)
self.changeThreadCount(-1)
if __name__ == '__main__':
def calucator(num):
i = random.randint(1, 100)
u = num
a = i * u
if (a % 6 == 0):
for x in range(5):
print "new thread"
# p.push(x)
p = w8_threadpool(3, calucator)
for i in range(100000):
p.push(i)
p.run()