Bagaimana cara memeriksa kata sandi yang dimasukkan adalah kata sandi yang valid untuk pengguna ini?

14

Skenario:

Dalam skrip bash, saya harus memeriksa apakah kata sandi yang diberikan oleh pengguna adalah kata sandi pengguna yang valid.

Yaitu saya kira saya memiliki pengguna A dengan kata sandi PA .. Dalam skrip saya meminta pengguna A untuk memasukkan kata sandinya, Jadi bagaimana cara memeriksa apakah string yang dimasukkan benar-benar kata sandinya? ...

Maythux
sumber
Apa maksud Anda dengan kata sandi yang valid? Ingin menguji apakah itu benar-benar kata sandi pengguna?
AB
@AB kata sandi yang valid = kata sandi masuk apa pun yang ingin Anda panggil ... Maksud saya kata sandi untuk pengguna ini
Maythux
@ AB Saya tahu ini akan menjadi lubang keamanan tetapi di sini Anda sudah tahu nama pengguna juga ... Dengan kata lain itu hanya tes jika kata sandi untuk pengguna ini ..
Maythux
3
Inilah jawaban Anda: unix.stackexchange.com/a/21728/107084
AB
Solusi berbasis di expectdi stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Jawaban:

14

Karena Anda ingin melakukan ini dalam skrip shell, beberapa kontribusi dalam Cara memeriksa kata sandi dengan Linux? (pada Unix.SE , disarankan oleh AB ) sangat relevan:

Untuk secara manual memeriksa apakah suatu string benar-benar beberapa kata sandi pengguna, Anda harus mengirisnya dengan algoritma hash yang sama seperti pada entri bayangan pengguna, dengan garam yang sama seperti pada entri bayangan pengguna. Kemudian bisa dibandingkan dengan kata sandi hash yang tersimpan di sana.

Saya telah menulis naskah yang lengkap dan berfungsi yang menunjukkan bagaimana melakukan ini.

  • Jika Anda menamainya chkpass, Anda dapat menjalankan dan itu akan membaca baris dari input standar dan memeriksa apakah itu kata sandi.chkpass useruser
  • Pasang whois Instal whois paket untuk mendapatkan mkpasswdutilitas yang menjadi dasar skrip ini.
  • Script ini harus dijalankan sebagai root untuk berhasil.
  • Sebelum menggunakan skrip ini atau bagian mana pun untuk melakukan pekerjaan nyata, silakan lihat Catatan Keamanan di bawah ini.
#!/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

Catatan Keamanan

Mungkin itu bukan pendekatan yang tepat.

Apakah pendekatan seperti ini harus dianggap aman atau tidak, tergantung pada detail tentang kasus penggunaan yang belum Anda berikan (pada tulisan ini).

Belum diaudit.

Meskipun saya sudah berusaha berhati-hati saat menulis skrip ini, itu belum diaudit dengan baik untuk kerentanan keamanan . Ini dimaksudkan sebagai demonstrasi, dan akan menjadi perangkat lunak "alpha" jika dirilis sebagai bagian dari proyek. Selanjutnya...

Pengguna lain yang "menonton" mungkin dapat menemukan pengguna tersebut garam .

Karena keterbatasan dalam cara mkpasswdmenerima data garam, skrip ini berisi kelemahan keamanan yang diketahui , yang Anda mungkin atau mungkin tidak anggap dapat diterima tergantung pada kasus penggunaan. Secara default, pengguna di Ubuntu dan sebagian besar sistem GNU / Linux lainnya dapat melihat informasi tentang proses yang dijalankan oleh pengguna lain (termasuk root), termasuk argumen baris perintah mereka. Baik input pengguna maupun hash kata sandi yang disimpan dilewatkan sebagai argumen baris perintah ke utilitas eksternal apa pun. Tapi garam , diambil dari shadowbasis data, yang diberikan sebagai argumen baris perintah untuk mkpasswd, karena ini adalah satu-satunya cara yang utilitas menerima garam sebagai masukan.

