Merge branch 'master' of github.com:sqlmapproject/sqlmap

This commit is contained in:
Miroslav Stampar 2013-01-29 20:50:40 +01:00
commit 95b922309c
10 changed files with 176 additions and 114 deletions

View File

@ -30,12 +30,14 @@ from lib.core.common import unhandledExceptionMessage
from lib.core.exception import SqlmapBaseException from lib.core.exception import SqlmapBaseException
from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapSilentQuitException
from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapUserQuitException
from lib.core.option import initOptions
from lib.core.option import init from lib.core.option import init
from lib.core.profiling import profile from lib.core.profiling import profile
from lib.core.settings import LEGAL_DISCLAIMER from lib.core.settings import LEGAL_DISCLAIMER
from lib.core.testing import smokeTest from lib.core.testing import smokeTest
from lib.core.testing import liveTest from lib.core.testing import liveTest
from lib.parse.cmdline import cmdLineParser from lib.parse.cmdline import cmdLineParser
from lib.utils.api import setRestAPILog
from lib.utils.api import StdDbOut from lib.utils.api import StdDbOut
def modulePath(): def modulePath():
@ -57,19 +59,22 @@ def main():
# Store original command line options for possible later restoration # Store original command line options for possible later restoration
cmdLineOptions.update(cmdLineParser().__dict__) cmdLineOptions.update(cmdLineParser().__dict__)
init(cmdLineOptions) initOptions(cmdLineOptions)
if hasattr(conf, "api"): if hasattr(conf, "api"):
# Overwrite system standard output and standard error to write # Overwrite system standard output and standard error to write
# to an IPC database # to an IPC database
sys.stdout = StdDbOut(conf.taskid, messagetype="stdout") sys.stdout = StdDbOut(conf.taskid, messagetype="stdout")
sys.stderr = StdDbOut(conf.taskid, messagetype="stderr") sys.stderr = StdDbOut(conf.taskid, messagetype="stderr")
setRestAPILog()
banner() banner()
dataToStdout("[!] legal disclaimer: %s\n\n" % LEGAL_DISCLAIMER, forceOutput=True) dataToStdout("[!] legal disclaimer: %s\n\n" % LEGAL_DISCLAIMER, forceOutput=True)
dataToStdout("[*] starting at %s\n\n" % time.strftime("%X"), forceOutput=True) dataToStdout("[*] starting at %s\n\n" % time.strftime("%X"), forceOutput=True)
init()
if conf.profile: if conf.profile:
profile() profile()
elif conf.smokeTest: elif conf.smokeTest:

View File

