# 远程命令执行系统实战:基于TCP Socket的安全实现
远程命令执行是系统管理和运维中的常见需求,通过TCP Socket实现这一功能需要兼顾通信效率和安全性。本文将深入探讨如何构建一个安全可靠的远程命令执行系统,涵盖协议设计、安全机制和完整实现。
## 系统架构设计
系统采用客户端-服务器架构,客户端发送命令请求,服务器执行命令并返回结果。整个系统设计遵循以下原则:
1. **协议先行**:定义清晰的通信协议
2. **安全第一**:实施多层次安全防护
3. **错误处理**:完善的异常处理机制
4. **性能考虑**:支持并发连接和命令执行
## 通信协议设计
协议采用二进制格式,包含认证、命令执行和结果返回三个阶段:
```c
// protocol.h - 通信协议定义
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
// 消息类型枚举
typedef enum {
MSG_TYPE_AUTH = 0x01, // 认证消息
MSG_TYPE_CMD_REQ = 0x02, // 命令请求
MSG_TYPE_CMD_RES = 0x03, // 命令结果
MSG_TYPE_ERROR = 0x04, // 错误消息
MSG_TYPE_HEARTBEAT = 0x05 // 心跳消息
} MsgType;
// 状态码定义
typedef enum {
STATUS_OK = 0x00,
STATUS_AUTH_FAILED = 0x01,
STATUS_CMD_EXEC_FAILED = 0x02,
STATUS_CMD_NOT_ALLOWED = 0x03,
STATUS_TIMEOUT = 0x04,
STATUS_INTERNAL_ERROR = 0x05
} StatusCode;
// 协议头结构(固定8字节)
typedef struct {
uint32_t magic; // 魔数:0x52434D44 (RCMD)
uint8_t version; // 协议版本:1
uint8_t type; // 消息类型
uint16_t length; // 数据部分长度(不包括头部)
} PacketHeader;
// 认证请求结构
typedef struct {
uint8_t username_len; // 用户名长度
uint8_t token_len; // 令牌长度
// 变长数据:用户名 + 认证令牌
} AuthRequest;
// 命令请求结构
typedef struct {
uint32_t timeout; // 超时时间(秒)
uint16_t cmd_len; // 命令长度
// 变长数据:命令字符串
} CmdRequest;
// 命令响应结构
typedef struct {
uint32_t exit_code; // 命令退出码
uint32_t output_len; // 输出长度
// 变长数据:命令输出
} CmdResponse;
#endif
```
## 服务器端实现
服务器端采用多进程模型,每个客户端连接由独立子进程处理,确保隔离性:
```c
// server.c - 服务器主程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include "protocol.h"
#define PORT 8888
#define BACKLOG 5
#define MAX_CMD_LEN 1024
#define MAX_OUTPUT_LEN 4096
#define DEFAULT_TIMEOUT 30
// 全局配置
typedef struct {
char *allowed_commands[50];
int cmd_count;
char *auth_token;
int max_clients;
int default_timeout;
} ServerConfig;
// 安全命令列表
const char *DEFAULT_ALLOWED_COMMANDS[] = {
"ls", "ls -l", "ls -la",
"pwd", "whoami", "hostname",
"date", "uptime", "free -h",
"df -h", "ps aux", "netstat -tln",
"ifconfig", "ping -c 3", "traceroute",
NULL
};
// 初始化配置
void init_config(ServerConfig *config) {
config->auth_token = "secure_token_2024";
config->max_clients = 100;
config->default_timeout = DEFAULT_TIMEOUT;
config->cmd_count = 0;
// 加载允许的命令
for (int i = 0; DEFAULT_ALLOWED_COMMANDS[i] != NULL; i++) {
config->allowed_commands[config->cmd_count++] =
strdup(DEFAULT_ALLOWED_COMMANDS[i]);
}
}
// 检查命令是否允许执行
int is_command_allowed(ServerConfig *config, const char *cmd) {
// 安全检查:过滤危险字符
const char *danger_chars = "&|;><`$(){}[]\"'\\";
for (int i = 0; danger_chars[i] != '\0'; i++) {
if (strchr(cmd, danger_chars[i]) != NULL) {
return 0;
}
}
// 检查命令是否在允许列表中
for (int i = 0; i < config->cmd_count; i++) {
if (strncmp(cmd, config->allowed_commands[i],
strlen(config->allowed_commands[i])) == 0) {
// 确保命令完全匹配或以空格分隔参数
size_t len = strlen(config->allowed_commands[i]);
if (cmd[len] == '\0' || cmd[len] == ' ') {
return 1;
}
}
}
return 0;
}
// 读取完整数据包
int read_packet(int sock_fd, void *buf, size_t len, int timeout_sec) {
fd_set fds;
struct timeval tv;
size_t total_read = 0;
while (total_read < len) {
FD_ZERO(&fds);
FD_SET(sock_fd, &fds);
tv.tv_sec = timeout_sec;
tv.tv_usec = 0;
int ret = select(sock_fd + 1, &fds, NULL, NULL, &tv);
if (ret <= 0) {
return -1; // 超时或错误
}
ssize_t n = read(sock_fd, (char*)buf + total_read, len - total_read);
if (n <= 0) {
return -1; // 连接关闭或错误
}
total_read += n;
}
return 0;
}
// 处理认证请求
int handle_auth(int sock_fd, ServerConfig *config) {
PacketHeader header;
if (read_packet(sock_fd, &header, sizeof(header), 5) < 0) {
return -1;
}
// 验证协议头
header.magic = ntohl(header.magic);
header.length = ntohs(header.length);
if (header.magic != 0x52434D44 || header.type != MSG_TYPE_AUTH) {
return -1;
}
// 读取认证数据
char *auth_data = malloc(header.length + 1);
if (!auth_data) return -1;
if (read_packet(sock_fd, auth_data, header.length, 5) < 0) {
free(auth_data);
return -1;
}
auth_data[header.length] = '\0';
// 解析认证信息
AuthRequest *auth_req = (AuthRequest*)auth_data;
char *username = auth_data + sizeof(AuthRequest);
char *token = username + auth_req->username_len;
// 验证令牌
int authenticated = 0;
if (auth_req->token_len == strlen(config->auth_token) &&
memcmp(token, config->auth_token, auth_req->token_len) == 0) {
authenticated = 1;
}
// 发送认证响应
PacketHeader resp_header;
resp_header.magic = htonl(0x52434D44);
resp_header.version = 1;
resp_header.type = authenticated ? MSG_TYPE_AUTH : MSG_TYPE_ERROR;
resp_header.length = htons(sizeof(StatusCode));
write(sock_fd, &resp_header, sizeof(resp_header));
StatusCode status = authenticated ? STATUS_OK : STATUS_AUTH_FAILED;
write(sock_fd, &status, sizeof(status));
// 记录日志
time_t now = time(NULL);
printf("[%ld] 用户 %.*s 认证 %s\n",
now, auth_req->username_len, username,
authenticated ? "成功" : "失败");
free(auth_data);
return authenticated ? 0 : -1;
}
// 执行命令并捕获输出
int execute_command(const char *cmd, int timeout, char *output, int output_size) {
int stdout_pipe[2], stderr_pipe[2];
if (pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) {
return -1;
}
pid_t pid = fork();
if (pid < 0) {
close(stdout_pipe[0]); close(stdout_pipe[1]);
close(stderr_pipe[0]); close(stderr_pipe[1]);
return -1;
}
if (pid == 0) {
// 子进程:设置进程组
setpgid(0, 0);
// 重定向输出
dup2(stdout_pipe[1], STDOUT_FILENO);
dup2(stderr_pipe[1], STDERR_FILENO);
// 关闭不需要的文件描述符
close(stdout_pipe[0]); close(stdout_pipe[1]);
close(stderr_pipe[0]); close(stderr_pipe[1]);
// 执行命令
execl("/bin/bash", "bash", "-c", cmd, NULL);
exit(127); // exec失败
}
// 父进程
close(stdout_pipe[1]);
close(stderr_pipe[1]);
// 设置超时
fd_set readfds;
struct timeval tv;
int max_fd = stdout_pipe[0] > stderr_pipe[0] ?
stdout_pipe[0] : stderr_pipe[0];
FD_ZERO(&readfds);
FD_SET(stdout_pipe[0], &readfds);
FD_SET(stderr_pipe[0], &readfds);
tv.tv_sec = timeout;
tv.tv_usec = 0;
int ret = select(max_fd + 1, &readfds, NULL, NULL, &tv);
if (ret == 0) {
// 超时,终止进程组
kill(-pid, SIGKILL);
waitpid(pid, NULL, 0);
close(stdout_pipe[0]);
close(stderr_pipe[0]);
return -2;
}
// 读取输出
output[0] = '\0';
char buffer[256];
ssize_t n;
// 读取标准输出
while ((n = read(stdout_pipe[0], buffer, sizeof(buffer)-1)) > 0) {
buffer[n] = '\0';
if (strlen(output) + n < output_size - 100) {
strcat(output, buffer);
}
}
// 读取标准错误
strcat(output, "\n--- stderr ---\n");
while ((n = read(stderr_pipe[0], buffer, sizeof(buffer)-1)) > 0) {
buffer[n] = '\0';
if (strlen(output) + n < output_size - 1) {
strcat(output, buffer);
}
}
// 等待子进程结束
int status;
waitpid(pid, &status, 0);
close(stdout_pipe[0]);
close(stderr_pipe[0]);
return WEXITSTATUS(status);
}
// 处理命令请求
void handle_command(int sock_fd, ServerConfig *config) {
PacketHeader header;
CmdRequest cmd_req;
// 读取命令请求头
if (read_packet(sock_fd, &header, sizeof(header), 10) < 0) {
return;
}
header.magic = ntohl(header.magic);
header.length = ntohs(header.length);
if (header.magic != 0x52434D44 || header.type != MSG_TYPE_CMD_REQ) {
send_error(sock_fd, STATUS_INTERNAL_ERROR);
return;
}
// 读取命令请求体
if (read_packet(sock_fd, &cmd_req, sizeof(cmd_req), 10) < 0) {
send_error(sock_fd, STATUS_INTERNAL_ERROR);
return;
}
cmd_req.timeout = ntohl(cmd_req.timeout);
cmd_req.cmd_len = ntohs(cmd_req.cmd_len);
if (cmd_req.cmd_len == 0 || cmd_req.cmd_len > MAX_CMD_LEN) {
<"4f.xingjungy.cn"><"6j.xingjungy.cn"><"2z.xingjungy.cn">
send_error(sock_fd, STATUS_CMD_NOT_ALLOWED);
return;
}
// 读取命令字符串
char command[MAX_CMD_LEN + 1];
if (read_packet(sock_fd, command, cmd_req.cmd_len, 10) < 0) {
send_error(sock_fd, STATUS_INTERNAL_ERROR);
return;
}
command[cmd_req.cmd_len] = '\0';
// 验证命令
if (!is_command_allowed(config, command)) {
send_error(sock_fd, STATUS_CMD_NOT_ALLOWED);
return;
}
// 设置执行超时
int timeout = cmd_req.timeout > 0 ? cmd_req.timeout : config->default_timeout;
if (timeout > 300) timeout = 300; // 限制最大5分钟
// 执行命令
char output[MAX_OUTPUT_LEN];
int exit_code = execute_command(command, timeout, output, sizeof(output));
// 发送响应
PacketHeader resp_header;
resp_header.magic = htonl(0x52434D44);
resp_header.version = 1;
if (exit_code >= 0) {
resp_header.type = MSG_TYPE_CMD_RES;
CmdResponse cmd_res;
cmd_res.exit_code = htonl(exit_code);
cmd_res.output_len = htonl(strlen(output));
resp_header.length = htons(sizeof(cmd_res) + strlen(output));
write(sock_fd, &resp_header, sizeof(resp_header));
write(sock_fd, &cmd_res, sizeof(cmd_res));
write(sock_fd, output, strlen(output));
} else if (exit_code == -2) {
send_error(sock_fd, STATUS_TIMEOUT);
} else {
send_error(sock_fd, STATUS_CMD_EXEC_FAILED);
}
// 记录日志
time_t now = time(NULL);
printf("[%ld] 执行命令: %s (退出码: %d)\n",
now, command, exit_code);
}
// 发送错误响应
void send_error(int sock_fd, StatusCode error_code) {
PacketHeader header;
header.magic = htonl(0x52434D44);
header.version = 1;
header.type = MSG_TYPE_ERROR;
header.length = htons(sizeof(StatusCode));
write(sock_fd, &header, sizeof(header));
write(sock_fd, &error_code, sizeof(error_code));
}
// 客户端处理函数
void handle_client(int sock_fd, ServerConfig *config) {
// 设置进程标题
char proc_title[32];
snprintf(proc_title, sizeof(proc_title), "rcmd-client-%d", sock_fd);
// 认证
if (handle_auth(sock_fd, config) < 0) {
close(sock_fd);
exit(0);
}
// 命令处理循环
while (1) {
// 设置接收超时
struct timeval tv = {300, 0}; // 5分钟
setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
PacketHeader header;
ssize_t n = recv(sock_fd, &header, sizeof(header), MSG_PEEK);
if (n <= 0) {
break; // 连接关闭或超时
}
if (n == sizeof(header)) {
recv(sock_fd, &header, sizeof(header), 0);
header.magic = ntohl(header.magic);
if (header.magic == 0x52434D44) {
switch (header.type) {
case MSG_TYPE_CMD_REQ:
handle_command(sock_fd, config);
break;
case MSG_TYPE_HEARTBEAT:
// 心跳包响应
header.type = MSG_TYPE_HEARTBEAT;
header.length = 0;
send(sock_fd, &header, sizeof(header), 0);
break;
default:
send_error(sock_fd, STATUS_INTERNAL_ERROR);
break;
}
}
}
}
close(sock_fd);
exit(0);
}
int main() {
int server_fd;
struct sockaddr_in addr;
ServerConfig config;
// 初始化配置
init_config(&config);
// 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket创建失败");
return 1;
}
// 设置socket选项
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("绑定地址失败");
close(server_fd);
return 1;
}
// 监听
if (listen(server_fd, BACKLOG) < 0) {
perror("监听失败");
close(server_fd);
return 1;
}
printf("远程命令执行服务器启动,端口: %d\n", PORT);
printf("认证令牌: %s\n", config.auth_token);
printf("允许的命令数量: %d\n", config.cmd_count);
// 忽略子进程退出信号
signal(SIGCHLD, SIG_IGN);
// 主循环
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd,
(struct sockaddr*)&client_addr,
&client_len);
if (client_fd < 0) {
if (errno == EINTR) continue;
perror("接受连接失败");
continue;
}
printf("新客户端: %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 创建子进程处理客户端
pid_t pid = fork();
if (pid < 0) {
perror("创建子进程失败");
close(client_fd);
continue;
}
if (pid == 0) {
// 子进程
close(server_fd);
handle_client(client_fd, &config);
} else {
// 父进程
close(client_fd);
}
}
close(server_fd);
return 0;
}
```
## 客户端实现
客户端提供交互式界面,支持命令执行和结果查看:
```c
// client.c - 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "protocol.h"
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define AUTH_TOKEN "secure_token_2024"
// 连接服务器
int connect_to_server(const char *ip, int port) {
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("创建socket失败");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) {
perror("无效的IP地址");
close(sock_fd);
return -1;
}
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("连接服务器失败");
close(sock_fd);
return -1;
}
// 设置socket选项
struct timeval tv = {10, 0}; // 10秒超时
setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
return sock_fd;
}
// 发送认证请求
int send_auth_request(int sock_fd, const char *username) {
PacketHeader header;
header.magic = htonl(0x52434D44);
header.version = 1;
header.type = MSG_TYPE_AUTH;
// 构建认证数据
size_t username_len = strlen(username);
size_t token_len = strlen(AUTH_TOKEN);
size_t total_len = sizeof(AuthRequest) + username_len + token_len;
header.length = htons(total_len);
// 发送协议头
if (send(sock_fd, &header, sizeof(header), 0) != sizeof(header)) {
return -1;
}
// 发送认证数据
AuthRequest auth_req;
auth_req.username_len = username_len;
auth_req.token_len = token_len;
send(sock_fd, &auth_req, sizeof(auth_req), 0);
send(sock_fd, username, username_len, 0);
send(sock_fd, AUTH_TOKEN, token_len, 0);
// 接收响应
if (recv(sock_fd, &header, sizeof(header), 0) != sizeof(header)) {
return -1;
}
header.magic = ntohl(header.magic);
header.length = ntohs(header.length);
if (header.magic != 0x52434D44) {
return -1;
}
StatusCode status;
if (recv(sock_fd, &status, sizeof(status), 0) != sizeof(status)) {
return -1;
}
return (status == STATUS_OK) ? 0 : -1;
}
// 执行远程命令
int execute_remote_command(int sock_fd, const char *cmd, int timeout) {
PacketHeader header;
header.magic = htonl(0x52434D44);
header.version = 1;
header.type = MSG_TYPE_CMD_REQ;
<"1q.xingjungy.cn">
// 构建命令请求
CmdRequest cmd_req;
cmd_req.timeout = htonl(timeout);
cmd_req.cmd_len = htons(strlen(cmd));
size_t total_len = sizeof(cmd_req) + strlen(cmd);
header.length = htons(total_len);
// 发送请求
send(sock_fd, &header, sizeof(header), 0);
send(sock_fd, &cmd_req, sizeof(cmd_req), 0);
send(sock_fd, cmd, strlen(cmd), 0);
// 接收响应
if (recv(sock_fd, &header, sizeof(header), 0) != sizeof(header)) {
return -1;
}
header.magic = ntohl(header.magic);
header.length = ntohs(header.length);
if (header.magic != 0x52434D44) {
printf("协议错误\n");
return -1;
}
// 处理响应
if (header.type == MSG_TYPE_CMD_RES) {
<"5a.xingjungy.cn"><"8t.xingjungy.cn">
CmdResponse cmd_res;
if (recv(sock_fd, &cmd_res, sizeof(cmd_res), 0) != sizeof(cmd_res)) {
return -1;
}
cmd_res.exit_code = ntohl(cmd_res.exit_code);
cmd_res.output_len = ntohl(cmd_res.output_len);
// 读取命令输出
if (cmd_res.output_len > 0) {
char *output = malloc(cmd_res.output_len + 1);
if (output) {
size_t total = 0;
while (total < cmd_res.output_len) {
ssize_t n = recv(sock_fd, output + total,
cmd_res.output_len - total, 0);
if (n <= 0) break;
total += n;
}
output[cmd_res.output_len] = '\0';
printf("%s\n", output);
free(output);
}
}
printf("退出码: %d\n", cmd_res.exit_code);
return cmd_res.exit_code;
} else if (header.type == MSG_TYPE_ERROR) {
StatusCode error_code;
if (recv(sock_fd, &error_code, sizeof(error_code), 0) != sizeof(error_code)) {
return -1;
}
switch (error_code) {
case STATUS_AUTH_FAILED:
printf("错误: 认证失败\n");
break;
case STATUS_CMD_NOT_ALLOWED:
printf("错误: 命令不允许执行\n");
break;
case STATUS_TIMEOUT:
printf("错误: 命令执行超时\n");
break;
default:
printf("错误: 服务器内部错误\n");
}
return -1;
}
return -1;
}
// 主函数
int main(int argc, char *argv[]) {
printf("远程命令执行客户端\n");
printf("==================\n");
// 连接服务器
int sock_fd = connect_to_server(SERVER_IP, SERVER_PORT);
if (sock_fd < 0) {
return 1;
}
// 获取用户名
char *username = getenv("USER");
if (!username) username = "unknown";
// 认证
if (send_auth_request(sock_fd, username) < 0) {
printf("认证失败\n");
close(sock_fd);
return 1;
}
printf("认证成功!已连接到服务器\n");
printf("输入 'exit' 或 'quit' 退出\n");
printf("输入 'help' 查看可用命令\n\n");
// 主循环
while (1) {
char *input = readline("remote> ");
if (!input) break;
// 添加历史记录
if (strlen(input) > 0) {
add_history(input);
}
// 退出命令
if (strcmp(input, "exit") == 0 || strcmp(input, "quit") == 0) {
free(input);
break;
}
// 帮助命令
if (strcmp(input, "help") == 0) {
printf("可用命令示例:\n");
printf(" ls, pwd, whoami, date\n");
printf(" df -h, ps aux, free -h\n");
printf(" ping -c 3 google.com\n");
printf(" 支持大多数常见系统命令\n");
free(input);
continue;
}
// 执行远程命令
if (strlen(input) > 0) {
int result = execute_remote_command(sock_fd, input, 30);
if (result < 0 && result != -2) {
printf("命令执行失败\n");
}
}
free(input);
}
close(sock_fd);
printf("客户端退出\n");
return 0;
}
```
## 编译和运行
```bash
# 编译服务器
gcc -o rcmd_server server.c -lpthread
# 编译客户端
gcc -o rcmd_client client.c -lreadline
# 启动服务器(需要端口权限)
sudo ./rcmd_server
# 在另一个终端启动客户端
./rcmd_client
```
## 安全增强建议
1. **SSL/TLS加密**:使用OpenSSL实现传输加密
2. **审计日志**:记录所有命令执行和访问
3. **双因素认证**:增加额外的认证方式
4. **命令白名单**:严格限制可执行命令
5. **资源限制**:限制命令执行时间和资源使用
6. **IP白名单**:限制可连接的客户端IP
## 调试和故障排除
```bash
# 查看服务器日志
tail -f /var/log/syslog | grep rcmd
# 网络连接检查
netstat -tlnp | grep 8888
# 使用strace调试
strace -f -e trace=network -p $(pidof rcmd_server)
# 使用tcpdump分析流量
tcpdump -i lo port 8888 -X
```
## 总结
通过实现远程命令执行系统,我们深入掌握了TCP Socket编程的多个关键方面。从协议设计、安全机制、并发处理到错误恢复,这个项目涵盖了构建生产级网络应用所需的核心技术。
安全始终是远程执行系统的首要考虑因素。通过实施多层安全防护,包括认证、命令验证、输入过滤和资源限制,可以构建出既实用又安全的远程管理系统。
这个系统可以作为许多实际应用的基础框架,经过适当扩展和增强,可以用于系统监控、自动化运维、远程管理等场景。掌握这些核心技术,将为开发更复杂的分布式系统和服务打下坚实基础。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3106259/ ,如需转载,请注明出处,否则将追究法律责任。