ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数据库被注入木马后恢复:用TaoToken统一Key排查异常连接与数据回滚

数据库被注入木马后恢复:用TaoToken统一Key排查异常连接与数据回滚 1. 数据库被注入木马后异常连接与可疑 SQL 到底怎么排查数据库被注入木马最典型的表现不是数据库直接崩掉而是页面里被塞进一段外部脚本或者某些字段莫名其妙多出一串script src...。我遇到过的一次是 SQL Server 里几乎所有varchar、nvarchar、text字段都被批量replace成了一段带外链的脚本攻击者用游标遍历所有用户表的所有字符列逐列执行update ... set ... replace(...)。这种注入不是单纯读数据而是写数据、改数据属于破坏性操作恢复难度比普通注入高一个量级。先明确这篇文章适合谁如果你正在处理数据库被注入木马后的应急恢复需要从异常连接、可疑 SQL 入手做备份校验、数据回滚和权限收紧那下面的步骤可以直接照着做。核心检索词就是「数据库被注入木马后恢复」我会把排查命令、恢复脚本和验证动作都写清楚。排查的第一步是确认「谁在连、连了什么、执行了什么」。以 SQL Server 为例先看当前活动连接和最近执行的语句-- 查看当前所有连接及其来源 SELECT s.session_id, s.login_name, s.host_name, s.program_name, s.client_interface_name, c.client_net_address, c.local_net_address, s.status, s.login_time, s.last_request_start_time, s.last_request_end_time FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_connections c ON s.session_id c.session_id WHERE s.is_user_process 1 ORDER BY s.last_request_start_time DESC;重点看host_name、program_name、client_net_address。如果出现陌生的主机名、非常规客户端程序名或者来自非业务网段的 IP就要标记为可疑。攻击者往往通过 Web 应用的注入点执行 SQL所以program_name可能是SQL Server Management Studio之外的连接库名比如某些脚本框架的驱动名。接着查最近执行过的 SQL 文本尤其是包含update、replace、cursor、exec的语句SELECT r.session_id, r.start_time, r.status, r.command, t.text AS sql_text FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.session_id 50 ORDER BY r.start_time DESC;如果数据库已经重启活动请求可能看不到历史。这时要依赖默认跟踪Default Trace或者审计日志。SQL Server 的默认跟踪文件可以用下面这句读取SELECT te.name AS event_name, t.DatabaseName, t.NTDomainName, t.ApplicationName, t.LoginName, t.StartTime, t.TextData FROM sys.fn_trace_gettable( (SELECT REVERSE(SUBSTRING(REVERSE(path), CHARINDEX(\, REVERSE(path)), 256)) FROM sys.traces WHERE is_default 1), DEFAULT) t JOIN sys.trace_events te ON t.EventClass te.trace_event_id WHERE te.name IN (Object:Altered, Object:Created, Object:Deleted) ORDER BY t.StartTime DESC;这段能帮你定位到「哪张表在什么时间被改过」。如果发现大量表在同一秒或同一分钟内被Altered基本可以确认是批量注入脚本干的。排查完连接和 SQL还要看数据本身。用下面这句统计哪些列被写入了可疑内容-- 以 SQL Server 为例查找包含 script 标签的列 DECLARE sql NVARCHAR(MAX) N; SELECT sql sql SELECT TABLE_SCHEMA . TABLE_NAME AS table_name, COLUMN_NAME AS column_name, COUNT(*) AS hit_count FROM [ TABLE_SCHEMA ].[ TABLE_NAME ] WHERE [ COLUMN_NAME ] LIKE %script% UNION ALL FROM INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE IN (varchar, nvarchar, text, ntext) AND TABLE_SCHEMA NOT IN (sys, INFORMATION_SCHEMA); SET sql LEFT(sql, LEN(sql) - 10); EXEC sp_executesql sql;这段脚本会生成一张「命中表」告诉你哪些表、哪些列被污染了。命中数量越多说明注入范围越大。到这一步你已经有了三个关键信息可疑连接来源、被改动的表清单、污染列清单。接下来才是恢复动作。2. TaoToken 统一 Key 在应急排查中的前置准备应急恢复时最怕的是「工具太多、Key 太散」。你可能同时用着多个模型服务、多个 API Key排查脚本里要调模型做日志分析恢复脚本里要调模型做 SQL 生成结果每个工具一套鉴权光配 Key 就耗掉半小时。TaoToken 的思路是把这些统一成一个 Key通过一个 Base URL 接入减少切换成本。TaoToken 是什么它是一个统一的大模型 API 接入层你可以在一个控制台里管理 Key用同一个 Base URL 调用不同模型。适合谁需要快速在脚本、CLI、IDE 插件里接入模型能力的开发和运维人员。能做什么统一鉴权、统一计费、统一模型 ID 管理避免在应急场景下被多个 Key 拖慢节奏。前置准备分三步。第一步拿到 Key。访问控制台创建 API Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole创建后复制 Key形如sk-...。第二步确认 Base URL。API 地址是https://taotoken.net/api注意这个地址不加 UTM 参数直接用于代码里的base_url。第三步确认模型 ID。你可以在模型对话页面先试一下目标模型是否可用https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodels如果你打算长期做编码和 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdocAPI Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys为什么应急场景要提这个因为排查注入时你往往需要让模型帮你快速解析一大段日志、生成回滚 SQL、或者对比备份差异。如果每次都要重新配 Key效率会很低。统一 Key 之后你的排查脚本、恢复脚本、验证脚本可以共用一套环境变量export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api这样后面所有 Python、Node、CLI 工具都能直接读这两个变量不用在每个工具里重复填。实测下来这一步能省掉大量「Key 在哪、用哪个」的来回确认。3. 可复制的配置片段把统一 Key 接进排查与恢复脚本这一节给可直接复制的配置。先给 Python 的 OpenAI SDK 配置这是最通用的方式# taotoken_client.py import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], # https://taotoken.net/api ) def analyze_log(log_text: str) - str: resp client.chat.completions.create( modelclaude-sonnet-4-20250514, # 按控制台可用模型 ID 替换 messages[ {role: system, content: 你是数据库安全分析助手只输出可疑 SQL 和连接特征。}, {role: user, content: log_text}, ], temperature0.2, ) return resp.choices[0].message.content如果你用 Node.js配置如下// taotokenClient.js import OpenAI from openai; const client new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, // https://taotoken.net/api }); export async function analyzeLog(logText) { const resp await client.chat.completions.create({ model: claude-sonnet-4-20250514, messages: [ { role: system, content: 你是数据库安全分析助手。 }, { role: user, content: logText }, ], temperature: 0.2, }); return resp.choices[0].message.content; }如果你用 Claude Code 或类似的 CLI 工具配置通常放在 settings 文件里。以 Claude Code 的settings.json为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }注意这里的三件套必须齐全Base URL、Key、Model ID。缺一个都会导致请求失败。如果你用的是 Cline 或 MCP 类工具配置里同样要写全这三项{ mcpServers: { taotoken: { command: npx, args: [-y, taotoken/mcp-server], env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_MODEL: claude-sonnet-4-20250514 } } } }如果你用 Codex 的auth.json结构类似{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: claude-sonnet-4-20250514 }这些配置的共同点是Base URL 固定为https://taotoken.net/apiKey 从环境变量或配置文件读取Model ID 按控制台实际可用的填。把这三件套写对后面的排查脚本和恢复脚本才能稳定调用模型做辅助分析。配置完成后先跑一个最小验证curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [{role: user, content: 只回复 OK}] }如果返回里有choices字段说明配置通了。这一步很重要因为后面恢复脚本里会用到模型做 SQL 生成和日志比对如果这里不通后面全卡住。4. 验证请求与成功结果备份校验、数据回滚与权限收紧配置通了之后进入恢复主体。顺序是先校验备份再回滚数据最后收紧权限。不要跳过备份校验直接回滚否则可能把污染数据覆盖到干净备份上。第一步备份校验。列出最近的备份文件确认时间点和大小SELECT database_name, backup_start_date, backup_finish_date, backup_size, physical_device_name FROM msdb.dbo.backupset bs JOIN msdb.dbo.backupmediafamily bmf ON bs.media_set_id bmf.media_set_id WHERE database_name 你的库名 ORDER BY backup_start_date DESC;找到注入发生前的那个备份记下physical_device_name。然后用RESTORE VERIFYONLY校验备份是否可读RESTORE VERIFYONLY FROM DISK D:\backup\你的库名_20250101.bak WITH CHECKSUM;如果返回「备份集有效」说明备份可用。如果报校验和错误换更早的备份。第二步数据回滚。有两种策略整库恢复和按表恢复。如果污染范围大直接整库恢复-- 先切到 master USE master; GO -- 断开现有连接 ALTER DATABASE [你的库名] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; GO -- 恢复 RESTORE DATABASE [你的库名] FROM DISK D:\backup\你的库名_20250101.bak WITH REPLACE, RECOVERY, CHECKSUM; GO -- 恢复多用户 ALTER DATABASE [你的库名] SET MULTI_USER; GO如果只想恢复被污染的表可以把备份还原成临时库再按表导回RESTORE DATABASE [你的库名_restore] FROM DISK D:\backup\你的库名_20250101.bak WITH MOVE 你的库名 TO D:\data\你的库名_restore.mdf, MOVE 你的库名_log TO D:\data\你的库名_restore_log.ldf, RECOVERY, CHECKSUM; GO -- 对比污染表按主键导回 INSERT INTO [你的库名].[dbo].[目标表] SELECT * FROM [你的库名_restore].[dbo].[目标表] t WHERE NOT EXISTS ( SELECT 1 FROM [你的库名].[dbo].[目标表] c WHERE c.id t.id );回滚完成后用第 1 节的污染统计脚本再跑一遍确认hit_count全部为 0。如果还有命中说明回滚不完整需要检查是否漏了表或列。第三步权限收紧。注入能成功通常是因为应用连接账号权限过大。检查当前账号权限-- 查看数据库用户及其角色 SELECT dp.name AS user_name, dp.type_desc, r.name AS role_name FROM sys.database_principals dp LEFT JOIN sys.database_role_members drm ON dp.principal_id drm.member_principal_id LEFT JOIN sys.database_principals r ON drm.role_principal_id r.principal_id WHERE dp.type IN (S, U, G) ORDER BY dp.name;如果应用账号是db_owner或sysadmin必须降权。只给必要的SELECT、INSERT、UPDATE、DELETE去掉DROP、ALTER、EXEC-- 撤销高危权限 REVOKE ALTER ON DATABASE::[你的库名] FROM [应用账号]; REVOKE CONTROL ON DATABASE::[你的库名] FROM [应用账号]; DENY EXECUTE ON SCHEMA::dbo TO [应用账号]; -- 只保留必要权限 GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO [应用账号];同时检查应用连接字符串确认没有用sa或高权限账号。如果用了立刻换成最小权限账号。验证动作用应用账号执行一次DROP TABLE测试应该被拒绝。如果还能执行说明权限没收紧到位。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth应急恢复过程中报错往往集中在鉴权和网络层。下面按真实报错逐个排查。401 Unauthorized。最常见的原因是 Key 没读到或写错。检查环境变量echo $TAOTOKEN_API_KEY如果为空说明没导出。如果 Key 里有空格或换行也会导致 401。重新复制 Key确保没有多余字符。另外确认 Base URL 是https://taotoken.net/api不要多写/v1或少写https。local proxy failed。这个报错通常出现在本地网络环境有额外代理设置时。检查环境变量env | grep -i proxy如果有HTTP_PROXY、HTTPS_PROXY先临时取消unset HTTP_PROXY HTTPS_PROXY然后重试请求。如果取消后正常说明是本地代理配置和当前网络环境冲突需要按实际网络环境调整不要保留无效代理。reading choices 报错。典型信息是Cannot read properties of undefined (reading choices)。这说明返回体里没有choices字段通常是请求没成功但代码直接取了resp.choices[0]。修复方式是先判断const resp await client.chat.completions.create({...}); if (!resp || !resp.choices || resp.choices.length 0) { console.error(返回异常:, JSON.stringify(resp)); throw new Error(模型返回为空); } return resp.choices[0].message.content;同时检查 Model ID 是否写错。如果模型 ID 不存在接口可能返回错误对象而不是标准结构。OAuth 相关报错。如果你用的是 Claude Code 或类似 CLI报OAuth token expired或invalid_grant说明 CLI 走了 OAuth 流程而不是 API Key。检查settings.json里是否同时存在 OAuth 配置和 API Key 配置。如果有冲突删掉 OAuth 相关字段只保留{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }然后重启 CLI。如果还报 OAuth检查是否有全局配置文件覆盖了项目配置。备份恢复报错。常见的是RESTORE DATABASE is terminating abnormally原因可能是备份文件被占用、磁盘空间不足、或者数据库还在使用中。先确认SINGLE_USER已设置再检查磁盘剩余空间df -h如果是 Windows用dir看目标盘剩余空间。空间不足时先清理旧备份。权限收紧后应用报错。如果应用突然报The SELECT permission was denied说明权限给少了。用下面这句查应用账号实际权限SELECT dp.name, o.name AS object_name, p.permission_name, p.state_desc FROM sys.database_permissions p JOIN sys.database_principals dp ON p.grantee_principal_id dp.principal_id LEFT JOIN sys.objects o ON p.major_id o.object_id WHERE dp.name 应用账号;对照报错的表名补上对应SELECT或EXECUTE权限。不要直接给db_owner按最小必要补。6. 把统一 Key 用在长期数据库安全巡检里恢复完成不代表结束。注入木马往往有反复攻击者可能留了后门或者继续尝试。把这次用到的排查脚本固化成日常巡检用统一 Key 驱动模型做日志摘要和异常检测能提前发现苗头。具体做法写一个定时任务每天拉取数据库审计日志用第 3 节的analyze_log函数做一次分析输出可疑 SQL 清单。如果清单非空触发告警。这样不用人工天天翻日志。如果你需要长期跑这类编码和 Agent 任务Coding Plan 比按次调用更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan接入文档里有完整的参数说明和示例https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdocKey 管理在控制台https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys想先试模型效果用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodels最后给一个实操建议把这次恢复过程中的所有 SQL 脚本、配置片段、报错记录整理成一个recovery.md放在项目根目录。下次再遇到类似问题直接按文档走不用重新摸索。数据库安全这件事恢复速度取决于平时准备了多少可复制的步骤。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进