Merge pull request #583 from mattoufoutu/api

RESTful API improvements
This commit is contained in:
Miroslav Stampar 2013-12-17 14:10:19 -08:00
commit fd6dcd8bf5

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/) Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/)
@ -31,40 +32,47 @@ from lib.core.enums import PART_RUN_CONTENT_TYPES
from lib.core.log import LOGGER_HANDLER from lib.core.log import LOGGER_HANDLER
from lib.core.optiondict import optDict from lib.core.optiondict import optDict
from lib.core.subprocessng import Popen from lib.core.subprocessng import Popen
from lib.core.subprocessng import send_all from thirdparty.bottle.bottle import error as return_error
from lib.core.subprocessng import recv_some
from thirdparty.bottle.bottle import abort
from thirdparty.bottle.bottle import error
from thirdparty.bottle.bottle import get from thirdparty.bottle.bottle import get
from thirdparty.bottle.bottle import hook from thirdparty.bottle.bottle import hook
from thirdparty.bottle.bottle import post from thirdparty.bottle.bottle import post
from thirdparty.bottle.bottle import request from thirdparty.bottle.bottle import request
from thirdparty.bottle.bottle import response from thirdparty.bottle.bottle import response
from thirdparty.bottle.bottle import run from thirdparty.bottle.bottle import run
from thirdparty.bottle.bottle import static_file
RESTAPI_SERVER_HOST = "127.0.0.1" RESTAPI_SERVER_HOST = "127.0.0.1"
RESTAPI_SERVER_PORT = 8775 RESTAPI_SERVER_PORT = 8775
# Local global variables
adminid = "" # global settings
db = None class DataStore(object):
db_filepath = None admin_id = ""
current_db = None
tasks = dict() tasks = dict()
# API objects # API objects
class Database(object): class Database(object):
global db_filepath filepath = None
LOGS_TABLE = ("CREATE TABLE logs("
LOGS_TABLE = "CREATE TABLE logs(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, time TEXT, level TEXT, message TEXT)" "id INTEGER PRIMARY KEY AUTOINCREMENT, "
DATA_TABLE = "CREATE TABLE data(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, status INTEGER, content_type INTEGER, value TEXT)" "taskid INTEGER, time TEXT, "
ERRORS_TABLE = "CREATE TABLE errors(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, error TEXT)" "level TEXT, message TEXT"
")")
DATA_TABLE = ("CREATE TABLE data("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"taskid INTEGER, status INTEGER, "
"content_type INTEGER, value TEXT"
")")
ERRORS_TABLE = ("CREATE TABLE errors("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"taskid INTEGER, error TEXT"
")")
def __init__(self, database=None): def __init__(self, database=None):
if database: self.database = self.filepath if database is None else database
self.database = database self.connection = None
else: self.cursor = None
self.database = db_filepath
def connect(self, who="server"): def connect(self, who="server"):
self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None) self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None)
@ -76,7 +84,7 @@ class Database(object):
self.connection.close() self.connection.close()
def commit(self): def commit(self):
self.cursor.commit() self.connection.commit()
def execute(self, statement, arguments=None): def execute(self, statement, arguments=None):
if arguments: if arguments:
@ -92,8 +100,8 @@ class Database(object):
self.execute(self.DATA_TABLE) self.execute(self.DATA_TABLE)
self.execute(self.ERRORS_TABLE) self.execute(self.ERRORS_TABLE)
class Task(object): class Task(object):
global db_filepath
def __init__(self, taskid): def __init__(self, taskid):
self.process = None self.process = None
@ -111,10 +119,11 @@ class Task(object):
type_ = unArrayizeValue(type_) type_ = unArrayizeValue(type_)
self.options[name] = _defaults.get(name, datatype[type_]) self.options[name] = _defaults.get(name, datatype[type_])
# Let sqlmap engine knows it is getting called by the API, the task ID and the file path of the IPC database # Let sqlmap engine knows it is getting called by the API,
# the task ID and the file path of the IPC database
self.options.api = True self.options.api = True
self.options.taskid = taskid self.options.taskid = taskid
self.options.database = db_filepath self.options.database = Database.filepath
# Enforce batch mode and disable coloring and ETA # Enforce batch mode and disable coloring and ETA
self.options.batch = True self.options.batch = True
@ -145,7 +154,8 @@ class Task(object):
shutil.rmtree(self.output_directory) shutil.rmtree(self.output_directory)
def engine_start(self): def engine_start(self):
self.process = Popen("python sqlmap.py --pickled-options %s" % base64pickle(self.options), shell=True, stdin=PIPE, close_fds=False) self.process = Popen("python sqlmap.py --pickled-options %s" % base64pickle(self.options),
shell=True, stdin=PIPE, close_fds=False)
def engine_stop(self): def engine_stop(self):
if self.process: if self.process:
@ -172,6 +182,7 @@ class Task(object):
def engine_has_terminated(self): def engine_has_terminated(self):
return isinstance(self.engine_get_returncode(), int) return isinstance(self.engine_get_returncode(), int)
# Wrapper functions for sqlmap engine # Wrapper functions for sqlmap engine
class StdDbOut(object): class StdDbOut(object):
def __init__(self, taskid, messagetype="stdout"): def __init__(self, taskid, messagetype="stdout"):
@ -194,25 +205,27 @@ class StdDbOut(object):
# Ignore all non-relevant messages # Ignore all non-relevant messages
return return
output = conf.database_cursor.execute("SELECT id, status, value FROM data WHERE taskid = ? AND content_type = ?", output = conf.database_cursor.execute(
"SELECT id, status, value FROM data WHERE taskid = ? AND content_type = ?",
(self.taskid, content_type)) (self.taskid, content_type))
#print >>sys.__stdout__, "output: %s\nvalue: %s\nstatus: %d\ncontent_type: %d\nkb.partRun: %s\n--------------" % (output, value, status, content_type, kb.partRun)
# Delete partial output from IPC database if we have got a complete output # Delete partial output from IPC database if we have got a complete output
if status == CONTENT_STATUS.COMPLETE: if status == CONTENT_STATUS.COMPLETE:
if len(output) > 0: if len(output) > 0:
for index in xrange(0, len(output)): for index in xrange(len(output)):
conf.database_cursor.execute("DELETE FROM data WHERE id = ?", (output[index][0],)) conf.database_cursor.execute("DELETE FROM data WHERE id = ?",
(output[index][0],))
conf.database_cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", (self.taskid, status, content_type, jsonize(value))) conf.database_cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)",
(self.taskid, status, content_type, jsonize(value)))
if kb.partRun: if kb.partRun:
kb.partRun = None kb.partRun = None
elif status == CONTENT_STATUS.IN_PROGRESS: elif status == CONTENT_STATUS.IN_PROGRESS:
if len(output) == 0: if len(output) == 0:
conf.database_cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", conf.database_cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)",
(self.taskid, status, content_type, jsonize(value))) (self.taskid, status, content_type,
jsonize(value)))
else: else:
new_value = "%s%s" % (dejsonize(output[0][2]), value) new_value = "%s%s" % (dejsonize(output[0][2]), value)
conf.database_cursor.execute("UPDATE data SET value = ? WHERE id = ?", conf.database_cursor.execute("UPDATE data SET value = ? WHERE id = ?",
@ -230,6 +243,7 @@ class StdDbOut(object):
def seek(self): def seek(self):
pass pass
class LogRecorder(logging.StreamHandler): class LogRecorder(logging.StreamHandler):
def emit(self, record): def emit(self, record):
""" """
@ -240,6 +254,7 @@ class LogRecorder(logging.StreamHandler):
(conf.taskid, time.strftime("%X"), record.levelname, (conf.taskid, time.strftime("%X"), record.levelname,
record.msg % record.args if record.args else record.msg)) record.msg % record.args if record.args else record.msg))
def setRestAPILog(): def setRestAPILog():
if hasattr(conf, "api"): if hasattr(conf, "api"):
conf.database_cursor = Database(conf.database) conf.database_cursor = Database(conf.database)
@ -250,13 +265,11 @@ def setRestAPILog():
LOGGER_RECORDER = LogRecorder() LOGGER_RECORDER = LogRecorder()
logger.addHandler(LOGGER_RECORDER) logger.addHandler(LOGGER_RECORDER)
# Generic functions # Generic functions
def is_admin(taskid): def is_admin(taskid):
global adminid return DataStore.admin_id == taskid
if adminid != taskid:
return False
else:
return True
@hook("after_request") @hook("after_request")
def security_headers(json_header=True): def security_headers(json_header=True):
@ -277,22 +290,26 @@ def security_headers(json_header=True):
# HTTP Status Code functions # # HTTP Status Code functions #
############################## ##############################
@error(401) # Access Denied
@return_error(401) # Access Denied
def error401(error=None): def error401(error=None):
security_headers(False) security_headers(False)
return "Access denied" return "Access denied"
@error(404) # Not Found
@return_error(404) # Not Found
def error404(error=None): def error404(error=None):
security_headers(False) security_headers(False)
return "Nothing here" return "Nothing here"
@error(405) # Method Not Allowed (e.g. when requesting a POST method via GET)
@return_error(405) # Method Not Allowed (e.g. when requesting a POST method via GET)
def error405(error=None): def error405(error=None):
security_headers(False) security_headers(False)
return "Method not allowed" return "Method not allowed"
@error(500) # Internal Server Error
@return_error(500) # Internal Server Error
def error500(error=None): def error500(error=None):
security_headers(False) security_headers(False)
return "Internal server error" return "Internal server error"
@ -301,208 +318,225 @@ def error500(error=None):
# Task management functions # # Task management functions #
############################# #############################
# Users' methods # Users' methods
@get("/task/new") @get("/task/new")
def task_new(): def task_new():
""" """
Create new task ID Create new task ID
""" """
global tasks
taskid = hexencode(os.urandom(8)) taskid = hexencode(os.urandom(8))
tasks[taskid] = Task(taskid) DataStore.tasks[taskid] = Task(taskid)
logger.debug(" [%s] Created new task" % taskid)
return jsonize({"success": True, "taskid": taskid})
logger.debug("Created new task ID: %s" % taskid)
return jsonize({"taskid": taskid})
@get("/task/<taskid>/delete") @get("/task/<taskid>/delete")
def task_delete(taskid): def task_delete(taskid):
""" """
Delete own task ID Delete own task ID
""" """
if taskid in tasks: if taskid in DataStore.tasks:
tasks[taskid].clean_filesystem() DataStore.tasks[taskid].clean_filesystem()
tasks.pop(taskid) DataStore.tasks.pop(taskid)
logger.debug("Deleted task ID: %s" % taskid) logger.debug("[%s] Deleted task" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
else: else:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to task_delete()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
################### ###################
# Admin functions # # Admin functions #
################### ###################
@get("/admin/<taskid>/list") @get("/admin/<taskid>/list")
def task_list(taskid): def task_list(taskid):
""" """
List task pull List task pull
""" """
if is_admin(taskid): if is_admin(taskid):
logger.debug("Listed task pull") logger.debug("[%s] Listed task pool" % taskid)
task_list = list(tasks) tasks = list(DataStore.tasks)
return jsonize({"tasks": task_list, "tasks_num": len(tasks)}) return jsonize({"success": True, "tasks": tasks, "tasks_num": len(tasks)})
else: else:
abort(401) logger.warning("[%s] Unauthorized call to task_list()" % taskid)
return jsonize({"success": False, "message": "Unauthorized"})
@get("/admin/<taskid>/flush") @get("/admin/<taskid>/flush")
def task_flush(taskid): def task_flush(taskid):
""" """
Flush task spool (delete all tasks) Flush task spool (delete all tasks)
""" """
global tasks
if is_admin(taskid): if is_admin(taskid):
for task in tasks: for task in DataStore.tasks:
tasks[task].clean_filesystem() DataStore.tasks[task].clean_filesystem()
tasks = dict() DataStore.tasks = dict()
logger.debug("Flushed task pull") logger.debug("[%s] Flushed task pool" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
else: else:
abort(401) logger.warning("[%s] Unauthorized call to task_flush()" % taskid)
return jsonize({"success": False, "message": "Unauthorized"})
################################## ##################################
# sqlmap core interact functions # # sqlmap core interact functions #
################################## ##################################
# Handle task's options # Handle task's options
@get("/option/<taskid>/list") @get("/option/<taskid>/list")
def option_list(taskid): def option_list(taskid):
""" """
List options for a certain task ID List options for a certain task ID
""" """
if taskid not in tasks: if taskid not in DataStore.tasks:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to option_list()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
logger.debug("[%s] Listed task options" % taskid)
return jsonize({"success": True, "options": DataStore.tasks[taskid].get_options()})
return jsonize({"options": tasks[taskid].get_options()})
@post("/option/<taskid>/get") @post("/option/<taskid>/get")
def option_get(taskid): def option_get(taskid):
""" """
Get the value of an option (command line switch) for a certain task ID Get the value of an option (command line switch) for a certain task ID
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to option_get()" % taskid)
if taskid not in tasks: return jsonize({"success": False, "message": "Invalid task ID"})
abort(500, "Invalid task ID")
option = request.json.get("option", "") option = request.json.get("option", "")
if option in tasks[taskid].options: if option in DataStore.tasks[taskid].options:
return jsonize({option: tasks[taskid].get_option(option)}) logger.debug("[%s] Retrieved value for option %s" % (taskid, option))
return jsonize({"success": True, option: DataStore.tasks[taskid].get_option(option)})
else: else:
return jsonize({option: "not set"}) logger.debug("[%s] Requested value for unknown option %s" % (taskid, option))
return jsonize({"success": False, "message": "Unknown option", option: "not set"})
@post("/option/<taskid>/set") @post("/option/<taskid>/set")
def option_set(taskid): def option_set(taskid):
""" """
Set an option (command line switch) for a certain task ID Set an option (command line switch) for a certain task ID
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to option_set()" % taskid)
if taskid not in tasks: return jsonize({"success": False, "message": "Invalid task ID"})
abort(500, "Invalid task ID")
for option, value in request.json.items(): for option, value in request.json.items():
tasks[taskid].set_option(option, value) DataStore.tasks[taskid].set_option(option, value)
logger.debug("[%s] Requested to set options" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
# Handle scans # Handle scans
@post("/scan/<taskid>/start") @post("/scan/<taskid>/start")
def scan_start(taskid): def scan_start(taskid):
""" """
Launch a scan Launch a scan
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_start()" % taskid)
if taskid not in tasks: return jsonize({"success": False, "message": "Invalid task ID"})
abort(500, "Invalid task ID")
tasks[taskid].reset_options()
# Initialize sqlmap engine's options with user's provided options, if any # Initialize sqlmap engine's options with user's provided options, if any
for option, value in request.json.items(): for option, value in request.json.items():
tasks[taskid].set_option(option, value) DataStore.tasks[taskid].set_option(option, value)
# Overwrite output directory value to a temporary directory # Overwrite output directory value to a temporary directory
tasks[taskid].set_output_directory() DataStore.tasks[taskid].set_output_directory()
# Launch sqlmap engine in a separate process # Launch sqlmap engine in a separate process
tasks[taskid].engine_start() DataStore.tasks[taskid].engine_start()
logger.debug("[%s] Started scan" % taskid)
return jsonize({"success": True, "engineid": DataStore.tasks[taskid].engine_get_id()})
logger.debug("Started scan for task ID %s" % taskid)
return jsonize({"success": True, "engineid": tasks[taskid].engine_get_id()})
@get("/scan/<taskid>/stop") @get("/scan/<taskid>/stop")
def scan_stop(taskid): def scan_stop(taskid):
""" """
Stop a scan Stop a scan
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_stop()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
if taskid not in tasks: DataStore.tasks[taskid].engine_stop()
abort(500, "Invalid task ID")
tasks[taskid].engine_stop() logger.debug("[%s] Stopped scan" % taskid)
logger.debug("Stopped scan for task ID %s" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
@get("/scan/<taskid>/kill") @get("/scan/<taskid>/kill")
def scan_kill(taskid): def scan_kill(taskid):
""" """
Kill a scan Kill a scan
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_kill()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
if taskid not in tasks: DataStore.tasks[taskid].engine_kill()
abort(500, "Invalid task ID")
tasks[taskid].engine_kill() logger.debug("[%s] Killed scan" % taskid)
logger.debug("Killed scan for task ID %s" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
@get("/scan/<taskid>/status") @get("/scan/<taskid>/status")
def scan_status(taskid): def scan_status(taskid):
""" """
Returns status of a scan Returns status of a scan
""" """
global tasks if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_status()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
if taskid not in tasks: status = "terminated" if DataStore.tasks[taskid].engine_has_terminated() is True else "running"
abort(500, "Invalid task ID")
status = "terminated" if tasks[taskid].engine_has_terminated() is True else "running" logger.debug("[%s] Retrieved scan status" % taskid)
return jsonize({
"success": True,
"status": status,
"returncode": DataStore.tasks[taskid].engine_get_returncode()
})
logger.debug("Requested status of scan for task ID %s" % taskid)
return jsonize({"status": status, "returncode": tasks[taskid].engine_get_returncode()})
@get("/scan/<taskid>/data") @get("/scan/<taskid>/data")
def scan_data(taskid): def scan_data(taskid):
""" """
Retrieve the data of a scan Retrieve the data of a scan
""" """
global db
global tasks
json_data_message = list() json_data_message = list()
json_errors_message = list() json_errors_message = list()
if taskid not in tasks: if taskid not in DataStore.tasks:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to scan_data()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
# Read all data from the IPC database for the taskid # Read all data from the IPC database for the taskid
for status, content_type, value in db.execute("SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC", (taskid,)): for status, content_type, value in DataStore.current_db.execute(
json_data_message.append({"status": status, "type": content_type, "value": dejsonize(value)}) "SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC",
(taskid,)):
json_data_message.append(
{"status": status, "type": content_type, "value": dejsonize(value)})
# Read all error messages from the IPC database # Read all error messages from the IPC database
for error in db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): for error in DataStore.current_db.execute(
"SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC",
(taskid,)):
json_errors_message.append(error) json_errors_message.append(error)
logger.debug("Retrieved data and error messages for scan for task ID %s" % taskid) logger.debug("[%s] Retrieved scan data and error messages" % taskid)
return jsonize({"data": json_data_message, "error": json_errors_message}) return jsonize({"success": True, "data": json_data_message, "error": json_errors_message})
# Functions to handle scans' logs # Functions to handle scans' logs
@get("/scan/<taskid>/log/<start>/<end>") @get("/scan/<taskid>/log/<start>/<end>")
@ -510,44 +544,49 @@ def scan_log_limited(taskid, start, end):
""" """
Retrieve a subset of log messages Retrieve a subset of log messages
""" """
global db
global tasks
json_log_messages = list() json_log_messages = list()
if taskid not in tasks: if taskid not in DataStore.tasks:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to scan_log_limited()")
return jsonize({"success": False, "message": "Invalid task ID"})
if not start.isdigit() or not end.isdigit() or end < start: if not start.isdigit() or not end.isdigit() or end < start:
abort(500, "Invalid start or end value, must be digits") logger.warning("[%s] Invalid start or end value provided to scan_log_limited()" % taskid)
return jsonize({"success": False, "message": "Invalid start or end value, must be digits"})
start = max(1, int(start)) start = max(1, int(start))
end = max(1, int(end)) end = max(1, int(end))
# Read a subset of log messages from the IPC database # Read a subset of log messages from the IPC database
for time_, level, message in db.execute("SELECT time, level, message FROM logs WHERE taskid = ? AND id >= ? AND id <= ? ORDER BY id ASC", (taskid, start, end)): for time_, level, message in DataStore.current_db.execute(
("SELECT time, level, message FROM logs WHERE "
"taskid = ? AND id >= ? AND id <= ? ORDER BY id ASC"),
(taskid, start, end)):
json_log_messages.append({"time": time_, "level": level, "message": message}) json_log_messages.append({"time": time_, "level": level, "message": message})
logger.debug("Retrieved subset of log messages for scan for task ID %s" % taskid) logger.debug("[%s] Retrieved scan log messages subset" % taskid)
return jsonize({"log": json_log_messages}) return jsonize({"success": True, "log": json_log_messages})
@get("/scan/<taskid>/log") @get("/scan/<taskid>/log")
def scan_log(taskid): def scan_log(taskid):
""" """
Retrieve the log messages Retrieve the log messages
""" """
global db
global tasks
json_log_messages = list() json_log_messages = list()
if taskid not in tasks: if taskid not in DataStore.tasks:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to scan_log()")
return jsonize({"success": False, "message": "Invalid task ID"})
# Read all log messages from the IPC database # Read all log messages from the IPC database
for time_, level, message in db.execute("SELECT time, level, message FROM logs WHERE taskid = ? ORDER BY id ASC", (taskid,)): for time_, level, message in DataStore.current_db.execute(
"SELECT time, level, message FROM logs WHERE taskid = ? ORDER BY id ASC", (taskid,)):
json_log_messages.append({"time": time_, "level": level, "message": message}) json_log_messages.append({"time": time_, "level": level, "message": message})
logger.debug("Retrieved log messages for scan for task ID %s" % taskid) logger.debug("[%s] Retrieved scan log messages" % taskid)
return jsonize({"log": json_log_messages}) return jsonize({"success": True, "log": json_log_messages})
# Function to handle files inside the output directory # Function to handle files inside the output directory
@get("/download/<taskid>/<target>/<filename:path>") @get("/download/<taskid>/<target>/<filename:path>")
@ -555,43 +594,47 @@ def download(taskid, target, filename):
""" """
Download a certain file from the file system Download a certain file from the file system
""" """
if taskid not in tasks: if taskid not in DataStore.tasks:
abort(500, "Invalid task ID") logger.warning("[%s] Invalid task ID provided to download()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
# Prevent file path traversal - the lame way # Prevent file path traversal - the lame way
if target.startswith("."): if ".." in target:
abort(500) logger.warning("[%s] Forbidden path (%s)" % (taskid, target))
return jsonize({"success": False, "message": "Forbidden path"})
path = os.path.join(paths.SQLMAP_OUTPUT_PATH, target) path = os.path.join(paths.SQLMAP_OUTPUT_PATH, target)
if os.path.exists(path): if os.path.exists(path):
return static_file(filename, root=path) logger.debug("[%s] Retrieved content of file %s" % (taskid, target))
with open(path, 'rb') as inf:
file_content = inf.read()
return jsonize({"success": True, "file": file_content.encode("base64")})
else: else:
abort(500, "File does not exist") logger.warning("[%s] File does not exist %s" % (taskid, target))
return jsonize({"success": False, "message": "File does not exist"})
def server(host="0.0.0.0", port=RESTAPI_SERVER_PORT): def server(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
""" """
REST-JSON API server REST-JSON API server
""" """
global adminid DataStore.admin_id = hexencode(os.urandom(16))
global db Database.filepath = tempfile.mkstemp(prefix="sqlmapipc-", text=False)[1]
global db_filepath
adminid = hexencode(os.urandom(16))
db_filepath = tempfile.mkstemp(prefix="sqlmapipc-", text=False)[1]
logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port)) logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port))
logger.info("Admin ID: %s" % adminid) logger.info("Admin ID: %s" % DataStore.admin_id)
logger.debug("IPC database: %s" % db_filepath) logger.debug("IPC database: %s" % Database.filepath)
# Initialize IPC database # Initialize IPC database
db = Database() DataStore.current_db = Database()
db.connect() DataStore.current_db.connect()
db.init() DataStore.current_db.init()
# Run RESTful API # Run RESTful API
run(host=host, port=port, quiet=True, debug=False) run(host=host, port=port, quiet=True, debug=False)
def client(host=RESTAPI_SERVER_HOST, port=RESTAPI_SERVER_PORT): def client(host=RESTAPI_SERVER_HOST, port=RESTAPI_SERVER_PORT):
""" """
REST-JSON API client REST-JSON API client
@ -602,6 +645,8 @@ def client(host=RESTAPI_SERVER_HOST, port=RESTAPI_SERVER_PORT):
# TODO: write a simple client with requests, for now use curl from command line # TODO: write a simple client with requests, for now use curl from command line
logger.error("Not yet implemented, use curl from command line instead for now, for example:") logger.error("Not yet implemented, use curl from command line instead for now, for example:")
print "\n\t$ curl http://%s:%d/task/new" % (host, port) print "\n\t$ curl http://%s:%d/task/new" % (host, port)
print "\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testphp.vulnweb.com/artists.php?artist=1\"}' http://%s:%d/scan/:taskid/start" % (host, port) print ("\t$ curl -H \"Content-Type: application/json\" "
"-X POST -d '{\"url\": \"http://testphp.vulnweb.com/artists.php?artist=1\"}' "
"http://%s:%d/scan/:taskid/start") % (host, port)
print "\t$ curl http://%s:%d/scan/:taskid/data" % (host, port) print "\t$ curl http://%s:%d/scan/:taskid/data" % (host, port)
print "\t$ curl http://%s:%d/scan/:taskid/log\n" % (host, port) print "\t$ curl http://%s:%d/scan/:taskid/log\n" % (host, port)