Làm thế nào để kiểm tra mật khẩu đã nhập là mật khẩu hợp lệ cho người dùng này?


14

Kịch bản:

Trong tập lệnh bash, tôi phải kiểm tra xem mật khẩu do người dùng cung cấp có phải là mật khẩu người dùng hợp lệ hay không.

Tức là tôi có một người dùng A có mật khẩu PA .. Trong đoạn script tôi đã yêu cầu người dùng A nhập mật khẩu của mình, vậy làm thế nào để kiểm tra xem chuỗi đã nhập có thực sự là mật khẩu của anh ta không? ...


Bạn có ý nghĩa gì với một mật khẩu hợp lệ? Bạn muốn kiểm tra nếu nó thực sự là mật khẩu của người dùng?
AB

@AB mật khẩu hợp lệ = mật khẩu đăng nhập bất cứ điều gì bạn muốn gọi ... Ý tôi là mật khẩu của người dùng này
Maythux

@AB Tôi biết đó sẽ là một lỗ hổng bảo mật nhưng ở đây bạn cũng đã biết tên người dùng ... Nói cách khác, đây chỉ là một thử nghiệm nếu mật khẩu dành cho người dùng này ..
Maythux

3
Đây là câu trả lời của bạn: unix.stackexchange.com/a/21728/107084
AB

Một giải pháp dựa trên expecttại stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Câu trả lời:


14

Vì bạn muốn làm điều này trong một tập lệnh shell, một vài đóng góp trong Cách kiểm tra mật khẩu với Linux? (trên Unix.SE , được đề xuất bởi AB ) đặc biệt có liên quan:

Để kiểm tra thủ công xem một chuỗi có thực sự là mật khẩu của người dùng hay không, bạn phải băm nó bằng thuật toán băm giống như trong mục nhập bóng của người dùng, với cùng một muối như trong mục nhập bóng của người dùng. Sau đó, nó có thể được so sánh với mật khẩu băm được lưu trữ ở đó.

Tôi đã viết một kịch bản hoàn chỉnh, làm việc chứng minh làm thế nào để làm điều này.

  • Nếu bạn đặt tên cho nó chkpass, bạn có thể chạy và nó sẽ đọc một dòng từ đầu vào tiêu chuẩn và kiểm tra xem đó có phải là mật khẩu của bạn không.chkpass useruser
  • Cài đặt gói whois Cài đặt whois để có được mkpasswdtiện ích mà tập lệnh này phụ thuộc vào.
  • Kịch bản này phải được chạy như root để thành công.
  • Trước khi sử dụng tập lệnh này hoặc bất kỳ phần nào của tập lệnh để thực hiện công việc thực tế, vui lòng xem Ghi chú bảo mật bên dưới.
#!/usr/bin/env bash

xcorrect=0 xwrong=1 enouser=2 enodata=3 esyntax=4 ehash=5  IFS=$
die() {
    printf '%s: %s\n' "$0" "$2" >&2
    exit $1
}
report() {
    if (($1 == xcorrect))
        then echo 'Correct password.'
        else echo 'Wrong password.'
    fi
    exit $1
}

(($# == 1)) || die $esyntax "Usage: $(basename "$0") <username>"
case "$(getent passwd "$1" | awk -F: '{print $2}')" in
    x)  ;;
    '') die $enouser "error: user '$1' not found";;
    *)  die $enodata "error: $1's password appears unshadowed!";;
esac

if [ -t 0 ]; then
    IFS= read -rsp "[$(basename "$0")] password for $1: " pass
    printf '\n'
else
    IFS= read -r pass
fi

set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
case "${ent[1]}" in
    1) hashtype=md5;;   5) hashtype=sha-256;;   6) hashtype=sha-512;;
    '') case "${ent[0]}" in
            \*|!)   report $xwrong;;
            '')     die $enodata "error: no shadow entry (are you root?)";;
            *)      die $enodata 'error: failure parsing shadow entry';;
        esac;;
    *)  die $ehash "error: password hash type is unsupported";;
