如何用C语言编程修改shadow文件哈希段?现有代码问题求优化
解决Linux Shadow文件哈希替换的两个核心问题
问题根源分析
- 短哈希残留问题:原代码若采用原地字节覆盖的方式修改哈希段,当新哈希长度短于原哈希时,原哈希未被覆盖的剩余字符会留在文件中,导致用户条目格式失效。
- 代码通用性不足:若代码硬编码了哈希位置、长度或用户条目格式,会无法适配不同哈希算法(如MD5/SHA-256/SHA-512)或shadow文件的字段变化。
规范通用的解决方案
正确的做法是采用**「读取-解析-修改-原子写入」**的安全流程,彻底避免原地修改的弊端,同时适配所有符合POSIX规范的shadow文件格式。
核心思路
- 以root权限读取shadow文件(仅root可访问)。
- 逐行解析用户条目,格式为:
用户名:哈希:last_change:min:max:warn:inactive:expire:reserved。 - 定位目标用户后,重新构建完整的用户条目行(替换哈希段)。
- 将所有处理后的行写入临时文件(权限与shadow保持一致)。
- 用原子操作
rename替换原shadow文件(避免中途崩溃导致文件损坏)。
完整可运行代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #define SHADOW_PATH "/etc/shadow" #define TEMP_SHADOW_PATH "/etc/shadow.tmp" #define BUFFER_SIZE 1024 // 替换指定用户的密码哈希 int update_shadow_hash(const char *username, const char *new_hash) { // 强制检查root权限 if (getuid() != 0) { fprintf(stderr, "错误:必须以root身份运行\n"); return -1; } // 打开原shadow文件 FILE *shadow_fp = fopen(SHADOW_PATH, "r"); if (!shadow_fp) { perror("打开shadow文件失败"); return -1; } // 创建临时文件,权限与shadow保持一致(0600) int temp_fd = open(TEMP_SHADOW_PATH, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (temp_fd == -1) { perror("创建临时shadow文件失败"); fclose(shadow_fp); return -1; } FILE *temp_fp = fdopen(temp_fd, "w"); if (!temp_fp) { perror("打开临时文件流失败"); close(temp_fd); unlink(TEMP_SHADOW_PATH); fclose(shadow_fp); return -1; } char buffer[BUFFER_SIZE]; int user_found = 0; // 逐行处理shadow文件 while (fgets(buffer, BUFFER_SIZE, shadow_fp) != NULL) { // 去除换行符 size_t line_len = strlen(buffer); if (line_len > 0 && buffer[line_len - 1] == '\n') { buffer[line_len - 1] = '\0'; } // 分割用户名(第一个冒号前的内容) char *current_user = strtok(buffer, ":"); if (!current_user) { // 无效行直接写入临时文件 fprintf(temp_fp, "%s\n", buffer); continue; } if (strcmp(current_user, username) == 0) { user_found = 1; // 跳过原哈希字段,获取后续所有字段 strtok(NULL, ":"); // 跳过旧哈希 char *remaining_fields = strtok(NULL, "\0"); // 从第三个字段开始的所有内容 if (remaining_fields) { fprintf(temp_fp, "%s:%s:%s\n", username, new_hash, remaining_fields); } else { // 极端情况:无后续字段 fprintf(temp_fp, "%s:%s:\n", username, new_hash); } } else { // 非目标用户,直接写入原行 fprintf(temp_fp, "%s\n", buffer); } } if (!user_found) { fprintf(stderr, "错误:未找到用户 '%s'\n", username); fclose(temp_fp); unlink(TEMP_SHADOW_PATH); fclose(shadow_fp); return -1; } // 关闭文件流 if (fclose(temp_fp) != 0 || fclose(shadow_fp) != 0) { perror("关闭文件失败"); unlink(TEMP_SHADOW_PATH); return -1; } // 原子替换原shadow文件(避免中途崩溃导致文件损坏) if (rename(TEMP_SHADOW_PATH, SHADOW_PATH) == -1) { perror("替换shadow文件失败"); unlink(TEMP_SHADOW_PATH); return -1; } printf("成功更新用户 '%s' 的哈希\n", username); return 0; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "用法:%s <用户名> <新哈希>\n", argv[0]); fprintf(stderr, "示例:%s myuser '$6$rounds=1000$abc$def...'\n", argv[0]); return EXIT_FAILURE; } return update_shadow_hash(argv[1], argv[2]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; }
代码优势说明
- 解决短哈希残留:通过重新构建完整用户条目,彻底替换原行,不会残留旧哈希的多余字符。
- 通用性强:适配所有Linux哈希算法(MD5/SHA-256/SHA-512等),兼容标准shadow文件的所有字段格式。
- 安全性高:
- 临时文件权限严格设置为
0600,避免权限泄露。 rename是原子操作,确保shadow文件始终处于有效状态。- 强制检查root权限,符合系统安全规范。
- 临时文件权限严格设置为
- 错误处理完善:所有系统调用(文件操作、权限检查等)均包含错误处理,便于排查问题。
注意事项
- 编译命令:
gcc -o shadow_update shadow_update.c - 运行方式:
sudo ./shadow_update 用户名 新哈希 - 新哈希必须符合Linux密码格式(如
$6$rounds=1000$salt$hash,6代表SHA-512算法)。 - 操作前建议备份shadow文件:
sudo cp /etc/shadow /etc/shadow.bak
内容的提问来源于stack exchange,提问作者peacism
相关产品推荐
相关产品推荐

