Skip to content

Instantly share code, notes, and snippets.

@VSharapov
Created November 6, 2024 18:42
Show Gist options
  • Select an option

  • Save VSharapov/d47d84aafe1ad663bce42a53a1111478 to your computer and use it in GitHub Desktop.

Select an option

Save VSharapov/d47d84aafe1ad663bce42a53a1111478 to your computer and use it in GitHub Desktop.
Try a set of credentials on all machines in a set of subnets
#!/usr/bin/env bash
### recommended usage is something like:
### export PARAM_SUBNETS="10.69.0.0/16 192.168.1.0/24"; export PARAM_USERNAME=ubuntu; [ -z "$PARAM_PASSWORD" ] && { read -s ; export PARAM_PASSWORD="$REPLY" ; } ; ./listAccessibleMachines.sh
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
setup_colors() {
if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then
NOFORMAT='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' ORANGE='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' YELLOW='\033[1;33m'
else
NOFORMAT='' RED='' GREEN='' ORANGE='' BLUE='' PURPLE='' CYAN='' YELLOW=''
fi
}
setup_colors
msg() {
echo >&2 -e "${1-}"
}
die() {
local msg=$1
local code=${2-1} # default exit status 1
msg "$msg"
exit "$code"
}
cleanup() {
trap - SIGINT SIGTERM ERR EXIT
rm -rf "$tmpdir"
}
for cmd in nmap expect nc; do
which "$cmd" > /dev/null || { msg "$cmd not found"; exit 1; }
done
tmpdir=$(mktemp -d)
# Parameters
if [ -z "${PARAM_SUBNETS-}" ]; then
msg "You can make this noninteractive by setting PARAM_SUBNETS"
msg "... e.g. export PARAM_SUBNETS=\"10.0.0.0/8 192.168.1.0/24\" ; $0"
subnets_to_scan=$(ip route | awk '/proto kernel/ {print $1}' | paste -sd " ")
while true; do
echo "Going to scan these subnets:"
echo "${subnets_to_scan}"
read -p "Press Enter to continue, or provide a subnet: "
if [ -z "$REPLY" ]; then
break
fi
# If the reply is already in the string, remove it
if [[ " ${subnets_to_scan} " =~ " $REPLY " ]]; then
subnets_to_scan=$(echo "$subnets_to_scan" | sed "s/ $REPLY / /g")
else
subnets_to_scan="$subnets_to_scan $REPLY"
fi
done
else
subnets_to_scan="${PARAM_SUBNETS}"
fi
if [ -z "${PARAM_USERNAME-}" ]; then
msg "You can make this noninteractive by setting PARAM_USERNAME"
msg "... e.g. export PARAM_USERNAME=ubuntu ; $0"
read -p "Username [$USER]: " username
if [ -z "$username" ]; then
username="$USER"
fi
else
username="${PARAM_USERNAME}"
fi
if [ -z "${PARAM_PASSWORD-}" ]; then
msg "You can make this noninteractive by setting PARAM_PASSWORD"
msg "... e.g. read -s ; export PARAM_PASSWORD=\"$REPLY\" ; $0"
read -p "Password: " -s password
else
password="${PARAM_PASSWORD}"
fi
# Setup
for subnet in $subnets_to_scan; do
nmap -n -sL "$subnet" | awk '/Nmap scan report/{print $NF}'
done > "$tmpdir/all_ips"
touch "$tmpdir/all_ips"
touch "$tmpdir/password_failures"
touch "$tmpdir/ssh_key_failures"
touch "$tmpdir/accessible_machines"
touch "$tmpdir/passwordless_sudo_machines"
touch "$tmpdir/port_22_failures"
# Functions
function check_sudo() {
trap - SIGINT SIGTERM ERR EXIT
ip="$1"
ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o BatchMode=yes -o UserKnownHostsFile=/dev/null "$username@$ip" sudo -n true || return 1
echo "$ip" >> "$tmpdir/passwordless_sudo_machines"
}
function test_ip() {
trap - SIGINT SIGTERM ERR EXIT
ip="$1"
# Fastest thing to check is port 22
timeout=1
nc -z -w $timeout "$ip" 22 || { echo "$ip" >> "$tmpdir/port_22_failures"; return 1; }
# Some machines may already have our ssh key
no_key_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
no_tty_args="-o PasswordAuthentication=no -o BatchMode=yes"
ssh $no_key_args $no_tty_args "$username@$ip" true && \
{ echo "$ip" >> "$tmpdir/accessible_machines"; check_sudo "$ip"; return 0; }
expect -c "
set timeout 10
spawn ssh-copy-id $no_key_args $username@$ip
expect {
\"password:\" {
send \"$password\r\"
expect {
\"Permission denied\" {
exit 1
}
\"Number of key(s) added:\" {
exit 0
}
eof {
exit 1
}
}
}
\"Number of key(s) added:\" {
exit 0
}
\"Permission denied\" {
exit 1
}
\"usage:\" {
exit 2
}
timeout {
exit 4
}
eof {
exit 3
}
}
" || exit_status=$?
msg "${RED}$ip ssh-copy-id exit status: $exit_status${NOFORMAT}"
case $exit_status in
0)
msg "Successfully copied SSH key to $ip"
;;
1)
echo "$ip" >> "$tmpdir/password_failures"
return 1
;;
2)
msg "$ip - Invalid usage of ssh-copy-id" >> "$tmpdir/errors"
return 1
;;
3)
msg "$ip - Unexpected EOF or error" >> "$tmpdir/errors"
return 1
;;
4)
msg "$ip - Connection timed out" >> "$tmpdir/errors"
return 1
;;
esac
# Now check if we can log in with our ssh keys
ssh $no_key_args $no_tty_args "$username@$ip" true && \
{ echo "$ip" >> "$tmpdir/accessible_machines"; check_sudo "$ip"; return 0; } ||\
{ echo "$ip" >> "$tmpdir/ssh_key_failures"; check_sudo "$ip"; return 1; }
}
# Let's go!
for ip in $(cat "$tmpdir/all_ips" | tr '\n' ' '); do
# Spawn a background function for each ip
(
msg "Checking $ip"
mkdir -p "$tmpdir/individual_logs/$ip"
test_ip "$ip" > "$tmpdir/individual_logs/$ip/stdout" 2> "$tmpdir/individual_logs/$ip/stderr"
) &
msg "Spawned $ip"
done
sleep 1
msg "Waiting for all background functions to finish"
wait
msg "IPs tested: $(cat "$tmpdir/all_ips" | tr ' ' '\n' | wc -l)"
msg "Failed port 22: $(cat "$tmpdir/port_22_failures" | wc -l)"
msg "Failed passwords: $(cat "$tmpdir/password_failures" | wc -l)"
msg "Failed ssh keys: $(cat "$tmpdir/ssh_key_failures" | wc -l)"
msg "Accessible machines: $(cat "$tmpdir/accessible_machines" | wc -l)"
msg "Passwordless sudo machines: $(cat "$tmpdir/passwordless_sudo_machines" | wc -l)"
msg "This temporary directory will be deleted when you exit the shell, so stash what you need."
pushd "$tmpdir"
bash || msg "Bye."
popd
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment