주어진 일반 텍스트 비밀번호가 /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);
}
또 다른 접근 방식 su
은 login
. 실제로 가능하다면 웹 애플리케이션이 를 통과하도록 준비하는 것이 더 좋습니다 su -c somecommand username
. 여기서 어려운 점은 에 비밀번호를 제공하는 것입니다 su
. 터미널을 시뮬레이션하는 일반적인 도구는 다음과 같습니다.예상되는, 그러나 이는 임베디드 시스템에 대한 큰 종속성입니다. 또한 su
BusyBox에서는 많은 용도로 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의 그룹)에 속해야 하기 때문에 루트로 실행해야 합니다. )에 속합니다. 사용자 이름을 매개변수로 사용합니다.
컴파일: 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()에 대한 호출을 제거할 수 있지만 프로그램이 올바르게 작동하는지 확인할 때까지는 둘 다 유용합니다. 나는 그것들을 추가하고 그것이 어떻게 그리고 어디서 실패하는지 확인해야 할 것입니다. 각 오류 종료도 다른 번호여야 하지만 이는 단지 내 의견일 뿐입니다.