From 1bb8e6f74408dd4bf21a25ed0cfa82d5453bcb53 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 14 Dec 2012 11:40:35 +0000 Subject: [PATCH 1/5] updated third party document to reflect inclusion of bottle web framework (#297) --- doc/THIRD-PARTY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index 2e0ce9e2e..379e752f8 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -277,6 +277,8 @@ be bound by the terms and conditions of this License Agreement. # MIT +* The bottle web framework library located under extra/bottle/. + Copyright (C) 2012, Marcel Hellkamp. * The PageRank library located under thirdparty/pagerank/. Copyright (C) 2010, Corey Goldberg. * The PrettyPrint library located under thirdparty/prettyprint/. From 156a291e2d22ea5d66b90a6c635292362a8fcba9 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 14 Dec 2012 11:55:54 +0000 Subject: [PATCH 2/5] typo fix --- lib/parse/cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 4b0f99e40..a282f2e81 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -685,7 +685,7 @@ def cmdLineParser(): parser.add_option("--restapi", dest="restApi", action="store_true", help=SUPPRESS_HELP) - parser.add_option("--restApi-port", dest="restApiPort", type="int", + parser.add_option("--restapi-port", dest="restApiPort", type="int", help=SUPPRESS_HELP) parser.add_option("--xmlrpc", dest="xmlRpc", action="store_true", From 90d5696b2558d361d6af60063bab2a96ed183ce4 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 14 Dec 2012 12:01:13 +0000 Subject: [PATCH 3/5] enhanced RESTful API to support JSON requests and improved standalone client/server skeleton (issue #297) --- lib/utils/restapi.py | 112 +++++++++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/lib/utils/restapi.py b/lib/utils/restapi.py index 82416ebf7..b72dc0924 100644 --- a/lib/utils/restapi.py +++ b/lib/utils/restapi.py @@ -5,35 +5,66 @@ Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ -import sys -import json +import argparse import os +import sys -from extra.bottle.bottle import abort, error, get, post, request, run, template, debug -from lib.controller.controller import start -from lib.core.convert import hexencode -from lib.core.datatype import AttribDict -from lib.core.data import cmdLineOptions -from lib.core.data import kb -from lib.core.data import logger -from lib.core.exception import SqlmapMissingDependence -from lib.core.option import init -from lib.core.settings import UNICODE_ENCODING -from lib.core.settings import RESTAPI_SERVER_PORT +try: + import simplejson as json +except ImportError: + import json +try: + from extra.bottle.bottle import abort + from extra.bottle.bottle import debug + from extra.bottle.bottle import error + from extra.bottle.bottle import get + from extra.bottle.bottle import post + from extra.bottle.bottle import request + from extra.bottle.bottle import response + from extra.bottle.bottle import Response + from extra.bottle.bottle import run + from extra.bottle.bottle import static_file + from extra.bottle.bottle import template +except ImportError: + try: + from bottle import * + except ImportError, e: + print "[x] '%s'" % str(e) + sys.exit(1) + +try: + sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..")) + from lib.controller.controller import start + from lib.core.convert import hexencode + from lib.core.datatype import AttribDict + from lib.core.data import cmdLineOptions + from lib.core.data import kb + from lib.core.data import logger + from lib.core.exception import SqlmapMissingDependence + from lib.core.option import init + from lib.core.settings import UNICODE_ENCODING + from lib.core.settings import RESTAPI_SERVER_PORT +except ImportError: + RESTAPI_SERVER_PORT = 8775 # local global variables session_ids = [] admin_id = "" +Response(headers={"X-Frame-Options": "sameorigin", "X-XSS-Protection": "1; mode=block"}) + # Generic functions def jsonize(data): - return json.dumps(data, sort_keys=False, indent=4) + #return json.dumps(data, sort_keys=False, indent=4) + return json.dumps(data, sort_keys=False) def is_admin(session_id): global admin_id + #print "[INFO] Admin ID: %s" % admin_id + #print "[INFO] Session ID: %s" % session_id if admin_id != session_id: return False else: @@ -55,6 +86,7 @@ def error404(error): def error405(error): return "Method not allowed" + @error(500) # Internal Server Error def error500(error): return "Internal server error" @@ -73,6 +105,7 @@ def session_new(): global session_ids session_id = hexencode(os.urandom(32)) session_ids.append(session_id) + response.content_type = "application/json; charset=UTF-8" return jsonize({"sessionid": session_id}) @@ -81,9 +114,7 @@ def session_destroy(): """ Destroy own session token """ - # TODO: replace use of request.forms with JSON - session_id = request.forms.get("sessionid", "") - # + session_id = request.json.get("sessionid", "") if session_id in session_ids: session_ids.remove(session_id) return "Done" @@ -96,25 +127,37 @@ def session_list(): """ List all active sessions """ - # TODO: replace use of request.forms with JSON - if is_admin(request.forms.get("sessionid", "")): + if is_admin(request.json.get("sessionid", "")): + response.content_type = "application/json; charset=UTF-8" return jsonize({"sessions": session_ids}) else: abort(401) -@get("/session/flush") +@post("/session/flush") def session_flush(): """ Flush session spool (destroy all sessions) """ global session_ids - if is_admin(request.forms.get("sessionid", "")): + if is_admin(request.json.get("sessionid", "")): session_ids = [] else: abort(401) +@post("/download//") +def download(target, filename): + """ + Download a certain file from the file system + """ + path = os.path.join(paths.SQLMAP_OUTPUT_PATH, target) + if os.path.exists(path): + return static_file(filename, root=path) + else: + abort(500) + + def restAPIrun(host="0.0.0.0", port=RESTAPI_SERVER_PORT): """ Initiate REST-JSON API @@ -126,9 +169,28 @@ def restAPIrun(host="0.0.0.0", port=RESTAPI_SERVER_PORT): logger.info("The admin session ID is: %s" % admin_id) run(host=host, port=port) - -if __name__ == "__main__": - addr = "http://localhost:%d" % (int(sys.argv[1]) if len(sys.argv) > 1 else RESTAPI_SERVER_PORT) - print "[i] Starting debug REST-JSON client to '%s'..." % addr +def client(host, port): + addr = "http://%s:%d" % (host, port) + print "[INFO] Starting debug REST-JSON client to '%s'..." % addr # TODO: write a simple client with urllib2, for now use curl from command line + print "[ERROR] Not yet implemented, use curl from command line instead for now, for example:" + print "\n\t$ curl --proxy http://127.0.0.1:8080 http://%s:%s/session/new" % (host, port) + print "\t$ curl --proxy http://127.0.0.1:8080 -H \"Content-Type: application/json\" -X POST -d '{\"sessionid\": \"\"}' http://%s:%d/session/list\n" % (host, port) + +if __name__ == "__main__": + """ + Standalone REST-JSON API wrapper function + """ + + parser = argparse.ArgumentParser() + parser.add_argument("-s", "--server", help="Act as a REST-JSON API server", default=RESTAPI_SERVER_PORT, action="store_true", required=False) + parser.add_argument("-c", "--client", help="Act as a REST-JSON API client", default=RESTAPI_SERVER_PORT, action="store_true", required=False) + parser.add_argument("-H", "--host", help="Host of the REST-JSON API server", default="0.0.0.0", action="store", required=False) + parser.add_argument("-p", "--port", help="Port of the the REST-JSON API server", default=RESTAPI_SERVER_PORT, action="store", required=False) + args = parser.parse_args() + + if args.server is True: + restAPIrun(args.host, args.port) + elif args.client is True: + client(args.host, args.port) From 7b43837238407babfbbdd15f1dbe50dda962cd02 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 14 Dec 2012 12:04:44 +0000 Subject: [PATCH 4/5] cleaner solution for imports as standalone client/server (issue #297) --- lib/utils/restapi.py | 54 ++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/lib/utils/restapi.py b/lib/utils/restapi.py index b72dc0924..13663ea04 100644 --- a/lib/utils/restapi.py +++ b/lib/utils/restapi.py @@ -14,39 +14,29 @@ try: except ImportError: import json -try: - from extra.bottle.bottle import abort - from extra.bottle.bottle import debug - from extra.bottle.bottle import error - from extra.bottle.bottle import get - from extra.bottle.bottle import post - from extra.bottle.bottle import request - from extra.bottle.bottle import response - from extra.bottle.bottle import Response - from extra.bottle.bottle import run - from extra.bottle.bottle import static_file - from extra.bottle.bottle import template -except ImportError: - try: - from bottle import * - except ImportError, e: - print "[x] '%s'" % str(e) - sys.exit(1) +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..")) -try: - sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..")) - from lib.controller.controller import start - from lib.core.convert import hexencode - from lib.core.datatype import AttribDict - from lib.core.data import cmdLineOptions - from lib.core.data import kb - from lib.core.data import logger - from lib.core.exception import SqlmapMissingDependence - from lib.core.option import init - from lib.core.settings import UNICODE_ENCODING - from lib.core.settings import RESTAPI_SERVER_PORT -except ImportError: - RESTAPI_SERVER_PORT = 8775 +from extra.bottle.bottle import abort +from extra.bottle.bottle import debug +from extra.bottle.bottle import error +from extra.bottle.bottle import get +from extra.bottle.bottle import post +from extra.bottle.bottle import request +from extra.bottle.bottle import response +from extra.bottle.bottle import Response +from extra.bottle.bottle import run +from extra.bottle.bottle import static_file +from extra.bottle.bottle import template +from lib.controller.controller import start +from lib.core.convert import hexencode +from lib.core.datatype import AttribDict +from lib.core.data import cmdLineOptions +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import SqlmapMissingDependence +from lib.core.option import init +from lib.core.settings import UNICODE_ENCODING +from lib.core.settings import RESTAPI_SERVER_PORT # local global variables session_ids = [] From 3d9779ffd495ca3546d555653adf1075bc052b88 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 14 Dec 2012 12:15:04 +0000 Subject: [PATCH 5/5] further improvements to RESTful API: enforce security headers across all HTTP responses properly and make consistent responses across methods (#297) --- lib/utils/restapi.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/utils/restapi.py b/lib/utils/restapi.py index 13663ea04..2ff4a7289 100644 --- a/lib/utils/restapi.py +++ b/lib/utils/restapi.py @@ -20,15 +20,16 @@ from extra.bottle.bottle import abort from extra.bottle.bottle import debug from extra.bottle.bottle import error from extra.bottle.bottle import get +from extra.bottle.bottle import hook from extra.bottle.bottle import post from extra.bottle.bottle import request from extra.bottle.bottle import response -from extra.bottle.bottle import Response from extra.bottle.bottle import run from extra.bottle.bottle import static_file from extra.bottle.bottle import template from lib.controller.controller import start from lib.core.convert import hexencode +from lib.core.data import paths from lib.core.datatype import AttribDict from lib.core.data import cmdLineOptions from lib.core.data import kb @@ -38,12 +39,11 @@ from lib.core.option import init from lib.core.settings import UNICODE_ENCODING from lib.core.settings import RESTAPI_SERVER_PORT + # local global variables session_ids = [] admin_id = "" -Response(headers={"X-Frame-Options": "sameorigin", "X-XSS-Protection": "1; mode=block"}) - # Generic functions def jsonize(data): @@ -61,6 +61,16 @@ def is_admin(session_id): return True +@hook('after_request') +def security_headers(): + """ + Set some headers across all HTTP responses + """ + response.headers["Server"] = "Server" + response.headers["X-Frame-Options"] = "sameorigin" + response.headers["X-XSS-Protection"] = "1; mode=block" + + # HTTP Status Code functions @error(401) # Access Denied def error401(error): @@ -107,7 +117,7 @@ def session_destroy(): session_id = request.json.get("sessionid", "") if session_id in session_ids: session_ids.remove(session_id) - return "Done" + return jsonize({"success": True}) else: abort(500) @@ -132,6 +142,7 @@ def session_flush(): global session_ids if is_admin(request.json.get("sessionid", "")): session_ids = [] + return jsonize({"success": True}) else: abort(401)