esac

if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]
    then report $xcorrect
    else report $xwrong
fi

Ghi chú bảo mật

Nó có thể không phải là phương pháp đúng đắn.

Việc tiếp cận như thế này có nên được coi là an toàn hay không và phù hợp tùy thuộc vào chi tiết về trường hợp sử dụng của bạn mà bạn chưa cung cấp (kể từ khi viết bài này).

Nó đã không được kiểm toán.

Mặc dù tôi đã cố gắng thực hiện việc chăm sóc trong khi viết kịch bản này, nhưng nó đã không được kiểm tra chính xác về các lỗ hổng bảo mật . Nó được dùng như một bản trình diễn và sẽ là phần mềm "alpha" nếu được phát hành như một phần của dự án. Hơn nữa ...

Một người dùng khác đang "xem" có thể phát hiện ra muối của người dùng .

Do những hạn chế trong cách mkpasswdchấp nhận dữ liệu muối, tập lệnh này chứa một lỗ hổng bảo mật đã biết , mà bạn có thể hoặc không thể xem là chấp nhận được tùy theo trường hợp sử dụng. Theo mặc định, người dùng trên Ubuntu và hầu hết các hệ thống GNU / Linux khác có thể xem thông tin về các quy trình được chạy bởi những người dùng khác (bao gồm cả root), bao gồm cả các đối số dòng lệnh của họ. Cả đầu vào và mật khẩu băm của người dùng đều không được truyền dưới dạng đối số dòng lệnh cho bất kỳ tiện ích bên ngoài nào. Nhưng muối , được trích xuất từ shadowcơ sở dữ liệu, được đưa ra dưới dạng đối số dòng lệnh mkpasswd, vì đây là cách duy nhất mà tiện ích chấp nhận muối làm đầu vào.

Nếu

  • người dùng khác trên hệ thống, hoặc
  • bất cứ ai có khả năng tạo bất kỳ tài khoản người dùng nào (ví dụ www-data:) chạy mã của họ, hoặc
  • bất cứ ai khác có thể xem thông tin về các quy trình đang chạy (bao gồm bằng cách kiểm tra thủ công các mục trong /proc)

có thể kiểm tra các đối số dòng lệnh để mkpasswdnó được chạy bởi tập lệnh này, sau đó chúng có thể lấy một bản sao muối của người dùng từ shadowcơ sở dữ liệu. Họ thể phải đoán khi nào lệnh đó được chạy, nhưng điều đó đôi khi có thể đạt được.

Kẻ tấn công bằng muối của bạn không tệ như kẻ tấn công bằng muối và băm của bạn , nhưng nó không lý tưởng. Muối không cung cấp đủ thông tin để ai đó khám phá mật khẩu của bạn. Nhưng nó cho phép ai đó tạo các bảng cầu vồng hoặc băm từ điển được tính toán trước cụ thể cho người dùng đó trên hệ thống đó. Điều này ban đầu là vô giá trị, nhưng nếu bảo mật của bạn bị xâm phạm vào một ngày sau đó và lấy được hàm băm đầy đủ, thì nó có thể bị bẻ khóa nhanh hơn để lấy mật khẩu của người dùng trước khi họ có cơ hội thay đổi nó.

Do đó, lỗ hổng bảo mật này là một yếu tố làm trầm trọng thêm trong một kịch bản tấn công phức tạp hơn là một lỗ hổng có thể khai thác hoàn toàn. Và bạn có thể xem xét tình huống trên rất xa. Nhưng tôi miễn cưỡng đề xuất bất kỳ phương pháp nào cho việc sử dụng chung, trong thế giới thực, rò rỉ bất kỳ dữ liệu không công khai nào từ /etc/shadowngười dùng không root.

