mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2024-11-24 10:33:47 +03:00
Implements #2647 (Basic authorization for sqlmapapi)
This commit is contained in:
parent
68ee1f361b
commit
fac6712a35
|
@ -19,7 +19,7 @@ from lib.core.enums import DBMS_DIRECTORY_NAME
|
|||
from lib.core.enums import OS
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.1.7.31"
|
||||
VERSION = "1.1.8.0"
|
||||
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
||||
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
|
||||
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
|
||||
|
|
|
@ -7,6 +7,7 @@ See the file 'doc/COPYING' for copying permission
|
|||
"""
|
||||
|
||||
import contextlib
|
||||
import httplib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
@ -43,6 +44,7 @@ from lib.core.settings import RESTAPI_DEFAULT_ADDRESS
|
|||
from lib.core.settings import RESTAPI_DEFAULT_PORT
|
||||
from lib.core.subprocessng import Popen
|
||||
from lib.parse.cmdline import cmdLineParser
|
||||
from thirdparty.bottle.bottle import abort
|
||||
from thirdparty.bottle.bottle import error as return_error
|
||||
from thirdparty.bottle.bottle import get
|
||||
from thirdparty.bottle.bottle import hook
|
||||
|
@ -52,13 +54,13 @@ from thirdparty.bottle.bottle import response
|
|||
from thirdparty.bottle.bottle import run
|
||||
from thirdparty.bottle.bottle import server_names
|
||||
|
||||
|
||||
# global settings
|
||||
# Global data storage
|
||||
class DataStore(object):
|
||||
admin_id = ""
|
||||
current_db = None
|
||||
tasks = dict()
|
||||
|
||||
username = None
|
||||
password = None
|
||||
|
||||
# API objects
|
||||
class Database(object):
|
||||
|
@ -118,7 +120,6 @@ class Database(object):
|
|||
"taskid INTEGER, error TEXT"
|
||||
")")
|
||||
|
||||
|
||||
class Task(object):
|
||||
def __init__(self, taskid, remote_addr):
|
||||
self.remote_addr = remote_addr
|
||||
|
@ -283,11 +284,32 @@ def setRestAPILog():
|
|||
LOGGER_RECORDER = LogRecorder()
|
||||
logger.addHandler(LOGGER_RECORDER)
|
||||
|
||||
|
||||
# Generic functions
|
||||
def is_admin(taskid):
|
||||
return DataStore.admin_id == taskid
|
||||
|
||||
@hook('before_request')
|
||||
def check_authentication():
|
||||
if not any((DataStore.username, DataStore.password)):
|
||||
return
|
||||
|
||||
authorization = request.headers.get("Authorization", "")
|
||||
match = re.search(r"(?i)\ABasic\s+([^\s]+)", authorization)
|
||||
|
||||
if not match:
|
||||
request.environ["PATH_INFO"] = "/error/401"
|
||||
|
||||
try:
|
||||
creds = match.group(1).decode("base64")
|
||||
except:
|
||||
request.environ["PATH_INFO"] = "/error/401"
|
||||
else:
|
||||
if creds.count(':') != 1:
|
||||
request.environ["PATH_INFO"] = "/error/401"
|
||||
else:
|
||||
username, password = creds.split(':')
|
||||
if username.strip() != (DataStore.username or "") or password.strip() != (DataStore.password or ""):
|
||||
request.environ["PATH_INFO"] = "/error/401"
|
||||
|
||||
@hook("after_request")
|
||||
def security_headers(json_header=True):
|
||||
|
@ -301,6 +323,7 @@ def security_headers(json_header=True):
|
|||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Cache-Control"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
|
||||
if json_header:
|
||||
response.content_type = "application/json; charset=UTF-8"
|
||||
|
||||
|
@ -308,35 +331,39 @@ def security_headers(json_header=True):
|
|||
# HTTP Status Code functions #
|
||||
##############################
|
||||
|
||||
|
||||
@return_error(401) # Access Denied
|
||||
def error401(error=None):
|
||||
security_headers(False)
|
||||
return "Access denied"
|
||||
|
||||
|
||||
@return_error(404) # Not Found
|
||||
def error404(error=None):
|
||||
security_headers(False)
|
||||
return "Nothing here"
|
||||
|
||||
|
||||
@return_error(405) # Method Not Allowed (e.g. when requesting a POST method via GET)
|
||||
def error405(error=None):
|
||||
security_headers(False)
|
||||
return "Method not allowed"
|
||||
|
||||
|
||||
@return_error(500) # Internal Server Error
|
||||
def error500(error=None):
|
||||
security_headers(False)
|
||||
return "Internal server error"
|
||||
|
||||
#############
|
||||
# Auxiliary #
|
||||
#############
|
||||
|
||||
@get('/error/401')
|
||||
def path_401():
|
||||
response.status = 401
|
||||
return response
|
||||
|
||||
#############################
|
||||
# Task management functions #
|
||||
#############################
|
||||
|
||||
|
||||
# Users' methods
|
||||
@get("/task/new")
|
||||
def task_new():
|
||||
|
@ -351,7 +378,6 @@ def task_new():
|
|||
logger.debug("Created new task: '%s'" % taskid)
|
||||
return jsonize({"success": True, "taskid": taskid})
|
||||
|
||||
|
||||
@get("/task/<taskid>/delete")
|
||||
def task_delete(taskid):
|
||||
"""
|
||||
|
@ -370,7 +396,6 @@ def task_delete(taskid):
|
|||
# Admin functions #
|
||||
###################
|
||||
|
||||
|
||||
@get("/admin/<taskid>/list")
|
||||
def task_list(taskid=None):
|
||||
"""
|
||||
|
@ -403,7 +428,6 @@ def task_flush(taskid):
|
|||
# sqlmap core interact functions #
|
||||
##################################
|
||||
|
||||
|
||||
# Handle task's options
|
||||
@get("/option/<taskid>/list")
|
||||
def option_list(taskid):
|
||||
|
@ -417,7 +441,6 @@ def option_list(taskid):
|
|||
logger.debug("[%s] Listed task options" % taskid)
|
||||
return jsonize({"success": True, "options": DataStore.tasks[taskid].get_options()})
|
||||
|
||||
|
||||
@post("/option/<taskid>/get")
|
||||
def option_get(taskid):
|
||||
"""
|
||||
|
@ -436,12 +459,12 @@ def option_get(taskid):
|
|||
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")
|
||||
def option_set(taskid):
|
||||
"""
|
||||
Set an option (command line switch) for a certain task ID
|
||||
"""
|
||||
|
||||
if taskid not in DataStore.tasks:
|
||||
logger.warning("[%s] Invalid task ID provided to option_set()" % taskid)
|
||||
return jsonize({"success": False, "message": "Invalid task ID"})
|
||||
|
@ -452,13 +475,13 @@ def option_set(taskid):
|
|||
logger.debug("[%s] Requested to set options" % taskid)
|
||||
return jsonize({"success": True})
|
||||
|
||||
|
||||
# Handle scans
|
||||
@post("/scan/<taskid>/start")
|
||||
def scan_start(taskid):
|
||||
"""
|
||||
Launch a scan
|
||||
"""
|
||||
|
||||
if taskid not in DataStore.tasks:
|
||||
logger.warning("[%s] Invalid task ID provided to scan_start()" % taskid)
|
||||
return jsonize({"success": False, "message": "Invalid task ID"})
|
||||
|
@ -473,12 +496,12 @@ def scan_start(taskid):
|
|||
logger.debug("[%s] Started scan" % taskid)
|
||||
return jsonize({"success": True, "engineid": DataStore.tasks[taskid].engine_get_id()})
|
||||
|
||||
|
||||
@get("/scan/<taskid>/stop")
|
||||
def scan_stop(taskid):
|
||||
"""
|
||||
Stop a scan
|
||||
"""
|
||||
|
||||
if (taskid not in DataStore.tasks or
|
||||
DataStore.tasks[taskid].engine_process() is None or
|
||||
DataStore.tasks[taskid].engine_has_terminated()):
|
||||
|
@ -490,12 +513,12 @@ def scan_stop(taskid):
|
|||
logger.debug("[%s] Stopped scan" % taskid)
|
||||
return jsonize({"success": True})
|
||||
|
||||
|
||||
@get("/scan/<taskid>/kill")
|
||||
def scan_kill(taskid):
|
||||
"""
|
||||
Kill a scan
|
||||
"""
|
||||
|
||||
if (taskid not in DataStore.tasks or
|
||||
DataStore.tasks[taskid].engine_process() is None or
|
||||
DataStore.tasks[taskid].engine_has_terminated()):
|
||||
|
@ -507,12 +530,12 @@ def scan_kill(taskid):
|
|||
logger.debug("[%s] Killed scan" % taskid)
|
||||
return jsonize({"success": True})
|
||||
|
||||
|
||||
@get("/scan/<taskid>/status")
|
||||
def scan_status(taskid):
|
||||
"""
|
||||
Returns status of a scan
|
||||
"""
|
||||
|
||||
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"})
|
||||
|
@ -529,12 +552,12 @@ def scan_status(taskid):
|
|||
"returncode": DataStore.tasks[taskid].engine_get_returncode()
|
||||
})
|
||||
|
||||
|
||||
@get("/scan/<taskid>/data")
|
||||
def scan_data(taskid):
|
||||
"""
|
||||
Retrieve the data of a scan
|
||||
"""
|
||||
|
||||
json_data_message = list()
|
||||
json_errors_message = list()
|
||||
|
||||
|
@ -560,6 +583,7 @@ def scan_log_limited(taskid, start, end):
|
|||
"""
|
||||
Retrieve a subset of log messages
|
||||
"""
|
||||
|
||||
json_log_messages = list()
|
||||
|
||||
if taskid not in DataStore.tasks:
|
||||
|
@ -586,6 +610,7 @@ def scan_log(taskid):
|
|||
"""
|
||||
Retrieve the log messages
|
||||
"""
|
||||
|
||||
json_log_messages = list()
|
||||
|
||||
if taskid not in DataStore.tasks:
|
||||
|
@ -606,6 +631,7 @@ def download(taskid, target, filename):
|
|||
"""
|
||||
Download a certain file from the file system
|
||||
"""
|
||||
|
||||
if taskid not in DataStore.tasks:
|
||||
logger.warning("[%s] Invalid task ID provided to download()" % taskid)
|
||||
return jsonize({"success": False, "message": "Invalid task ID"})
|
||||
|
@ -626,13 +652,17 @@ def download(taskid, target, filename):
|
|||
return jsonize({"success": False, "message": "File does not exist"})
|
||||
|
||||
|
||||
def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER):
|
||||
def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None):
|
||||
"""
|
||||
REST-JSON API server
|
||||
"""
|
||||
|
||||
DataStore.admin_id = hexencode(os.urandom(16))
|
||||
handle, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False)
|
||||
os.close(handle)
|
||||
DataStore.username = username
|
||||
DataStore.password = password
|
||||
|
||||
_, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False)
|
||||
os.close(_)
|
||||
|
||||
if port == 0: # random
|
||||
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
|
@ -660,7 +690,7 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST
|
|||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
logger.debug("Using adapter '%s' to run bottle" % adapter)
|
||||
run(host=host, port=port, quiet=True, debug=False, server=adapter)
|
||||
run(host=host, port=port, quiet=True, debug=True, server=adapter)
|
||||
except socket.error, ex:
|
||||
if "already in use" in getSafeExString(ex):
|
||||
logger.error("Address already in use ('%s:%s')" % (host, port))
|
||||
|
@ -681,7 +711,12 @@ def _client(url, options=None):
|
|||
data = None
|
||||
if options is not None:
|
||||
data = jsonize(options)
|
||||
req = urllib2.Request(url, data, {"Content-Type": "application/json"})
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if DataStore.username or DataStore.password:
|
||||
headers["Authorization"] = "Basic %s" % ("%s:%s" % (DataStore.username or "", DataStore.password or "")).encode("base64").strip()
|
||||
|
||||
req = urllib2.Request(url, data, headers)
|
||||
response = urllib2.urlopen(req)
|
||||
text = response.read()
|
||||
except:
|
||||
|
@ -690,12 +725,14 @@ def _client(url, options=None):
|
|||
raise
|
||||
return text
|
||||
|
||||
|
||||
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT):
|
||||
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None):
|
||||
"""
|
||||
REST-JSON API client
|
||||
"""
|
||||
|
||||
DataStore.username = username
|
||||
DataStore.password = password
|
||||
|
||||
dbgMsg = "Example client access from command line:"
|
||||
dbgMsg += "\n\t$ taskid=$(curl http://%s:%d/task/new 2>1 | grep -o -I '[a-f0-9]\{16\}') && echo $taskid" % (host, port)
|
||||
dbgMsg += "\n\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)
|
||||
|
@ -709,7 +746,7 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT):
|
|||
try:
|
||||
_client(addr)
|
||||
except Exception, ex:
|
||||
if not isinstance(ex, urllib2.HTTPError):
|
||||
if not isinstance(ex, urllib2.HTTPError) or ex.code == httplib.UNAUTHORIZED:
|
||||
errMsg = "There has been a problem while connecting to the "
|
||||
errMsg += "REST-JSON API server at '%s' " % addr
|
||||
errMsg += "(%s)" % ex
|
||||
|
|
|
@ -40,13 +40,15 @@ def main():
|
|||
apiparser.add_option("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS, action="store")
|
||||
apiparser.add_option("-p", "--port", help="Port of the the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type="int", action="store")
|
||||
apiparser.add_option("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER, action="store")
|
||||
apiparser.add_option("--username", help="Basic authentication username (optional)", action="store")
|
||||
apiparser.add_option("--password", help="Basic authentication password (optional)", action="store")
|
||||
(args, _) = apiparser.parse_args()
|
||||
|
||||
# Start the client or the server
|
||||
if args.server is True:
|
||||
server(args.host, args.port, adapter=args.adapter)
|
||||
server(args.host, args.port, adapter=args.adapter, username=args.username, password=args.password)
|
||||
elif args.client is True:
|
||||
client(args.host, args.port)
|
||||
client(args.host, args.port, username=args.username, password=args.password)
|
||||
else:
|
||||
apiparser.print_help()
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ fbf750dc617c3549ee423d6c2334ba4d lib/core/option.py
|
|||
d8e9250f3775119df07e9070eddccd16 lib/core/replication.py
|
||||
785f86e3f963fa3798f84286a4e83ff2 lib/core/revision.py
|
||||
40c80b28b3a5819b737a5a17d4565ae9 lib/core/session.py
|
||||
d6dc3f75b2f3aff43a7f3382059bea76 lib/core/settings.py
|
||||
2a84a6aba8a3c553fdf6058a011a491f lib/core/settings.py
|
||||
d91291997d2bd2f6028aaf371bf1d3b6 lib/core/shell.py
|
||||
2ad85c130cc5f2b3701ea85c2f6bbf20 lib/core/subprocessng.py
|
||||
85e3a98bc9ba62125baa13e864f37a3f lib/core/target.py
|
||||
|
@ -98,7 +98,7 @@ d3da4c7ceaf57c4687a052d58722f6bb lib/techniques/dns/use.py
|
|||
310efc965c862cfbd7b0da5150a5ad36 lib/techniques/union/__init__.py
|
||||
d71e48e6fd08f75cc612bf8b260994ce lib/techniques/union/test.py
|
||||
db3090ff9a740ba096ba676fcf44ebfc lib/techniques/union/use.py
|
||||
9e903297f6d6bb11660af5c7b109ccab lib/utils/api.py
|
||||
720e899d5097d701d258bdc30eb8aa51 lib/utils/api.py
|
||||
7d10ba0851da8ee9cd3c140dcd18798e lib/utils/brute.py
|
||||
c08d2487a53a1db8170178ebcf87c864 lib/utils/crawler.py
|
||||
ba12c69a90061aa14d848b8396e79191 lib/utils/deps.py
|
||||
|
@ -223,7 +223,7 @@ b04db3e861edde1f9dd0a3850d5b96c8 shell/backdoor.asp_
|
|||
c3cc8b7727161e64ab59f312c33b541a shell/stager.aspx_
|
||||
1f7f125f30e0e800beb21e2ebbab18e1 shell/stager.jsp_
|
||||
01e3505e796edf19aad6a996101c81c9 shell/stager.php_
|
||||
0751a45ac4c130131f2cdb74d866b664 sqlmapapi.py
|
||||
8755985bcb91e3fea7aaaea3e98ec2dc sqlmapapi.py
|
||||
41a637eda3e182d520fa4fb435edc1ec sqlmap.py
|
||||
08c711a470d7e0bf705320ba3c48b886 tamper/apostrophemask.py
|
||||
e8509df10d3f1c28014d7825562d32dd tamper/apostrophenullencode.py
|
||||
|
|
Loading…
Reference in New Issue
Block a user