Sunday, September 27, 2026

Tunneling SOCKS5 through Cloudflare R2 with R2Socks

Tunneling SOCKS5 through Cloudflare R2 with R2Socks

R2Socks relays a local SOCKS5 proxy over encrypted objects in a Cloudflare R2 bucket, giving authorized red teams a dependency-free covert-channel option for lab and engagement use.

Toolivancabrera02/R2Socks — SOCKS5 proxy tunneled through Cloudflare R2 object storage with Python and C++ agents
CategoryCovert-channel tunneling / proxy tooling
Primary UsePivoting TCP traffic out of a restricted network during authorized engagements by relaying SOCKS5 CONNECT requests through an R2 bucket instead of a direct C2 channel
Safe UseIntended for authorized penetration tests, isolated labs, and egress-filtering research on systems you own or have written permission to test
Telemetry NoteGenerates R2 API calls against your own bucket (visible in Cloudflare audit and access logs) and leaves timestamped objects if not cleaned; defenders can watch for anomalous object-storage polling patterns on corporate credentials

R2Socks is a fascinating exercise in abusing trusted infrastructure as a transport. Instead of opening a direct connection between an operator and a target network, it converts every SOCKS5 CONNECT request into serialized binary packets stored as objects in a Cloudflare R2 bucket, with an agent inside the target network polling that same bucket to pick up work and relay real TCP connections. The repo (ivancabrera02/R2Socks, Python, 31 stars at review time) ships three components: a Python proxy and agent in a single r2socks.py file, plus a standalone Windows C++ agent r2agent.cpp that is protocol-compatible and, notably, has zero external dependencies.

The architecture diagram in the README says it all: [Client App] ◄─SOCKS5─► [Proxy] ◄─── R2 Bucket ───► [Agent] ◄─TCP─► [Target]. The proxy runs a local SOCKS5 server on the operator's machine (default listen 127.0.0.1:1080), so anything that speaks SOCKS — browsers, proxychains, internal tooling — can use it transparently. On the far side, the agent sees only outbound HTTPS-flavored R2 API traffic, which is the whole point: object storage egress is rarely blocked because it looks like legitimate cloud SDK behavior.

The protocol is a compact custom framing. Each packet is CMD (1B) | ConnectionID (16B) | Len (4B big-endian) | Payload, with five commands: NEW (0x01) carrying ATYP + address + port, ACK (0x02) with a one-byte status, DATA (0x03) with raw TCP bytes up to 1MB per packet, CLOSE (0x04), and PING (0x05) for keepalive. Multiple packets are batched into a single R2 object up to 4MB to reduce API call volume — a pragmatic optimization, since per-request R2 pricing and rate limits would otherwise bite quickly under chatty traffic.

Ordering and lifecycle are handled through object naming: objects carry microsecond timestamps in their keys so proxy and agent can reconstruct packet order despite eventual-consistency quirks, and objects are deleted immediately after consumption. That deletion matters operationally — a channel that finishes clean leaves little residue in the bucket — and there is an explicit clean subcommand (python r2socks.py clean -b my-bucket -a <account_id> -c <channel_id>) to purge any leftovers from a channel. From a blue-team perspective, though, the audit trail is not zero: Cloudflare logs the API activity on the account, and the polling cadence itself is a signature.

Polling cadence is handled by an adaptive interval that is worth calling out as thoughtful engineering. Under active traffic, both sides poll every 50ms for low latency; when idle, the interval grows by 1.3x per cycle up to a 2-second ceiling, and when traffic resumes it shrinks by 0.5x per cycle back down to 50ms. Stats are logged every 30 seconds covering active connections, objects transferred, and bytes moved. The trade-off is explicit in the design: responsiveness versus R2 API call volume, and the exponential decay on both sides is a sensible compromise.

Encryption is optional but well-implemented for a small project. Passing -p/--password to both proxy and agent enables AES-256-GCM over all R2 payloads, with the key derived via PBKDF2-SHA256 at 600,000 iterations. Without it, packets traverse the bucket in plaintext framing — still TLS-protected in transit by the storage API itself, but readable by anything with bucket credentials. Given that the bucket contents are literally your relayed TCP streams, enabling the password on both ends is the obvious configuration choice; the README's note that both sides must share the same password is the only real footgun.

Setup is straightforward for anyone who has touched boto3. You create an R2 bucket, mint an API token with Object Read & Write scope, and note the account ID from the dashboard URL. The proxy is started with python r2socks.py proxy -b my-bucket -a <account_id> --access-key <key> --secret-key <key> after pip install boto3; it generates a channel ID and prints the matching agent command. The Python agent takes the same flags plus -c <channel_id>, and the C++ agent mirrors them as r2agent.exe -b ... -a ... -c ... -k ... -s .... Authentication on the SOCKS side is available via --socks-user/--socks-pass if you want the local listener locked down.

The C++ agent deserves its own paragraph because it is the most operationally interesting artifact in the repo. Built with a single cl /std:c++17 /EHsc /O2 r2agent.cpp /link ws2_32.lib winhttp.lib bcrypt.lib invocation from a Visual Studio developer prompt, it links only Windows-native libraries — WinHTTP for the storage API, bcrypt for AES, Winsock2 for TCP — meaning no runtime DLLs, no Python interpreter, no redistributable baggage on the target host. For defenders writing detections, that profile matters: a single statically-behaving EXE making periodic WinHTTP calls to object-storage endpoints is a pattern worth hunting in process and network telemetry.

The troubleshooting section is honest and reveals real-world friction: SignatureDoesNotMatch and RequestTimeTooSkewed both trace back to credential errors or system clock drift (the S3-compatible signing used by R2 is timestamp-sensitive), with w32tm /resync /force suggested on Windows. AccessDenied points to insufficient token scope. These are mundane details, but they signal the author actually ran the tool rather than publishing an idea.

Where does this fit in an authorized workflow? It is a pivot and egress tool for engagements where direct reverse shells or VPN tunnels are blocked but cloud storage APIs are reachable — a category alongside DNS and ICMP tunneling that exists precisely because allow-listing cloud endpoints is so common. It is equally valuable defensively: standing it up in a lab and capturing its R2 traffic gives detection engineers concrete, reproducible telemetry for a technique that commercial malware families have used with legitimate cloud providers for years. Treat it as a research instrument first, run it only against infrastructure you control or are contracted to test, and remember that the cloud account holder is always identifiable — this is covert channeling, not anonymity tooling.

Official project repository for ivancabrera02/R2Socks.
Download Tool

Educational analysis for authorized security professionals. Use only in controlled, authorized environments.

Share articleFacebookXLinkedIn

Continue exploring

Browse all articles →

0 comentários:

Post a Comment

Note: Only a member of this blog may post a comment.