Skip to content

Conversation

lionello
Copy link

@lionello lionello commented Dec 28, 2017

This PR implements basic Cross-Origin Resource Sharing (CORS) support to the RPC server, as per the spec at https://www.w3.org/TR/cors/#resource-requests . The spec has been quoted verbatim in the source code for easier validation and maintenance of the code.

  • added support for OPTIONS HTTP method
  • interpret CORS request headers for pre-flight requests
  • set CORS response headers
  • two test cases: standard CORS request, and pre-flight request

In practice this PR allows the REST interface to be used directly from a browser.

All the existing restrictions to the REST interface still apply: IP subnet, port, username, password. For this reason this PR doesn't explicitly check the request's Origin with a whitelist.

@TheBlueMatt
Copy link
Contributor

Hmm, I mean in general its probably a terrible idea to be calling bitcoind's RPC from a browser directly...I would probably be happy to see this if we force the setting of an -alloworigin=$ORIGIN option (which implies IP whitelist as well) to force users to use it at least somewhat securely.

@lionello
Copy link
Author

lionello commented Jan 5, 2018

There's plenty of good reason to enable RPC from browser, but I agree we have to make it opt-in. Will add that to the patch.

@gmaxwell
Copy link
Contributor

gmaxwell commented Jan 8, 2018

Things like this are not sufficient to make access from browsers safe, because we must also be safe even if the user has old browsers with CORS bugs on their system. (e.g. IIRC old IE was trivially bypassable).

As a result there would have to be a lot of developer resources available to implement, review, and characterize the full set of protections needed to prevent that from being a gaping vulnerability-- and a commensurate really good application for it.

@lionello lionello force-pushed the http-options-pre-flight branch 2 times, most recently from 3b490cb to 07fe036 Compare February 6, 2018 04:25
@lionello
Copy link
Author

lionello commented Feb 6, 2018

@gmaxwell Done: I've added the flag -rpccorsdomain to match the existing options (and similar option in Ethereum's Geth.)

I've also added tests for that flag.

As for your question: we have an internal website that we use to sign transactions with hardware wallets. We use RPC to send out the signed transactions. We'd prefer to do this without a service (which needs audits, security updates, etc..)

@Sjors
Copy link
Member

Sjors commented Feb 20, 2018

Needs rebase. I'm generally a fan of correct CORS / CSP headers, will review later.

@Sjors
Copy link
Member

Sjors commented Feb 20, 2018

POST works (assuming bitcoin:bitcoin credentials, running on port 8085):


curl -X POST \
  http://localhost:8085/ \
  -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu=' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '{
	"method": "getblockchaininfo"
}'

But OPTIONS doesn't:

curl -X OPTIONS \
  http://localhost:8085/ \
  -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu=' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json'
JSONRPC server handles only POST requests

That was using Postman which doesn't set the origin header, but I get the same error from a browser for the OPTIONS request.

@lionello
Copy link
Author

@Sjors Just using OPTIONS doesn't make it a valid CORS pre-flight request. Try this:

curl -X OPTIONS \
    http://localhost:8085/ \
    -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu=' \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Type: application/json' \
    -H 'Origin: null' \
    -H 'access-control-request-method: POST' \
    -d '{
	"method": "getblockchaininfo"
  }'

@lionello lionello force-pushed the http-options-pre-flight branch from 07fe036 to 25f1000 Compare February 21, 2018 00:22
@Sjors
Copy link
Member

Sjors commented Feb 22, 2018

Same error unfortunately.

Incorrect OPTIONS request should probably throw a unique error regardless.

I tried your full example, but since the JSON payload itself shouldn't be needed in an OPTIONS request, I think it can be shortened a bit:

curl -X OPTIONS \
    http://localhost:8085/ \
    -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu=' \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Type: application/json' \
    -H 'Origin: null' \
    -H 'access-control-request-method: POST'

@lionello
Copy link
Author

@Sjors Did you provide the -rpccorsdomain command line arg?

@Sjors
Copy link
Member

Sjors commented Feb 22, 2018

Yes, I used -rpccorsdomain=http://localhost:8080. However I used the wrong port in my Origin header.

Better error messages would be very useful here, either through the browser or rpc log entries.

This worked:

