เนื่องจากคุณต้องการทำสิ่งนี้ในเชลล์สคริปต์มีส่วนร่วมสองสามอย่างในวิธีตรวจสอบรหัสผ่านด้วย Linux? (ในUnix.SE , แนะนำโดย AB ) มีความเกี่ยวข้องโดยเฉพาะ
ในการตรวจสอบด้วยตนเองว่าสตริงนั้นเป็นรหัสผ่านของผู้ใช้จริง ๆ หรือไม่คุณจะต้องแฮชมันด้วยอัลกอริธึมการแฮชแบบเดียวกับในรายการเงาของผู้ใช้ด้วยเกลือเดียวกันกับในรายการเงาของผู้ใช้ จากนั้นสามารถเปรียบเทียบกับแฮรหัสผ่านที่เก็บไว้ที่นั่น
ฉันได้เขียนสคริปต์การทำงานที่สมบูรณ์ซึ่งแสดงให้เห็นถึงวิธีการทำสิ่งนี้
- หากคุณตั้งชื่อ
chkpass
คุณสามารถเรียกใช้และจะอ่านบรรทัดจากอินพุตมาตรฐานและตรวจสอบว่าเป็นรหัสผ่านหรือไม่chkpass user
user
- ติดตั้งแพ็คเกจwhois เพื่อรับ
mkpasswd
ยูทิลิตี้ที่สคริปต์นี้ใช้
- สคริปต์นี้ต้องถูกเรียกใช้ในฐานะรูท
- ก่อนที่จะใช้สคริปต์นี้หรือส่วนใดส่วนหนึ่งของสคริปต์เพื่อใช้งานได้จริงโปรดดูSecurity Notesด้านล่าง
#!/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
หมายเหตุด้านความปลอดภัย
มันอาจไม่ใช่วิธีที่ถูกต้อง
ไม่ว่าวิธีการเช่นนี้ควรได้รับการพิจารณาว่าปลอดภัยหรือไม่ขึ้นอยู่กับรายละเอียดเกี่ยวกับกรณีการใช้งานที่คุณไม่ได้ให้ไว้ (ตามที่เขียนไว้นี้)
มันยังไม่ได้รับการตรวจสอบ
ถึงแม้ว่าผมได้พยายามที่จะดูแลการออกกำลังกายในขณะที่เขียนสคริปต์นี้ยังไม่ได้รับการตรวจสอบอย่างถูกต้องสำหรับช่องโหว่ความปลอดภัย มันมีจุดประสงค์เพื่อเป็นการสาธิตและจะเป็นซอฟต์แวร์ "อัลฟ่า" หากวางจำหน่ายโดยเป็นส่วนหนึ่งของโครงการ นอกจากนี้ ...
ผู้ใช้ที่มีการ "ดู" อีกอาจจะไม่สามารถที่จะค้นพบผู้ใช้เกลือ
เนื่องจากข้อ จำกัด ในการmkpasswd
ยอมรับข้อมูลเกลือสคริปต์นี้มีข้อบกพร่องด้านความปลอดภัยที่รู้จักซึ่งคุณอาจหรืออาจจะไม่ได้รับการพิจารณาขึ้นอยู่กับกรณีการใช้งาน ตามค่าเริ่มต้นผู้ใช้บน Ubuntu และระบบ GNU / Linux อื่น ๆ ส่วนใหญ่สามารถดูข้อมูลเกี่ยวกับกระบวนการที่ดำเนินการโดยผู้ใช้รายอื่น (รวมถึงรูท) รวมถึงอาร์กิวเมนต์บรรทัดคำสั่ง ไม่มีการป้อนข้อมูลของผู้ใช้หรือรหัสผ่านแฮชที่เก็บไว้เป็นอาร์กิวเมนต์บรรทัดคำสั่งไปยังโปรแกรมอรรถประโยชน์ภายนอก แต่เกลือที่ถูกแยกออกมาจากshadow
ฐานข้อมูลนั้นถูกกำหนดให้เป็นอาร์กิวเมนต์บรรทัดคำสั่งmkpasswd
เนื่องจากนี่เป็นวิธีเดียวที่ยูทิลิตี้ยอมรับเกลือเป็นอินพุต
ถ้า
- ผู้ใช้รายอื่นในระบบหรือ
- ทุกคนที่มีความสามารถในการสร้างบัญชีผู้ใช้ (เช่น
www-data
) เรียกใช้รหัสของพวกเขาหรือ
- ทุกคนที่สามารถดูข้อมูลเกี่ยวกับกระบวนการทำงาน (รวมถึงโดยการตรวจสอบรายการด้วยตนเอง
/proc
)
สามารถตรวจสอบอาร์กิวเมนต์บรรทัดคำสั่งได้mkpasswd
เนื่องจากสคริปต์นี้รันจากนั้นพวกเขาสามารถรับสำเนาเกลือของผู้ใช้จากshadow
ฐานข้อมูล พวกเขาอาจต้องเดาได้เมื่อคำสั่งนั้นรัน แต่บางครั้งก็สามารถทำได้
ผู้โจมตีด้วยเกลือของคุณไม่ได้เลวร้ายเท่ากับผู้โจมตีด้วยเกลือและกัญชาของคุณแต่ก็ไม่เหมาะ เกลือไม่ได้ให้ข้อมูลที่เพียงพอสำหรับบางคนในการค้นหารหัสผ่านของคุณ แต่จะอนุญาตให้บางคนสร้างตารางเรนโบว์หรือพจนานุกรมที่คำนวณล่วงหน้าสำหรับผู้ใช้นั้นในระบบนั้น นี้เป็นครั้งแรกที่ไร้ค่า แต่ถ้าความปลอดภัยของคุณถูกบุกรุกในภายหลังและกัญชาเต็มรูปแบบจะได้รับแล้วมันอาจจะแตกได้รวดเร็วยิ่งขึ้นที่จะได้รับรหัสผ่านของผู้ใช้ก่อนที่พวกเขาจะได้รับโอกาสที่จะเปลี่ยนมัน
ดังนั้นข้อบกพร่องด้านความปลอดภัยนี้จึงเป็นปัจจัยที่ทวีความรุนแรงยิ่งขึ้นในสถานการณ์การโจมตีที่มีความซับซ้อนมากกว่าที่จะเป็นช่องโหว่ที่มีช่องโหว่ และคุณอาจพิจารณาสถานการณ์ข้างต้นอย่างลึกซึ้ง แต่ฉันลังเลที่จะแนะนำวิธีการใด ๆ สำหรับการใช้งานทั่วไปและในโลกแห่งความเป็นจริงที่รั่วไหลข้อมูลที่ไม่ใช่ข้อมูลสาธารณะใด ๆ/etc/shadow
สู่ผู้ใช้ที่ไม่ใช่รูท
คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดย:
- เป็นส่วนหนึ่งของการเขียนสคริปต์ของคุณใน Perl หรือภาษาอื่น ๆ ที่ช่วยให้คุณสามารถเรียกใช้ฟังก์ชัน C เป็นแสดงให้เห็นในคำตอบของ Gillesเพื่อคำถาม Unix.SE ที่เกี่ยวข้อง , หรือ
- เขียนสคริปต์ / โปรแกรมทั้งหมดของคุณในภาษาดังกล่าวแทนที่จะใช้ทุบตี (ขึ้นอยู่กับวิธีที่คุณติดแท็กคำถามดูเหมือนว่าคุณต้องการใช้ bash)
ระวังวิธีที่คุณเรียกสคริปต์นี้
ถ้าคุณอนุญาตให้ผู้ใช้ที่ไม่น่าเชื่อถือในการเรียกใช้สคริปต์นี้เป็นรากหรือเรียกใช้กระบวนการใด ๆ ที่เป็นรากที่เรียกสคริปต์นี้ต้องระวัง โดยการเปลี่ยนสภาพแวดล้อมที่พวกเขาสามารถทำสคริปต์นี้ - หรือใด ๆสคริปต์ที่วิ่งเป็นราก - ทำอะไร นอกจากว่าคุณจะสามารถป้องกันไม่ให้สิ่งนี้เกิดขึ้นคุณต้องไม่อนุญาตให้ผู้ใช้ยกระดับสิทธิ์สำหรับการเรียกใช้เชลล์สคริปต์
ดู10.4 ภาษาสคริปต์ Shell (sh และ csh Derivatives)ใน David A. Wheeler ของSecure Programming สำหรับ Linux และ Unix HOWTOสำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ ในขณะที่การนำเสนอของเขามุ่งเน้นไปที่สคริปต์ setuid กลไกอื่น ๆ อาจตกเป็นเหยื่อของปัญหาเดียวกันบางอย่างหากพวกเขาไม่ทำความสะอาดสภาพแวดล้อมอย่างถูกต้อง
หมายเหตุอื่น ๆ
สนับสนุนการอ่านแฮชจากshadow
ฐานข้อมูลเท่านั้น
รหัสผ่านจะต้องมีเงาเพื่อให้สคริปต์นี้ทำงาน (เช่นแฮชควรอยู่ใน/etc/shadow
ไฟล์แยกต่างหากที่มีเพียงรูทเท่านั้นที่สามารถอ่านไม่ใช่ใน/etc/passwd
)
นี่ควรเป็นกรณีใน Ubuntu เสมอ ในกรณีใด ๆ หากจำเป็นสคริปต์ที่สามารถขยายได้นิด ๆ ในการอ่านรหัสผ่าน hashes จากเช่นเดียวกับpasswd
shadow
โปรดทราบIFS
เมื่อแก้ไขสคริปต์นี้
ผมตั้งที่จุดเริ่มต้นตั้งแต่สามข้อมูลในฟิลด์กัญชาของรายการเงาจะถูกคั่นด้วยIFS=$
$
- พวกเขายังมีชั้นนำ
$
ซึ่งเป็นสาเหตุที่ประเภทกัญชาและเกลือ"${ent[1]}"
และ"${ent[2]}"
มากกว่า"${ent[0]}"
และ"${ent[1]}"
ตามลำดับ
ที่เดียวในสคริปต์นี้ที่$IFS
กำหนดว่าเชลล์แยกหรือรวมคำต่างๆอย่างไร
เมื่อข้อมูลเหล่านี้ถูกแบ่งออกเป็นอาร์เรย์โดยการเริ่มต้นจากการ$(
)
ทดแทนคำสั่งunquote ใน:
set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
เมื่ออาร์เรย์ถูกสร้างขึ้นใหม่เป็นสตริงเพื่อเปรียบเทียบกับเขตข้อมูลเต็มจากshadow
การ"${ent[*]}"
แสดงออกใน:
if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]
หากคุณแก้ไขสคริปต์และให้สคริปต์แบ่งคำ (หรือการรวมคำ) ในสถานการณ์อื่น ๆ คุณจะต้องตั้งค่าIFS
ต่าง ๆ สำหรับคำสั่งต่าง ๆ หรือส่วนต่าง ๆ ของสคริปต์
หากคุณไม่คำนึงถึงเรื่องนี้และถือว่าการ$IFS
ตั้งค่าเป็นช่องว่างปกติ ( $' \t\n'
) คุณอาจท้ายทำให้สคริปต์ของคุณทำงานในรูปแบบที่ดูแปลก ๆ