Jika

  • pengguna lain pada sistem, atau
  • siapa pun yang memiliki kemampuan untuk membuat akun pengguna apa pun (misalnya, www-data) menjalankan kode mereka, atau
  • siapa pun yang dapat melihat informasi tentang proses yang sedang berjalan (termasuk dengan memeriksa entri secara manual /proc)

dapat memeriksa argumen baris perintah mkpasswdsaat dijalankan oleh skrip ini, kemudian mereka dapat memperoleh salinan garam pengguna dari shadowdatabase. Mereka mungkin harus bisa menebak kapan perintah itu dijalankan, tetapi itu kadang-kadang dapat dicapai.

Seorang penyerang dengan garam Anda tidak seburuk penyerang dengan garam dan hash Anda , tetapi itu tidak ideal. Garam tidak memberikan informasi yang cukup bagi seseorang untuk menemukan kata sandi Anda. Tapi itu memungkinkan seseorang untuk menghasilkan tabel pelangi atau hash kamus pra-dihitung khusus untuk pengguna pada sistem itu. Ini pada awalnya tidak berharga, tetapi jika keamanan Anda dikompromikan di kemudian hari dan hash lengkap diperoleh, maka dapat dipecah lebih cepat untuk mendapatkan kata sandi pengguna sebelum mereka mendapatkan kesempatan untuk mengubahnya.

Dengan demikian kelemahan keamanan ini merupakan faktor yang memperburuk skenario serangan yang lebih kompleks daripada kerentanan sepenuhnya dapat dieksploitasi. Dan Anda mungkin menganggap situasi di atas tidak masuk akal. Tapi saya enggan untuk merekomendasikan metode apapun untuk umum, penggunaan dunia nyata bahwa kebocoran setiap data non-publik dari /etc/shadowke pengguna non-root.

Anda dapat menghindari masalah ini sepenuhnya dengan:

  • menulis bagian dari naskah Anda di Perl atau bahasa lain yang memungkinkan Anda menelepon fungsi C, seperti yang ditunjukkan dalam jawaban Gilles untuk pertanyaan Unix.SE terkait , atau
  • menulis seluruh skrip / program Anda dalam bahasa seperti itu, daripada menggunakan bash. (Berdasarkan cara Anda menandai pertanyaan, tampaknya Anda lebih suka menggunakan bash.)

Berhati-hatilah dengan panggilan skrip ini.

Jika Anda mengizinkan pengguna yang tidak dipercaya untuk menjalankan skrip ini sebagai root atau menjalankan proses apa pun sebagai root yang memanggil skrip ini, berhati-hatilah . Dengan mengubah lingkungan, mereka dapat membuat skrip ini - atau skrip apa pun yang berjalan sebagai root - melakukan apa saja . Kecuali Anda dapat mencegah hal ini terjadi, Anda tidak boleh mengizinkan pengguna hak istimewa tinggi untuk menjalankan skrip shell.

Lihat 10.4. Bahasa Shell Scripting (turunan sh dan csh) di Pemrograman Aman David A. Wheeler untuk Linux dan Unix HOWTO untuk informasi lebih lanjut tentang ini. Sementara presentasinya berfokus pada skrip setuid, mekanisme lain dapat menjadi mangsa beberapa masalah yang sama jika mereka tidak membersihkan lingkungan dengan benar.

Catatan lain

Ini mendukung bacaan hash dari shadow database saja.

Kata sandi harus dibayangi agar skrip ini berfungsi (yaitu, hash mereka harus dalam /etc/shadowfile terpisah yang hanya dapat dibaca oleh root, bukan di /etc/passwd).

Ini harus selalu menjadi kasus di Ubuntu. Dalam kasus apa pun, jika diperlukan, skrip dapat dengan mudah diperluas untuk membaca hash kata sandi passwdjuga shadow.

Perlu IFSdiingat saat memodifikasi skrip ini.