curl -v -X OPTIONS \
    http://localhost:8085/ \
    -H 'Origin: http://localhost:8080' \
    -H 'Access-Control-Request-Method: POST' \
    -H 'Access-Control-Request-Headers: authorization,content-type'

I added -v so you can see the returned headers. I also removed the Authorization header from the OPTIONS request in this example, because browsers won't send credentials during such a request.

If anyone else wants to test a POST request:

curl 'http://localhost:8085/' \
   -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu=' \
   -H 'Origin: http://localhost:8080' \
   -H 'Content-Type: application/json' \
   -H 'Accept: application/json' \
   --data-binary '{"method":"getblockchaininfo"}'

Nice to have: support for multiple -rpccorsdomain arguments.

@Sjors
Copy link
Member

Sjors commented Feb 22, 2018

Any malicious website would need to trick the user into handing out RPC authentication details, as well as setting server=1 in bitcoin.conf.

For HTTP Basic Auth they need to trick the user into configuring rpcuser= and rpcpassword= or run the credentials script. Otherwise they need to make the user find and upload the cookie.

I think it would be particularly useful for data visualization. E.g. a mempool chart is much easier to build using client-side javascript that just fetches the data through ajax.

Although potentially very useful for hardware wallets, facilitating wallet access from a browser potentially opens a can of worms. For a non-wallet node I don't see much downside.

Perhaps a compromise (for now) could be to only allow this flag when built with --disable-wallet or --seatbelts=0. A less strict rule could work too, but is likely more difficult to implement and maintain.

@gmaxwell regarding old browsers, I think we should work under the assumption that CORS is broken and any request can be made. Afaik enabling CORS is just a way to allow modern browsers to use the RPC (they require the OPTIONS request to return the right data), not to prevent older browsers from doing that.

@lionello
Copy link
Author

@Sjors The default is still CORS disabled. Would you still want it to only be available with --disable-wallet or --seatbelts=0?

@maflcko
Copy link
Member

maflcko commented May 9, 2018

Needs rebase if still relevant

@lionello lionello force-pushed the http-options-pre-flight branch from 25f1000 to 5f60c1b Compare May 10, 2018 00:25
@lionello
Copy link
Author

@MarcoFalke @Sjors Rebased.

@takahser
Copy link

takahser commented Nov 6, 2018

any news on that one? is it going to be merged anytime soon?

@lionello lionello force-pushed the http-options-pre-flight branch from 5f60c1b to 8d848da Compare November 7, 2018 01:10
@lionello lionello force-pushed the http-options-pre-flight branch from 8d848da to 53e054a Compare November 7, 2018 01:40
@lionello
Copy link
Author

lionello commented Nov 7, 2018

Rebased, if anybody still cared.

This patch got merged in Bitcoin ABC which was what I cared about ;)

@takahser
Copy link

takahser commented Nov 7, 2018

@lionello that was quick, thank you.
I hope this will get merged soon, since I'd like to spin up some CORS-enabled bitcoind nodes.

@ryanofsky
Copy link
Contributor

This seems like a nice change, and I'd really curious to know what some use-cases are. (Lacking web background, or enough of an imagination myself...)

Maybe something about this could also be added to the documentation https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md

@takahser
Copy link

takahser commented Nov 7, 2018

@ryanofsky in my case, I'm considering to use in disablewallet mode as a bitcoin node, that is used by a web wallet, for fetching and sending transactions (transactions are signed on the client, so no private key's are sent around the network). Ofc we can't say for sure, who (which IP) will use the web wallet, but they have one thing in common: the URL of the webwallet. We can leverage this piece of information if the btc node is CORS-enabled.

@lionello
Copy link
Author

lionello commented Nov 8, 2018

@ryanofsky The scenarios are having a website interact with the blockchain node directly, without the need for a backend: browser --rpc--> bitcoind. You can do transaction signing in the browser (JS libs, or HW device) and send transactions directly.

@lionello lionello force-pushed the http-options-pre-flight branch from 53e054a to 20d3c18 Compare November 8, 2018 00:53
@lionello lionello force-pushed the http-options-pre-flight branch from 20d3c18 to 210b8ee Compare November 8, 2018 00:59
Copy link
Member

@Sjors Sjors left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK 210b8ee (with a preference for renaming the variables)

