Wooyun漏洞地址url爬虫及数据处理

之前做测试的时候,发现@四爷发现个漏洞,一个非常古怪的地址,我问四爷:“你咋知道这个的?”,四爷说:“扫的啊!”,我:“。。。”,所以才有了这篇文章。

PS:以上故事纯属扯淡 ! 0.0

其实是今年给自己定了个目标,就是好好把编程给学好,毕竟自己安全那么菜,说不定就失业了。。。

0x00 获取Wooyun漏洞地址

这里选择爬取的目标是https://wooyun.shuimugan.com,这个镜像站有个好处,就是它的wooyun漏洞地址存入数据库的时候使用了id,即https://wooyun.shuimugan.com/bug/view?bug_no=1这种形式,所以,我们只需要遍历bug_no即可获取所有的漏洞地址(这里考虑到数据下载、处理、文件io等因素,选择了先把存在漏洞的url先存起来)。

感谢网站的站长的不杀之恩!因为,在进行获取地址的时候由于一直再调试并发引擎,所以爬了好多遍。。。

在获取存在漏洞URL的时候只使用了协程,然后主程序使用的进程,数据下载部分使用的进程+协程。

在使用requests请求的时候虽然一直用verif=False来忽略ssl,但是会一直打印:

1
2
/Library/Python/2.7/site-packages/requests/packages/urllib3/connectionpool.py:768: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.org/en/latest/security.html
InsecureRequestWarning)

搞的很不爽,百度了下使用了下面代码解决:

1
2
3
4
5
6
7
import ssl
try:
_create_unverified_https_context = ssl._create_unverified_context # 忽略证书错误
except AttributeError:
pass
else:
ssl._create_default_https_context = _create_unverified_https_context

请求的时候使用了通用的反爬虫策略:

  • 伪装header头
  • 随机User-Aagent
  • 随机IP

这里本来是打算使用自己的代理池的,无奈免费的代理实在是慢,浪费时间,同时可能导致数据不准确。所以就没有使用代理IP了。

1
2
3
4
5
6
7
headers = {
"User-Agent": random.choice(User-Agent),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "application/x-www-form-urlencoded"
}

协程部分:

1
2
3
4
5
6
7
8
9
10
from gevent import monkey, pool
monkey.patch_all()
reload(sys)
sys.setdefaultencoding('utf8')
p = pool.Pool(150)
for i in range(NUM):
_url = url + str(i)
jobs.append(p.spawn(getURL, _url))
gevent.joinall(jobs)

网站bug_no总共有230305个,获取到了8万多个存在漏洞的地址。

0x01 下载数据

下载数据是爬虫的重点,本着只多不少的原则,本来考虑到大的数据量,效率问题,使用正则表达式来进行处理,但是发现获取的脏数据太多,对后面的数据处理造成了很多无法预知的麻烦,所以最终选择的方案是使用xpath+re来进行数据处理。

首先,使用xpath获取漏洞细节部分(有些url在poc字段,但是考虑到大部分,所以只选择了漏洞细节字段,所以爬取的结果不是很准确。)

xpath://*[@id="w0"]/tbody/tr[21]/td/text()

这里遇到个问题就是无论怎么复制,都无法获取到漏洞细节里面的内容,使用chrome的xpath helper也的确是能匹配到内容,实在是郁闷!最终在我@加菲猫和@nearg1e斌哥哥的帮助下找到了原因。

原因在于:chrome浏览器在进行页面渲染时使用了htmlparse进行了处理,所以诸如tbody等一些标签是chrome自动加载的。

最终的xpath://*[@id="w0"]/tr[21]/td//text()

下面就是从漏洞细节中获取所有的URL了,这里还是为了更多的获取URL,找了个非常全面的正则表达式:

