PoC 插件基类
为了简化 PoC 插件的编写,Pocsuite3 实现了 PoC 基类:POCBase
,很多共用的代码片段都可以放到此基类中。我们编写 PoC 时,只需要继承该基类就可,比较常用的属性和方法如下:
常用属性:
self.url # 目标 url
self.scheme # 目标 url 的协议
self.rhost # 目标 url 的主机名
self.rport # 目标 url 的端口
self.host_ip # 本机的 wan 口 ip
常用方法:
self._check()
self.get_option('key') # 获取自定义命令行参数的值
self.parse_output({}) # 返回结果的方法,参数是一个字典,建议统一使用该方法返回结果
self._check()
方法代码如下,会进行端口开放检查、http/https 协议自动纠正,首页关键词 check,关键词蜜罐检查等功能。可以一定程度避免将 Payload
发送到蜜罐,减少误报。
def _check(self, dork='', allow_redirects=False, return_obj=False, is_http=True, honeypot_check=True):
if conf.get('no_check', False):
return True
u = urlparse(self.url)
# the port closed
if u.port and not check_port(u.hostname, u.port):
logger.debug(f'{mosaic(self.url)}, the port is closed.')
return False
if is_http is False or self.current_protocol != POC_CATEGORY.PROTOCOL.HTTP:
return True
res = None
# this only covers most cases
redirect_https_keyword = [
# https://www.zoomeye.org/searchResult?q=%22request%20was%20sent%20to%20HTTPS%20port%22
'request was sent to https port',
# https://www.zoomeye.org/searchResult?q=%22running%20in%20SSL%20mode.%20Try%22
'running in ssl mode. try'
]
redirect_https_keyword_found = False
origin_url = self.url
netloc = self.url.split('://', 1)[-1]
urls = OrderedSet()
urls.add(self.url)
urls.add(f'http://{netloc}')
# The user has not provided a port in URL, dynamically switching to HTTPS's default port 443
pr = urlparse(self.url)
is_ipv6 = pr.netloc.startswith('[')
if ':' not in self.target.split('://')[-1] and pr.port == 80:
pr = pr._replace(scheme='https')
pr = pr._replace(netloc=f'[{pr.hostname}]:443' if is_ipv6 else f'{pr.hostname}:443')
urls.add(pr.geturl())
else:
urls.add(f'https://{netloc}')
for url in urls:
try:
time.sleep(0.1)
res = requests.get(url, allow_redirects=allow_redirects)
"""
https://github.com/knownsec/pocsuite3/issues/330
https://github.com/knownsec/pocsuite3/issues/356
status_code:
- 20x
- 30x
- 40x
- 50x
"""
# if HTTPS handshake is successful, return directly
if url.startswith('https://'):
break
# if we send an HTTP request to an HTTPS service, but the server may return 20x
for k in redirect_https_keyword:
if k.lower() in res.text.lower():
redirect_https_keyword_found = True
break
if redirect_https_keyword_found:
continue
# if we send an HTTP request to an HTTPS service, the server may return 30x, 40x, or 50x...
if not str(res.status_code).startswith('20'):
continue
break
except requests.RequestException:
pass
if not isinstance(res, requests.Response):
return False
self.url = res.request.url.rstrip('/')
if res.history:
self.url = res.history[0].request.url.rstrip('/')
if self.url.split('://')[0] != self.scheme:
self.url = self.build_url(self.url)
logger.warn(f'auto correct url: {mosaic(origin_url)} -> {mosaic(self.url)}')
if return_obj:
return res
content = str(res.headers).lower() + res.text.lower()
dork = dork.lower()
if dork not in content:
return False
if not honeypot_check:
return True
is_honeypot = False
# detect honeypot
# https://www.zoomeye.org/searchResult?q=%22GoAhead-Webs%22%20%2B%22Apache-Coyote%22
keyword = [
'goahead-webs',
'apache-coyote',
'upnp/',
'openresty',
'tomcat'
]
sin = 0
for k in keyword:
if k in content:
sin += 1
if sin >= 3:
logger.debug(f'honeypot: sin({sin}) >= 3')
is_honeypot = True
# maybe some false positives
elif len(re.findall('<title>(.*)</title>', content)) > 5:
logger.debug('honeypot: too many title')
is_honeypot = True
elif len(re.findall('basic realm=', content)) > 5:
logger.debug('honeypot: too many www-auth')
is_honeypot = True
elif len(re.findall('server: ', content)) > 5:
logger.debug('honeypot: too many server')
is_honeypot = True
if is_honeypot:
logger.warn(f'{mosaic(self.url)} is a honeypot.')
return not is_honeypot