【CTF】广度搜索的 BeautifulSoup 网站爬虫

37次阅读

共计 1973 个字符,预计需要花费 5 分钟才能阅读完成。

本人习惯使用 pyhton2 进行编程,因此 beautifulsoup 也是使用 python2 版本的,但据说 python2 明年就要停止支持了,忧伤得很。。。
0x01 题目要求
如图所示,在页面源码当中找到 5 个 flag,然后拼接起来,还给了 flagA 的示例。

flagA:

打开站点是一个 ctf-wiki 的 demo 站点,了解这个站的人应该都知道它的体量,所以手动一个个找是不现实的,需要用到爬虫了(题目名称也暗示了)。
0x02 解题思路
我考虑使用广度优先搜索(BFS)实现一个网站爬虫,不了解广度搜索的童鞋可以自行百度。具体实现方法如下:

建立待请求链接 visiting_urls 和已请求链接 visited_urls 的 2 个列表(也可看做队列)
从 visiting_urls 取出一条链接,使用 requrests.get 请求页面源码
在源码中正则匹配 flag 字段

beautifulsoup 获取页面中所有的 a 标签,符合要求的加入 visiting_urls

visiting_urls 不为空,则执行 [2]

当中需要考虑 2 个问题:

去重问题:当爬取链接时,难免会遇到存在不同位置的 url 指向同一个页面,爬取时不需要再请求相同页面,因此要对爬取到的 url 进行去重。方法如下:

维护 visiting_urls visited_urls 列表,比对爬取 url 与已爬取过的 url 是否重复;
根据 mkdocs 网站 url 特点,包含 ”../” 的是回溯链接,此类链接不需要再次请求。

正则匹配问题:这个方面没有多想,写个能使用的正则匹配规则就行,在本题中需要 2 种正则匹配:

匹配 flag:flag[ABCDE],我的目的是匹配到 flag 的标志,而不是把 flag 整个都匹配出来,因为我不清楚 flag 当中有没有其他奇怪字符,防止出现漏匹配的情况;
匹配 url:[\w\/]+index\.html,目的是匹配路径为字母数字(不包含 ”..”)且末尾是 ”index.html” 的 url。

到此,整个任务就完成了。
0x03 完整脚本
#coding=utf-8
import requests,re
from bs4 import BeautifulSoup

s = requests.session()
s.keep_alive=False

flagre = re.compile(‘flag[ABCDE]’)
urlre = re.compile(‘[\w\/]+index\.html’)

base_url = ‘http://23.236.125.55:1000/ctf-wiki/’
flagA_url = ‘http://23.236.125.55:1000/ctf-wiki/assembly/mips/readme/index.html’

visiting_urls = [‘http://23.236.125.55:1000/ctf-wiki/index.html’]
visited_urls = []

def find_flag(url,html):
flist = flagre.findall(html)
if len(flist) > 0:
print flist,url

def BFS():
url = visiting_urls[0]
del(visiting_urls[0])
visited_urls.append(url)
r = s.get(url)
#r.encoding = ‘utf-8′
find_flag(url,r.text)
soup = BeautifulSoup(r.text,’lxml’)
for a in soup.find_all(‘a’):
link = a[‘href’]
if urlre.findall(link) and “..” not in link:
new_url = base_url + link
if new_url not in visited_urls and new_url not in visiting_urls:
visiting_urls.append(new_url)

if __name__ == ‘__main__’:
while len(visiting_urls) > 0:
BFS()

上面思路已经提到了,该脚本只能提取到包含 flag 标志的页面,而不是 flag 本身,因此还需要手动访问这些页面去寻找 flag(手动狗头),如果还想直接显示 flag,那就需要优化一下正则匹配了。
提示一点,在获取到页面源码后,使用 r.encoding = ‘utf-8’ 转码会导致 EOFError,具体原因不详,本想能够匹配中文页面,结果画蛇添足搞了半天以为匹配没成功。提示两点,requests.session() 的好处,相较于直接 requests.get(),可以防止建立过多的 HTTP 连接,导致新连接无法建立的问题。参考页面:https://segmentfault.com/q/10…

执行效果如下:

最后拼接一下,完事了。

正文完
 0