diff --git a/Makefile b/Makefile index a15e0ec..ca1e6eb 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,10 @@ PREFIX = ~/bin -build: +build: bash -c '. config.env && \ mkdir -p scripts && \ cp src/ssh-backdoor scripts/ssh-backdoor && \ - cp src/ssh-backdoor-connect-local scripts/ssh-backdoor-connect-local && \ cp src/ssh-backdoor-connect scripts/ssh-backdoor-connect && \ sed \ -e "s/{{BACKDOORHOST}}/$${BACKDOORHOST}/g" \ @@ -15,19 +14,26 @@ build: -e "s/{{BACKDOORHOST}}/$${BACKDOORHOST}/g" \ -e "s/{{BACKDOORPORT}}/$${BACKDOORPORT}/g" \ -e "s,{{BACKDOORURL}},$${BACKDOORURL},g" \ - -e "s,{{BACKDOORURLPATH}},$${BACKDOORURLPATH},g" \ src/ssh-backdoor-open > scripts/ssh-backdoor-open && \ chmod +x scripts/* && \ true' install: build bash -c '. config.env && \ - cp -av scripts/ssh-backdoor-open "$${BACKDOORURLPATH}" && \ + if [[ -n "$${BACKDOORURLPATH}" ]]; then cp -av scripts/ssh-backdoor-open "$${BACKDOORURLPATH}"; fi && \ + if [[ -n "$${CLIENTURLPATH}" ]]; then cp -av scripts/ssh-backdoor-open "$${BACKDOORURLPATH}"; fi && \ mkdir -p ${PREFIX} && \ cp -av scripts/ssh-backdoor ${PREFIX}/ssh-backdoor && \ - cp -av scripts/ssh-backdoor-connect-local ${PREFIX}/ssh-backdoor-connect-local && \ cp -av scripts/ssh-backdoor-connect ${PREFIX}/ssh-backdoor-connect && \ true' clean: rm -rf scripts + +uninstall: + bash -c '\ + rm -fv ${PREFIX}/ssh-backdoor && \ + rm -fv ${PREFIX}/ssh-backdoor-connect && \ + if [[ -f "$${BACKDOORURLPATH}" ]]; then rm -fv $${BACKDOORURLPATH}; fi && \ + true + ' diff --git a/README.md b/README.md new file mode 100644 index 0000000..31c1de3 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# What? + +This is a backdoor for servers in NAT networks. + +One server on the public internet acts as a backdoor server, where other +computers connect, creating reverse tunnels to their ports 22. + +The program manages random ports that are then available at the server localhost. + +# install + +configure config.env and run `make install` + +- ssh-backdoor will go to ~/bin/ssh-backdoor +- Servers behind NAT should access the ssh-backdoor-open script over a http(s) server +- Clients that want to connect to the backdoored servers should be able to access + ssh-backdoor-connect script +- use the 'ad-hoc-www-server' if no proper http server available + +# running + +- NATted servers must copy their `id_rsa.pub` to backdoor server's `authorized_keys` +- Clients should do the same, for ease of access +- Have NATted servers run `ssh-backdoor-open` at boot (in cron or otherwise) +- Use the ssh-backdoor command directly, or ssh-backdoor-connect from client machines + to connect + + +# utils + +- `ssh-list` lists all processes with ssh and backdoor +- `ssh-kill-all` kills all ssh connections (except the current parent), and all python processes + diff --git a/ad-hoc-www-server b/ad-hoc-www-server new file mode 100755 index 0000000..91461c7 --- /dev/null +++ b/ad-hoc-www-server @@ -0,0 +1,8 @@ +#!/bin/bash + +cd $( dirname $( readlink -f $0 ) ) +set -e +. config.env + +cd "$ADHOCSERVERROOT" +python3 -m http.server $ADHOCSERVERPORT diff --git a/config.env.example b/config.env.example new file mode 100644 index 0000000..f43efdb --- /dev/null +++ b/config.env.example @@ -0,0 +1,13 @@ +# Host name of the SSH server (and user name) +BACKDOORHOST=admin@my.server.org +# Port of the SSH server +BACKDOORPORT=22 +# Where to download the ssh-backdoor-open and ssh-backdoor-connect script (may be left empty) +BACKDOORURL=https://my.server.org/pub/ssh-backdoor-open +CLIENTURL=https://my.server.org/pub/ssh-backdoor-connect +# Location of the dowloadable script at the server (may be empty) +BACKDOORURLPATH=/home/admin/www/pub/ssh-backdoor-open +CLIENTURLPATH=/home/admin/www/pub/ssh-backdoor-connect +# Ad-hoc web server port (if used) +ADHOCSERVERPORT=8080 +ADHOCSERVERROOT=/home/admin/www/pub/ diff --git a/src/ssh-backdoor b/src/ssh-backdoor index f2c6493..01c4329 100755 --- a/src/ssh-backdoor +++ b/src/ssh-backdoor @@ -6,6 +6,7 @@ import psutil import random import sqlite3 import time, os, sys +import subprocess def setup_options(): ''' Setup the command line options ''' @@ -20,44 +21,6 @@ def setup_options(): type = str, help = "Sqlite file for database: %(default)s" ) - # TODO --connect (replace ssh-local-connect) - parser.add_argument( - "--clear", - action = 'store_true', - dest = 'clear', - default = False, - help = "Clear the database of everything, first" - ) - parser.add_argument( - "--kill", - action = 'store', - dest = 'kill', - default = None, - type = str, - help="Kill processes of given ID" - ) - parser.add_argument( - "--list","-l", - action = 'store_true', - dest = 'list', - default = False, - help = "List ports" - ) - parser.add_argument( - "--list-names", - action = 'store_true', - dest = 'list_names', - default = False, - help = "List alive host names only" - ) - parser.add_argument( - "--query", - action = 'store', - dest = 'query', - default = None, - type = str, - help="Query port for an id" - ) parser.add_argument( "--quiet", "-q", action = 'store_true', @@ -65,22 +28,78 @@ def setup_options(): default = False, help = "Quiet operation: %(default)s" ) - parser.add_argument( - "--wait", "-w", - action = 'store_true', - dest = 'wait', - default = False, - help = "Enter infinite loop" + + subparsers = parser.add_subparsers( + help = "Help for subcommand", + dest = 'command' ) - parser.add_argument( + parser_clear = subparsers.add_parser( + 'clear', + help = "Clear the database of everything" + ) + + parser_connect = subparsers.add_parser( + 'connect', + help = "Connect to a backdoor" + ) + parser_connect.add_argument( + dest = "id", + help = "Backdoor ID to connect to" + ) + + parser_kill = subparsers.add_parser( + 'kill', + help = "Kill a backdoor" + ) + parser_kill.add_argument( + dest = "id", + help = "Backdoor ID to kill" + ) + + parser_list = subparsers.add_parser( + "list", + help = "List ports" + ) + + parser_list_names = subparsers.add_parser( + "list-names", + help = "List alive host names only" + ) + + parser_query = subparsers.add_parser( + "query", + help="Query port for an id" + ) + parser_query.add_argument( + dest = "id", + help = "Backdoor ID to query" + ) + + parser_open = subparsers.add_parser( + "open", + help = "Open a new port. Returns the port number" + ) + parser_open.add_argument( action = 'store', dest = 'id', - default = None , type = str, - nargs = '?', - help="Id name for port" + help="Id name for backdoor" + ) + + parser_wait = subparsers.add_parser( + "keep", + help = "Keep updating alive status (for 1 hour)" + ) + parser_wait.add_argument( + action = 'store', + dest = 'id', + type = str, + help="Id name for backdoor" ) options=parser.parse_args() + if options.command == None: + parser.print_help(sys.stderr) + sys.exit(1) return options @@ -254,52 +273,83 @@ class DataBase: if len(result) == 0: return port + def connect_backdoor(self, id): + self.db = self.conn.cursor() + self.db.execute("SELECT port,pid FROM ports WHERE id = ?", (id,)) + result = self.db.fetchall() + if len(result) == 0: + eprint("No such ID") + return + port = result[0][0] + pid = result[0][1] + if not self.is_running(pid): + eprint("Backdoor not open") + return + + user = id.split("@")[0] + os.execlp( + "ssh", + "ssh", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-tt", "-XYC", + "-p", str(port), + "%s@localhost"%( user, ) + ) + + if __name__ == "__main__": opts=setup_options() db = DataBase(opts.DB) start_time = time.time() - if opts.clear: + if opts.command == "clear": db.clear() - if opts.list_names: + if opts.command == "list-names": for row in db.list(): - if row[5]: + if row[-1]: print(row[0]) sys.exit(0) - if opts.list: + if opts.command == "list": print(tabulate( db.list(), headers = ['Id','Port','Host','Age','PID','Die','Alive'] )) - if opts.query != None: - port = db.get_port(opts.query) + if opts.command == "query": + port = db.get_port(opts.id) if port == None: sys.exit(1) print(port) sys.exit(0) - if opts.kill: - db.set_to_die(opts.kill) + if opts.command == "kill": + db.set_to_die(opts.id) sys.exit(0) - if opts.id != None: + if opts.command == "connect": + db.connect_backdoor(opts.id) + + if opts.command == "open": print(db.update(opts.id)) - if opts.wait: - ewrite( - " Connected\r" - ) - while True: - if time.time() - start_time > 3600: - sys.exit(0) - db.check_die() - db.update(opts.id) - for i in range(10): - time.sleep(10 * random.random()) - ewrite( - " " + - time.strftime("%c") + - "\r" - ) + + if opts.command == "keep": + print(db.update(opts.id)) + ewrite( + " Connected\r" + ) + while True: + # Run 1 hour, then exit to reconnect + if time.time() - start_time > 3600: + sys.exit(0) + db.check_die() + db.update(opts.id) + for i in range(10): + time.sleep(10 * random.random()) + ewrite( + " " + + time.strftime("%c") + + "\r" + ) diff --git a/src/ssh-backdoor-connect b/src/ssh-backdoor-connect index 4eed169..3e85e6c 100755 --- a/src/ssh-backdoor-connect +++ b/src/ssh-backdoor-connect @@ -15,8 +15,8 @@ BACKDOORHOST={{BACKDOORHOST}} BACKDOORPORT={{BACKDOORPORT}} _list() { echo 'usage: [-auto] user@host' - _ssh bin/ssh-backdoor -l - ids=( $( _ssh bin/ssh-backdoor --list-names ) ) + _ssh bin/ssh-backdoor list + ids=( $( _ssh bin/ssh-backdoor list-names ) ) if [[ ${#ids[@]} -eq 0 ]]; then exit fi @@ -33,6 +33,8 @@ _ssh() { -o UserKnownHostsFile=/dev/null \ -o StrictHostKeyChecking=no \ -o ConnectTimeout=10 \ + -o ServerAliveInterval=15 \ + -o ServerAliveCountMax=3 \ -p ${BACKDOORPORT} ${BACKDOORHOST} \ "$@" #~ -o "ExitOnForwardFailure yes" \ @@ -48,17 +50,15 @@ for (( i=1; i<=$#; i++ )); do host="${!i}" done -port=$( _ssh bin/ssh-backdoor --query "$host" ) +port=$( _ssh bin/ssh-backdoor query "$host" ) [[ $? -ne 0 ]] && { echo No such id _list } -login=(${host//@/ }) while :; do _ssh \ -tt \ - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ - -tt -XYC -p $port ${login[0]}@localhost + bin/ssh-backdoor connect "$host" [[ $? -eq 0 ]] && exit [[ "$auto_reconnect" -ne 1 ]] && { exit $?; } echo Auto-reconnect diff --git a/src/ssh-backdoor-connect-local b/src/ssh-backdoor-connect-local deleted file mode 100755 index ef1a98f..0000000 --- a/src/ssh-backdoor-connect-local +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -_list() { - echo usage: user@host - ssh-backdoor -l - exit -} - -[[ -z "$1" ]] && _list - -port=$( ssh-backdoor --query "$1" ) -[[ $? -ne 0 ]] && { - echo No such id - _list -} -login=(${1//@/ }) -set -x -exec ssh \ - -o UserKnownHostsFile=/dev/null \ - -o StrictHostKeyChecking=no \ - -tt -XYC -p $port ${login[0]}@localhost diff --git a/src/ssh-backdoor-open b/src/ssh-backdoor-open index dad0dc7..f22baef 100755 --- a/src/ssh-backdoor-open +++ b/src/ssh-backdoor-open @@ -31,13 +31,13 @@ USER=$( id -u -n ) echo use of ssh-add is encouraged ( sleep 3; printf "%d\r" $SECONDS ) & while true; do - port=$( _ssh bin/ssh-backdoor $USER@$HOSTNAME ) + port=$( _ssh bin/ssh-backdoor open $USER@$HOSTNAME ) [[ -z "$port" ]] && { sleep 2; continue; } echo "$port port assigned" #~ _ssh pkill -a -f $USER@$HOSTNAME _ssh \ -R $port:localhost:22 \ - bin/ssh-backdoor -w $USER@$HOSTNAME || { + bin/ssh-backdoor keep $USER@$HOSTNAME || { true # failed #_ssh bin/ssh-kill $USER@$HOSTNAME $port || true diff --git a/src/ssh-list b/src/ssh-list deleted file mode 100755 index 3aa3888..0000000 --- a/src/ssh-list +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -ps axuw | grep -e [s]sh -e [p]ython3 - diff --git a/src/qolop b/utils/qolop similarity index 100% rename from src/qolop rename to utils/qolop diff --git a/src/ssh-kill-all b/utils/ssh-kill-all similarity index 100% rename from src/ssh-kill-all rename to utils/ssh-kill-all diff --git a/utils/ssh-list b/utils/ssh-list new file mode 100755 index 0000000..842d451 --- /dev/null +++ b/utils/ssh-list @@ -0,0 +1,4 @@ +#!/bin/bash + +ps axuw | grep -e [s]sh -e [b]ackdoor +