Running geth on ubuntu 16.04 with
/usr/bin/geth --ws --wsport 8540 --wsorigins 127.0.0.1
But ws is not available:
wscat -c ws://127.0.0.1:8540
error: Error: unexpected server response (403)
and the geth log:
geth[5403]: WARN [04-30|09:16:38] origin '' not allowed on WS-RPC interface
In this line:
https://github.com/Bunjin/go-ethereum/blob/master/rpc/websocket.go#L110
req.Header.Get("Origin") should be changed to req.Header.Get("origin")
PR:
https://github.com/ethereum/go-ethereum/pull/16609
Edit: This doesn't solve the issue, see below
I tried to rebuild from source with the fix in my PR, but it doesn't seem to solve the issue however.
origin or req.Header.Get("origin") is still undefined :'(
I'll investigate more and post here if I manage to solve this myself.
So actually this was a problem with web3 which doesn't set the origin.
https://github.com/ethereum/web3.js/blob/1.0/packages/web3-providers-ws/src/index.js#L78
I changed undefined to 127.0.0.1 and it worked :+1:
TL;DR: See the last paragraph
I've just experienced a similar issue. My situation was that I needed to use web3 1.0 websocket on my node side to connect to my local geth client.
After some investigations, my conclusion is that neither(geth or web3) is behaving incorrectly. Instead what one needs to do is just to specifiy origin on the web3 side. I'd like to share some research on this issue. Hope it can clear up some confusion that I also had.
Basically, origin '' not allowed on WS-RPC interface is caused because websocket's Origin header is not specified, while geth client requires it(default settings is to only accept connections from http://localhost, if not specified with --wsorigins option).
websocket's Origin is a header set by the browser. Why does such header exists in websocket protocol and why geth asks for it? This is to protect possible abuse and hijacking from other sites since WebSockets are not restrained by the same-origin policy.(see this and this) But also, note that Origin header is subject to be manipulated since it's only a header added by the browser.
The WebsocketProvider that web3 1.0 provides now is a websocket for the node(dependent on WebSocket-Node). You can use it on the browser side too, but note that even if you pass the Origin header onto the provider(see final code section), browser will automatically override that value with the current host. So when connecting from a browser, what you need to do is to add correctly the expected host onto geth's --wsorigins.
On the other hand, when this websocket provider is used in node, Origin header may not have a valid meaning, since the websocket connection from a node process is not really in a user-agent scenario where the script is provided by the domain. From the WebSocket-Node documentation:
originis an optional field that can be used in user-agent scenarios to identify the page containing any scripting content that caused the connection to be requested. (This seems unlikely in node.. probably should leave it null most of the time.)
This is why the web3 code left it undefined by default. But still, you can set the Origin header with the options object(which is the 2nd param of WebsocketProvider constructor) with headers > Origin key, which will eventually written into the Origin header.
To sum up, one can just go with --wsorigins * option for geth if security is not of much concern, or rather specify origin header explicitly with some meaningful string(or the actual host) on the web3 websocket provider and pass the value onto geth's --wsorigins. One easy option is to set the Origin header as http://localhost on web3 ws-provider, and run geth without --wsorigins since geth defaults to accept connections from http://localhost, which is the handier equivalent of what @Bunjin did.
Example:
const wsProvider = new Web3.providers.WebsocketProvider("ws://localhost:8546", {
headers: {
Origin: "http://localhost"
}
});
web3 = new Web3(wsProvider);
@vzts
Great write up, thanks @vzts !
The fact that the headers are written by javascript code and not by the browser should render this "protection" totally pointless. An attacker can write whatever header she wants, right?
read again what @vzts wrote in 1) and the links he provided.
It's not made to protect the App, it's made to protect the user from cross site requests.
Okay, then it is not possible to override the the "Origin" header with Javascript. This should indeed protect from cross site request forgery. But there is no link to prove that point.
Thanks. But these are the links from above. It is just people saying "I have read that the Origin header is secure". I think a link to the specification for the browsers would be most helpful.
I see, you want a strong proof that browsers indeed behave so.
maybe this global spec?
https://www.w3.org/TR/websockets/
https://html.spec.whatwg.org/multipage/web-sockets.html#network
and then pick an open source browser and look at the code I guess :)
Let me know if you get the definite proof :)
Citing from there:
Fire an event named message at the WebSocket object, using MessageEvent, with the origin attribute initialized to the serialization of the WebSocket object's url's origin, and the data attribute initialized to dataForEvent.
But then the following is true: Whenever a websocket connection to the server is opened from within a browser the Origin header will be set and have a value. So if the server receives a request without the Origin header, it is safe to respond, because it is a request coming from a non browser environment where the header can be set arbitrarily. Geth should be fixed and just respond normally when not finding a header instead of enforcing this hack to set the header by hand.
I come here because I stumbled on this while trying to get my tests running from node. It is very annoying to have to add such stuff to the production codebase just to make some tests run.
@Levino I think you could go with --wsorigins * option in that case. I agree with you that the geth should not arbitrarily assume that the client environment is a browser. Maybe the default behavior of geth should be fixed.
@vzts Thanks for joining the discussion. I fear your "solution" would just render the whole protection useless. Then any websocket from any website would be served. Whereas when just serving connections without an Origin header and serving connections with Origin headers which have a whitelisted origin would still keep the protection in place.
@Levino You're definitely right on that point. --wsorigins * I've suggested was just for testing. Actually, I think I should look into the official spec in detail so that we can come to a consensus on the "correct behavior" of geth, and WebSocket-Node. Maybe the original solution I suggested is only a workaround. Admit it was a hasty conclusion. Thanks for the alert for everyone.
@Levino One quick thought on the nature of WebSocket though, WebSocket spec is nothing more than a standardization of lower level TCP socket, and it is for the "web" specifically, where user-agents are mostly browsers. If you look at its history, the protocol itself is somewhat for the browser.
I've checked that the WebSocket protocol development has moved to IETF, and here is the RFC. If you look at the 10.2 part "Origin Considerations":
The |Origin| header field protects from the attack cases when the
untrusted party is typically the author of a JavaScript application
that is executing in the context of the trusted client.
The client itself can contact the server and, via the mechanism of the |Origin|
header field, determine whether to extend those communication
privileges to the JavaScript application. The intent is not to
prevent non-browsers from establishing connections but rather to
ensure that trusted browsers under the control of potentially
malicious JavaScript cannot fake a WebSocket handshake.
According to this, my original suggestion seems kind of valid.
Security consideration is always double-edged, and even if you have that origin header authentication on your server side, there's a way to manipulate that. So the origin header is only for preventing hijacking(the post I originally quoted), if you really want to whitelist the specific client you want, rather than general users that accessing your website from their browser, you will need to authenticate the users using other mechanisms such as private keys or tokens.
I was not challenging your original solution. It is a valid solution and I am using it:
When you run this (please note I changed the value for Origin)
const wsProvider = new Web3.providers.WebsocketProvider("ws://localhost:8546", {
headers: {
Origin: "http://invalid.host"
}
});
web3 = new Web3(wsProvider);
two different things happen depending on where it is run:
http://localhost:3000 the origin header will be set to http://localhost:3000http://invalid.hostThe connection from the javascript run with node will only be allowed, when http://invalid.host is whitelisted in geth with the --wsorigins http://localhost:3000,http://invalid.host flag.
BUT when you run the code like so in Node:
const wsProvider = new Web3.providers.WebsocketProvider("ws://localhost:8546")
web3 = new Web3(wsProvider);
then the origin header will be missing and geth will discard the connection to "protect against third party hijacking". The thing is: If the header is missing, it is safe to allow the connection: The "attacker" must be accessing the endpoint from a non browser environment (because otherwise the browser would have set the origin header). That means that the attacker can set any header she/he wants. So then the hacker just puts http://localhost:3000 as the origin and gets access to the node. So instead of enforcing the correct header geth should enforce a correct header, iff the header is present (note the second "f"). If no header is present, just allow the access, there is no possible protection anyhow.
So back to the problem at hand:
So I am making the point that someone did not really think things through when implementing the current behaviour of geth and this should be fixed instead of forcing everyone to use this workaround.
I would say that geth is behaving correctly, since it is suggested in the abstraction of the RFC:
The goal of this technology is to provide a mechanism for browser-based
applications that need two-way communication with servers that does
not rely on opening multiple HTTP connections
The security model used for this is the origin-based security model commonly used
by web browsers.
Servers that are not intended to process input from any web page but
only for certain sites SHOULD verify the |Origin| field is an origin
they expect. If the origin indicated is unacceptable to the server,
then it SHOULD respond to the WebSocket handshake with a reply
containing HTTP 403 Forbidden status code.
If your server is intended to process input from any web page, then there is option "--wsorigins *". I think the origin header is okay to be forced by default since the main purpose of websocket technology is for the browsers, not the execution env like node.
While I think your idea to validate origin header only when it exists is nice, but the opinions on the idea may be subjective. I really like it though. Agree with your idea.
While I think your idea to validate origin header only when it exists is nice, but the opinions on the idea may be subjective. I really like it though. Agree with your idea.
Agreed, that would be a nice default behavior that would allow both to use node scripts without setting an origin while still having this browser protection for the users.
I tentatively agree that a request without Origin header should be excempt from origin-checks (since it's obviously not a CORS-attempt via the browser).
I'll have to think about this for a bit, to figure out there any other cases to consider where this would have a negative effect on security.
@holiman Are you done thinking about this? 馃槈 This is causing problems with Augur and the current workaround is to tell users to do --ws-origns '*' which is not secure for them.
Your latest comment is correct, if no origin is present then it means the connecting client is not attempting to protect the user for cross origin attacks, most likely because it isn't a browser. There is no situation where a client would submit no origin and expect to be protected against cross origin attacks since the mechanism for protecting against cross origin attacks is by supplying an Origin to the server and trusting the server to help you out with the protection.
Overall is it secure to use geth with following web socket flags as follows: --ws --wsaddr="127.0.0.1" --wsorigins "*" ?
We've decided today that to implement the suggested feature: a request without Origin header should be excempt from origin-checks (since it's obviously not a CORS-attempt via the browser).
Hooray!
Most helpful comment
TL;DR: See the last paragraph
I've just experienced a similar issue. My situation was that I needed to use web3 1.0 websocket on my node side to connect to my local geth client.
After some investigations, my conclusion is that neither(geth or web3) is behaving incorrectly. Instead what one needs to do is just to specifiy origin on the web3 side. I'd like to share some research on this issue. Hope it can clear up some confusion that I also had.
Basically,
origin '' not allowed on WS-RPC interfaceis caused because websocket'sOriginheader is not specified, while geth client requires it(default settings is to only accept connections fromhttp://localhost, if not specified with--wsoriginsoption).websocket's
Originis a header set by the browser. Why does such header exists in websocket protocol and why geth asks for it? This is to protect possible abuse and hijacking from other sites since WebSockets are not restrained by the same-origin policy.(see this and this) But also, note thatOriginheader is subject to be manipulated since it's only a header added by the browser.The
WebsocketProviderthat web3 1.0 provides now is a websocket for the node(dependent on WebSocket-Node). You can use it on the browser side too, but note that even if you pass theOriginheader onto the provider(see final code section), browser will automatically override that value with the current host. So when connecting from a browser, what you need to do is to add correctly the expected host onto geth's--wsorigins.On the other hand, when this websocket provider is used in node,
Originheader may not have a valid meaning, since the websocket connection from a node process is not really in a user-agent scenario where the script is provided by the domain. From the WebSocket-Node documentation:This is why the web3 code left it
undefinedby default. But still, you can set theOriginheader with theoptionsobject(which is the 2nd param ofWebsocketProviderconstructor) withheaders>Originkey, which will eventually written into theOriginheader.To sum up, one can just go with
--wsorigins *option for geth if security is not of much concern, or rather specifyoriginheader explicitly with some meaningful string(or the actual host) on the web3 websocket provider and pass the value onto geth's--wsorigins. One easy option is to set theOriginheader ashttp://localhoston web3 ws-provider, and run geth without--wsoriginssince geth defaults to accept connections fromhttp://localhost, which is the handier equivalent of what @Bunjin did.Example:
@vzts