3
votes

As many people around, I've had a lot of problems using CSRF and Django.

Here is the context :
- I've created a https website where user can upload files
- I've used Django 1.4.2 to create this website
- I've created an app *file_manager* that does what I want
- I'm only using this app through the admin urls

If I disable django.middleware.csrf.CsrfViewMiddleware in the MIDDLEWARE_CLASSES of my settings.py, everything works just fine.
I can upload a file on my template with cURL in command line under Debian Squeeze, the file hits the server, no problem.
However, it seems it is not safe.

So I enabled django.middleware.csrf.CsrfViewMiddleware It doesn't work anymore. I get all kind of errors regarding CSRF verification.

I believe I've eliminated the usual suspects (I hope at least) :
- {% csrf_token %}
- RequestContext
- CsrfViewMiddleware in settings.py

You'll find below all the files (I hope) involved in the process :

views.py

from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.template import Context, loader, RequestContext
from django.shortcuts import render, get_object_or_404, redirect, render_to_response
from django.core.urlresolvers import reverse

from django.core.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt

from file_manager.models import MyClass
from file_manager.forms import MyClassForm

def index(request):
    latest_file_list = MyClass.objects.order_by('-name')[:5]
    context = Context({
        'latest_file_list': latest_file_list,
    })
    return render(request, 'file_manager/index.html', context)

def list(request):
    # Handle file upload
    if request.method == 'POST':
        form = MyClassForm(request.POST, request.FILES)
        if form.is_valid():
            newdoc = MyClass(name='testupl', filefield = request.FILES['docfile'], uploader='fcav')
            newdoc.save()

            # Redirect to the document list after POST
            return HttpResponseRedirect(reverse('file_manager.views.list'))

    else:
        form = MyClassForm() # A empty, unbound form

    # Load documents for the list page
    documents = MyClass.objects.all()

    # Render list page with the documents and the form
    con = {'documents': documents, 'form': form}
    con.update(csrf(request))
    return render_to_response(
        'list.html',
        con,
        context_instance=RequestContext(request)
    )

list.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Minimal Django File Upload Example</title>
    </head>
    <body>
    <!-- List of uploaded documents -->
    {% if documents %}
        <ul>
        {% for document in documents %}
            <li><a href="{{ document.filefield.url }}">{{ document.filefield.name }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No documents.</p>
    {% endif %}

        <!-- Upload form. Note enctype attribute! -->
        <form action="{% url list %}" method="post" enctype="multipart/form-data">{% csrf_token %}
            <p>{{ form.non_field_errors }}</p>
            <p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
            <p>
                {{ form.docfile.errors }}
                {{ form.docfile }}
            </p>
            <p><input type="submit" value="Upload" /></p>
        </form>
    </body>
</html>

And here is my curl request :

    curl --cacert /home/fcav/apache.pem --user admin:password
-e "https://domain.com/admin/" -X POST 
-F "docfile=@/home/fcav/Downloads/basket.csv"
https://domain.com/admin/file_manager/myclass/list/

I've tried many variations of the curl request by trying to send the cookie in the request as well, but now I think my mind is completely mixed up on how cURL handles the upload.

All I get is CSRF Cookie not set or CSRF verification failed.

If anyone knows cURL and Django enough to give me hints about how I can try to upload on my website without disabling CSRF, I would really appreciate it.

Regards, Florian

2

2 Answers

5
votes

There are a couple of methods of passing CSRF validation:

  • Pass csrftoken CSRF cookie which will contain the CSRF token, which Django will use to validate the request
  • Pass CSRF token as X-CSRFToken HTTP header

When you do your request in curl, you are making a request to a page which Django will validate with CSRF, however you are not passing the CSRF token to it, so that Django cannot validate the request.

I don't know how to use curl, so please forgive me that I don't know how to do that in curl but the following steps should help you in solving the issue:

1 Step one is to get the csrf token:

You can do that by first requesting using GET the url which list view is responsible for. That will return a HTML document, which will contain the content of what {% csrf_token %} returned. You can parse that segment and store the token value from there.

Second method is to add ensure_csrf_cookie decorator to the list view. That decorator will make sure that view will always return a csrftoken cookie with the request. Then you still have to make a GET request, however instead of parsing the result of {% csrf_token %}, you can just get the csrf token from the returned cookie.

This step will provide you with the value of the csrf token which you will use in the next step.

2 Now that you will have the csrf toke, when making the POST request, you will also pass additional X-CSRFToken header with the value of the token you got in previous step. Passing the token will allow Django to validate the request and hence process the uploaded file.


Following these steps will allow you to make csrf-valid requests. Please for this approach it does not matter if the request is made using HTTP or HTTPS. HTTPS provides assurance of data-integrity, data-secrecy and validates the server authenticity. It does not provide client validation to the server, which is why CSRF is applicable for both HTTP and HTTPS.

1
votes

So... I made it...
Took a lot of tests but I finally succeeded...
Looked like I needed to send via cURL :
- the certificate from the server
- a referer header with the domain name
- the cookie csrftoken with its value
- an extra header with the csrf token value
- the file I want to upload

Kind of something like this :

curl --cacert /path/to/cert/apache.pem -e "https://domain.com" --cookie "csrftoken=[value]" -H "X-CSRFToken: [value]" -X POST -F "docfile=@/path/to/myfile/file.csv" https://domain.com/admin/list/