Saya atur IFS=$di awal, karena tiga data dalam bidang hash dari entri bayangan dipisahkan oleh $.

  • Mereka juga memiliki terkemuka $, itulah sebabnya jenis dan garam hash "${ent[1]}"dan "${ent[2]}"bukannya "${ent[0]}"dan "${ent[1]}", masing-masing.

Satu-satunya tempat di skrip ini yang $IFSmenentukan bagaimana shell membagi atau menggabungkan kata-kata

  • ketika data ini dipecah menjadi sebuah array, dengan menginisialisasi dari $( )substitusi perintah yang tidak dikutip dalam:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • ketika array disusun kembali menjadi string untuk dibandingkan dengan bidang penuh dari shadow, "${ent[*]}"ekspresi di:

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

Jika Anda memodifikasi skrip dan membuatnya melakukan pemisahan kata (atau penggabungan kata) dalam situasi lain, Anda harus mengatur IFSnilai yang berbeda untuk perintah yang berbeda atau bagian skrip yang berbeda.

Jika Anda tidak mengingatnya dan menganggap $IFSdiatur ke spasi putih biasa ( $' \t\n'), Anda bisa membuat skrip Anda berperilaku dalam beberapa cara yang tampak aneh.

Eliah Kagan
sumber
8

Anda dapat menyalahgunakan sudoini. sudomemiliki -lopsi, untuk menguji hak sudo yang dimiliki pengguna, dan -Suntuk membaca kata sandi dari stdin. Namun, tidak peduli apa tingkat hak istimewa yang dimiliki pengguna, jika berhasil diautentikasi, sudokembali dengan status keluar 0. Jadi, Anda dapat mengambil status keluar lainnya sebagai indikasi bahwa otentikasi tidak berfungsi (dengan asumsi sudoitu sendiri tidak memiliki masalah, seperti kesalahan izin atau sudoerskonfigurasi yang tidak valid ).

Sesuatu seperti:

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

Script ini sangat tergantung pada sudoerskonfigurasi. Saya telah mengasumsikan pengaturan default. Hal-hal yang dapat menyebabkannya gagal:

  • targetpwatau runaspwdiatur
  • listpw adalah never
  • dll.

Masalah lain termasuk (terima kasih Eliah):

  • Upaya yang salah akan masuk /var/log/auth.log
  • sudoharus dijalankan sebagai pengguna yang Anda autentikasi. Kecuali Anda memiliki sudohak istimewa sehingga Anda dapat menjalankan sudo -u foo sudo -lS, itu berarti Anda harus menjalankan skrip sebagai pengguna target.

Sekarang, alasan saya menggunakan di sini-docs adalah untuk menghambat menguping. Variabel yang digunakan sebagai bagian dari baris perintah lebih mudah diungkapkan dengan menggunakan topatau psatau alat lain untuk memeriksa proses.

muru
sumber
2
Cara ini terlihat bagus. Meskipun tidak akan bekerja pada sistem dengan sudokonfigurasi yang tidak biasa (seperti yang Anda katakan) dan mengacaukan /var/log/auth.logdengan catatan setiap upaya, ini cepat, sederhana, dan menggunakan utilitas yang ada, daripada menciptakan kembali roda (sehingga untuk berbicara). Saya menyarankan pengaturan IFS=untuk read, untuk mencegah keberhasilan palsu untuk input pengguna dengan spasi dan kata sandi non-empuk, serta kegagalan palsu untuk input pengguna dengan spasi dan kata sandi empuk. (Solusi saya memiliki bug yang serupa.) Anda mungkin juga ingin menjelaskan bagaimana itu harus dijalankan sebagai pengguna yang kata sandinya sedang diperiksa.
Eliah Kagan
@EliahKagan setiap upaya yang gagal dicatat, tetapi ya, Anda benar tentang semua yang lain.
muru
Otentikasi yang berhasil dicatat juga. sudo -lmenyebabkan entri ditulis dengan " COMMAND=list". Btw (tidak terkait), sementara itu tidak obyektif lebih baik untuk melakukannya, menggunakan string di sini sebagai pengganti dokumen di sini mencapai tujuan keamanan yang sama dan lebih kompak. Karena skrip sudah menggunakan bashisms read -sdan &>, penggunaannya if sudo -lS &>/dev/null <<<"$PASSWD"; thentidak kalah portabel. Saya tidak menyarankan Anda harus mengubah apa yang Anda miliki (tidak apa-apa). Sebaliknya, saya menyebutkannya sehingga pembaca tahu mereka juga memiliki opsi ini.
Eliah Kagan
@EliahKagan ah. Saya pikir sudo -lmenonaktifkan sesuatu - mungkin itu melaporkan ketika pengguna mencoba menjalankan perintah yang tidak diizinkan.
muru
@EliahKagan Memang. Saya telah menggunakan herestrings seperti itu sebelumnya . Saya bekerja di bawah kesalahpahaman di sini, yang mengarah ke heredocs, tetapi kemudian saya memutuskan untuk menyimpannya.
muru
4

