Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501f"Basic {str_to_b64str('nocolon')}",502"Basic ",503]504for auth_value in cases:505conn = BitcoinHTTPConnection(self.node)506conn.headers = {"Authorization": f"{auth_value}"}507response = conn.post('/', '{"method": "getbestblockhash"}')508assert_equal(response.status, http.client.UNAUTHORIZED)509assert response.getheader('WWW-Authenticate') is not None512def check_disallowed_http_methods(self):513self.log.info("Check that unsafe or unsupported HTTP methods are rejected")514for method, err in [515['TRACE', http.client.METHOD_NOT_ALLOWED],516['CONNECT', http.client.METHOD_NOT_ALLOWED],517['DELETE', http.client.METHOD_NOT_ALLOWED],518['PATCH', http.client.METHOD_NOT_ALLOWED],519['OPTIONS', http.client.METHOD_NOT_ALLOWED],520['GET', http.client.METHOD_NOT_ALLOWED] # RPC endpoint '/' only handles POST521]:522conn = BitcoinHTTPConnection(self.node)523response = conn._request(method, '/', data=None, connection_header=None)524assert_equal(response.status, err)527def check_path_traversal(self):528self.log.info("Check that path traversal attempts are safely rejected")529traversal_paths = [530'/../etc/passwd',531'/../../etc/shadow',532'/%2e%2e/%2e%2e/etc/passwd', # URL-encoded dots533'/..%2Fetc%2Fpasswd', # URL-encoded slash534'/.%2e/.%2e/etc/passwd', # mixed encoding535'/valid/../../../etc/passwd',536]537for path in traversal_paths:538conn = BitcoinHTTPConnection(self.node)539response = conn.get(path)540assert_equal(response.status, http.client.NOT_FOUND)543def check_request_smuggling_cl_te(self):544self.log.info("Check request smuggling is not possible")545# https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3546# Transfer-Encoding takes precedence over Content-Length.547# Sending both creates a smuggling vector: a front-end proxy that548# uses Content-Length while the back-end uses Transfer-Encoding lets an549# attacker prepend arbitrary bytes to the next victim's request.551# The real JSON-RPC body sent as a single chunk.552body = b'{"method":"getblockcount"}'553# Content-Length is set to the length of just the chunk-size line554# ("1a\r\n" = 4 bytes), not the full chunked body — the ambiguity that555# smuggling exploits. A server using Transfer-Encoding reads the complete556# chunk and responds with the block count; a server confused by the557# mismatch may stall, close the connection, or return an error.558chunk_size_line = f"{len(body):x}\r\n".encode("ascii")559# Signals end of body560empty_chunk = b'\r\n0\r\n\r\n'561chunk_body = chunk_size_line + body + empty_chunk563conn = BitcoinHTTPConnection(self.node)564raw = (565f"POST / HTTP/1.1\r\n"566f"Host: {conn.url.hostname}\r\n"567f"Authorization: Basic {str_to_b64str(conn.authpair)}\r\n"568f"Content-Length: {len(chunk_size_line)}\r\n"569f"Transfer-Encoding: chunked\r\n"570f"\r\n"571).encode("ascii") + chunk_body572conn.send_raw(raw)573response = conn.recv_raw().decode()574assert "HTTP/1.1 200 OK" in response575count = self.node.getblockcount()576assert f'"result":{count}' in response579def check_duplicate_content_length(self):580self.log.info("Check that duplicate Content-Length headers are handled")581# https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3582# Multiple Content-Length headers with differing values "MUST"583# result in an error.584conn = BitcoinHTTPConnection(self.node)585body = '{"method":"getblockcount"}'586raw = (587f"POST / HTTP/1.1\r\n"588f"Host: {conn.url.hostname}\r\n"589f"Authorization: Basic {str_to_b64str(conn.authpair)}\r\n"590f"Content-Length: {len(body)}\r\n"591f"Content-Length: 999\r\n"592f"\r\n"593f"{body}"594).encode("ascii")595conn.send_raw(raw)596response = conn.recv_raw().decode()597assert response.startswith("HTTP/1.1 400")600def check_null_byte_in_uri(self):601self.log.info("Check that null bytes in the URI are safely rejected")602# Null-byte injection can truncate the path string in C environments,603# bypassing suffix/extension checks and causing unexpected file access.604conn = BitcoinHTTPConnection(self.node)605raw = (606"GET /safe\x00/../etc/passwd HTTP/1.1\r\n"607f"Host: {conn.url.hostname}\r\n"608f"Authorization: Basic {str_to_b64str(conn.authpair)}\r\n"609"\r\n"610).encode("ascii")611conn.send_raw(raw)612response = conn.recv_raw().decode()613assert response.startswith("HTTP/1.1 400")616def check_invalid_http_version(self):617self.log.info("Check that requests with invalid HTTP versions are safely rejected")618cases = [619b"GET / \r\n\r\n", # HTTP/0.9 — no version620b"GET / HTTP/9.9\r\nHost: localhost\r\n\r\n", # far-future version621b"GET / HTTP/INVALID\r\nHost: localhost\r\n\r\n", # non-numeric version622b"GET / NOTHTTP/1.1\r\nHost: localhost\r\n\r\n", # wrong protocol name623]624for raw in cases:625conn = BitcoinHTTPConnection(self.node)626conn.send_raw(raw)627response = conn.recv_raw().decode()628assert response.startswith("HTTP/1.1 400")631def check_whitespace_in_headers(self):632self.log.info("Check that requests with whitespace in headers are rejected")633# Extra whitespace before colon in header.634conn = BitcoinHTTPConnection(self.node)635conn.headers = {"Authorization ": f"Basic {str_to_b64str(conn.authpair)}"}636self.send_bad_and_tolerate_disconnect(conn, lambda: conn.post('/', '{"method": "getbestblockhash"}'))638# Extra whitespace at start of new line.639# "line folding" as defined in640# https://www.rfc-editor.org/rfc/rfc2616#section-2.2641# is considered unsafe and is explicitly deprecated in642# https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4643conn = BitcoinHTTPConnection(self.node)644conn.headers = {"Authorization": f"Basic \n {str_to_b64str(conn.authpair)}"}645self.send_bad_and_tolerate_disconnect(conn, lambda: conn.post('/', '{"method": "getbestblockhash"}'))648def check_connection_limit(self):649self.log.info("Check connection limits")651# Disable timeout so the initial batch of clients stays connected652# until the end of the test.653for comment, extra_args, limit in [654("default (16)", ["-rpcservertimeout=0", "-rest"], 16),655("-rpcmaxconnections=64", ["-rpcservertimeout=0", "-rest",656"-rpcmaxconnections=64", "-maxconnections=16"], 64)657]:658self.log.info(f"Using connection limit: {comment}")659self.restart_node(0, extra_args=extra_args)661# Close the persistent HTTP connection to this node by replacing it with662# a new AuthServiceProxy, reducing HTTPServer::GetConnectionsCount() to 0.663# The new AuthServiceProxy won't actually open an HTTP connection until664# it needs to send an RPC (for example, to stop the node at the end of the test).665self.node._rpc = self.node.create_new_rpc_connection(mode="AUTHPROXY")667MAX_HTTP_CONNECTIONS = limit668connections = []670# Connections all succeed up to the limit671with self.node.assert_debug_log(672expected_msgs = [f"method=invalidrpc_{i} " for i in range(1, MAX_HTTP_CONNECTIONS + 1)]673):674for i in range(1, MAX_HTTP_CONNECTIONS + 1):675conn = BitcoinHTTPConnection(self.node)676# Each client makes a unique request so it's easy to find in the log677conn.post('/', f'{{"method": "invalidrpc_{i}"}}', connection_header='keep-alive').read()678connections.append(conn)680# The next connection is over the limit, expect it to timeout681with self.node.assert_debug_log(682expected_msgs = [],683unexpected_msgs = ["method=never_accepted"]684):685conn = BitcoinHTTPConnection(self.node)686conn.set_timeout(5)687assert_raises(TimeoutError, lambda: conn.post('/', '{"method": "never_accepted"}', connection_header='keep-alive').read())689# All original clients are still connected690assert_equal(len(connections), MAX_HTTP_CONNECTIONS)691for client in connections:692assert not client.sock_closed()694# Try connecting again, but this time we'll wait for acceptance.695# Because the send is blocking, we'll execute in a background thread.697def wait_for_send(conn):698return conn.get('/rest/blockhashbyheight/0.json').read()700conn = BitcoinHTTPConnection(self.node)701conn.set_timeout(None)702executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)703waiting_request = executor.submit(704wait_for_send,705conn706)708# We are waiting709assert not waiting_request.done()711# Close one of the original connections712popped_client = connections.pop()713popped_client.close_sock()715# The waiting connection gets processed716delayed_response = waiting_request.result(timeout=5)717assert "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" in delayed_response.decode()719# Close all remaining connections for clean up720for client in connections:721client.close_sock()724def check_pipelined_data_is_throttled(self):725self.log.info("Check that pipelined data is throttled while a request is in flight")726self.restart_node(0, extra_args=["-rpcservertimeout=0"])728conn = BitcoinHTTPConnection(self.node)730# A blocking RPC request: the server reads it fully and it enters the731# worker pool as "in flight" (m_req_busy) until a new block arrives.732tip_height = self.node.getblockcount()733conn.post_raw('/', f'{{"method": "waitforblockheight", "params": [{tip_height + 1}]}}')735# The server logs each successful socket read with the peer's address736# (our ephemeral port) and the connection id. The port uniquely737# identifies the flood connection's log lines.738local_port = conn.conn.sock.getsockname()[1]740# Flood the same connection with big pipelined requests:741# Large garbage submitblock (just under MAX_BODY_SIZE each, including HTTP/jsonrpc overhead)742garbage_block = "0" * (MAX_BODY_SIZE - 100)743body = f'{{"method": "submitblock", "params": ["{garbage_block}"]}}'744flood = (745f'POST / HTTP/1.1\r\nAuthorization: Basic {str_to_b64str(conn.authpair)}\r\n'746f'Content-Length: {len(body)}\r\n\r\n' +747body748).encode("ascii")750# Save a debug log checkpoint
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.