Skip to content

Commit c8f86d0

Browse files
authored
Merge pull request #2 from GitHubNull/run_at_time_feature
Run at time feature
2 parents 821e081 + 452780e commit c8f86d0

File tree

1 file changed

+153
-23
lines changed

1 file changed

+153
-23
lines changed

lib/utils/api.py

Lines changed: 153 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from __future__ import print_function
1010

1111
import contextlib
12+
import datetime
1213
import logging
1314
import os
1415
import re
@@ -60,6 +61,7 @@
6061
from lib.parse.cmdline import cmdLineParser
6162
from thirdparty.bottle.bottle import error as return_error
6263
from thirdparty.bottle.bottle import get
64+
from thirdparty.bottle.bottle import route
6365
from thirdparty.bottle.bottle import hook
6466
from thirdparty.bottle.bottle import post
6567
from thirdparty.bottle.bottle import request
@@ -78,6 +80,7 @@
7880
# Global data storage
7981
MAX_TASKS_NUMBER = multiprocessing.cpu_count() - 1
8082
ROOT_DIRECTORY = os.getcwd()
83+
datetime_format = "%Y-%m-%d %H:%M:%S"
8184

8285
class DataStore(object):
8386
admin_token = ""
@@ -169,6 +172,7 @@ def __init__(self, taskid, remote_addr):
169172
self.options = None
170173
self.status = TaskStatus.New
171174
self._original_options = None
175+
self.start_datetime = None
172176
self.initialize_options(taskid)
173177

174178
def initialize_options(self, taskid):
@@ -378,10 +382,20 @@ def perform_task():
378382
if running_task_count < MAX_TASKS_NUMBER:
379383
for task in runnable_list:
380384
if running_task_count < MAX_TASKS_NUMBER:
381-
running_task_count += 1
382-
logger.info("run task %s" % task.options.taskid)
383-
task.engine_start()
384-
task.status = TaskStatus.Running
385+
if task.start_datetime is not None:
386+
if datetime.datetime.now() >= task.start_datetime:
387+
running_task_count += 1
388+
logger.info("run task %s" % task.options.taskid)
389+
task.engine_start()
390+
task.status = TaskStatus.Running
391+
else:
392+
continue
393+
else:
394+
running_task_count += 1
395+
logger.info("run task %s" % task.options.taskid)
396+
task.start_datetime = datetime.datetime.now()
397+
task.engine_start()
398+
task.status = TaskStatus.Running
385399

386400

387401
def run_task(interval):
@@ -441,6 +455,8 @@ def security_headers(json_header=True):
441455
response.headers["X-XSS-Protection"] = "1; mode=block"
442456
response.headers["Pragma"] = "no-cache"
443457
response['Access-Control-Allow-Origin'] = 'http://localhost:5173'
458+
response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS'
459+
response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
444460
response.headers["Cache-Control"] = "no-cache"
445461
response.headers["Expires"] = "0"
446462

@@ -449,6 +465,11 @@ def security_headers(json_header=True):
449465
# else:
450466
# response.content_type = "text/html; charset=utf-8"
451467

468+
# 处理 OPTIONS 请求
469+
@route('/<path:path>', method=['OPTIONS'])
470+
def options_handler(path):
471+
return
472+
452473
##############################
453474
# HTTP Status Code functions #
454475
##############################
@@ -568,7 +589,7 @@ def task_delete(taskid):
568589
DataStore.tasks[taskid].engine_kill()
569590
DataStore.tasks.pop(taskid)
570591

571-
logger.debug("(%s) Deleted task" % taskid)
592+
logger.debug("[%s] Deleted task" % taskid)
572593
return jsonize({"success": True})
573594
else:
574595
response.status = 404
@@ -648,6 +669,7 @@ def task_ls(token=None):
648669

649670
resul_task_item = {
650671
"index": index,
672+
"start_datetime": None if task.start_datetime is None else task.start_datetime.strftime("%Y-%m-%d %H:%M:%S"),
651673
"task_id": taskid,
652674
"errors": errors_count,
653675
"logs": logs_count,
@@ -697,7 +719,7 @@ def option_list(taskid):
697719
"[%s] Invalid task ID provided to option_list()" % taskid)
698720
return jsonize({"success": False, "message": "Invalid task ID"})
699721

