SQLMAP API
SQLMAP API
最近在学习小迪的python利用sqlmap api进行开发,想弄清楚sqlmap调用api的原理,于是去看了一下源码,参考了一些网上的资料,写下这篇文章用于学习
本文参考:
sqlmapapi介绍&利用sqlmapapi发起扫描
使用sqlmap api的好处
由于SQLMAP每检测一个站点都需要开启一个新的命令行窗口或者结束掉上一个检测任务。虽然 -m 参数可以批量扫描URL,但是模式也是一个结束扫描后才开始另一个扫描任务。通过api接口,下发扫描任务就简单了,无需开启一个新的命令行窗口。
sqlmap 下载地址
GitHub - sqlmapproject/sqlmap: Automatic SQL injection and database takeover tool
sqlmap: automatic SQL injection and database takeover tool
sqlmap api的使用
在安装完sqlmap之后,在sqlmap的安装目录会看到一个sqlmapapi.py的文件,sqlmap api有两种模式,一种是基于HTTP协议的接口模式,一种是基于命令行的接口模式。在本文中主要使用的是HTTP协议的接口模式
在命令行中输入python sqlmapapi.py -h
Usage: sqlmapapi.py [options]
Options:
-h, --help show this help message and exit 显示帮助信息并退出
-s, --server Run as a REST-JSON API server 作为api服务端运行
-c, --client Run as a REST-JSON API client #作为api客户端运行
-H HOST, --host=HOST Host of the REST-JSON API server #指定服务端IP地址 (默认 "127.0.0.1")
-p PORT, --port=PORT Port of the the REST-JSON API server #指定服务端端口 (默认 8775)
--adapter=ADAPTER Server (bottle) adapter to use #服务端标准接口 (默认是 "wsgiref")
--username=USERNAME Basic authentication username (optional) #可空,设置用户名
--password=PASSWORD Basic authentication password (optional) #可空,设置密码
开启api服务端
python sqlmapapi.py -s
命令成功后,在命令行中会返回一些信息。以下命令大概的意思是api服务端在本地8775端口上运行,admin token为44073354ab4e0dc73877de29851264b8,IPC数据库的位置在C:\Users\XXX\AppData\Local/Temp/sqlmapipc-gppg14ra,api服务端已经和IPC数据库连接上了,正在使用bottle 框架wsgiref标准接口
[16:20:52] [INFO] Running REST-JSON API server at '127.0.0.1:8775'..
[16:20:52] [INFO] Admin (secret) token: 44073354ab4e0dc73877de29851264b8
[16:20:52] [DEBUG] IPC database: 'C:\Users\XXX\AppData\Local\Temp\sqlmapipc-gppg14ra'
[16:20:52] [DEBUG] REST-JSON API server connected to IPC database
[16:20:52] [DEBUG] Using adapter 'wsgiref' to run bottle
如果希望能够通过远程客户端来调用api可以使用一下命令开启api服务
python sqlmapapi.py -s -H "0.0.0.0" -p 8775
命令成功后,远程客户端就可以通过指定远程主机IP和端口来连接到API服务端。
API源码(基于HTTP协议)
进入到lib/utils/api.py的server类,可以发现通过向server提交数据进行与服务的交互。 一共分为3种类型
Users' methods 用户方法
Admin function 管理函数
sqlmap core interact functions 核心交互函数
下列都是基于HTTP协议API交互的所有方法:提示:“@get”就说明需要通过GET请求的,“@post”就说明需要通过POST请求的;POST请求需要修改HTTP头中的Content-Type字段为application/json
#辅助
@get('/error/401')
@get("/task/new")
@get("/task/<taskid>/delete")
#Admin 命令
@get("/admin/list")
@get("/admin/<token>/list")
@get("/admin/flush")
@get("/admin/<token>/flush")
#sqlmap 核心交互命令
@get("/option/<taskid>/list")
@post("/option/<taskid>/get")
@post("/option/<taskid>/set")
@post("/scan/<taskid>/start")
@get("/scan/<taskid>/stop")
@get("/scan/<taskid>/kill")
@get("/scan/<taskid>/status")
@get("/scan/<taskid>/data")
@get("/scan/<taskid>/log/<start>/<end>")
@get("/scan/<taskid>/log")
@get("/download/<taskid>/<target>/<filename:path>")
用户方法
@get("/task/new")
该接口用于创建一个新的任务,使用后会返回一个随机的taskid
@get("/task/new")
def task_new():
"""
Create a new task
"""
taskid = encodeHex(os.urandom(8), binary=False)
remote_addr = request.remote_addr
DataStore.tasks[taskid] = Task(taskid, remote_addr)
logger.debug("Created new task: '%s'" % taskid)
return jsonize({"success": True, "taskid": taskid})
@get("/task/delete")
该接口用于删除taskid。在调用时指定taskid,不指定taskid会有问题。具体代码如下:
@get("/task/<taskid>/delete")
def task_delete(taskid):
"""
Delete an existing task
"""
if taskid in DataStore.tasks:
DataStore.tasks.pop(taskid)
logger.debug("(%s) Deleted task" % taskid)
return jsonize({"success": True})
else:
response.status = 404
logger.warning("[%s] Non-existing task ID provided to task_delete()" % taskid)
return jsonize({"success": False, "message": "Non-existing task ID"})
管理函数
@get("/admin/list")/@get("/admin/<'token'>/list")
该接口用于返回所有taskid。在调用时指定token,不指定token会有问题。具体代码如下
@get("/admin/list")
@get("/admin/<token>/list")
def task_list(token=None):
"""
Pull task list
"""
tasks = {}
for key in DataStore.tasks:
if is_admin(token) or DataStore.tasks[key].remote_addr == request.remote_addr:
tasks[key] = dejsonize(scan_status(key))["status"]
logger.debug("(%s) Listed task pool (%s)" % (token, "admin" if is_admin(token) else request.remote_addr))
return jsonize({"success": True, "tasks": tasks, "tasks_num": len(tasks)})
@get("/admin/flush")/@get("/admin/<'token'>/flush")
@get("/admin/flush")
@get("/admin/<token>/flush")
def task_flush(token=None):
"""
Flush task spool (delete all tasks)
"""
for key in list(DataStore.tasks):
if is_admin(token) or DataStore.tasks[key].remote_addr == request.remote_addr:
DataStore.tasks[key].engine_kill()
del DataStore.tasks[key]
logger.debug("(%s) Flushed task pool (%s)" % (token, "admin" if is_admin(token) else request.remote_addr))
return jsonize({"success": True})
核心交互函数
@get("/option/<'taskid'>/list")
该接口可获取特定任务ID的列表选项,调用时请指定taskid,不然会出现问题。具体代码如下:
@get("/option/<taskid>/list")
def option_list(taskid):
"""
List options for a certain task ID
"""
if taskid not in DataStore.tasks:
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()})
@post("/option/<'taskid'>/get")
该接口可获取特定任务ID的选项值,调用时请指定taskid,不然会出现问题。具体代码如下:
@post("/option/<taskid>/get")
def option_get(taskid):
"""
Get value of option(s) for a certain task ID
"""
if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to option_get()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
options = request.json or []
results = {}
for option in options:
if option in DataStore.tasks[taskid].options:
results[option] = DataStore.tasks[taskid].options[option]
else:
logger.debug("(%s) Requested value for unknown option '%s'" % (taskid, option))
return jsonize({"success": False, "message": "Unknown option '%s'" % option})
logger.debug("(%s) Retrieved values for option(s) '%s'" % (taskid, ','.join(options)))
return jsonize({"success": True, "options": results})
@post("/option/<'taskid'>/set")
该接口为特定任务ID设置选项值,调用时请指定taskid,不然会出现问题。具体代码如下:
@post("/option/<taskid>/set")
def option_set(taskid):
"""
Set value of option(s) 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"})
if request.json is None:
logger.warning("[%s] Invalid JSON options provided to option_set()" % taskid)
return jsonize({"success": False, "message": "Invalid JSON options"})
for option, value in request.json.items():
DataStore.tasks[taskid].set_option(option, value)
logger.debug("(%s) Requested to set options" % taskid)
return jsonize({"success": True})
@post("/scan/<'taskid'>/start")
该接口定义开始扫描特定任务,调用时请指定taskid,不然会出现问题。具体代码如下:
@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"})
if request.json is None:
logger.warning("[%s] Invalid JSON options provided to scan_start()" % taskid)
return jsonize({"success": False, "message": "Invalid JSON options"})
for key in request.json:
if key in RESTAPI_UNSUPPORTED_OPTIONS:
logger.warning("[%s] Unsupported option '%s' provided to scan_start()" % (taskid, key))
return jsonize({"success": False, "message": "Unsupported option '%s'" % key})
# Initialize sqlmap engine's options with user's provided options, if any
for option, value in request.json.items():
DataStore.tasks[taskid].set_option(option, value)
# Launch sqlmap engine in a separate process
DataStore.tasks[taskid].engine_start()
logger.debug("(%s) Started scan" % taskid)
return jsonize({"success": True, "engineid": DataStore.tasks[taskid].engine_get_id()})
@get("/scan/<'taskid'>/stop")
该接口定义停止扫描特定任务,调用时请指定taskid,不然会出现问题。具体代码如下:
@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()):
logger.warning("[%s] Invalid task ID provided to scan_stop()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
DataStore.tasks[taskid].engine_stop()
logger.debug("(%s) Stopped scan" % taskid)
return jsonize({"success": True})
@get("/scan/<'taskid'>/kill")
该接口可杀死特定任务,需要指定taskid,不然会出现问题。具体代码如下:
@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()):
logger.warning("[%s] Invalid task ID provided to scan_kill()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
DataStore.tasks[taskid].engine_kill()
logger.debug("(%s) Killed scan" % taskid)
return jsonize({"success": True})
@get("/scan/<'taskid'>/status")
该接口可查询扫描状态,调用时请指定taskid,不然会出现问题。具体代码如下:
@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"})
if DataStore.tasks[taskid].engine_process() is None:
status = "not running"
else:
status = "terminated" if DataStore.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()
})
@get("/scan/<'taskid'>/data")
该接口可获得到扫描结果,调用时请指定taskid,不然会出现问题。具体代码如下:
@get("/scan/<taskid>/data")
def scan_data(taskid):
"""
Retrieve the data of a scan
"""
json_data_message = list()
json_errors_message = list()
if taskid not in DataStore.tasks:
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
for status, content_type, value in DataStore.current_db.execute("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
for error in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)):
json_errors_message.append(error)
logger.debug("(%s) Retrieved scan data and error messages" % taskid)
return jsonize({"success": True, "data": json_data_message, "error": json_errors_message})
@get("/scan/<'taskid'>/log")
该接口可查询特定任务的扫描的日志,调用时请指定taskid,不然会出现问题。具体代码如下:
@get("/scan/<taskid>/log")
def scan_log(taskid):
"""
Retrieve the log messages
"""
json_log_messages = list()
if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_log()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"})
# Read all log messages from the IPC database
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})
logger.debug("(%s) Retrieved scan log messages" % taskid)
return jsonize({"success": True, "log": json_log_messages})
@get("/download/<'taskid'>/<'target'>/filename:path")
下载服务端指定任务的文件。具体代码如下:
@get("/download/<taskid>/<target>/<filename:path>")
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"})
path = os.path.abspath(os.path.join(paths.SQLMAP_OUTPUT_PATH, target, filename))
# Prevent file path traversal
if not path.startswith(paths.SQLMAP_OUTPUT_PATH):
logger.warning("[%s] Forbidden path (%s)" % (taskid, target))
return jsonize({"success": False, "message": "Forbidden path"})
if os.path.isfile(path):
logger.debug("(%s) Retrieved content of file %s" % (taskid, target))
content = openFile(path, "rb").read()
return jsonize({"success": True, "file": encodeBase64(content, binary=False)})
else:
logger.warning("[%s] File does not exist %s" % (taskid, target))
return jsonize({"success": False, "message": "File does not exist"})
API的使用
创建新任务记录
import requests
import json
task_url = 'http://127.0.0.1:8775/task/new'
res = requests.get(task_url)
print(res.json())
输出结果
{'success': True, 'taskid': '5f5e94ae8d26d42f'}
服务端显示
[17:24:40] [DEBUG] Created new task: '5f5e94ae8d26d42f'
设置任务id扫描信息
import requests
import json
task_url = 'http://127.0.0.1:8775/task/new'
res = requests.get(task_url)
task_id = res.json()['taskid']
data = {
'url':'http://127.0.0.1:8080/index.pgp?id=1'
}
headers = {
'Content-Type':'application/json'
}
task_set_url = 'http://127.0.0.1:8775/option/'+task_id+'/set'
task_set_res = requests.post(task_set_url,data=json.dumps(data),headers=headers)
启动对应的扫扫描任务
task_start_url = 'http://127.0.0.1:8775/scan/'+task_id+'/start'
task_start_res = requests.post(task_start_url,data=json.dumps(data),headers=headers)
获取对应task状态
task_status_url = 'http://127.0.0.1:8775/scan/'+task_id+'/status'
task_status_res = requests.get(task_status_url)
获取对应task扫描结果
task_data_url = 'http://127.0.0.1:8775/scan/'+task_id+'/data'
task_data_res = requests.get(task_data_url)

浙公网安备 33010602011771号