我使用getspnam()和putspent()在C语言中编写了代码。 我有两个用户user1和user2,我分别使用用户1和用户2的代码更改了密码。
更改了user2的密码后,user1密码重设了最早的密码。
我应该在任何地方刷新或重置影子文件吗?
#include <errno.h>
#include <crypt.h>
#include <shadow.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void print_usage() {
printf("Usage: change_password username old_password new_password\n");
}
int main(int argc, const char *argv[]) {
if (argc < 4) {
print_usage();
return 1;
}
if (setuid(0)) {
perror("setuid");
return 1;
}
FILE* fps;
if (!(fps = fopen("/etc/shadow", "r+"))) {
perror("Error opening shadow");
return (1);
}
// Get shadow password.
struct spwd *spw = getspnam(argv[1]);
if (!spw) {
if (errno == EACCES) puts("Permission denied.");
else if (!errno) puts("No such user.");
else puts(strerror(errno));
return 1;
}
char *buffer = argv[2];
char *hashed = crypt(buffer, spw->sp_pwdp);
// printf("%s\n%s\n", spw->sp_pwdp, hashed);
if (!strcmp(spw->sp_pwdp, hashed)) {
puts("Password matched.");
} else {
puts("Password DID NOT match.");
return -1;
}
char *newpwd = crypt(argv[3], spw->sp_pwdp);
spw->sp_pwdp = newpwd;
strcpy(spw->sp_pwdp, newpwd);
putspent(spw, fps);
fclose(fps);
return 0;
}
答案 0 :(得分:3)
如果要通过脚本或程序更改某些用户的密码,请使用chpasswd
实用程序(特别是/usr/sbin/chpasswd
)。
如果我们假设您已编写了可以更新用户密码的安全特权实用程序或应用程序,则可以使用类似的
int change_password(const char *username, const char *password)
{
FILE *cmd;
int status;
if (!username || !*username)
return errno = EINVAL; /* NULL or empty username */
if (!password || !*password)
return errno = EINVAL; /* NULL or empty password */
if (strlen(username) != strcspn(username, "\t\n\r:"))
return errno = EINVAL; /* Username contains definitely invalid characters. */
if (strlen(password) != strcspn(password, "\t\n\r"))
return errno = EINVAL; /* Password contains definitely invalid characters. */
/* Ensure that whatever sh variant is used,
the path we supply will be used as-is. */
setenv("IFS", "", 1);
/* Use the default C locale, just in case. */
setenv("LANG", "C", 1);
setenv("LC_ALL", "C", 1);
errno = ENOMEM;
cmd = popen("/usr/sbin/chpasswd >/dev/null 2>/dev/null", "w");
if (!cmd)
return errno;
fprintf(cmd, "%s:%s\n", username, password);
if (fflush(cmd) || ferror(cmd)) {
const int saved_errno = errno;
pclose(cmd);
return errno;
}
status = pclose(cmd);
if (!WIFEXITED(status))
return errno = ECHILD; /* chpasswd died unexpectedly. */
if (WEXITSTATUS(status))
return errno = EACCES; /* chpasswd failed to change the password. */
/* Success. */
return 0;
}
(但这未经测试,因为我个人将使用底层的POSIX <unistd.h>
I / O(fork()
,exec*()
等)来获得最大控制。请参见例如{ {3}}介绍如何处理非特权操作,如何在用户首选的应用程序中打开文件或URL。对于特权数据,我会更加偏执。特别是,我会检查{{ 1}},/
,/usr/
和/usr/sbin/
,依次使用this example,以确保我的应用程序/实用程序不被误导执行伪造的{ {1}}。)
密码以明文形式提供给该功能。 PAM将处理加密(每个/usr/sbin/chpasswd
)和使用的相关系统配置(每个chpasswd
)。
所有有关特权操作的标准安全警告均适用。您不希望将用户名和密码作为命令行参数传递,因为它们在进程列表中可见(例如,请参见/etc/pam.d/chpasswd
输出)。环境变量的访问方式类似,默认情况下会传递给所有子进程,因此它们也不存在。通过/etc/login.defs
或ps axfu
获得的描述符是安全的,除非您将描述符泄漏给子进程。在启动另一个子进程时打开pipe()
流到子进程,通常会使父级和第一个子级之间的描述符泄漏给后一个子级。
您必须采取一切可能的预防措施,以防止您的应用程序/实用程序被用来颠覆用户帐户。您或者在刚开始考虑编写代码或脚本来管理用户信息时就学会了这样做,或者添加了试图利用无辜用户的邪恶行为者所利用的不安全代码的可怕烙印,并且永远不会学会正确地做到这一点。 (不,您稍后将不学习它。没有人说他们以后会添加检查和安全性,实际上是这样做的。我们人类只是没有那样做。从一开始就进入,或者后来像糖霜一样随意打耳光:即使看起来不错,它也不会改变。)
socketpair()
命令确保FILE
在默认的C语言环境中运行。 Andrew Henle指出,如果setenv()
环境变量设置为适当的值,则某些chpasswd
实现可能会分割命令路径,因此,以防万一,我们将其清除为空。尾随sh
会将其标准输出和标准错误重定向到IFS
(无处),以防万一可能会打印出一条包含敏感信息的错误消息。 (如果确实发生任何错误,它将以非零退出状态可靠地退出;这就是我们在上面所依赖的。)
答案 1 :(得分:1)
使用passwd
命令来设置密码。在写入模式下使用passwd
打开popen
,并发送密码以修改影子文件。
#include <errno.h>
#include <crypt.h>
#include <shadow.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void print_usage() {
}
int change_password(char *username, char *password)
{
char cmd[32];
sprintf(cmd, " passwd %s 2>/dev/null", username);
FILE *fp= popen(cmd, "w");
fprintf(fp, "%s\n", password);
fprintf(fp, "%s\n", password);
pclose(fp);
return 0;
}
int main(int argc, const char *argv[]) {
if (argc < 4) {
print_usage();
return 1;
}
if (setuid(0)) {
perror("setuid");
return 1;
}
FILE* fps;
if (!(fps = fopen("/etc/shadow", "r+"))) {
perror("Error opening shadow");
return (1);
}
// Get shadow password.
struct spwd *spw = getspnam(argv[1]);
if (!spw) {
if (errno == EACCES) puts("Permission denied.");
else if (!errno) puts("No such user.");
else puts(strerror(errno));
return 1;
}
char *buffer = argv[2];
char *hashed = crypt(buffer, spw->sp_pwdp);
// printf("%s\n%s\n", spw->sp_pwdp, hashed);
if (!strcmp(spw->sp_pwdp, hashed)) {
puts("Password matched.");
} else {
puts("Password DID NOT match.");
return -1;
}
change_password(argv[1], argv[3]);
fclose(fps);
return 0;
}