为什么写这篇:两个看起来不相关的小技巧,生产里偶尔要用到——① SQL Server 触发器拉 WebHook 推送变更通知(典型场景:业务系统监听数据变更同步到第三方);② Navicat 的密码忘了或不方便找 DBA 要,需要从导出的 connections.ncx 文件里解出来。
适用读者:运维 DBA、后端开发、密码管理员。
前置知识:会用 SQL Server Management Studio、有 Python 基础。
⚠️ 本文仅做技术研究——所有密码相关示例均使用公开的开源工具与算法,不针对任何特定软件版本绕过授权。NcxReader 解密章节旨在让读者理解 Navicat 的"密码 = 透明存储"风险,从而推动生产环境改用专业密码管理工具。请勿用于非法用途。
目录
- SQL Server 触发器调用 Web 接口
- Navicat 密码加密原理
- NcxReader 一键解密
1. SQL Server 触发器调用 Web 接口
1.1 业务场景
某业务系统需要"主库一插入新数据,立即推送到第三方 API"——不想改业务代码(其他表也有类似需求),决定用触发器实现。
⚠️ 副作用警告:触发器里同步调 HTTP 会拖慢原表的 INSERT 性能——HTTP 慢、超时、第三方挂掉都会影响。建议配合异步队列(触发器只写队列表,外部消费者拉队列)使用。本文演示的是最简同步版。
1.2 启用 Ole Automation Procedures
SQL Server 默认不开启 Ole Automation(用于在 T-SQL 里调用 COM 对象),需要先开:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -- 1. 允许修改高级选项
sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
-- 2. 启用 Ole Automation Procedures
sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
-- 3. 验证
EXEC sp_configure 'Ole Automation Procedures';
GO
|
Ole Automation 仅在 Windows 上可用,Linux 上的 SQL Server 跑不了。
1.3 HTTP 请求的存储过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| -- 创建辅助存储过程
CREATE PROCEDURE P_GET_HttpRequestData (
@URL VARCHAR(500),
@status INT = 0 OUT,
@returnText VARCHAR(2000) = '' OUT
) AS
BEGIN
DECLARE @object INT, @errSrc INT;
-- 创建 COM 对象(Msxml2.ServerXMLHTTP.3.0)
EXEC @status = SP_OACreate 'Msxml2.ServerXMLHTTP.3.0', @object OUT;
IF @status <> 0 BEGIN
EXEC SP_OAGetErrorInfo @object, @errSrc OUT, @returnText OUT;
RETURN;
END;
-- open GET
EXEC @status = SP_OAMethod @object, 'open', NULL, 'GET', @URL;
IF @status <> 0 BEGIN
EXEC SP_OAGetErrorInfo @object, @errSrc OUT, @returnText OUT;
RETURN;
END;
-- setRequestHeader
EXEC @status = SP_OAMethod @object, 'setRequestHeader',
'Content-Type', 'application/x-www-form-urlencoded';
-- send
EXEC @status = SP_OAMethod @object, 'send', NULL;
IF @status <> 0 BEGIN
EXEC SP_OAGetErrorInfo @object, @errSrc OUT, @returnText OUT;
RETURN;
END;
END
|
1.4 创建触发器(AFTER INSERT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| -- 1. 删旧触发器
DROP TRIGGER IF EXISTS [dbo].[hg_demo_notify_trigger];
-- 2. 建触发器
CREATE TRIGGER [dbo].[hg_demo_notify_trigger]
ON [dbo].[hg_demo]
AFTER INSERT
AS
BEGIN
DECLARE @id VARCHAR(50), @name VARCHAR(50);
DECLARE @url VARCHAR(4000), @t VARCHAR(10);
-- 从 inserted 虚表里拿新插入的数据
SELECT @id = id, @name = name FROM inserted;
-- 拼 URL(这里要按第三方 API 协议拼)
SET @t = '''';
SET @url = 'http://internal.example.com/api/v1/demo/insert' +
'?sql=insert into hg_demo(id,name)values(' +
@t + @id + @t + ',' + @t + @name + @t + ')';
-- 调用 HTTP
EXECUTE P_GET_HttpRequestData @url;
END;
|
1.5 验证触发器
1
2
3
4
5
| -- 1. 插入一条数据
INSERT INTO hg_demo (id, name) VALUES ('test_001', 'hello world');
-- 2. 检查目标端是否收到
-- 调第三方 API 的 admin 后台
|
1.6 关闭 Ole Automation(如果不用了)
1
2
3
4
5
6
7
8
9
| -- 关闭
sp_configure 'show advanced options', 1;
RECONFIGURE;
sp_configure 'Ole Automation Procedures', 0;
RECONFIGURE;
-- 关 advanced options
sp_configure 'show advanced options', 0;
RECONFIGURE;
|
1.7 性能与稳定性建议
| 问题 | 修法 |
|---|
| 触发器拖慢 INSERT | 异步化:触发器只写队列表,后台 Job 拉队列调 HTTP |
| HTTP 超时卡死触发器 | 加 timeout:SP_OASetProperty @object, 'setTimeouts', 5000, 5000, 10000, 10000 |
| 第三方 4xx/5xx 仍要写库 | 触发器里加 BEGIN TRY ... END TRY / BEGIN CATCH ... END CATCH 捕获异常 |
| HTTP 同步调易丢失上下文 | 触发器里 SET NOCOUNT ON,避免消息干扰 |
1.8 异步化改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| -- 队列表
CREATE TABLE http_notify_queue (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
payload NVARCHAR(MAX),
created_at DATETIME DEFAULT GETDATE(),
sent_at DATETIME NULL
);
-- 触发器只入队
CREATE TRIGGER [dbo].[hg_demo_async_trigger]
ON [dbo].[hg_demo]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @id VARCHAR(50), @name VARCHAR(50);
SELECT @id = id, @name = name FROM inserted;
INSERT INTO http_notify_queue (payload)
VALUES ('{"id":"' + @id + '","name":"' + @name + '"}');
END
-- 后台 Job(每 10 秒拉一批)
DECLARE @id BIGINT, @payload NVARCHAR(MAX);
DECLARE cur CURSOR FOR
SELECT TOP 100 id, payload
FROM http_notify_queue
WHERE sent_at IS NULL
ORDER BY id;
OPEN cur;
FETCH NEXT FROM cur INTO @id, @payload;
WHILE @@FETCH_STATUS = 0
BEGIN
-- 调 HTTP(用 PowerShell 或 SQL Agent 的 Web Service Task)
EXEC P_GET_HttpRequestData 'http://internal.example.com/api/notify?data=' + @payload;
UPDATE http_notify_queue SET sent_at = GETDATE() WHERE id = @id;
FETCH NEXT FROM cur INTO @id, @payload;
END
CLOSE cur;
DEALLOCATE cur;
|
2. Navicat 密码加密原理
2.1 connections.ncx 是什么
Navicat Premium 12+ 把所有连接信息存在两个地方:
- 注册表(Windows):
HKEY_CURRENT_USER\Software\PremiumSoft\Navicat\Servers - JSON 文件(导出):
connections.ncx
2.2 导出 ncx 文件
菜单:文件 → 导出连接 → 选 *.ncx 格式。
2.3 ncx 文件结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <?xml version="1.0" encoding="UTF-8"?>
<Connections Ver="1.4">
<Connection
ConnectionName="localhost_5432-pg"
ConnType="POSTGRESQL"
Host="localhost"
Port="5432"
Database="postgres"
UserName="postgres"
Password="6E3CD79374732F89F047B5316FD28637"
SavePassword="true"
SettingsSavePath="C:\Users\me\Documents\Navicat\PostgreSQL\Servers\localhost_5432-pg"
...
/>
</Connections>
|
关键字段:Password="6E3CD79374732F89F047B5316FD28637"——这是一段AES 加密的十六进制。
2.4 加密算法
Navicat 12+ 使用 AES-128-CBC:
- 密钥:固定的
libcckeyi_dc(早期版本)或动态计算(新版) - IV:固定的
libcciv 字符串 - 填充:PKCS7
不同 Navicat 版本密钥有差异——Navicat 11/12 用 libcckeyi_dc,Navicat 15+ 用 b'\x95\xd1\xdc\x7b\x9c\x53\x4f\xfb\x38\xa7\x1c\x8a\x47\x39\x33\x48' 等。
3. Navicat 密码安全建议(替代 NcxReader 解密)
⚠️ 本节为合规整改——上一版本"用 NcxReader 一键解密"的内容已全部删除。
原因:解密工具的"一键回显明文"在生产环境等于把数据库明文密码公示给任何能拿到 connections.ncx 的人。即使只用于"自用恢复"也存在合规风险(密码出现在工具输出里、shell history 里、剪贴板里、屏幕录像里)。本节改为给运维/管理员的密码管理建议。
3.1 风险评估:把 Navicat 密码当"明文"对待
从第 2 节的算法分析可以反推风险:
| 资产 | 暴露场景 | 风险等级 |
|---|
connections.ncx 文件 | 备份到云盘、邮件转发、U 盘拷贝 | 高(拿到即拿到所有明文密码) |
注册表 HKCU\Software\PremiumSoft\Navicat\Servers | 同事借电脑、镜像备份 | 高 |
| 屏幕/截屏 | 录屏软件、远程协助 | 中 |
| shell 历史(误用工具时) | history 命令、运维审计 | 中 |
结论:Navicat 的"密码保护"只是防误看的提示框,不是安全机制。生产环境的数据库密码绝对不能依赖 Navicat 存。
3.2 推荐方案:1Password / KeePass / Vault
生产推荐:所有数据库密码放进专业密码管理工具,Navicat 里只配连接信息(host/port/user),Password 字段留空——每次连手动从密码管理器复制粘贴。
1
2
3
4
5
| # 1Password CLI 取密码(登录后)
op read "op://Prod/PostgreSQL/password"
# 复制到剪贴板(不进入 shell history)
op read "op://Prod/PostgreSQL/password" | pbcopy # macOS
op read "op://Prod/PostgreSQL/password" | clip # Windows
|
1
2
| # HashiCorp Vault(自建场景)
vault kv get -field=password secret/postgres/prod
|
3.3 推荐方案:SSH 私钥 / IAM 临时凭证
云数据库/堡垒机场景——根本不用静态密码:
| 场景 | 推荐方案 |
|---|
| AWS RDS | IAM 数据库身份验证(临时令牌,15 分钟过期) |
| Azure SQL | Azure AD 身份验证 |
| 阿里云 RDS | RAM 角色 + 临时 AccessKey |
| 跳板机登录 | SSH 公私钥(Navicat 直接支持) |
| 自建 PG | pg_hba.conf 改 cert 认证(SSL 客户端证书) |
优势:没有静态密码 = 没有泄露。
3.4 Navicat 自带的"安全"配置(仍然不够)
如果一定要在 Navicat 里存密码,至少做以下几项:
- Navicat → 工具 → 选项 → 常规 → 勾选"使用密码管理主密码"(让 Navicat 用一个 master password 二次加密)
- 不要导出 connections.ncx——导出 = 把二次加密绕过
- 不在 Navicat 里存 root / sa / 超级管理员密码——只存低权限的查询账号
- 定期 Rotate 密码——即使泄露也能在窗口期内失效
3.5 应急恢复(忘记密码的合法途径)
| 身份 | 该怎么办 |
|---|
| 开发者本人 | 联系 DBA 重置数据库密码(5 分钟搞定) |
| DBA | 用 SQL 重置:ALTER USER app_user WITH PASSWORD 'new_pwd';(PG/MySQL) / ALTER LOGIN sa WITH PASSWORD = '...';(MSSQL) |
| 离职人员 | 不该有恢复密码的诉求——走正式交接,DBA 重置所有相关账号 |
| 审计/合规 | 从密码管理器/堡垒机审计日志查——永远不查 Navicat |
3.6 清理 Navicat 缓存的旧密码
1
2
3
4
5
| # Windows:删除注册表里的所有 Navicat 密码
Remove-Item -Path "HKCU:\Software\PremiumSoft\Navicat\Servers\*" -Recurse -Force
# macOS:删除偏好设置
rm -rf ~/Library/Preferences/com.premiumsoft.navicat*
|
或者图形化:
1
| Navicat → 连接属性 → 高级 → 清空 Password 字段
|
3.7 一次完整整改清单
按下面 5 步走,把团队的 Navicat 密码问题一次性根治:
- 盘点:列出所有用 Navicat 管理数据库的同事 + 数据库清单
- 建库:1Password / Vault 建
Databases vault,按数据库分条目 - 轮换:所有数据库账号密码轮换一次(Navicat 旧密码全部作废)
- 配置:Navicat 只填连接信息,密码字段留空
- 文档:写一个 1 页 SOP:“新员工入职数据库访问申请流程”
经验总结
- 触发器调 Web API 慎用同步——同步触发器=业务稳定性的脆弱点
- Ole Automation 仅 Windows——Linux SQL Server 走 HTTP 需要装 SQL Server Machine Learning Services + Python
- Navicat 密码不是真加密——固定 IV + 公开密钥的 AES-CBC 等于明文,视为泄露
- 生产密码放 1Password / KeePass / Vault,不放 Navicat
- 尽量不存静态密码——云数据库走 IAM/AD 认证、自建 PG 走 SSL 证书
- 导出 ncx = 全公司数据库钥匙拱手送人——禁止导出,本地连接用密码管理器代填