@ -66,9 +66,10 @@ def main():
test_counts = [] test_counts = []
attachments = {} attachments = {}
command_line = "python /opt/sqlmap/sqlmap.py --live-test" proc = subprocess.Popen("python /opt/sqlmap/sqlmap.py --update", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc = subprocess.Popen(command_line, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait()
proc = subprocess.Popen("python /opt/sqlmap/sqlmap.py --live-test", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.wait() proc.wait()
stdout, stderr = proc.communicate() stdout, stderr = proc.communicate()

View File

@ -12,6 +12,7 @@ from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.data import paths from lib.core.data import paths
from lib.core.enums import API_CONTENT_TYPE
from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapNoneDataException
from lib.core.exception import SqlmapUnsupportedDBMSException from lib.core.exception import SqlmapUnsupportedDBMSException
from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import SUPPORTED_DBMS
@ -77,7 +78,7 @@ def action():
if conf.getPasswordHashes: if conf.getPasswordHashes:
try: try:
conf.dumper.userSettings("database management system users password hashes", conf.dumper.userSettings("database management system users password hashes",
conf.dbmsHandler.getPasswordHashes(), "password hash") conf.dbmsHandler.getPasswordHashes(), "password hash", API_CONTENT_TYPE.PASSWORDS)
except SqlmapNoneDataException, ex: except SqlmapNoneDataException, ex:
logger.critical(ex) logger.critical(ex)
except: except:
@ -86,7 +87,7 @@ def action():
if conf.getPrivileges: if conf.getPrivileges:
try: try:
conf.dumper.userSettings("database management system users privileges", conf.dumper.userSettings("database management system users privileges",
conf.dbmsHandler.getPrivileges(), "privilege") conf.dbmsHandler.getPrivileges(), "privilege", API_CONTENT_TYPE.PRIVILEGES)
except SqlmapNoneDataException, ex: except SqlmapNoneDataException, ex:
logger.critical(ex) logger.critical(ex)
except: except:
@ -95,7 +96,7 @@ def action():
if conf.getRoles: if conf.getRoles:
try: try:
conf.dumper.userSettings("database management system users roles", conf.dumper.userSettings("database management system users roles",
conf.dbmsHandler.getRoles(), "role") conf.dbmsHandler.getRoles(), "role", API_CONTENT_TYPE.ROLES)
except SqlmapNoneDataException, ex: except SqlmapNoneDataException, ex:
logger.critical(ex) logger.critical(ex)
except: except:
@ -111,10 +112,10 @@ def action():
conf.dumper.dbTables(tableExists(paths.COMMON_TABLES)) conf.dumper.dbTables(tableExists(paths.COMMON_TABLES))
if conf.getSchema: if conf.getSchema:
conf.dumper.dbTableColumns(conf.dbmsHandler.getSchema()) conf.dumper.dbTableColumns(conf.dbmsHandler.getSchema(), API_CONTENT_TYPE.SCHEMA)
if conf.getColumns: if conf.getColumns:
conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns()) conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns(), API_CONTENT_TYPE.COLUMNS)
if conf.getCount: if conf.getCount:
conf.dumper.dbTablesCount(conf.dbmsHandler.getCount()) conf.dumper.dbTablesCount(conf.dbmsHandler.getCount())

View File

@ -36,6 +36,7 @@ from lib.core.common import urldecode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.enums import API_CONTENT_TYPE
from lib.core.enums import HASHDB_KEYS from lib.core.enums import HASHDB_KEYS
from lib.core.enums import HEURISTIC_TEST from lib.core.enums import HEURISTIC_TEST
from lib.core.enums import HTTPMETHOD from lib.core.enums import HTTPMETHOD
@ -151,9 +152,11 @@ def _showInjections():
header = "sqlmap identified the following injection points with " header = "sqlmap identified the following injection points with "
header += "a total of %d HTTP(s) requests" % kb.testQueryCount header += "a total of %d HTTP(s) requests" % kb.testQueryCount
data = "".join(set(map(lambda x: _formatInjection(x), kb.injections))).rstrip("\n") if hasattr(conf, "api"):
conf.dumper.string("", kb.injections, content_type=API_CONTENT_TYPE.TECHNIQUES)
conf.dumper.string(header, data) else:
data = "".join(set(map(lambda x: _formatInjection(x), kb.injections))).rstrip("\n")
conf.dumper.string(header, data)
if conf.tamper: if conf.tamper:
warnMsg = "changes made by tampering scripts are not " warnMsg = "changes made by tampering scripts are not "

View File

@ -760,7 +760,8 @@ def dataToStdout(data, forceOutput=False, bold=False, content_type=None, status=
message = data message = data
if hasattr(conf, "api"): if hasattr(conf, "api"):
sys.stdout.write(message, status=status, content_type=content_type) if content_type and status:
sys.stdout.write(message, status, content_type)
else: else:
sys.stdout.write(setColor(message, bold)) sys.stdout.write(setColor(message, bold))
@ -772,7 +773,7 @@ def dataToStdout(data, forceOutput=False, bold=False, content_type=None, status=
if kb.get("multiThreadMode"): if kb.get("multiThreadMode"):
logging._releaseLock() logging._releaseLock()
kb.prependFlag = len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n' kb.prependFlag = isinstance(data, basestring) and (len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n')
def dataToTrafficFile(data): def dataToTrafficFile(data):
if not conf.trafficFile: if not conf.trafficFile:

View File

@ -46,7 +46,6 @@ class Dump(object):
""" """
This class defines methods used to parse and output the results This class defines methods used to parse and output the results
of SQL injection actions of SQL injection actions
""" """
def __init__(self): def __init__(self):
@ -85,8 +84,8 @@ class Dump(object):
def getOutputFile(self): def getOutputFile(self):
return self._outputFile return self._outputFile
def singleString(self, data): def singleString(self, data, content_type=None):
self._write(data) self._write(data, content_type=content_type)
def string(self, header, data, content_type=None, sort=True): def string(self, header, data, content_type=None, sort=True):
kb.stickyLevel = None kb.stickyLevel = None
@ -161,9 +160,6 @@ class Dump(object):
def userSettings(self, header, userSettings, subHeader, content_type=None): def userSettings(self, header, userSettings, subHeader, content_type=None):
self._areAdmins = set() self._areAdmins = set()
if userSettings:
self._write("%s:" % header)
if isinstance(userSettings, (tuple, list, set)): if isinstance(userSettings, (tuple, list, set)):
self._areAdmins = userSettings[1] self._areAdmins = userSettings[1]
userSettings = userSettings[0] userSettings = userSettings[0]
@ -171,6 +167,13 @@ class Dump(object):
users = userSettings.keys() users = userSettings.keys()
users.sort(key=lambda x: x.lower() if isinstance(x, basestring) else x) users.sort(key=lambda x: x.lower() if isinstance(x, basestring) else x)
if hasattr(conf, "api"):
self._write(userSettings, content_type=content_type)
return
if userSettings:
self._write("%s:" % header)
for user in users: for user in users:
settings = userSettings[user] settings = userSettings[user]
@ -196,8 +199,12 @@ class Dump(object):
def dbs(self, dbs): def dbs(self, dbs):
self.lister("available databases", dbs, content_type=API_CONTENT_TYPE.DBS) self.lister("available databases", dbs, content_type=API_CONTENT_TYPE.DBS)
def dbTables(self, dbTables, content_type=API_CONTENT_TYPE.TABLES): def dbTables(self, dbTables):
if isinstance(dbTables, dict) and len(dbTables) > 0: if isinstance(dbTables, dict) and len(dbTables) > 0:
if hasattr(conf, "api"):
self._write(dbTables, content_type=API_CONTENT_TYPE.TABLES)
return
maxlength = 0 maxlength = 0
for tables in dbTables.values(): for tables in dbTables.values():
@ -230,12 +237,16 @@ class Dump(object):
self._write("+%s+\n" % lines) self._write("+%s+\n" % lines)
elif dbTables is None or len(dbTables) == 0: elif dbTables is None or len(dbTables) == 0:
self.singleString("No tables found") self.singleString("No tables found", content_type=API_CONTENT_TYPE.TABLES)
else: else:
self.string("tables", dbTables) self.string("tables", dbTables, content_type=API_CONTENT_TYPE.TABLES)
def dbTableColumns(self, tableColumns, content_type=API_CONTENT_TYPE.COLUMNS): def dbTableColumns(self, tableColumns, content_type=None):
if isinstance(tableColumns, dict) and len(tableColumns) > 0: if isinstance(tableColumns, dict) and len(tableColumns) > 0:
if hasattr(conf, "api"):
self._write(tableColumns, content_type=content_type)
return
for db, tables in tableColumns.items(): for db, tables in tableColumns.items():
if not db: if not db:
db = "All" db = "All"
@ -301,8 +312,12 @@ class Dump(object):
else: else:
self._write("+%s+\n" % lines1) self._write("+%s+\n" % lines1)
def dbTablesCount(self, dbTables, content_type=API_CONTENT_TYPE.COUNT): def dbTablesCount(self, dbTables):
if isinstance(dbTables, dict) and len(dbTables) > 0: if isinstance(dbTables, dict) and len(dbTables) > 0:
if hasattr(conf, "api"):
self._write(dbTables, content_type=API_CONTENT_TYPE.COUNT)
return
maxlength1 = len("Table") maxlength1 = len("Table")
maxlength2 = len("Entries") maxlength2 = len("Entries")
@ -343,7 +358,7 @@ class Dump(object):
else: else:
logger.error("unable to retrieve the number of entries for any table") logger.error("unable to retrieve the number of entries for any table")
def dbTableValues(self, tableValues, content_type=API_CONTENT_TYPE.DUMP_TABLE): def dbTableValues(self, tableValues):
replication = None replication = None
rtable = None rtable = None
dumpFP = None dumpFP = None
@ -356,6 +371,10 @@ class Dump(object):
db = "All" db = "All"
table = tableValues["__infos__"]["table"] table = tableValues["__infos__"]["table"]
if hasattr(conf, "api"):
self._write(tableValues, content_type=API_CONTENT_TYPE.DUMP_TABLE)
return
if conf.dumpFormat == DUMP_FORMAT.SQLITE: if conf.dumpFormat == DUMP_FORMAT.SQLITE:
replication = Replication("%s%s%s.sqlite3" % (conf.dumpPath, os.sep, unsafeSQLIdentificatorNaming(db))) replication = Replication("%s%s%s.sqlite3" % (conf.dumpPath, os.sep, unsafeSQLIdentificatorNaming(db)))
elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML): elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML):
@ -549,7 +568,11 @@ class Dump(object):
dumpFP.close() dumpFP.close()
logger.info("table '%s.%s' dumped to %s file '%s'" % (db, table, conf.dumpFormat, dumpFileName)) logger.info("table '%s.%s' dumped to %s file '%s'" % (db, table, conf.dumpFormat, dumpFileName))
def dbColumns(self, dbColumnsDict, colConsider, dbs, content_type=API_CONTENT_TYPE.COLUMNS): def dbColumns(self, dbColumnsDict, colConsider, dbs):
if hasattr(conf, "api"):
self._write(dbColumnsDict, content_type=API_CONTENT_TYPE.COLUMNS)
return
for column in dbColumnsDict.keys(): for column in dbColumnsDict.keys():
if colConsider == "1": if colConsider == "1":
colConsiderStr = "s like '" + column + "' were" colConsiderStr = "s like '" + column + "' were"

