99from __future__ import print_function
1010
1111import contextlib
12+ import datetime
1213import logging
1314import os
1415import re
6061from lib .parse .cmdline import cmdLineParser
6162from thirdparty .bottle .bottle import error as return_error
6263from thirdparty .bottle .bottle import get
64+ from thirdparty .bottle .bottle import route
6365from thirdparty .bottle .bottle import hook
6466from thirdparty .bottle .bottle import post
6567from thirdparty .bottle .bottle import request
7880# Global data storage
7981MAX_TASKS_NUMBER = multiprocessing .cpu_count () - 1
8082ROOT_DIRECTORY = os .getcwd ()
83+ datetime_format = "%Y-%m-%d %H:%M:%S"
8184
8285class 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
387401def 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>' )
796926def 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