Bạn có thể tránh vấn đề này hoàn toàn bằng cách:

  • viết một phần tập lệnh của bạn bằng Perl hoặc một số ngôn ngữ khác cho phép bạn gọi các hàm C, như thể hiện trong câu trả lời của Gilles cho câu hỏi Unix.SE liên quan , hoặc
  • viết toàn bộ kịch bản / chương trình của bạn bằng ngôn ngữ như vậy, thay vì sử dụng bash. (Dựa trên cách bạn đã gắn thẻ câu hỏi, có vẻ như bạn thích sử dụng bash hơn.)

Hãy cẩn thận làm thế nào bạn gọi kịch bản này.

Nếu bạn cho phép người dùng không tin cậy chạy tập lệnh này với quyền root hoặc chạy bất kỳ quy trình nào dưới dạng root gọi tập lệnh này, hãy cẩn thận . Bằng cách thay đổi môi trường, họ có thể tạo tập lệnh này - hoặc bất kỳ tập lệnh nào chạy bằng root - làm bất cứ điều gì . Trừ khi bạn có thể ngăn điều này xảy ra, bạn không được phép người dùng nâng cao đặc quyền để chạy các kịch bản shell.

Xem 10,4. Shell Scripting Languages ​​(sh và csh phái sinh) trong David A. Wheeler Lập trình bảo mật cho Linux và Unix HOWTO để biết thêm thông tin về điều này. Mặc dù bài thuyết trình của ông tập trung vào các tập lệnh setuid, các cơ chế khác có thể trở thành con mồi của một số vấn đề tương tự nếu chúng không vệ sinh môi trường một cách chính xác.

Ghi chú khác

Nó chỉ hỗ trợ đọc băm từ shadowcơ sở dữ liệu.

Mật khẩu phải được làm mờ để tập lệnh này hoạt động (nghĩa là băm của chúng phải ở trong một /etc/shadowtệp riêng biệt mà chỉ root mới có thể đọc, không phải trong /etc/passwd).

Điều này luôn luôn là trường hợp trong Ubuntu. Trong mọi trường hợp, nếu cần kịch bản có thể được trivially mở rộng để đọc băm mật khẩu từ passwdcũng như shadow.

Hãy IFSghi nhớ khi sửa đổi tập lệnh này.

Tôi đặt IFS=$ở đầu, vì ba dữ liệu trong trường băm của mục nhập bóng được phân tách bằng $.

  • Họ cũng có một hàng đầu $, đó là lý do tại sao các loại băm và muối là "${ent[1]}""${ent[2]}"hơn "${ent[0]}""${ent[1]}"tương ứng.

Các vị trí duy nhất trong tập lệnh này $IFSxác định cách shell phân tách hoặc kết hợp các từ

  • khi các dữ liệu này được chia thành một mảng, bằng cách khởi tạo nó từ sự $( )thay thế lệnh không được trích dẫn trong:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • khi mảng được hoàn nguyên thành một chuỗi để so sánh với trường đầy đủ từ đó shadow, "${ent[*]}"biểu thức trong:

    if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]

Nếu bạn sửa đổi tập lệnh và để nó thực hiện phân tách từ (hoặc nối từ) trong các tình huống khác, bạn sẽ cần đặt IFSthành các giá trị khác nhau cho các lệnh khác nhau hoặc các phần khác nhau của tập lệnh.

Nếu bạn không ghi nhớ điều này và giả sử $IFSđược đặt thành khoảng trắng thông thường ( $' \t\n'), cuối cùng bạn có thể khiến tập lệnh của mình hoạt động theo một số cách có vẻ khá kỳ lạ.


8

