How to Map, Analyze, and Exploit Non-HTTP Attack Surfaces from Source Code

How to Map, Analyze, and Exploit Non-HTTP Attack Surfaces from Source Code

Ask ten security engineers to audit a piece of software and nine of them will start by looking for a web interface. That instinct is understandable — HTTP is well-documented, tooling is mature, and Burp Suite makes the workflow almost mechanical. But a huge amount of high-value attack surface never touches HTTP at all: custom RPC protocols, message queues, gRPC services, native IPC, database wire protocols, industrial control protocols, and countless proprietary binary formats. I want to lay out a repeatable methodology for finding and exploiting this kind of surface directly from source code, since — unlike black-box HTTP testing — this is fundamentally a code-reading discipline.

Why Non-HTTP Surface Gets Overlooked

There are a few structural reasons this surface is chronically under-tested:

The Methodology: From Source to Exploit

flowchart TD
    A[Identify Candidate Entry Points in Source] --> B[Classify Transport & Protocol Type]
    B --> C[Trace Data Flow: Source to Sink]
    C --> D[Identify Trust Boundaries Crossed]
    D --> E[Build Minimal Interaction Tooling]
    E --> F[Confirm Vulnerability with Proof-of-Concept]
    F --> G[Assess Impact & Exploitability]

Step 1: Identify Candidate Entry Points

Start by grepping for the primitives that indicate a listener exists, regardless of language:

