Beispiel #1
0
    def __init__(self,
                 http_version,
                 status_code,
                 reason=None,
                 headers=(),
                 content=None,
                 trailers=None,
                 timestamp_start=None,
                 timestamp_end=None):
        if isinstance(http_version, str):
            http_version = http_version.encode("ascii", "strict")
        if isinstance(reason, str):
            reason = reason.encode("ascii", "strict")
        if not isinstance(headers, nheaders.Headers):
            headers = nheaders.Headers(headers)
        if isinstance(content, str):
            raise ValueError("Content must be bytes, not {}".format(
                type(content).__name__))
        if trailers is not None and not isinstance(trailers, nheaders.Headers):
            trailers = nheaders.Headers(trailers)

        self.http_version = http_version
        self.status_code = status_code
        self.reason = reason
        self.headers = headers
        self.content = content
        self.trailers = trailers
        self.timestamp_start = timestamp_start
        self.timestamp_end = timestamp_end
Beispiel #2
0
    def make(cls,
             method: str,
             url: str,
             content: Union[bytes, str] = "",
             headers: Union[Dict[str, AnyStr], Iterable[Tuple[bytes,
                                                              bytes]]] = ()):
        """
        Simplified API for creating request objects.
        """
        req = cls("absolute", method, "", "", "", "", "HTTP/1.1", (), b"")

        req.url = url

        # Headers can be list or dict, we differentiate here.
        if isinstance(headers, dict):
            req.headers = nheaders.Headers(**headers)
        elif isinstance(headers, Iterable):
            req.headers = nheaders.Headers(headers)
        else:
            raise TypeError(
                "Expected headers to be an iterable or dict, but is {}.".
                format(type(headers).__name__))

        # Assign this manually to update the content-length header.
        if isinstance(content, bytes):
            req.content = content
        elif isinstance(content, str):
            req.text = content
        else:
            raise TypeError(
                "Expected content to be str or bytes, but is {}.".format(
                    type(content).__name__))

        return req
Beispiel #3
0
    def make(cls,
             status_code: int = 200,
             content: Union[bytes, str] = b"",
             headers: Union[Dict[str, AnyStr], Iterable[Tuple[bytes,
                                                              bytes]]] = ()):
        """
        Simplified API for creating response objects.
        """
        resp = cls(b"HTTP/1.1", status_code,
                   status_codes.RESPONSES.get(status_code, "").encode(), (),
                   None)

        # Headers can be list or dict, we differentiate here.
        if isinstance(headers, dict):
            resp.headers = nheaders.Headers(**headers)
        elif isinstance(headers, Iterable):
            resp.headers = nheaders.Headers(headers)
        else:
            raise TypeError(
                "Expected headers to be an iterable or dict, but is {}.".
                format(type(headers).__name__))

        # Assign this manually to update the content-length header.
        if isinstance(content, bytes):
            resp.content = content
        elif isinstance(content, str):
            resp.text = content
        else:
            raise TypeError(
                "Expected content to be str or bytes, but is {}.".format(
                    type(content).__name__))

        return resp
Beispiel #4
0
def _read_headers(rfile):
    """
        Read a set of headers.
        Stop once a blank line is reached.

        Returns:
            A headers object

        Raises:
            exceptions.HttpSyntaxException
    """
    ret = []
    while True:
        line = rfile.readline()
        if not line or line == b"\r\n" or line == b"\n":
            break
        if line[0] in b" \t":
            if not ret:
                raise exceptions.HttpSyntaxException("Invalid headers")
            # continued header
            ret[-1] = (ret[-1][0], ret[-1][1] + b'\r\n ' + line.strip())
        else:
            try:
                name, value = line.split(b":", 1)
                value = value.strip()
                if not name:
                    raise ValueError()
                ret.append((name, value))
            except ValueError:
                raise exceptions.HttpSyntaxException(
                    "Invalid header line: %s" % repr(line))
    return headers.Headers(ret)
Beispiel #5
0
def _read_headers(lines: Iterable[bytes]) -> headers.Headers:
    """
        Read a set of headers.
        Stop once a blank line is reached.

        Returns:
            A headers object

        Raises:
            exceptions.HttpSyntaxException
    """
    ret: List[Tuple[bytes, bytes]] = []
    for line in lines:
        if line[0] in b" \t":
            if not ret:
                raise ValueError("Invalid headers")
            # continued header
            ret[-1] = (ret[-1][0], ret[-1][1] + b'\r\n ' + line.strip())
        else:
            try:
                name, value = line.split(b":", 1)
                value = value.strip()
                if not name:
                    raise ValueError()
                ret.append((name, value))
            except ValueError:
                raise ValueError(f"Invalid header line: {line!r}")
    return headers.Headers(ret)
