The Unsafe Send: How an HTTP Client Library Led to Remote Code Execution

Finding a vulnerability in a core library is always a "hold your breath" moment. You realize that the impact isn't just limited to one endpoint, but potentially to every application that includes this dependency.

Recently, while auditing a Ruby-based HTTP client library (we'll call it `redacted`), I discovered a critical flaw: a Remote Code Execution (RCE) vulnerability that allowed me to escape the boundaries of a simple HTTP request and execute arbitrary system commands.

All it took was a developer trusting a "verb."

Reconnaissance (The Setup)

I've been spending a lot of time lately looking at how modern applications handle outgoing requests. Webhooks, third-party integrations, and API wrappers are everywhere, and they almost always rely on a few standard gems to do the heavy lifting.

I decided to take a look at `redacted`, a lightweight wrapper designed to make `Faraday` connections more manageable. I wasn't looking for anything specific initially just mapping out the attack surface.

I started by digging into the core request handler. If there was going to be an issue, it would likely be where the library translates user-provided options into actual network calls.

The Rabbit Hole (The Struggle)

My first instinct with HTTP clients is always to look for Server-Side Request Forgery (SSRF). I checked how they handled redirects, how they parsed base URLs, and if they had any built-in blacklists for internal IP ranges.

They didn't have much in the way of blacklists, but most users were expected to provide the base URL themselves, which limited the exploitability in a typical "feature" context. It was all very standard, very safe.

I was about to move on when I noticed how the library handled different HTTP methods (GET, POST, etc.). I wondered: "How does it decide which method to call on the underlying connection object?"

The "Aha!" Moment (Exploitation)

The breakthrough came when I opened `lib/redacted/redacted.rb`. I found a method called `perform_request` (sanitized name) that looked like this:

def perform_request(http_verb: :get, resource_path: '/', data_body: {})
# … setup logic …
# The "Aha!" Moment:
connection.send(http_verb, resource_path, data_body) do |req|
yield(req) if block_given?
end
# … response handling …
end

In Ruby, `send` is a powerful (and dangerous) method. It allows you to invoke *any* method on an object by name. While the developer intended for `http_verb` to be `:get` or `:post`, there was absolutely no validation.

Since the `connection` object inherits from `Object` and includes the `Kernel` module, it has access to methods like `eval`, `system`, and `exec`.

If I could control the `http_verb` and the `resource_path`, I could call `eval` and pass it a string of Ruby code to execute.

I tested my theory with a simple script:

# Attacker-controlled inputs
attacker_verb = "eval"
attacker_payload = "system('whoami > /tmp/pwned')"
client = Redacted::Request.new(base_url: "https://redacted.com")
# The Exploit
client.perform_request(
http_verb: attacker_verb,
resource_path: attacker_payload,
data_body: {} # Passed as nil to eval
)

It worked perfectly. Because `send` doesn't care about method visibility, I was able to jump straight from "sending an HTTP request" to "executing system commands."

Impact

The real-world impact of this is massive. Any application using this library that allows a user to specify the HTTP method — for example, a "Webhook Tester" or a "Proxy Tool" becomes instantly vulnerable to full server takeover.

An attacker can execute arbitrary shell commands, exfiltrate environment variables (like AWS keys and database credentials), and establish a persistent foothold in the infrastructure.

Key Takeaways

  • Avoid `send` with User Input: Never use `Object#send` (or `public_send`) with data that originates from a user. If you must use it, validate the input against a strict allowlist.
  • Visibility Matters: Even if you use `public_send`, remember that `Kernel` methods are often available.
  • Defense in Depth: Library maintainers should always validate that the "method" being called is actually a valid HTTP verb.

References

Timeline:

  • Reported: January 20, 2026
  • Triaged: February 18,2026
  • Rewarded with: $750 bounty. February 18,2026
  • Resolved: February 18,2026