41

I want to check, from the linux command line, if a given cleartext password is the same of a crypted password on a /etc/shadow

(I need this to authenticate web users. I'm running an embedded linux.)

I have access to the /etc/shadow file itself.

9 Answers9

31

You can easily extract the encrypted password with awk. You then need to extract the prefix $algorithm$salt$ (assuming that this system isn't using the traditional DES, which is strongly deprecated because it can be brute-forced these days).

correct=$(</etc/shadow awk -v user=bob -F : 'user == $1 {print $2}')
prefix=${correct%"${correct#\$*\$*\$}"}

For password checking, the underlying C function is crypt, but there's no standard shell command to access it.

On the command line, you can use a Perl one-liner to invoke crypt on the password.

supplied=$(echo "$password" |
           perl -e '$_ = <STDIN>; chomp; print crypt($_, $ARGV[0])' "$prefix")
if [ "$supplied" = "$correct" ]; then …

Since this can't be done in pure shell tools, if you have Perl available, you might as well do it all in Perl. (Or Python, Ruby, … whatever you have available that can call the crypt function.) Warning, untested code.

#!/usr/bin/env perl
use warnings;
use strict;
my @pwent = getpwnam($ARGV[0]);
if (!@pwent) {die "Invalid username: $ARGV[0]\n";}
my $supplied = <STDIN>;
chomp($supplied);
if (crypt($supplied, $pwent[1]) eq $pwent[1]) {
    exit(0);
} else {
    print STDERR "Invalid password for $ARGV[0]\n";
    exit(1);
}

On an embedded system without Perl, I'd use a small, dedicated C program. Warning, typed directly into the browser, I haven't even tried to compile. This is meant to illustrate the necessary steps, not as a robust implementation!

/* Usage: echo password | check_password username */
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
    char password[100];
    struct spwd shadow_entry;
    char *p, *correct, *supplied, *salt;
    if (argc < 2) return 2;
    /* Read the password from stdin */
    p = fgets(password, sizeof(password), stdin);
    if (p == NULL) return 2;
    *p = 0;
    /* Read the correct hash from the shadow entry */
    shadow_entry = getspnam(username);
    if (shadow_entry == NULL) return 1;
    correct = shadow_entry->sp_pwdp;
    /* Extract the salt. Remember to free the memory. */
    salt = strdup(correct);
    if (salt == NULL) return 2;
    p = strchr(salt + 1, '$');
    if (p == NULL) return 2;
    p = strchr(p + 1, '$');
    if (p == NULL) return 2;
    p[1] = 0;
    /*Encrypt the supplied password with the salt and compare the results*/
    supplied = crypt(password, salt);
    if (supplied == NULL) return 2;
    return !!strcmp(supplied, correct);
}

A different approach is to use an existing program such as su or login. In fact, if you can, it would be ideal to arrange for the web application to perform whatever it needs via su -c somecommand username. The difficulty here is to feed the password to su; this requires a terminal. The usual tool to emulate a terminal is expect, but it's a big dependency for an embedded system. Also, while su is in BusyBox, it's often omitted because many of its uses require the BusyBox binary to be setuid root. Still, if you can do it, this is the most robust approach from a security point of view.

14

Have a look at man 5 shadow and man 3 crypt. From the latter, you can learn that password hashes in /etc/shadow have the following form:

 $id$salt$encrypted

where id defines the type of encryption and, reading further, can be one of

          ID  | Method
          ---------------------------------------------------------
          1   | MD5
          2a  | Blowfish (not in mainline glibc; added in some
              | Linux distributions)
          5   | SHA-256 (since glibc 2.7)
          6   | SHA-512 (since glibc 2.7)

Depending on the type of hash, you need to use the appropriate function/tool for generating and verifying the password "by hand". If the system contains mkpasswd program, you can use it as suggested here. (You take the salt from the shadow file, if that wasn't obvious.) For example, with md5 passwords :

 mkpasswd -5 <the_salt> <the_password>

will generate the string that should match /etc/shadow entry.

  • 3
    On my Debian wheezy I had a completely different syntax for the command mkpasswd, which I had to install using apt-get install whois. The command line for the shadow line <user>:$6$<salt>$<pwd>: was mkpasswd -msha-512 <password> <salt> – Daniel Alder Jul 09 '14 at 09:34
2

There was a similar question asked on Stack Overflow. cluelessCoder provided a script using expect, which you may or may not have on your embedded system.

#!/bin/bash
#
# login.sh $USERNAME $PASSWORD

#this script doesn't work if it is run as root, since then we don't have to specify a pw for 'su'
if [ $(id -u) -eq 0 ]; then
        echo "This script can't be run as root." 1>&2
        exit 1
fi

if [ ! $# -eq 2 ]; then
        echo "Wrong Number of Arguments (expected 2, got $#)" 1>&2
        exit 1
fi

USERNAME=$1
PASSWORD=$2

#since we use expect inside a bash-script, we have to escape tcl-$.
expect << EOF
spawn su $USERNAME -c "exit" 
expect "Password:"
send "$PASSWORD\r"
#expect eof

set wait_result  [wait]

# check if it is an OS error or a return code from our command
#   index 2 should be -1 for OS erro, 0 for command return code
if {[lindex \$wait_result 2] == 0} {
        exit [lindex \$wait_result 3]
} 
else {
        exit 1 
}
EOF
mr.Shu
  • 121
1

Bear in mind that, assuming the system is properly configured, the program will need to be run as root.

A better solution than reading the shadow file directly and writing your own code around crypt would be to just use the pam bindings.

The squid tarball used to come with a simple CLI tool for verifying usernames/passwords using stdio - so simple to adapt to using arguments - although the version I hacked previously was hardly a pin-up poster for structured programming. A quick google and it looks like the more recent versions have been cleaned up significantly but still a few 'goto's in there.

symcbean
  • 5,540
1

The C code of the person I'm responding to has bugs. Never understand why people publish code here without checking it works first, because there's ALWAYS bugs. Not like I don't have to check my code. Largest file I've ever made without a (syntax) bug on the first try was 1000 lines, and that's in 30 years of experience.

I've tested this under KDE Neon, it needs to be run as root because you need elevated privileges to read /etc/shadow or the user calling it needs to belong to the "shadow" group (that's the group that /etc/shadow belongs to). It takes the username as an argument.

Compile with: gcc <source_file.c> -lcrypt

#define _GNU_SOURCE

#include <stdio.h> #include <stdlib.h> #include <pwd.h> #include <shadow.h> #include <sys/types.h> #include <unistd.h> #include <string.h>

#define DBG()
do {
char buf[100];
sprintf (buf, "error: %d\n", LINE);
perror (buf);
} while (0)

void chomp (char str) { while (str != '\0' && str != '\n') { str++; } str = '\0'; }

int main(int argc, char argv[]) { char password[100]; struct spwd shadow_entry; char p, correct, supplied, salt; if (argc < 2) { DBG (); return 2; } /* Read the password from stdin / p = fgets(password, sizeof(password), stdin); if (p == NULL) { DBG (); return 2; } //p = 0; - this was a pretty obvious error chomp (p); // this is what was intended above printf ("password = %s\n", p); /* Read the correct hash from the shadow entry / shadow_entry = getspnam( argv[1] ); if (shadow_entry == NULL) { DBG (); return 1; } correct = shadow_entry->sp_pwdp; / Extract the salt. Remember to free the memory. / salt = strdup(correct); if (salt == NULL) { DBG (); return 2; } p = strchr(salt + 1, '$'); if (p == NULL) { DBG (); return 2; } p = strchr(p + 1, '$'); if (p == NULL) { DBG (); return 2; } p[1] = 0; /Encrypt the supplied password with the salt and compare the results*/ supplied = crypt(password, salt); if (supplied == NULL) { DBG (); return 2; } if (strcmp(supplied, correct) == 0) { printf ("pass\n %s\n %s\n", supplied, correct); return (0); } else { printf ("fail\n %s\n %s\n", supplied, correct); return (1); } }

You can remove the printf functions and remove calls to DBG(); but both are useful until you can verify that the program is working properly. I had to add them in to see how and where it was failing. Every error exit should be a different number as well, but that's just me being anal retentive.

  • Works good. Glad you mentioned it has to run as root -- most answers omit that. Also, you may want to [edit] your answer and reword your last sentence about being "careful" -- it flagged your answer as possible spam! – Mark Stewart Mar 30 '21 at 18:22
0

I don't see a "quick and dirty" answer, so I'll post my solution:

One line

openssl recent enough

userline=$(sudo awk -v u=$user -F: 'u==$1 {print $2}' /etc/shadow); IFS='$'; a=($userline); [[ "$(printf "${pass}"|openssl passwd -"${a[1]}" -salt "${a[2]}" -stdin)" = "${userline}" ]]

with perl

user="user1";pass="eeeeee";userline="$(awk -v u="$user" -F: 'u==$1 {print $2}' /etc/shadow)"; a="$(echo "$userline"|grep -Eo '^\$.*\$.*\$')"; [[ "$(perl -e "print crypt('${pass}', '${a}')")" = "${userline}" ]]

In both case, it's a one-liner that returns 0 if the supplied password is correct.

You need three things:

  1. Set the variable "$user"
  2. Ensure that the user exists in the /etc/shadow (e.g., if ! grep -q $user /etc/shadow; then return 1; fi)
  3. Set the variable "$pass"

Some explanations

  1. First get the shadow line of the user
  2. Split it on $
  3. Use the openssl command to generate the string from the supplied password
  4. Check if the generated string matches the stored one
Boop
  • 101
  • 3
  • (1)  printf "${pass}" will blow up if $pass contains % or \.  It’s better to say printf "%s" "$pass".  (P.S.  As I just illustrated, you almost never need to use {} when referencing variables.  ${variable_name} doesn’t mean what you think it does …) (2)  echo "$userline" is even worse.  echo is probably safe to use for simple, alphanumeric constant strings, like echo 'Processing is done.';  … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 06 '22 at 21:32
  • (Cont’d) …  for anything including control characters (e.g., \n or \033) or variables (e.g., "$userline"), you should probably stick to printf.  (3) You say u=$user in your first command and u="$user" in the second, and then you regress to -q $user in the explanations.  You should always quote shell variables unless you have a good reason not to, and you’re sure you know what you’re doing. – G-Man Says 'Reinstate Monica' Aug 06 '22 at 21:32
0

I wrote a script similar to @Boops oneliner. You can find it here - feel free to use.

AdminBee
  • 22,803
Tobsec
  • 1
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center. – Community Sep 20 '21 at 16:49
-1

Use the passwd command from a terminal.

If the password entered is correct the passwd command will prompt for a new password:

Enter new UNIX password

Ctrl+D a few times to stop this, should output:

passwd: Authentication token manipulation error
passwd: password unchanged

If the password entered is incorrect the passwd command will output:

passwd: Authentication token manipulation error
passwd: password unchanged
AdminBee
  • 22,803
  • 2
    Although this might work, I'd argue this is very unwise. What is someone manages to put newline characters in the input? A failure to properly sanitize user input could lead to arbitrary passwords being set and changed. Perhaps this hack might work in a highly controlled environment, but I wouldn't risk using it anywhere else. – Matt Mar 20 '21 at 14:49
-2

In BASH you may use:

function is-password-correct(){
username=$1
password=$2
su ${username} -c 'echo' &lt;&lt;&lt; ${password} 
return $?

}

is-password-correct root passw0rd [[ $? == 0 ]] && echo "password is correct" || echo "password is NOT correct"

  • Does this work for you? I get “su: must be run from a terminal”. Please describe your operating environment. … … … … … … … … … … … … … … … … … … … … … Please do not respond in comments; [edit] your answer to make it clearer and more complete. (Or, if you can’t identify an environment where this actually works, feel free to delete it.) – G-Man Says 'Reinstate Monica' Jul 13 '22 at 11:32
  • Also, (2) It probably doesn’t *really* matter in this case (if the above function is invoked by an infrastructure that does extensive sanity checks on the input), but it’s good to get into the habit of always quoting shell variables. Using {} isn’t nearly as important. ${variable_name} doesn’t mean what you think it does … (3a) You hardly ever need to reference $? directly; just run the program / script / function and test its status, and (3b) in my opinion, && and || are overused. They are easy to misuse. … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 06 '22 at 19:19
  • (Cont’d) …  Why not just say if is-password-correct root passw0rd; then echo "password is correct"; else echo "password is NOT correct"; fi? – G-Man Says 'Reinstate Monica' Aug 06 '22 at 19:19