C 更改密码不会保留在linux卷影文件中

C 更改密码不会保留在linux卷影文件中,c,linux,C,Linux,我使用getspnam&puttle用C编写了一段代码。 我有两个用户user1和user2,我使用user1和user2的代码按顺序更改了密码 在我更改user2的密码后,user1密码重置回最旧的密码 我应该在任何地方刷新或重置阴影文件吗 #include <errno.h> #include <crypt.h> #include <shadow.h> #include <stdio.h> #include <string.h> #

我使用getspnam&puttle用C编写了一段代码。 我有两个用户user1和user2,我使用user1和user2的代码按顺序更改了密码

在我更改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;
}
改为使用passwd命令设置密码。在写入模式下使用popen打开passwd并发送密码以修改卷影文件

#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;
}
改为使用passwd命令设置密码。在写入模式下使用popen打开passwd并发送密码以修改卷影文件

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

如果要从脚本或程序更改某个用户的密码,请特别使用实用程序/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 I/O fork、exec*等来实现最大程度的控制。例如,请参阅如何处理非特权操作,在用户首选应用程序中打开文件或URL。有了特权数据,我更加偏执。特别是,我将首先检查/、/usr/、/usr/sbin/、和/usr/sbin/chpasswd的所有权和模式,按照顺序,使用,以确保我的应用程序/实用程序没有被蒙蔽而执行假chpasswd

密码以明文形式提供给函数。PAM将按照/etc/PAM.d/chpasswd对其进行加密,并按照/etc/login.defs使用相关的系统配置

所有关于特权操作的标准安全警告都适用。您不希望将用户名和密码作为命令行参数传递,因为它们在进程列表中可见,例如,请参见ps axfu输出。环境变量同样可以访问,默认情况下会传递给所有子进程,因此它们也会被删除。通过管道或套接字对获取的描述符是安全的,除非您将描述符泄漏给子进程。在启动另一个进程时将文件流打开到子进程,通常会将父进程和第一个子进程之间的描述符泄漏到后一个子进程

您必须采取一切可能的预防措施,以阻止您的应用程序/实用程序被用于颠覆用户帐户。您要么在第一次开始考虑编写代码或脚本来管理用户信息时就学会了这样做,要么在试图利用无辜用户的邪恶行为者所利用的可怕的不安全代码堆中添加了不安全的代码,并且永远也学不会正确地这样做。不,你以后不会学的。没有人说他们以后会增加支票和安全措施,实际上是这样做的。我们人类就是不这样工作。安全性和健壮性要么从一开始就被烘焙,要么在以后像磨砂一样被随意拍打:它不会改变任何东西,即使看起来很好


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

如果您想从脚本或程序中更改某个用户的密码,请专门使用实用程序/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 I/O fork、exec*等来实现最大程度的控制。例如,请参阅如何处理非特权操作,在用户首选应用程序中打开文件或URL。有了特权数据,我更加偏执。特别是,我将首先检查/、/usr/、/usr/sbin/、和/usr/sbin/chpasswd的所有权和模式,按照顺序,使用,以确保我的应用程序/实用程序没有被蒙蔽而执行假chpasswd

密码以明文形式提供给函数。PAM将按照/etc/PAM.d/chpasswd对其进行加密,并按照/etc/login.defs使用相关的系统配置

所有关于特权操作的标准安全警告都适用。您不希望将用户名和密码作为命令行参数传递,因为它们在进程列表中可见,例如,请参见ps axfu输出。环境变量同样可以访问,默认情况下会传递给所有子进程,因此它们也会被删除。通过管道或套接字对获取的描述符是安全的,除非您将描述符泄漏给子进程。在启动另一个进程时,将文件流打开到子进程,通常会在子进程之间泄漏描述符 将父级和第一个子级转换为后一个子级

您必须采取一切可能的预防措施,以阻止您的应用程序/实用程序被用于颠覆用户帐户。您要么在第一次开始考虑编写代码或脚本来管理用户信息时就学会了这样做,要么在试图利用无辜用户的邪恶行为者所利用的可怕的不安全代码堆中添加了不安全的代码,并且永远也学不会正确地这样做。不,你以后不会学的。没有人说他们以后会增加支票和安全措施,实际上是这样做的。我们人类就是不这样工作。安全性和健壮性要么从一开始就被烘焙,要么在以后像磨砂一样被随意拍打:它不会改变任何东西,即使看起来很好


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