700-
logger.debug("(%s) Listed task options" % taskid)
722+
logger.debug("[%s] Listed task options" % taskid)
701723
return jsonize({"success": True, "options": DataStore.tasks[taskid].get_options()})
702724

703725

@@ -719,10 +741,10 @@ def option_get(taskid):
719741
results[option] = DataStore.tasks[taskid].options[option]
720742
else:
721743
logger.debug(
722-
"(%s) Requested value for unknown option '%s'" % (taskid, option))
744+
"[%s] Requested value for unknown option '%s'" % (taskid, option))
723745
return jsonize({"success": False, "message": "Unknown option '%s'" % option})
724746

725-
logger.debug("(%s) Retrieved values for option(s) '%s'" %
747+
logger.debug("[%s] Retrieved values for option(s) '%s'" %
726748
(taskid, ','.join(options)))
727749

728750
return jsonize({"success": True, "options": results})
@@ -747,7 +769,7 @@ def option_set(taskid):
747769
for option, value in request.json.items():
748770
DataStore.tasks[taskid].set_option(option, value)
749771

750-
logger.debug("(%s) Requested to set options" % taskid)
772+
logger.debug("[%s] Requested to set options" % taskid)
751773
return jsonize({"success": True})
752774

753775
# Handle scans
@@ -777,6 +799,52 @@ def scan_start(taskid):
777799
return jsonize({"success": False, "message": "Unsupported option '%s'" % key})
778800

779801
# Initialize sqlmap engine's options with user's provided options, if any
802+
with DataStore.tasks_lock:
803+
if DataStore.tasks[taskid].status == TaskStatus.Blocked:
804+
DataStore.tasks[taskid].status = TaskStatus.Runnable
805+
logger.debug("[%s] Unblocked" % taskid)
806+
return jsonize({"success": True, "engineid": 0})
807+
808+
for option, value in request.json.items():
809+
DataStore.tasks[taskid].set_option(option, value)
810+
811+
# Launch sqlmap engine in a separate process
812+
DataStore.tasks[taskid].status = TaskStatus.Runnable
813+
814+
logger.debug("Add [%s] to scan list" % taskid)
815+
return jsonize({"success": True, "engineid": 0})
816+
817+
@post('/scan/start_at_datetime/<taskid>')
818+
def scan_start_at_datetime(taskid):
819+
"""
820+
Start a scan at a specific datetime
821+
"""
822+
823+
with DataStore.tasks_lock:
824+
if taskid not in DataStore.tasks:
825+
logger.warning("[%s] Invalid task ID provided to scan_start()" % taskid)
826+
return jsonize({"success": False, "message": "Invalid task ID"})
827+
828+
829+
if request.json is None:
830+
return jsonize({"success": False, "message": "Invalid request"})
831+
832+
params = request.params
833+
834+
if 'start_datetime' not in params:
835+
return jsonize({"success": False, "message": "Invalid start_datetime"})
836+
837+
start_datetime = params['start_datetime']
838+
839+
if not isinstance(start_datetime, str):
840+
return jsonize({"success": False, "message": "Invalid start_datetime"})
841+
842+
for key in request.json:
843+
if key in RESTAPI_UNSUPPORTED_OPTIONS:
844+
logger.warning(
845+
"[%s] Unsupported option '%s' provided to scan_start()" % (taskid, key))
846+
return jsonize({"success": False, "message": "Unsupported option '%s'" % key})
847+
780848
with DataStore.tasks_lock:
781849
if DataStore.tasks[taskid].status == TaskStatus.Blocked:
782850
DataStore.tasks[taskid].status = TaskStatus.Runnable
@@ -788,10 +856,72 @@ def scan_start(taskid):
788856

789857
# Launch sqlmap engine in a separate process
790858
DataStore.tasks[taskid].status = TaskStatus.Runnable
859+
860+
DataStore.tasks[taskid].start_datetime = datetime.datetime.strptime(start_datetime, datetime_format)
791861

792862
logger.debug("Add (%s) to scan list" % taskid)
793-
return jsonize({"success": True, "engineid": 0})
863+
return jsonize({"success": True, "engineid": 0})
864+
865+
@post('/scan/update_start_datetime/<taskid>')
866+
def scan_update_start_datetime(taskid):
867+
"""
868+
Update the start datetime of a scan
869+
"""
870+
871+
logger.debug("[%s] Updating start datetime" % taskid)
794872

873+
with DataStore.tasks_lock:
874+
if taskid not in DataStore.tasks:
875+
logger.warning("[%s] Invalid task ID provided to scan_update_start_datetime()" % taskid)
876+
return jsonize({"success": False, "message": "Invalid task ID"})
877+
878+
start_datetime = request.json.get("start_datetime", None)
879+
if start_datetime is None:
880+
logger.warning("[%s] No start_datetime provided to scan_update_start_datetime()" % taskid)
881+
return jsonize({"success": False, "message": "Invalid start datetime"})
882+
883+
now = datetime.datetime.now()
884+
time_five_seconds_later = now + datetime.timedelta(seconds=5)
885+
start_datetime = datetime.datetime.strptime(start_datetime, datetime_format)
886+
if DataStore.tasks[taskid].start_datetime is None:
887+
if start_datetime > time_five_seconds_later:
888+
DataStore.tasks[taskid].start_datetime = start_datetime
889+
return jsonize({"success": True, "message": "update start da"})
890+
else:
891+
return jsonize({"success": False, "message": "start datetime is too early"})
892+
else:
893+
if DataStore.tasks[taskid].status in [TaskStatus.New, TaskStatus.Runnable]:
894+
if start_datetime > now:
895+
DataStore.tasks[taskid].start_datetime = start_datetime
896+
return jsonize({"success": True, "message": "update start datetime success"})
897+
else:
898+
return jsonize({"success": False, "message": "start datetime must be greater than now"})
899+
elif DataStore.tasks[taskid].status == TaskStatus.Running:
900+
# 检查你的datetime对象是否大于现在时间5秒
901+
if start_datetime > time_five_seconds_later:
902+
DataStore.tasks[taskid].engine_stop()
903+
DataStore.tasks[taskid].start_datetime = start_datetime
904+
return jsonize({"success": True, "message": "Update start datetime success"})
905+
else:
906+
return jsonize({"success": False, "message": "Invalid start datetime"})
907+
elif DataStore.tasks[taskid].status == TaskStatus.Terminated:
908+
if start_datetime > time_five_seconds_later:
909+
DataStore.tasks[taskid].start_datetime = start_datetime
910+
DataStore.tasks[taskid].status = TaskStatus.Runnable
911+
return jsonize({"success": True, "message": "Task resumed"})
912+
else:
913+
return jsonize({"success": False, "message": "Invalid start datetime"})
914+
elif DataStore.tasks[taskid].status == TaskStatus.Blocked:
915+
if start_datetime > time_five_seconds_later:
916+
DataStore.tasks[taskid].start_datetime = start_datetime
917+
DataStore.tasks[taskid].status = TaskStatus.Runnable
918+
return jsonize({"success": True, "message": "Task resumed"})
919+
else:
920+
return jsonize({"success": False, "message": "Invalid start datetime"})
921+
else:
922+
return jsonize({"success": False, "message": "Invalid task status"})
923+
924+
795925
@get('/scan/startBlocked/<taskid>')
796926
def scan_startBlocked(taskid):
797927
"""
@@ -805,7 +935,7 @@ def scan_startBlocked(taskid):
805935

806936
if DataStore.tasks[taskid].status == TaskStatus.Blocked:
807937
DataStore.tasks[taskid].status = TaskStatus.Runnable
808-
logger.debug("(%s) Unblocked" % taskid)
938+
logger.debug("[%s] Unblocked" % taskid)
809939
return jsonize({"success": True, "engineid": 0})
810940

811941
else:
@@ -826,11 +956,11 @@ def scan_stop(taskid):
826956
if DataStore.tasks[taskid].status == TaskStatus.Running:
827957
DataStore.tasks[taskid].engine_stop()
828958
DataStore.tasks[taskid].status = TaskStatus.Blocked
829-
logger.debug("(%s) Stopped scan" % taskid)
959+
logger.debug("[%s] Stopped scan" % taskid)
830960
return jsonize({"success": True})
831961
elif DataStore.tasks[taskid].status in [TaskStatus.New, TaskStatus.Runnable]:
832962
DataStore.tasks[taskid].status = TaskStatus.Blocked
833-
logger.debug("(%s) Stopped scan" % taskid)
963+
logger.debug("[%s] Stopped scan" % taskid)
834964
return jsonize({"success": True})
835965
elif DataStore.tasks[taskid].status == TaskStatus.Blocked:
836966
logger.warning("[%s] task had blocked" % taskid)
@@ -855,7 +985,7 @@ def scan_kill(taskid):
855985
# del DataStore.tasks[taskid]
856986
DataStore.tasks[taskid].status = TaskStatus.Terminated
857987

858-
logger.debug("(%s) Killed scan" % taskid)
988+
logger.debug("[%s] Killed scan" % taskid)
859989
return jsonize({"success": True})
860990

861991

@@ -877,7 +1007,7 @@ def scan_status(taskid):
8771007
status = "terminated" if DataStore.tasks[taskid].engine_has_terminated(
8781008
) is True else "running"
8791009

880-
logger.debug("(%s) Retrieved scan status" % taskid)
1010+
logger.debug("[%s] Retrieved scan status" % taskid)
8811011
return jsonize({
8821012
"success": True,
8831013
"status": status,
@@ -908,7 +1038,7 @@ def scan_payload_details(taskid):
9081038
payloads.append({"index": index, "status": status,
9091039
"payload_type": content_type, "payload_value": value})
9101040

911-
logger.debug("(%s) Retrieved scan data and error messages" % taskid)
1041+
logger.debug("[%s] Retrieved scan data and error messages" % taskid)
9121042
return jsonize({"success": True, "payloads": payloads})
9131043

9141044

@@ -934,7 +1064,7 @@ def scan_data(taskid):
9341064
for error in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)):
9351065
json_errors_message.append(error)
9361066

937-
logger.debug("(%s) Retrieved scan data and error messages" % taskid)
1067+
logger.debug("[%s] Retrieved scan data and error messages" % taskid)
9381068
return jsonize({"success": True, "data": json_data_message, "error": json_errors_message})
9391069

9401070
# Functions to handle scans' logs
@@ -966,7 +1096,7 @@ def scan_log_limited(taskid, start, end):
9661096
json_log_messages.append(
9671097
{"datetime": datetime_, "level": level, "message": message})
9681098

969-
logger.debug("(%s) Retrieved scan log messages subset" % taskid)
1099+
logger.debug("[%s] Retrieved scan log messages subset" % taskid)
9701100
return jsonize({"success": True, "log": json_log_messages})
9711101

9721102

@@ -996,7 +1126,7 @@ def scan_log_details(taskid):
9961126
logs.append({"index": index, "datetime": datetime_,
9971127
"level": level, "message": message})
9981128

999-
logger.debug("(%s) Retrieved scan log messages" % taskid)
1129+
logger.debug("[%s] Retrieved scan log messages" % taskid)
10001130
return jsonize({"success": True, "logs": logs})
10011131

10021132

@@ -1017,7 +1147,7 @@ def scan_log(taskid):
10171147
json_log_messages.append(
10181148
{"datetime": datetime_, "level": level, "message": message})
10191149

1020-
logger.debug("(%s) Retrieved scan log messages" % taskid)
1150+
logger.debug("[%s] Retrieved scan log messages" % taskid)
10211151
return jsonize({"success": True, "log": json_log_messages})
10221152

10231153
# Function to handle files inside the output directory
@@ -1037,11 +1167,11 @@ def download(taskid, target, filename):
10371167
paths.SQLMAP_OUTPUT_PATH, target, filename))
10381168
# Prevent file path traversal
10391169
if not path.startswith(paths.SQLMAP_OUTPUT_PATH):
1040-
logger.warning("[%s] Forbidden path (%s)" % (taskid, target))
1170+
logger.warning("[%s] Forbidden path [%s]" % (taskid, target))
10411171
return jsonize({"success": False, "message": "Forbidden path"})
10421172

10431173
if os.path.isfile(path):
1044-
logger.debug("(%s) Retrieved content of file %s" % (taskid, target))
1174+
logger.debug("[%s] Retrieved content of file %s" % (taskid, target))
10451175
content = openFile(path, "rb").read()
10461176
return jsonize({"success": True, "file": encodeBase64(content, binary=False)})
10471177
else:
@@ -1055,7 +1185,7 @@ def version(token=None):
10551185
Fetch server version
10561186
"""
10571187

1058-
logger.debug("Fetched version (%s)" %
1188+
logger.debug("Fetched version [%s]" %
10591189
("admin" if is_admin(token) else request.remote_addr))
10601190
return jsonize({"success": True, "version": VERSION_STRING.split('/')[-1]})
10611191

0 commit comments

Comments
 (0)