Metode lain (mungkin lebih menarik untuk konten teoretis daripada aplikasi praktisnya).

Kata sandi pengguna disimpan di /etc/shadow.

Kata sandi yang disimpan di sini dienkripsi, dalam rilis Ubuntu terbaru menggunakan SHA-512.

Khususnya, pada saat pembuatan kata sandi, kata sandi dalam teks yang jelas akan digarami dan dienkripsi SHA-512 .

Salah satu solusi kemudian akan garam / mengenkripsi kata sandi yang diberikan dan mencocokkannya dengan kata sandi pengguna terenkripsi yang disimpan dalam pengguna yang diberikan /etc/shadow entri .

Untuk memberikan uraian singkat tentang bagaimana kata sandi disimpan di setiap /etc/shadowentri pengguna , berikut ini /etc/shadowentri sampel untuk pengguna foodengan kata sandi bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: nama pengguna
  • 6: jenis enkripsi kata sandi
  • lWS1oJnmDlaXrx1F: garam enkripsi kata sandi
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512 salted / password terenkripsi

Untuk mencocokkan kata sandi yang diberikan baruntuk pengguna yang diberikan foo, hal pertama yang harus dilakukan adalah mendapatkan garam:

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

Maka seseorang harus mendapatkan kata sandi penuh asin / terenkripsi:

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

Kemudian kata sandi yang diberikan dapat diasinkan / dienkripsi dan dicocokkan dengan kata sandi pengguna asin / terenkripsi yang disimpan di /etc/shadow:

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

Mereka cocok! Semuanya dimasukkan ke dalam bashskrip:

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

Keluaran:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match
kos
sumber
1
sudo getent shadow>> sudo grep .... Kalau tidak, bagus. Inilah yang saya pikirkan pada awalnya, kemudian menjadi terlalu malas.
muru
@muru, terima kasih. Pasti terasa lebih baik, jadi diperbarui, tapi tetap saja saya tidak yakin apakah dalam kasus ini getent+ grep+ cut> grep+cut
kos
1
Eek. getentdapat melakukan pencarian untuk Anda. Saya mengambil kebebasan mengedit.
muru
@uru Ya, seharusnya Anda memiliki fakta, sekarang saya mengerti intinya!
kos
2
Saya pikir ini sebenarnya cukup praktis, meskipun saya mungkin bias: ini adalah metode yang sama yang saya gunakan. Implementasi dan presentasi Anda sangat berbeda - ini jelas jawaban yang berharga. Sedangkan saya digunakan mkpasswduntuk menghasilkan hash dari input pengguna dan garam yang ada (dan termasuk fitur tambahan dan diagnostik), Anda malah mengkodekan program Python sederhana yang mengimplementasikan mkpasswdfungsi paling penting dan menggunakannya. Saya sarankan Anda mengatur IFS=ketika membaca masukan password, dan kutipan ${password}dengan ", sehingga upaya sandi dengan spasi tidak melanggar script.
Eliah Kagan