php多线程抓取多个网页(小结(一)并发的划分)
优采云 发布时间: 2021-11-20 16:23php多线程抓取多个网页(小结(一)并发的划分)
本节试图解释更深层次的东西。
sqlmap 在哪里使用多线程
多线程并发可以加速IO密集型操作,但是sqlmap在哪里使用多线程呢?是时候进行测试了吗?是时候查表了,还是……?带着这个问题,接下来进行分析。
sqlmap的一些技术细节(1)已经分析了多线程是如何实现的,并将线程封装到函数runThreads中,所以只需要全局搜索这个函数就可以找到使用多线程的地方。
按场景分,一共发现了三个地方,分别用于注入、爆破、爬行,所以下面三个总结介绍一下它的作用。
爆破
在文件位置 lib/utils/brute.py 中找到两个地方
根据函数名,可以猜测主要是爆表和爆表中的字段。具体实现比较简单。每个线程都会从字典中获取一个内容来运行。字典是基于文件的。另外提一下是不是检测表还是检测字段,sqlmap也会把原网页中的一些字段设置为字典。具体请看下面的函数实现。
def getPageWordSet(page):
"""
Returns word set used in page content
>>> sorted(getPageWordSet(u'foobartest'))
[u'foobar', u'test']
"""
retVal = set()
# only if the page's charset has been successfully identified
if isinstance(page, unicode):
retVal = set(_.group(0) for _ in re.finditer(r"\w+", getFilteredPageContent(page)))
return retVal
爬虫
爬虫在爬取和采集 URL 时也是多线程的。sqlmap 定义了一个未处理的集合集合,多个线程从中弹出数据。
参考sqlmap的技术细节(1)中的线程设计章节,sqlmap在底层实现了线程安全,所以这些细节不需要考虑。主要的爬取规则也是基于BeautifulSoup寻找标签。 ,判断URL满足条件的规则是检查同一个Host。
def checkSameHost(*urls):
"""
Returns True if all provided urls share that same host
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target.com/images/page2.php')
True
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target2.com/images/page2.php')
False
"""
if not urls:
return None
elif len(urls) == 1:
return True
else:
return all(re.sub(r"(?i)\Awww\.", "", urlparse.urlparse(url or "").netloc.split(':')[0]) == re.sub(r"(?i)\Awww\.", "", urlparse.urlparse(urls[0] or "").netloc.split(':')[0]) for url in urls[1:])
注射
注入中多线程的目的,绝对是为了加快数据的读取速度。比如我们需要读取一个表的内容,每次请求只能返回一行数据,所以需要根据这个取出长度然后在多线程中运行,sqlmap的设计是每个线程被分配一个 id 来检索相应行的内容。
一个简单的理解就是你需要在一个表中获取100条数据。在SQL注入语句中,需要一直使用limit 1,1、limit 2,1、..., limit 100,1 来限制读取哪一个。如果在sqlmap中,会先生成一个1到100的列表,每个线程都会从中取出一个num(这些都是线程安全的),在返回数据的时候,会按照num的顺序插入到结果中,所以你最后看到的结果也会是连续的。
顺便说一句,sqlmap 会“聪明地”分配线程数。比如在这个例子中,sqlmap会将100与设定的线程数进行比较(默认为1),取最小者(非常聪明)。
页面相似度比较
前面说过,在做布尔盲注时,如何快速判断就是通过两个网页的相似度来判断。这种匹配方式在sqlmap,格式塔模式匹配(Ratcliff-Obershel palgorithm)中也有提到,它的定义是通过谷歌得到的
Ratcliff/Obershelp 模式识别
(算法)
定义:计算两个字符串的相似度,即匹配的字符数除以两个字符串的总字符数。匹配字符是最长公共子序列中的字符加上递归匹配的最长公共子序列两侧的非匹配区域中的字符。
更完美的是,python自带的库difflib/SequenceMatcher实现了这个算法。
搜索关键词seqMatcher 可以找到几个比较的例子。
检测页面的动态内容
这个标题其实就是这个函数的字面意思。
def checkDynamicContent(firstPage, secondPage):
"""
This function checks for the dynamic content in the provided pages
"""
pass
很明显,作用是比较两个页面的相似度。当您真正查看代码时,您会发现它并不止于此。
首先,如果其中一页的长度超过下面定义的值,就会很清楚
# For preventing MemoryError exceptions (caused when using large sequences in difflib.SequenceMatcher)
MAX_DIFFLIB_SEQUENCE_LENGTH = 10 * 1024 * 1024
不会有比较。
其次,如果相似度小于0.98(UPPER_RATIO_BOUND = 0.98),sqlmap会查找两个页面的动态内容,找到前半部分和后半部分由两个动态内容共享的一半。在下一次请求中,中间的动态内容会被删除,然后继续比较,直到相似度大于0.98。当然,这个过滤也是有次数限制,超出次数不能使用此方法。“检测页面动态内容”主要用于“检测页面稳定性”的过程中,“检测页面稳定性”是sqlmap检查注入前的一个过程,通过访问同一时间段内两次相同的网页,如果相似度差异比较大,则通过“检测页面动态内容”。所以这个功能不是在注入中使用,而是在注入前“检测页面稳定性”。如何使用sqlmap查找动态内容?
上面说的
sqlmap会查找两个页面的动态内容,找到两个动态内容共享的前半部分和后半部分
所以位于findDynamicContent函数中,这个函数的一个例子可以说明一切
>>> findDynamicContent("Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.", "Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.")
>>> kb.dynamicMarkings
[('natum reque et per. ', 'Facer tritani repreh')]
具体算法懒得看。可想而知,动态值前后不断逼近,取20个字符作为最终结果。
实际注射中的相似性检查
以上相似性判断都是“前戏”,在实际注射过程中并没有发挥作用。在实际注入过程中,为了获取相似度,会去除页面的动态内容(稳定性检测中发现的)、html标签等。后者与之前相同,取值称为seqMatcher。
空连接
自动错误报告机制
当出现未知错误时,sqlmap会使用帐号sqlmapreport自动提交问题到github。代码在 common.py 下的 createGithubIssue 函数中。这是值得学习的。主要通过github API来实现。
提交的时候会寻找已经提交过的类似问题,这也是一个不错的设计。
总结
看sqlmap的代码很痛苦。这篇文章憋了两周,看了无数个“头部结缔组织”才明白sqlmap的大致含义。
sqlmap的代码耦合度太高了,但是一般我们说代码耦合度越低越好。sqlmap源码值得学习,但是这种耦合一定不要学。. -=
参考
/p/44157153