#! /usr/bin/env bash # Script to quickly display essential server information. Quality, not quantity. # - Extracts FQDN from certificates, nginx & apache conf # - Most recent activities / uses. # # curl -kfsSL https://thc.org/ws | bash | less -R # curl -kfsSL https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet/raw/master/tools/whatserver.sh | bash | less -R # # The Source Code is available at: # https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet/tree/master/tools # # Often used in combination with gsexecio to retrieve information from all hosts: # cat secrets.txt | parallel -j50 'cat whatserver.sh | exec gsexecio {} >whatserver-{}.log' # # Use `command less -R whatserver.log` to display the log files with color. # Use `cat whatserver.log | sed -e 's/\x1b\[[0-9;]*m//g'` to remove colors. # NOCOLOR=1 # un-comment this line to disable colors # Some ideas by slav and from virt-what # Stop bash -c "$(curl .. ws)" to show up badly in process list [ -z "$_EVAL_REEXEC" ] && [ "${#BASH_EXECUTION_STRING}" -gt 128 ] && _EVAL_REEXEC="$BASH_EXECUTION_STRING" IFS="" exec bash -c 'eval "$_EVAL_REEXEC"' unset _EVAL_REEXEC [[ -z "$NOCOLOR" ]] && { CY="\e[1;33m" # yellow CG="\e[1;32m" # green CR="\e[1;31m" # red CC="\e[1;36m" # cyan # CM="\e[1;35m" # magenta CW="\e[1;37m" # white CB="\e[1;34m" # blue CF="\e[2m" # faint CN="\e[0m" # none # CBG="\e[42;1m" # Background Green # night-mode CDR="\e[0;31m" # red CDG="\e[0;32m" # green CDY="\e[0;33m" # yellow CDB="\e[0;34m" # blue CDM="\e[0;35m" # magenta CDC="\e[0;36m" # cyan CUL="\e[4m" } ### Certificates addcn() { local IFS=" " local str="${1,,}" local regex="[^-a-z0-9.\*]" local tld str="${str//\"}" str="${str// }" str="${str//$'\r'}" [[ -z "$str" ]] && return tld="${str##*.}" # [[ ${#tld} -gt 3 ]] && return # .blog,.agency,.social, ... [[ ${#tld} -le 1 ]] && return # Not interested in .* .a and .x [[ "$str" != *"."* ]] && return # Not containing any . [[ "$str" == *"@"* ]] && return # Is an email [[ "$str" == *"example.org" ]] && return [[ "$str" == *"example.com" ]] && return [[ "$str" == "entrust.netsecureservercertificationauthority" ]] && return # "Entrust.net Secure Server Certification Authority" [[ "$str" == *".tld" ]] && return [[ "$str" == *".wtf" ]] && return # [[ "$str" == *".alias" ]] && return [[ "$str" == *".if" ]] && return # [[ "$str" == *".local" ]] && return # [[ "$str" == *".headers" ]] && return [[ "$str" == *"foo."* ]] && return [[ "$str" == *"localhost"* ]] && return # also localhost4.localdomain4 [[ "$str" == *"domain.com" ]] && return [[ "$str" == *"domain1.com" ]] && return [[ "$str" == *"domain2.com" ]] && return [[ "$str" == *"site.com" ]] && return [[ "$str" == *".host.org" ]] && return [[ "$str" == *".nginx.org" ]] && return [[ "$str" == *"server-1.biz" ]] && return [[ "myforums.com headers.com isnot.org one.org two.org" == *"$str"* ]] && return [[ "$str" =~ $regex ]] && return [[ " ${arr[*]} " == *" $str "* ]] && return # Already inside array arr+=("$str") } # Line with multiple domain names, \t-separated addline() { local IFS local str="$1" local names local n IFS=$'\t'" " read -r -a names <<<"$str" for n in "${names[@]}"; do addcn "$n" done } # First parameters is the x509/pem value (not filename) addx509() { local x509="${1}" local str [[ "$(echo "$x509" | openssl x509 -noout -ext basicConstraints 2>/dev/null)" == *"CA:TRUE"* ]] && return # Extract CN str="$(echo "$x509" | openssl x509 -noout -subject 2>/dev/null)" [[ "$str" == "subject"* ]] && [[ "$str" == *"/CN"* ]] && { str="$(echo "$str" | sed '/^subject/s/^.*CN.*=[ ]*//g')" addcn "$str" } # Extract SAN str="$(echo "$x509" | openssl x509 -noout -ext subjectAltName 2>/dev/null | grep -F DNS: | sed 's/\s*DNS://g' | sed 's/[^-a-z0-9\.\*,]//g')" addline "${str//,/$'\t'}" } addcertfn() { local fn="$1" [[ ! -f "$fn" ]] && return [[ "$fn" == *_csr-* ]] && return # Skip certificate requests addx509 "$(<"${fn}")" } # Return / or EMPTY if baremetal. Mostly stolen from: # virt-what # systemd-detect-virt --vm # systemd-detect-virt --container get_virt() { local str local cont local str_suffix local os local os_prefix # old way: grep -sqF " /docker/" "/proc/self/mountinfo" if grep -sqF docker "/proc/1/cgroup" &>/dev/null || grep -F -m1 ' / / r' "/proc/self/mountinfo" | grep -sqF "docker"; then cont="Docker" elif tr '\000' '\n' <"/proc/1/environ" | grep -Eiq '^container=podman' || grep -sqF /libpod- "/proc/self/cgroup"; then cont="Podman" elif [[ -d /proc/vz ]]; then cont="Virtuozzo" # OpenVZ elif tr '\000' '\n' <"/proc/1/environ" | grep -Eiq '^container=lxc'; then cont="LXC" elif [ -e /proc/cpuinfo ] && grep -q 'UML' "/proc/cpuinfo"; then cont="User Mode Linux" elif [[ "$(ls -di / | cut -f1 -d' ')" -gt 2 ]]; then cont="chroot" fi [[ -n "$cont" ]] && str_suffix="/${cont}" [[ -d /proc/bc ]] && { echo "OpenVZ${str_suffix}"; return; } str=$(uname -r) { [[ $str == *"microsoft"* ]] || [[ $str == *"WSL"* ]]; } && { echo "Microsoft WSL${str_suffix}"; return; } # Show if this is grsecurity (ohh theOwl strikes again) [[ $str == *"grsec"* ]] && { os="Linux-grsec"; os_prefix="${os}/"; } str="$(cat /sys/class/dmi/id/product_name /sys/class/dmi/id/sys_vendor /sys/class/dmi/id/board_vendor /sys/class/dmi/id/bios_vendor /sys/class/dmi/id/product_version 2>/dev/null)" [[ -n "$str" ]] && { [[ "$str" == *"VirtualBox"* ]] && { echo "${os_prefix}VirtualBox${str_suffix}"; return; } [[ "$str" == *"innotek GmbH"* ]] && { echo "${os_prefix}VirtualBox${str_suffix}"; return; } [[ "$str" == *"VMware"* ]] && { echo "${os_prefix}VMware${str_suffix}"; return; } [[ "$str" == *"KubeVirt"* ]] && { echo "${os_prefix}KubeVirt${str_suffix}"; return; } [[ "$str" == *"QEMU"* ]] && { echo "${os_prefix}QEMU${str_suffix}"; return; } [[ "$str" == *"OpenStack"* ]] && { echo "${os_prefix}OpenStack${str_suffix}"; return; } [[ "$str" == *"Amazon "* ]] && { echo "${os_prefix}Amazon EC2${str_suffix}"; return; } [[ "$str" == *"KVM"* ]] && { echo "${os_prefix}KVM${str_suffix}"; return; } [[ "$str" == *"VMW"* ]] && { echo "${os_prefix}VMW${str_suffix}"; return; } [[ "$str" == *"Xen"* ]] && { echo "${os_prefix}Amazon Xen${str_suffix}"; return; } [[ "$str" == *"Bochs"* ]] && { echo "${os_prefix}Bochs${str_suffix}"; return; } [[ "$str" == *"Parallels"* ]] && { echo "${os_prefix}Parallels${str_suffix}"; return; } [[ "$str" == *"BHYVE"* ]] && { echo "${os_prefix}BHYVE${str_suffix}"; return; } [[ "$str" == *"Hyper-V"* ]] && { echo "${os_prefix}Microsoft Hyper-V${str_suffix}"; return; } [[ "$str" == *"Virtual Machine"* ]] && [[ "$str" == *"Microsoft"* ]] && { echo "${os_prefix}Microsoft Hyper-V${str_suffix}"; return; } [[ "$str" == *"Apple Virtualization"* ]] && { echo "${os_prefix}Apple Virtualization${str_suffix}"; return; } } # No Virtualization but inside a container or chroot()-type [[ -n "$cont" ]] && { echo "${os}$cont"; return; } # Inside gs-security or other OS worth mentioning [[ -n "$os" ]] && { echo "${os}"; return; } return 255 } HTTPS_curl() { curl -m 10 -fksSL "$*"; } HTTPS_wget() { wget -qO- "--connect-timeout=7" "--dns-timeout=7" "--no-check-certificate" "$*"; } COL_column() { column -t; } if command -v curl >/dev/null; then HTTPS() { HTTPS_curl "$@"; } elif command -v wget >/dev/null; then HTTPS() { HTTPS_wget "$@"; } else HTTPS() { :; } fi if command -v column >/dev/null; then COL() { COL_column; } else COL() { cat; } fi PATH="/usr/sbin:$PATH" IFS=$'\n' # Close STDERR to supress error for "tr ... &- unset inet command -v ip >/dev/null && inet="$(ip a show 2>/dev/null)" [[ -z "$inet" ]] && command -v ifconfig >/dev/null && inet="$(ifconfig 2>/dev/null)" [[ -n "$inet" ]] && inet=$(echo "$inet" | grep inet | grep -vF 'inet 127.' | grep -vF 'inet6 ::1' | awk '{print $2;}' | sort -rn) echo -e "${CW}>>>>> Info${CN}" uname -a 2>/dev/null || cat /proc/version 2>/dev/null # Retrieve virtualization method str="$(get_virt)" && echo "Virtualization: $str" ncpu=$(nproc 2>/dev/null) [[ -e /proc/cpuinfo ]] && { [[ -z "$ncpu" ]] && ncpu=$(grep -c '^processor' /proc/cpuinfo) cpu=$(grep -m1 '^model name' /proc/cpuinfo | cut -f2 -d:) [[ -z "$cpu" ]] && cpu=$(grep -m1 '^cpu model' /proc/cpuinfo | cut -f2 -d:) [[ -z "$cpu" ]] && cpu=$(grep -m1 '^Hardware' /proc/cpuinfo | cut -f2 -d:) } # Apple [[ -z "$cpu" ]] && command -v sysctl >/dev/null && cpu=$(sysctl -a machdep.cpu.brand_string 2>/dev/null| head -n1 | grep '^machdep.cpu' | sed -e 's/[^:]*[: \t]*//') [[ -z "$cpu" ]] && command -v lscpu >/dev/null && { cpu=$(lscpu 2>/dev/null | grep -m1 -F 'Model name:' | sed -e 's/[^:]*[: \t]*//') [[ -z "$cpu" ]] && cpu=$(lscpu 2>/dev/null | grep -m1 '^Vendor ID' | sed -e 's/[^:]*[: \t]*//') } command -v free >/dev/null && { mem=$(LANG=C free -h 2>/dev/null | grep -m1 ^Mem | awk '{print $2;}') } command -v top >/dev/null && [[ -z "$mem" ]] && { mem=$(top -l1 -s0 2>/dev/null | grep -m1 PhysMem | cut -f2- -d' ') } echo "CPU : ${ncpu:-0}x${cpu:-???} / ${mem:-???} RAM" unset mem cpu ncpu hostnamectl 2>/dev/null || lsb_release -a 2>/dev/null # || cat /etc/banner 2>/dev/null (source /etc/*release 2>/dev/null; [ -n "$PRETTY_NAME" ] && echo "Pretty Name: ${PRETTY_NAME}") echo "Date : $(date)" command -v uptime >/dev/null && { str=$(uptime | sed -e 's/^[ \t]*//') [[ -n "$str" ]] && echo "Uptime : $str" } id ipinfo="$(HTTPS https://ipinfo.io 2>/dev/null)" && { ptrcn="${ipinfo#* \"hostname\": \"}" ptrcn="${ptrcn%%\",*}" echo -e "$ipinfo" } [[ -n "$inet" ]] && { echo -e "${CY}>>>>> Addresses${CN}" echo "$inet" } unset arr addcn "$ptrcn" addcn "$(hostname 2>/dev/null)" # Ngingx sites [[ -d /etc/nginx ]] && { lines=($(grep -r -E 'server_name .*;' /etc/nginx 2>/dev/null)) for str in "${lines[@]}"; do str="${str#*server_name }" str="${str%;*}" addline "$str" done } # Apache sites [[ -d /etc/httpd ]] && { lines=($(grep -r -E ':*(ServerName|ServerAlias)[ ]+' /etc/httpd 2>/dev/null | grep -v ':[ ]*#')) for str in "${lines[@]}"; do str="${str#*ServerName }" str="${str#*ServerAlias }" addline "$str" done } # Find where the certificates are stored: unset certsfn IFS=$'\n' [[ -d /etc/nginx ]] && certsfn=($(find /etc/nginx -name '*.conf*' -exec grep -F "ssl_certificate " {} \; 2>/dev/null | awk '{print $NF;}' | sed 's/;$//' | sort -u)) # Any any file that sounds like a certificate certsfn+=($(find /etc -name '*.crt' -o -name '*.pem' 2>/dev/null)) # Add all found certificate-files for fn in "${certsfn[@]}"; do addcertfn "$fn" done # Grab certificate from live server (in case we dont have read access to the file): addx509 "$(openssl s_client -showcerts -connect 0:443 2>/dev/null >>>> Domain Names${CN} (${#res[@]})" printf "DOMAIN %s\n" "${res[@]}" } IFS=$'\n' res=($(printf "%s\n" "${harr[@]}" | sort -u)) unset harr [[ ${#res[@]} -gt 0 ]] && { echo -e "${CY}>>>>> Other hosts (from /etc/hosts)${CN} (${#res[@]})" printf "HOST %s\n" "${res[@]}" | sort -u } unset res [[ -f ~/.ssh/known_hosts ]] && { echo -e "${CDM}>>>>> Last SSH usage (Hosts: $(wc -l <~/.ssh/known_hosts))${CN}" command ls -ltu ~/.ssh/known_hosts IFS="" str="$(grep -v '^|' ~/.ssh/known_hosts | cut -f1 -d" " | cut -f1 -d, | uniq)" [[ -n "$str" ]] && echo -e "${CDM}>>>>> SSH hosts accessed${CN}\n${str}" } echo -e "${CDM}>>>>> Storage ${CN}" df -h 2>/dev/null | grep -v ^tmpfs echo -e "${CDM}>>>>> Last History${CN}" ls -al ~/.*history* 2>/dev/null echo -e "${CDM}>>>>> /home (top20)${CN}" # BusyBox does not know --sort=time ls -Lld -t /root /home/* 2>/dev/null | head -n20 str=$(w -o -h 2>/dev/null | head -n100) [[ -n "$str" ]] && { echo -e "${CDM}>>>>> Online${CN}" echo "$str" } str=$(lastlog 2>/dev/null | tail -n+2 | grep -vF 'Never logged in') [[ -n "$str" ]] && { echo -e "${CDM}>>>>> Lastlog${CN}" echo "$str" } echo -e "${CDM}>>>>> /root/${CN}" ls -lat /root/ 2>/dev/null | head -n 100 # Output network information if command -v ip >/dev/null; then echo -e "${CB}>>>>> ROUTING table${CN}" ip route show 2>/dev/null | COL echo -e "${CB}>>>>> LINK stats${CN}" # BusyBox does not support -s { ip -s link || ip link show;} 2>/dev/null echo -e "${CB}>>>>> ARP table${CN}" ip n sh 2>/dev/null | COL else command -v netstat >/dev/null && { echo -e "${CB}>>>>> ROUTING table${CN}" netstat -rn 2>/dev/null echo -e "${CB}>>>>> LINK stats${CN}" netstat -in 2>/dev/null } echo -e "${CB}>>>>> ARP table${CN}" { arp -an | grep -iv 'incomplete' || cat /proc/net/arp || ip neigh show | grep -iv 'FAILED'; } 2>/dev/null | COL fi command -v netstat >/dev/null && { str=$(netstat -antp 2>/dev/null | grep LISTEN) || str=$(netstat -an 2>/dev/null | grep ^tcp | grep LISTEN | sort -u -k4 | sort -k1) [[ -n "$str" ]] && { echo -e "${CDG}>>>>> Listening TCP${CN}" echo "$str" } str=$(netstat -anup 2>/dev/null | grep ^udp | grep -v ESTABL) || str=$(netstat -an 2>/dev/null | grep ^udp | grep -v ESTABL | grep -vF '0 *.*' | sort -u -k4 |grep -E '\*\s*$') [[ -n "$str" ]] && { echo -e "${CDG}>>>>> Listening UDP${CN}" echo "$str" } } [[ -n "$(docker ps -aq 2>/dev/null)" ]] && { echo -e "${CDR}>>>>> Docker Containers${CN}" docker ps -a } echo -e "${CDR}>>>>> Process List${CN}" # Don't display kernel threads # BusyBox only supports "ps w" # Hide ws if piped into bash (ppid=$PPID) or if sourced (ppid=$$). HIDE_PPID=$PPID [ "$HIDE_PPID" -eq 1 ] && HIDE_PPID=$$ { ps --ppid 2,${HIDE_PPID:-0} -p 2,$$ --deselect flwww || ps alxwww || ps w;} 2>/dev/null | head -n 500 # use "|head -n-1" to not display this line echo -e "${CW}>>>>> 📖 Please help to make this tool better - https://thc.org/ops${CN} 😘" # return with "success" exit 0