Skip to content

Commit

Permalink
Adjust HTTP listeners' responses to resemble IIS 7.5 more (#277)
Browse files Browse the repository at this point in the history
* HTTP listeners - make welcome.png case-insensitive

Signed-off-by: adamczi <adamczi@users.noreply.github.com>

* HTTP listeners - imitate 405 response page

Signed-off-by: adamczi <adamczi@users.noreply.github.com>

* HTTP listeners - adapt to resemble IIS more
- fixed 200 to 404 code in http.py when no cookie is passed
- minor changes in HTML responses to make alike to original IIS pages
- bump Werkzeug's HTTP version header from 1.0 to 1.1 for the same reason

Signed-off-by: adamczi <adamczi@users.noreply.github.com>

* HTTP listeners - route to default IIS iisstart.htm

Signed-off-by: adamczi <adamczi@users.noreply.github.com>

Co-authored-by: Anthony Rose <anthony.rose@bc-security.org>
  • Loading branch information
adamczi and Cx01N authored Aug 12, 2020
1 parent 17ad549 commit b8afdeb
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 44 deletions.
95 changes: 71 additions & 24 deletions lib/listeners/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from builtins import str

from flask import Flask, request, make_response, send_from_directory
from werkzeug.serving import WSGIRequestHandler
from pydispatch import dispatcher

from lib.common import bypasses
Expand Down Expand Up @@ -193,7 +194,7 @@ def default_response(self):
Returns an IIS 7.5 404 not found page.
"""

return '\n'.join([
return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
Expand All @@ -202,10 +203,10 @@ def default_response(self):
'<style type="text/css">',
'<!--',
'body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}',
'fieldset{padding:0 15px 10px 15px;}',
'fieldset{padding:0 15px 10px 15px;} ',
'h1{font-size:2.4em;margin:0;color:#FFF;}',
'h2{font-size:1.7em;margin:0;color:#CC0000;}',
'h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}',
'h2{font-size:1.7em;margin:0;color:#CC0000;} ',
'h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} ',
'#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;',
'background-color:#555555;}',
'#content{margin:0 0 0 2%;position:relative;}',
Expand All @@ -226,12 +227,49 @@ def default_response(self):
' ' * self.header_offset, # randomize the length of the header to evade signature based detection
])

def method_not_allowed_page(self):
"""
Imitates IIS 7.5 405 "method not allowed" page.
"""

return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>',
'<title>405 - HTTP verb used to access this page is not allowed.</title>',
'<style type="text/css">',
'<!--',
'body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}',
'fieldset{padding:0 15px 10px 15px;} ',
'h1{font-size:2.4em;margin:0;color:#FFF;}',
'h2{font-size:1.7em;margin:0;color:#CC0000;} ',
'h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} ',
'#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;',
'background-color:#555555;}',
'#content{margin:0 0 0 2%;position:relative;}',
'.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}',
'-->',
'</style>',
'</head>',
'<body>',
'<div id="header"><h1>Server Error</h1></div>',
'<div id="content">',
' <div class="content-container"><fieldset>',
' <h2>405 - HTTP verb used to access this page is not allowed.</h2>',
' <h3>The page you are looking for cannot be displayed because an invalid method (HTTP verb) was used to attempt access.</h3>',
' </fieldset></div>',
'</div>',
'</body>',
'</html>\r\n'
])

def index_page(self):
"""
Returns a default HTTP server page.
"""

return '\n'.join([
return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
Expand All @@ -240,16 +278,16 @@ def index_page(self):
'<style type="text/css">',
'<!--',
'body {',
' color:#000000;',
' background-color:#B3B3B3;',
' margin:0;',
' color:#000000;',
' background-color:#B3B3B3;',
' margin:0;',
'}',
'',
'#container {',
' margin-left:auto;',
' margin-right:auto;',
' text-align:center;',
' }',
' margin-left:auto;',
' margin-right:auto;',
' text-align:center;',
' }',
'',
'a img {',
' border:none;',
Expand Down Expand Up @@ -956,6 +994,9 @@ def start_server(self, listenerOptions):
app = Flask(__name__)
self.app = app

# Set HTTP/1.1 as in IIS 7.5 instead of /1.0
WSGIRequestHandler.protocol_version = "HTTP/1.1"

@app.route('/download/<stager>')
def send_stager(stager):
if 'po' in stager:
Expand Down Expand Up @@ -1003,25 +1044,23 @@ def add_proxy_headers(response):
response.headers['Expires'] = "0"
return response

@app.errorhandler(405)
def handle_405(e):
"""
Returns IIS 7.5 405 page for every Flask 405 error.
"""
return make_response(self.method_not_allowed_page(), 405)

@app.route('/')
@app.route('/index.html')
@app.route('/iisstart.htm')
def serve_index():
"""
Return default server web page if user navigates to index.
"""

static_dir = self.mainMenu.installPath + "data/misc/"
return make_response(self.index_page(), 200)

@app.route('/welcome.png')
def serve_index_helper():
"""
Serves image loaded by index page.
"""

static_dir = self.mainMenu.installPath + "data/misc/"
return send_from_directory(static_dir, 'welcome.png')


@app.route('/<path:request_uri>', methods=['GET'])
def handle_get(request_uri):
"""
Expand All @@ -1030,6 +1069,14 @@ def handle_get(request_uri):
This is used during the first step of the staging process,
and when the agent requests taskings.
"""
if request_uri.lower() == 'welcome.png':
# Serves image loaded by index page.
#
# Thanks to making it case-insensitive it works the same way as in
# an actual IIS server
static_dir = self.mainMenu.installPath + "data/misc/"
return send_from_directory(static_dir, 'welcome.png')

clientIP = request.remote_addr

listenerName = self.options['Name']['Value']
Expand Down Expand Up @@ -1131,7 +1178,7 @@ def handle_get(request_uri):
'message': message
})
dispatcher.send(signal, sender="listeners/http/{}".format(listenerName))
return make_response(self.default_response(), 200)
return make_response(self.default_response(), 404)

@app.route('/<path:request_uri>', methods=['POST'])
def handle_post(request_uri):
Expand Down
87 changes: 67 additions & 20 deletions lib/listeners/http_com.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from builtins import str

from flask import Flask, request, make_response, send_from_directory
from werkzeug.serving import WSGIRequestHandler
from pydispatch import dispatcher

from lib.common import bypasses
Expand Down Expand Up @@ -165,7 +166,7 @@ def default_response(self):
Returns an IIS 7.5 404 not found page.
"""

