XSS via check-in rights
(1) By wyoung on 2019-08-18 23:34:12 [link]
The new mechanism effectively allows anyone with login privileges to A to be able to inject arbitrary JS on B's website.
For those not familiar with the way Content Security Policies (CSPs) work, what he's saying is that if I have two different Fossil repos hosted on a single web site and one of them may have committers I don't fully trust, the new feature allows those committers to create a document in the foreign repo ("A" in drh's example) that could then reach across and see things in the "B" repo if they're both running at the same time, such as in separate tabs. While they're not running at the same time, there might be other things the foreign repo's JS code could snoop on; I'm no security guru, so let's just posit that there are such things and leave it there.
For this attack to work, a single DNS domain name would have to have multiple Fossil repos under it. For example, repo A (the foreign repo) could be shared publicly as
example.com/foreign and repo B (the more fully trusted-one) could be shared as
example.com/trusted. If it were otherwise, the browser's same-origin policy will protect you.
Solution 1: Audit Same-Origin Policy
You could also mix the two schemes, such as
foreign.example.com. That should also trigger the browser's same-origin protections.
Either way, because the repos are now on separate domains, the browser will prevent either from interfering with the other.
Solution 2: Document the Risk
While doing all of this
server-docs stuff, we should document the risk from hosting multiple repos under the same domain
This documentation should also make clear that the risk can only come from someone you've given check-in rights to — possibly quite indirectly, as drh correctly points out — but if your repo is hosting source code, as I assume the majority of Fossil repos are, that user can also check-in source code that runs under your user's account when you inevitably update and build the next version of that project on your machine! Why would we be worried about running JS from such a user and not, say, C code?
While it's true that this new feature expands the scope of JS-based attacks from those with Admin privilege only, and therefore the ability to modify the site's skin, to those with Check-in privilege as well, I don't think it's an inappropriate widening. If you trust someone to modify your embedded docs, is that not already a pretty high level of trust, which should allow running JS on that page?
This could be a big issue for chiselapp as each repo is a subdirectory under the /user/USERNAME/repository/REPONAME path.
In this case any bad actor can simply create a repo and ....
Yes, that's a pretty bad case. We do indeed need a way for this feature to be default-off so that a site like Chisel will not run it accidentally, and we need to document why you should not enable it on such a site.
I think the policy should be that the administrator gets to approve/reject any changes to executable code running on the server, or code running on clients on behalf of that server. In other words, you need more than just check-in rights in order to change the executable.
But with the new $NONCE mechanism, people have the opportunity to check in new code which starts running immediately, before I get a chance to review it.
I've been thinking about mitigations, and none of them are pretty.
(1) Require code signing for executable code
Fossil has always, from the beginning, allowed PGP signing of check-ins and tags. I still do this on check-ins to the TH3 repository, but I'm not aware of anybody else who does it.
The policy could be that $NONCE only works for check-ins that have been signed by some set of trusted reviewers. Store the "approvers" public keys in the database, and check them before setting the $NONCE.
One Problem with this approach is that, while Fossil allows PGP signed tags and check-ins (and any other artifact for that matter) it does not have any code in place to verify those signatures. Can OpenSSL be leveraged to verify a PGP clearsign? I don't know. Is there anybody willing to step up and contribute code to give Fossil the ability to verify PGP clearsigned artifacts?
(2) Keep a table of hashes for approved scripts.
We could scan for all
<script>...</script> elements in a document, compute a cryptographic hash on the content, then consult a table in the repository to see if that script is on the "approved" list. If it is on the list, then automatically add the $NONCE and let it through. If it is not on the list, then omit it entirely. Or if the script is not on the approved list and is being viewed by an administrator, give a link so that the administrator can review the script and add it to the approved list if he or she thinks it is acceptable.
For development purposes, it would be ok to allow through all scripts served from "fossil ui", perhaps. Or maybe allow scripts through if the viewer is logged in with high privilege and the check-in name is the magic "ckout" label and the developers adds that "debug=1" query parameter to the /doc URL. Something like that.
(3) Only allow $NONCE if the check-in comes from a trusted contributor
This does not work, as the name of the committer is easily forged. You must use digital signatures to get this to work.
(4) Only render $NONCE if the document comes from a leaf check-in or some other check-in (ex: "release") that is specifically approved by the admin
Some people might be willing to give normal check-in users the ability to upload executable code if they knew that the code could be disabled if it were found to be harmful. In the current implemention, it is not possible to disable the code (apart from shunning the file) since it will continue to be accessible using the check-in hash. But if the $NONCE mechanism only works for leaf check-ins, then a harmful upload could be neutralized simply by checking in a follow-up change that removed the harmful code.
(5) Only render $NONCE if there is a configuration setting enables it and the default is off
This solves the problem for people who keep the setting turned off, but is no help to those who want to turn it on. On some projects, with only a few commits, this could work. I would keep the setting turned off on the Fossil repo itself.
You could also extend this restriction to say that only documents that match a comma-separated list of GLOB patterns will render $NONCE. That does not change all that much, but does make the system more complex.
(6) Provide the ability to audit the repository for embedded documents that use
This is good and important, and should perhaps be done regardless. But what to do if you find something that somebody has slipped in? Is there a way to disable it. Do you have to shun the artifact to turn it off?
Regardless of what happens with $NONCE, I intend to add this auditing mechanism in the "Security Audit" section. I also intend to add code that verifies that CSP is enabled and issue warnings if it is not. Since
<script> works now (and has for a long time) on systems with CSP disabled, I will be thinking about ways to disable harmful scripts that might still exist deep down in some blockchains.
What if we have a default-off option that enables this substitution for local prototyping, then before deployment to a "real" site, the admin extracts the JS into a static file and overrides the default CSP in the skin header to allow JS to be pulled from
https://example.com/scripts or similar?
This solution assumes you have a mixed-mode site that can serve static content as well as Fossil hits, as on
This then appears to leave us wanting a solution for the pure Fossil case. (e.g.
fossil server) However, such a site cannot run into this XSS problem at all, due to the same-origin policy, so they can either enable the
nonce="$NONCE" substitution feature or override the default restrictive CSP.
If there's a case where this XSS problem can bite and it somehow doesn't allow mixed-mode content serving, they still have the option of overriding the default CSP.
(7) By wyoung on 2019-08-19 01:39:25 in reply to 3
Or, plan Z: throw away my nice JS document picker and go back to the prior scheme involving the matrix of document choices.
I never saw the JS document picker, but I think the matrix is quite nice. Though we may want to add some additional icons for those cases where a setup isn't documented, but is possible, versus those cases where a setup is not possible or just doesn't make sense.
I don't think any of the cases currently marked with an ❌are impossible. They do vary in likelihood of use, but none are outright impossible to pull off.
I'm certain I could make Fossil work under
inetd on Windows, for example. I wouldn't even need to use Cygwin or WSL: it would be possible to write a fully-native
inetd for Windows. (Someone probably already has!) But none of that means there is anyone who cares enough about the possibility to bother documenting it, not even me for the challenge value; as I said earlier, I want some practical benefit to fall out of solving such puzzles.
You should read "❌" as "no one has bothered to write a document showing this method yet."
I think there is another option. I don't know how hard it would be to do.
(7) Add a new "page" type of script similar to Wiki, Ticket, etc. with it's own permissions.
This would allow the addition of scripts that are allowed by those with permissions to add scripts. Wouldn't be the same as allowing it in embedded docs, but I get the feeling that we don't necessarily want to allow it there. This would act similar to the existing systems in Fossil, and just extend it with all necessary functions to manage the scripts.
Related to #1, I have a patch to Fossil that I use to allow for X.509v3 (S/MIME) signing of commits which is trivially verified using OpenSSL. I've brought it up a couple of times, but right now I still maintain this patch out of tree.
Verification done via
openssl smime -verify
Given the state of WWW in 2019, this is likely to end up fragile and susceptible to both false negatives and false positives.
The solution would be to host each repo as a sub-domain rather than a sub-directory: trusted.example.com and foreign.example.com.
This would break lots of setups that work okay right now, might not be able to migrate (suppose your server is only given one domain name
fossil.example.org and asking for changes in DNS is out of question? not to mention
chiselapp.com), all for the benefit that might not appeal to some users at all. (Hmm, my doc pages can now execute Turing-complete programs on machines used to view them. So what?)
Why would we be worried about running JS from such a user and not, say, C code?