[CKAN-Security] Potential http Cache poisoning

Adrià Mercader adria.mercader at okfn.org
Fri May 10 12:36:07 UTC 2019


Hi Cody,

Just to keep you updated on this front. We have confirmed that this is an
issue on 2.8.x + the default nginx setup suggested in the docs.

We are planning on releasing a patch release including a patch to ensure
Cache-control: private headers are included on all Flask requests:

diff --git a/ckan/config/middleware/flask_app.py
b/ckan/config/middleware/flask_app.pyindex c6d437a72..08e038bff
100644--- a/ckan/config/middleware/flask_app.py+++
b/ckan/config/middleware/flask_app.py@@ -327,6 +327,10 @@ def
ckan_after_request(response):     url =
request.environ['CKAN_CURRENT_URL'].split('?')[0]      log.info(' %s
render time %.3f seconds' % (url, r_time))+    # Default to
cache-control private if it was not set+    if
response.cache_control.private is None:+
response.cache_control.private = True      return response

It would be great if you could apply it and see if that solves the problem
on your end.
This might sound extreme but essentially is what CKAN has been doing in all
previous versions. We will work on a better approach going forward but
right now the priority is patch any potential vulnerabilities.

Below is a longer description of the issue from our internal security
tracker. Hope it makes sense. If you have more questions feel free to reach
out.

Have a nice weekend.

Adrià

---

In terms of patching this in 2.8.3, defaulting to Cache-control: private on
Flask requests is trivial:

diff --git a/ckan/config/middleware/flask_app.py
b/ckan/config/middleware/flask_app.pyindex c6d437a72..08e038bff
100644--- a/ckan/config/middleware/flask_app.py+++
b/ckan/config/middleware/flask_app.py@@ -327,6 +327,10 @@ def
ckan_after_request(response):     url =
request.environ['CKAN_CURRENT_URL'].split('?')[0]      log.info(' %s
render time %.3f seconds' % (url, r_time))+    # Default to
cache-control private if it was not set+    if
response.cache_control.private is None:+
response.cache_control.private = True      return response

This effectively means that all Flask requests in 2.8 (api, home, user,
admin, dashboard and feeds requests) will have the Cache-control: private
added to it.

In terms of impacting current 2.8.x instances I had a closer look at the
logic <https://github.com/ckan/ckan/blob/master/ckan/lib/base.py#L202:L226>
used in Pylons to assign public cache headers.

Taking into account that if we pass extra_vars to the templates caching is
not allow (which I find very questionable), these are the only endpoints
where caching was enabled on Pylons:

home.index (cache forced)home.aboututil.i18_js_strings (cache
forced)admin.trashuser.indexuser.readuser.resetuser.followersuser.activity_streamuser.dashboard_datasetsuser.dashboard_organizationsuser.dashboard_groups

That's assuming that there is no session data (eg flash messages) and the
user is not logged in.

But, and that's a big but, unless you have set the ckan.cache_expires
<https://docs.ckan.org/en/latest/maintaining/configuration.html#ckan-cache-expires>
config option, the header you will get is:

Cache-Control: public, max-age=0, must-revalidate

Which, to all intents and purposes it's pretty close to no-cache (see eg
this <https://stackoverflow.com/a/19938619/235993>).

So, I think that the likelihood of breaking someone's cache system by
defaulting all Flask requests to private is pretty low and the patch above
could be applied to CKAN 2.8.3.

In master / 2.9 where all requests will be served by Flask we can choose a
better approach, that is managed at the view level and not in a centralized
place.












On Mon, 29 Apr 2019 at 13:33, codywboyko <codywboyko at gmail.com> wrote:

> Hi Adrià,
>
> Thanks for the reply.
>
> For this I used the source install on ubuntu 16.04 and 18.04 following the
> docs. And for nginx i also tested against the docs  configuration. If i
> disable caching or use paster in dev mode theres no issue present. Its only
> when caching is used.
>
> proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache:30m max_size=250m;
> proxy_temp_path /tmp/nginx_proxy 1 2;
>
> server {
>     client_max_body_size 100M;
>     location / {
>         proxy_pass http://127.0.0.1:8080/;
>         proxy_set_header X-Forwarded-For $remote_addr;
>         proxy_set_header Host $host;
>         proxy_cache cache;
>         proxy_cache_bypass $cookie_auth_tkt;
>         proxy_no_cache $cookie_auth_tkt;
>         proxy_cache_valid 30m;
>         proxy_cache_key $host$scheme$proxy_host$request_uri;
>         # In emergency comment out line to force caching
>         # proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
>     }
>
> }
>
>
> Thanks,
> Cody
>
>
>
> -------- Original message --------
> From: Adrià Mercader <adria.mercader at okfn.org>
> Date: 2019-04-29 5:35 AM (GMT-05:00)
> To: codywboyko at gmail.com
> Cc: CKAN Security Alerts/Discussions <security at lists.okfn.org>
> Subject: Re: [CKAN-Security] Potential http Cache poisoning
>
> One more thing,
>
> Are you using the standard CKAN package installation? Have you customized
> the cache settings in Nginx? Can you share your nginx configuration?
>
> Thanks a lot,
>
> Adrià
>
> On Mon, 29 Apr 2019 at 11:32, Adrià Mercader <adria.mercader at okfn.org>
> wrote:
>
>> Thanks for the report Cody. We'll discuss it and get back to you as soon
>> as we can. Also feel free to join one of our public meetings in case you
>> want to discuss it further.
>>
>> Dev Meetings Notes - HackMD <https://hack.allmende.io/ckan-meeting>
>>
>> On Mon, 29 Apr 2019 at 11:24, Cody B <codywboyko at gmail.com> wrote:
>>
>>> Hi there,
>>>
>>> I've come across this potential bug previously but was unsure if it was
>>> a CKAN core issue or just an extension issue.  After further testing I
>>> believe this is originating in CKAN core.
>>>
>>> I have further notes on my previous experience with this issue that I'm
>>> happy to share if you'd like. Also feel free to let me know of any other
>>> way I can assist.
>>>
>>> *Potential security bug:*
>>>
>>> CKAN does not seem to pass the correct cache-control headers in some
>>> cases. This is allowing cache servers to store some user information and
>>> return this information to unauthenticated users. See reproducible steps
>>> below. The below example is a simple one to demonstrate. This issue
>>> thread
>>> <https://github.com/NaturalHistoryMuseum/ckanext-ldap/issues/40#issuecomment-485869920>
>>> shows the potential larger issues. Note: when I posted there I didn't
>>> really believe this was a larger issue (and I may be wrong).
>>>
>>> *Reproducible steps:*
>>>
>>>    1. Clean install of CKAN 2.8 on Ubuntu 16.04 LTS (and on 18.04)
>>>    following the default Source install documentation
>>>    <https://docs.ckan.org/en/2.8/maintaining/installing/install-from-source.html>
>>>    and deployment with NGinx and Apache
>>>    <https://docs.ckan.org/en/2.8/maintaining/installing/deployment.html>
>>>    2. Add new sysadmin user.
>>>    3. Navigate to a protected action in a new blueprint
>>>    (***All browser testing is done in new chrome incognito windows
>>>    after closing the previous ones***)
>>>    *Tested example endpoint:* `http://
>>>    <site-domain>/api/3/action/dashboard_activity_list`
>>>    4. The expected response is given: `{"help": "http://<domain>/api/3/action/help_show?name=dashboard_activity_list",
>>>    "success": false, "error": {"message": "Access denied: You must be logged
>>>    in to access your dashboard.", "__type": "Authorization Error"}}`
>>>    5. New browser - Navigate to same url but pass an Authorization
>>>    header with a SysAdmin user's API key. (I used a proxy to intercept the
>>>    request and add the header to keep testing in the browser but presumably
>>>    this would be the same with a script)
>>>    6. The expected response is given: ` {"help": "http://<domain>/api/3/action/help_show?name=dashboard_activity_list",
>>>    "success": true, "result": [{"user_id":
>>>    "45ddssaa9c4-dasdas-4451-b29e-c399a2db93d6", "timestamp":
>>>    "2019-04-24T14:59:28.939536", "is_new": false, "object_id":
>>>    "45ddssaa9c4-dasdas-4451-b29e-c399a2db93d6 ", "revision_id": null,
>>>    "data": {}, "id": "36asdsas1b9-ece6-4a02-b211-f9afdsaabb927",
>>>    "activity_type": "new user"}]}  `
>>>    Note: it's a brand new site with no data really so no activity, but
>>>    success is true and this is the expected response.
>>>    7. New browser - Navigate to same url without intercepting request
>>>    or including an authorization header with the API key and you get the same
>>>    response `{"help": "http://<domain>/api/3/action/help_show?name=dashboard_activity_list",
>>>    "success": true, "result": [{"user_id":
>>>    "45ddssaa9c4-dasdas-4451-b29e-c399a2db93d6", "timestamp":
>>>    "2019-04-24T14:59:28.939536", "is_new": false, "object_id":
>>>    "45ddssaa9c4-dasdas-4451-b29e-c399a2db93d6 ", "revision_id": null, "data":
>>>    {}, "id": "36asdsas1b9-ece6-4a02-b211-f9afdsaabb927", "activity_type": "new
>>>    user"}]}`
>>>     Note: This is a non-authenticated user request, the expected
>>>    response should be the same as bullet 4.
>>>    8. Clear nginx cache or wait for it to expire (default is 30 min).
>>>    In my case I delete the cache directories, then restart the servers.
>>>    `sudo rm -rf /tmp/nginx_*`
>>>    `sudo service nginx restart && sudo service apache2 restart`
>>>    9. New browser - Navigate to same url *without* intercepting request
>>>    or including an authorization header with the API key and you get the
>>>    expected response as per bullet 4 : `{"help": "http://<domain>/api/3/action/help_show?name=dashboard_activity_list",
>>>    "success": false, "error": {"message": "Access denied: You must be logged
>>>    in to access your dashboard.", "__type": "Authorization Error"}}`
>>>
>>> *What I've noticed:*
>>>
>>>    - This seemed to happen in various areas starting in CKAN 2.8.
>>>    - A few authorization extensions (issue thread)
>>>    <https://github.com/NaturalHistoryMuseum/ckanext-ldap/issues/40#issuecomment-485869920>
>>>    seem to be uncovering this when users login and logout (users can't logout,
>>>    or user switching is happening). I think this is around the session related
>>>    info being cached unexpectedly (session cookies?). So even though CKAN
>>>    tries to clear cookies on logout (`check_session_cookie`, NGinx is
>>>    returning active session info on some requests.
>>>    - There was a decent amount of work done on flask blueprints and
>>>    caching in CKAN around 2.8. I've done some comparisons to 2.7 and 2.8 to
>>>    see the changes but there are many.
>>>    - CKAN application code isn't passing the right cache-control header
>>>    values on some responses which is causing them to be cached when they
>>>    shouldn't be
>>>
>>> Thanks,
>>> Cody
>>> _______________________________________________
>>> CKAN security
>>> https://lists.okfn.org/mailman/listinfo/security
>>> https://lists.okfn.org/mailman/options/security/adria.mercader%40okfn.org
>>>
>>> Repo: https://github.com/ckan/ckan-security
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.okfn.org/mailman/private/security/attachments/20190510/ced472b8/attachment-0001.html>


More information about the Security mailing list