View File

@ -246,29 +246,30 @@ class WEB_API:
class API_CONTENT_TYPE: class API_CONTENT_TYPE:
TECHNIQUES = 0 TECHNIQUES = 0
BANNER = 1 DBMS_FINGERPRINT = 1
CURRENT_USER = 2 BANNER = 2
CURRENT_DB = 3 CURRENT_USER = 3
HOSTNAME = 4 CURRENT_DB = 4
IS_DBA = 5 HOSTNAME = 5
USERS = 6 IS_DBA = 6
PASSWORDS = 7 USERS = 7
PRIVILEGES = 8 PASSWORDS = 8
ROLES = 9 PRIVILEGES = 9
DBS = 10 ROLES = 10
TABLES = 11 DBS = 11
COLUMNS = 12 TABLES = 12
SCHEMA = 13 COLUMNS = 13
COUNT = 14 SCHEMA = 14
DUMP_TABLE = 15 COUNT = 15
SEARCH = 16 DUMP_TABLE = 16
SQL_QUERY = 17 SEARCH = 17
COMMON_TABLES = 18 SQL_QUERY = 18
COMMON_COLUMNS = 19 COMMON_TABLES = 19
FILE_READ = 20 COMMON_COLUMNS = 20
FILE_WRITE = 21 FILE_READ = 21
OS_CMD = 22 FILE_WRITE = 22
REG_READ = 23 OS_CMD = 23
REG_READ = 24
class API_CONTENT_STATUS: class API_CONTENT_STATUS:
IN_PROGRESS = 0 IN_PROGRESS = 0

