404页面识别
前言
最近在挖洞前做资产收集的时跑了一波子域名,但是目前很多子域名挖掘机挖出来资产还是存在很多水分,准确率一般;于是乎写了个脚本用是否能请求成功作为筛选条件进行第一轮筛选,本以为这样就做好子域名收集工作,但是发现不少子域名居然是404页面,没有什么用武之地。于是乎打算再写一个404页面识别过滤掉域名中所有没有用处的404。
404页面识别思路
在编写之前先看一下目前网站404的呈现有哪些方式
-
web容器设置404错误页面,服务端返回404状态码
例如freebuf,当我们随便访问一个不存在的页面,返回的404页面,此时状态码返回了404。
这种情况下,可以根据返回response的状态码来直接判断是不是404页面。
-
将404错误页面指向一个新的页面,页面上显示404信息,但是此时状态码并不为404,一般返回状态码有301、302,或者直接返回状态码为200的错误页面。
比如Baidu,当我们访问一个不存在的页面,会返回https://www.baidu.com/search/error。
根据以上这2种情况,大致得出404页面的识别思路。
-
从状态码是否为404判断
-
获取域名的404页面,然后判断请求的页面和404页面是否相似,相似则可以判断为404页面。
404页面过滤
页面相似度
根据上面提到的识别思路,首先要解决的第一个问题是页面相似度
的判断。如何判断两个页面的相似度呢?
这里使用hashes.simhash
,对两个页面的body计算hash值,再调用similarity
获取两个页面的相似值,自定义一个阀值作为标准判断是否相似,radio
可以根据具体情况调整。
from hashes.simhash import simhash
def is_similar_page(res1, res2, radio=0.85):
if res1 is None or res2 is None:
return False
body1 = res1.body
body2 = res2.body
url1 = res1.get_url()
url2 = res2.get_url()
simhash1 = simhash(body1.decode('utf-8'))
simhash2 = simhash(body2.decode('utf-8'))
calc_radio = simhash1.similarity(simhash2)
# print("[%s]与[%s]两个页面的相似度为:%s" % (url1, url2, calc_radio))
if calc_radio >= radio:
return True
else:
return False
构造404页面
这里很简单,访问一个随机生成字符串为后缀的页面。
def generate_404_kb(self, url):
# 获取URL的拓展名
domain = url.get_domain() #www.freebuf.com
domain_path = url.get_domain_path() #https://www.freebuf.com
rand_file = rand_letters(8) + '.html'
url_404 = domain_path.urljoin(rand_file)
resp_200 = requests.get(domain_path)
resp_404 = requests.get(url_404)
# 有些网站做了容错处理,并不会直接返回404,而会返回当前页面
if is_similar_page(resp_200, resp_404):
pass
else:
self._404_already_domain.append(domain)
self._404_kb.append((domain, resp_404))
整体思路
class page_404:
_instance = None
def __init__(self):
self._404_already_domain = []
self._404_kb = []
# 根据301、302跳转或状态码为200的404页面
self._404_code_list = [200, 301, 302]
def generate_404_kb(self, url):
# 获取URL的拓展名
domain = url.get_domain()
domain_path = url.get_domain_path()
rand_file = rand_letters(8) + '.html'
url_404 = domain_path.urljoin(rand_file)
resp_200 = requests.get(domain_path)
resp_404 = requests.get(url_404)
# 有些网站做了容错处理,并不会直接返回404,而会返回当前页面
if is_similar_page(resp_200, resp_404):
pass
else:
self._404_already_domain.append(domain)
self._404_kb.append((domain, resp_404))
def set_check(self):
self._404_kb = []
self._404_checked = False
def is_404(self, http_response):
code = http_response.get_code()
url = http_response.get_url()
domain = url.get_domain()
if domain not in self._404_already_domain:
self.generate_404_kb(url)
# 如果状态码为404直接返回
if code == 404:
return True
if code in self._404_code_list:
for domain_404, resp_404 in self._404_kb:
# 判断域名是否一样
if domain == domain_404:
if is_similar_page(http_response, resp_404):
return True
return False
def is_404(http_response):
if page_404._instance is None:
page_404._instance = page_404()
return page_404._instance.is_404(http_response)
荒废的域名
本来脚本过滤写到这里应该就差不多了,但是今天测试的时候还是发现了一个bug。当域名本身无效,即访问domain本身也会跳转到一个404页面,这时候情况就有点不一样了。
比如直接访问http://e.apptaxi.com.cn,会发生302跳转到定义好的404页面https://img-ys011.didistatic.com/static/dfe_default_page/index.html,但是直接访问https://img-ys011.didistatic.com,仍然会跳转到404页面[https://img-ys011.didistatic.com/static/dfe_default_page/index.html]。
仔细看看上面构造404页面
部分
resp_200 = requests.get(domain_path)
resp_404 = requests.get(url_404)
# 有些网站做了容错处理,并不会直接返回404,而会返回当前页面
if is_similar_page(resp_200, resp_404):
pass
我理所当然认为直接访问https://www.domain.com必定是一个200页面,即正常的页面。但是一般来说子域名挖掘机挖出来的不少子域名可能是无效的,或者是直接301、302跳转到404页面。这时候本身resp_200
就不是正常的页面,所以不能当作识别404页面的标准。
这里我的思路是,假若resp_200
本身就是一个404页面
的话,那么它和随机生成的resp_404
页面相似度radio
可能大于0.95甚至相似度为1。所以这种情况分开做判断。
def generate_404_kb(self, url):
# 获取URL的拓展名
domain = url.get_domain() #www.freebuf.com
domain_path = url.get_domain_path() #https://www.freebuf.com
rand_file = rand_letters(8) + '.html'
url_404 = domain_path.urljoin(rand_file)
resp_200 = requests.get(domain_path)
resp_404 = requests.get(url_404)
# 有些网站做了容错处理,并不会直接返回404,而会返回当前页面
if is_similar_page(resp_200, resp_404):
# 如果相似度等于1的话,证明本身domain页面也是404页面
if is_similar_page(resp_200, resp_404, 1):
self._404_already_domain.append(domain)
self._404_kb.append((domain, resp_200))
pass
else:
self._404_already_domain.append(domain)
self._404_kb.append((domain, resp_404))
后记
其实整个404页面识别过程中,我认为最难解决的是页面相似度
判断这一个问题,这里简单的用response_body
来计算hash
并根据一个大概的radio
来判断,然后根据具体网站的404的情况再制定好判断逻辑就好了。
另外文章中的代码是从自己的项目中截取出来,可能不能直接执行,但是大致已经提供了一个思路。师傅们有什么指点请问问指教。
参考《白帽子将web扫描》
来源:https://xz.aliyun.com/ 感谢【Bojack 】
原文始发于微信公众号(衡阳信安):404页面识别
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论