├── 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 | 
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)", ('\'', ';'),
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)",
35 | r"(?s)",
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 |
--------------------------------------------------------------------------------