1
r = re.compile(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s\`!()\[\]{};:\'".,<>?«»“”‘’]))')

导致匹配的URL什么奇葩都有,具体可看:dicc.txt的内容

在使用这个re的时候又遇到个问题,程序跑着跑着停了,很是无语,后来经帮助调试发现是有些sql注入的URL中存在(,)这两个字符,导致正则死掉,为了图方便,直接做了个判断给剔除了 -。-

并发部分参考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
def create_process(level, jobqueue, plugins, debug, headers):
for _ in range(level):
process = multiprocessing.Process(target=worker, args=(jobqueue, plugins, debug, headers))
process.daemon = True
process.start()
def worker(jobqueue, plugins, debug, headers):
while True:
try:
target = jobqueue.get()
gevent_scan_task(target, plugins, debug, headers)
except Exception, e:
print traceback.format_exc()
finally:
jobqueue.task_done()
def gevent_scan_task(target, plugins, debug, headers):
evt = Event()
pool = []
scanmodle = ScanModle(target, plugins, debug, headers)
pool.append(gevent.spawn(scanmodle.scan_port(evt)))
pool.append(gevent.spawn(scanmodle.scan_plugins(evt)))
for port in scanmodle.get_ports():
pool.append(gevent.spawn(scanmodle.scan_plugins(evt, port)))
gevent.joinall(pool)
save_cache(scanmodle)

最终获取到了153217个地址

0x02 数据处理

这部分说难也难说不难也不难,我是不太在行【捂脸】,目前我处理的步骤如下:

  1. 去除所有中文部分(这里由于有些url中的确是带中文,所有使用换行替换)
  2. 去除所有只有/.len(strip(link) == 0的部分
  3. 获取所有URL中的目录部分(使用urlparse)
  4. 去除一些其他比如wooyun/bugsjdbc**xmlnst.cnldapssh等非预期字符及长度小于等于4的部分
  5. 使用urlparse获取p.path, p.query,p.fragment,并使用isinstance来判断两个URL是否相同来去重

说明:

1、第三步没有使用迭代,比如:

1
2
3
4
5
6
7
8
/wooyun/bugs/?id=1
没有获取:
?id=1
/wooyun
/wooyun/bugs/
只是获取到:
/wooyun/bugs/?id=1
有兴趣可参考:https://github.com/Xyntax/POC-T/blob/2.0/plugin/urlparser.py

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urlparse
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
def iterate_path(ori_str):
# parser = urlparse.urlparse(ori_str)
# _path_list = parser.path.replace('//', '/').strip('/').split('/')
# print _path_list
_ans_list = set()
_ans_list.add(ori_str)
_ans_list = list(_ans_list)
for i in range(len(_ans_list)):
p = urlparse.urlparse(_ans_list[i])
return urlparse.urlunsplit(['', '', p.path, p.query, p.fragment])

2、第五步是简单去除如下类型的URL:

1
2
3
4
5
6
7
/wooyun/?id=1&url=123
/wooyun/?id=2&url=233
/wooyun/?id=3&url=666
/wooyun/1.html
/wooyun/233.html
/wooyun/6666.html

0x03 后续

其实我主要是想获取个扫描目录的字典,但是如果发散下呢?这些URL有什么用?

我思考的如下:

1、在我刚算入门安全的时候wooyun叽叽了,所以有些漏洞自己基本上没见过,要是在一个一个去看文章,那代价太大了也没有时间。使用这个做目录扫描的时候,如果发现存在此URL,那么说明在wooyun上是存在这个漏洞的,如果自己不知道就可以搜一波,学习下了,快捷、高效。

2、做agent、入侵防御、态势感知的话,可把这些做成漏洞库,遇到访问这些地址就可以来个告警,也可以当个威胁情报搞搞。

3、做个漏洞分析,看看大佬们都喜欢提交啥漏洞(我不会告诉你排在第一二的是svn和git)

获取的数据,处理和没处理的都可以在下面地址下载(PS:处理真的只是简单处理,比如大佬们上传的webshell、一些只针对某个网站的URL筛选等都得做,,,慎用!后续还得优化,大佬们有兴趣的话带带我。 0.0)

未处理:dicc.txt

已处理:dicc8.txt

大爷,赏个铜板呗!