Skip to content

PoC Base Class

In order to simplify the writing of PoC scripts, Pocsuite3 implements the PoC base class: POCBase, where many common code can be placed. When writing PoC, we only need to inherit the base class, and write the core code of the Vulnerability. The commonly used properties and methods are as follows:


Common properties:

self.url # target url
self.scheme # the protocol of the target url
self.rhost # hostname of the target url
self.rport # port of the target url
self.host_ip # WAN ip address

Common method:

self._check()

# Get the value of the custom command line parameter
self.get_option('key')

# The method to return the result, the parameter is a dictionary,
# it is recommended to use this method to return the result
self.parse_output({})

The self._check() method code is as follows, it will perform port opening check, http/https protocol automatic correction, home page keyword check, honeypot check. It can avoid sending Payload to the honeypot, reducing false positives.

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.