-
Notifications
You must be signed in to change notification settings - Fork 2.8k
luci-base: http.uc: add CSP headers to http response #8161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
2d0c4b2 to
7d37d28
Compare
I didn't try it, but does this prevent "System -> Backup/Flash" from downloading .img files directly? How about Attended Sysupgrade, which does a bunch of POST and GET fetches to do its upgrades? You can see some of this at Line 116 in a0531c7
|
|
Good catch. Attended sysupgrade fails, and will need adjustment to the CSP headers. I'll add rules as required to ensure it passes.
I'm not sure what to test in System -> Backup/Flash. I don't see anything there causing (or which should cause) HTTP requests anywhere? |
Oh, ok, I didn't dig into it, I thought it did a |
7d37d28 to
b2275b5
Compare
|
Adding https://sysupgrade.openwrt.org fixes the default//OOB config, but I see that it will break with custom configurations:
I guess probably the simplest, most pragmatic option is just to set connect-src to allow anything. The CSP will still be a major improvement over what is already there (which is no hardening at all). |
b2275b5 to
9c46809
Compare
|
With the testing I've been able to to, it seems like attended sysupgrade now works as well! |
|
Yeah, changing the upstream url, or adding one to "Rebuilders" complicates it. Could the ASU code add exceptions somehow for the duration of its use??? You can use https://sysupgrade.guerra24.net as primary server, or added as a rebuilder, if you felt like testing that... |
|
I went for the pragmatic and simple solution: allow fetch from any HTTPS-host. It’s not perfect, but it’s still clearly a major improvement over what is there today due to the restricted script and default permissions. In absence of any other areas to validate/test, I don’t plan any other updates to this PR. |
|
Hmm, maybe another one. I have luci-app-statistics installed on one of my APs, and am getting this when I try to view the generated images: Hacked it up to contain |
|
And firefox says But how do browsers deal with these "bad" ones? Just ignore them? In which case, who cares if it "errors"? That one specifically seems to have very narrow support, but if it is supported, seems like it should be there. |
|
For reasons you're now discovering, this is why we've not bothered with extended security measures like this, since most of the customisation people do or use depends on the freedom to do so. There are perhaps only a few apps which call to external resources like this, and the sources are entirely verifiable. Introducing this change will likely break some functionality somewhere... :/ |
Nice. That sounds like a reasonable addition as well.
They ignore them. They can’t apply rules/restrictions they don’t know about. This one in particular is required by Safari and does no harm elsewhere as far as I’ve tested. |
These are IMO simple issues to work around or loosen up. If people are qualified to customize these things, they’re probably qualified to update or remove this header too?
People might get malicious browser extensions which wants to inject scripts into your router interface. This would help protect against that to a certain extent.
IMO script-src is the most important one to get in and the rest can be loosened where there’s any plausible reason for doing so. |
|
From a brainstorming POV, would it be possible to implement this as a luci-app? luci-app-csp? Then those who want it can install it and it could contain a config-pane (with these defaults) which injects the headers into |
|
It could be made configurable indeed. Add a option somewhere maybe in the system menu or something, different degrees of allowing things. Add a help text to explain. |
Or just a text area to write your CSP from start to finish, which is injected into So, an interface to do it properly is available, without having the end user tinkering with ucode files, but the CSP must be written by the end user. So, nothing broken by installing the app. |
This sounds more viable from a practical point of view, and for my first Luci submission 😄 Provide a reasonable default, and allow whatever the user wants. If non-empty, use as a CSP headers. If empty, don't provide a CSP header. I think that's OK for a version 1 implementation, isn't it? |
Depends on the default, if the default is empty then this is NOK (people would not know what to fill in). I do like the idea of user defined. I would still go for drop down, 1) none 2) loose 3) tight 4) user defined. {previewing the new header) This is most user friendly and will serve everybody. I'm guessing 99% of the people will use 1), 2) or 3). Do not choose for the easy solution just because you do not have the experience to implement, there are plenty of people willing to help out. I do wonder what the default should be, 1) or 2) or 3)... |
9c46809 to
082d034
Compare
|
I've added and pushed a first iteration of using UCI for settings and a minimal UI for it.
Do we have any good examples in the luci interface of a dropdown which automatically populates the value of an accompagnying text-field? |
This comment has been minimized.
This comment has been minimized.
Yes there should be plenty around, for example look at luci-app-usteer which I made. |
| o.default = o.enabled; | ||
| o.rmempty = false; | ||
|
|
||
| o = s.taboption('general', form.Value, 'csp_policy', _('CSP Policy'), _('Custom Content-Security-Policy header value. Leave empty to use the default policy.')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a textbox.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first time I'm trying to build something in the Luci UI. From what I can tell, this is how most textboxes in the uhttpd.js file is defined.
Could you be more concrete wrt to what you think should be "textboxed" here?
082d034 to
1284716
Compare
nevermind, you do not get what i mean |
3074e8f to
ad1ec17
Compare
ad1ec17 to
7c60817
Compare
This comment has been minimized.
This comment has been minimized.
e55cbff to
3a5fea1
Compare
| csp_mode_option.rmempty = false; | ||
|
|
||
| var csp_policy_option = s.taboption('general', form.Value, 'csp_policy', _('Custom CSP Policy String'), _('The Content-Security-Policy header-value used in custom-mode.') + "<br />" + _(' WARNING: Wrong values for this setting can render the web-UI inaccessible and require recovery by SSH.')); | ||
| csp_policy_option.default = 'default-src \'none\'; script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' \'trusted-types-eval\'; img-src \'self\' data: blob:; style-src \'self\' \'unsafe-inline\'; connect-src \'self\' https://sysupgrade.openwrt.org;' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@systemcrash Wouldn't it be more appropriate, for here and for http.uc, to create a dedicated section in /etc/config/uhttpd where we put the different CSP default values, rather than hardcoding them in the js and ucode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no?
On the one hand I'd like to avoid code-repetition, embrace modularity and extensibility. That talks in favour of your suggestions.
On the other hand I'd like to make it simple to use. Not just now, but over time.
I think "strict" and "permissive" are tightly connected to the OpenWRT codebase and release itself. What is possible and/or required for "strict" may change over time. If we store this in /etc/config/uhttpd somewhere, user's will typically choose to preserve these settings when doing upgrades.
If the default values for those presets have changed between releases, that will be a upgrade which can fail in unepexted/undesirable ways. Keeping these presets fixed in code avoids that.
And I think that is a bigger problem/risk than these presets being hardcoded in code. That's an argument for keeping things as is.
I'm leaning more towards that direction, but I'm not 100% set on this if someone can explain a way we can make it "upgrade-safe".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josteink I don't understand your concerns. It doesn't change anything from the user's point of view, the strings will just be stored in the httpd config file, and not in the code any more.
It will be easier for developers to maintain, and it doesn't change anything from the user's point of view.
The section containing them will not be editable from JS.
I am waiting for the opinion of a maintainer, in case there are any contraindications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just chiming in with a concrete example while awaiting the @systemcrash 's opinion:
- The user installs OpenWRT release with CSP support (lets say 25.12.1)
- CSP presets/profiles are stored in
/etc/config/uhttpd- based on that release - A new Luci/OpenWRT release comes out later (lets says 26.10.0), with some different JS in the browser which requires permissions not granted by the existing CSP preset/profiles. We update the profiles as well to ensure it works.
- User upgrades to 26.12.0 and checks the option "keep existing config".
- He will get the config file from 25.12.1 with the old presets, but the Luci code which requires the 26.10.0 presets.
- Luci may fail to load as a result. User may be unhappy. Depending on his ability to SSH in and edit the relevant config files, he may just say the upgrade "bricked" his router.
That's the scenario I am worried about.
If I'm worrying about something which is trivial to solve, by all means, I'm happy to go that route. Just let me know how!
Just in case it's not obvious: This is my first OpenWRT contribution and I'm fairly new to the source-code 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josteink I understand your concern better now, I don't know if there's a way to remedy this.
What's unfortunate at the moment, from a maintainability standpoint, is the repetition of the "strict" CSP in JS and ucode, as well as their decentralisation (not all written CSP at the same place).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a script in /etc/uci-defaults/ could be a solution, I'm curious to hear a maintainer's opinion on this matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Custom is going to be custom, no matter what, so that's obviously something which should go in /etc/config/uhttpd as it does now.
is the repetition of the "strict" CSP in JS and ucode
Technically speaking the CSP in the JS is not "strict" but a default-value for "custom", as a convenience, based on "strict". I thought it would be a "nice-to-have" sort of thing, but if it makes things complicated/decentralized, we could just remove the default as well, leaving the only 2 profiles in the ucode part of the codebase.
Either way I'm curious to hear about other, possibly better solutions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@systemcrash ping
applications/luci-app-uhttpd/htdocs/luci-static/resources/view/uhttpd/uhttpd.js
Outdated
Show resolved
Hide resolved
applications/luci-app-uhttpd/htdocs/luci-static/resources/view/uhttpd/uhttpd.js
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
3a5fea1 to
7882662
Compare
|
The problems I see at the moment are as follows:
This potentially introduces new behaviour, yet does not surface a control element. The behaviour is in base, but the config element is in a separate app. luci-app-uhttpd seems like a good place for it. But luci-app-uhttpd configures uhttpd behaviour. This change is about controlling the front-end behaviour. It seems to me the better placement would be under system->admin->http, which is about the only decent place in base. This addition correctly 'fails open' - if there is no config present, it remains in a disabled state. You might want to flag that in the GUI - that disabled is the default state. It also might be a bit inconsistent, but use " to wrap the string, and ' to quote the elements in it. What settings can render the UI inaccessible? |
I may be misunderstanding something, but the file I modified (uhttpd.js) for the settings is where these kind of HTTP controls used to be already.
This only introduces a new behaviour, if configured. What control elements are you looking for, beside the UI provided?
The setting you are referring to seems (to my untrained eye) to be uhttpd.js. Check the URL for the admin-page you are referring to:
When grepping the source for "Redirect to HTTPS", this setting is only found in
Good idea. I'll make it so.
Fine by me 😄
Only setting which can render the UI inaccessible is "custom" with a bad policy which excludes required JS scripts, in the area clearly marked as "experts only", and with a safe default provided. Edit: I see my grep was incorrect and there is a different module I can (should?) insert these changes into. I'll make it so too! @systemcrash : I assume Or should I keep it in both? |
7882662 to
d7d577b
Compare
|
All comments should in theory be resolved? Anything else I should change? 😄 |
|
If you're aware of any settings already which might be problematic, we might surface a time limited warning when those are entered. I propose a I suggest that such a long string warrants a textbox, so everything is visible at once. If the setting is available in both places (app and mod system), that's OK. |
No settings are by themselves "problematic" more so than the others.
Right now, if you do a default OpenWRT install on any device, apply any of the suggested modes (including custom with default value policy) and click "Save & Apply", everything default in OpenWRT will keep on working 100%. You will not be prevented from entering the same admin-section to change the setting later. Does that clear up the expected behaviour sufficiently?
The only place where there is a real risk is when using "custom" mode. We cannot validate up front if that will be successful or not. So I guess that means |
|
@systemcrash : I tried the following code to just to test. It works, kinda, but I always get the validate-handler invoked twice on changes, leading to the ui popup to appear twice as well. csp_mode_option.validate = function(section_id, value) {
// triggers UI warning when using custom-mode with UI timer based fallback
if (value === "custom")
{
ui.addTimeLimitedNotification(_('Danger zone'), E('p', _('Custom CSP header will be used as is. If deemed invalid by the browser, you will need to use SSH to reset the value in /etc/config/uhttpd.')), 60000, 'warning');
}
return true;
};
Is this a general luci problem, or is it anything else I'm doing wrong? 😄 |
|
No, validate is triggered on the cfgvalue and load and I think another one of the function calls, IIRC. :/ That custom string? I can't see anything. That's why it should be a text box. What I mean are policies like the following: So scanning for the key-word |
Is there a convention somewhere on how to avoid double warnings then, or should I just leave it?
What's the best/right way to do that? I don't seen any
To be absoultely clear, the default-value for custom (based on strict) intentionally defaults to That is, you use the other policies to whitelist only the things you know you need. So this warning will also trigger for the default value. Also, for instance, for Creating a CSP-validator which is meaningful is going to be a much bigger job than just getting the basic settings in there, and it will be 10x more complicated than the actual feature already proposed. If someone later feels CSP is great, and they want a better interactive editor for that... I would delegate that responsibility to a separate, installable It is my opinion that this PR, as is, will provide a substantial security improvement to LUCI compared to how it was before. Let's resolve the above issues, and leave the CSP-editor/validator aspects to eventually be solved later, if there turns out to be a need? |
There is, IIRC. I just can't remember it now... let me have a think. Ah, sorry - take a look at: https://openwrt.github.io/luci/jsapi/LuCI.form.TextValue.html There are some in use in the code-base. Take a peek at how others are used.
I took a look at MDN which says: the single value So we can at least add a note about that in the GUI: e.g. |
This commit adds support for Content Security Policy to Luci Web. Content Security Policy is a very effective security hardenining for browser-based applications. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP The CSP can be enabled/disabled and customized via uci. The default "strict" provided CSP effectively limits the Luci web-app to only allow: - scripts from its own origin/IP, and nowhere else. - css from its own origin/IP, and nowhere else. - images from its own origin/IP, and nowhere else. - fetch/etc from own origin/IP. and sysupgrade. nowhere else. - (etc) Softening exceptions: - Allow javascript to use inline scripts, eval() and trusted types. - Allow CSS to use inline styles. - Allow IMG-tags with data: and blob:-type URLs. - Allow XHR/fetch for any https:// url - required to support attended sysupgrade. trusted-types-eval is required by Safari on MacOS, supported in Firefox Nightly, and ignored in Chrome/Chromium. This CSP should prevent almost any kind of XSS or script-injection while at the same time allow Luci to perform any task it needs to do. The default "permissive" allows more https:// sources. "Custom" is defined by the user entirely. Signed-off-by: Jostein Kjønigsen <jostein@kjonigsen.net>
- add dropdown to select CSP-mode - add textbox to configure custom CSP policy Signed-off-by: Jostein Kjønigsen <jostein@kjonigsen.net>
- add dropdown to select CSP-mode - add textbox to configure custom CSP policy Signed-off-by: Jostein Kjønigsen <jostein@kjonigsen.net>
|
No new changes in latest pull. Have been busy during Christmas as everyone else 😄 Just did a rebase on upstream git master to ensure I'm not walking into any merge-conflicts. |




Signed-off-by: <my@email.address>row (viagit commit --signoff)<package name>: titlefirst line subject for packagesPKG_VERSIONin the MakefileThis PR adds support for Content Security Policy to Luci Web.
Content Security Policy is a very effective security hardening for browser-based applications:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
CSP for OpenWRT/Luci is implemented in 4 different modes:
This option is stored in
/etc/config/uhttpdand also manageable via the Luci Web UI under "System" -> "Administration" -> "uHTTPd" -> "HTTP(s) Acces" -> "General settings".