#!/usr/bin/env bash # # JbTecWiz Support Centre -- generated fix script # # Fault : Accounts locked out -- faillock, pam_tally and expired passwords # Fix : Clear the failed attempt counter # Source: https://jbtecwiz.com/support/lnx-sec-pam-lockout # # Run as : Root shell # Expect : 15 minutes # Risk : medium # Reversible : yes # # WHEN THIS IS THE RIGHT FIX # Too many failed attempts. # # HOW TO UNDO IT # The counter rebuilds naturally; policy changes can be reverted in # faillock.conf. # # Walks the fix one step at a time and asks before each. Steps with no # command are yours to do -- it prints those and waits. DRYRUN=1 prints # without executing; UNATTENDED=1 does not ask. # # -------------------------------------------------------------------- # NO WARRANTY - USE AT YOUR OWN RISK # # This script is provided by JbTecWiz as-is and with no warranty of any # kind, express or implied. You run it entirely at your own risk. # # JbTecWiz accepts no liability for any loss or damage arising from its # use, including but not limited to data loss, downtime, or configuration # changes that turn out to be wrong for your system. # # You are responsible for reading this script before running it, for # satisfying yourself that it suits the machine in front of you, and for # having a working backup first. Some steps cannot be undone. # -------------------------------------------------------------------- set -uo pipefail DRYRUN="${DRYRUN:-0}" UNATTENDED="${UNATTENDED:-0}" failed=0 if [ "$(id -u)" -ne 0 ]; then echo " This fix is documented as needing root. Re-run with sudo." >&2 exit 3 fi rule() { printf "\n%s\n" "$(printf '-%.0s' $(seq 1 70))"; if [ $# -gt 0 ]; then echo "$1"; fi; } prose() { echo "$1" | fold -s -w 74 | sed "s/^/ /"; } # Returns 0 when the caller should run the command, 1 when it should not. # A manual step always returns 1 -- there is nothing for the caller to run. step() { # step [command lines...] local n="$1" dotext="$2" why="$3" mode="$4"; shift 4 rule " Step $n of 5" prose "$dotext" if [ -n "$why" ]; then echo; prose "$why"; fi if [ "$mode" = "manual" ]; then echo; echo " -> Do this yourself, then press Enter to carry on." if [ "$UNATTENDED" = "0" ] && [ "$DRYRUN" = "0" ]; then read -r _; fi return 1 fi echo; printf " %s\n" "$@"; echo if [ "$DRYRUN" = "1" ]; then echo " (dry run -- not executed)"; return 1; fi if [ "$UNATTENDED" = "0" ]; then read -r -p " Run this step? [Y]es / [S]kip / [Q]uit " a case "$a" in [Qq]*) echo " Stopped at your request."; exit 0 ;; [Ss]*) echo " Skipped."; return 1 ;; esac fi return 0 } rule echo " Accounts locked out -- faillock, pam_tally and expired passwords" echo " Clear the failed attempt counter" echo echo " Risk: medium Reversible 15 minutes" echo prose 'No warranty. Use at your own risk - JbTecWiz accepts no liability. Read it before you run it, and have a backup.' rule echo if [ "$UNATTENDED" = "0" ] && [ "$DRYRUN" = "0" ]; then read -r -p " Ready? [y/N] " go case "$go" in [Yy]*) ;; *) echo " Nothing was changed."; exit 0;; esac fi if step 1 'See the counter and when it will clear on its own.' '' cmd 'sudo faillock --user username' 'sudo pam_tally2 --user username 2>/dev/null'; then sudo faillock --user username sudo pam_tally2 --user username 2>/dev/null if [ $? -ne 0 ]; then failed=$((failed+1)) echo " Step 1 failed. The rest of the fix may depend on it." >&2 fi fi if step 2 'Reset it.' '' cmd 'sudo faillock --user username --reset'; then sudo faillock --user username --reset if [ $? -ne 0 ]; then failed=$((failed+1)) echo " Step 2 failed. The rest of the fix may depend on it." >&2 fi fi if step 3 'Find out what caused the failures before assuming it was the user. A saved credential in a phone or a scheduled task retrying an old password will lock the account again within minutes.' 'Repeated lockouts are almost never the person typing. The source address in the log names the device or service that is still trying the old password, and that is the thing to fix.' cmd 'sudo journalctl -t sshd --since '\''2 hours ago'\'' | grep -i '\''failed password'\'' | awk '\''{print $(NF-3)}'\'' | sort | uniq -c | sort -rn | head'; then sudo journalctl -t sshd --since '2 hours ago' | grep -i 'failed password' | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head if [ $? -ne 0 ]; then failed=$((failed+1)) echo " Step 3 failed. The rest of the fix may depend on it." >&2 fi fi if step 4 'Review the policy if lockouts are frequent -- the defaults in /etc/security/faillock.conf are often stricter than intended.' '' cmd 'grep -vE '\''^\s*#|^\s*$'\'' /etc/security/faillock.conf'; then grep -vE '^\s*#|^\s*$' /etc/security/faillock.conf if [ $? -ne 0 ]; then failed=$((failed+1)) echo " Step 4 failed. The rest of the fix may depend on it." >&2 fi fi step 5 'Consider whether the account should be exposed to the internet at all. Key-only SSH removes password lockouts entirely.' '' manual || true rule " Confirm it worked" prose 'The user signs in and the counter stays at zero.' if [ "$DRYRUN" = "0" ]; then sudo faillock --user username fi rule if [ "$failed" -gt 0 ]; then echo " Finished with $failed failed step(s)." echo " Read the full write-up at https://jbtecwiz.com/support/lnx-sec-pam-lockout" else echo " Finished." fi echo prose 'To undo: The counter rebuilds naturally; policy changes can be reverted in faillock.conf.' rule