Bash Script: Alert to Telegram on SSH Login


Hàng ngày, có hàng tỉ lượt brute-force vào cổng SSH trên các server public cổng SSH này, có thể đó là cổng 22 mặc định.

Nó là khởi đầu cho vô vàn thảm hoạ, nếu hacker vào được server của bạn.

Hoặc giả dụ, bạn làm mất SSH Private Key, lộ SSH Password thì đây là công cụ bạn cần.

Nó giúp bạn ngay lập tức nhận được 1 tin nhắn tới Telegram nếu phát hiện có phiên đăng nhập vào SSH từ địa chỉ IP lạ. 

Sẽ rất tốt nếu bạn có 1 VPN an toàn, whitelisted hoặc IP tĩnh. 

Hoặc đơn giản, khai báo địa chỉ IP whitelisted vào file bash, hệ thống sẽ giúp bạn giám sát, cảnh báo sớm nếu có lượt đăng nhập trái phép full-width
ssh-login-alert.bash
#!/bin/bash
########################################################################
#    _    _                     _   _                                  #
#   | |  | |                   | \ | |                                 #
#   | |__| |_   _ _ __   __ _  |  \| | __ _ _   _ _   _  ___ _ __      #
#   |  __  | | | | '_ \ / _` | | . ` |/ _` | | | | | | |/ _ \ '_ \     #
#   | |  | | |_| | | | | (_| | | |\  | (_| | |_| | |_| |  __/ | | |    #
#   |_|  |_|\__,_|_| |_|\__, | |_| \_|\__, |\__,_|\__, |\___|_| |_|    #
#                        __/ |         __/ |       __/ |               #
#                       |___/         |___/       |___/                #
#----------------------------------------------------------------------#
#                 HungNG Manage Server Script Configure                #
########################################################################
SCRIPT_VERSION="1.7.1"
# Function to merge multiple arrays into one
merge_arrays() {
  local result=()    # Initialize an empty array to store the merged result
  local array_name   # Variable to hold the name of each array passed as an argument
  local element      # Variable to store individual elements of arrays
  local array_length # Variable to store the length of the current array
  local i            # Index variable for looping through array elements
  # Loop through each array name passed to the function
  for array_name in "$@"; do
    array_length=$(eval "echo \${#${array_name}[@]}") # Get the length of the array using eval to access the array by name
    # Loop through the elements of the array using index
    for ((i = 0; i < array_length; i++)); do
      element=$(eval "echo \${${array_name}[$i]}") # Get each element from the array by name and index
      result+=("$element")                         # Append the element to the result array
    done
  done
  printf "%s\n" "${result[@]}" # Output all elements of the result array, each on a new line
}
# Function parse whitelist IP addresses from configuration to array
parse_whitelist_config_on_your_server() {
  local config_file="/etc/hungng/hungng-server-whitelist.txt"
  local whitelist_ips=()
  # Check if the config file exists and is readable
  if [[ ! -e "$config_file" || ! -r "$config_file" ]]; then
    return 0
  fi
  # Read the IP addresses from the file and parse into an array
  while IFS= read -r line; do
    line=$(echo "$line" | xargs) # Trim whitespace
    if [ -n "$line" ]; then
      whitelist_ips+=("$line")
    fi
  done <"$config_file"
  printf "%s\n" "${whitelist_ips[@]}" # Output the array as space-separated values
}
count_processes_by_user() {
  if [[ -z "$1" ]]; then
    echo 0
    return
  fi
  local process_count
  process_count=$(ps -eLf | awk -v pattern="$1" '$0 ~ pattern {count++} END {print count}')
  echo "$process_count"
}
has_command() {
  command -v "$1" &>/dev/null
}
#----------------------------------------------------------------------#
#   Script alert SSH Login to Telegram                                 #
#----------------------------------------------------------------------#
# Define whitelist IP addresses
DEFAULT_WHITELIST_IP_ADDRESS=(
  "1.1.1.1" # Whitelist IP address
  "8.8.8.8" # Whitelist IP address
)
# Call parse_whitelist_config_on_your_server and read the result into an array using read -a
PARSED_IPS_FROM_CONFIG=()
while IFS= read -r line; do
  PARSED_IPS_FROM_CONFIG+=("$line")
done < <(parse_whitelist_config_on_your_server)

# Check if PARSED_IPS_FROM_CONFIG is not empty and is an array
if [ ${#PARSED_IPS_FROM_CONFIG[@]} -gt 0 ]; then
  # Merge the existing_ips and PARSED_IPS_FROM_CONFIG into a new array using read -a
  WHITELIST_IP_ADDRESS=()
  while IFS= read -r ip; do
    WHITELIST_IP_ADDRESS+=("$ip")
  done < <(merge_arrays DEFAULT_WHITELIST_IP_ADDRESS PARSED_IPS_FROM_CONFIG)
else
  WHITELIST_IP_ADDRESS=("${DEFAULT_WHITELIST_IP_ADDRESS[@]}")
fi

# Telegram Bot Configuration
export TELEGRAM_REQUEST_TIMEOUT=5
export BOT_TOKEN="{{YOUR_TELEGRAM_ALERT_BOT_TOKEN}}" # Your Telegram Bot Token
export CHAT_ID="{{YOUR_TELEGRAM_SSH_ALERT_CHAT_ID}}" # Your Telegram ChatID / Chat Group ID
# Check if SSH_CLIENT variable is not empty
if [ -n "$SSH_CLIENT" ]; then
  # Color
  NC='\033[0m'
  YELLOW='\033[0;33m'
  GREEN='\033[0;32m'
  PURPLE="\033[0;35m"
  ORANGE="\033[38;5;208m"

  # Function to get the last successful login details for a specific user, excluding the current session
  get_previous_login_info() {
    local username=$1
    # Check if username is provided
    if [[ -z "$username" ]]; then
      echo "Username is required."
      return 1
    fi
    # Get the last login information, excluding the current session
    local login_info
    login_info=$(last -i "$username" | grep -v 'still logged in' | head -n 2 | tail -n 1)
    # Check if login_info is empty
    if [[ -z "$login_info" ]]; then
      echo -e "No previous login information found for user ${YELLOW}$username${NC}."
      return 1
    fi
    # Extract IP address and login time using `awk`
    local ip_address
    ip_address=$(echo "$login_info" | awk '{print $3}')
    local latest_time
    latest_time=$(echo "$login_info" | awk '{print $4, $5, $6, $7, $8, $9, $10}')
    # Print the result in the desired format
    echo -e "Last ${YELLOW}$username${NC} login successfully from IP ${YELLOW}$ip_address${NC} at time ${YELLOW}$latest_time${NC}"
  }

  # Extract IP address and port from SSH_CLIENT variable
  SSH_CLIENT_IP=$(echo "$SSH_CLIENT" | awk '{print $1}')
  SSH_CLIENT_PORT=$(echo "$SSH_CLIENT" | awk '{print $3}')
  SSH_CLIENT_WHOIS_URL="https://ip.nguyenanhung.com/view?ip=${SSH_CLIENT_IP}"

  # Get hostname and IP address of the server
  SERVER_HOSTNAME=$(hostname -f)
  fetch_public_ip() {
    local url IP
    for url in "https://checkip.amazonaws.com/" "https://icanhazip.com/" "https://whatismyip.akamai.com/" "https://api.ipify.org/" "https://cpanel.net/showip.cgi" "https://myip.directadmin.com/"; do
      IP=$(curl -s --max-time 1 $url) && [ -n "$IP" ] && echo "$IP" && return 0
    done
    return 1
  }
  SERVER_IPADDR=$(fetch_public_ip)

  # Get login time of SSH session
  SSH_SESSION_LOGIN_TIME=$(date "+%Y-%m-%d %H:%M:%S")
  SSH_SESSION_LOGIN_DATE_EXEC=$(date +"%Y-%m-%d-%I-%M-%S-%p")

  # Define the user's home directory
  USER_HOME_DIR=$(eval echo ~"${USER}")

  # Define the temporary directory and file path
  USER_TMP_DIR="$USER_HOME_DIR/tmp"
  mkdir -p "$USER_TMP_DIR"

  # Define temporary file to store IP information
  TELEGRAM_ALERT_SSH_TMP_FILE="$USER_TMP_DIR/ipinfo-$SSH_SESSION_LOGIN_DATE_EXEC.txt"

  # Retrieve IP information from ip.nguyenanhung.com using curl
  curl "https://ip.nguyenanhung.com/json?ip=$SSH_CLIENT_IP" -s -o "$TELEGRAM_ALERT_SSH_TMP_FILE"

  # Extract location information from the temporary file
  SSH_CLIENT_LOGIN_CITY=$(jq -r '.city' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_REGION=$(jq -r '.region' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_REGION_NAME=$(jq -r '.regionName' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_COUNTRY=$(jq -r '.country' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_COUNTRY_CODE=$(jq -r '.countryCode' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_ISP=$(jq -r '.isp' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_ORG=$(jq -r '.org' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')
  SSH_CLIENT_LOGIN_ORG_AS=$(jq -r '.as' "$TELEGRAM_ALERT_SSH_TMP_FILE" | sed 's/"//g')

  # Check if SSH_CLIENT_IP is in the whitelist
  IN_WHITELIST=false
  for ip in "${WHITELIST_IP_ADDRESS[@]}"; do
    if [ "$SSH_CLIENT_IP" = "$ip" ]; then
      IN_WHITELIST=true
      break
    fi
  done

  # If SSH_CLIENT_IP is in the whitelist, exit without sending alert
  if $IN_WHITELIST; then
    read_df_info=$(df -h / | tail -n 1)
    read -r disk_filesystem disk_size disk_used disk_avail disk_use_percentage disk_mount <<<"$read_df_info"
    ssh_login_uptime_output=$(uptime)
    ssh_login_uptime_only=$(echo "$ssh_login_uptime_output" | awk -F', ' '{print $1}')
    ssh_login_uptime_only_trim=$(echo "$ssh_login_uptime_only" | awk '{$1=$1};1')
    ssh_login_load_average=$(echo "$ssh_login_uptime_output" | awk -F'load average:' '{print $2}')
    ssh_login_load_average_trim=$(echo "$ssh_login_load_average" | awk '{$1=$1};1')
    server_os_compatibility=$(grep '^ID_LIKE=' /etc/os-release | cut -d= -f2- | tr -d '"')
    os_compatibility="${server_os_compatibility^^}"
    swap_total=$(free -m | awk '/^Swap:/{print $2}')
    swap_used=$(free -m | awk '/^Swap:/{print $3}')
    if [ "$swap_total" -eq 0 ]; then
      swap_usage_percent=0
    else
      swap_usage_percent=$(((swap_used * 100) / swap_total))
    fi

    clear
    get_previous_login_info "$USER"
    echo -e "Current ${GREEN}$USER${NC} logged SSH on port ${GREEN}$SSH_CLIENT_PORT${NC} with IP address ${GREEN}$SSH_CLIENT_IP${NC} is in the whitelist. No alert sent."
    echo
    # Hiển thị dữ liệu thông tin chi tiết của địa chỉ IP
    echo "|-------------------------------------------------------------------------------|"
    printf "| %-16s | %-58s |\n" "City" "$SSH_CLIENT_LOGIN_CITY"
    printf "| %-16s | %-58s |\n" "Region" "$SSH_CLIENT_LOGIN_REGION ($SSH_CLIENT_LOGIN_REGION_NAME)"
    printf "| %-16s | %-58s |\n" "Country" "$SSH_CLIENT_LOGIN_COUNTRY_CODE ($SSH_CLIENT_LOGIN_COUNTRY)"
    printf "| %-16s | %-58s |\n" "Organization" "$SSH_CLIENT_LOGIN_ORG_AS"
    printf "| %-16s | %-58s |\n" "ISP" "$SSH_CLIENT_LOGIN_ORG"
    printf "| %-16s | %-58s |\n" "Whois URL" "$SSH_CLIENT_WHOIS_URL"
    echo "|-------------------------------------------------------------------------------|"
    printf "| %-16s | %-58s |\n" "Useful command" "helpme"
    echo "|-------------------------------------------------------------------------------|"
    printf "| %-16s | %-58s |\n" "Server Time" "$(date) - $(date "+%Y-%m-%d %H:%M:%S")"
    printf "| %-16s | %-58s |\n" "OS Name" "$(grep '^PRETTY_NAME=' /etc/os-release | cut -d= -f2- | tr -d '"')"
    printf "| %-16s | %-58s |\n" "OS Architecture" "$(uname -m)"
    printf "| %-16s | %-58s |\n" "OS Kernel" "$(uname -r)"
    printf "| %-16s | %-58s |\n" "OS Compatibility" "$os_compatibility"
    printf "| %-16s | %-58s |\n" "CPU model" "$(awk -F: '/model name/ {name=$2} END {print name}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//')"
    printf "| %-16s | %-58s |\n" "Number of cores" "$(awk -F: '/model name/ {core++} END {print core}' /proc/cpuinfo)"
    printf "| %-16s | %-58s |\n" "CPU frequency" "$(awk -F: '/cpu MHz/ {freq=$2} END {print freq}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//')MHz"
    printf "| %-16s | %-58s |\n" "System Uptime" "$(awk '{a=$1/86400;b=($1%86400)/3600;c=($1%3600)/60} {printf("%d days, %d hour %d min\n",a,b,c)}' /proc/uptime)"
    printf "| %-16s | %-58s |\n" "Load average" "$(w | head -1 | awk -F'load average:' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//')"
    printf "| %-16s | %-58s |\n" "Usage of /" "${disk_use_percentage} (Used: ${disk_used} / Total: ${disk_size} / Free: ${disk_avail})"
    printf "| %-16s | %-58s |\n" "Memory usage" "$(free -m | awk 'NR==2 {print $3/$2 * 100 "%"}')"
    printf "| %-16s | %-58s |\n" "Swap usage" "${swap_usage_percent}%"
    printf "| %-16s | %-58s |\n" "Processes" "$(ps aux | wc -l)"
    check_service_processes() {
      if has_command mysql; then
        printf "| %-16s | %-58s |\n" "MySQL" "$(count_processes_by_user "mysql") processes"
      fi
      if has_command nginx; then
        printf "| %-16s | %-58s |\n" "Nginx" "$(count_processes_by_user "nginx") processes"
      fi
      if has_command php-fpm; then
        printf "| %-16s | %-58s |\n" "PHP-FPM" "$(count_processes_by_user "php-fpm") processes"
      fi
    }
    echo "|-------------------------------------------------------------------------------|"
    check_service_processes
    echo "|-------------------------------------------------------------------------------|"
    printf "| %-16s | %-58s |\n" "Users logged" "$(who | wc -l)"
    printf "| %-16s | %-58s |\n" "eth0 IPv4" "$(ip addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')"
    printf "| %-16s | %-58s |\n" "eth0 IPv6" "$(ip addr show eth0 | grep -oP '(?<=inet6\s)[\da-f:]+' | head -n 1)"
    echo "|-------------------------------------------------------------------------------|"
    # Copyright
    echo
    echo -e "Script Manage Server version ${SCRIPT_VERSION} by ${PURPLE}Hung Nguyen${NC} ${GREEN}${NC}"
    echo
    echo "---------------------------------------------------------------------------------"
    echo
    # Unset
    unset disk_mount disk_use_percentage disk_avail disk_used disk_size disk_filesystem read_df_info
    unset ssh_login_load_average ssh_login_load_average_trim ssh_login_uptime_only ssh_login_uptime_only_trim ssh_login_uptime_output
  else
    # Telegram Configuration
    TELEGRAM_ALERT_URL="https://api.telegram.org/bot${BOT_TOKEN}/sendMessage"

    # Create alert message for Telegram
    TEXT_MSG="*$SSH_SESSION_LOGIN_TIME*: User *${USER}* logged in *SSH* to *$SERVER_HOSTNAME* ($SERVER_IPADDR) from *$SSH_CLIENT_IP* -
		$SSH_CLIENT_LOGIN_ORG_AS ($SSH_CLIENT_LOGIN_ORG) - $SSH_CLIENT_LOGIN_CITY, $SSH_CLIENT_LOGIN_REGION, $SSH_CLIENT_LOGIN_COUNTRY
		on port *$SSH_CLIENT_PORT*"

    # Send alert message to Telegram
    if [ -n "$BOT_TOKEN" ] && [ -n "$CHAT_ID" ]; then
      curl -s --max-time "$TELEGRAM_REQUEST_TIMEOUT" -d "chat_id=${CHAT_ID}&parse_mode=Markdown&disable_web_page_preview=1&text=${TEXT_MSG}" "$TELEGRAM_ALERT_URL" >/dev/null
    fi
    # Free up variables to lighten memory usage
    unset TEXT_MSG TELEGRAM_ALERT_URL
  fi
  # Remove temporary file
  rm -f "$TELEGRAM_ALERT_SSH_TMP_FILE"

  # Free up variables to lighten memory usage
  unset IN_WHITELIST
  unset SSH_CLIENT_LOGIN_ORG_AS SSH_CLIENT_LOGIN_ORG SSH_CLIENT_LOGIN_ISP SSH_CLIENT_LOGIN_COUNTRY_CODE SSH_CLIENT_LOGIN_COUNTRY
  unset SSH_CLIENT_LOGIN_REGION_NAME SSH_CLIENT_LOGIN_REGION SSH_CLIENT_LOGIN_CITY
  unset TELEGRAM_ALERT_SSH_TMP_FILE SSH_SESSION_LOGIN_DATE_EXEC SSH_SESSION_LOGIN_TIME
  unset SERVER_IPADDR SERVER_HOSTNAME
  unset SSH_CLIENT_WHOIS_URL SSH_CLIENT_PORT SSH_CLIENT_IP
  unset RED GREEN PURPLE ORANGE CYAN YELLOW NC
fi
unset BOT_TOKEN CHAT_ID TELEGRAM_REQUEST_TIMEOUT WHITELIST_IP_ADDRESS

Post a Comment

Mới hơn Cũ hơn