1111import contextlib
1212import logging
1313import os
14+ import queue
1415import re
1516import shlex
1617import socket
@@ -174,14 +175,22 @@ def engine_start(self):
174175 os .close (handle )
175176 saveConfig (self .options , configFile )
176177
178+ # 准备子进程环境变量,传递外部日志 URL(如果配置了)
179+ env = os .environ .copy ()
180+ if hasattr (self .options , 'externalLogUrl' ) and self .options .externalLogUrl :
181+ env ['SQLMAP_EXTERNAL_LOG_URL' ] = self .options .externalLogUrl
182+ elif 'SQLMAP_EXTERNAL_LOG_URL' in os .environ :
183+ # 如果父进程设置了环境变量,也传递给子进程
184+ pass # 已经在 env.copy() 中包含了
185+
177186 if os .path .exists ("sqlmap.py" ):
178- self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , close_fds = not IS_WIN )
187+ self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , close_fds = not IS_WIN , env = env )
179188 elif os .path .exists (os .path .join (os .getcwd (), "sqlmap.py" )):
180- self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , cwd = os .getcwd (), close_fds = not IS_WIN )
189+ self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , cwd = os .getcwd (), close_fds = not IS_WIN , env = env )
181190 elif os .path .exists (os .path .join (os .path .abspath (os .path .dirname (sys .argv [0 ])), "sqlmap.py" )):
182- self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , cwd = os .path .join (os .path .abspath (os .path .dirname (sys .argv [0 ]))), close_fds = not IS_WIN )
191+ self .process = Popen ([sys .executable or "python" , "sqlmap.py" , "--api" , "-c" , configFile ], shell = False , cwd = os .path .join (os .path .abspath (os .path .dirname (sys .argv [0 ]))), close_fds = not IS_WIN , env = env )
183192 else :
184- self .process = Popen (["sqlmap" , "--api" , "-c" , configFile ], shell = False , close_fds = not IS_WIN )
193+ self .process = Popen (["sqlmap" , "--api" , "-c" , configFile ], shell = False , close_fds = not IS_WIN , env = env )
185194
186195 def engine_stop (self ):
187196 if self .process :
@@ -271,12 +280,97 @@ def seek(self):
271280 pass
272281
273282class LogRecorder (logging .StreamHandler ):
283+ _log_queue = None
284+ _log_thread = None
285+ _log_thread_lock = threading .Lock ()
286+ _external_log_url = None
287+
288+ def __init__ (self ):
289+ super (LogRecorder , self ).__init__ ()
290+ # 从环境变量或配置中获取外部日志接口 URL
291+ external_log_url = os .environ .get ("SQLMAP_EXTERNAL_LOG_URL" )
292+
293+ # 如果配置对象中有 externalLogUrl 选项,优先使用
294+ if hasattr (conf , 'externalLogUrl' ) and conf .externalLogUrl :
295+ external_log_url = conf .externalLogUrl
296+
297+ # 如果设置了外部日志 URL,初始化日志发送队列和线程
298+ if external_log_url :
299+ with LogRecorder ._log_thread_lock :
300+ if LogRecorder ._log_queue is None :
301+ LogRecorder ._external_log_url = external_log_url
302+ LogRecorder ._log_queue = queue .Queue (maxsize = 1000 ) # 设置队列大小,避免内存溢出
303+ LogRecorder ._log_thread = threading .Thread (target = LogRecorder ._log_sender_worker , daemon = True )
304+ LogRecorder ._log_thread .start ()
305+
306+ @staticmethod
307+ def _log_sender_worker ():
308+ """
309+ 后台工作线程,从队列中取出日志并发送到外部接口
310+ """
311+ while True :
312+ try :
313+ log_data = LogRecorder ._log_queue .get (timeout = 1 )
314+ if log_data is None : # 退出信号
315+ break
316+
317+ taskid , log_time , level , message = log_data
318+ LogRecorder ._send_log_to_external (taskid , log_time , level , message )
319+ LogRecorder ._log_queue .task_done ()
320+ except queue .Empty :
321+ continue
322+ except Exception :
323+ # 静默处理错误,避免影响主程序
324+ pass
325+
326+ @staticmethod
327+ def _send_log_to_external (taskid , log_time , level , message ):
328+ """
329+ 发送日志到外部接口
330+ """
331+ if not LogRecorder ._external_log_url :
332+ return
333+
334+ try :
335+ log_payload = {
336+ "taskid" : taskid ,
337+ "time" : log_time ,
338+ "level" : level ,
339+ "message" : message
340+ }
341+
342+ data = getBytes (jsonize (log_payload ))
343+ headers = {"Content-Type" : "application/json" }
344+
345+ req = _urllib .request .Request (LogRecorder ._external_log_url , data , headers )
346+
347+ # 设置超时,避免阻塞
348+ response = _urllib .request .urlopen (req , timeout = 5 )
349+ response .read () # 读取响应,确保请求完成
350+ except Exception :
351+ # 静默处理所有异常,确保外部接口失败不影响日志记录
352+ pass
353+
274354 def emit (self , record ):
275355 """
276356 Record emitted events to IPC database for asynchronous I/O
277357 communication with the parent process
358+ Also forward logs to external interface if configured
278359 """
279- conf .databaseCursor .execute ("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)" , (conf .taskid , time .strftime ("%X" ), record .levelname , str (record .msg % record .args if record .args else record .msg )))
360+ log_time = time .strftime ("%X" )
361+ log_level = record .levelname
362+ log_message = str (record .msg % record .args if record .args else record .msg )
363+
364+ # 写入数据库
365+ conf .databaseCursor .execute ("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)" , (conf .taskid , log_time , log_level , log_message ))
366+
367+ # 如果配置了外部日志接口,将日志加入队列异步发送
368+ if LogRecorder ._external_log_url and LogRecorder ._log_queue is not None :
369+ try :
370+ LogRecorder ._log_queue .put_nowait ((conf .taskid , log_time , log_level , log_message ))
371+ except queue .Full :
372+ # 队列满时静默丢弃,避免阻塞
373+ pass
280374
281375def setRestAPILog ():
282376 if conf .api :
@@ -680,7 +774,7 @@ def version(token=None):
680774 logger .debug ("Fetched version (%s)" % ("admin" if is_admin (token ) else request .remote_addr ))
681775 return jsonize ({"success" : True , "version" : VERSION_STRING .split ('/' )[- 1 ]})
682776
683- def server (host = RESTAPI_DEFAULT_ADDRESS , port = RESTAPI_DEFAULT_PORT , adapter = RESTAPI_DEFAULT_ADAPTER , username = None , password = None , database = None ):
777+ def server (host = RESTAPI_DEFAULT_ADDRESS , port = RESTAPI_DEFAULT_PORT , adapter = RESTAPI_DEFAULT_ADAPTER , username = None , password = None , database = None , log_url = None ):
684778 """
685779 REST-JSON API server
686780 """
@@ -695,6 +789,13 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST
695789 else :
696790 Database .filepath = database
697791
792+ # 如果提供了 log_url 参数,设置环境变量,这样所有子进程都会继承
793+ if log_url :
794+ os .environ ['SQLMAP_EXTERNAL_LOG_URL' ] = log_url
795+ logger .info ("External log URL configured: %s" % log_url )
796+ elif 'SQLMAP_EXTERNAL_LOG_URL' in os .environ :
797+ logger .info ("External log URL from environment: %s" % os .environ ['SQLMAP_EXTERNAL_LOG_URL' ])
798+
698799 if port == 0 : # random
699800 with contextlib .closing (socket .socket (socket .AF_INET , socket .SOCK_STREAM )) as s :
700801 s .bind ((host , 0 ))
0 commit comments