2

So let's start with some background. I have a 3-tier system, with an API implemented in django running with mod_wsgi on an Apache2 server.

Today I decided to upgrade the server, running at DigitalOcean, from Ubuntu 12.04 to Ubuntu 14.04. Nothing special, only that Apache2 also got updated to version 2.4.7. After wasting a good part of the day figuring out that they actually changed the default folder from /var/www to /var/www/html, breaking functionality, I decided to test my API. Without touching a single line of code, some of my functions were not working.

I'll use one of the smaller functions as an example:

# Returns the location information for the specified animal, within the specified period.
@csrf_exempt # Prevents Cross Site Request Forgery errors.
def get_animal_location_reports_in_time_frame(request):
    start_date = request.META.get('HTTP_START_DATE')
    end_date = request.META.get('HTTP_END_DATE')
    reports = ur_animal_location_reports.objects.select_related('species').filter(date__range=(start_date, end_date), species__localizable=True).order_by('-date')
    # Filter by animal if parameter sent.
    if request.META.get('HTTP_SPECIES') is not None:
        reports = reports.filter(species=request.META.get('HTTP_SPECIES'))

    # Add each information to the result object.
    response = []
    for rep in reports:
        response.append(dict(
            ID=rep.id,
            Species=rep.species.ai_species_species,
            Species_slug=rep.species.ai_species_species_slug,
            Date=str(rep.date),
            Lat=rep.latitude,
            Lon=rep.longitude,
            Verified=(rep.tracker is not None),
        ))
    # Return the object as a JSON string.
    return HttpResponse(json.dumps(response, indent = 4))

After some debugging, I observed that request.META.get('HTTP_START_DATE') and request.META.get('HTTP_END_DATE') were returning None. I tried many clients, ranging from REST Clients (such as the one in PyCharm and RestConsole for Chrome) to the Android app that would normally communicate with the API, but the result was the same, those 2 parameters were not being sent.

I then decided to test whether other parameters are being sent and to my horror, they were. In the above function, request.META.get('HTTP_SPECIES') would have the correct value.

After a bit of fiddling around with the names, I observed that ALL the parameters that had a _ character in the title, would not make it to the API.

So I thought, cool, I'll just use - instead of _ , that ought to work, right? Wrong. The - arrives at the API as a _!

At this point I was completely puzzled so I decided to find the culprit. I ran the API using the django development server, by running:

sudo python manage.py runserver 0.0.0.0:8000

When sending the same parameters, using the same clients, they are picked up fine by the API! Hence, django is not causing this, Ubuntu 14.04 is not causing this, the only thing that could be causing it is Apache 2.4.7!

Now moving the default folder from /var/www to /var/www/html, thus breaking functionality, all for a (in my opinion) very stupid reason is bad enough, but this is just too much.

Does anyone have an idea of what is actually happening here and why?

Community
  • 1
  • 1
Vlad Schnakovszki
  • 8,434
  • 6
  • 80
  • 114

2 Answers2

3

This is a change in Apache 2.4.

This is from Apache HTTP Server Documentation Version 2.4:

MOD CGI, MOD INCLUDE, MOD ISAPI, ... Translation of headers to environment variables is more strict than before to mitigate some possible cross-site-scripting attacks via header injection. Headers containing invalid characters (including underscores) are now silently dropped. Environment Variables in Apache (p. 81) has some pointers on how to work around broken legacy clients which require such headers. (This affects all modules which use these environment variables.)

– Page 11

For portability reasons, the names of environment variables may contain only letters, numbers, and the underscore character. In addition, the first character may not be a number. Characters which do not match this restriction will be replaced by an underscore when passed to CGI scripts and SSI pages.

– Page 86

A pretty significant change in other words. So you need to rewrite your application so send dashes instead of underscores, which Apache in turn will substitute for underscores.

EDIT

There seems to be a way around this. If you look at this document over at apache.org, you can see that you can fix it in .htaccess by putting the value of your foo_bar into a new variable called foo-bar which in turn will be turned back to foo_bar by Apache. See example below:

SetEnvIfNoCase ^foo.bar$ ^(.*)$ fix_accept_encoding=$1
RequestHeader set foo-bar %{fix_accept_encoding}e env=fix_accept_encoding

The only downside to this is that you have to make a rule per header, but you won't have to make any changes to the code either client or server side.

Yahya
  • 520
  • 2
  • 10
  • 26
Jørgen R
  • 10,568
  • 7
  • 42
  • 59
0

Are you sure Django didn't get upgraded as well?

https://docs.djangoproject.com/en/dev/ref/request-response/

With the exception of CONTENT_LENGTH and CONTENT_TYPE, as given above, any HTTP headers in the request are converted to META keys by converting all characters to uppercase, replacing any hyphens with underscores and adding an HTTP_ prefix to the name. So, for example, a header called X-Bender would be mapped to the META key HTTP_X_BENDER.

The key bits are: Django is converting '-' to underscore and also prepending 'HTTP_' to it. If you are already adding a HTTP_ prefix when you call the api, it might be getting doubled up. Eg 'HTTP_HTTP_SPECIES'

Jon Anderson
  • 696
  • 4
  • 9
  • It is not Django which does the name translation but the WSGI server. The WSGI specification requires the WSGI server populate the WSGI environ dictionary, which becomes META in Django, with names translated as per the CGI specification rules. So ultimately this is dictated due to a CGI specification on which parts of WSGI is based. – Graham Dumpleton Jul 23 '14 at 01:23
  • Django did get upgraded as well but it shouldn't be the problem as when running the API using the django server, everything worked as normal. I am aware of the things mentioned above, however it doesn't mention that the underscores are not acceptable, nor that this would be something in a newer version of django. Graham, you might be on to something there, it might be wsgi to blame. – Vlad Schnakovszki Jul 23 '14 at 06:53
  • Have you tried simply printing out the request.META map with all its key-values? That would tell you exactly what is happening. – Jon Anderson Jul 23 '14 at 12:43
  • I have Jon and the parameters that contain underscores are missing. – Vlad Schnakovszki Jul 23 '14 at 13:23