首页 > 应用开发 > IT综合 > TCP Socket编程实战:实现远程命令执行系统
# TCP Socket编程实战:实现远程命令执行系统
远程命令执行是网络编程中的重要应用场景,通过TCP Socket实现安全的远程命令执行系统,既能深入理解网络通信原理,又能掌握实际应用开发技能。本文将手把手实现一个完整的远程命令执行系统。
## 系统设计与安全考虑
在设计远程命令执行系统时,安全性是首要考虑因素。我们的系统设计遵循以下原则:
1. **最小权限原则**:使用非特权用户运行
2. **输入验证**:严格验证所有用户输入
3. **访问控制**:实现基于令牌的认证
4. **命令限制**:限制可执行的命令范围
5. **日志记录**:完整记录所有操作
## 通信协议设计
我们设计一个简单的二进制协议,包含认证和命令执行两个阶段:
```c
// protocol.h
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
// 协议版本
#define PROTOCOL_VERSION 1
// 消息类型
typedef enum {
MSG_AUTH_REQUEST = 1, // 认证请求
MSG_AUTH_RESPONSE = 2, // 认证响应
MSG_COMMAND_REQUEST = 3, // 命令请求
MSG_COMMAND_OUTPUT = 4, // 命令输出
MSG_COMMAND_EXIT = 5, // 命令退出状态
MSG_ERROR = 6 // 错误消息
} MessageType;
// 认证请求结构
typedef struct {
uint32_t magic; // 魔数 0x52454D4F (REMO)
uint16_t version; // 协议版本
uint16_t type; // 消息类型
uint32_t auth_token_len; // 令牌长度
uint32_t username_len; // 用户名长度
// 变长数据:令牌 + 用户名
} AuthRequest;
// 响应结构
typedef struct {
uint32_t magic; // 魔数 0x52455350 (RESP)
uint16_t version;
uint16_t type;
uint32_t status; // 状态码
uint32_t data_len; // 数据长度
// 变长数据
} ResponseHeader;
// 命令请求结构
typedef struct {
uint32_t magic;
uint16_t version;
uint16_t type;
uint32_t cmd_len; // 命令长度
uint32_t timeout; // 超时时间(秒)
// 变长数据:命令字符串
} CommandRequest;
// 状态码定义
#define STATUS_SUCCESS 0
#define STATUS_AUTH_FAILED 1
#define STATUS_INVALID_REQUEST 2
#define STATUS_COMMAND_DENIED 3
#define STATUS_TIMEOUT 4
#define STATUS_INTERNAL_ERROR 5
#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/select.h>
#include <pthread.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>
#include "protocol.h"
#define PORT 8888
#define BACKLOG 10
#define MAX_CMD_LENGTH 1024
#define MAX_OUTPUT_SIZE 4096
#define AUTH_TOKEN "secure_token_2024"
// 允许的命令列表
const char *ALLOWED_COMMANDS[] = {
"ls", "pwd", "whoami", "date", "uptime",
"df -h", "ps aux", "netstat -tln",
NULL
};
// 检查命令是否被允许
int is_command_allowed(const char *cmd) {
// 安全检查:不能包含危险字符
const char *dangerous[] = {"&", "|", ";", ">", "<", "`", "$", "(", ")", "{", "}", "[", "]", "\"", "'", NULL};
for (int i = 0; dangerous[i] != NULL; i++) {
if (strstr(cmd, dangerous[i]) != NULL) {
return 0;
}
}
// 检查是否在允许列表中
for (int i = 0; ALLOWED_COMMANDS[i] != NULL; i++) {
if (strncmp(cmd, ALLOWED_COMMANDS[i], strlen(ALLOWED_COMMANDS[i])) == 0) {
// 确保完全匹配或后面是空格/结束符
int len = strlen(ALLOWED_COMMANDS[i]);
if (cmd[len] == '\0' || cmd[len] == ' ') {
return 1;
}
}
}
return 0;
}
// 安全读取变长数据
int read_variable_data(int sock_fd, void **data, uint32_t length) {
if (length == 0) {
*data = NULL;
return 0;
}
*data = malloc(length + 1);
if (*data == NULL) {
return -1;
}
ssize_t total = 0;
while (total < length) {
ssize_t n = read(sock_fd, (char*)(*data) + total, length - total);
if (n <= 0) {
free(*data);
return -1;
}
total += n;
}
((char*)(*data))[length] = '\0';
return 0;
}
// 处理认证请求
int handle_auth(int client_fd) {
AuthRequest req;
ssize_t n = read(client_fd, &req, sizeof(AuthRequest));
if (n != sizeof(AuthRequest)) {
return -1;
}
// 验证魔数和版本
if (req.magic != htonl(0x52454D4F) || req.version != htons(PROTOCOL_VERSION)) {
return -1;
}
// 读取令牌和用户名
char *auth_token = NULL;
char *username = NULL;
if (read_variable_data(client_fd, (void**)&auth_token, ntohl(req.auth_token_len)) < 0 ||
read_variable_data(client_fd, (void**)&username, ntohl(req.username_len)) < 0) {
free(auth_token);
free(username);
return -1;
}
// 验证令牌
int authenticated = (strcmp(auth_token, AUTH_TOKEN) == 0);
// 发送响应
ResponseHeader resp;
resp.magic = htonl(0x52455350);
resp.version = htons(PROTOCOL_VERSION);
resp.type = htons(MSG_AUTH_RESPONSE);
resp.status = authenticated ? htonl(STATUS_SUCCESS) : htonl(STATUS_AUTH_FAILED);
resp.data_len = 0;
write(client_fd, &resp, sizeof(ResponseHeader));
// 记录日志
time_t now = time(NULL);
printf("[%s] %s 尝试认证,结果: %s\n",
ctime(&now), username, authenticated ? "成功" : "失败");
free(auth_token);
free(username);
return authenticated ? 0 : -1;
}
// 执行命令并获取输出
int execute_command(const char *cmd, int timeout, char *output, size_t output_size) {
int pipe_stdout[2], pipe_stderr[2];
if (pipe(pipe_stdout) < 0 || pipe(pipe_stderr) < 0) {
return -1;
}
pid_t pid = fork();
if (pid < 0) {
close(pipe_stdout[0]); close(pipe_stdout[1]);
close(pipe_stderr[0]); close(pipe_stderr[1]);
return -1;
}
if (pid == 0) { // 子进程
// 设置进程组,便于超时控制
setpgid(0, 0);
// 重定向标准输出和错误
dup2(pipe_stdout[1], STDOUT_FILENO);
dup2(pipe_stderr[1], STDERR_FILENO);
// 关闭不需要的文件描述符
close(pipe_stdout[0]); close(pipe_stdout[1]);
close(pipe_stderr[0]); close(pipe_stderr[1]);
// 使用安全的shell执行
execl("/bin/sh", "sh", "-c", cmd, NULL);
// 如果exec失败
exit(127);
}
// 父进程
close(pipe_stdout[1]);
close(pipe_stderr[1]);
// 设置超时
fd_set fds;
struct timeval tv;
int max_fd = (pipe_stdout[0] > pipe_stderr[0]) ? pipe_stdout[0] : pipe_stderr[0];
FD_ZERO(&fds);
FD_SET(pipe_stdout[0], &fds);
FD_SET(pipe_stderr[0], &fds);
tv.tv_sec = timeout;
tv.tv_usec = 0;
int ret = select(max_fd + 1, &fds, NULL, NULL, &tv);
if (ret == 0) { // 超时
kill(-pid, SIGKILL); // 杀死整个进程组
waitpid(pid, NULL, 0);
close(pipe_stdout[0]);
close(pipe_stderr[0]);
return -2; // 超时错误码
}
// 读取输出
output[0] = '\0';
char buffer[256];
ssize_t n;
while ((n = read(pipe_stdout[0], buffer, sizeof(buffer) - 1)) > 0) {
buffer[n] = '\0';
if (strlen(output) + n < output_size - 1) {
strcat(output, buffer);
}
}
// 读取错误输出
strcat(output, "\n[stderr]\n");
while ((n = read(pipe_stderr[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(pipe_stdout[0]);
close(pipe_stderr[0]);
return WEXITSTATUS(status);
}
// 处理命令请求
void handle_command_request(int client_fd) {
CommandRequest req;
ssize_t n = read(client_fd, &req, sizeof(CommandRequest));
if (n != sizeof(CommandRequest)) {
return;
}
// 验证请求
if (req.magic != htonl(0x52454D4F) || req.version != htons(PROTOCOL_VERSION)) {
send_error(client_fd, STATUS_INVALID_REQUEST);
return;
}
// 读取命令
uint32_t cmd_len = ntohl(req.cmd_len);
if (cmd_len == 0 || cmd_len > MAX_CMD_LENGTH) {
send_error(client_fd, STATUS_INVALID_REQUEST);
return;
}
char *command = malloc(cmd_len + 1);
if (command == NULL || read_variable_data(client_fd, (void**)&command, cmd_len) < 0) {
free(command);
send_error(client_fd, STATUS_INTERNAL_ERROR);
return;
}
// 命令安全检查
command[cmd_len] = '\0';
// 修剪命令两端的空白字符
char *trimmed_cmd = command;
while (isspace(*trimmed_cmd)) trimmed_cmd++;
char *end = trimmed_cmd + strlen(trimmed_cmd) - 1;
while (end > trimmed_cmd && isspace(*end)) end--;
*(end + 1) = '\0';
// 检查命令是否被允许
if (!is_command_allowed(trimmed_cmd)) {
free(command);
send_error(client_fd, STATUS_COMMAND_DENIED);
return;
}
// 记录日志
time_t now = time(NULL);
printf("[%s] 执行命令: %s\n", ctime(&now), trimmed_cmd);
// 执行命令
char output[MAX_OUTPUT_SIZE];
int timeout = ntohl(req.timeout);
if (timeout <= 0 || timeout > 60) {
timeout = 30; // 默认30秒
}
int exit_code = execute_command(trimmed_cmd, timeout, output, sizeof(output));
// 发送命令输出
ResponseHeader resp;
resp.magic = htonl(0x52455350);
resp.version = htons(PROTOCOL_VERSION);
if (exit_code >= 0) {
resp.type = htons(MSG_COMMAND_OUTPUT);
resp.status = htons(STATUS_SUCCESS);
resp.data_len = htonl(strlen(output));
write(client_fd, &resp, sizeof(ResponseHeader));
write(client_fd, output, strlen(output));
// 发送退出状态
resp.type = htons(MSG_COMMAND_EXIT);
resp.data_len = htonl(sizeof(int));
write(client_fd, &resp, sizeof(ResponseHeader));
write(client_fd, &exit_code, sizeof(int));
} else if (exit_code == -2) {
send_error(client_fd, STATUS_TIMEOUT);
} else {
send_error(client_fd, STATUS_INTERNAL_ERROR);
}
free(command);
}
// 发送错误消息
void send_error(int client_fd, uint32_t error_code) {
ResponseHeader resp;
resp.magic = htonl(0x52455350);
resp.version = htons(PROTOCOL_VERSION);
resp.type = htons(MSG_ERROR);
resp.status = htonl(error_code);
resp.data_len = 0;
write(client_fd, &resp, sizeof(ResponseHeader));
}
// 客户端处理线程
void *client_handler(void *arg) {
int client_fd = *(int*)arg;
free(arg);
// 设置连接超时
struct timeval tv;
tv.tv_sec = 30;
tv.tv_usec = 0;
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// 认证阶段
if (handle_auth(client_fd) < 0) {
close(client_fd);
return NULL;
}
// 命令执行循环
while (1) {
// 读取消息类型
uint16_t msg_type;
ssize_t n = read(client_fd, &msg_type, sizeof(msg_type));
<"1t.xingjungy.cn"><"9q.xingjungy.cn"><"5m.xingjungy.cn">
if (n <= 0) {
break; // 连接关闭或错误
}
msg_type = ntohs(msg_type);
switch (msg_type) {
case MSG_COMMAND_REQUEST:
handle_command_request(client_fd);
break;
default:
send_error(client_fd, STATUS_INVALID_REQUEST);
break;
}
}
close(client_fd);
return NULL;
}
int main() {
int 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));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
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", AUTH_TOKEN);
// 忽略SIGPIPE信号,防止写断开连接时进程退出
signal(SIGPIPE, SIG_IGN);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int *client_fd = malloc(sizeof(int));
*client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (*client_fd < 0) {
perror("接受连接失败");
free(client_fd);
continue;
}
printf("新客户端连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 创建新线程处理客户端
pthread_t thread;
if (pthread_create(&thread, NULL, client_handler, client_fd) != 0) {
perror("创建线程失败");
close(*client_fd);
free(client_fd);
continue;
}
pthread_detach(thread);
}
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_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);
inet_pton(AF_INET, ip, &addr.sin_addr);
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("连接服务器失败");
close(sock_fd);
return -1;
}
return sock_fd;
}
// 发送认证请求
int authenticate(int sock_fd, const char *username) {
AuthRequest req;
req.magic = htonl(0x52454D4F);
req.version = htons(PROTOCOL_VERSION);
req.type = htons(MSG_AUTH_REQUEST);
req.auth_token_len = htonl(strlen(AUTH_TOKEN));
req.username_len = htonl(strlen(username));
// 发送请求头
if (write(sock_fd, &req, sizeof(req)) != sizeof(req)) {
return -1;
}
// 发送令牌和用户名
write(sock_fd, AUTH_TOKEN, strlen(AUTH_TOKEN));
write(sock_fd, username, strlen(username));
// 读取响应
ResponseHeader resp;
if (read(sock_fd, &resp, sizeof(resp)) != sizeof(resp)) {
return -1;
}
if (resp.magic != htonl(0x52455350) ||
resp.status != htonl(STATUS_SUCCESS)) {
return -1;
}
return 0;
}
// 执行远程命令
int execute_remote_command(int sock_fd, const char *cmd, int timeout) {
CommandRequest req;
req.magic = htonl(0x52454D4F);
req.version = htons(PROTOCOL_VERSION);
req.type = htons(MSG_COMMAND_REQUEST);
req.cmd_len = htonl(strlen(cmd));
req.timeout = htonl(timeout);
// 发送请求
write(sock_fd, &req, sizeof(req));
write(sock_fd, cmd, strlen(cmd));
// 读取响应
char buffer[4096];
ssize_t n;
while (1) {
ResponseHeader resp;
n = read(sock_fd, &resp, sizeof(resp));
if (n != sizeof(resp)) {
printf("协议错误\n");
return -1;
}
if (resp.magic != htonl(0x52455350)) {
printf("无效响应\n");
return -1;
}
uint16_t type = ntohs(resp.type);
uint32_t status = ntohl(resp.status);
uint32_t data_len = ntohl(resp.data_len);
switch (type) {
case MSG_COMMAND_OUTPUT:
if (data_len > 0) {
if (data_len > sizeof(buffer) - 1) {
data_len = sizeof(buffer) - 1;
}
read(sock_fd, buffer, data_len);
buffer[data_len] = '\0';
printf("%s", buffer);
}
break;
case MSG_COMMAND_EXIT:
if (data_len == sizeof(int)) {
int exit_code;
read(sock_fd, &exit_code, sizeof(int));
printf("\n命令退出码: %d\n", exit_code);
return exit_code;
}
break;
case MSG_ERROR:
printf("服务器错误: ");
switch (status) {
case STATUS_AUTH_FAILED: printf("认证失败\n"); break;
case STATUS_COMMAND_DENIED: printf("命令被拒绝\n"); break;
case STATUS_TIMEOUT: printf("命令执行超时\n"); break;
default: printf("未知错误\n");
}
return -1;
default:
printf("未知响应类型\n");
return -1;
}
}
}
int main() {
printf("远程命令执行客户端\n");
printf("==================\n");
// 连接服务器
int sock_fd = connect_server(SERVER_IP, SERVER_PORT);
if (sock_fd < 0) {
return 1;
}
// 获取用户名
char *username = getenv("USER");
if (username == NULL) {
username = "unknown";
}
// 认证
if (authenticate(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 == NULL) break;
// 添加到历史记录
if (strlen(input) > 0) {
add_history(input);
}
// 检查退出命令
if (strcmp(input, "exit") == 0 || strcmp(input, "quit") == 0) {
<"2d.xingjungy.cn"><"8h.xingjungy.cn"><"0s.xingjungy.cn">
free(input);
break;
}
// 帮助命令
if (strcmp(input, "help") == 0) {
printf("可用命令:\n");
printf(" ls, pwd, whoami, date, uptime\n");
printf(" df -h, ps aux, netstat -tln\n");
printf(" 输入命令直接执行\n");
free(input);
continue;
}
// 执行远程命令(默认30秒超时)
execute_remote_command(sock_fd, input, 30);
free(input);
}
close(sock_fd);
printf("客户端退出\n");
return 0;
}
```
## 编译与运行
```bash
# 编译服务器
gcc -o rcmd_server server.c -lpthread
# 编译客户端
gcc -o rcmd_client client.c -lreadline
# 启动服务器(需要root权限或绑定端口权限)
sudo ./rcmd_server
# 在另一个终端启动客户端
./rcmd_client
```
## 安全增强措施
### 1. SSL/TLS加密通信
```c
// 使用OpenSSL加密通信
#include <openssl/ssl.h>
#include <openssl/err.h>
SSL_CTX* init_ssl_context() {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);
return ctx;
}
```
### 2. 基于时间的访问控制
```c
// 限制特定时间段访问
int is_access_allowed() {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
// 只允许工作时间访问(9:00-18:00)
if (tm->tm_hour < 9 || tm->tm_hour >= 18) {
return 0;
}
// 不允许周末访问
if (tm->tm_wday == 0 || tm->tm_wday == 6) {
return 0;
}
return 1;
}
```
### 3. 命令执行速率限制
```c
// 令牌桶算法实现速率限制
typedef struct {
int tokens;
int capacity;
time_t last_refill;
pthread_mutex_t lock;
} RateLimiter;
int consume_token(RateLimiter *limiter) {
pthread_mutex_lock(&limiter->lock);
// 补充令牌
time_t now = time(NULL);
int seconds = now - limiter->last_refill;
if (seconds > 0) {
limiter->tokens += seconds; // 每秒补充1个令牌
if (limiter->tokens > limiter->capacity) {
limiter->tokens = limiter->capacity;
}
limiter->last_refill = now;
}
// 消费令牌
if (limiter->tokens > 0) {
limiter->tokens--;
pthread_mutex_unlock(&limiter->lock);
return 1;
}
pthread_mutex_unlock(&limiter->lock);
return 0;
}
```
## 调试与监控
```bash
# 查看服务器日志
tail -f /var/log/syslog | grep rcmd_server
# 网络连接监控
sudo netstat -tlnp | grep 8888
# 进程监控
ps aux | grep rcmd_server
# 使用strace调试
sudo strace -p $(pidof rcmd_server) -f -e trace=network
# 使用tcpdump抓包分析
sudo tcpdump -i lo port 8888 -w rcmd.pcap
```
## 总结
通过实现远程命令执行系统,我们深入掌握了TCP Socket编程的核心技术。从协议设计到安全实现,从并发处理到错误恢复,这个项目涵盖了网络编程的多个关键方面。
安全性是远程命令执行系统的生命线。我们通过输入验证、命令限制、访问控制等多层防护,确保了系统的安全性。同时,合理的架构设计和错误处理机制保证了系统的稳定性和可靠性。
这个系统可以作为许多实际应用的基础,如远程管理工具、监控系统、自动化运维平台等。理解这些基础原理和技术,将为开发更复杂的分布式系统打下坚实基础。在实际部署时,还需要考虑日志审计、性能监控、故障转移等生产环境需求。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3106258/ ,如需转载,请注明出处,否则将追究法律责任。