Skip to content

Commit ac9b155

Browse files
committed
Modify child processes to allow logging to other interfaces.
1 parent 876f141 commit ac9b155

1 file changed

Lines changed: 107 additions & 6 deletions

File tree

lib/utils/api.py

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import contextlib
1212
import logging
1313
import os
14+
import queue
1415
import re
1516
import shlex
1617
import 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

273282
class 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

281375
def 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

Comments
 (0)