Bạn có thể sử dụng sai sudocho việc này. sudo-ltùy chọn, để kiểm tra các đặc quyền sudo mà người dùng có và -Sđể đọc mật khẩu từ stdin. Tuy nhiên, cho dù người dùng có cấp đặc quyền nào, nếu được xác thực thành công, sudosẽ trả về trạng thái thoát 0. Vì vậy, bạn có thể lấy bất kỳ trạng thái thoát nào khác làm dấu hiệu cho thấy xác thực không hoạt động (giả sử sudobản thân không có vấn đề gì, như lỗi cấp phép hoặc sudoerscấu hình không hợp lệ ).

Cái gì đó như:

#! /bin/bash
IFS= read -rs PASSWD
sudo -k
if sudo -lS &> /dev/null << EOF
$PASSWD
EOF
then
    echo 'Correct password.'
else 
    echo 'Wrong password.'
fi

Kịch bản này phụ thuộc khá nhiều vào sudoerscấu hình. Tôi đã giả định thiết lập mặc định. Những thứ có thể khiến nó thất bại:

  • targetpwhoặc runaspwđược đặt
  • listpwnever
  • Vân vân.

Các vấn đề khác bao gồm (cảm ơn Eliah):

  • Những nỗ lực không chính xác sẽ được đăng nhập /var/log/auth.log
  • sudophải được chạy khi người dùng bạn xác thực. Trừ khi bạn có sudođặc quyền để bạn có thể chạy sudo -u foo sudo -lS, điều đó có nghĩa là bạn sẽ phải chạy tập lệnh với tư cách là người dùng mục tiêu.

Bây giờ, lý do tôi sử dụng ở đây-docs là để cản trở việc nghe lén. Một biến được sử dụng như một phần của dòng lệnh dễ dàng được tiết lộ hơn bằng cách sử dụng tophoặc pshoặc các công cụ khác để kiểm tra các quy trình.


2
Cách này có vẻ xuất sắc. Mặc dù nó sẽ không hoạt động trên các hệ thống có cấu hình không phổ biến sudo(như bạn nói) và phân tích /var/log/auth.logcác bản ghi của từng lần thử, nhưng điều này nhanh chóng, đơn giản và sử dụng một tiện ích hiện có, thay vì phát minh lại bánh xe (có thể nói là như vậy). Tôi đề nghị thiết lập IFS=cho read, để ngăn chặn thành công sai cho đầu vào sử dụng khoảng trắng độn và mật khẩu phi độn, cũng như thất bại sai cho đầu vào sử dụng khoảng trắng độn và mật khẩu đệm. (Giải pháp của tôi có một lỗi tương tự.) Bạn cũng có thể muốn giải thích cách nó phải được chạy khi người dùng có mật khẩu đang được kiểm tra.
Eliah Kagan

@EliahKagan mỗi lần thử thất bại được ghi lại, nhưng vâng, bạn đúng về mọi thứ khác.
muru

Các xác thực thành công cũng được ghi lại. sudo -llàm cho một mục được viết bằng " COMMAND=list". Btw (không liên quan), trong khi nó không khách quan hơn để làm như vậy, sử dụng chuỗi ở đây thay cho tài liệu ở đây đạt được cùng một mục tiêu bảo mật và gọn hơn. Vì tập lệnh đã sử dụng bashism read -s&>, sử dụng if sudo -lS &>/dev/null <<<"$PASSWD"; thenkhông kém phần di động. Tôi không khuyên bạn nên thay đổi những gì bạn có (nó vẫn ổn). Thay vào đó, tôi đề cập đến nó để độc giả biết họ cũng có tùy chọn này.
Eliah Kagan

@EliahKagan ah. Tôi nghĩ rằng đã sudo -lvô hiệu hóa một cái gì đó - có lẽ nó đã báo cáo khi người dùng cố gắng chạy một lệnh mà họ không được phép.
muru

@EliahKagan Thật vậy. Tôi đã sử dụng herestrings theo cách đó trước đây . Tôi đã lao động theo một quan niệm sai lầm ở đây, dẫn đến heredocs, nhưng sau đó tôi quyết định giữ chúng bằng mọi giá.
muru

4

