{"uuid": "a4a1c8f1-fbb0-413c-b172-ee07df21a1ba", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-3094", "type": "seen", "source": "https://gist.github.com/kaiesh/d66ccb41dca37adfd5f84457ef28913b", "content": "#!/usr/bin/env bash\n#\n# ssh_supplychain_audit.sh\n# ------------------------------------------------------------------\n# Read-only audit for known SSH supply-chain compromises, trojanized\n# PAM/OpenSSH login components, and a named APT login-stack campaign.\n# This script CHANGES NOTHING on the system. It only reads files, package\n# metadata, and process state, then reports PASS / WARN / FAIL.\n#\n# Covers (section numbers match the on-screen output):\n#   1.  XZ / liblzma backdoor  (CVE-2024-3094, liblzma 5.6.0 / 5.6.1)\n#   2.  Trojanized sshd / rogue SSH .so libraries\n#       (e.g. ELF/Sshdinjector \"libsshd.so\" / \"libssdh.so\" family)\n#   3.  Binary integrity of sshd / curl / sudo / xz / coreutils vs. the package DB\n#   4.  authorized_keys audit across all accounts\n#   4b. PAM modules and /etc/pam.d config (PAM-based login backdoors)\n#   5.  sshd_config review (incl. cross-check against your expected SSH port)\n#   6.  Running sshd and listeners (port-aware; live connections on that port)\n#   7.  Velvet Ant / \"Operation Highland\" IOC hunt (Sygnia, Jun 2026):\n#       backdoored pam_unix.so + OpenSSH credential/keylog implants\n#\n# Recommended: run as root so package verification and process maps are\n# fully visible.  It will still run as a normal user with reduced coverage.\n#\n# Usage:   sudo bash ssh_supplychain_audit.sh [-p PORT] [PORT]\n#          Default SSH port is 22. Pass your custom port either way:\n#            sudo bash ssh_supplychain_audit.sh 2828\n#            sudo bash ssh_supplychain_audit.sh --port 2828\n# Exit codes: 0 = no FAIL findings, 2 = at least one FAIL finding.\n# ------------------------------------------------------------------\n\nset -u\nLC_ALL=C\nexport LC_ALL\n\n# ---- output helpers ------------------------------------------------\nif [ -t 1 ]; then\n  R=$'\\e[31m'; G=$'\\e[32m'; Y=$'\\e[33m'; B=$'\\e[36m'; BOLD=$'\\e[1m'; N=$'\\e[0m'\nelse\n  R=\"\"; G=\"\"; Y=\"\"; B=\"\"; BOLD=\"\"; N=\"\"\nfi\n\nFAIL_COUNT=0\nWARN_COUNT=0\n\nsection() { printf '\\n%s== %s ==%s\\n' \"$BOLD\" \"$1\" \"$N\"; }\npass()    { printf '  %s[ PASS ]%s %s\\n' \"$G\" \"$N\" \"$1\"; }\ninfo()    { printf '  %s[ INFO ]%s %s\\n' \"$B\" \"$N\" \"$1\"; }\nwarn()    { printf '  %s[ WARN ]%s %s\\n' \"$Y\" \"$N\" \"$1\"; WARN_COUNT=$((WARN_COUNT+1)); }\nfail()    { printf '  %s[ FAIL ]%s %s\\n' \"$R\" \"$N\" \"$1\"; FAIL_COUNT=$((FAIL_COUNT+1)); }\n\nhave() { command -v \"$1\" &gt;/dev/null 2&gt;&amp;1; }\n\n# ---- binary scanning without depending on binutils 'strings' ------\n# Prefer GNU 'grep -a' (always present, reads binaries statically);\n# fall back to 'strings' only if grep -a is somehow unavailable.\nBIN_SCAN_OK=1\nif ! printf 'x' | grep -aq 'x' 2&gt;/dev/null; then\n  have strings || BIN_SCAN_OK=0\nfi\nbin_has_fixed() {   # $1=needle  $2=file  -&gt; exit 0 if needle present in file\n  grep -aqF -- \"$1\" \"$2\" 2&gt;/dev/null &amp;&amp; return 0\n  have strings &amp;&amp; strings -a \"$2\" 2&gt;/dev/null | grep -qF -- \"$1\" &amp;&amp; return 0\n  return 1\n}\nxz_version_of() {   # $1=file -&gt; prints \"xz (XZ Utils) X.Y.Z\" if found\n  local v\n  v=$(grep -aoE 'xz \\(XZ Utils\\) [0-9]+\\.[0-9]+\\.[0-9]+' \"$1\" 2&gt;/dev/null | head -1)\n  [ -z \"$v\" ] &amp;&amp; have strings &amp;&amp; v=$(strings -a \"$1\" 2&gt;/dev/null | grep -m1 'xz (XZ Utils)')\n  [ -z \"$v\" ] &amp;&amp; v=$(\"$1\" --version 2&gt;/dev/null | grep -m1 'XZ Utils')   # last resort\n  printf '%s' \"$v\"\n}\n\n# ---- arguments: SSH port to check ---------------------------------\nSSH_PORT=22\nprint_usage() {\n  cat &lt;&amp;2; exit 1; }\n      SSH_PORT=\"$2\"; shift 2 ;;\n    --port=*) SSH_PORT=\"${1#*=}\"; shift ;;\n    -h|--help) print_usage; exit 0 ;;\n    -*) echo \"Unknown option: $1\" &gt;&amp;2; print_usage; exit 1 ;;\n    *)  SSH_PORT=\"$1\"; shift ;;\n  esac\ndone\ncase \"$SSH_PORT\" in\n  ''|*[!0-9]*) echo \"Invalid port: '$SSH_PORT' (must be an integer 1-65535).\" &gt;&amp;2; exit 1 ;;\nesac\nSSH_PORT=$((10#$SSH_PORT))   # force base-10 (tolerate leading zeros)\nif [ \"$SSH_PORT\" -lt 1 ] || [ \"$SSH_PORT\" -gt 65535 ]; then\n  echo \"Invalid port: $SSH_PORT (must be 1-65535).\" &gt;&amp;2; exit 1\nfi\n\n# ---- environment facts --------------------------------------------\nDISTRO_FAMILY=\"unknown\"\nif [ -f /etc/os-release ]; then\n  . /etc/os-release\n  case \"${ID:-}${ID_LIKE:-}\" in\n    *debian*|*ubuntu*) DISTRO_FAMILY=\"debian\" ;;\n    *rhel*|*fedora*|*centos*|*rocky*|*almalinux*) DISTRO_FAMILY=\"rhel\" ;;\n  esac\nfi\n\nIS_ROOT=0; [ \"$(id -u)\" = \"0\" ] &amp;&amp; IS_ROOT=1\n\nprintf '%sSSH supply-chain audit%s  host=%s  family=%s  root=%s  ssh_port=%s  date=%s\\n' \\\n  \"$BOLD\" \"$N\" \"$(hostname 2&gt;/dev/null)\" \"$DISTRO_FAMILY\" \"$IS_ROOT\" \"$SSH_PORT\" \"$(date -u +%FT%TZ)\"\n[ \"$IS_ROOT\" = \"0\" ] &amp;&amp; warn \"Not running as root - package verification and process maps will be incomplete. Re-run with sudo for full coverage.\"\n\n# ===================================================================\n# 1. XZ / liblzma backdoor (CVE-2024-3094)\n# ===================================================================\nsection \"1. XZ / liblzma backdoor (CVE-2024-3094)\"\n\n# 1a. Version of every xz binary on PATH and on disk (static read; no binutils needed)\nxz_bad=0\nif have xz || [ -e /usr/bin/xz ] || [ -e /bin/xz ]; then\n  for xz_p in $( { type -a xz 2&gt;/dev/null | awk '{print $NF}'; echo /usr/bin/xz; echo /bin/xz; } | sort -u ); do\n    [ -e \"$xz_p\" ] || continue\n    ver=$(xz_version_of \"$xz_p\")\n    case \"$ver\" in\n      *5.6.0*|*5.6.1*) fail \"Backdoored xz version at $xz_p : $ver\"; xz_bad=1 ;;\n      \"\") warn \"xz IS present at $xz_p but its version string could not be read here - confirm with: xz --version  (and see package check below)\" ;;\n      *)  info \"$xz_p -&gt; $ver\" ;;\n    esac\n  done\nelse\n  info \"No xz binary found on PATH or in /usr/bin,/bin\"\nfi\n\n# 1b. Installed package version\n# NOTE: Debian/Ubuntu shipped the fix as version \"5.6.1+really5.4.5-...\".\n# That string starts with 5.6.1 but is SAFE, so we explicitly skip *really*.\nif [ \"$DISTRO_FAMILY\" = \"debian\" ] &amp;&amp; have dpkg-query; then\n  badpkg=$(dpkg-query -W -f='${Package} ${Version}\\n' xz-utils liblzma5 2&gt;/dev/null | \\\n    while read -r p v; do\n      case \"$v\" in\n        *really*) : ;;                         # rolled-back, safe\n        5.6.0*|5.6.1*) echo \"$p $v\" ;;\n      esac\n    done)\n  if [ -n \"$badpkg\" ]; then fail \"Package DB reports backdoored xz/liblzma: $badpkg\"; xz_bad=1\n  else info \"Package DB xz/liblzma version is not in the 5.6.0/5.6.1 backdoor range\"; fi\nelif [ \"$DISTRO_FAMILY\" = \"rhel\" ] &amp;&amp; have rpm; then\n  badpkg=$(rpm -q --qf '%{NAME} %{VERSION}\\n' xz xz-libs 2&gt;/dev/null | \\\n    while read -r p v; do\n      case \"$v\" in\n        *really*) : ;;\n        5.6.0*|5.6.1*) echo \"$p $v\" ;;\n      esac\n    done)\n  if [ -n \"$badpkg\" ]; then fail \"Package DB reports backdoored xz/xz-libs: $badpkg\"; xz_bad=1\n  else info \"Package DB xz/xz-libs version is not in the 5.6.0/5.6.1 backdoor range\"; fi\nfi\n\n# 1c. The decisive check: hunt the known backdoor string inside liblzma.\n#     This string is a published indicator embedded in the malicious build.\nKILLSTR='yolAbejyiejuvnup=Evjtgvsh5okmkAvj'\nlib_hits=0\nwhile IFS= read -r so; do\n  [ -e \"$so\" ] || continue\n  lib_hits=$((lib_hits+1))\n  if bin_has_fixed \"$KILLSTR\" \"$so\"; then\n    fail \"Backdoor indicator string found inside $so  &lt;-- STRONG sign of XZ backdoor\"\n    xz_bad=1\n  fi\ndone &lt; &lt;(find /lib /lib64 /usr/lib /usr/lib64 /usr/local/lib -xdev -name 'liblzma.so.5*' 2&gt;/dev/null)\nif [ \"$lib_hits\" -gt 0 ] &amp;&amp; [ \"$xz_bad\" = \"0\" ]; then\n  if [ \"$BIN_SCAN_OK\" = \"1\" ]; then\n    pass \"liblzma.so.5 present, scanned, no backdoor indicator string found\"\n  else\n    warn \"liblzma.so.5 present, but this host has neither 'grep -a' nor 'strings' - the backdoor-string scan was NOT performed (do not treat this as clean)\"\n  fi\nfi\n\n# 1d. Does sshd link liblzma? (the XZ payload reaches sshd via the\n#     systemd notification path; this tells you whether you were exposed.)\nSSHD_BIN=$(command -v sshd 2&gt;/dev/null || echo /usr/sbin/sshd)\nif [ -e \"$SSHD_BIN\" ] &amp;&amp; have ldd; then\n  if ldd \"$SSHD_BIN\" 2&gt;/dev/null | grep -q 'liblzma'; then\n    if [ \"$xz_bad\" = \"1\" ]; then\n      fail \"$SSHD_BIN links liblzma AND a bad xz/liblzma was detected above\"\n    else\n      info \"$SSHD_BIN links liblzma (normal on systemd distros). Exposure depends on the liblzma version checked above.\"\n    fi\n  else\n    pass \"$SSHD_BIN does not directly link liblzma\"\n  fi\nfi\n\n[ \"$xz_bad\" = \"0\" ] &amp;&amp; pass \"No vulnerable/backdoored XZ artifacts detected\"\n\n# ===================================================================\n# 2. Trojanized sshd / malicious SSH shared objects\n# ===================================================================\nsection \"2. Trojanized sshd / rogue SSH libraries\"\n\n# 2a. Known-bad library names used by sshd-injector malware families.\nssh_lib_bad=0\nfor badname in libsshd.so libssdh.so; do\n  while IFS= read -r f; do\n    [ -e \"$f\" ] || continue\n    ssh_lib_bad=1\n    fail \"Suspicious library present: $f  (name associated with sshd-injector malware)\"\n  done &lt; &lt;(find /lib /lib64 /usr/lib /usr/lib64 /usr/local/lib /tmp /var/tmp /dev/shm /root -xdev -name \"$badname\" 2&gt;/dev/null)\ndone\n[ \"$ssh_lib_bad\" = \"0\" ] &amp;&amp; pass \"No known sshd-injector library names found\"\n\n# 2b. Libraries that sshd has actually mapped from suspicious locations.\nif [ \"$IS_ROOT\" = \"1\" ]; then\n  mapped_bad=0\n  for pid in $(pgrep -x sshd 2&gt;/dev/null); do\n    if [ -r \"/proc/$pid/maps\" ]; then\n      while IFS= read -r path; do\n        case \"$path\" in\n          /tmp/*|/var/tmp/*|/dev/shm/*|/home/*|*/.* )\n            fail \"sshd (pid $pid) has mapped a library from an unusual path: $path\"\n            mapped_bad=1 ;;\n        esac\n      done &lt; &lt;(awk '$6 ~ /\\.so/ {print $6}' \"/proc/$pid/maps\" 2&gt;/dev/null | sort -u)\n    fi\n  done\n  [ \"$mapped_bad\" = \"0\" ] &amp;&amp; pass \"Running sshd maps no libraries from tmp/shm/home/hidden paths\"\nelse\n  info \"Skipping sshd memory-map check (needs root)\"\nfi\n\n# 2c. sshd binary not where the package manager expects it.\nif [ -e \"$SSHD_BIN\" ]; then\n  case \"$SSHD_BIN\" in\n    /usr/sbin/sshd|/usr/bin/sshd|/sbin/sshd) info \"sshd binary path: $SSHD_BIN\" ;;\n    *) warn \"sshd is running from a non-standard path: $SSHD_BIN\" ;;\n  esac\nfi\n\n# ===================================================================\n# 3. Binary integrity vs. package database\n# ===================================================================\nsection \"3. Package-integrity verification (sshd, curl, sudo, xz, coreutils)\"\n\nWATCH=\"openssh-server openssh openssh-clients curl libcurl sudo xz xz-libs xz-utils coreutils\"\n\nif [ \"$DISTRO_FAMILY\" = \"rhel\" ] &amp;&amp; have rpm; then\n  integrity_bad=0\n  for pkg in $WATCH; do\n    rpm -q \"$pkg\" &gt;/dev/null 2&gt;&amp;1 || continue\n    # rpm -V output: columns of flags, '5' = digest/hash mismatch on a file.\n    out=$(rpm -V \"$pkg\" 2&gt;/dev/null | grep -E '^..5|^missing' )\n    if [ -n \"$out\" ]; then\n      integrity_bad=1\n      fail \"Integrity mismatch in package '$pkg':\"\n      printf '%s\\n' \"$out\" | sed 's/^/        /'\n    fi\n  done\n  [ \"$integrity_bad\" = \"0\" ] &amp;&amp; pass \"rpm -V reports no hash mismatches on watched packages\"\nelif [ \"$DISTRO_FAMILY\" = \"debian\" ]; then\n  if have debsums; then\n    out=$(debsums -c 2&gt;/dev/null | grep -Ei 'ssh|curl|sudo|/xz|coreutils')\n    if [ -n \"$out\" ]; then\n      fail \"debsums reports modified files:\"; printf '%s\\n' \"$out\" | sed 's/^/        /'\n    else\n      pass \"debsums reports no modified files among watched binaries\"\n    fi\n  elif have dpkg; then\n    out=$(dpkg -V 2&gt;/dev/null | grep -vE '/usr/share/(doc|man|locale|info)/' | grep -Ei 'ssh|curl|sudo|/xz|coreutils')\n    if [ -n \"$out\" ]; then\n      warn \"dpkg -V flags changed files (note: dpkg only checks files with stored hashes):\"\n      printf '%s\\n' \"$out\" | sed 's/^/        /'\n    else\n      pass \"dpkg -V reports no relevant binary changes among watched packages\"\n    fi\n    info \"dpkg -V cannot verify packages lacking stored hashes. Install 'debsums' for fuller coverage.\"\n  fi\nelse\n  info \"Unknown package family - skipping automated integrity verification.\"\nfi\n\n# ===================================================================\n# 4. authorized_keys audit\n# ===================================================================\nsection \"4. authorized_keys across all accounts\"\n\nak_issue=0\n# Build list of \"user:home\" from passwd, plus root explicitly.\nwhile IFS=: read -r user _ uid _ _ home _; do\n  [ -d \"$home\" ] || continue\n  for akf in \"$home/.ssh/authorized_keys\" \"$home/.ssh/authorized_keys2\"; do\n    [ -f \"$akf\" ] || continue\n    nkeys=$(grep -cE '^(ssh-|ecdsa-|sk-)' \"$akf\" 2&gt;/dev/null)\n    perms=$(stat -c '%a' \"$akf\" 2&gt;/dev/null)\n    info \"$user ($uid): $nkeys key(s) in $akf  perms=$perms\"\n    # world/group writable authorized_keys is dangerous\n    case \"$perms\" in\n      *[2367]) fail \"  $akf is group/world-writable (perms $perms) - anyone could add a key\"; ak_issue=1 ;;\n    esac\n    # keys with forced commands or odd options can hide backdoors\n    if grep -qE '(command=|no-pty|permitopen|tunnel=)' \"$akf\" 2&gt;/dev/null; then\n      warn \"  $akf contains key options (command=/permitopen/tunnel) - review each line is intentional\"\n    fi\n    # show key comments so a human can eyeball unexpected entries\n    awk '{c=$NF; if (c !~ /^(ssh-|ecdsa-|sk-)/) print \"        key comment: \" c}' \"$akf\" 2&gt;/dev/null\n  done\ndone &lt; /etc/passwd\n[ \"$ak_issue\" = \"0\" ] &amp;&amp; pass \"No insecure authorized_keys permissions detected\"\ninfo \"Eyeball every key comment above. Any key you do not recognize should be treated as hostile.\"\n\n# ===================================================================\n# 4b. PAM authentication modules  (PAM-based SSH/login backdoors)\n# ===================================================================\nsection \"4b. PAM modules and configuration\"\n\n# Resolve the PAM module directory(ies) for this distro+arch, deduped by realpath.\nPAM_DIRS=\"\"\nfor d in /lib/security /lib64/security /usr/lib/security /usr/lib64/security \\\n         /lib/*-linux-gnu/security /usr/lib/*-linux-gnu/security; do\n  [ -d \"$d\" ] || continue\n  rp=$(readlink -f \"$d\" 2&gt;/dev/null || echo \"$d\")\n  case \" $PAM_DIRS \" in *\" $rp \"*) : ;; *) PAM_DIRS=\"$PAM_DIRS $rp\" ;; esac\ndone\n\nif [ -z \"$PAM_DIRS\" ]; then\n  info \"No PAM module directory found at the usual locations - verify manually.\"\nelse\n  for pd in $PAM_DIRS; do info \"PAM module dir: $pd\"; done\n\n  # (a) Recently modified PAM modules - a backdoor is often a freshly written .so\n  recent_pam=$(find $PAM_DIRS -type f -name '*.so' -mtime -30 2&gt;/dev/null)\n  if [ -n \"$recent_pam\" ]; then\n    warn \"PAM modules modified in the last 30 days (expect this only right after a pam package update):\"\n    find $PAM_DIRS -type f -name '*.so' -mtime -30 -exec stat --format='        %y  %n' {} + 2&gt;/dev/null | sort -r\n  else\n    pass \"No PAM modules modified in the last 30 days\"\n  fi\n\n  # (b) PAM modules not owned by ANY package = very strong red flag\n  pam_verify=\"yes\"\n  if { [ \"$DISTRO_FAMILY\" = \"debian\" ] &amp;&amp; have dpkg; } || { [ \"$DISTRO_FAMILY\" = \"rhel\" ] &amp;&amp; have rpm; }; then :; else pam_verify=\"no\"; fi\n  unowned=0\n  if [ \"$pam_verify\" = \"yes\" ]; then\n    while IFS= read -r so; do\n      [ -e \"$so\" ] || continue\n      rp=$(readlink -f \"$so\" 2&gt;/dev/null || echo \"$so\")\n      if [ \"$DISTRO_FAMILY\" = \"debian\" ]; then\n        dpkg -S \"$so\" &gt;/dev/null 2&gt;&amp;1 || dpkg -S \"$rp\" &gt;/dev/null 2&gt;&amp;1 || { fail \"PAM module not owned by any package: $so  &lt;-- investigate as a possible backdoor\"; unowned=1; }\n      else\n        rpm -qf \"$rp\" &gt;/dev/null 2&gt;&amp;1 || { fail \"PAM module not owned by any package: $so  &lt;-- investigate as a possible backdoor\"; unowned=1; }\n      fi\n    done &lt; &lt;(find $PAM_DIRS -type f -name '*.so' 2&gt;/dev/null)\n    [ \"$unowned\" = \"0\" ] &amp;&amp; pass \"Every PAM module belongs to an installed package\"\n  else\n    info \"Cannot verify PAM module package-ownership on this distro family\"\n  fi\n\n  # (c) Group/world-writable PAM modules\n  ww=$(find $PAM_DIRS -type f -perm /022 2&gt;/dev/null)\n  if [ -n \"$ww\" ]; then fail \"Group/world-writable PAM module(s):\"; printf '%s\\n' \"$ww\" | sed 's/^/        /'\n  else pass \"No group/world-writable PAM modules\"; fi\nfi\n\n# (d) Integrity of the PAM packages against the package database\nif [ \"$DISTRO_FAMILY\" = \"debian\" ]; then\n  if have debsums; then\n    out=$(debsums -c libpam-modules libpam-modules-bin libpam0g libpam-runtime 2&gt;/dev/null)\n    if [ -n \"$out\" ]; then fail \"debsums reports modified PAM files:\"; printf '%s\\n' \"$out\" | sed 's/^/        /'\n    else pass \"debsums: PAM packages intact\"; fi\n  elif have dpkg; then\n    out=$(dpkg -V libpam-modules libpam0g 2&gt;/dev/null | grep -vE '/usr/share/(doc|man|locale)/')\n    if [ -n \"$out\" ]; then warn \"dpkg -V flags PAM file changes:\"; printf '%s\\n' \"$out\" | sed 's/^/        /'\n    else pass \"dpkg -V: no PAM changes (limited - install debsums for a full hash check)\"; fi\n  fi\nelif [ \"$DISTRO_FAMILY\" = \"rhel\" ] &amp;&amp; have rpm; then\n  out=$(rpm -V pam 2&gt;/dev/null | grep -E '^..5|^missing')\n  if [ -n \"$out\" ]; then fail \"rpm -V reports PAM file changes:\"; printf '%s\\n' \"$out\" | sed 's/^/        /'\n  else pass \"rpm -V: pam package intact\"; fi\nfi\n\n# (e) Suspicious entries in /etc/pam.d\nif [ -d /etc/pam.d ]; then\n  # pam_permit.so is BENIGN as a 'required' terminator (Debian default). It is a\n  # backdoor only when reached via 'sufficient' or a bracket that returns success.\n  permit=$(grep -rnE '^[[:space:]]*auth[[:space:]]+(sufficient|\\[[^]]*success=[^]]*\\])[[:space:]]+pam_permit\\.so' /etc/pam.d 2&gt;/dev/null)\n  [ -n \"$permit\" ] &amp;&amp; { fail \"auth stack reaches pam_permit.so via sufficient/success control (bypasses authentication):\"; printf '%s\\n' \"$permit\" | sed 's/^/        /'; }\n  pexec=$(grep -rlE 'pam_exec\\.so' /etc/pam.d 2&gt;/dev/null)\n  [ -n \"$pexec\" ] &amp;&amp; { warn \"pam_exec.so present (runs an external program during auth - confirm each use is intended):\"; printf '%s\\n' \"$pexec\" | sed 's/^/        /'; }\n  oddpath=$(grep -rhE '[[:space:]]/(tmp|home|dev|var/tmp)[^ ]*\\.so' /etc/pam.d 2&gt;/dev/null)\n  [ -n \"$oddpath\" ] &amp;&amp; { fail \"PAM config references a module by an unusual absolute path:\"; printf '%s\\n' \"$oddpath\" | sed 's/^/        /'; }\n  wwc=$(find /etc/pam.d -type f -perm /022 2&gt;/dev/null)\n  [ -n \"$wwc\" ] &amp;&amp; { fail \"Group/world-writable PAM config file(s):\"; printf '%s\\n' \"$wwc\" | sed 's/^/        /'; }\n  [ -z \"$permit$pexec$oddpath$wwc\" ] &amp;&amp; pass \"/etc/pam.d shows no obvious backdoor patterns\"\nfi\n\n# ===================================================================\n# 5. sshd_config review\n# ===================================================================\nsection \"5. sshd_config review\"\n\nif [ ! -e \"$SSHD_BIN\" ] &amp;&amp; [ ! -f /etc/ssh/sshd_config ]; then\n  info \"No sshd binary or sshd_config found - this host does not appear to run an SSH server. Skipping sections 5 and 6.\"\nelse\n# Prefer the effective, fully-resolved config if we can get it.\nEFFECTIVE=\"\"\nif [ \"$IS_ROOT\" = \"1\" ] &amp;&amp; \"$SSHD_BIN\" -T &gt;/dev/null 2&gt;&amp;1; then\n  EFFECTIVE=$(\"$SSHD_BIN\" -T 2&gt;/dev/null)\nfi\n\ngetopt_eff() { printf '%s\\n' \"$EFFECTIVE\" | grep -i \"^$1 \" | awk '{print $2}'; }\n\nif [ -n \"$EFFECTIVE\" ]; then\n  pra=$(getopt_eff permitrootlogin)\n  pwa=$(getopt_eff passwordauthentication)\n  pue=$(getopt_eff permituserenvironment)\n  akf=$(printf '%s\\n' \"$EFFECTIVE\" | grep -i '^authorizedkeysfile ' | cut -d' ' -f2-)\n  fcmd=$(getopt_eff forcecommand)\n\n  case \"$pra\" in\n    yes) warn \"PermitRootLogin yes - direct root login over SSH is enabled\" ;;\n    *)   info \"PermitRootLogin = ${pra:-unset}\" ;;\n  esac\n  case \"$pwa\" in\n    yes) info \"PasswordAuthentication yes (key-only auth is safer where feasible)\" ;;\n    no)  pass \"PasswordAuthentication no\" ;;\n  esac\n  case \"$pue\" in\n    yes) warn \"PermitUserEnvironment yes - lets users set environment via keys/files; can aid persistence/injection\" ;;\n    *)   pass \"PermitUserEnvironment = ${pue:-no}\" ;;\n  esac\n  case \"$akf\" in\n    \".ssh/authorized_keys .ssh/authorized_keys2\"|\".ssh/authorized_keys\") info \"AuthorizedKeysFile = $akf (default)\" ;;\n    *) warn \"AuthorizedKeysFile is non-default: '$akf' - confirm these paths are expected (a rogue path can hide keys)\" ;;\n  esac\n  [ -n \"$fcmd\" ] &amp;&amp; [ \"$fcmd\" != \"none\" ] &amp;&amp; warn \"Global ForceCommand set: $fcmd - confirm this is intended\"\n\n  # Configured Port(s) vs. the port you expect to be running on\n  cfg_ports=$(printf '%s\\n' \"$EFFECTIVE\" | awk '/^port /{print $2}')\n  if printf '%s\\n' \"$cfg_ports\" | grep -qx \"$SSH_PORT\"; then\n    pass \"sshd_config is set to your expected port ${SSH_PORT}\"\n  else\n    warn \"sshd_config does NOT list expected port ${SSH_PORT}; configured port(s): $(printf '%s ' $cfg_ports)\"\n  fi\n  if [ \"$SSH_PORT\" != \"22\" ] &amp;&amp; printf '%s\\n' \"$cfg_ports\" | grep -qx 22; then\n    warn \"sshd is configured for port 22 even though you expected ${SSH_PORT} - possible config reversion or tampering\"\n  fi\n\n  # AllowUsers / AllowGroups can contain an attacker's \"magic\" account\n  printf '%s\\n' \"$EFFECTIVE\" | grep -iE '^(allowusers|allowgroups|denyusers) ' | while read -r line; do\n    info \"Access rule: $line  (verify every listed user/group)\"\n  done\nelse\n  warn \"Could not get effective config via 'sshd -T' (needs root). Falling back to reading /etc/ssh/sshd_config.\"\n  if [ -f /etc/ssh/sshd_config ]; then\n    grep -iE '^\\s*(PermitRootLogin|PasswordAuthentication|PermitUserEnvironment|AuthorizedKeysFile|ForceCommand|AllowUsers|AllowGroups|Subsystem)\\b' \\\n      /etc/ssh/sshd_config | sed 's/^/        /'\n  fi\nfi\n\n# Drop-in configs and recently modified ssh config files\nif [ -d /etc/ssh ]; then\n  recent=$(find /etc/ssh -type f -mtime -30 2&gt;/dev/null)\n  [ -n \"$recent\" ] &amp;&amp; warn \"SSH config files modified in the last 30 days:\" &amp;&amp; printf '%s\\n' \"$recent\" | sed 's/^/        /'\nfi\n\n# ===================================================================\n# 6. Running sshd / listener anomalies\n# ===================================================================\nsection \"6. Running sshd and listeners (expected SSH port: ${SSH_PORT})\"\n\nif have ss; then\n  listeners=$(ss -tlnp 2&gt;/dev/null | grep -i sshd)\n  if [ -n \"$listeners\" ]; then\n    info \"sshd listeners:\"; printf '%s\\n' \"$listeners\" | sed 's/^/        /'\n    if printf '%s\\n' \"$listeners\" | grep -qE \":${SSH_PORT}\\b\"; then\n      pass \"sshd is listening on the expected port ${SSH_PORT}\"\n    else\n      warn \"sshd is NOT listening on the expected port ${SSH_PORT} - verify the daemon and its config\"\n    fi\n    # any sshd listener on a port OTHER than the one you expect\n    other=$(printf '%s\\n' \"$listeners\" | grep -vE \":${SSH_PORT}\\b\")\n    if [ -n \"$other\" ]; then\n      warn \"sshd is also listening on (an) unexpected port(s) - could be a rogue or leftover daemon:\"\n      printf '%s\\n' \"$other\" | sed 's/^/        /'\n    fi\n  else\n    info \"No sshd listener seen via ss (sshd may be socket-activated or not running).\"\n  fi\n  # Active connections on the SSH port (who is connected right now) - lsof -i :PORT equivalent\n  est=$(ss -tnp state established \"( sport = :${SSH_PORT} or dport = :${SSH_PORT} )\" 2&gt;/dev/null)\n  if [ -n \"$est\" ]; then\n    info \"Established connections on port ${SSH_PORT}:\"; printf '%s\\n' \"$est\" | sed 's/^/        /'\n  else\n    info \"No established connections on port ${SSH_PORT} right now.\"\n  fi\nfi\n\n# sshd with a parent that is not init/systemd is unusual for the master daemon\nif have ps; then\n  ps -eo pid,ppid,comm 2&gt;/dev/null | awk '$3==\"sshd\"{print}' | while read -r pid ppid comm; do\n    pcomm=$(ps -o comm= -p \"$ppid\" 2&gt;/dev/null)\n    case \"$pcomm\" in\n      systemd|init|sshd|\"\") ;;  # normal\n      *) warn \"sshd pid $pid has unexpected parent '$pcomm' (ppid $ppid) - investigate\" ;;\n    esac\n  done\nfi\n\nfi  # end \"host runs SSH server\" guard\n\n# ===================================================================\n# 7. Velvet Ant / \"Operation Highland\" IOC hunt  (Sygnia, Jun 2026)\n#    Post-intrusion backdooring of PAM + OpenSSH by a China-nexus actor.\n#    Indicators below are drawn from Sygnia's published report. Any of the\n#    artifact files is a HIGH-CONFIDENCE compromise signal. NOTE: this is a\n#    single-vendor IOC set for one campaign - a clean result is not proof of\n#    absence; binary integrity comparison (sections 3/4b) is the broader net.\n# ===================================================================\nsection \"7. Velvet Ant / Operation Highland IOC hunt\"\n\nva_hits=0\n\n# 7a. Known malicious artifact files/dirs (credential logs, keylogs, tooling)\nfor p in \\\n  /usr/sbin/.ssh.log \\\n  /usr/sbin/auditdb \\\n  /usr/share/man9 \\\n  /usr/share/man9/ph \\\n  /usr/share/man9/ph/.ph.man \\\n  /usr/lib/eth-scsi \\\n  /usr/lib/eth-scsi/libethscsi.so \\\n  /var/lib/sam ; do\n  if [ -e \"$p\" ]; then fail \"Velvet Ant artifact present: $p\"; va_hits=1; fi\ndone\nif ls /var/lib/sam/sam_* &gt;/dev/null 2&gt;&amp;1; then\n  fail \"Velvet Ant keylog files present: /var/lib/sam/sam_*\"; va_hits=1\nfi\n\n# 7b. Trojanized pam_unix.so: scan the module for dev-environment RPATH\n#     leftovers (and the backdoor password) a legitimate module never has.\nwhile IFS= read -r so; do\n  [ -e \"$so\" ] || continue\n  if bin_has_fixed 'Desktop/Linux-PAM' \"$so\" || bin_has_fixed '/c/src/Linux-PAM' \"$so\" || bin_has_fixed 'Pamauth@123456' \"$so\"; then\n    fail \"pam_unix.so shows Velvet Ant backdoor indicators: $so\"; va_hits=1\n  fi\ndone &lt; &lt;(find /lib /lib64 /usr/lib /usr/lib64 -xdev -name 'pam_unix.so' 2&gt;/dev/null)\n\n# 7c. Trojanized OpenSSH binaries: scan ssh/sshd/scp/sftp/ssh-keygen for the\n#     artifact paths a backdoored binary references internally.\nfor b in ssh sshd scp sftp ssh-keygen; do\n  seen=\"\"\n  for cand in \"$(command -v \"$b\" 2&gt;/dev/null)\" \"/usr/bin/$b\" \"/usr/sbin/$b\" \"/bin/$b\" \"/sbin/$b\"; do\n    [ -n \"$cand\" ] &amp;&amp; [ -e \"$cand\" ] || continue\n    rp=$(readlink -f \"$cand\" 2&gt;/dev/null || echo \"$cand\")\n    case \" $seen \" in *\" $rp \"*) continue ;; esac   # skip symlink duplicates\n    seen=\"$seen $rp\"\n    if bin_has_fixed '/usr/share/man9/ph' \"$cand\" || bin_has_fixed 'eth-scsi/libethscsi' \"$cand\" || bin_has_fixed '/var/lib/sam/sam_' \"$cand\"; then\n      fail \"OpenSSH binary references a Velvet Ant artifact path: $rp\"; va_hits=1\n    fi\n  done\ndone\n\n# 7d. Process masquerades via argv[0] manipulation. Genuine kernel threads\n#     have NO backing executable, so a [khubd]/[kauditd] WITH a real exe is an\n#     impostor (this avoids flagging the real kthreads on older kernels).\nif [ \"$IS_ROOT\" = \"1\" ]; then\n  for pid in $(ls /proc 2&gt;/dev/null | grep -E '^[0-9]+$'); do\n    [ -r \"/proc/$pid/cmdline\" ] || continue\n    exe=$(readlink \"/proc/$pid/exe\" 2&gt;/dev/null)   # empty for kernel threads\n    [ -n \"$exe\" ] || continue                       # skip genuine kthreads\n    cmd=$(tr '\\0' ' ' &lt; \"/proc/$pid/cmdline\" 2&gt;/dev/null)\n    comm=$(cat \"/proc/$pid/comm\" 2&gt;/dev/null)\n    case \"$comm $cmd\" in\n      *'[khubd]'*|*'[kauditd]'*)\n        fail \"PID $pid masquerades as a kernel thread but has a real exe ($exe): '$cmd'\"; va_hits=1 ;;\n    esac\n    case \"${exe##*/}\" in\n      auditdb) fail \"PID $pid runs 'auditdb' (Velvet Ant GS-Netcat tool): $exe\"; va_hits=1 ;;\n    esac\n    case \"$cmd\" in\n      smbd*) [ \"${exe##*/}\" != \"smbd\" ] &amp;&amp; { fail \"PID $pid presents as 'smbd' but its exe is ${exe##*/} ($exe) - possible disguised proxy\"; va_hits=1; } ;;\n    esac\n  done\nelse\n  info \"Skipping process-masquerade checks (need root to read /proc/PID/exe)\"\nfi\n\n# 7e. Persistence / C2 references in startup files\nfor d in /lib/systemd/system /etc/systemd/system /etc/init.d; do\n  [ -d \"$d\" ] || continue\n  hit=$(grep -rilE 'auditdb|gs\\.thc\\.org|\\.thc\\.org' \"$d\" 2&gt;/dev/null)\n  if [ -n \"$hit\" ]; then\n    fail \"Startup file references a Velvet Ant tool/C2 domain:\"; printf '%s\\n' \"$hit\" | sed 's/^/        /'; va_hits=1\n  fi\ndone\n\nif [ \"$va_hits\" = \"0\" ]; then\n  pass \"No Velvet Ant / Operation Highland indicators found\"\nelse\n  fail \"Velvet Ant indicators detected - treat this host as compromised. Do NOT rotate credentials until the backdoor is removed (resets get re-harvested via the same modules).\"\nfi\n\n# ===================================================================\n# Summary\n# ===================================================================\nsection \"Summary\"\nprintf '  FAIL findings: %s%s%s\\n' \"$([ \"$FAIL_COUNT\" -gt 0 ] &amp;&amp; echo \"$R\" || echo \"$G\")\" \"$FAIL_COUNT\" \"$N\"\nprintf '  WARN findings: %s%s%s\\n' \"$([ \"$WARN_COUNT\" -gt 0 ] &amp;&amp; echo \"$Y\" || echo \"$G\")\" \"$WARN_COUNT\" \"$N\"\necho\nif [ \"$FAIL_COUNT\" -gt 0 ]; then\n  echo \"  One or more FAIL findings indicate possible compromise or a vulnerable component.\"\n  echo \"  Treat the host as suspect: do NOT trust its own tooling to clean itself.\"\n  echo \"  See the remediation guidance accompanying this script.\"\n  exit 2\nelse\n  echo \"  No hard FAIL findings. Review every WARN/INFO line above by hand -\"\n  echo \"  this script narrows the search, it does not certify the host as clean.\"\n  exit 0\nfi", "creation_timestamp": "2026-06-15T04:24:28.000000Z"}