先看看巡风总体的架构css
aider 这个目录是辅助验证的html
db 是数据库前端
dockerconf docker的配置web
docs 里面保存是windows Linux docker环境下的使用文档ajax
install 安装 这个是.sh ,那应该就是Linux的安装时才使用到的文件夹了mongodb
masscan 目录 里面存放的是根据不一样操做系统选择不一样的masscan的版本docker
nascan 这个目录存放的是资产探测相关的东西数据库
venv 虚拟环境,这个是根据requirements.txt建立的虚拟安装flask
views 巡风的视图存放windows
vulscan 目录存放的是漏洞探测(其中包括打poc)
再看views这个目录,看这目录中的源码以前,必定必定要把巡风玩熟练了,再看,要否则很容易懵逼。玩的时候注意视图函数的跳转,有的地方是不跳转的。
这个目录比较复杂,因此我就单独拿出来写了。若是作过flask框架,或者作事后端,比较容易理解,由于前端真的是很扎心。。。。
lib能够说是一个动态库(我百度搜的。。。)
lib 里面的 ___init__.py是作初始化的工做,就是在引用这个目录的文件以前,先执行这个__init___
AntiCSRF巡风的csrf防护,巡风对于csrf防护的时候用的是加referer的形式(csrf防护有两种机制,一种是加token,一种是加referer,相对来讲仍是加token更安全一点,毕竟referer是能够伪造的)
这里附上巡风的csrf防护的referer源码,判断referer的依据是,首先在一个request里面要有referer,再有这个referer的格式化必需要跟服务器内的相等
from functools import wraps from flask import url_for, redirect, request # 检查referer def anticsrf(f): @wraps(f) def wrapper(*args, **kwargs): try: if request.referrer and request.referrer.replace('http://', '').split('/')[0] == request.host: return f(*args, **kwargs) else: return redirect(url_for('NotFound')) except Exception, e: print e return redirect(url_for('Error')) return wrapper
ps:巡风自己是一个内网探测的工具,但是这个工具还须要加上csrf防护?我感受这就不必了吧。。毕竟是内网,csrf的用处不就是加权限?难道我作内网测试的时候,还有人打我?(嘻嘻)
Conn.py 是链接mongdb的数据的文件,大体知道下就好了
from pymongo import MongoClient # 数据库链接 class MongoDB(object): def __init__(self, host='localhost', port=27017, database='xunfeng', username='', password=''): self.host = host self.port = port self.database = database self.conn = MongoClient(self.host, self.port) self.coll = self.conn[self.database] self.coll.authenticate(username, password)
Create_Excel.py
这个是建立excel的脚本,这个脚本自我感受仍是比较有意思的,当我点击这个下载的时候就会执行这个脚本里面的某个函数,一会详细分析
源码:
# -*- coding: UTF-8 -*- import xlwt import StringIO # 将数据保存成excel def write_data(data, tname): file = xlwt.Workbook(encoding='utf-8') table = file.add_sheet(tname, cell_overwrite_ok=True) l = 0 for line in data: c = 0 for _ in line: table.write(l, c, line[c]) c += 1 l += 1 sio = StringIO.StringIO() file.save(sio) return sio # excel业务逻辑处理 def CreateTable(cursor, id): item = [] item.append(['IP', '端口', '主机名', '风险等级', '漏洞描述', '插件类型', '任务名称', '时间', '扫描批次']) for i in cursor: if i['lastscan']: _ = [i['ip'], i['port'], i['hostname'], i['vul_level'], i['info'], i['vul_name'], i['title'], i['time'].strftime('%Y-%m-%d %H:%M:%S'), i['lastscan'].strftime('%Y-%m-%d %H:%M:%S')] else: _ = [i['ip'], i['port'], i['hostname'], i['vul_level'], i['info'], i['vul_name'], i['title'], i['time'].strftime('%Y-%m-%d %H:%M:%S'), ''] item.append(_) file = write_data(item, id) return file.getvalue()
这个里面最重要的是导入了一个xlwt这个库,这个库在写文件的时候是整行整行的写
StringIO与file对象很是像,但它不是磁盘上文件,而是一个内存里的“文件”,咱们能够像操做磁盘文件那样来操做StringIO。
def write_data()就是一个保存和写一个excel的操做,create_table是excel里面写的操做。
Login.py 这个用的是一个装饰器,就是限制有没有session的,不过巡风的这个比较有意思,一会看到视图函数的时候,再去说
def logincheck(f): @wraps(f) def wrapper(*args, **kwargs): try: if session.has_key('login'): if session['login'] == 'loginsuccess': return f(*args, **kwargs) else: return redirect(url_for('Login')) else: return redirect(url_for('Login')) except Exception, e: print e return redirect(url_for('Error'))
QueryLogic,就是在首页点搜索的时候,要对数据进行处理,由于这个我以前写过一个搜索的方法,因此看着仍是比较容易的。
import re def mgo_text_split(query_text): ''' split text to support mongodb $text match on a phrase ''' sep = r'[`\-=~!@#$%^&*()_+\[\]{};\'\\:"|<,./<>?]' word_lst = re.split(sep, query_text) text_query = ' '.join('\"{}\"'.format(w) for w in word_lst) return text_query # 搜索逻辑 def querylogic(list): query = {} if len(list) > 1 or len(list[0].split(':')) > 1: for _ in list: if _.find(':') > -1: q_key, q_value = _.split(':', 1) if q_key == 'port': query['port'] = int(q_value) elif q_key == 'banner': zhPattern = re.compile(u'[\u4e00-\u9fa5]+') contents = q_value match = zhPattern.search(contents) # 若是没有中文用全文索引 if match: query['banner'] = {"$regex": q_value, '$options': 'i'} else: text_query = mgo_text_split(q_value) query['$text'] = {'$search': text_query, '$caseSensitive':True} elif q_key == 'ip': query['ip'] = {"$regex": q_value} elif q_key == 'server': query['server'] = q_value.lower() elif q_key == 'title': query['webinfo.title'] = {"$regex": q_value, '$options': 'i'} elif q_key == 'tag': query['webinfo.tag'] = q_value.lower() elif q_key == 'hostname': query['hostname'] = {"$regex": q_value, '$options': 'i'} elif q_key == 'all': filter_lst = [] for i in ('ip', 'banner', 'port', 'time', 'webinfo.tag', 'webinfo.title', 'server', 'hostname'): filter_lst.append({i: {"$regex": q_value, '$options': 'i'}}) query['$or'] = filter_lst else: query[q_key] = q_value else: filter_lst = [] for i in ('ip', 'banner', 'port', 'time', 'webinfo.tag', 'webinfo.title', 'server', 'hostname'): filter_lst.append({i: {"$regex": list[0], '$options': 'i'}}) query['$or'] = filter_lst return query
简单说下,若是用户输入的是port:22,那么巡风如才知道的呢??分析下端口是22。这个要先根据 :分片,这个巡风在进行检索的时候才会是知道port 22的全部结果。
views.static 目录,这个目录就是把图片,css,js前端相关的一些东西除了html都封装到这个目录里面了,这个是flask定义好的。
views.templates这个目录里面封装的是视图函数用到的全部html。不过我可纳闷,为何html 、css、js这些东西非要分开放两个文件夹呢?不直接放到一个目录下,这个有空再去搜索看吧(主要仍是暂时没搜索到)。
view.views 这个文件就是存放巡风全部的视图了。视图能够理解为就是一个url连接。
导入库就不说了,没啥东西,直奔主题,从头开始说。
巡风没有注册(ps:就是我的扫描内网用的,还须要啥注册,直接登陆)
登陆视图的源码
@app.route('/login', methods=['get', 'post']) def Login(): if request.method == 'GET': return render_template('login.html') else: account = request.form.get('account') password = request.form.get('password') if account == app.config.get('ACCOUNT') and password == app.config.get('PASSWORD'): session['login'] = 'loginsuccess' return redirect(url_for('Search')) else: return redirect(url_for('Login'))
分析下,获取account,password而后判断一下是否跟config里面保存的ACCOUNT、PASSWORD同样
这里导入下config.py
# coding:utf-8 class Config(object): ACCOUNT = 'admin' PASSWORD = '123456' class ProductionConfig(Config): DB = '127.0.0.1' PORT = 65521 DBUSERNAME = 'scan' DBPASSWORD = 'scanlol66' DBNAME = 'xunfeng'
这里巡风仍是比较巧的,由于是我的使用,因此在数据库里面没有建立一张用户表。直接跟config里面的验证一下,而后session添加一个常量就能够了。若是验证正确就重定向到Search,若是错误就重定向到Login里面。
filter就是直接渲染一个search.html
@app.route('/filter') @logincheck def Search(): return render_template('search.html')
search.html里面中,除了这个搜索,其余都是经过a标签的方式进行重定向,就只有搜索功能是经过ajax,发送消息(这个暂时没有找到,等我找到了,就补充上)
假如搜索的是port : 22,那么结果就应该是
对应的就是这个视图
@app.route('/') @logincheck def Main(): q = request.args.get('q', '') # 这里获取里两个参数,经过这样获取到搜索的条件进行查询 page = int(request.args.get('page', '1')) plugin = Mongo.coll['Plugin'].find() # 插件列表 plugin_type = plugin.distinct('type') # 插件类型列表 if q: # 基于搜索条件显示结果 result = q.strip().split(';') query = querylogic(result) cursor = Mongo.coll['Info'].find(query).sort('time', -1).limit(page_size).skip((page - 1) * page_size) return render_template('main.html', item=cursor, plugin=plugin, itemcount=cursor.count(), plugin_type=plugin_type, query=q) else: # 自定义,无任何结果,用户手工添加 return render_template('main.html', item=[], plugin=plugin, itemcount=0, plugin_type=plugin_type)
q是获取搜索的标签,其中这个request.args.get()这个是能够好好研究研究的
分析web界面实在是太长了,就有空再写回来吧,基本上都是经过ajax方式给后端发送数据。好好找都是可以找到的,若是是作后端的话,感受看不懂ajax也不要紧,由于ajax是前端写的(嘻嘻)。
有空必定要学习下用markdown是怎么上传到博客园的,博客园写小的博客记录还行,大的话,滚动条拉的蓝瘦