Một phương pháp khác (có lẽ thú vị hơn cho nội dung lý thuyết của nó hơn là cho các ứng dụng thực tế của nó).

Mật khẩu của người dùng được lưu trữ trong /etc/shadow.

Mật khẩu được lưu trữ ở đây được mã hóa, trong các bản phát hành Ubuntu mới nhất sử dụng SHA-512.

Cụ thể, khi tạo mật khẩu, mật khẩu trong văn bản rõ ràng được muối và mã hóa thông qua SHA-512.

Sau đó, một giải pháp sẽ là muối / mã hóa mật khẩu đã cho và khớp với mật khẩu người dùng được mã hóa được lưu trữ trong /etc/shadowmục nhập của người dùng cụ thể .

Để phân tích nhanh về cách lưu trữ mật khẩu trong mỗi /etc/shadowmục nhập của người dùng , đây là /etc/shadowmục nhập mẫu cho người dùng foocó mật khẩu bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: tên tài khoản
  • 6: loại mã hóa mật khẩu
  • lWS1oJnmDlaXrx1F: muối mật khẩu
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512mật khẩu muối / mã hóa

Để khớp mật khẩu đã barcho cho người dùng đã cho foo, điều đầu tiên cần làm là lấy muối:

$ sudo getent shadow foo | cut -d$ -f3
lWS1oJnmDlaXrx1F

Sau đó, người ta sẽ nhận được mật khẩu muối / mã hóa đầy đủ:

$ sudo getent shadow foo | cut -d: -f2
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/

Sau đó, mật khẩu đã cho có thể được nhập / mã hóa và khớp với mật khẩu người dùng đã được mã hóa / được lưu trữ trong /etc/shadow:

$ python -c 'import crypt; print crypt.crypt("bar", "$6$lWS1oJnmDlaXrx1F")'
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/

Họ hợp nhau! Mọi thứ được đưa vào một bashkịch bản:

#!/bin/bash

read -p "Username >" username
IFS= read -p "Password >" password
salt=$(sudo getent shadow $username | cut -d$ -f3)
epassword=$(sudo getent shadow $username | cut -d: -f2)
match=$(python -c 'import crypt; print crypt.crypt("'"${password}"'", "$6$'${salt}'")')
[ ${match} == ${epassword} ] && echo "Password matches" || echo "Password doesn't match"

Đầu ra:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match

1
sudo getent shadow>> sudo grep .... Nếu không, tốt đẹp. Đây là những gì tôi nghĩ ban đầu, sau đó trở nên quá lười biếng.
muru

@muru Cảm ơn. Chắc chắn cảm thấy đẹp hơn, vì vậy được cập nhật, nhưng tôi vẫn không chắc chắn trong trường hợp này getent+ grep+ cut> grep+cut
kos

1
Eek. getentcó thể làm việc tìm kiếm cho bạn. Tôi đã tự do chỉnh sửa.
muru

@muru Yea bạn nên có trong thực tế, bây giờ tôi thấy điểm!
kos

2
Tôi nghĩ rằng điều này thực sự khá thực tế, mặc dù tôi có thể bị thiên vị: đó là cùng một phương pháp tôi đã sử dụng. Việc thực hiện và trình bày của bạn khá khác nhau mặc dù - đây chắc chắn là một câu trả lời có giá trị. Trong khi tôi đã sử dụng mkpasswdđể tạo ra một hàm băm từ đầu vào của người dùng và muối hiện có (và bao gồm các tính năng và chẩn đoán bổ sung), thay vào đó bạn đã mã hóa một chương trình Python đơn giản thực hiện mkpasswdchức năng quan trọng nhất và sử dụng chức năng đó. Tôi khuyên bạn nên thiết IFS=khi đọc nhập mật khẩu, và trích dẫn ${password}với ", vì vậy những nỗ lực mật khẩu với khoảng trắng không phá vỡ kịch bản.
Eliah Kagan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.