Skip to content

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

Released under the GPLv2 License.