说putpowned函数写。。。要流式处理的卷影密码文件格式的文本行。它没有说任何关于替换现有条目的内容,只是写了一行文本。你检查过文件中有没有重复的条目吗?没有多余的条目。我第一次在shadow中创建user1条目。在我更改user2的密码后,它将恢复影子文件中的user1条目。在我再次更改user1的密码后,它将恢复user2密码。然后我猜PutPassword函数实际上只是在文件的当前位置写入。您可能需要找到要修改的用户的特定位置,并寻找该位置。请注意,当前发布的两个答案都有权限提升漏洞,允许调用方直接和完全访问根目录。编写安全代码很难——使用操作系统提供的工具。表示函数写入。。。要流式处理的卷影密码文件格式的文本行。它没有说任何关于替换现有条目的内容,只是写了一行文本。你检查过文件中有没有重复的条目吗?没有多余的条目。我第一次在shadow中创建user1条目。在我更改user2的密码后,它将恢复影子文件中的user1条目。在我再次更改user1的密码后,它将恢复user2密码。然后我猜PutPassword函数实际上只是在文件的当前位置写入。您可能需要找到要修改的用户的特定位置,并寻找该位置。请注意,当前发布的两个答案都有权限提升漏洞,允许调用方直接和完全访问根目录。编写安全代码很难-使用操作系统提供的工具。。。不会。passwd实用程序几乎肯定会使用控制终端而不是stdin从中读取密码。看见另外:getpass函数打开/dev/tty…@NominalAnimal请注意,发布的代码是不安全的。假设它是以setuid root安装的,它允许调用用户完全访问root帐户。@AndrewHenle:同意;我甚至没有阅读代码,而是将这种方法作为一个单独的答案发布了出来,我认为是适当的警告和管理。如果用户运行ln-s/bin/bash/tmp/passwd&&PATH=/tmp:$PATH/PATH/to/this setuid程序,他们将获得完整的根shell。我建议修复潜在的资源泄漏:fps。建议修复:添加fclosefps;最后一次返回前1;并返回-1;。使用popen打开passwd。。。不会。passwd实用程序几乎肯定会使用控制终端而不是stdin从中读取密码。看见另外:getpass函数打开/dev/tty…@NominalAnimal请注意,发布的代码是不安全的。假设它是以setuid root安装的,它允许调用用户完全访问root帐户。@AndrewHenle:同意;我甚至没有阅读代码,而是将这种方法作为一个单独的答案发布了出来,我认为是适当的警告和管理。如果用户运行ln-s/bin/bash/tmp/passwd&&PATH=/tmp:$PATH/PATH/to/this setuid程序,他们将获得完整的根shell。我建议修复潜在的资源泄漏:fps。建议修复:添加fclosefps;最后一次返回前1;并返回-1;。如果要从setuid根进程运行/usr/bin/chpasswd,需要将IFS=添加到环境中。@AndrewHenle:好的,但为什么?shell根本不参与解析这些行,我也不认为在内部使用IFS。shell参与了解析。Per:执行命令的环境应类似于使用fork函数在popen调用中创建子进程,子进程使用
e调用:execlshell路径,sh,-c,command,char*0;恶意用户可以设置IFS=/并按用户路径运行usr命令。它将作为root运行。看到一些Shell确实忽略了IFS。@AndrewHenle:啊,现在我明白了:这不是因为用户名/密码可能被破坏,而是因为在sh的一些实现中,二进制文件的路径可能会受到影响。dash和bash是大多数Linux系统上用于sh的两个Shell,不受影响。就我个人而言,我总是使用fork和exec*来实现这样的完全控制;我真的不相信有一个未知的二进制文件,它知道sh的哪个实现在中间。如果要从setuid根进程运行/usr/bin/chpasswd,您需要将IFS=添加到您的环境中。@AndrewHenle:好的,但为什么?shell根本不参与解析这些行,我也不认为在内部使用IFS。shell参与了解析。Per:已执行命令的环境应类似于使用fork函数在popen调用中创建子进程,子进程使用调用调用调用sh实用程序:execlshell path,sh,-c,command,char*0;恶意用户可以设置IFS=/并按用户路径运行usr命令。它将作为root运行。看到一些Shell确实忽略了IFS。@AndrewHenle:啊,现在我明白了:这不是因为用户名/密码可能被破坏,而是因为在sh的一些实现中,二进制文件的路径可能会受到影响。dash和bash是大多数Linux系统上用于sh的两个Shell,不受影响。就我个人而言,我总是使用fork和exec*来实现这样的完全控制;我真的不相信有一个未知的二进制文件,它知道sh的哪个实现在中间。