VxScan源码分析

项目信息

Author:al0ne

GitHub:传送门

目录结构

.
├── LICENSE Apache开源许可证2.0
├── README.md 英文文档
├── README.zh-CN.md 中文文档
├── Vxscan.py 主程序
├── analyzer.py
├── data
│ ├── GeoLite2-ASN.mmdb geoip数据库
│ ├── apps.json 应用指纹
│ ├── apps.txt 应用指纹
│ ├── password.txt
│ └── path
├── doc
│ ├── logo.jpg
│ ├── logo1.jpg
│ └── logo2.jpg
├── lib
│ ├── Requests.py 自封装请求对象
│ ├── bcolors.py 字体颜色
│ ├── cli_output.py 客户端信息输出
│ ├── common.py 公共函数
│ ├── iscdn.py 判断CDN
│ ├── options.py 获取命令行参数任务调度
│ ├── random_header.py 随机UA
│ ├── settings.py 全局配置
│ ├── sqldb.py
│ ├── url.py 解析URL及主机
│ ├── verify.py
│ ├── vuln.py
│ ├── waf.py WAF匹配
│ ├── web_info.py
│ └── whiteip.py 白名单IP
├── plugins
│ ├── ActiveReconnaissance
│ ├── InformationGathering
│ ├── PassiveReconnaissance
│ └── Scanning
├── report
│ ├── bootstrap.min.css
│ ├── en.js
│ ├── fonts
│ ├── index.css
│ ├── index.js
│ ├── report.htm
│ └── vue.min.js
├── report.py
├── requirements.txt
└── script
├── Weblogic_CVE_2017_10271_RCE.py
├── apache_struts_all.py
├── django_urljump.py
├── docker_unauthorized_access.py
├── find_admin.py
├── fingerprint.py
├── ftp_anonymous.py
├── get_title.py
├── http_put.py
├── jboss_jmx_console.py
├── leaks.py
├── memcached_unauthorized_access.py
├── mongodb_unauthorized_access.py
├── phpinfo.py
├── pulse_cve_2019_11510.py
├── redis_unauthorized_access.py
├── rsync_unauthorized_access.py
├── solr_rce_via_velocity.py
├── solr_unauthorized_access.py
├── thinkphp5_rce_1.py
├── thinkphp_5_0_23_rce.py
├── zabbix_jsrpc_sqli.py
└── zookeeper_unauthorized_access.py

项目路径:/Tools/pentest/Vxscan

源码分析

本次使用Pycharm分析此项目, 进入程序主文件Vxscan.py, 主程序的13~18行判断当前目录是否存在error.log日志文件并删除。如果Python大版本小于3或小版本小于6则不予运行程序使用sys.exit函数退出执行。

image-20201229204101268

配置日志文件名和等级, 只记录ERROR等级的日志。使用try/except执行banner和options函数。

image-20210103170153797

均位于Vxscan/lib目录下。跟进函数的执行。

/Vxscan/lib/cli_output.py

使用pyfiglet模块的figlet_format函数获取Vxscan字符串的ASCII图形字符。而后输出时加上了颜色

image-20201229205604298

/Vxscan/lib/options.py

使用argparse模块获取命令行参数, 如运行程序时未指定保存文件名则为result 。如果指定了-i参数则IP扫描调用inet函数。

image-20210103170953213

这里用的是url扫描, 调用start_out函数传入url, Vxscan/lib/cli_output.py

标准输出开始信息

image-20201229211908772

回到主程序继续向下运行, 初始化ActiveCheck类后调用pool方法:

1
2
if ActiveCheck([args.url]).pool():
start(args.url, dbname)

pool方法使用concurrent.futures模块的ThreadPoolExecutor函数获取一个线程池并设置最大值为20, 而后通过字典推导式提交任务给线程池执行, 返回的结果赋值给result变量。循环as_completed方法获取任务的返回结果。

image-20210103171222603

跟进提交到线程池执行的方法代码块。check方法代码篇幅过长, 故此出只总结一下该函数的用途:

  • 解析URL获取Host
  • verify_country方法验证国家(默认关闭)
  • Host为IP时调用WhiteIP类checkip方法, 使用二分查找检查是否为境外IP
  • 根据操作系统判断IP存活:Windows使用Ping命令, 其余则使用nmap模块扫描

执行完pool函数后继续回到lib/options.py的第六十行代码, 调用lib/common.pyd的start函数, 传入了url与数据库文件名。

image-20210101172629172

start函数调用lib/url.py的parse_ip函数通过dns.resolver模块获取A记录的解析IP。再调用lib/verify.py的verify_https函数验证url是否为http或https。并跟随302获取跳转后的地址

image-20210101173208758

如果url可访问则isopen为true, 调用lib/web_info.py的web_info函数获取服务器信息, 应用, 标题

image-20210101182729619

web_info函数代码如下, 下面再跟进函数分析其是如何实现的

image-20210102124810178

首先是geoip函数, 用于获取IP地址的真实物理位置, plugins/InformationGathering/geoip.py的geoip函数。其内部是通过geoip2模块的免费数据库获取IP的中文地理位置

image-20210102125238925

位于plugins/ActiveReconnaissance/check_waf.py的checkwaf函数:

这块代码比较简单, 就是通过发送Payload请求的Response Header的信息到规则库去匹配是否命中。

image-20210102125530484

WAF规则:

image-20210102125626483

回到获取页面信息的地方, plugins/PassiveReconnaissance/wappalyzer.py的WebPage类;

__init__方法:

image-20210102154956031

Wappalyzer类的初始化方法获取data/apps.json的数据转为字典, 将字典中apps键的value赋值给self.apps属性, 然后循环调用_prepare_app方法:

image-20210102155930888

_prepare_app方法用作循环传递的app变量, 其为一个字典, 通过循环判断是否存在某个key, 若存在但不为列表/字典则重新定义为列表/字典, 对不存在的key赋空列表/字典, 并通过_prepare_pattern方法获取用于正则匹配的pattern对象。

image-20210102163437152

_prepare_pattern方法:

image-20210102163552787

wappalyzer对象的初始化工作到此结束, 回到self.apps = wappalyzer.analyze()

image-20210102195505192

回到lib/common.py的start函数33行向下执行, 判断其是否CDN进行端口扫描, 跟进plugins/Scanning/port_scan.py分析如何实现端口扫描:

image-20210103002655313

socket_scan方法使用socket套接字进行TCP握手连接, 如开放端口大于30个则不予继续扫描。针对开放端口使用regex方法进行正则服务识别。

image-20210103004430977

端口扫描完毕后继续回到start函数进行POC扫描与目录扫描(爬虫项默认False 暂跳过):

image-20210103005611056

跟进Vuln类的run方法:

image-20210103163430144

vuln方法取出脚本的check函数调用后追加到self.out列表。

image-20210103163923581

目录扫描作者用得到技术栈是uvloop + asyncio + aiohttp **实现的异步扫描, 但个人认为Python的目录扫描dirsearch已经做得很好了。并且笔者目前只接触过asyncio + aiohttp**, 还未使用过uvloop。这部分内容也许会等到我有机会接触dirsearch的源码分析再做吧。(当然短时间内是不可能了。)


最后gener函数从数据库取出扫描结果后生成HTML报告:

image-20210103164834790