安全路透社
当前位置:安全路透社 > 网络转载 > 正文

QtWebKit爬虫与去重规则

*本文作者:DX安全团队—-0d9y,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。  

一个漏扫程序想要检测更多的漏洞,那么他配备的爬虫一定得给力,能够爬取很多其他爬虫爬不到的链接,就有可能比其他漏扫程序检测出更多的漏洞。

基于Qtwebkit的爬虫

记得刚刚入Python爬虫的坑的时候,用的是urllib2配合 thread做的一个简单的爬虫,后来学会request后就取代了urllib2 ,这种爬虫很简单,很高效,不过爬取不了一个页面很多的链接,比较死板,不能解析 js,很多ajax链接或者通过 js动态生成的网站就不容易通过程序自动去抓取,需要通过人工去重新写一个爬虫去适配抓取想要抓取的内容,所以这种爬虫肯定是不能拿来做漏扫程序的。作为一个漏扫程序,能解析 js是很有必要的。阅读了解一些资料后,发现 Qtwebkit很适合我们的漏扫程序,当然,谷歌出的HeadlessChrome 也是可以的,等有空打算再研究。Qtwebkit 是安装了Qt后内置的一个封装好的浏览器内核,先上一个官方的文档 http://doc.qt.io/qt-5 ,我们可以通过它实现抓起ajax 的链接,或者某个事件后生成的链接,js动态生成的链接等。

QtWebKit作为爬虫重点的两个类 

想要利用QtWebKit,首先得明白Qtwebkit运行的机制。其中 QWebViewQNetworkAccessManager类尤其重要。QWebView 相当于一个简易的浏览器空间,每次打开Url前要对浏览器进行操作或者打开Url后要对浏览器进行操作都需要重构这个类里面的函数, QNetworkAccessManager是一个网络请求管理类,将我们的浏览器和其绑定后,我们的所有浏览器执行的请求都会被这里类管理,譬如我们需要改造请求头或者获取相应头等操作都需要用到这个类,下面我们详细讲解一下这两个类。

QNetworkAccessManager 类

正如上面所说,要想用这个类首先需要让webkit内核绑定这个类,然后才可以实现请求的监控拦截,和右键审核元素里的网络监控类似。Python 绑定代码为page.setNetworkAccessManager(manager)其中pageQWebPage() 构建的类。

其中进行拦截监控的主要是通过重写QNetworkAccessManager类中的createRequest函数,这个函数原本的功能是创建一个网络请求,我们可以在创建请求前先记录请求的 Url、请求的方式以及请求的数据。不过由于在createRequest函数的时候,还没有获取相应头,如果要特定记录返回状态码为 200 的链接的话就需要另外一个函数——–_finished函数_finished 函数是每一次请求结束的时候调用,传入一个已经具有相应信息的类作为参数,可以通过获取status后进行判断。判断status 的进行记录,可以后期拿来检测 js404漏洞等,还有返回header也需要记录,可以用来进行检测URL 重定向等漏洞。

url = str(reply.url().toString())    
# 获得返回状态
status =reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
status, ok = status.toInt()

Qwebview 类

这个类主要获取是QtWebKit进行解析js 后获取生成的源文件还有在解析前或者解析成功后执行一些 js代码等。不过这个类没有自带的网站访问超时时间,一旦对方响应的时间比较慢,会进入漫长的等待。所以要重写load 函数在其中加上 qtimer 进行定时。load函数是每一次Qwebview 进行加载页面之前就会执行的一个函数。

def load(self,url):    
self.loop = QEventLoop()
    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(self._loadFinished)
    timer.start(self.timeout * 1000)
super(Browser, self).load(url)
self.loop.exec_()
if timer.isActive():
        timer.stop()
else:
print "Request time out:"+ str(url.url().toString())

我们可以在类的定义处加入

self.loadFinished.connect(self._loadFinished)

self.loadStarted.connect(self._loadStarted),

代表QWebKit load成功后就会执行 _loadStatred函数。在成功渲染后会执行_loadFinished函数。再load成功 QWebKit渲染前,我们把confirmalert prompt等可能会导致有窗口弹出导致js代码不继续运行的函数给替换掉。

def _loadStarted(self):    
    frame = self.page().mainFrame()
    frame.evaluateJavaScript("window.alert=function(){}")
    frame.evaluateJavaScript("window.confirm=function(){returntrue}")
    frame.evaluateJavaScript("window.prompt =function(){return 0}")
    frame.evaluateJavaScript("window.open =function(){}")

