Fossil Forum

Forum
Login

XSS via check-in rights

(1) By wyoung on 2019-08-18 23:34:12 [link]

The Problem

In another thread, drh raised this concern regarding my new nonce="$NONCE" feature for embedded docs:

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

Which brings me to my first mitigation idea: have the Security Audit page look through the browser's tabs and cookies using JavaScript to see if it can find other Fossil repos on the same domain, and if so, warn that they're sharing the browser's same-origin, which you might not want to allow. The solution would be to host each repo as a sub-domain rather than a sub-directory: trusted.example.com and foreign.example.com.

You could also mix the two schemes, such as example.com/trusted and 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?

(2) By anonymous on 2019-08-19 00:39:59 [link] in reply to 1

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 ....

(4) By wyoung on 2019-08-19 01:28:10 [link] in reply to 2

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.

(3) By drh on 2019-08-19 00:49:01 [link] in reply to 1

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.

People check in javascript code to Fossil all the time. But none of that code becomes executable until I log onto the server and recompile and reinstall the binary. So that is acceptable, since I (the administrator) have the opportunity to approve or reject any changes before they go live.

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 <script>

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.

(6) By wyoung on 2019-08-19 01:31:17 and edited on 2019-08-19 01:32:02 [link] in reply to 3

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?

(8) By wyoung on 2019-08-19 02:03:47 [link] in reply to 6

This solution assumes you have a mixed-mode site that can serve static content as well as Fossil hits, as on fossil-scm.org.

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.

(11) By ckennedy on 2019-08-19 21:02:39 [link] in reply to 7

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.

(12) By wyoung on 2019-08-20 01:44:38 [link] in reply to 11

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."

(14) By wyoung on 2019-08-20 02:10:29 and edited on 2019-08-20 02:34:56 [link] in reply to 12

(9) By ckennedy on 2019-08-19 17:47:21 [link] in reply to 3

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.

Thanks.

(15) By rkeene on 2019-08-20 17:14:20 [link] in reply to 3

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.

http://www.rkeene.org/viewer/tmp/fossil-6ca400a315-add-smime-support-1rsk.diff.htm

Verification done via openssl smime -verify

(10) By anonymous on 2019-08-19 18:57:50 [link] in reply to 1

have the Security Audit page look through the browser's tabs and cookies using JavaScript to see if it can find other Fossil repos on the same domain

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?

When you clone a repo, you can read the code first and decide to run it (or not to run it) later. When you open a website, you are already running someone else's JavaScript, unless you happen to be one of the people to disable it by default (and make browsing the WWW impossible for the most part).

If only user-written JavaScript didn't run in the same context as Fossil web interface, then it wouldn't be possible to check-in some JavaScript for the site admin to stumble upon and have something executed with their access rights that a regular user cannot. But this is probably a case of an attacker happening to be on the inner side of the airtight hatchway, since Fossil "cathedral" tradition means trusting a small group of users with check-in rights, rather than half-trusting a larger group, as "bazaars" do.