第 51 课:安全重构:把高风险远控改造成合规远程运维工具
# 第 51 课:安全重构:把高风险远控改造成合规远程运维工具
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课是整个专栏的收束课。前43 课已经把源码结构、主控端、网络协议、插件加载、屏幕音频采集、注入、驱动、凭据读取、横向风险、检测工程和应急响应都讲过了。本课不再继续拆某一个危险功能,而是反过来思考:如果企业真的需要远程运维工具,应该如何设计成合规、安全、可控、可追溯的系统。

# 1. 重构目标
不能把高风险远控源码简单换个界面就当远程运维工具。合规远程运维工具的底线是:有授权、有身份、有权限边界、有用户提示、有操作记录、有插件可信链、有最小化采集、有应急停用能力
| 高风险设计 | 合规替代 |
|---|---|
| 短标识或弱口令连接 | mTLS、MFA、设备证书 |
| 命令直接进入功能分支 | RBAC、审批、会话级授权 |
| 插件二进制直接下发 | 插件签名、哈希白名单、版本清单 |
| 隐蔽采集屏幕、音频、摄像头 | 用户可见提示、授权确认、采集范围限制 |
| 高危能力默认可用 | 删除或按场景隔离,并增加强制审批 |
| 无统一操作记录 | 结构化日志、会话记录、结果回执 |
依赖说明: 重构时不能只替换网络库或界面。HPSocket 类通信层需要升级为强认证和加密通道,插件加载链需要签名和哈希白名单,屏幕/音频/摄像头依赖需要用户可见授权,SQLite/OpenSSL/DPAPI 类凭据读取能力应删除,MemoryModule/yapi/远程线程类能力应删除或隔离到受控安全实验环境。后续示例只给合规设计方向,不提供高危复用步骤。
# 2. 先切断哪些能。
有些能力不适合出现在企业远程运维产品中,即便加上认证也不建议保留
| 能力 | 处理建议 | 原因 |
|---|---|---|
| 浏览器密码和 Cookie 读取 | 删除 | 运维没有合理理由读取用户凭据 |
| 代码块注入、远程线 | 删除 | 进程边界突破风险过高 |
| 内核隐藏、强制删除、内核注 | 删除 | 不符合透明运维原则 |
| 键盘记录 | 删除 | 隐私风险不可接受 |
| 无提示摄像头和麦克风采集 | 删除或改成用户主动授 | 必须满足隐私合规 |
| 高并发流量生 | 删除 | 与运维目标无关,风险过高 |
# 3. 源码反面样例:通信标识不足以证明身。
下面这段代码10 字节 m_password 承载通信标识,其中包含时间和位数字段。它可以帮助程序识别连接状态,但不能证明操作者身份,也不能证明服务端可信。
// 源码位置:主插件\HPSocket\TcpSocket.cpp,约55-70 bool CTcpSocket::Connect(LPCTSTR lpszHost, UINT nPort)
{
activetime = timeGetTime();
ZeroMemory(m_password, 10);
memcpy(m_password, &activetime, 8);
m_password[8] = TOKEN_ACTIVED;
#ifdef _WIN64
m_password[9] = 1;
#else
m_password[9] = 0;
#endif
m_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 防御分析:这里更像会话标识,不是强身份认证 // 合规远程运维必须使用双向身份认证和可吊销设备证书}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
改造方向:
| 设计 | 建议 |
|---|---|
| 管理端身 | 使用企业 IdP 登录,强MFA |
| 被管理端身份 | 设备证书绑定资产 ID |
| 通信通道 | mTLS,证书链校验和证书吊销 |
| 会话密钥 | 每次会话协商短期密钥 |
| 连接策略 | 仅允许登记资产和登记平台互连 |
# 4. 源码反面样例:异或不是加。
// 源码位置:主插件\HPSocket\TcpSocket.cpp,约200-208 int CTcpSocket::Send(LPBYTE lpData, UINT nSize)
{
LONG nBufLen = nSize + 14;
m_WriteBuffer.Write((PBYTE)&nBufLen, 4);
m_WriteBuffer.Write((PBYTE)m_password, 10);
m_WriteBuffer.Write(lpData, nSize, 1, m_password);
SendWithSplit(
m_WriteBuffer.GetBuffer(),
m_WriteBuffer.GetBufferLen(),
MAX_SEND_BUFFER);
// 防御分析:数据体经过异或处理,但缺少标准加密和完整性校验 // 合规设计应使TLS 1.3 AEAD,并校验每条消息}
2
3
4
5
6
7
8
9
10
11
12
13
// 源码位置:主插件\HPSocket\TcpSocket.cpp,约275-302 bool CTcpSocket::OnReceive(const BYTE* pData, int iLength)
{
m_CompressionBuffer.Write((LPBYTE)pData, iLength);
while ((int)(m_CompressionBuffer.GetBufferLen()) > 14)
{
if ((memcmp(m_password, m_CompressionBuffer.GetBuffer(4), 10) != 0))
{
m_CompressionBuffer.ClearBuffer();
return FALSE;
}
int nSize = 0;
CopyMemory(&nSize, m_CompressionBuffer.GetBuffer(0), sizeof(int));
m_DeCompressionBuffer.Write(
m_CompressionBuffer.GetBuffer(14),
nSize - 14,
1,
m_password);
m_pManager->OnReceive(
m_DeCompressionBuffer.GetBuffer(0),
m_DeCompressionBuffer.GetBufferLen());
// 防御分析:包完整后直接进入管理器分发 // 合规产品应在业务分发前完成签名、权限和会话状态校验 }
}
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
协议重构建议
| 当前问题 | 合规改 |
|---|---|
| 包头只有长度和短标识 | 增加协议版本、会ID、序列号、时间戳 |
| 数据体只做异 | 使用标准 TLS 通道保护 |
| 缺少消息完整 | 每条消息AEAD tag HMAC |
| 缺少防重 | 使用序列号、nonce、时间窗 |
| 命令无操作者上下文 | 每条命令绑定用户、角色、资产和会话 |
# 5. 零信任会话模。

合规远程运维的会话不应该是“连上就能操作”。推荐流程如下:
操作者使用企业身份登录,强制 MFA?
控制平台检查操作者角色、资产权限、时间窗口和工单?
管理端与被管理端建立 mTLS 会话?
被管理端显示用户可见提示,必要时要求本地用户确认?
每条命令记录操作者、资产、命令、结果、数据量和时间?
会话结束后自动关闭权限,保留结构化记录 | 控制。| 最低要求 | | --- | --- | | 登录 | MFA、设备可信检| | 授权 | 按资产、功能、时间、工单限| | 会话 | mTLS、短期会话密| | 本地提示 | 远程控制、屏幕、音频、文件操作均要可| | 操作记录 | 不允许关闭,不允许本地篡|
# 6. 命令分发前必须做权限判断
当前主控端收到完整包后,会根据窗口类型直接转给不同对话框处理。合规改造时,不能让功能窗口自己假设命令可信,权限判断必须放在统一网关层。
// 源码位置:主控\Quick\MainFrm.cpp,约1785-1865 void CMainFrame::ProcessReceiveComplete(ClientContext* pContext)
{
if (pContext->m_DeCompressionBuffer.GetBuffer(0)[0] == TOKEN_HEARTBEAT)
{
BYTE bToken[2] = { TOKEN_HEARTBEAT, 1 };
g_pSocketBase->Send(pContext, (LPBYTE)&bToken, 2);
return;
}
if (pContext->m_Dialog[0] > 0)
{
switch (pContext->m_Dialog[0])
{
case FILEMANAGER_DLG:
((CFileManagerDlg*)dlg)->OnReceiveComplete();
break;
case PROXY_DLG:
((CProxyMapDlg*)dlg)->OnReceiveComplete();
break;
case KERNEL_DLG:
((CKernelDlg*)dlg)->OnReceiveComplete();
break;
}
}
// 防御分析:这里应增加统一权限网关 // 命令进入任何功能前,都要校验用户、资产、功能和会话状态}
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
建议增加统一的命令上下文
| 字段 | 说明 |
|---|---|
operator_id | 操作者身 |
asset_id | 被管理资 |
session_id | 会话编号 |
approval_id | 工单或审批编 |
capability | 功能类别 |
risk_level | 风险等级 |
expires_at | 授权过期时间 |
更具体的实现可以先定义一个命令上下文对象。它不是安全银弹,但能强制每条命令都带上身份、资产和授权信息。
// 示例位置:合规重构后的统一命令网关
// 防御分析:CommandContext 把“谁、对谁、做什么、凭什么授权”放在同一处// 功能模块只能接收已验证的上下文,不能直接接收原始网络包struct CommandContext
{
std::string operator_id;
std::string asset_id;
std::string session_id;
std::string approval_id;
std::string capability;
std::string command_name;
uint64_t expires_at;
uint32_t risk_level;
};
bool AuthorizeCommand(const CommandContext& ctx)
{
// 防御分析:这里应连接企业身份、资产、工单和策略系统 // 返回 false 时要记录拒绝原因,不能静默丢弃 if (ctx.operator_id.empty() || ctx.asset_id.empty())
return false;
if (ctx.approval_id.empty() && ctx.risk_level >= 3)
return false;
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 7. 插件可信。

源码中插件数据可以经网络进入,被缓存到注册表,并由后续线程使用。合规远程运维工具必须把插件当成供应链资产管理,而不是当作任意二进制数据。
// 源码位置:主插件\登录模块\登录模块\LoginManager.cpp,约314-366 case COMMAND_SENDLL:
{
DllSendData* temp = new DllSendData;
ZeroMemory(temp, sizeof(DllSendData));
::memcpy(temp, lpBuffer + 1, sizeof(DllSendData));
BYTE* pPluginDllShellcodeData = new BYTE[temp->dllDataSize];
::memcpy(
pPluginDllShellcodeData,
lpBuffer + 1 + sizeof(DllSendData),
temp->dllDataSize);
int writeregeditsize = sizeof(DllSendData) + temp->dllDataSize;
char* writeregeditbuffer = new char[writeregeditsize];
::RegSetValueEx(
hKey,
DllName,
0,
REG_BINARY,
(unsigned char*)writeregeditbuffer,
writeregeditsize);
HANDLE hWorker = (HANDLE)_beginthreadex(
NULL,
0,
Loop_DllManager,
(void*)pDllData,
0,
NULL);
// 防御分析:合规设计应在缓存和执行前完成签名 // 哈希、版本、发布清单和权限范围校验}
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
插件安全设计
| 阶段 | 控制要求 |
|---|---|
| 构建 | CI 生成产物哈希SBOM |
| 签名 | 使用企业代码签名证书 |
| 发布 | 插件进入受控仓库,生成版本清单 |
| 下发 | 客户端只接受清单内插 |
| 验证 | 本地校验证书链、哈希、版本和适用平台 |
| 回滚 | 支持吊销版本和禁用插 |
插件清单建议具体到字段,不要只写“校验签名”。
// 示例位置:合规插件发布清单结// 防御分析:客户端只接受清单内插件,且哈希、签名、权限范围都要匹配// 这类结构用于防止任意二进制经网络进入插件链struct PluginManifest
{
std::string plugin_name;
std::string version;
std::string platform;
std::string sha256;
std::string signer_thumbprint;
std::string min_agent_version;
std::vector<std::string> allowed_capabilities;
bool revoked;
};
2
3
4
5
6
7
8
9
10
11
# 8. 隐私最小化设计

远程运维可能需要屏幕共享、文件传输、命令执行,但这些能力都必须遵循最小化原则:只采集完成任务所必需的数据,只在授权时间内采集,只保存必要记录。 摄像头模块的源码说明,远程命令可以改变设备和分辨率,并发送帧数据。合规产品中,这类能力必须有用户确认和明显提示。
// 源码位置:主插件\视频查看\视频查看\VideoManager.cpp,约37-63 void CVideoManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT) return;
switch (lpBuffer[0])
{
case COMMAND_WEBCAM_RESIZE:
m_SelectedDevice = *((LPDWORD)(lpBuffer + 9));
ResetScreen(*((LPDWORD)(lpBuffer + 1)), *((LPDWORD)(lpBuffer + 5)));
break;
}
// 防御分析:摄像头相关能力必须有用户授权、可见提示和关闭入口 // 企业远程运维不应默认启用摄像头或麦克风}
2
3
4
5
6
7
8
9
10
11
12
13
// 源码位置:主插件\视频查看\视频查看\VideoManager.cpp,约83-100 void CVideoManager::sendNextScreen()
{
LPVOID lpDIB = m_pCapVideo->GetDIB();
int* p_len = (int*)lpDIB;
int len = *p_len;
int nHeadLen = 1 + 1 + 4;
UINT nBufferLen = nHeadLen + len;
LPBYTE lpBuffer = new BYTE[nBufferLen];
// 防御分析:图像帧属于敏感数据 // 合规设计应限制采集范围、显示会话水印,并记录数据量和持续时间}
2
3
4
5
6
7
8
9
10
11
12
隐私控制建议
| 场景 | 合规要求 |
|---|---|
| 屏幕共享 | 用户确认、明显提示、会话水 |
| 文件传输 | 路径限制、敏感目录保护、传输记 |
| 命令执行 | 命令审批、结果记录、危险命令阻断 |
| 音频/摄像 | 默认禁用,必须本地确 |
| 剪贴 | 默认不采集,确需使用时提示用 |
# 9. 具体改造路。
把重构落地到工程任务时,可以按模块拆分,而不是笼统写“增强安全”
| 模块 | 立即删除 | 保留但重 | 验收标准 |
|---|---|---|---|
| 通信 | 异或和短标识认证 | mTLS、设备证书、会话日 | 未注册证书无法连 |
| 插件 | 任意二进制下 | 签名、哈希、版本清单 | 篡改插件无法缓存和加 |
| 文件管理 | 任意路径读写 | 路径授权、敏感目录保 | 未授权路径操作被拒绝并记 |
| 远程终端 | 隐藏命令解释 | 工单审批、命令分级、全量记 | 高危命令无审批不能执 |
| 屏幕共享 | 无提示采 | 用户确认、会话水印、停止按 | 用户可见且可终止 |
| 音视 | 默认远程采集 | 默认禁用,本地确 | 无本地确认不采集 |
| 凭据读取/注入/驱动隐藏 | 删除 | 不进入运维产 | 源码和构建产物均不存 |
# 10. 合规控制矩阵

| 控制。 | 要替代的问题 | 工程实现 |
|---|---|---|
| 身份认证 | 短标识、弱口令 | mTLS、MFA、设备证书 |
| 权限控制 | 命令直接执行 | RBAC、工单审批、时间窗 |
| 插件可信 | 二进制直接下发 | 签名、哈希、版本清单 |
| 隐私保护 | 隐蔽采集 | 可见提示、用户确认、采集最小化 |
| 操作记录 | 难以追溯 | 结构化日志、会话记录、结果回执 |
| 应急控制 | 无法快速停止 | 远程吊销、插件禁用、证书吊销 |
# 11. 企业检测点
即使完成重构,也要给安全团队留下可检测、可验证的数据
| 数据 | 作用 |
|---|---|
| 登录事件 | 谁登录了控制平台 |
| 授权事件 | 谁批准了哪个资产的哪个能 |
| 会话事件 | 会话开始、结束、来IP、设备证书 |
| 命令事件 | 命令类别、参数摘要、结果、数据量 |
| 插件事件 | 插件名、版本、哈希、签名验证结果 |
| 隐私事件 | 屏幕、文件、音频、摄像头授权状态 |
| 异常事件 | 拒绝原因、权限不足、签名失 |
# 12. 合法练习:
选取前面任意一个高危模块,说明它应该删除、保留还是改造,并写出理由?
设计一个远程文件管理功能的 RBAC 矩阵:只读、上传、下载、删除分别需要什么权限?
写一份插件发布清单字段:插件名、版本、平台、哈希、签名证书、发布时间、适用资产?
设计一个远程屏幕共享的用户提示内容:操作者、公司、工单号、开始时间、停止按钮?
写一条验证规则:未通过插件签名校验的二进制不得缓存、不得加载、不得执行。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言