├── README.md ├── lib ├── SqlScan.py ├── SsrfScan.py ├── XssScan.py ├── __init__.py ├── config.py ├── customizeScan.py ├── headers.py ├── log.py ├── tryReqest.py └── white.py ├── log └── log.txt ├── nginxlog.py ├── pic └── 111.png ├── requirements.txt ├── run.py └── tasks.py /README.md: -------------------------------------------------------------------------------- 1 | # PassiveSecCheck 0.1 2 | 3 | 自动化被动扫描系统共分为数据源、数据处理、任务分发、漏洞验证四个字系统,本系统属于任务发布、漏洞验证部分,读取数据源信息,进行分布式安全验证,确定是否包含相关严重漏洞。 4 | 5 | ## Author ## 6 | 7 | 咚咚呛 8 | 9 | 如有其他建议,可联系微信280495355 10 | 11 | ## Support ## 12 | 13 | 满足如下安全需求 14 | 15 | 1、对提供的http协议的五个元素进行安全测试(协议、方法、host、接口、参数) 16 | 2、可提供host和session对照表,用于登录后测试 17 | 3、提供自定义规则扫描,用于测试越权漏洞和常见漏洞等 18 | 19 | 技术细节如下: 20 | 21 | 1、接受数据源为json格式,以含五元素为主 22 | 2、分布式采用Celery + Redis结构 23 | 3、提供SQL注入、SSRF、XSS等检测, 24 | 4、SSRF的检测,原理是把相关参数替换为指定的链接信息,然后读取此web日志,确定漏洞 25 | 5、SQL注入使用sqlmapapi的接口验证漏洞 26 | 6、提供参数替换接口、防止参数中出现session等值 27 | 7、提供host和session对照接口,可事实进行测试替换 28 | 8、提供白名单接口,可无条件放过测试 29 | 9、提供自定义规则接口,进行自定义漏洞检测,主要用于越权和常见漏洞检测 30 | 10、扫描结果以KV数据存储到Redis中 31 | 11、各个模块的配置信息和规则均存储在redis中,可实时修改。 32 | 33 | ## Test Environment ## 34 | 35 | >Windows 7 旗舰版 / centos 7 36 | > 37 | >python 2.7.5 38 | 39 | ## Tree ## 40 | 41 | PublicSecScan 42 | ----lib #模块库文件 43 | ----log #日志目录 44 | ----tasks.py #分布式调度任务 45 | ----run.py #任务分发主程序,用于测试 46 | ----nginxlog.py #SSRF漏洞验证,用于读取nginx访问日志 47 | 48 | ## Deploy ## 49 | 50 | 部署分为三块,一个任务分发、一个扫描任务执行Worker、一个是ssrf漏洞验证Worker 51 | 因为各个模块用的配置为一个配置文件,故建议先性配置完毕./lib/config.py后,在部署各个模块。 52 | 53 | 1、任意机子安装redis,用于分布式漏洞验证消息队列 54 | $ yum install redis 55 | $ vim /etc/redis.conf 56 | # 更改bind 127.0.0.1 改成了 bind 0.0.0.0 57 | # 添加一行requirepass xxxxx密码 58 | # 修改daemonize yes 59 | $ redis-server /etc/redis.conf 60 | 61 | 2、任务发布 62 | $ pip install -r requirements.txt 63 | # 配置./lib/config.py 文件,填入数据源redis信息和漏洞验证消息队列redis 64 | # 配置定时任务cron.d,按照自身的要求,定时扫一遍所有接口的安全隐患 65 | $ python run.py 可手动任务发布一次 66 | 67 | 2、Worker部署(建议以centos为主) 68 | 1) pip install -r requirements.txt 69 | 2) 下载sqlmap,并执行 nohup python sqlmapapi.py -s & 70 | 2) 配置./lib/config.py 文件,填入Redis和其他等相关信息 71 | 3) cmd代码目录执行,-c 1代表多一个worker进程,可增加,执行如下: 72 | celery -A tasks worker -c 1 --loglevel=info -Ofair 73 | 74 | 3、找一台机子部署ssrf验证worker 75 | 1) yum install nginx 76 | 2) 配置/etc/nginx 文件,修改如下:log_format main '$request'; 77 | 3) 启动nginx 78 | 4) 配置./lib/config.py 文件,填入Redis和其他等相关信息 79 | 5) 写文件cron.d定时执行python nginxlog.py 80 | 81 | 82 | 83 | ## Config ## 84 | 85 | 配置目录:./lib/config.py 86 | 87 | # 此处为数据源存储的redis信息 88 | DATA_REDIS_HOST = '127.0.0.1' 89 | DATA_REDIS_PORT = 6379 90 | DATA_REDIS_PASSWORD = '122112121212' 91 | DATA_REDIS_DB = 5 92 | 93 | # 此处为分布式漏洞验证消息队列redis 94 | REDIS_HOST = '182.61.11.11' 95 | REDIS_PORT = 6379 96 | REDIS_PASSWORD = '11112222' 97 | REDIS_DB = 5 98 | 99 | # ------------------------------------------------------------------ 100 | # ------任务发布 可不需要配置下列信息---------------------------------- 101 | # ------------------------------------------------------------------ 102 | 103 | # 配置参数中的替换字符,防止参数中出现session权限判断等,在get或post方法中出现如下参数名称则替换为响应的参数值 104 | conf_parameter_json = { 105 | 'session': '12234234234' 106 | } 107 | # 维持一个sesson访问列表 108 | conf_cookies = [ 109 | {'domain': '*.test.com', 'cookie': 'aaaaa=bbbbb'}, 110 | {'domain': 'www.test.com', 'cookie': 'session=aaaa'}, 111 | {'domain': 'aaa.testbbb.com', 'cookie': 'NL=1234'} 112 | ] 113 | # 白名单路径,路径中出现如/admin不进行任何安全检测 114 | conf_white_path = ['/admin', '/administra'] 115 | # 自定义漏洞匹配规则,遍历每个参数名称,当设定parameter时,值替换为设定的value字串,并在response中匹配正则rule 116 | # 不设定parameter时,代表挨个替换 117 | # 可以测试一些越权操作和一些常见漏洞 118 | # 比如设定参数名为phone,替换其参数值为手机号,并匹配response的body内,是否出现此人的个人信息。 119 | conf_scan_rule = [ 120 | {'value': '17600296111', 'rule': '111111111', 'name': 'Exceed Permissions', 'parameter': 'phone'}, 121 | {'value': '17600296112', 'rule': '22222222', 'name': 'Exceed Permissions', 'parameter': 'iphone'} 122 | ] 123 | # server sqlmap远程ip链接 124 | sqlmap_server = 'http://127.0.0.1:8775/' 125 | # SQL检测最大运行时间(S) 126 | sqlmap_max_time = 600 127 | # db_type数据库检测类型 128 | sqlmap_db_type = '' 129 | # sqlmap扫描等级和风险 130 | sqlmap_level = 1 131 | sqlmap_risk = 1 132 | # ssrf_server远程ip或domain 133 | # ssrf实质是服务端访问了客户端传递的地址 134 | # 预先搭建一台web,收集分析日志可以判断ssrf触发点。 135 | ssrf_server = '10.1.1.3' 136 | # ssrf web日志路径 137 | ssrf_logpath = '/usr/local/openresty/nginx/logs/access.log' 138 | 139 | 140 | ## Worker Screenshot ## 141 | 142 | ![Screenshot](pic/111.png) 143 | -------------------------------------------------------------------------------- /lib/SqlScan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import redis, requests, json, urlparse, urllib, time 3 | from config import * 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class SQL_Scan(): 9 | def __init__(self, target, logger=None): 10 | self.protocol = target['protocol'] 11 | self.ng_request_url_short = target['ng_request_url_short'] 12 | self.domain = target['domain'] 13 | self.method = target['method'].strip().upper() 14 | self.arg = target['arg'] 15 | self.cookie = target['cookie'] 16 | self.ua = target['ua'] 17 | self.logger = logger 18 | 19 | self.redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 20 | self.start_time = time.time() 21 | self.taskid = '' 22 | # server sqlmap远程ip链接 23 | self.server = 'http://127.0.0.1:8775/' 24 | # 当前任务max_time最大运行时间 25 | self.max_time = 300 26 | # db_type数据库检测类型 27 | self.db_type = 'MySQL' 28 | # sqlmap扫描等级和风险 29 | self.level, self.risk = 3, 1 30 | 31 | # 新建扫描任务 32 | def task_new(self): 33 | self.taskid = json.loads(requests.get(self.server + 'task/new').text)['taskid'] 34 | if len(self.taskid) > 0: 35 | return True 36 | if self.logger: self.logger.infostring('create sqlmap task error') 37 | return False 38 | 39 | # 删除扫描任务 40 | def task_delete(self): 41 | if json.loads(requests.get(self.server + 'task/' + self.taskid + '/delete').text)['success']: 42 | return True 43 | return False 44 | 45 | # 扫描任务开始 46 | def scan_start(self): 47 | if self.method == 'GET': 48 | payload = {'url': self.protocol + self.domain + self.ng_request_url_short + '?' + self.arg} 49 | else: 50 | payload = {'url': self.protocol + self.domain + self.ng_request_url_short} 51 | url = self.server + 'scan/' + self.taskid + '/start' 52 | t = json.loads(requests.post(url, data=json.dumps(payload), headers={'Content-Type': 'application/json'}).text) 53 | engineid = t['engineid'] 54 | if len(str(engineid)) > 0 and t['success']: 55 | return True 56 | if self.logger: self.logger.infostring('sqlmap task start error') 57 | return False 58 | 59 | # 扫描任务的状态 60 | def scan_status(self): 61 | self.status = json.loads( 62 | requests.get(self.server + 'scan/' + self.taskid + '/status').text)['status'] 63 | if self.status == 'running': 64 | return 'running' 65 | elif self.status == 'terminated': 66 | return 'terminated' 67 | else: 68 | return 'error' 69 | 70 | # 扫描任务的结果 71 | def scan_data(self): 72 | data = json.loads( 73 | requests.get(self.server + 'scan/' + self.taskid + '/data').text)['data'] 74 | if len(data) > 0: 75 | if self.logger: self.logger.infostring( 76 | 'found sql injection,info: %s ' % (self.domain + self.ng_request_url_short)) 77 | current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) 78 | value = {'method': self.method, 'protocol': self.protocol, 'cookie': self.cookie, 'domain': self.domain, 79 | 'ng_request_url_short': self.ng_request_url_short, 'arg': self.arg, 'time': current_time, 80 | 'risk_type': 'SQL Injection', 'data': data} 81 | 82 | self.redis_r.hset('passive_scan_risk', 'SQL_Injection_' + self.ng_request_url_short, value) 83 | 84 | # 扫描的设置,主要的是参数的设置 85 | def option_set(self): 86 | headers = {'Content-Type': 'application/json'} 87 | data = {'cookie': self.cookie} 88 | if self.db_type: data['dbms'] = self.db_type 89 | if self.risk: data['risk'] = self.risk 90 | if self.level: data['level'] = self.level 91 | data['user-agent'] = self.ua 92 | if self.method == "POST": data["data"] = self.arg 93 | url = self.server + 'option/' + self.taskid + '/set' 94 | requests.post(url, data=json.dumps(data), headers=headers) 95 | 96 | # 停止扫描任务 97 | def scan_stop(self): 98 | requests.get(self.server + 'scan/' + self.taskid + '/stop') 99 | 100 | # 杀死扫描任务进程 101 | def scan_kill(self): 102 | requests.get(self.server + 'scan/' + self.taskid + '/kill') 103 | 104 | # 删除此次任务 105 | def scan_del(self): 106 | requests.get(self.server + 'task/' + self.taskid + '/delete') 107 | 108 | # 判断URL是否进行扫描 109 | def assessment_scan(self): 110 | # 首先判断URL是否存在参数,如果不存在参数则直接放弃扫描 111 | if not self.arg: 112 | return False 113 | return True 114 | 115 | # 配置系统参数 116 | def conf_sys(self): 117 | try: 118 | if self.redis_r.hget('passive_config', 'sqlmap_level'): 119 | self.level = self.redis_r.hget('passive_config', 'sqlmap_level') 120 | if self.redis_r.hget('passive_config', 'sqlmap_risk'): 121 | self.risk = self.redis_r.hget('passive_config', 'sqlmap_risk') 122 | if self.redis_r.hget('passive_config', 'sqlmap_server'): 123 | self.server = self.redis_r.hget('passive_config', 'sqlmap_server') 124 | if self.redis_r.hget('passive_config', 'sqlmap_max_time'): 125 | self.max_time = int(self.redis_r.hget('passive_config', 'sqlmap_max_time')) 126 | if self.redis_r.hget('passive_config', 'sqlmap_db_type'): 127 | self.db_type = self.redis_r.hget('passive_config', 'sqlmap_db_type') 128 | except Exception, e: 129 | if self.logger: self.logger.infostring('read conf info error,error function conf_sys: %s' % str(e)) 130 | 131 | def run(self): 132 | try: 133 | if self.logger: self.logger.infostring('start sql inject scan') 134 | if not self.arg: 135 | if self.logger: self.logger.infostring('the target dont no arg') 136 | if self.logger: self.logger.infostring('finsh sql injection task') 137 | return False 138 | 139 | self.conf_sys() 140 | 141 | if not self.task_new(): 142 | if self.logger: self.logger.infostring('finsh sql injection task') 143 | return False 144 | # 配置扫描选项 145 | self.option_set() 146 | # 开始扫描 147 | if not self.scan_start(): 148 | if self.logger: self.logger.infostring('finsh sql injection task') 149 | return False 150 | # 判断任务状态 151 | while True: 152 | if self.scan_status() == 'running': 153 | time.sleep(10) 154 | elif self.scan_status() == 'terminated': 155 | break 156 | else: 157 | break 158 | if time.time() - self.start_time > self.max_time: 159 | self.scan_stop() 160 | self.task_delete() 161 | self.redis_r.execute_command("QUIT") 162 | if self.logger: self.logger.infostring('sqlmap task exceeded the maximum time limit') 163 | if self.logger: self.logger.infostring('finsh sql injection task') 164 | return 165 | # 获取扫描结果 166 | self.scan_data() 167 | # 删除扫描任务 168 | self.task_delete() 169 | self.redis_r.execute_command("QUIT") 170 | if self.logger: self.logger.infostring('finsh sql injection task') 171 | except Exception, e: 172 | if self.logger: self.logger.infostring('tash run error,error: %s' % str(e)) 173 | if self.logger: self.logger.infostring('finsh sql injection task') 174 | return False 175 | -------------------------------------------------------------------------------- /lib/SsrfScan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import redis, requests, urlparse 3 | from config import * 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class SSRF_Scan(): 9 | def __init__(self, target, logger=None): 10 | self.target = target 11 | self.protocol = target['protocol'] 12 | self.ng_request_url_short = target['ng_request_url_short'] 13 | self.domain = target['domain'] 14 | self.method = target['method'].strip().upper() 15 | self.arg = dict(urlparse.parse_qsl(target['arg'])) 16 | self.cookie = target['cookie'] 17 | self.ua = target['ua'] 18 | self.logger = logger 19 | self.redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 20 | if self.redis_r.hget('passive_config', 'ssrf_server'): 21 | self.server = self.redis_r.hget('passive_config', 'ssrf_server') 22 | 23 | def request(self): 24 | if self.server: 25 | for key in self.arg: 26 | try: 27 | temp_arg = self.arg.copy() 28 | temp_arg[key] = 'http://' + self.server + '/ssrf?data=%s' % self.target 29 | headers = {'User-Agent': self.ua, 'Cookie': self.cookie} 30 | url = self.protocol + self.domain + self.ng_request_url_short 31 | if self.method == 'GET': 32 | requests.get(url, params=temp_arg, headers=headers, 33 | verify=False, allow_redirects=False) 34 | elif self.method == 'POST': 35 | requests.post(url, data=temp_arg, headers=headers, 36 | verify=False, allow_redirects=False) 37 | except Exception, e: 38 | print str(e) 39 | continue 40 | else: 41 | if self.logger: self.logger.infostring('no service address,please configuration') 42 | 43 | def run(self): 44 | if self.logger: self.logger.infostring('start ssrf scan') 45 | self.request() 46 | if self.logger: self.logger.infostring('finsh ssrf task') 47 | return 48 | -------------------------------------------------------------------------------- /lib/XssScan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import random, re, string, urllib, urllib2, time, redis 3 | from config import * 4 | 5 | SMALLER_CHAR_POOL = ('<', '>') 6 | LARGER_CHAR_POOL = ('\'', '"', '>', '<', ';') 7 | 8 | DOM_FILTER_REGEX = r"(?s)|\bescape\([^)]+\)|\([^)]+==[^(]+\)|\"[^\"]+\"|'[^']+'" 9 | 10 | REGULAR_PATTERNS = ( 11 | # each (regular pattern) item consists of (r"context regex", (prerequisite unfiltered characters), "info text", r"content removal regex") 12 | (r"\A[^<>]*%(chars)s[^<>]*\Z", ('<', '>'), "\".xss.\", pure text response, %(filtering)s filtering", None), 13 | (r"", ('<', '>'), 14 | "\"\", inside the comment, %(filtering)s filtering", None), 15 | (r"(?s)]*>[^<]*?'[^<']*%(chars)s|%(chars)s[^<']*'[^<]*", ('\'', ';'), 16 | "\"\", enclosed by ', ('"', ';'), 18 | "'', enclosed by ", (';',), 20 | "\"\", enclosed by |"), 23 | (r"<[^>]*'[^>']*%(chars)s[^>']*'[^>]*>", ('\'',), 24 | "\"<.'.xss.'.>\", inside the tag, inside single-quotes, %(filtering)s filtering", 25 | r"(?s)|"), 26 | (r'<[^>]*"[^>"]*%(chars)s[^>"]*"[^>]*>', ('"',), 27 | "'<.\".xss.\".>', inside the tag, inside double-quotes, %(filtering)s filtering", 28 | r"(?s)|"), 29 | (r"<[^>]*%(chars)s[^>]*>", (), "\"<.xss.>\", inside the tag, outside of quotes, %(filtering)s filtering", 30 | r"(?s)|"), 31 | ) 32 | 33 | DOM_PATTERNS = ( # each (dom pattern) item consists of r"recognition regex" 34 | r"(?s)]*>[^<]*?(var|\n)\s*(\w+)\s*=[^;]*(document\.(location|URL|documentURI)|location\.(href|search)|window\.location)[^;]*;[^<]*(document\.write(ln)?\(|\.innerHTML\s*=|eval\(|setTimeout\(|setInterval\(|location\.(replace|assign)\(|setAttribute\()[^;]*\2.*?", 35 | r"(?s)]*>[^<]*?(document\.write\(|\.innerHTML\s*=|eval\(|setTimeout\(|setInterval\(|location\.(replace|assign)\(|setAttribute\()[^;]*(document\.(location|URL|documentURI)|location\.(href|search)|window\.location).*?", 36 | ) 37 | 38 | 39 | class XSS_Scan: 40 | def __init__(self, target, logger=None): 41 | self.target = target 42 | self.protocol = target['protocol'] 43 | self.ng_request_url_short = target['ng_request_url_short'] 44 | self.domain = target['domain'] 45 | self.method = target['method'].strip().upper() 46 | self.arg = target['arg'] 47 | self.cookie = target['cookie'] 48 | self.ua = target['ua'] 49 | self.logger = logger 50 | self._headers = {'Cookie': self.cookie, 'User-Agent': self.ua} 51 | self.payload = [] 52 | 53 | def _retrieve_content(self, url, data=None): 54 | try: 55 | req = urllib2.Request( 56 | "".join(url[i].replace(' ', "%20") if i > url.find('?') else url[i] for i in xrange(len(url))), data, 57 | self._headers) 58 | retval = urllib2.urlopen(req, timeout=30).read() 59 | except Exception, ex: 60 | retval = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", str()) 61 | return retval or "" 62 | 63 | def _contains(self, content, chars): 64 | content = re.sub(r"\\[%s]" % re.escape("".join(chars)), "", content) if chars else content 65 | return all(char in content for char in chars) 66 | 67 | def scan_page(self, url, data=None): 68 | retval, usable = False, False 69 | url, data = re.sub(r"=(&|\Z)", "=1\g<1>", url) if url else url, re.sub(r"=(&|\Z)", "=1\g<1>", 70 | data) if data else data 71 | original = re.sub(DOM_FILTER_REGEX, "", self._retrieve_content(url, data)) 72 | dom = max(re.search(_, original) for _ in DOM_PATTERNS) 73 | if dom: 74 | self.payload.append(dom.group(0)) 75 | retval = True 76 | try: 77 | for phase in ("GET", "POST"): 78 | current = url if phase == "GET" else (data or "") 79 | for match in re.finditer(r"((\A|[?&])(?P[\w\[\]]+)=)(?P[^&#]*)", current): 80 | found, usable = False, True 81 | prefix, suffix = ("".join(random.sample(string.ascii_lowercase, 5)) for i in xrange(2)) 82 | for pool in (LARGER_CHAR_POOL, SMALLER_CHAR_POOL): 83 | if not found: 84 | tampered = current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote( 85 | "%s%s%s%s" % ("'" if pool == LARGER_CHAR_POOL else "", prefix, 86 | "".join(random.sample(pool, len(pool))), suffix)))) 87 | content = ( 88 | self._retrieve_content(tampered, data) if phase == "GET" else self._retrieve_content( 89 | url, tampered)).replace( 90 | "%s%s" % ("'" if pool == LARGER_CHAR_POOL else "", prefix), prefix) 91 | for sample in re.finditer("%s([^ ]+?)%s" % (prefix, suffix), content, re.I): 92 | for regex, condition, info, content_removal_regex in REGULAR_PATTERNS: 93 | context = re.search(regex % {"chars": re.escape(sample.group(0))}, 94 | re.sub(content_removal_regex or "", "", content), re.I) 95 | if context and not found and sample.group(1).strip(): 96 | if self._contains(sample.group(1), condition): 97 | self.payload.append(tampered) 98 | found = retval = True 99 | break 100 | except Exception, e: 101 | if self.logger: self.logger.infostring('xss scan error,error : %s' % str(e)) 102 | return retval 103 | 104 | def callback(self): 105 | if self.payload: 106 | if self.logger: self.logger.infostring( 107 | 'success found xss,target : %s ' % (self.domain + self.ng_request_url_short)) 108 | current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) 109 | value = {'method': self.method, 'protocol': self.protocol, 'cookie': self.cookie, 'domain': self.domain, 110 | 'ng_request_url_short': self.ng_request_url_short, 'arg': self.arg, 'time': current_time, 111 | 'risk_type': 'XSS', 'data': self.payload} 112 | redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 113 | redis_r.hset('passive_scan_risk', 'XSS_' + self.ng_request_url_short, value) 114 | redis_r.execute_command("QUIT") 115 | 116 | def run(self): 117 | if self.logger: self.logger.infostring('start xss scan') 118 | if self.method == 'GET': 119 | url = self.protocol + self.domain + self.ng_request_url_short + '?' + self.arg 120 | self.scan_page(url) 121 | elif self.method == 'POST': 122 | url = self.protocol + self.domain + self.ng_request_url_short 123 | self.scan_page(url, self.arg) 124 | self.callback() 125 | if self.logger: self.logger.infostring('finsh ssrf task') 126 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'grayddq' 4 | __version__ = '0.1' 5 | -------------------------------------------------------------------------------- /lib/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # redis,用于任务发布,此处为数据源存储的redis 3 | DATA_REDIS_HOST = '127.0.0.1' 4 | DATA_REDIS_PORT = 6379 5 | DATA_REDIS_PASSWORD = '122112121212' 6 | DATA_REDIS_DB = 5 7 | 8 | # redis,用于漏洞验证消息队列 9 | REDIS_HOST = '182.61.11.11' 10 | REDIS_PORT = 6379 11 | REDIS_PASSWORD = '11112222' 12 | REDIS_DB = 5 13 | 14 | # ----------------------------------------------------------------------------------------------- 15 | # --------------------------------任务发布 可不需要配置下列信息---------------------------------- 16 | # ----------------------------------------------------------------------------------------------- 17 | 18 | # 配置参数中的替换字符,防止参数中出现session权限判断等 19 | conf_parameter_json = { 20 | 'session': '12234234234' 21 | } 22 | # 维持一个sesson访问列表 23 | conf_cookies = [ 24 | {'domain': '*.test.com', 'cookie': 'aaaaa=bbbbb'}, 25 | {'domain': 'www.test.com', 'cookie': 'session=aaaa'}, 26 | {'domain': 'aaa.testbbb.com', 'cookie': 'NL=1234'} 27 | ] 28 | # 白名单路径,路径中出现如/admin不进行任何安全检测 29 | conf_white_path = ['/admin', '/administra'] 30 | # 自定义漏洞匹配规则,遍历每个参数名称,当设定parameter时,值替换为设定的value字串,并在response中匹配正则rule 31 | # 不设定parameter时,代表挨个替换 32 | # 可以测试一些越权操作和一些常见漏洞 33 | # 比如设定参数名为phone,替换其参数值为手机号,并匹配response的body内,是否出现此人的个人信息。 34 | conf_scan_rule = [ 35 | {'value': '17600296111', 'rule': '111111111', 'name': 'Exceed Permissions', 'parameter': 'phone'}, 36 | {'value': '17600296112', 'rule': '22222222', 'name': 'Exceed Permissions', 'parameter': 'iphone'} 37 | ] 38 | # server sqlmap远程ip链接 39 | sqlmap_server = 'http://127.0.0.1:8775/' 40 | # SQL检测最大运行时间(S) 41 | sqlmap_max_time = 600 42 | # db_type数据库检测类型 43 | sqlmap_db_type = '' 44 | # sqlmap扫描等级和风险 45 | sqlmap_level = 1 46 | sqlmap_risk = 1 47 | # ssrf_server远程ip或domain 48 | # ssrf实质是服务端访问了客户端传递的地址 49 | # 预先搭建一台web,收集分析日志可以判断ssrf触发点。 50 | ssrf_server = '10.1.1.3' 51 | # ssrf web日志路径 52 | ssrf_logpath = '/usr/local/openresty/nginx/logs/access.log' 53 | -------------------------------------------------------------------------------- /lib/customizeScan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import redis, urlparse, requests, re, time 3 | from config import * 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class Customize_Scan(): 9 | def __init__(self, target, logger=None): 10 | self.target = target 11 | self.protocol = target['protocol'] 12 | self.ng_request_url_short = target['ng_request_url_short'] 13 | self.domain = target['domain'] 14 | self.method = target['method'].strip().upper() 15 | self.arg = dict(urlparse.parse_qsl(target['arg'])) 16 | self.cookie = target['cookie'] 17 | self.ua = target['ua'] 18 | self.logger = logger 19 | self.redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 20 | self.rules = self.redis_r.hget('passive_config', 'conf_scan_rule') 21 | 22 | def scan(self): 23 | headers = {'User-Agent': self.ua, 'Cookie': self.cookie} 24 | for key in self.arg: 25 | for rule in self.rules: 26 | try: 27 | temp_arg = self.arg.copy() 28 | if rule['parameter']: 29 | if key == rule['parameter']: temp_arg[key] = rule['value'] 30 | else: 31 | temp_arg[key] = rule['value'] 32 | if cmp(self.arg, temp_arg) == 0: continue 33 | url = self.protocol + self.domain + self.ng_request_url_short 34 | if self.method == 'GET': 35 | response = requests.get(url, params=temp_arg, headers=headers, verify=False, 36 | allow_redirects=True) 37 | else: 38 | response = requests.post(url, data=temp_arg, headers=headers, verify=False, 39 | allow_redirects=True) 40 | if not response.content: 41 | continue 42 | if re.search(rule['rule'], response.content): 43 | current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) 44 | value = {'method': self.method, 'protocol': self.protocol, 'cookie': self.cookie, 45 | 'domain': self.domain, 'ng_request_url_short': self.ng_request_url_short, 46 | 'arg': self.arg, 'time': current_time, 'risk_type': rule['name'], 47 | 'change_arg': temp_arg, 'data': response.content} 48 | self.redis_r.hset('passive_scan_risk', rule['name'] + '_' + self.ng_request_url_short, value) 49 | except Exception, e: 50 | continue 51 | return 52 | 53 | def run(self): 54 | if self.logger: self.logger.infostring('start customize scan') 55 | if not self.rules or not self.arg: 56 | return 57 | self.scan() 58 | if self.logger: self.logger.infostring('finsh customize scan') 59 | -------------------------------------------------------------------------------- /lib/headers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import redis, urlparse, urllib, types 3 | from config import * 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class Check_Heads(): 9 | def __init__(self, target): 10 | self.redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 11 | target['ua'] = \ 12 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106' 13 | target['cookie'] = '' 14 | 15 | if self.redis_r.hget('passive_config', 'parameter_json'): 16 | parameter_json = eval(self.redis_r.hget('passive_config', 'parameter_json')) 17 | if type(target['arg']) is types.StringType: 18 | qs_dict = dict(urlparse.parse_qsl(target['arg'])) 19 | for k in qs_dict: 20 | if k in parameter_json: 21 | qs_dict[k] = parameter_json[k] 22 | target['arg'] = urllib.unquote(urllib.urlencode(qs_dict)) 23 | elif type(target['arg']) == dict: 24 | for k in target['arg']: 25 | if k in parameter_json: 26 | target['arg'][k] = parameter_json[k] 27 | 28 | cookies_list = self.redis_r.hget('passive_config', 'cookies') 29 | if cookies_list: 30 | for cookie in eval(cookies_list): 31 | if '*' in cookie['domain'] and cookie['domain'].replace('*', '') in target['domain']: 32 | target['cookie'] = cookie['cookie'] 33 | elif target['domain'] == cookie['domain']: 34 | target['cookie'] = cookie['cookie'] 35 | return 36 | -------------------------------------------------------------------------------- /lib/log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging, os 3 | import logging.handlers 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "Public Monitor", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class LogInfo: 9 | def __init__(self): 10 | self.log_file = 'log/log.txt' 11 | if not os.path.exists('log'): 12 | os.mkdir('log') 13 | logging.basicConfig( 14 | level=logging.INFO, 15 | format='%(asctime)s - %(name)s - %(message)s' 16 | ) 17 | self.logger = logging.getLogger('LogInfo') 18 | fh = logging.FileHandler(self.log_file) 19 | fh.setLevel(logging.INFO) 20 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s') 21 | fh.setFormatter(formatter) 22 | self.logger.addHandler(fh) 23 | 24 | def infostring(self, infostring): 25 | self.logger.info(infostring) 26 | -------------------------------------------------------------------------------- /lib/tryReqest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests, urlparse 3 | 4 | 5 | class Try_Request(): 6 | def __init__(self, target, logger=None): 7 | self.target = target 8 | self.protocol = target['protocol'] 9 | self.ng_request_url_short = target['ng_request_url_short'] 10 | self.domain = target['domain'] 11 | self.method = target['method'].strip().upper() 12 | self.arg = dict(urlparse.parse_qsl(target['arg'])) 13 | self.cookie = target['cookie'] 14 | self.ua = target['ua'] 15 | self.logger = logger 16 | 17 | def run(self): 18 | try: 19 | url = self.protocol + self.domain + self.ng_request_url_short 20 | headers = {'User-Agent': self.ua, 'Cookie': self.cookie} 21 | if self.method == 'GET': 22 | response = requests.get(url, params=self.arg, headers=headers, verify=False, allow_redirects=False) 23 | else: 24 | response = requests.post(url, data=self.arg, headers=headers, verify=False, allow_redirects=False) 25 | if (response.status_code == 404) or (response.status_code == 403): 26 | if self.logger: self.logger.infostring('target url response 404/403, tash failed') 27 | return False 28 | return True 29 | except Exception, e: 30 | return False 31 | -------------------------------------------------------------------------------- /lib/white.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from config import * 3 | import redis 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | 8 | class White_Check: 9 | def __init__(self, target, logger=None): 10 | self.redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 11 | self.target = target 12 | self.logger = logger 13 | 14 | def run(self): 15 | if self.redis_r.hget('passive_config', 'white_path'): 16 | white_list = eval(self.redis_r.hget('passive_config', 'white_path')) 17 | for path in white_list: 18 | if path in self.target['ng_request_url_short']: 19 | self.redis_r.execute_command("QUIT") 20 | if self.logger: self.logger.infostring('the target path in the whitelist,no scan') 21 | if self.logger: self.logger.infostring('finsh task') 22 | return True 23 | return False 24 | -------------------------------------------------------------------------------- /log/log.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayddq/PassiveSecCheck/67ea628d6dcb8de1ac04ebdc3073e0ac6a5fe65c/log/log.txt -------------------------------------------------------------------------------- /nginxlog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os, urllib, redis, time 3 | from lib.config import * 4 | 5 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 6 | 7 | if __name__ == '__main__': 8 | redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 9 | logPath = redis_r.hget('passive_config', 'ssrf_logpath') 10 | if not os.path.exists(logPath): 11 | print 'log file not exist' 12 | file = open(logPath) 13 | for line in file: 14 | lines = line.strip().strip('\n').split(' ') 15 | for s in lines: 16 | if '/ssrf?data=' in s: 17 | info = urllib.unquote(s.replace('/ssrf?data=', '')).split('ssrf?data=') 18 | if len(info) > 1: 19 | target = eval(info[1].replace(',+', ',').replace(':+', ':')) 20 | if len(target) > 0: 21 | target['time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) 22 | target['risk_type'] = 'SSRF' 23 | target['data'] = '' 24 | redis_r.hset('passive_scan_risk', 'SSRF_' + target['ng_request_url_short'], target) 25 | os.system('cat /dev/null > %s' % logPath) 26 | -------------------------------------------------------------------------------- /pic/111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayddq/PassiveSecCheck/67ea628d6dcb8de1ac04ebdc3073e0ac6a5fe65c/pic/111.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis==2.10.5 2 | celery==3.1.7 3 | Requests==2.18.4 4 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from tasks import * 3 | import redis 4 | from lib.config import * 5 | 6 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 7 | 8 | if __name__ == '__main__': 9 | redis_r_data = redis.StrictRedis(host=DATA_REDIS_HOST, port=DATA_REDIS_PORT, password=DATA_REDIS_PASSWORD, 10 | db=DATA_REDIS_DB) 11 | 12 | key_list = redis_r_data.keys('DataSort_*') 13 | for key in key_list: 14 | values = eval(redis_r_data.get(key)) 15 | print "[+] push info to redis , url_short: %s" % values['ng_request_url_short'] 16 | passive_scan_dispath.delay(values) 17 | print "success push task." 18 | ''' 19 | target = {} 20 | target['protocol'] = "http://" 21 | target['ng_request_url_short'] = "/user/my.php" 22 | target['domain'] = "www.babytree.com" 23 | target['method'] = "GET" 24 | target['arg'] = "tab=message&view=user" 25 | print "[+] push info to redis , url_short: %s" % target['ng_request_url_short'] 26 | passive_scan_dispath.delay(target) 27 | ''' -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from celery import Celery, platforms 3 | from lib.SqlScan import * 4 | from lib.log import * 5 | from lib.headers import * 6 | from lib.SsrfScan import * 7 | from lib.XssScan import * 8 | from lib.white import * 9 | from lib.customizeScan import * 10 | from lib.tryReqest import * 11 | 12 | NAME, VERSION, AUTHOR, LICENSE = "PublicSecScan", "V0.1", "咚咚呛", "Public (FREE)" 13 | 14 | 15 | def config(): 16 | redis_r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=REDIS_DB) 17 | # 防止任务开始时修改已有的配置信息,当已存在配置信息时,不进行配置设置. 18 | if redis_r.keys('passive_config'): 19 | if len(redis_r.hkeys('passive_config')) > 2: 20 | return 21 | # 配置参数中的替换字符,防止由于越权导致的误操作 22 | redis_r.hset('passive_config', 'parameter_json', conf_parameter_json) 23 | # 维持一个sesson访问列表 24 | redis_r.hset('passive_config', 'cookies', conf_cookies) 25 | # 白名单路径,路径中出现如/admin不进行检测 26 | redis_r.hset('passive_config', 'white_path', conf_white_path) 27 | # 自定义扫描规则 28 | redis_r.hset('passive_config', 'conf_scan_rule', conf_scan_rule) 29 | # sqlmap远程ip链接 30 | redis_r.hset('passive_config', 'sqlmap_server', sqlmap_server) 31 | # 当前任务max_time最大运行时间 32 | redis_r.hset('passive_config', 'sqlmap_max_time', sqlmap_max_time) 33 | # db_type数据库检测类型 34 | redis_r.hset('passive_config', 'sqlmap_db_type', sqlmap_db_type) 35 | # sqlmap扫描等级和风险 36 | redis_r.hset('passive_config', 'sqlmap_level', sqlmap_level) 37 | redis_r.hset('passive_config', 'sqlmap_risk', sqlmap_risk) 38 | # server远程ip或domain,建议侦测ssrf的web放到内网 39 | redis_r.hset('passive_config', 'ssrf_server', ssrf_server) 40 | # ssrf web日志绝对地址 41 | redis_r.hset('passive_config', 'ssrf_logpath', ssrf_logpath) 42 | 43 | 44 | logger = LogInfo() 45 | # 初始化相关配置, 46 | config() 47 | 48 | app = Celery() 49 | platforms.C_FORCE_ROOT = True 50 | DEBUG_INFO = True 51 | app.conf.update( 52 | CELERY_IMPORTS=("tasks",), 53 | BROKER_URL='redis://:' + REDIS_PASSWORD + '@' + REDIS_HOST + ':' + str(REDIS_PORT) + '/' + str(REDIS_DB), 54 | CELERY_TASK_SERIALIZER='json', 55 | CELERY_RESULT_SERIALIZER='json', 56 | CELERY_TIMEZONE='Asia/Shanghai', 57 | CELERY_ENABLE_UTC=True, 58 | CELERY_REDIS_MAX_CONNECTIONS=5000, 59 | BROKER_HEARTBEAT=30, 60 | BROKER_TRANSPORT_OPTIONS={'visibility_timeout': 3600}, 61 | ) 62 | 63 | 64 | # 数据源5元素[方法、协议、host、接口、参数] 65 | # 方法method GET或POST 66 | # 协议protocol http://或https:// 67 | # domain www.baidu.com 68 | # 接口ng_request_url_short /api/1.php 69 | # 参数arg a=1&b=2&c=3 70 | @app.task(name='tasks.passive_scan_dispath') 71 | def passive_scan_dispath(targets): 72 | logger.infostring('create sec task,target %s...' % (targets['domain'] + targets['ng_request_url_short'])) 73 | # 白名单验证 74 | if White_Check(targets, logger).run(): 75 | return 76 | # 导入配置信息 77 | Check_Heads(targets) 78 | # 判断是否允许访问 79 | if not Try_Request(targets): 80 | return 81 | # SQL注入扫描 82 | SQL_Scan(targets, logger).run() 83 | # SSRF扫描 84 | SSRF_Scan(targets, logger).run() 85 | # XSS扫描 86 | XSS_Scan(targets, logger).run() 87 | # 自定义漏洞规则扫描 88 | Customize_Scan(targets, logger).run() 89 | 90 | logger.infostring('finsh task.') 91 | --------------------------------------------------------------------------------