如何在无需sudo/su/root权限的情况下为用户提供systemctl的部分访问权限?
如何在无需sudo/su/root权限的情况下为用户提供systemctl的部分访问权限?
嗨,看了你的问题,我来给你梳理几个靠谱的方案,既解决权限需求,又能规避setuid程序带来的安全风险:
一、优先用sudoers配置替代setuid程序(最安全推荐)
自己写setuid程序很容易踩安全坑,而sudo的权限控制系统经过多年打磨,不仅更安全,还能实现精细的权限管控,完全能满足你的需求:
- 编辑sudoers文件:一定要用
visudo命令编辑(它会自动做语法校验,避免写错导致sudo失效),执行:visudo - 添加权限规则:根据你的需求,允许特定用户/用户组仅操作指定的3个服务。比如:
- 允许单个用户
gabriel无需输入密码,执行指定服务的start/stop/restart/status操作:gabriel ALL=(ALL) NOPASSWD: /bin/systemctl start service1.service, /bin/systemctl stop service1.service, /bin/systemctl restart service1.service, /bin/systemctl status service1.service, /bin/systemctl start service2.service, /bin/systemctl stop service2.service, /bin/systemctl restart service2.service, /bin/systemctl status service2.service, /bin/systemctl start service3.service, /bin/systemctl stop service3.service, /bin/systemctl restart service3.service, /bin/systemctl status service3.service - 如果是多个用户,可以先创建用户组(比如
service-managers),再给组授权:
注意:上面的%service-managers ALL=(ALL) NOPASSWD: /bin/systemctl * service1.service, /bin/systemctl * service2.service, /bin/systemctl * service3.service*会匹配所有systemctl子命令,如果不想允许enable/disable这类改变开机自启的操作,建议像第一个例子那样明确列出允许的子命令。
- 允许单个用户
- 验证效果:用户只需在执行命令前加
sudo即可,比如:
因为配置了sudo systemctl restart service1.serviceNOPASSWD,所以不需要输入密码。
二、如果你一定要重构原setuid程序(不推荐,但满足需求)
反编译后的代码通常可读性差,不如直接重新写一个极简的C程序,实现和原程序一样的逻辑,同时注意安全编程规范:
示例安全代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pwd.h> #include <string.h> // 允许操作的用户名列表 const char *allowed_users[] = {"gabriel", "other_user", NULL}; // 允许操作的服务列表 const char *allowed_services[] = {"service1.service", "service2.service", "service3.service", NULL}; // 检查用户是否在允许列表内 int is_allowed_user(const char *username) { for (int i = 0; allowed_users[i] != NULL; i++) { if (strcmp(username, allowed_users[i]) == 0) { return 1; } } return 0; } // 检查服务是否在允许列表内 int is_allowed_service(const char *service) { for (int i = 0; allowed_services[i] != NULL; i++) { if (strcmp(service, allowed_services[i]) == 0) { return 1; } } return 0; } int main(int argc, char *argv[]) { // 参数校验:必须传入子命令和服务名,比如 ./my-systemctl restart service1.service if (argc < 3) { fprintf(stderr, "用法: %s <command> <service>\n", argv[0]); return 1; } // 获取当前登录用户的信息 uid_t current_uid = getuid(); struct passwd *user_info = getpwuid(current_uid); if (user_info == NULL) { perror("获取用户信息失败"); return 1; } // 校验用户权限 if (!is_allowed_user(user_info->pw_name)) { fprintf(stderr, "错误:你没有权限执行此操作\n"); return 1; } // 校验服务权限 if (!is_allowed_service(argv[2])) { fprintf(stderr, "错误:该服务不允许操作\n"); return 1; } // 切换到root权限(因为程序设置了setuid位) if (seteuid(0) != 0) { perror("切换权限失败"); return 1; } // 用execve执行systemctl,避免shell注入风险(绝对不要用system()函数) char *systemctl_cmd[] = {"/bin/systemctl", argv[1], argv[2], NULL}; execve("/bin/systemctl", systemctl_cmd, NULL); // 如果execve返回,说明执行失败 perror("执行systemctl命令失败"); return 1; }
编译与权限设置
# 编译程序 gcc -o my-systemctl my-systemctl.c # 设置所有者为root chown root:root my-systemctl # 设置setuid位 chmod u+s my-systemctl
⚠️ 重要提醒:这个程序一定要保持极简,不要添加多余功能,所有输入参数都要严格校验,否则很容易被攻击者利用漏洞获取root权限。
三、关于setuid程序的安全风险
你担心的点非常正确,setuid程序确实有很高的安全隐患:
- 程序会以root权限运行,一旦存在缓冲区溢出、参数校验不严、使用不安全函数(比如
strcpy()、system())等漏洞,攻击者就能直接获取root权限 - 自己编写的代码很难覆盖所有安全场景,相比之下,sudo是经过全球安全社区长期测试和维护的,安全性高很多
- sudo还自带审计日志,方便追踪用户的操作,而自定义setuid程序需要自己实现日志功能,否则出问题很难排查
备注:内容来源于stack exchange,提问作者Gabriel Grinspan




