- From: Adam Barth <w3c@adambarth.com>
- Date: Mon, 16 Jul 2012 23:10:04 -0700
- To: Henry Story <henry.story@bblfish.net>
- Cc: public-webappsec@w3.org, public-webapps <public-webapps@w3.org>, WebID <public-webid@w3.org>
- Message-ID: <CAJE5ia9RDQ5_KBxRhDtxY5aFkXQRuUVEeDqp1X9LApN=ELaR=A@mail.gmail.com>
On Mon, Jul 16, 2012 at 11:01 PM, Henry Story <henry.story@bblfish.net>wrote: > I first posted this to public-webapps, and was then told the security > discussions were taking > place on public-webappsec, so I reposted there. > > On 17 Jul 2012, at 00:39, Adam Barth wrote: > > As I wrote when you first posted this to public-webapps: > > [[ > I'm not sure I fully understand the issue you're worried about, but if I > understand it correctly, you've installed an HTTP proxy that forcibly adds > CORS headers to every response. Such a proxy will indeed lead to security > problems. You and I can debate whether the blame for those security > problem lies with the proxy or with CORS, but I think reasonable people > would agree that forcibly injecting a security policy into the responses of > other servers without their consent is a bad practice. > ]] > > > Hmm, I think I just understood where the mistake in my reasoning is: When > making a request on the CORS proxy ( > http://proxy.com/cors?url=http://bank.com/joe/statement on a resource > intended for bank.com, the browser will not send the bank.com credentials > to the proxy - since bank.com and proxy.com are different domains. So > the attack I was imagining won't work, since the proxy won't just be able > to use those to pass itself for the browser user. > > Installing an HTTP proxy locally would of course be a trojan horse attack, > and then all bets are off. > > The danger might rather be the inverse, namely that if a CORS proxy is > hosted by a web site used by the browser user for other authenticated > purposes ( say a social web server running on a freedom box ) that the > browser pass the authentication information to the proxy in the form of > cookies, and that this proxy then pass those to any site the initial script > was connected to. > > Here using only client side certificate authentication helps, as that is > an authentication mechanism that cannot be forged or accidentally passed on. > > --- > > Having said that, are there plans to get to a point where JavaScript > agents could be identified in a more fine grained manner? > No. > Cryptographically perhaps with a WebID? > That's unlikely to happen soon. Then the Origin header could be a WebID and it would be possible for a user > to specify in his foaf profile his trust for a number of such agents? But > perhaps I should start that discussion in another thread.... > I'd recommend getting some implementor interest in your proposals before starting such a thread. A standard without implementations is like a fish without water. Adam > On Mon, Jul 16, 2012 at 8:13 AM, Henry Story <henry.story@bblfish.net>wrote: > >> Hi, >> >> Two things came together to make me notice the problem I want to discuss >> here: >> >> 1. On the read-write-web and WebID community groups we were discussion >> the possibility of delegated authorisation with WebID [1] >> 2. I was building a CORS proxy for linked data javascript agents (see >> the forwarded message ) >> >> Doing this I ended up seeing some very strong parallels between CORS and >> WebID delegation. One could >> say that they are nearly the same protocol except that WebID delegation >> uses a WebID URL to identify an agent, rather than the CORS Origin >> protocol:hostname:port service id. The use case for WebID authorisation >> delegation is also different. In CORS the browser is the secretary doing >> protecting information leakage to an external JS agent, in WebID >> authorisation delegation a server would be doing work on behalf of a user. >> >> Having worked out the parallel I was able to start looking into the >> security reasoning between CORS. And this is where I found a huge security >> hole ( I think ). Essentially if you allow authenticated CORS requests, >> then any javascript agent can use a doggy CORS proxy, and use that to fool >> any browser into thinking the server was aware that it was not the user >> directly it was communicating with but the javascript agent. >> >> This is possible because CORS authenticated delegation with cookies is >> hugely insecure. CORS authenticated delegation over TLS would not suffer >> from this problem, as the proxy could not pass itself off as the browser >> user. >> >> Another reason to use TLS, another reason to use WebID. >> >> >> More of the detailed reasoning below... >> >> Henry >> >> >> [1] http://www.w3.org/wiki/WebID/Authorization_Delegation using >> http://webid.info/ >> >> Begin forwarded message: >> > From: Henry Story <henry.story@bblfish.net> >> > Subject: Re: CORS Proxy >> > Date: 7 July 2012 08:37:24 CEST >> > To: Read-Write-Web <public-rww@w3.org> >> > Cc: WebID <public-webid@w3.org>, Joe Presbrey <presbrey@gmail.com>, >> Mike Jones <mike.jones@manchester.ac.uk>, Romain BLIN < >> romain.blin@etu.univ-st-etienne.fr>, Julien Subercaze < >> julien.subercaze@univ-st-etienne.fr> >> > >> > >> > On 7 Jul 2012, at 07:35, Henry Story wrote: >> > >> >> >> >> On 6 Jul 2012, at 23:10, Henry Story wrote: >> >> >> >>> Hi, >> >>> >> >>> I just quickly put together a CORS Proxy [1], inspired by Joe >> Presbrey's data.fm CORS proxy [2]. >> >>> >> >>> But first, what is a CORS proxy? >> >>> -------------------------------- >> >>> >> >>> A CORS [3] proxy is needed in order to allow read-write-web pages >> containing javascript agents written with libraries such as rdflib [5] to >> fetch remote resources. Pages containing such javascript agents are able to >> fetch and parse RDF on the web, and thus crawl the web by following their >> robotic nose. A CORS Proxy is needed here because: >> >>> >> >>> 1- browsers restrict which sites javascript agents can fetch data >> from to those from which the javascript came from - the famous "same origin >> policy" ( javascript can only fetch resources from the same site it >> originated from) >> >>> 2- CORS allows an exception to the above restriction, if the resource >> has the proper headers. For a GET request this is the >> Access-Control-Allow-Origin header >> >>> 3- most RDF resources on the web are not served with such headers >> >>> >> >>> Hence javascript agents running inside web browsers that need to >> crawl the web, need a CORS proxy, so that libraries such as rdflib can go >> forward and make those requests through the proxy. In short: a CORS proxy >> is a service that can forward the request to the appropriate server and on >> receiving the answer add the correct headers, if none were found. >> >>> >> >>> Security >> >>> -------- >> >>> >> >>> So is there a security problem having a CORS proxy make a GET request >> for some information on behalf of JS Agent? This is an important question, >> because otherwise we'd be introducing a security hole with such a proxy. >> >>> >> >>> In order to answer that question we need to explain why browsers have >> the same origin restriction. >> >>> >> >>> The answer is quite simple I think. A Javascript agent running in a >> browser is using the credentials of the user when it makes requests for >> resources on the web. One can therefore think of the browser as acting as a >> secretary for the javascript agent: the JS agent makes a request, but does >> not log in to a web site, but instead asks the browser to fetch the >> information. The browser uses its authentication credentials - the user of >> the browser's credentials to be precise - to connect to remote sites and >> request resources. The remote site may be perfectly fine with the browser >> user/owner having access to the resource, but not like the idea of the >> agent in the browser doing so. (after all that could be some JS on some >> random site the user came across) In order to avoid this danger, the >> browser sends along with its requests an Origin: header and the URL of the >> host where the javascript was found. The service receiving such a request >> must respond with an Access-Control-Allow-Origin header to make clear that >> it is ok with the JS Agent receiving this information. >> >>> IF the browser finds out that the web site allows the JS Agent to >> receive the information too, then it will pass the information on to it. >> >>> >> >>> This is a bit like what we discussed about a secretary agent on a web >> site requesting a resource On-Behalf-Of a user in our WebID delegation [5] >> . Here the Browser is the secretary, and the JS agent is the user being >> acted on behalf of. The difference is that the Secretary/Browser in this >> case is well known, and the JS Agent is the unknown; or put another way in >> the CORS case the server's authorisation policies were geared towards >> giving the browser owner access and not for the JS it is acting on behalf >> of, whereas in our delegation use case we were imagining the remote >> resource not being initially authorised directly to the secretary, but >> rather for the agent she was acting on behalf of. (small shift in focus) >> >>> >> >>> Anonymous Proxy >> >>> >> >>> So now it should be clear why using the proxy is not creating a new >> security issue. This is because the Proxy is not the Browser and so has NOT >> authenticated as the user when it is making a request. The Proxy is making >> an anonymous request to a remote resource. This remote resource is >> therefore public. As such it is fine to allow any JS agent to read it. This >> is particularly true of GET requests. But it should even be true of POST, >> PUT and DELETE requests. If those are public and allow anonymous usage, >> then it should be possible for a CORS proxy to do that on behalf of a >> javascript. It is quite possible that the CORS proxy might want to >> authenticate the user in order to not become a vector for denial of service >> attacks, and it could even give users a history of requests it made. Unless >> perhaps some people are placing identification cookies in URLs! (But one >> could argue that's their problem?!) >> >> >> >> Ah, there is an issue here I just realised. Currently my proxy is >> passing along all the headers sent to >> >> it by the client. Well all minus the "Accept" headers, because my >> Proxy only understands a subset of RDF >> >> serialisations. ( And the proxy has to interpret the rdf in the >> serialisation in order to transform relative >> >> URIs into full URIs. The proxy we are building is not a real HTTP >> Proxy, because those would require a browser to be set up correctly and >> doing that would probably not be possible on a case by case basis ) >> >> >> >> >> >> case CORSFetch(url, headers) => { >> >> val hdrs = for (key <- (headers - "Accept").keys; >> >> value <- (headers(key))) yield (key,value) >> >> val promiseIteratee: Promise[Iteratee[Array[Byte], >> Either[CORSException, CORSResponse[Rdf]]]] = >> >> WS.url(url.toExternalForm). >> >> withHeaders(hdrs.toSeq:_*). >> >> withHeaders("Accept" -> >> "application/rdf+xml,text/turtle,application/xhtml+xml;q=0.8,text/html;q=0.7,text/n3;q=0.2"). >> >> get { >> >> response: ResponseHeaders => >> >> .... >> >> >> >> So here all the headers - "Accept" are passed along. But that means >> cookies are passed along too! >> >> And if cookies are passed along, then the user's identifications are >> passed along, and that would be >> >> problematic, for the reasons given above. >> >> >> >> Luckily as explained by this CORS article >> >> >> https://developer.mozilla.org/en/http_access_control#Requests_with_credentials >> >> >> >> unless the javascript makes the XMLHttpRequest with the >> withCredentials property set to true, cookies are not sent. Also - even >> then - unless the server in its response places the header >> >> >> >> Access-Control-Allow-Credentials: true >> >> >> >> in the header the browser will not pass the credentials on to the >> javascript (and it also has to be explicitly about which origin it allows) >> >> >> >> But this raises another issue: it would be extremely easy for me to >> change my proxy to add those headers to the response. If so this means that >> any evil script, can communicate with such a slightly enhanced proxy that >> does the transformation described. And so CORS is protecting nothing >> seriously here. >> > >> > An evil proxy could also - come to think of it - change the headers for >> the Origin of the request to one that any bank would feel comfortable with, >> so that with this system in place a service that would allow access to some >> Origin, would allow access to all of them (via evil proxies). >> > >> > Interestingly enough, this attack does not work with WebID over TLS >> authentication. The Proxy cannot know the browser's private key, and so >> cannot authenticate to the remote service other than by using its own >> certificate and WebID. Identity here is not passed by a spoofable cookie, >> but as a cryptographically verified TLS session. >> > >> > In a secure WebID world one may still want the proxy to act on behalf >> of the browser owner. This could be done if the Proxy specified that it was >> acting on behalf of the user of the browser using WebID Authorization >> Delegation [5]. This would be something that the resource serving service >> could both verify and evaluate. >> > >> > >> >> >> >> >> >> >> >>> >> >>> Authenticated Proxies >> >>> >> >>> Now let us assume we have CORS proxies that can also authenticate >> with WebID. If they did so, then they would have to follow the same rules >> as the Browser: they should not pass on the information unless the server >> had allowed them to by setting the correct Access-Control-Allow-Origin >> headers, and allowing the javascript access, since the information they >> were given was not meant for this other JS agent. >> >>> >> >>> But things could get a little more advanced yet: Imagine that the >> proxy authenticated itself on behalf of the user who made the request. The >> server serving the resource could then verify that the proxy was allowed to >> do act on behalf of the user using the procedure outlined in WebID >> delegation [5]. Then we would have to deal with WebID delegation plus CORS >> delegation. The server would know that the proxy was acting on behalf of >> the user, and that the user was acting on behalf of the JS Agent... This >> may be the use case we had trouble putting our finger on at this weeks >> WebID teleconf... >> >>> >> >>> >> >>> Improving CORS with LinkedData >> >>> ------------------------------ >> >>> >> >>> Here is an idea to improve CORS: In WebID delegation we found a way >> to let the server know what relation the secretary had to the agent she >> acted on behalf of. That Agent she was acting on behalf of could add a >> >>> >> >>> :me cert:secretaty myprofile:secr . >> >>> >> >>> relation to his profile. This would help the server know what >> relation the secretary had to the agent she claimed to be working for. >> >>> >> >>> With CORS this relationship is nowhere made explicit (as far as I can >> see): there is no way for me to tell a web site that the javascript I am >> using ( and that is hosted on my freedom box) is something I would like the >> server I am connecting to trust, as opposed to some javascript that just >> started executing itself on some server I came across. This makes me thing >> that >> >>> 1. it would be useful if JS could be signed so as to have a better >> identity than just the identity of a whole site >> >>> 2. we could create a relation such as the cert:secretary one that >> would allow me in my profile to say for example >> >>> >> >>> :me cert:trustedJS <https://bblfish.net> . >> >>> >> >>> IT's really much to vague, but it would make it easier for people to >> trust my JS. >> >>> >> >>> >> >>> OTHER TODOS >> >>> ----------- >> >>> >> >>> There are really quite a lot of details questions left open for CORS >> proxies. Should a CORS proxy return a 203? When? Who should it deal with >> error messages? .... I think we should organise those on a wiki page, and >> keep the discussion alive. My code at present was put together really >> quickly, but it made me ask a lot of questions whilst writing it. >> >>> >> >>> >> >>> Henry >> >>> >> >>> >> >>> [1] the code is here >> https://github.com/bblfish/Play20/blob/webid/framework/src/webid/src/main/scala/org/w3/readwriteweb/play/CORSProxy.scala >> >>> But I have not placed it online yet. >> >>> [2] Joe's proxy is online and available by sending GET requests here: >> http://data.fm/proxy?uri={uri} >> >>> and the code is somewhere here: https://github.com/linkeddata/data.fm >> >>> [3] http://www.w3.org/TR/cors/ >> >>> [4] https://github.com/linkeddata/rdflib.js >> >>> [5] http://www.w3.org/wiki/WebID/Delegation >> >> >> > > Social Web Architect > http://bblfish.net/ > >
Received on Tuesday, 17 July 2012 06:11:08 UTC