Recipe to test:

  • launch REST server at port 8080:
src/bitcoind -server -rest=1 -rpcport=8080 -rpcuser=bitcoin -rpcpassword=bitcoin -rpccorsdomain=http://localhost:8080
  • pretend to be a browser visiting a website on port 8085 which calls the REST server:
curl 'http://localhost:8080/'    -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu='    -H 'Origin: http://localhost:8080'    -H 'Content-Type: application/json'    -H 'Accept: application/json'    --data-binary '{"method":"getblockchaininfo"}'
  • do some wallet operation on wallet "A":
curl 'http://localhost:8080/'    -H 'Authorization: Basic Yml0Y29pbjpiaXRjb2lu='    -H 'Origin: http://localhost:8080/wallet/A'    -H 'Content-Type: application/json'    -H 'Accept: application/json'    --data-binary '{"method":"getwalletinfo"}'

You can also dynamically load any wallet on the nodes filesystem, so indeed disablewallet=1 is probably a good idea.

There's no way to configure this by accident and for good measure most users are behind a NAT. I think a warning in the release notes should suffice, i.e. to use disablewallet=1, to never expose a machine with a wallet to the internet, and not to share RPC credentials in general.

Longer term I think we should add some sort of user-permission system, which is also quite useful in a Lightning node context (e.g. a connected Lightning node should be able to get information, request an address to refund on, etc, but not steal coins off the node).

@@ -66,6 +66,8 @@ class HTTPRPCTimerInterface : public RPCTimerInterface
static std::string strRPCUserColonPass;
/* Stored RPC timer interface (for unregistration) */
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
/* RPC CORS Domain, allowed Origin */
static std::string strRPCCORSDomain;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call this str_rest_cors_domain (new naming convention) and the parameter -restcorsdomain (since it's only used with -rest=1).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other options still use the old -rpc... naming though, so I'm not convinced this helps anyone. Even the variables declared right before strRPCCORSDomain use the old naming.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the use of -rpc confusing when a setting is about the REST interface. -rpcauth, -rpcuser and -rpcpassword is shared between the regular RPC and REST so there it makes sense (although I'm not a fan of that sharing). The rest is just internal variables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST doesn't have authentication, I thought?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sipa it does, see my command-line example above. It has full wallet access too.

Copy link
Member

@sipa sipa Nov 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sjors I'm very confused. The REST interface only exposes public information (blocks, transactions with txindex, headers, chaininfo, utxoset, mempool), and should never require authentication. Does this PR change that? Your example seems to just use the RPC interface.

@Sjors
Copy link
Member

Sjors commented Nov 10, 2018

Actually NAT doesn't help, because an attacker could tell a user to run bitcoind -rpccorsdomain=http://evil.com/ and have them visit http://evil.com/ which would then empty the users wallet (https://evil.com might not work because some browsers refuse to XHR to http from an https site, but I'd have to test that).

@Sjors
Copy link
Member

Sjors commented Nov 20, 2018

I missed how much RPC and REST are intertwined; it's one and the same server, not a separate one as I assumed. So in the examples above I'm just making normal (authenticated) JSON-RPC requests. Let's try that again:

curl 'http://localhost:8080/rest/chaininfo.json' -H 'Origin: http://localhost:8080'    -H 'Content-Type: application/json'  -H 'Accept: application/json'

The REST interace doesn't expose anything private.

So all you need to change is the only add CORS headers to /rest/*. In that case -restcorsdomain would still be a better param name I think.

@Sjors
Copy link
Member

Sjors commented Nov 20, 2018

After brief IRC discussion today I'm switching to light Concept NACK.

The REST API wasn't intended for this use case. It's meant as a more performant alternative to the JSON-RPC (using bin and hex formats). It's not battle hardened so should only be called by semi trusted software locally. It's not safe against DoS.

I think it's better to recommend users who really want this to run nginx and have that add the headers, as well as shield URLs that are not part of /rest/, only whitelist URLs their application really needs, etc. That seems both safer and to prevent scope creep.

@DrahtBot
Copy link
Contributor

DrahtBot commented May 13, 2019

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

No conflicts as of last run.

@fanquake
Copy link
Member

Going to close this. A couple of (implied?) NACKs, and a reversal from a utACK to NACK, no further discussion for 8 months.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.