Language/FrameworkGrep Targets
C/C++socket(, bind(, listen(, accept(, recvfrom(
Pythonsocket.socket(, asyncio.start_server(, grpc.server(
JavaServerSocket, Netty ChannelInitializer, @GrpcService
Gonet.Listen(, net.ListenUDP(, grpc.NewServer(
RustTcpListener::bind(, tonic::transport::Server
Node.jsnet.createServer(, dgram.createSocket(

Every hit is a candidate entry point worth cataloging: what address/port/path does it bind to, what’s the protocol, and is it reachable from outside the process’s immediate trust zone?

Step 2: Classify the Transport and Protocol

Once you’ve found a listener, figure out what’s actually running on it. This dictates your entire testing approach:

Step 3: Trace Data Flow — Source to Sink

This is the core of source-code-driven vulnerability research, and it applies identically whether the entry point is HTTP or not. Starting from the point where bytes come off the socket (the source), trace every transformation the data undergoes until it reaches a sink — a database query, a file write, a command execution, a memory allocation, a deserialization call.

# Illustrative example: tracing a custom TCP protocol handler in Python
def handle_connection(sock):
    header = sock.recv(8)                      # SOURCE: untrusted bytes
    msg_type, length = struct.unpack('!II', header)
    payload = sock.recv(length)                 # length attacker-controlled -- Q: bounded?
    if msg_type == MSG_TYPE_QUERY:
        query = payload.decode('utf-8')
        result = db.execute(f"SELECT * FROM items WHERE name='{query}'")  # SINK: SQLi
    elif msg_type == MSG_TYPE_LOAD:
        obj = pickle.loads(payload)              # SINK: insecure deserialization

This tiny example contains two classic sink types that have nothing to do with HTTP at all: a SQL injection reachable only through a custom binary protocol, and an insecure deserialization sink (pickle.loads on attacker-controlled bytes is a well-known remote code execution primitive in Python). Neither would show up in a web-focused scan, because there’s no HTTP request involved anywhere.

Step 4: Identify Trust Boundaries

Ask, for every entry point: who is expected to connect here, and is that expectation actually enforced? A gRPC service listening on an internal Kubernetes ClusterIP is “internal” only until something else in the cluster is compromised, or the service is accidentally exposed via a misconfigured LoadBalancer type, or a debug port gets forwarded during troubleshooting and forgotten.

Step 5: Build Minimal Interaction Tooling

Since there’s rarely a point-and-click tool for a custom protocol, you build the smallest thing that lets you send and receive crafted messages. For most binary protocols, Python’s struct module plus raw sockets gets you 90% of the way:

import socket, struct

def send_msg(host, port, msg_type, payload: bytes):
    s = socket.create_connection((host, port))
    header = struct.pack('!II', msg_type, len(payload))
    s.sendall(header + payload)
    return s.recv(4096)

# Reproduce the SQLi sink identified above
resp = send_msg('target', 9090, 1, b"' OR '1'='1")
print(resp)

For gRPC specifically, since it’s Protocol Buffers over HTTP/2, tools like grpcurl or ghz let you interact with services once you have (or can extract, via reflection or decompiling client code) the .proto definitions.

Common Non-HTTP Protocols and Their Known Risk Patterns

ProtocolTransportCommon Vulnerability Classes
gRPC / ProtobufHTTP/2Deserialization issues, missing auth on reflection service, resource exhaustion
Redis protocol (RESP)TCPUnauthenticated access leading to RCE (module loading, CONFIG SET abuse)
Memcached protocolUDP/TCPAmplification DDoS, unauthenticated data exposure
AMQP/MQTT (message queues)TCPBroker misconfig, missing ACLs, topic/queue injection
Java RMITCPInsecure deserialization leading to RCE
SMB/CIFSTCPAuth relay attacks, protocol downgrade
Custom TLV binary protocolsTCP/UDPBuffer overflows, integer overflow in length fields, missing auth
Database wire protocols (MySQL, PostgreSQL)TCPAuth bypass, injection when application constructs raw protocol messages

Real-World Examples

Building an Attack Surface Inventory from Source

A practical workflow for a codebase-wide review:

  1. Grep for listener primitives across every language/framework in the repo (see table above).
  2. For each listener, record: bind address, port/path, protocol type, authentication mechanism (if any), and the handler function that processes incoming data.
  3. For each handler, trace to sinks: database calls, filesystem operations, deserialization calls, subprocess execution, memory allocation from attacker-controlled sizes.
  4. Cross-reference with deployment configuration (Kubernetes manifests, Docker Compose, systemd units, firewall rules) to determine actual reachability — a listener bound to 0.0.0.0 inside a container might still only be reachable within a private VPC, or might be exposed via a misconfigured ingress.
  5. Prioritize by reachability × sink severity: an unauthenticated, internet-reachable listener with a deserialization sink is a critical finding; an internal-only listener with a minor information disclosure sink is a much lower priority.

Defensive Best Practices

Frequently Asked Questions

Why don’t standard web vulnerability scanners catch these issues? Web scanners are built around the HTTP request/response model — they crawl links, fuzz parameters, and analyze responses. A custom binary protocol on a TCP port has none of that structure, so scanners simply never send it anything meaningful, and often don’t even recognize it as a target.

Is reverse engineering required if I have source code access? Not in the same sense as black-box reverse engineering, but you still need to reconstruct the wire format by reading the parsing code — effectively “reverse engineering the protocol from its own implementation,” which is faster and more reliable than black-box protocol reversing but requires the same systematic mindset.

How do I prioritize which non-HTTP services to review first in a large codebase? Start with anything reachable from outside the immediate trust zone (internet-facing, or reachable from a less-trusted network segment/tenant), then anything handling data from a source you don’t fully control (partner integrations, IoT devices, other microservices), then work inward.

Are gRPC services inherently safer than raw custom protocols because they use Protocol Buffers? Protobuf’s schema-driven serialization eliminates a lot of manual parsing bugs compared to hand-rolled binary formats, but it doesn’t provide authentication, authorization, or protection against logic bugs in the service implementation — those still have to be built and reviewed separately.

References

Summary and Recommendations

Non-HTTP attack surface is often the highest-value, least-tested part of a system precisely because it’s harder to find and harder to tool against. The methodology doesn’t fundamentally differ from web application security — find the entry points, trace source to sink, identify trust boundaries — but the execution requires reading source code rather than relying on off-the-shelf scanners, and building small, purpose-built tools to interact with whatever protocol you find. If your security program only tests what shows up in a browser, you’re systematically missing the services most likely to be genuinely under-reviewed.

Exit mobile version