View File

@ -136,7 +136,6 @@ from lib.request.httpshandler import HTTPSHandler
from lib.request.rangehandler import HTTPRangeHandler from lib.request.rangehandler import HTTPRangeHandler
from lib.request.redirecthandler import SmartRedirectHandler from lib.request.redirecthandler import SmartRedirectHandler
from lib.request.templates import getPageTemplate from lib.request.templates import getPageTemplate
from lib.utils.api import setRestAPILog
from lib.utils.crawler import crawl from lib.utils.crawler import crawl
from lib.utils.deps import checkDependencies from lib.utils.deps import checkDependencies
from lib.utils.google import Google from lib.utils.google import Google
@ -2052,21 +2051,22 @@ def _resolveCrossReferences():
lib.core.common.getPageTemplate = getPageTemplate lib.core.common.getPageTemplate = getPageTemplate
lib.core.convert.singleTimeWarnMessage = singleTimeWarnMessage lib.core.convert.singleTimeWarnMessage = singleTimeWarnMessage
def init(inputOptions=AttribDict(), overrideOptions=False): def initOptions(inputOptions=AttribDict(), overrideOptions=False):
"""
Set attributes into both configuration and knowledge base singletons
based upon command line and configuration file options.
"""
if not inputOptions.disableColoring: if not inputOptions.disableColoring:
coloramainit() coloramainit()
_setConfAttributes() _setConfAttributes()
_setKnowledgeBaseAttributes() _setKnowledgeBaseAttributes()
_mergeOptions(inputOptions, overrideOptions) _mergeOptions(inputOptions, overrideOptions)
def init():
"""
Set attributes into both configuration and knowledge base singletons
based upon command line and configuration file options.
"""
_useWizardInterface() _useWizardInterface()
setVerbosity() setVerbosity()
setRestAPILog()
_saveCmdline() _saveCmdline()
_setRequestFromFile() _setRequestFromFile()
_cleanupOptions() _cleanupOptions()

