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?)

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Jan 22, 2019 at 20:25
\$\endgroup\$
1
  • 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\$ Commented Jan 22, 2019 at 21:21

1 Answer 1

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

answered Apr 9, 2020 at 18:58
\$\endgroup\$
1
  • 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\$ Commented Apr 9, 2020 at 20:21

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.