爬取cnnvd思路
目录:
需求分析:
比较好的扫描器,都是国外公司出品的。
因此,扫描出来后会有cve,但是通过cve库去查到相关的漏洞信息都是英文的。
如果我们想要中文的漏洞信息怎么办?
在国家信息安全漏洞库有中文的漏洞信息。
实现步骤:
- 通过
cve
查询到对应的cnnvd
- 再用
cnnvd
在国家信息安全漏洞库查询到对应漏洞的中文信息
程序思路:
- 程序接受一个输入:cve编号
- 通过cve编号查询到cnnvd编号
- 通过cnnvd编号去查询具体的中文漏洞信息
实现细节:
1.访问漏洞信息页面:
需要用到CookieJar对象自动管理cookie
- 代码片段:
# 设置全局变量 : pageOpener对象
global pageOpener
# 创建CookieJar对象,自动管理cookie
Cookie = cookielib.CookieJar()
# 创建pageOpener对象,把cookiejar塞进去,每次打开页面都会自动使用cookie
pageOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor(Cookie))
2.发送查询请求(POST)
将PostData定义为一个字典。
# 查询 时需要post的数据
PostData = {
#'CSRFToken': csrftoken,
'cvHazardRating': '',
'cvVultype' : '',
'qstartdateXq' :'',
'cvUsedStyle' :'',
'cvCnnvdUpdatedateXq' : '',
'cpvendor' : '',
'relLdKey' : '',
'hotLd' : '',
'isArea' : '',
'qcvCname' : '',
'qcvCnnvdid' : CVE_number1,
'qstartdate' : '',
'qenddate' :''
}
post_url = 'http://www.cnnvd.org.cn/web/vulnerability/queryLds.tag'
# request object请求对象,需要Post header和cookie
post_data = urllib.urlencode(PostData)
post_data = 'CSRFToken=%s&'%csrftoken + post_data
最重要的是,里面要带上csrftoken,csrftoken用过一次后就会失效。
所以爬虫需要带的参数,比较重要的有这两个:
- cookie(CookieJar处理)
- csrftoken(从页面抓取)
每次查询重新获取token:
csrftoken
使用一次后就会失效
要在页面中爬到我们想要的csrf-token
在获取token之后,就可以用post方式带上csrftoken和cev_num发包过去了。 接下来按理来说就要处理返回包,从返回的信息中获取到我们想要的信息。
但是这当中突然多了一个坑,页面获得后是乱码,无法处理,后来才知道是因为urllib2不支持gzip页面的自动解压。
urllib & urllib2 & gzip页面自动解压
- 代码片段:
PostRequest = urllib2.Request(post_url, post_data, Header)
PostResponse = pageOpener.open(PostRequest).read() #chardet.detect(loginResponse)结果为{'confidence': 0.0,'language': None, 'encoding': None}
#urllib支持gzip页面自动解压而urllib2不支持。 所以对于这种页面, 先解压再read
buf = StringIO.StringIO(PostResponse)
f = gzip.GzipFile(fileobj=buf)
content = f.read()
3.处理返回页面,抓取需要的信息
共需要获取9项信息:
- 漏洞名称
- cnnvd_id
- 发布时间
- 危害等级
- 漏洞类型
- CVE编号
- 漏洞细节
- 威胁类型
- 漏洞解决方案
获取漏洞名字
定位到要抓取的信息后,就想想用什么方法来抓取。 之前我比较常用正则表达式来抓取内容,这次试了下lxml这个模块。感觉还挺好用的。 上手还挺容易的。哈哈哈
还发现google浏览器有个非常好用的功能,在查看元素的页面,可以调出搜索框。
搜索框支持这几种查询:包含Xpath
支持Xpath,之前有听过这个概念,但是没认真去了解过。。。
- 代码片段:
# 解析html
tmp = etree.HTML(PageHTML, parser=etree.HTMLParser(encoding='utf-8'))
# 开始抓取内容
# 获取漏洞名称 bug_name
bug_name = tmp.xpath('/html/body/div[4]/div[1]/div[1]/div[2]/h2/text()')[0]
其他项的信息,抓取的原理是一样的,就是xpath路径要修改一下即可。
关键概念:
Xpath:
XPath 是一门在 XML 文档中查找信息的语言。
XPath 可用来在 XML 文档中对元素和属性进行遍历。
- XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。
- 因此,对 XPath 的理解是很多高级 XML 应用的基础。
XPath 路径表达式:
- XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
- 这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
之前只是拿来主义使用urllib和urllib2,没有花时间去了解清楚它们俩。 现在好好了解一下。
urllib模块:
urllib.urlopen(url[, data[, proxies]])
:
- 创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。
- 参数url表示远程数据的路径,一般是网址;
- 参数data表示以post方式提交到url的数据
- 参数proxies用于设置代理。
urlopen返回一个类文件对象,他提供了如下方法:
- read(),readline(),readlines(), fileno(), close():这些方法的使用方式与文件对象完全一样;
- info():返回一个httplib.HTTPMessage对象,表示远程服务器返回的头信息;
- getcode():返回Http状态码。如果是http请求,200表示请求成功完成;404表示网址未找到;
- geturl():返回请求的url;
例子:
google = urllib.urlopen('http://www.google.com')
print 'http header:/n', google.info()
print 'http status:', google.getcode()
print 'url:', google.geturl()
for line in google: # 就像在操作本地文件
print line,
google.close()
urllib.urlretrieve(url[, filename[, reporthook[, data]]]):
- urlretrieve方法直接将远程数据下载到本地。
- 参数filename指定了保存到本地的路径(如果未指定该参数,urllib会生成一个临时文件来保存数据);
- 参数reporthook是一个回调函数,当连接上服务器、以及相应的数据块传输完毕的时候会触发该回调。我们可以利用这个回调函 数来显示当前的下载进度,下面的例子会展示。
- 参数data指post到服务器的数据。该方法返回一个包含两个元素的元组(filename, headers),filename表示保存到本地的路径,header表示服务器的响应头。
def cbk(a, b, c):
'''''回调函数
@a: 已经下载的数据块
@b: 数据块的大小
@c: 远程文件的大小
'''
per = 100.0 * a * b / c
if per > 100:
per = 100
print '%.2f%%' % per
url = 'http://www.sina.com.cn'
local = 'd://sina.html'
urllib.urlretrieve(url, local, cbk)
urllib中还提供了一些辅助方法,用于对url进行编码、解码。 url中是不能出现一些特殊的符号的,有些符号有特殊的用途。
- 我们知道以get方式提交数据的时候,会在url中添加key=value这样的字符串,所以在value中是不允许有'=',因此要对其进行编码;
与此同时服务器接收到这些参数的时候,要进行解码,还原成原始的数据。这个时候,这些辅助方法会很有用:
urllib.quote(string[, safe])
:对字符串进行编码。参数safe指定了不需要编码的字符;urllib.unquote(string)
:对字符串进行解码;urllib.quote_plus(string[,safe])
:与urllib.quote类似,但这个方法用'+'来替换' ',而quote用'%20'来代替' 'urllib.unquote_plus(string)
:对字符串进行解码;urllib.urlencode(query[, doseq])
:将dict或者包含两个元素的元组列表转换成url参数。例如 字典{'name': 'dark-bull', 'age': 200}将被转换为"name=dark-bull&age=200"urllib.pathname2url(path)
:将本地路径转换成url路径;urllib.url2pathname(path)
:将url路径转换成本地路径;
urllib2模块:
import urllib2
req = urllib2.Request("http://www.douban.com")
response = urllib2.urlopen(req)
html = response.read()
print html
实际步骤:
- 1、
urllib2.Request()
的功能是构造一个请求信息,返回的req就是一个构造好的请求 - 2、
urllib2.urlopen()
的功能是发送刚刚构造好的请求req,并返回一个文件类的对象response,包括了所有的返回信息。 3、通过
response.read()
可以读取到response里面的html,通过response.info()
可以读到一些额外的信息。response.info():
读额外的信息:有时你会碰到,程序也对,但是服务器拒绝你的访问。这是为什么呢?
- 问题出在请求中的头信息(header)。
- 有的服务端有洁癖,不喜欢程序来触摸它。这个时候你需要将你的程序伪装成浏览器来发出请求。请求的方式就包含在header中。
例子:
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'# 将user_agent写入头信息
values = {'name' : 'who','password':'123456'}
headers = { 'User-Agent' : user_agent }
data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
the_page = response.read()
urllib2带cookie的使用:
- 例子
#coding:utf-8
import urllib2,urllib
import cookielib
url = r'http://www.renren.com/ajaxLogin'
#创建一个cj的cookie的容器
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
#将要POST出去的数据进行编码
data = urllib.urlencode({"email":email,"password":pass})
r = opener.open(url,data)
print cj
参考:urllib2模块讲解
httplib模块:
httplib是一个相对底层的http请求模块,其上有专门的包装模块,如urllib内建模块,goto等第三方模块,但是封装的越高就越不灵活,比如urllib模块里请求错误时就不会返回结果页的内容,只有头信息,对于某些需要检测错误请求返回值的场景就不适用,所以就得用这个模块了。
class httplib.HTTPConnection
说明:
- 该类用于创建一个http类型的请求链接
原型:
HTTPConnection(host[, port[, strict[, timeout]]])
- host: 请求的服务器host,不能带http://开头
- port: 服务器web服务端口
- strict: 是否严格检查请求的状态行,就是http1.0/1.1 协议版本的那一行,即请求的第一行,默认为False,为True时检查错误会抛异常
- timeout: 单次请求的超时时间,没有时默认使用httplib模块内的全局的超时时间
返回:
- HTTPConnection类会实例并返回一个HTTPConnection对象
例子:
conn1 = HTTPConnection('www.baidu.com:80') conn2 = HTTPconnection('www.baidu.com',80) conn3 = HTTPConnection('www.baidu.com',80,True,10)
class httplib.HTTPSConnection
说明:
- 该类用于创建一个https类型的请求链接
原型:
- HTTPSConnection(host[, port[, key_file[, cert_file[, strict[, timeout]]]]])
- key_file:一个包含PEM格式的私钥文件
- cert_file:一个包含PEM格式的认证文件
- other:其它同http参数
返回:
- 同样返回一个HTTPSConnection对象
注意:
- 要创建https链接,必须要保证底层的socket模块是支持ssl的编译模式,即编译时ssl选项的开关是开着的
例子:
conn3 = HTTPSConnection('accounts.google.com',443,key_file,cert_file,True,10)
HTTPConnection对象request方法
:
说明:
- 发送一个请求
原型:
- conn.request(method, url[, body[, headers]])
- method: 请求的方式,如'GET','POST','HEAD','PUT','DELETE'等
- url: 请求的网页路径。如:'/index.html'
- body: 请求是否带数据,该参数是一个字典
- headers: 请求是否带头信息,该参数是一个字典,不过键的名字是指定的http头关键字
返回:
- 无返回,其实就是相对于向服务其发送数据,但是没有最后回车
例子:
conn.request('GET', '/', '', {'user-agent':'test'})
urllib模块和urllib2模块的区别:
- urllib 和urllib2都是接受URL请求的相关模块
- urllib2可以接受一个Request类的实例来设置URL请求的headers
- urllib仅可以接受URL。
- 这意味着,你不可以通过urllib模块伪装你的User Agent字符串等(伪装浏览器)。
- urllib提供urlencode方法用来GET查询字符串的产生,而urllib2没有。
- 这是为何urllib常和urllib2一起使用的原因。
- urllib2模块比较优势的地方:urllib2.urlopen可以接受Request对象作为参数,从而可以控制HTTP Request的header部。
- 但是urllib.urlretrieve函数以及urllib.quote等一系列quote和unquote功能没有被加入urllib2中,因此有时也需要urllib的辅助。
参考:
参考:
启发:
写程序的思路:
在写这个代码的时候,是没用过lxml模块和xpath语法的,不过不要有畏难心理。 一开始想一次性把xpath语法写对,一直写不对,有一点点挫败感。 后来,发现每写xpath的一部分的时候,就调试一下,在每次对的基础之上,慢慢添加路径,这样就比较有成就感,感觉这个思路才是对的。
用小的模块去组成大的模块
有过实战经验,做过项目的意义:
在写这个程序的时候,要抓的csrftoken是在页面源码中的。
立刻就想起之前写过一个教务系统爬虫,要抓__VIEWSTATE。
<input type="hidden" name="__VIEWSTATE" value="dDw1NjkwNTA3NjQ7dDw7bDxpPDE+Oz47bDx0PDtsPGk8Mz47aTwxND47aTwxNz47PjtsPHQ8cDxsPFZpc2libGU7PjtsPG88Zj47Pj47Oz47dDxwPDtwPGw8b25jbGljazs+O2w8d2luZG93LmNsb3NlKClcOzs+Pj47Oz47dDxwPGw8VmlzaWJsZTs+O2w8bzxmPjs+Pjs7Pjs+Pjs+PjtsPGltZ0RMOz4+y6kA4C28m4Zv+8qNY7Y/ILehplc=" />
这两个参数的共同点:
- 都是在服务端会进行验证的,若不带这个参数的话,服务端会拒绝返回页面。
这两个参数的不同点:
__VIEWSTATE目的是:保存数据
- VIEWSTATE作用? 在当前页面中保存数据的.
- 像session.是会话级别的.只要会话没有过期.session中存的数据就在.
- viewstate是页面级别的.只要这个页面在.viewstate中存的数据就在. 保存了控件的很多状态信息的一个东西,用base64编码在客户端保存,每次提交时,返回到服务器,服务器再解析出这些控件的状态。
csrftoken目的是:防止csrf攻击
- 作用:防御CSRF(Cross Site Request Forgery)跨站域请求伪造,
- 要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
- Token 的中文有人翻译成 “令牌”,我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡。
你带token,服务器才会返回页面给你
- 基于 Token 的身份验证
- web安全之token
有过实战经验,做过项目的意义,就是遇到类似的、相同的问题,可以很快的举一反三去解决问题。 因为这个知识点,你组过块,不再是零散的知识了。而是一块块大块的知识,在知识迁移方面会很快。
但是前提是:
- 你习惯经常总结
- 总结的目的就是不断的组块,不让知识点那么零散
TO DO:
之前我问过同事ying哥,搞web安全、搞渗透感觉需要积累很多猥琐的思路 ying哥说:是啊,所以要经常总结。
没错就是要:
经常总结 经常总结 经常总结
重要的事情说3遍!!!