From 53065234466918daf36ebe6d3b10d264979fc8ae Mon Sep 17 00:00:00 2001 From: Guillem Anguera Date: Thu, 11 Feb 2016 00:14:38 +0100 Subject: [PATCH 1/2] Added Basic Auth support for MITMWeb interface --- libmproxy/cmdline.py | 15 +++++++++++++++ libmproxy/main.py | 3 +++ libmproxy/web/__init__.py | 24 +++++++++++++++++++++++- libmproxy/web/app.py | 37 ++++++++++++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index d8b6000cb..b1cbfa3a7 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -760,6 +760,21 @@ def mitmweb(): action="store_true", dest="wdebug", help="Turn on mitmweb debugging" ) + group.add_argument( + "--wsingleuser", + action="store", dest="wsingleuser", type=str, + metavar="USER", + help=""" + Allows access to a a single user, specified in the form + username:password. + """ + ) + group.add_argument( + "--whtpasswd", + action="store", dest="whtpasswd", type=str, + metavar="PATH", + help="Allow access to users specified in an Apache htpasswd file." + ) common_options(parser) group = parser.add_argument_group( diff --git a/libmproxy/main.py b/libmproxy/main.py index f6664924a..ef1357545 100644 --- a/libmproxy/main.py +++ b/libmproxy/main.py @@ -125,6 +125,9 @@ def mitmweb(args=None): # pragma: no cover web_options.wdebug = options.wdebug web_options.wiface = options.wiface web_options.wport = options.wport + web_options.wsingleuser = options.wsingleuser + web_options.whtpasswd = options.whtpasswd + web_options.process_web_options(parser) server = get_server(web_options.no_server, proxy_config) diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py index c48b3d09b..50c49e8db 100644 --- a/libmproxy/web/__init__.py +++ b/libmproxy/web/__init__.py @@ -3,6 +3,8 @@ import collections import tornado.ioloop import tornado.httpserver +from netlib.http import authentication + from .. import controller, flow from . import app @@ -113,6 +115,9 @@ class Options(object): "wdebug", "wport", "wiface", + "wauthenticator", + "wsingleuser", + "whtpasswd", ] def __init__(self, **kwargs): @@ -122,13 +127,30 @@ class Options(object): if not hasattr(self, i): setattr(self, i, None) + def process_web_options(self, parser): + if self.wsingleuser or self.whtpasswd: + if self.wsingleuser: + if len(self.wsingleuser.split(':')) != 2: + return parser.error( + "Invalid single-user specification. Please use the format username:password" + ) + username, password = self.wsingleuser.split(':') + self.wauthenticator = authentication.PassManSingleUser(username, password) + elif self.whtpasswd: + try: + self.wauthenticator = authentication.PassManHtpasswd(self.whtpasswd) + except ValueError as v: + return parser.error(v.message) + else: + self.wauthenticator = None + class WebMaster(flow.FlowMaster): def __init__(self, server, options): self.options = options super(WebMaster, self).__init__(server, WebState()) - self.app = app.Application(self, self.options.wdebug) + self.app = app.Application(self, self.options.wdebug, self.options.wauthenticator) if options.rfile: try: self.load_flows_file(options.rfile) diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py index 958b86698..557118370 100644 --- a/libmproxy/web/app.py +++ b/libmproxy/web/app.py @@ -4,6 +4,7 @@ import tornado.web import tornado.websocket import logging import json +import base64 from netlib.http import CONTENT_MISSING from .. import version, filt @@ -40,7 +41,31 @@ class APIError(tornado.web.HTTPError): pass -class RequestHandler(tornado.web.RequestHandler): +class BasicAuth(object): + def set_auth_headers(self): + self.set_status(401) + self.set_header('WWW-Authenticate', 'Basic realm=MITMWeb') + self._transforms = [] + self.finish() + + def initialize(self, **kwargs): + self.wauthenticator = kwargs.get("wauthenticator") + + def prepare(self): + if self.wauthenticator: + auth_header = self.request.headers.get('Authorization') + if auth_header is None or not auth_header.startswith('Basic '): + self.set_auth_headers() + else: + self.auth_decoded = base64.decodestring(auth_header[6:]) + self.username, self.password = self.auth_decoded.split(':', 2) + if not self.wauthenticator.test(self.username, self.password): + self.set_auth_headers() + raise APIError(401, "Invalid username or password.") + + + +class RequestHandler(BasicAuth, tornado.web.RequestHandler): def set_default_headers(self): super(RequestHandler, self).set_default_headers() @@ -100,7 +125,7 @@ class FiltHelp(RequestHandler): )) -class WebSocketEventBroadcaster(tornado.websocket.WebSocketHandler): +class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler): # raise an error if inherited class doesn't specify its own instance. connections = None @@ -284,8 +309,11 @@ class Settings(RequestHandler): class Application(tornado.web.Application): - def __init__(self, master, debug): + def __init__(self, master, debug, wauthenticator): self.master = master + self.additional_args = dict( + wauthenticator=wauthenticator, + ) handlers = [ (r"/", IndexHandler), (r"/filter-help", FiltHelp), @@ -302,6 +330,9 @@ class Application(tornado.web.Application): (r"/settings", Settings), (r"/clear", ClearAll), ] + for i, handler in enumerate(handlers): + handlers[i] += (self.additional_args,) + settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), From 195ea8e15ac3822c1524ee4742ec42ca76638876 Mon Sep 17 00:00:00 2001 From: Guillem Anguera Date: Fri, 12 Feb 2016 23:30:42 +0100 Subject: [PATCH 2/2] Send password_manager as a general application setting instead of per handler --- libmproxy/web/app.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py index 557118370..2c45a1dcc 100644 --- a/libmproxy/web/app.py +++ b/libmproxy/web/app.py @@ -48,23 +48,20 @@ class BasicAuth(object): self._transforms = [] self.finish() - def initialize(self, **kwargs): - self.wauthenticator = kwargs.get("wauthenticator") - def prepare(self): - if self.wauthenticator: + wauthenticator = self.application.settings['wauthenticator'] + if wauthenticator: auth_header = self.request.headers.get('Authorization') if auth_header is None or not auth_header.startswith('Basic '): self.set_auth_headers() else: self.auth_decoded = base64.decodestring(auth_header[6:]) self.username, self.password = self.auth_decoded.split(':', 2) - if not self.wauthenticator.test(self.username, self.password): + if not wauthenticator.test(self.username, self.password): self.set_auth_headers() raise APIError(401, "Invalid username or password.") - class RequestHandler(BasicAuth, tornado.web.RequestHandler): def set_default_headers(self): @@ -311,9 +308,6 @@ class Application(tornado.web.Application): def __init__(self, master, debug, wauthenticator): self.master = master - self.additional_args = dict( - wauthenticator=wauthenticator, - ) handlers = [ (r"/", IndexHandler), (r"/filter-help", FiltHelp), @@ -330,14 +324,12 @@ class Application(tornado.web.Application): (r"/settings", Settings), (r"/clear", ClearAll), ] - for i, handler in enumerate(handlers): - handlers[i] += (self.additional_args,) - settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), xsrf_cookies=True, cookie_secret=os.urandom(256), debug=debug, + wauthenticator=wauthenticator, ) super(Application, self).__init__(handlers, **settings)