深信服安全软件拦截子进程的排查与解决方案
最近在跑量化回测脚本时,遇到一个诡异问题:Python脚本执行到subprocess调用时直接卡死,PowerShell窗口无响应,任务管理器里能看到进程但无法结束。排查了一圈,最后定位到是深信服(Sangfor)安全软件在拦截子进程创建。
这篇文章记录完整排查过程和解决方案,给同样被深信服坑过的朋友一个参考。
问题现象
我有个自动化脚本,需要周期性调用外部程序获取行情数据:
import subprocess import time def fetch_market_data(): # 调用外部行情程序获取数据 result = subprocess.run( ['market_data_fetcher.exe', '--symbol', 'BTCUSDT', '--interval', '1m'], capture_output=True, text=True, timeout=30 ) return result.stdout if __name__ == '__main__': while True: try: data = fetch_market_data() print(f"获取数据成功: {len(data)} bytes") except subprocess.TimeoutExpired: print("警告: 子进程执行超时") except Exception as e: print(f"错误: {e}") time.sleep(5)运行后出现以下现象:
- 脚本卡在
subprocess.run(),既不返回也不报错 - 任务管理器能看到
market_data_fetcher.exe进程,但CPU占用为0 - 手动打开PowerShell执行相同命令,PowerShell直接无响应
- 关闭深信服安全软件后,一切恢复正常
排查过程
第一步:确认问题范围
先写个最小化测试脚本,排除业务代码干扰:
import subprocess # 测试1: 基础命令 try: result = subprocess.run(['echo', 'hello'], capture_output=True, timeout=5) print(f"echo 执行成功: {result.stdout}") except Exception as e: print(f"echo 执行失败: {e}") # 测试2: PowerShell命令 try: result = subprocess.run( ['powershell', '-Command', 'Get-Process | Select-Object -First 5'], capture_output=True, timeout=10 ) print(f"PowerShell 执行成功: {result.stdout.decode()}") except Exception as e: print(f"PowerShell 执行失败: {e}")测试结果:
echo命令正常执行powershell命令卡死cmd /c dir正常执行- 任何需要创建新进程的调用都可能被拦截
这说明问题不是简单的权限问题,而是安全软件对特定进程创建行为的拦截。
第二步:检查安全软件日志
深信服安全软件的管理后台有详细的进程拦截日志。如果你有管理员权限,可以查看:
- 终端管理→安全日志→进程防护日志
- 重点关注被拦截的进程路径和操作类型
日志中会明确显示拦截规则,例如:
进程: powershell.exe 操作: 创建子进程 目标: cmd.exe 规则: 禁止PowerShell创建子进程第三步:确认拦截规则
深信服的默认策略比较激进,特别是针对PowerShell和WScript这类脚本宿主。常见的拦截场景:
- PowerShell创建子进程- 防止恶意脚本通过PowerShell下载执行
- Python调用系统命令- 防止Python脚本逃逸沙箱
- 进程注入行为- 某些正常的数据采集也会被误判
临时解决方案
在没有IT管理员权限的情况下,先用以下方法绕过:
方案一:使用os.system替代subprocess
import os # 使用os.system替代subprocess.run os.system('market_data_fetcher.exe --symbol BTCUSDT --interval 1m') # 如果需要获取输出,重定向到文件 os.system('market_data_fetcher.exe --symbol BTCUSDT --interval 1m > output.txt')注意:os.system有命令注入风险,且无法直接获取输出,仅作临时方案。
方案二:通过cmd中转
import subprocess def run_with_cmd(command): """通过cmd.exe中转执行命令""" # 将命令包装为cmd /c的形式 cmd_command = f'cmd /c {command}' try: result = subprocess.run( cmd_command, shell=True, capture_output=True, text=True, timeout=15 ) return result.stdout except subprocess.TimeoutExpired: return "执行超时" # 使用示例 output = run_with_cmd('market_data_fetcher.exe --symbol BTCUSDT --interval 1m') print(output)这个方法利用cmd.exe作为中间层,很多时候能绕过安全软件对直接创建子进程的拦截。
方案三:使用ctypes调用Windows API
import ctypes import ctypes.wintypes def run_powershell_script(script): """使用Windows API直接执行PowerShell脚本""" # 创建进程的Windows API CREATE_NO_WINDOW = 0x08000000 class STARTUPINFO(ctypes.Structure): _fields_ = [("cb", ctypes.wintypes.DWORD), ("lpReserved", ctypes.wintypes.LPWSTR), ("lpDesktop", ctypes.wintypes.LPWSTR), ("lpTitle", ctypes.wintypes.LPWSTR), ("dwX", ctypes.wintypes.DWORD), ("dwY", ctypes.wintypes.DWORD), ("dwXSize", ctypes.wintypes.DWORD), ("dwYSize", ctypes.wintypes.DWORD), ("dwXCountChars", ctypes.wintypes.DWORD), ("dwYCountChars", ctypes.wintypes.DWORD), ("dwFillAttribute", ctypes.wintypes.DWORD), ("dwFlags", ctypes.wintypes.DWORD), ("wShowWindow", ctypes.wintypes.WORD), ("cbReserved2", ctypes.wintypes.WORD), ("lpReserved2", ctypes.POINTER(ctypes.c_byte)), ("hStdInput", ctypes.wintypes.HANDLE), ("hStdOutput", ctypes.wintypes.HANDLE), ("hStdError", ctypes.wintypes.HANDLE)] class PROCESS_INFORMATION(ctypes.Structure): _fields_ = [("hProcess", ctypes.wintypes.HANDLE), ("hThread", ctypes.wintypes.HANDLE), ("dwProcessId", ctypes.wintypes.DWORD), ("dwThreadId", ctypes.wintypes.DWORD)] # 构造命令 command = f'powershell -Command "{script}"' # 创建进程 si = STARTUPINFO() pi = PROCESS_INFORMATION() si.cb = ctypes.sizeof(STARTUPINFO) success = ctypes.windll.kernel32.CreateProcessW( None, # 应用程序名 command, # 命令行 None, # 进程安全属性 None, # 线程安全属性 False, # 句柄继承 CREATE_NO_WINDOW, # 创建标志 None, # 环境变量 None, # 当前目录 ctypes.byref(si), ctypes.byref(pi) ) if not success: error_code = ctypes.windll.kernel32.GetLastError() return f"创建进程失败,错误码: {error_code}" # 等待进程结束 ctypes.windll.kernel32.WaitForSingleObject(pi.hProcess, 30000) # 关闭句柄 ctypes.windll.kernel32.CloseHandle(pi.hProcess) ctypes.windll.kernel32.CloseHandle(pi.hThread) return "执行完成" # 使用示例 result = run_powershell_script("Get-Process | Select-Object -First 5") print(result)这个方案绕过常规的进程创建API,直接调用Win32 API,成功率较高。
方案四:使用计划任务
import subprocess import xml.etree.ElementTree as ET def create_scheduled_task(task_name, command, args): """通过计划任务执行命令""" # 创建计划任务XML task_xml = f'''<?xml version="1.0" encoding="UTF-16"?> <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <Triggers> <CalendarTrigger> <StartBoundary>2024-01-01T00:00:00</StartBoundary> <Enabled>true</Enabled> <ScheduleByDay> <DaysInterval>1</DaysInterval> </ScheduleByDay> </CalendarTrigger> </Triggers> <Principals> <Principal id="Author"> <LogonType>InteractiveToken</LogonType> <RunLevel>LeastPrivilege</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> </Settings> <Actions> <Exec> <Command>{command}</Command> <Arguments>{args}</Arguments> </Exec> </Actions> </Task>''' # 保存XML文件 with open(f'{task_name}.xml', 'w', encoding='utf-16') as f: f.write(task_xml) # 导入计划任务 subprocess.run(['schtasks', '/Create', '/TN', task_name, '/XML', f'{task_name}.xml', '/F'], capture_output=True, text=True) # 立即运行 subprocess.run(['schtasks', '/Run', '/TN', task_name], capture_output=True, text=True) # 清理 subprocess.run(['schtasks', '/Delete', '/TN', task_name, '/F'], capture_output=True, text=True) import os os.remove(f'{task_name}.xml') # 使用示例 create_scheduled_task('data_fetch', 'market_data_fetcher.exe', '--symbol BTCUSDT --interval 1m')计划任务由系统服务启动,通常能绕过安全软件的进程创建拦截。
与IT部门沟通建议
临时方案只能应急,根本解决需要IT部门调整安全策略。建议这样沟通:
1. 准备证据
# 收集拦截证据的脚本 import subprocess import datetime import json def collect_evidence(): """收集安全软件拦截的证据""" evidence = { 'timestamp': datetime.datetime.now().isoformat(), 'test_cases': [] } # 测试用例1: Python直接调用subprocess try: result = subprocess.run(['powershell', '-Command', 'echo test'], capture_output=True, timeout=5) evidence['test_cases'].append({ 'name': 'python_subprocess_powershell', 'status': 'success', 'output': result.stdout.decode() }) except Exception as e: evidence['test_cases'].append({ 'name': 'python_subprocess_powershell', 'status': 'failed', 'error': str(e) }) # 测试用例2: 通过cmd中转 try: result = subprocess.run(['cmd', '/c', 'echo test'], capture_output=True, timeout=5) evidence['test_cases'].append({ 'name': 'python_subprocess_cmd', 'status': 'success', 'output': result.stdout.decode() }) except Exception as e: evidence['test_cases'].append({ 'name': 'python_subprocess_cmd', 'status': 'failed', 'error': str(e) }) # 保存证据 with open('intercept_evidence.json', 'w', encoding='utf-8') as f: json.dump(evidence, f, ensure_ascii=False, indent=2) print(f"证据已保存到 intercept_evidence.json") print(json.dumps(evidence, ensure_ascii=False, indent=2)) if __name__ == '__main__': collect_evidence()2. 沟通要点
- 明确业务需求:说明Python脚本是量化交易系统的一部分,需要调用外部程序获取行情数据
- 提供测试结果:展示证据文件,说明哪些操作被拦截,哪些正常
- 请求白名单:建议将Python解释器和常用工具加入白名单
- 最小权限原则:请求调整安全策略,而不是完全关闭安全软件
总结
深信服安全软件对子进程创建的拦截确实给自动化脚本带来不少麻烦。从排查到解决,核心思路是:
- 确认问题范围- 用最小化测试脚本定位被拦截的操作
- 查看安全日志- 获取具体的拦截规则
- 临时绕过- 使用
os.system、cmd中转、Win32 API或计划任务 - 根本解决- 与IT部门沟通,调整安全策略
如果你的环境里也有类似的安全软件拦截问题,建议优先走正规渠道解决。临时方案只是权宜之计,长期运行还是需要稳定的环境。
更多Python量化与自动化实战内容,请关注本站。