4
\$\begingroup\$

I'm creating multiple Django apps with vote possibilities, so I made an app vote to handle all this votes. In my templates I'm including an ajax-function named vote. To know on which model I'm liking I add app_name and model_name to the vote function (I made some templatetags to get these values). In my views.py I use model = apps.get_model(app_name, model_name) to get the model class. But now I'm worried a hacker could do something with the app_name and model_name values.

vote/ajax.html (only function):

function vote(bool){
    $.ajax({
        type: "post",
        timeout: 8000,
        url: '{% url 'ajax:vote' %}',
        dataType: 'json',
        data: {
            'csrfmiddlewaretoken': getCookie('csrftoken'),
            'model_name': "{{ model|get_model_name }}",
            'app_name': "{{ model|get_app_name }}",
            'voted': bool,
            'id': "{{ model.id }}",
        },
        success: function(data) {
            if (!data.error){
                if (bool){
                    $(".half .fa-thumbs-up").removeClass("far").addClass("fas");
                    $(".half #count").text(parseInt($(".half #count").text()) + 1);
                } else {
                    $(".half .fa-thumbs-down").removeClass("far").addClass("fas");
                    $(".half #count").text(parseInt($(".half #count").text()) - 1);
                }
            }
        }
    });
}

ajax/views.py:

def vote(request):
    try:
        app_name = request.POST.get("app_name")
        model_name = request.POST.get("model_name")
        id = request.POST.get("id")
        votedFor = True if request.POST.get("voted") == "true" else False
    except ValueError:
        return JsonResponse({"error": True})

    model = apps.get_model(app_name, model_name)

    if model is None or id is None:
        return JsonResponse({"error": True})

    try:
        usable_model = model.objects.get(id=id)
    except model.DoesNotExist:
        return JsonResponse({"error": True})

    try:
        usable_model.vote._meta.get_field("votes")
    except FieldDoesNotExist:
        return JsonResponse({"error": True})

    usable_model.vote.vote(request, votedFor)
    return JsonResponse({"error": False})

vote/functions:

def vote(self, request, votedFor):
    if request.user.is_authenticated:
        if not UserVoted.objects.filter(User=request.user, Vote=self).exists():
            UserVoted.objects.create(User=request.user, Vote=self, votedFor=votedFor)
            self._like_or_dislike(votedFor)
            return True
        return False
    ip = get_client_ip(request)
    if ip:
        if not UserVoted.objects.filter(ip=ip, Vote=self).exists():
            UserVoted.objects.create(ip=ip, Vote=self, votedFor=votedFor)
            self._like_or_dislike(votedFor)
            return True
        return False
    return False

def _like_or_dislike(self, votedFor):
    if votedFor is not None:
        Vote.objects.filter(id=self.id).update(votes=F('votes') + 1) if votedFor else Vote.objects.filter(id=self.id).update(votes=F('votes') - 1)
        return True
    return False

I already manipulated app_name and model_name and the server didn't crash but I don't know what a hacker can do. Can he crash my server when he manipulate these values? (maybe "ajax-injection" or something like this?)

\$\endgroup\$
  • 5
    \$\begingroup\$ Your indentation is off. This being Python, indentation is very important. Remove your code, paste it in file-by-file and with every bit you paste in, select it, hit Ctrl + K. The question editor should do the rest. \$\endgroup\$ – Mast Jan 22 '19 at 21:21
1
\$\begingroup\$

Question

But now I'm worried a hacker could do something with the app_name and model_name values.

Can he crash my server when he manipulate these values? (maybe "ajax-injection" or something like this?)

I'm not sure what the best approach to this would be. Perhaps it would be wise to define a list of the values that are acceptable for a user to pass there, though maybe that wouldn't be sufficient.

Other review points

function vote(bool){

The name bool is not very descriptive. Given that it is used as the value for the voted parameter a name like voted would be more appropriate. And if your code complies with standards you could simply write voted

in the list of parameters as a shorthand for voted: voted


The success handler looks like this:

success: function(data) {
    if (!data.error){
        if (bool){
            $(".half .fa-thumbs-up").removeClass("far").addClass("fas");
            $(".half #count").text(parseInt($(".half #count").text()) + 1);
        } else {
            $(".half .fa-thumbs-down").removeClass("far").addClass("fas");
            $(".half #count").text(parseInt($(".half #count").text()) - 1);
        }

There is quite a bit of redundancy in both cases. Also parseInt() calls should pass a radix1.

if (!data.error){
    const thumbClass = '.fa-thumbs-' + bool ? 'up' : 'down';
    $(".half " + thumbClass).removeClass("far").addClass("fas");
    const toAdd = bool ? 1 : -1;
    $(".half #count").text(parseInt($(".half #count").text(), 10) + toAdd);

In the vote function in ajax/views.py there is this line:

votedFor = True if request.GET.get("voted") == "true" else False

This could be simplified to just:

votedFor = request.GET.get("voted") == "true"

1https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Octal_interpretations_with_no_radix

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Wow thank you for your answer for my old question, that isn`t very common! I already found a better way. I register all models in a global dict by saving the model class with a random ID. In the frontend then I use the ID and send it, in the backend I then can easily get the model by the ID. \$\endgroup\$ – Myzel394 Apr 9 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.