Beispiel #6
0
def collect_response_headers(response, omit=None):
    response_headers = nheaders.Headers()
    for name in response.headers:
        if omit and name in omit:
            continue
        response_headers[name] = response.headers[name]
    return response_headers
Beispiel #7
0
def health_check_response(flow):
    # if log_debug:
    #     bubble_log.debug('health_check_response: special bubble health check request, responding with OK')
    response_headers = nheaders.Headers()
    response_headers[HEADER_HEALTH_CHECK] = 'OK'
    response_headers[HEADER_CONTENT_LENGTH] = '3'
    if flow.response is None:
        flow.response = http.HTTPResponse(http_version='HTTP/1.1',
                                          status_code=200,
                                          reason='OK',
                                          headers=response_headers,
                                          content=b'OK\n')
    else:
        flow.response.headers = nheaders.Headers()
        flow.response.headers = response_headers
        flow.response.status_code = 200
        flow.response.reason = 'OK'
        flow.response.stream = lambda chunks: [b'OK\n']
Beispiel #8
0
def tarpit_response(flow, host):
    # if log_debug:
    #     bubble_log.debug('health_check_response: special bubble health check request, responding with OK')
    response_headers = nheaders.Headers()
    if host is None:
        host = PUBLIC_IP
    response_headers[HEADER_LOCATION] = 'http://' + host + ':' + str(
        TARPIT_PORT) + '/admin/index.php'
    if flow.response is None:
        flow.response = http.HTTPResponse(http_version='HTTP/1.1',
                                          status_code=301,
                                          reason='Moved Permanently',
                                          headers=response_headers,
                                          content=b'')
    else:
        flow.response.headers = nheaders.Headers()
        flow.response.headers = response_headers
        flow.response.status_code = 301
        flow.response.reason = 'Moved Permanently'
Beispiel #9
0
    def __init__(self,
                 first_line_format,
                 method,
                 scheme,
                 host,
                 port,
                 path,
                 http_version,
                 headers=(),
                 content=None,
                 trailers=None,
                 timestamp_start=None,
                 timestamp_end=None):
        if isinstance(method, str):
            method = method.encode("ascii", "strict")
        if isinstance(scheme, str):
            scheme = scheme.encode("ascii", "strict")
        if isinstance(host, str):
            host = host.encode("idna", "strict")
        if isinstance(path, str):
            path = path.encode("ascii", "strict")
        if isinstance(http_version, str):
            http_version = http_version.encode("ascii", "strict")
        if not isinstance(headers, nheaders.Headers):
            headers = nheaders.Headers(headers)
        if isinstance(content, str):
            raise ValueError("Content must be bytes, not {}".format(
                type(content).__name__))
        if trailers is not None and not isinstance(trailers, nheaders.Headers):
            trailers = nheaders.Headers(trailers)

        self.first_line_format = first_line_format
        self.method = method
        self.scheme = scheme
        self.host = host
        self.port = port
        self.path = path
        self.http_version = http_version
        self.headers = headers
        self.content = content
        self.trailers = trailers
        self.timestamp_start = timestamp_start
        self.timestamp_end = timestamp_end
