首页 > 解决方案 > 更改密码不会保留在 linux 影子文件中

问题描述

我使用 getspnam() 和 putspent() 用 C 语言编写了一个代码。我有两个用户 user1 和 user2,我使用我的 user1 代码更改密码,然后按顺序更改 user2。

在我更改用户 2 的密码后,用户 1 密码重置回最旧的密码。

我应该在任何地方刷新或重置影子文件吗?

#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;
}

标签: clinux

解决方案


如果您想通过脚本或程序更改某些用户的密码,请使用该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*()等)来实现最大控制。请参阅这个示例,了解我如何处理非特权操作,在用户的首选应用程序。使用特权数据,我更加偏执。特别是,我会检查, , 和首先的所有权和模式/,按顺序使用and,以确保我的应用程序/实用程序没有被欺骗执行一个假的。)/usr//usr/sbin//usr/sbin/chpasswdlstat()stat()chpasswd

密码以明文形式提供给函数。PAM 将处理加密它 (per /etc/pam.d/chpasswd),以及使用的相关系统配置 (per /etc/login.defs)。

所有关于特权操作的标准安全警告都适用。您不想将用户名和密码作为命令行参数传递,因为它们在进程列表中可见(ps axfu例如,参见输出)。环境变量同样可以访问,并且默认传递给所有子进程,所以它们也被淘汰了。通过pipe()or获得socketpair()的描述符是安全的,除非您将描述符泄漏给子进程。在启动另一个子进程时打开FILE流到子进程,通常会将父进程和第一个子进程之间的描述符泄漏给后一个子进程。

您必须采取一切可能的预防措施来阻止您的应用程序/实用程序被用于破坏用户帐户。当您第一次开始考虑编写代码或脚本来管理用户信息时,您要么学会这样做,要么添加到试图利用无辜用户的邪恶演员利用的可怕的不安全代码堆中,并且永远不会学会正确地做到这一点。(不,你以后不会学到它。没有人说他们稍后会添加检查和安全性,实际上会这样做。我们人类只是不那样工作。安全性和稳健性要么从一开始就融入其中,要么后来像结霜一样随意打了耳光:即使看起来不错,它也不会改变任何东西。)

setenv()命令确保chpasswd在默认的 C 语言环境中运行。Andrew Henle 指出,sh如果环境变量设置为合适的值,某些实现可能会拆分命令路径IFS,因此我们将其清除为空,以防万一。尾随>/dev/null 2>/dev/null将其标准输出和标准错误重定向到/dev/null(无处),以防它可能打印包含敏感信息的错误消息。(如果确实发生任何错误,它将以非零退出状态可靠地退出;这就是我们所依赖的,上面。)


推荐阅读