然后再渲染后写_loadFinshed函数遍历所有的事件,达到抓取一些触发事件后才发起或者生成的链接。首先把a标签里的 href改成onclick 然后再利用js 触发 on事件,这样不会跳转而且可以出发href里面的 js 代码。之后遍历特定可能存在生成或者触发链接的事件后,通过 QNetworkAccessManager类获取事件后发起的网络请求链接存到已爬取的列表。至于源代码中删除的则是通过把渲染后生成的源代码传入 parse 函数进行处理,抓取a 链接进入等待爬起的列表,抓取表单中的action input 的数据,存入已经爬起的表单列表,后续可以进行 csrf测试。

def _loadFinished(self):
   
self.loop.quit()
    frame =
self.page().mainFrame()
   
# 事件先按一遍 然后到parse处理
   
frame.evaluateJavaScript('selectdom=document.querySelectorAll("a");for(var i= 0; i< selectdom.length; i ++){if(!selectdom[i].getAttribute("onclick")){selectdom[i].setAttribute("onclick",selectdom[i].getAttribute("href"))}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onerror]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onerror();}catch(err){continue;}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onchange]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onchange();}catch(err){continue;}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onclick]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onclick();}catch(err){continue;}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onfocus]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onfocus();}catch(err){continue;}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onmouseout]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onmouseout();}catch(err){continue;}}')
    frame.evaluateJavaScript(
'selectdom=document.querySelectorAll("[onmouseover]");for(var i= 0; i< selectdom.length; i ++){try{selectdom[i].onmouseover();}catch(err){continue;}}')

爬虫的去重规则

众所周知,一个爬虫的去重规则越好越不容易爬死,爬取的效率和内容越好。因为很多网站类型其实是一样的,不过Url不一样而已,譬如freebuf 中很多文章,只是文字变了,网站是id变了,做漏扫程序其实是不用重复来爬取的,因为他们后台源码都是同一个。目前我的去重处理如下:

首先把一个Url去除netloc部分后进行正则替换 ,把所有的数字统统都替换成{{{int}}}

之后把query部分取出来,通过{netloc:[{参数 1的名称:[参数1 的内容 , 参数1的内容] ,参数 2的名称:[参数2 的内容 ,]}]}这种结构进行储存,当发现某一个 neiloc

某参数的值种类多达10重之后,这个参数就被认为是无效参数,后续去重时即便这个参数和之前的不同,也会被认定是相同的。

举几个上面规则的例子,譬如某安全网站的的某链接 https://www.anquanke.com/post/id/1 是一种伪静态,被第一步处理之后就变成了 https://www.anquanke.com/post/id/{{{int}}}

第二部后就成了 {‘www.anquanke.com/post/id/{{{int}}}’:[]} 存入内存。

之后继续遇到 https://www.anquanke.com/post/id/1 处理后变成 https://www.anquanke.com/post/id/{{{int}}} 查询到内存中已经有了,于是去除。

再具一个复杂点的例子:譬如百度贴吧的 http://tieba.baidu.com/home/main?un=_0d9y&fr=index&red_tag=p280448481

被第一个规则处理后就变成了 http://tieba.baidu.com/home/main?un=_{{{int}}}d{{{int}}}y&fr=index&red_tag=p{{{int}}}

之后存入结构变成了 {‘tieba.baidu.com/home/main’:[‘un’:[‘{{{int}}}d{{{int}}}y’],’fr’:[‘index’],’red_tag’:[‘p{{{int}}}’]]}

之后如果遇到了 http://tieba.baidu.com/home/main?un=_XXXXX&fr=index&red_tag=p280448481

则会继续爬起 把原来的结构的参数un中加入 _XXXXX值,当爬取发现un参数变化的值高于10个,则下次匹配不会管这个参数。 {‘tieba.baidu.com/home/main’:[‘un’:[‘{{{int}}}d{{{int}}}y’,’XXXXX’],’fr’:[‘index’],’red_tag’:[‘p{{{int}}}’]]}

通过这两个规则可以很有效的过滤一些伪静态,或者是一些un这类的用户昵称参数等,达到有效的去重。

另外一些小心得

Qtwebkit想要更加的高效,可以采取多进程,因为一个page的资源是不可以共享的,所以不可以多线程。在链接爬取的过程中,建议爬取表单可以把提交按钮是什么存起来,后期做 csrf会用到,还有把爬到的链接是什么请求,请求的内容,请求后的状态码和Cookie给记录一下,后期检测sql 注入,xssjson劫持,404js ,重定向等漏洞都是会用到的。还有请求的User-Agent的值最好在QNetworkAccessManager类中进行随机生成,避免一些防爬策略。

*本文作者:DX安全团队—-0d9y,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。 

未经允许不得转载:安全路透社 » QtWebKit爬虫与去重规则

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册