Beispiel #10
0
    def bubble_handle_request(self, flow):
        client_addr = flow.client_conn.address[0]
        server_addr = flow.server_conn.address[0]
        flow = update_host_and_port(flow)

        if flow.client_conn.tls_established:
            sni = flow.client_conn.connection.get_servername()
            is_http = False
        else:
            sni = None
            is_http = True

        # Determine if this request should be filtered
        host_header = flow.request.host_header
        host = flow.request.host
        path = flow.request.path
        if sni or host_header:
            log_url = flow.request.scheme + '://' + host + path

            # If https, we have already checked that the client/server are legal in bubble_conn_check.py
            # If http, we validate client/server here
            if is_http:
                fqdns = [host]
                if is_bubble_health_check(path):
                    # Health check
                    health_check_response(flow)
                    return None

                elif is_bubble_request(server_addr, fqdns):
                    if log_debug:
                        bubble_log.debug(
                            'bubble_handle_request: redirecting to https for LOCAL bubble='
                            + server_addr + ' (bubble_host (' + bubble_host +
                            ') in fqdns or bubble_host_alias (' +
                            bubble_host_alias + ') in fqdns) for client=' +
                            client_addr + ', fqdns=' + repr(fqdns) +
                            ', path=' + path)
                    add_flow_ctx(flow, CTX_BUBBLE_ABORT, 301)
                    add_flow_ctx(flow, CTX_BUBBLE_LOCATION,
                                 'https://' + host + path)
                    return None

                elif is_sage_request(server_addr, fqdns):
                    if log_debug:
                        bubble_log.debug(
                            'bubble_handle_request: redirecting to https for SAGE server='
                            + server_addr + ' for client=' + client_addr)
                    add_flow_ctx(flow, CTX_BUBBLE_ABORT, 301)
                    add_flow_ctx(flow, CTX_BUBBLE_LOCATION,
                                 'https://' + host + path)
                    return None

                elif is_not_from_vpn(client_addr):
                    if log_warning:
                        bubble_log.warning(
                            'bubble_handle_request: sending to tarpit: non-VPN client='
                            + client_addr + ', url=' + log_url + ' host=' +
                            host)
                    bubble_activity_log(client_addr, server_addr,
                                        'http_tarpit_non_vpn', fqdns)
                    tarpit_response(flow, host)
                    return None

            if is_bubble_special_path(path):
                add_flow_ctx(flow, CTX_BUBBLE_SPECIAL, True)
            else:
                matcher_response = self.get_matchers(flow, sni or host_header)
                if matcher_response:
                    has_decision = 'decision' in matcher_response and matcher_response[
                        'decision'] is not None
                    if has_decision and matcher_response[
                            'decision'] == 'pass_thru':
                        if log_info:
                            bubble_log.info(
                                'bubble_handle_request: REQ-DECISION PASSTHRU '
                                + log_url +
                                ' passthru response returned, passing thru...')
                        add_flow_ctx(flow, CTX_BUBBLE_PASSTHRU, True)
                        bubble_activity_log(client_addr, server_addr,
                                            'http_passthru', log_url)
                        return host

                    elif has_decision and matcher_response[
                            'decision'].startswith('abort_'):
                        decision = str(matcher_response['decision'])
                        if log_debug:
                            bubble_log.debug(
                                'bubble_handle_request: found abort code: ' +
                                decision + ', aborting')
                        if decision == 'abort_ok':
                            abort_code = 200
                        elif decision == 'abort_not_found':
                            abort_code = 404
                        else:
                            if log_debug:
                                bubble_log.debug(
                                    'bubble_handle_request: unknown abort code: '
                                    + decision +
                                    ', aborting with 404 Not Found')
                            abort_code = 404
                        flow.request.headers = nheaders.Headers([])
                        flow.request.content = b''
                        add_flow_ctx(flow, CTX_BUBBLE_ABORT, abort_code)
                        bubble_activity_log(client_addr, server_addr,
                                            'http_abort' + str(abort_code),
                                            log_url)
                        if log_info:
                            bubble_log.info(
                                'bubble_handle_request: REQ-DECISION: BLOCK ' +
                                log_url + ' (' + decision + ')')
                        return None

                    elif has_decision and matcher_response[
                            'decision'] == 'no_match':
                        if log_info:
                            decision = str(matcher_response['decision'])
                            bubble_log.info(
                                'bubble_handle_request: REQ-DECISION: ALLOW ' +
                                log_url + ' (' + decision + ')')
                        bubble_activity_log(client_addr, server_addr,
                                            'http_no_match', log_url)
                        return host

                    elif ('matchers' in matcher_response
                          and 'request_id' in matcher_response
                          and len(matcher_response['matchers']) > 0):
                        req_id = matcher_response['request_id']
                        if log_info:
                            bubble_log.info(
                                'bubble_handle_request: REQ-DECISION: FILTER '
                                + log_url + ' found request_id: ' + req_id +
                                ' with matchers: ' +
                                repr(matcher_response['matchers']))
                        add_flow_ctx(flow, CTX_BUBBLE_MATCHERS,
                                     matcher_response['matchers'])
                        add_flow_ctx(flow, CTX_BUBBLE_REQUEST_ID, req_id)
                        bubble_activity_log(client_addr, server_addr,
                                            'http_match', log_url)
                    else:
                        if log_info:
                            bubble_log.info(
                                'bubble_handle_request: REQ-DECISION: ALLOW ' +
                                log_url + ' no rules returned')
                        bubble_activity_log(client_addr, server_addr,
                                            'http_no_rules', log_url)
                else:
                    if log_info:
                        bubble_log.info(
                            'bubble_handle_request: REQ-DECISION: ALLOW ' +
                            log_url + 'no matcher_response')
                    # bubble_activity_log(client_addr, server_addr, 'http_no_matcher_response', log_url)

        elif is_http and is_not_from_vpn(client_addr):
            if log_warning:
                bubble_log.warning(
                    'bubble_handle_request: sending to tarpit: non-VPN client='
                    + client_addr)
            bubble_activity_log(client_addr, server_addr,
                                'http_tarpit_non_vpn', [server_addr])
            tarpit_response(flow, host)
            return None

        else:
            if log_warning:
                bubble_log.warning(
                    'bubble_handle_request: no sni/host found, not applying rules to path: '
                    + path)
            bubble_activity_log(client_addr, server_addr,
                                'http_no_sni_or_host', [server_addr])

        return host