return '\n'.join([
return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
Expand Down Expand Up @@ -198,12 +199,49 @@ def default_response(self):
' ' * self.header_offset, # randomize the length of the header to evade signature based detection
])

def method_not_allowed_page(self):
"""
Imitates IIS 7.5 405 "method not allowed" page.
"""

return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>',
'<title>405 - HTTP verb used to access this page is not allowed.</title>',
'<style type="text/css">',
'<!--',
'body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}',
'fieldset{padding:0 15px 10px 15px;} ',
'h1{font-size:2.4em;margin:0;color:#FFF;}',
'h2{font-size:1.7em;margin:0;color:#CC0000;} ',
'h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} ',
'#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;',
'background-color:#555555;}',
'#content{margin:0 0 0 2%;position:relative;}',
'.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}',
'-->',
'</style>',
'</head>',
'<body>',
'<div id="header"><h1>Server Error</h1></div>',
'<div id="content">',
' <div class="content-container"><fieldset>',
' <h2>405 - HTTP verb used to access this page is not allowed.</h2>',
' <h3>The page you are looking for cannot be displayed because an invalid method (HTTP verb) was used to attempt access.</h3>',
' </fieldset></div>',
'</div>',
'</body>',
'</html>\r\n'
])

def index_page(self):
"""
Returns a default HTTP server page.
"""

return '\n'.join([
return '\r\n'.join([
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml">',
'<head>',
Expand All @@ -212,19 +250,19 @@ def index_page(self):
'<style type="text/css">',
'<!--',
'body {',
' color:#000000;',
' background-color:#B3B3B3;',
' margin:0;',
' color:#000000;',
' background-color:#B3B3B3;',
' margin:0;',
'}',
'',
'#container {',
' margin-left:auto;',
' margin-right:auto;',
' text-align:center;',
' }',
' margin-left:auto;',
' margin-right:auto;',
' text-align:center;',
' }',
'',
'a img {',
' border:none;',
' border:none;',
'}',
'',
'-->',
Expand Down Expand Up @@ -658,6 +696,9 @@ def start_server(self, listenerOptions):
app = Flask(__name__)
self.app = app

# Set HTTP/1.1 as in IIS 7.5 instead of /1.0
WSGIRequestHandler.protocol_version = "HTTP/1.1"

@app.before_request
def check_ip():
"""
Expand Down Expand Up @@ -690,8 +731,15 @@ def add_proxy_headers(response):
response.headers['Expires'] = "0"
return response

@app.errorhandler(405)
def handle_405(e):
"""
Returns IIS 7.5 405 page for every Flask 405 error.
"""
return make_response(self.method_not_allowed_page(), 405)

@app.route('/')
@app.route('/index.html')
@app.route('/iisstart.htm')
def serve_index():
"""
Return default server web page if user navigates to index.
Expand All @@ -700,15 +748,6 @@ def serve_index():
static_dir = self.mainMenu.installPath + "data/misc/"
return make_response(self.index_page(), 200)

@app.route('/welcome.png')
def serve_index_helper():
"""
Serves image loaded by index page.
"""

static_dir = self.mainMenu.installPath + "data/misc/"
return send_from_directory(static_dir, 'welcome.png')

@app.route('/<path:request_uri>', methods=['GET'])
def handle_get(request_uri):
"""
Expand All @@ -717,6 +756,14 @@ def handle_get(request_uri):
This is used during the first step of the staging process,
and when the agent requests taskings.
"""
if request_uri.lower() == 'welcome.png':
# Serves image loaded by index page.
#
# Thanks to making it case-insensitive it works the same way as in
# an actual IIS server
static_dir = self.mainMenu.installPath + "data/misc/"
return send_from_directory(static_dir, 'welcome.png')

clientIP = request.remote_addr

listenerName = self.options['Name']['Value']
Expand Down

0 comments on commit b8afdeb

Please sign in to comment.