View File

@ -29,6 +29,7 @@ from lib.core.exception import SqlmapBaseException
from lib.core.exception import SqlmapNotVulnerableException from lib.core.exception import SqlmapNotVulnerableException
from lib.core.log import LOGGER_HANDLER from lib.core.log import LOGGER_HANDLER
from lib.core.option import init from lib.core.option import init
from lib.core.option import initOptions
from lib.core.optiondict import optDict from lib.core.optiondict import optDict
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.parse.cmdline import cmdLineParser from lib.parse.cmdline import cmdLineParser
@ -243,7 +244,8 @@ def initCase(switches=None):
if key in cmdLineOptions.__dict__: if key in cmdLineOptions.__dict__:
cmdLineOptions.__dict__[key] = value cmdLineOptions.__dict__[key] = value
init(cmdLineOptions, True) initOptions(cmdLineOptions, True)
init()
def cleanCase(): def cleanCase():
shutil.rmtree(paths.SQLMAP_OUTPUT_PATH, True) shutil.rmtree(paths.SQLMAP_OUTPUT_PATH, True)

View File

@ -60,10 +60,10 @@ class Database(object):
def create(self): def create(self):
_, self.database = tempfile.mkstemp(prefix="sqlmapipc-", text=False) _, self.database = tempfile.mkstemp(prefix="sqlmapipc-", text=False)
logger.info("IPC database is %s" % self.database) logger.debug("IPC database: %s" % self.database)
def connect(self): def connect(self):
self.connection = sqlite3.connect(self.database, timeout=1, isolation_level=None) self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None)
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
def disconnect(self): def disconnect(self):
@ -132,18 +132,32 @@ 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) 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:
self.process.terminate() return self.process.terminate()
else:
return None
def engine_kill(self): def engine_kill(self):
if self.process: if self.process:
self.process.kill() return self.process.kill()
else:
return None
def engine_get_pid(self): def engine_get_id(self):
return self.processid.pid if self.process:
return self.process.pid
else:
return None
def engine_get_returncode(self):
self.process.poll()
return self.process.returncode
def engine_has_terminated(self):
return isinstance(self.engine_get_returncode(), int)
# Wrapper functions for sqlmap engine # Wrapper functions for sqlmap engine
class StdDbOut(object): class StdDbOut(object):
@ -162,9 +176,13 @@ class StdDbOut(object):
def write(self, value, status=None, content_type=None): def write(self, value, status=None, content_type=None):
if self.messagetype == "stdout": if self.messagetype == "stdout":
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, base64pickle(value)))
conf.database_cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)",
(self.taskid, status, content_type, jsonize(value)))
else: else:
conf.database_cursor.execute("INSERT INTO errors VALUES(NULL, ?, ?)", (self.taskid, value)) conf.database_cursor.execute("INSERT INTO errors VALUES(NULL, ?, ?)",
(self.taskid, str(value) if value else ""))
def flush(self): def flush(self):
pass pass
@ -257,35 +275,42 @@ def task_new():
taskid = hexencode(os.urandom(8)) taskid = hexencode(os.urandom(8))
tasks[taskid] = Task(taskid) tasks[taskid] = Task(taskid)
logger.debug("Created new task ID: %s" % taskid)
return jsonize({"taskid": taskid}) return jsonize({"taskid": taskid})
@get("/task/<taskid>/destroy") @get("/task/<taskid>/delete")
def task_destroy(taskid): def task_delete(taskid):
""" """
Destroy own task ID Delete own task ID
""" """
if taskid in tasks: if taskid in tasks:
tasks[taskid].clean_filesystem() tasks[taskid].clean_filesystem()
tasks.pop(taskid) tasks.pop(taskid)
logger.debug("Deleted task ID: %s" % taskid)
return jsonize({"success": True}) return jsonize({"success": True})
else: else:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
# Admin's methods ###################
@get("/task/<taskid>/list") # Admin functions #
###################
@get("/admin/<taskid>/list")
def task_list(taskid): def task_list(taskid):
""" """
List all active tasks List task pull
""" """
if is_admin(taskid): if is_admin(taskid):
return jsonize({"tasks": tasks}) logger.debug("Listed task pull")
return jsonize({"tasks": tasks, "tasks_num": len(tasks)})
else: else:
abort(401) abort(401)
@get("/task/<taskid>/flush") @get("/admin/<taskid>/flush")
def task_flush(taskid): def task_flush(taskid):
""" """
Flush task spool (destroy all tasks) Flush task spool (delete all tasks)
""" """
global tasks global tasks
@ -294,6 +319,7 @@ def task_flush(taskid):
tasks[task].clean_filesystem() tasks[task].clean_filesystem()
tasks = dict() tasks = dict()
logger.debug("Flushed task pull")
return jsonize({"success": True}) return jsonize({"success": True})
else: else:
abort(401) abort(401)
@ -302,20 +328,7 @@ def task_flush(taskid):
# sqlmap core interact functions # # sqlmap core interact functions #
################################## ##################################
# Admin's methods # Handle task's options
@get("/status/<taskid>")
def status(taskid):
"""
Verify the status of the API as well as the core
"""
if is_admin(taskid):
tasks_num = len(tasks)
return jsonize({"tasks": tasks_num})
else:
abort(401)
# Functions to handle options
@get("/option/<taskid>/list") @get("/option/<taskid>/list")
def option_list(taskid): def option_list(taskid):
""" """
@ -324,13 +337,15 @@ def option_list(taskid):
if taskid not in tasks: if taskid not in tasks:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
return jsonize(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 tasks: if taskid not in tasks:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
@ -339,7 +354,7 @@ def option_get(taskid):
if option in tasks[taskid]: if option in tasks[taskid]:
return jsonize({option: tasks[taskid].get_option(option)}) return jsonize({option: tasks[taskid].get_option(option)})
else: else:
return jsonize({option: None}) return jsonize({option: "not set"})
@post("/option/<taskid>/set") @post("/option/<taskid>/set")
def option_set(taskid): def option_set(taskid):
@ -356,7 +371,7 @@ def option_set(taskid):
return jsonize({"success": True}) return jsonize({"success": True})
# Function to handle scans # Handle scans
@post("/scan/<taskid>/start") @post("/scan/<taskid>/start")
def scan_start(taskid): def scan_start(taskid):
""" """
@ -374,13 +389,11 @@ def scan_start(taskid):
# Overwrite output directory value to a temporary directory # Overwrite output directory value to a temporary directory
tasks[taskid].set_output_directory() tasks[taskid].set_output_directory()
# Launch sqlmap engine in a separate thread # Launch sqlmap engine in a separate process
logger.debug("starting a scan for task ID %s" % taskid)
# Launch sqlmap engine
tasks[taskid].engine_start() tasks[taskid].engine_start()
return jsonize({"success": True}) 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):
@ -392,7 +405,10 @@ def scan_stop(taskid):
if taskid not in tasks: if taskid not in tasks:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
return jsonize({"success": tasks[taskid].engine_stop()}) tasks[taskid].engine_stop()
logger.debug("Stopped scan for task ID %s" % taskid)
return jsonize({"success": True})
@get("/scan/<taskid>/kill") @get("/scan/<taskid>/kill")
def scan_kill(taskid): def scan_kill(taskid):
@ -404,22 +420,25 @@ def scan_kill(taskid):
if taskid not in tasks: if taskid not in tasks:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
return jsonize({"success": tasks[taskid].engine_kill()}) tasks[taskid].engine_kill()
@get("/scan/<taskid>/delete") logger.debug("Killed scan for task ID %s" % taskid)
def scan_delete(taskid): return jsonize({"success": True})
@get("/scan/<taskid>/status")
def scan_status(taskid):
""" """
Delete a scan and corresponding temporary output directory and IPC database Returns status of a scan
""" """
global tasks global tasks
if taskid not in tasks: if taskid not in tasks:
abort(500, "Invalid task ID") abort(500, "Invalid task ID")
scan_stop(taskid) status = "terminated" if tasks[taskid].engine_has_terminated() is True else "running"
tasks[taskid].clean_filesystem()
return jsonize({"success": True}) 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):
@ -436,12 +455,14 @@ def scan_data(taskid):
# 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 db.execute("SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC", (taskid,)):
json_data_message.append([status, content_type, dejsonize(value)]) #json_data_message.append({"status": status, "type": content_type, "value": base64unpickle(value)})
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 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)
return jsonize({"data": json_data_message, "error": json_errors_message}) return jsonize({"data": json_data_message, "error": json_errors_message})
# Functions to handle scans' logs # Functions to handle scans' logs
@ -467,6 +488,7 @@ def scan_log_limited(taskid, start, end):
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 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)
return jsonize({"log": json_log_messages}) return jsonize({"log": json_log_messages})
@get("/scan/<taskid>/log") @get("/scan/<taskid>/log")
@ -485,6 +507,7 @@ def scan_log(taskid):
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 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)
return jsonize({"log": json_log_messages}) return jsonize({"log": json_log_messages})
# Function to handle files inside the output directory # Function to handle files inside the output directory
@ -505,7 +528,7 @@ def download(taskid, target, filename):
if os.path.exists(path): if os.path.exists(path):
return static_file(filename, root=path) return static_file(filename, root=path)
else: else:
abort(500) abort(500, "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):
""" """
@ -515,24 +538,26 @@ def server(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
global db global db
adminid = hexencode(os.urandom(16)) adminid = hexencode(os.urandom(16))
logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port))
logger.info("Admin ID: %s" % adminid)
# Initialize IPC database
db = Database() db = Database()
db.initialize() db.initialize()
logger.info("running REST-JSON API server at '%s:%d'.." % (host, port))
logger.info("the admin task ID is: %s" % adminid)
# Run RESTful API # Run RESTful API
run(host=host, port=port, quiet=False, 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
""" """
addr = "http://%s:%d" % (host, port) addr = "http://%s:%d" % (host, port)
logger.info("starting debug REST-JSON client to '%s'..." % addr) logger.info("Starting REST-JSON API client to '%s'..." % addr)
# 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/output" % (host, port) print "\t$ curl http://%s:%d/scan/:taskid/output" % (host, port)