From accad358209b35c2b21928463e297e6e2ac031b0 Mon Sep 17 00:00:00 2001 From: "Mariusz B. / mgeeky" Date: Fri, 7 Oct 2022 15:39:52 +0200 Subject: [PATCH] Added 'remove_these_response_headers' parameter fixing issue with Cobalt Strike 4.7+ unable to process response body. --- RedWarden.py | 6 +++--- example-config.yaml | 11 +++++++++++ lib/proxyhandler.py | 23 +++++++++++++++++++++-- plugins/IProxyPlugin.py | 1 + plugins/malleable_redirector.py | 6 ++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/RedWarden.py b/RedWarden.py index 3cf90a5..81d3aab 100644 --- a/RedWarden.py +++ b/RedWarden.py @@ -28,14 +28,14 @@ # 0.9 added support for RedELK logs generation. # # Author: -# Mariusz Banach / mgeeky, '16-'21 +# Mariusz Banach / mgeeky, '16-'22 # # # (originally based on: @inaz2 implementation: https://github.com/futuresimple/proxy2) # (now obsoleted) # -VERSION = '0.9.1' +VERSION = '0.9.3' import sys, os @@ -174,7 +174,7 @@ def main(): :: RedWarden - Keeps your malleable C2 packets slipping through AVs, EDRs, Blue Teams and club bouncers like nothing else! - by Mariusz Banach / mgeeky, '19-'21 + by Mariusz Banach / mgeeky, '19-'22 v{} diff --git a/example-config.yaml b/example-config.yaml index 7c8aa7b..a4add47 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -483,6 +483,17 @@ policy: #repair_these_headers: # - Accept-Encoding +# +# This option specifies which headers coming from Teamserver responses should be removed before +# reaching Beacon process. With Cobalt Strike 4.7+ I noticed that Teamserver removes Content-Encoding header +# automatically without any notice, thus violating our malleable http-(get|post).server contract. +# Since RedWarden followed the contract, Beacon was either dropping responses or decompressing them incorrectly. +# +# Either way, with Cobalt Strike 4.7+ we need to remove Content-Encoding header from responses to keep on going. +# +remove_these_response_headers: + - Content-Encoding + # # Malleable Redirector plugin can act as a basic oracle API responding to calls diff --git a/lib/proxyhandler.py b/lib/proxyhandler.py index b2ac76e..ee241f9 100644 --- a/lib/proxyhandler.py +++ b/lib/proxyhandler.py @@ -606,6 +606,7 @@ class Response(object): origin = (scheme, inbound_origin) neworigin = (scheme, netloc) + res_msg_hdrs = {} class MyResponse(http.client.HTTPResponse): def __init__(self, req, origreq): @@ -722,6 +723,10 @@ def __init__(self, req, origreq): res = MyResponse(myreq, self) self.response_headers = res.headers + res_msg_hdrs = res.msg.copy() + + if len(res.msg) == 0 and len(res.headers) > 0: + res_msg_hdrs = res.headers.copy() res_body = "" if ignore_response_decompression_errors: @@ -768,8 +773,8 @@ def __init__(self, req, origreq): if options['debug']: raise return - setattr(res, 'headers', res.msg) - self.response_headers = res.msg + setattr(res, 'headers', res_msg_hdrs) + self.response_headers = res_msg_hdrs content_encoding = res.headers.get('Content-Encoding', 'identity') if ignore_response_decompression_errors: @@ -789,6 +794,20 @@ def __init__(self, req, origreq): newuri = self.request.uri self.request.uri = origuri + reskeys = [x.lower() for x in res.headers.keys()] + + # if content_encoding == 'identity' and 'content-encoding' in reskeys: + # self.logger.dbg('Removed Content-Encoding response header.') + # del res.headers['Content-Encoding'] + + if plugins.IProxyPlugin.proxy2_metadata_headers['remove_response_headers'] in reskeys: + hdrs = res.headers[IProxyPlugin.proxy2_metadata_headers['remove_response_headers']] + self.logger.dbg('Removing these response headers: ' + hdrs) + + hdrsList = hdrs.split(',') + for h in hdrsList: + del res.headers[h] + if type(modified) == bool: modified |= (newuri != origuri) diff --git a/plugins/IProxyPlugin.py b/plugins/IProxyPlugin.py index 072f43d..53b5896 100644 --- a/plugins/IProxyPlugin.py +++ b/plugins/IProxyPlugin.py @@ -12,6 +12,7 @@ 'override_host_header' : 'X-Proxy2-Override-Host-Header', 'domain_front_host_header' : 'X-Proxy2-Domain-Front-Host-Header', 'keep_alive_this_connection' : 'X-Proxy2-Keep-Alive', + 'remove_response_headers' : 'X-Proxy2-Remove-Response-Headers', } class DropConnectionException(Exception): diff --git a/plugins/malleable_redirector.py b/plugins/malleable_redirector.py index 0d365dc..932b2a6 100644 --- a/plugins/malleable_redirector.py +++ b/plugins/malleable_redirector.py @@ -268,6 +268,7 @@ def parse(self, path): except Exception as e: self.logger.fatal(f'Could not process line as ([set] parameter ["value", ...] :\n\n\t{line}\n\nMake sure your line doesnt include apostrophes, or other characters breaking compregexes["parameter-value"] regex.') + if values == []: values = '' elif len(values) == 1: @@ -412,6 +413,7 @@ class AlterHostHeader(Exception): 'mitigate_replay_attack': False, 'whitelisted_ip_addresses' : [], 'protect_these_headers_from_tampering' : [], + 'remove_these_response_headers' : [], 'verify_peer_ip_details': True, 'malleable_redirector_hidden_api_endpoint' : '', 'remove_superfluous_headers': True, @@ -964,6 +966,10 @@ def redirect(self, req, _target, malleable_meta): req.headers[proxy2_metadata_headers['ignore_response_decompression_errors']] = "1" req.headers[proxy2_metadata_headers['override_host_header']] = newhost + if 'remove_these_response_headers' in self.proxyOptions.keys() and len(self.proxyOptions['remove_these_response_headers']) > 0: + removeThese = ','.join([x.lower() for x in self.proxyOptions['remove_these_response_headers']]) + req.headers[proxy2_metadata_headers['remove_response_headers']] = removeThese + if 'host' in malleable_meta.keys() and len(malleable_meta['host']) > 0: req.headers[proxy2_metadata_headers['domain_front_host_header']] = malleable_meta['host']