Running Scripts over SSH

back · home · projects · posted 2021-02-15 · run plain bash over ssh, really fast

tldr; tool to run shell scripts over ssh quickly, supports IP ranges and brute forcing credentials. source


People learning to 'red team'1 typically want to automate the decimation of blue team systems as fast as possible, since time is critical. The goal is to run their scripts on many remote machines at once, by abusing default credentials.

One of my friends tried to whip up a script to do this, and it looked like this:

# warning: incorrect code
IPs=$(cat IPs.txt)
for ip in $IPs; do

    # SSH to target
    ssh root@$ip

    # Run persistence script
    echo "I'm persisting!"


This kind of makes sense if your mental model of the shell is a single stream of input, and sshing to something "switches" that stream. Of course, the expectation here is that it will prompt them for ssh credentials, then run the subsequent commands on the remote host, but in actuality, it initiates an ssh session, and when that session quits/dies/fails, it runs their persistence setup on their local machine-- which even if using a VM, is not great.

In any case, a quick search will show them that it's not too hard to get close to what they want (post):

IPs=$(cat IPs.txt)

for ip in $IPs; do
    ssh root@$ip 'bash -s' <

And this works! But does it work... enough?

Robustness and Features

Besides being slow, there's still the complexity of specifying valid machines, entering credentials, and multiple script files-- it's not useful or robust enough to be a good red teaming tool. For example, what if you want to target an IP range of 100 boxes? What if you want to brute multiple usernames and passwords automatically (an unlikely use case for typical automation)? And so on.

There are also a lot of interesting challenges that arise, since SSH is essentially just a TCP tunnel to a shell (at least, when you execute more than one command). Most of these issues arise from not having a separate control and data channel; for example, how do you know when a command finishes executing, and how do you get the status code? Well, it varies by shell, but in bash, you (of course) know your command is finished when it prints your prompt and/or accepts new input. Something like this:

root@blue:~/ ls
bin   etc    lib    mnt   swapfile  tmp   var
boot  dev    home   root  sbin      srv   usr
root@blue:~/ echo $?

But how can you tell programmatically? You just have stdout and stderr. You can send commands down the pipe and wait for a number, but that will fail if any other commands output numbers. You could just wait for some time and assume it's running, but that's decidedly not reliable.

The solution I went with is to echo a random string, like echo "8rq532vindjl", then wait for that to come back on stdout. Coordinate does this (assuming the --no-validate flag is not set) to ensure the shell is ready, and to check when a script is done running. And it works pretty alright! Unfortunately, this does add a default dependency of echo on remote systems, which is not always available.

Existing Solutions

Of course, there are already solutions for this type of challenge, namely Ansible or Chef or Vagrant or whatever, the RC files you can use with Metasploit, or even pdsh (but that only does single commands). But, I wanted to use exclusively POB (Plain Ol' Bash), wanted strong concurrency features, and didn't want to rely on a heavy framework.

As a side note, I'm using this for CCDC as well (a defensive, blue team event). If something breaks, we can just clone the scripts on the local machine and run them there, since, you know, they're just bash.

Is This Malware?

I am strongly of the opinion that it makes the world a better place to publicly release "malicious" things, so that people are empowered to understand what's possible with the systems they rely on. For every one actual evil use of a given piece of "malicious" software, I imagine a hundred people will use a tool to learn about it or practice, with even more people vaguely aware of it, which is overwhelmingly a net positive.

In any case, this project doesn't actually provide any malicious code (unless you consider slowly brute forcing SSH logins malicious). To find malicious code you'll have to move one repo over to STARFOX.


Most of the problems listed above can be solved with bash (all of them, actually), but given the complexity and type of issues present, I figured it would be better and more fun to use a different language (I chose go, because I like it). Thus, coordinate was slammed together in a couple days, and slowly improved afterwards. For documentation, please see the project page:, and run it to see usage (./coordinate).

  1. (Warning: pedantry) In this context, I'm using "red teaming" to mean "trying to maintain persistence in vulnerable blue team machines over the course of a blue team competition". You can often log in with default credentials at minute zero. At least in my mind, this differs from "pentesting", as pentesting is much less confrontational, and you spend almost all your time trying to get in rather than trying to be as fast as possible in setting up persistence mechanisms. ↩︎
If you have any questions or feedback, please email my public inbox at ~sourque/