Linuxでパスワードを確認するには?

Linuxでパスワードを確認するには?

与えられたプレーンテキストのパスワードが/etc/shadowの暗号化されたパスワードと同じであることをLinuxのコマンドラインで確認したいと思います。

(ネットワークユーザーを認証するにはこれが必要です。組み込みLinuxを実行しています。)

/etc/shadow ファイル自体にアクセスできます。

答え1

awkを使用すると、暗号化されたパスワードを簡単に抽出できます。その後、プレフィックスを抽出する必要があります$algorithm$salt$(システムがレガシーDESを使用していないと仮定します。レガシーDESは今や無差別攻撃を可能にするため、使用されなくなりました)。

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

パスワードを確認するためのデフォルトのC関数は次のとおりです。cryptただし、これにアクセスできる標準のシェルコマンドはありません。

コマンドラインからPerlシングルライナーを使用してcryptパスワードを呼び出すことができます。

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

これは純粋なシェルツールでは実行できないため、Perlが利用可能な場合はPerlでもすべての操作を実行できます。 (またはPython、Rubyなど、この関数を呼び出すことができるすべての利用可能なツール。crypt)警告、テストされていないコード。

#!/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);
}

Perlのない組み込みシステムでは、小さな専用Cプログラムを使用します。警告、ブラウザに直接入力しましたが、コンパイルを試していませんでした。これは安定した実装ではなく、必要な手順を説明するためのものです。

/* 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);
}

もう一つのアプローチsulogin。実際に可能であれば、Webアプリケーションを通過する準備をすることをお勧めしますsu -c somecommand username。ここで難しいのは、パスワードを提供することですsu。端末をシミュレートする一般的なツールは次のとおりです。予想されるしかし、これは組み込みシステムへの大きな依存関係です。また、suBusyBoxでは、多くの目的でBusyBoxバイナリがsetuidルートである必要があるため、省略されることが多いです。しかし、そうすることができれば、セキュリティの面で最も信頼性の高い方法です。

答え2

man 5 shadowそしてを見てくださいman 3 crypt。後者から、パスワードハッシュの/etc/shadow形式は次のようになります。

 $id$salt$encrypted

id暗号化タイプが定義されている場合、追加の読み取りは次のいずれかになります。

          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)

ハッシュの種類に応じて、適切な機能/ツールを使用してパスワードを「手動で」生成して確認する必要があります。システムにプログラムがある場合は、mkpasswd次のものを使用できます。ここで提案されているように。 (あなたが持っている明確でない場合は、シャドウファイルから。 )たとえば、md5パスワードを使用する場合:

 mkpasswd -5 <the_salt> <the_password>

項目に一致する文字列を生成します/etc/shadow

答え3

一つあるStack Overflowでも同様の質問が出ました。CluelessCoder は Expect を使用してスクリプトを提供します。、これは組み込みシステムで使用可能または使用できない場合があります。

#!/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

答え4

私が返信した人のCコードにエラーがありました。常にバグがあるので、コードが機能していることを最初に確認せず、人々がここにコードを投稿する理由を決して理解しないでください。私のコードをチェックする必要がないわけではありません。 (構文)エラーなしで作成しようとした最大のファイルは1000行の長さでした。これは30年の経験の結果です。

KDE Neonでこれをテストしましたが、/etc/shadowを読むには高い権限が必要な場合、またはそれを呼び出すユーザーが "shadow"グループ(たとえば/ etc / shadowのグループ)に属している必要があるため、rootとして実行する必要があります。 )に属します。ユーザー名をパラメーターとして使用します。

コンパイル: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);
  }
}

printf関数を削除してDBG()への呼び出しを削除することはできますが、プログラムが正しく機能していることを確認するまで、どちらも便利です。私はそれらを追加し、それがどのようにそしてどこで失敗するかを確認する必要があります。各エラー終了も異なる番号でなければなりませんが、これは私の意見です。

関連情報