def get_bwapp_cookies(cont_ip): """Log in to bWAPP and return valid cookie.""" install_url = 'http://' + cont_ip + '/install.php?install=yes' helper.HTTPSession(install_url) login_url = 'http://' + cont_ip + '/login.php' http_session = helper.HTTPSession(login_url) http_session.data = 'login=bee&password=bug&security_level=0&form=submit' successful_text = 'Welcome Bee' http_session.formauth_by_response(successful_text) if not http_session.is_auth: return {} return http_session.cookies
def has_breach(site: str, port: int = PORT) -> bool: """ Check if BREACH is present. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ url = 'https://{}:{}'.format(site, port) common_compressors = [ 'compress', 'exi', 'gzip', 'identity', 'pack200-gzip', 'br', 'bzip2', 'lzma', 'peerdist', 'sdch', 'xpress', 'xz' ] for compression in common_compressors: header = {'Accept-Encoding': '{},deflate'.format(compression)} try: sess = http.HTTPSession(url, headers=header) fingerprint = sess.get_fingerprint() if 'Content-Encoding' in sess.response.headers: if compression in sess.response.headers['Content-Encoding']: show_open('Site vulnerable to BREACH attack', details=dict(site=site, port=port, compression=compression, fingerprint=fingerprint)) return True except http.ConnError as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return False show_close('Site not vulnerable to BREACH attack', details=dict(site=site, port=port)) return False
def get_vulns_ossindex(package_manager: str, package: str, version: str) -> tuple: """ Search vulnerabilities on given package_manager/package/version. :param package_manager: Package manager. :param package: Package name. :param version: Package version. """ if version: url = 'https://ossindex.net/v2.0/package/{}/{}/{}'.format( _url_encode(package_manager), _url_encode(package), _url_encode(version)) else: url = 'https://ossindex.net/v2.0/package/{}/{}'.format( _url_encode(package_manager), _url_encode(package)) try: sess = http.HTTPSession(url) resp = json.loads(sess.response.text)[0] vuln_titles = tuple() if resp['id'] == 0: return vuln_titles if int(resp['vulnerability-matches']) > 0: vulns = resp['vulnerabilities'] vuln_titles = tuple([x['title'], ", ".join(x['versions'])] for x in vulns) vuln_titles = tuple(reduce( lambda l, x: l.append(x) or l if x not in l else l, vuln_titles, [])) return vuln_titles except http.ConnError: raise ConnError
def is_insecure_in_url(image_url: str, expected_text: str, *args, **kwargs) -> bool: r""" Check if the image in the URL is an insecure CAPTCHA. The check is performed by converting the image to text and comparing with the given expected text. :param image_url: Path to the image to be tested. :param expected_text: Text the image might contain. :param \*args: Optional positional arguments for :class:`~fluidasserts.helper.http.HTTPSession`. :param \*\*kwargs: Optional keyword arguments for :class:`~fluidasserts.helper.http.HTTPSession`. """ session = http.HTTPSession(image_url, stream=True, *args, **kwargs) fingerprint = session.get_fingerprint() image = session.response.raw result = pytesseract.image_to_string(Image.open(image)) if result == expected_text: show_open('Captcha is insecure', details=dict(expected=expected_text, reversed=result, fingerprint=fingerprint)) return True show_close('Captcha is secure', details=dict(expected=expected_text, reversed=result, fingerprint=fingerprint)) return False
def accepts_insecure_accept_header(url: str, *args, **kwargs) -> bool: r""" Check if given URL accepts insecure Accept request header value. :param url: URL to test. :param \*args: Optional arguments for :class:`HTTPSession`. :param \*\*kwargs: Optional arguments for :class:`HTTPSession`. """ expected_codes = [406, 415] if 'headers' in kwargs: kwargs['headers'].update({'Accept': '*/*'}) elif kwargs: kwargs['headers'] = {'Accept': '*/*'} else: kwargs = {'headers': {'Accept': '*/*'}} try: session = http.HTTPSession(url, *args, **kwargs) except http.ConnError as exc: show_unknown('URL {} returned error'.format(url), details=dict(error=str(exc).replace(':', ','))) return False if session.response.status_code not in expected_codes: show_open( 'URL {} accepts insecure Accept request header value'.format(url)) return True show_close( 'URL {} rejects insecure Accept request header value'.format(url)) return False
def test_has_not_secure_in_cookiejar_close(): """Cookiejar has secure attribute?.""" url = '%s/http/cookies/secure/ok' % (MOCK_SERVICE) cookie_name = 'JSESSID' sess = http.HTTPSession(url) assert not cookie.has_not_secure_in_cookiejar(cookie_name, sess.cookies) assert not cookie.has_not_secure_in_cookiejar(cookie_name, None) assert not cookie.has_not_secure_in_cookiejar(None, sess.cookies)
def is_header_hsts_missing(url: str, *args, **kwargs) -> bool: r""" Check if Strict-Transport-Security HTTP header is properly set. :param url: URL to test. :param \*args: Optional arguments for :class:`.HTTPSession`. :param \*\*kwargs: Optional arguments for :class:`.HTTPSession`. """ result = True try: http_session = http.HTTPSession(url, *args, **kwargs) headers_info = http_session.response.headers fingerprint = http_session.get_fingerprint() except http.ConnError as exc: show_unknown('Could not connnect', details=dict(url=url, error=str(exc).replace(':', ','))) return False except http.ParameterError as exc: show_unknown('An invalid parameter was passed', details=dict(url=url, error=str(exc).replace(':', ','))) return False header = 'Strict-Transport-Security' if header in headers_info: value = headers_info[header] if re.match(HDR_RGX[header.lower()], value, re.IGNORECASE): hdr_attrs = value.split(';') max_age = list(filter(lambda x: x.startswith('max-age'), hdr_attrs))[0] max_age_val = max_age.split('=')[1] if int(max_age_val) >= 31536000: show_close('HTTP header {} is secure'.format(header), details=dict(url=url, header=header, value=value, fingerprint=fingerprint)) result = False else: show_open('{} HTTP header is insecure'.format(header), details=dict(url=url, header=header, value=value, fingerprint=fingerprint)) result = True else: show_open('{} HTTP header is insecure'.format(header), details=dict(url=url, header=header, value=value, fingerprint=fingerprint)) result = True return result
def polycom_phone_has_default_credentials(hostname: str, proto: str = 'https', port: int = '443', password: str = '456') -> bool: """ Check if Polycom SoundStation IP 6000 has default credentials. :param hostname: IP or host of phone. :param password: Default password. """ try: url = '{}://{}:{}/login.htm'.format(proto, hostname, port) sess = http.HTTPSession(url) if 'Polycom Web Configuration Utility' not in sess.response.text: show_unknown('Resources not found. Is it a valid phone version?', details=dict(host=hostname, url=url, status_code=sess.response.status_code)) return False creds = 'Polycom:{}'.format(password) encoded = base64.b64encode(creds.encode()) sess.headers.update({'X-Requested-With': 'XMLHttpRequest'}) sess.headers.update({'Authorization': 'Basic {}' .format(encoded.decode())}) sess.url = '{}://{}:{}/auth.htm?\ t=Tue,%2020%20Nov%202018%2019:48:43%20GMT'.format(proto, hostname, port) sess.do_request() except http.ConnError as exc: show_unknown('Could not connect', details=dict(hostname=hostname, url=url, reason=str(exc).replace(':', ','))) return False expected = "SoundStation IP 6000" if sess.response.status_code > 401: show_unknown('Resources not found. Is it a valid phone version?', details=dict(host=hostname, url=url, status_code=sess.response.status_code)) return False if expected in sess.response.text: show_open('Phone has default credentials', details=dict(host=hostname, username='******', password=password)) result = True else: show_close('Phone has not default credentials', details=dict(host=hostname, username='******', password=password)) result = False return result
def has_access(url: str, *args, **kwargs) -> bool: r""" Check if HTTP access to given URL is possible (i.e. response 200 OK). :param url: URL to test. :param \*args: Optional arguments for :class:`HTTPSession`. :param \*\*kwargs: Optional arguments for :class:`HTTPSession`. """ http_session = http.HTTPSession(url, *args, **kwargs) ok_access_list = [200, 202, 204, 301, 302, 307] if http_session.response.status_code in ok_access_list: show_open('Access available to {}'.format(url)) return True show_close('Access not available to {}'.format(url)) return False
def unify_phone_has_default_credentials(hostname: str, proto: str = 'https', port: int = '443', password: str = '123456') -> bool: """ Check if Unify OpenScape Desk Phone IP 55G has default credentials. :param hostname: IP or host of phone. :param password: Default password. """ try: url = '{}://{}:{}/index.cmd?user=Admin'.format(proto, hostname, port) sess = http.HTTPSession(url) if 'OpenScape Desk Phone IP Admin' not in sess.response.text: show_unknown('Resources not found. Is it a valid phone version?', details=dict(host=hostname, url=url, status_code=sess.response.status_code)) return False sess.data = 'page_submit=WEBMp_Admin_Login&lang=es&AdminPassword={}'\ .format(password) sess.url = '{}://{}:{}/page.cmd'.format(proto, hostname, port) sess.do_request() except http.ConnError as exc: show_unknown('Could not connect', details=dict(hostname=hostname, url=url, reason=str(exc).replace(':', ','))) return False failed = "action='./page.cmd'" if sess.response.status_code > 400: show_unknown('Resources not found. Is it a valid phone version?', details=dict(host=hostname, url=url, status_code=sess.response.status_code)) return False if failed not in sess.response.text: show_open('Phone has default credentials', details=dict(host=hostname, username='******', password=password)) result = True else: show_close('Phone has not default credentials', details=dict(host=hostname, username='******', password=password)) result = False return result
def _has_not_same_site(cookie_name: str, url: Optional[str], cookie_jar: Optional[dict], *args, **kwargs) -> bool: r""" Check if a cookie has the ``samesite`` attribute. :param cookie_name: Name of the cookie to test. Exactly one of the following has to be ``None``. :param url: URL to get cookies. :param cookie_jar: Dict-like collection of cookies as returned by ``requests.cookies``. :param \*args: Optional positional arguments for :class:`~fluidasserts.helper.http.HTTPSession`. :param \*\*kwargs: Optional keyword arguments for :class:`~fluidasserts.helper.http.HTTPSession`. """ fingerprint = None if url is not None: try: sess = http.HTTPSession(url, *args, **kwargs) cookielist = sess.cookies fingerprint = sess.get_fingerprint() except http.ConnError: show_unknown('Could not connect', details=dict(url=url)) return False else: cookielist = cookie_jar if cookielist is None: show_unknown('{} Cookies not present'.format(cookie_name), details=dict(url=url, fingerprint=fingerprint)) return False for cookie in cookielist: if cookie.name == cookie_name: if cookie.has_nonstandard_attr('SameSite'): if cookie.get_nonstandard_attr('SameSite') == 'Strict': show_close('SameSite is set to Strict', details=dict(url=url, cookie=cookie_name, fingerprint=fingerprint)) return False show_open('Cookie SameSite not present or Lax', details=dict(url=url, cookie=cookie_name, fingerprint=fingerprint)) return True show_unknown('Cookie "{}" not found'.format(cookie_name), details=dict(url=url, fingerprint=fingerprint)) return False
def get_vulns_snyk(package_manager: str, package: str, version: str) -> tuple: """ Search vulnerabilities on given package_manager/package/version. :param package_manager: Package manager. :param package: Package name. :param version: Package version. """ if version: url = 'https://snyk.io/vuln/{}:{}@{}'.format( _url_encode(package_manager), _url_encode(package), _url_encode(version)) else: url = 'https://snyk.io/vuln/{}:{}'.format( _url_encode(package_manager), _url_encode(package)) try: sess = http.HTTPSession(url, timeout=20) return _parse_snyk_vulns(sess.response.text) except http.ConnError: raise ConnError
def accepts_empty_content_type(url: str, *args, **kwargs) -> bool: r""" Check if given URL accepts empty Content-Type requests. :param url: URL to test. :param \*args: Optional arguments for :class:`HTTPSession`. :param \*\*kwargs: Optional arguments for :class:`HTTPSession`. """ if 'headers' in kwargs: if 'Content-Type' in kwargs['headers']: kwargs['headers'].pop('Content-Type', None) expected_codes = [406, 415] try: session = http.HTTPSession(url, *args, **kwargs) except http.ConnError as exc: show_unknown('URL {} returned error'.format(url), details=dict(error=str(exc).replace(':', ','))) return False if session.response.status_code not in expected_codes: show_open('URL {} accepts empty Content-Type requests'.format(url)) return True show_close('URL {} rejects empty Content-Type requests'.format(url)) return False
def __init__(self, url) -> None: """Build a new Service object.""" self.url = url self.sess = http.HTTPSession(self.url)
def test_has_not_httponly_in_cookiejar_open(): """Cookiejar has http-only attribute?.""" url = '%s/http/cookies/http_only/fail' % (MOCK_SERVICE) cookie_name = 'JSESSID' sess = http.HTTPSession(url) assert cookie.has_not_httponly_in_cookiejar(cookie_name, sess.cookies)