My dear reader, how are you? السلام عليكم

I will love the light for it shows me the way, yet I will endure the darkness for it shows me the stars – Og Mandino

In this post, we are extending MovieStore tutorial series by exploring how to upload image files as banners to our movies in MovieStore application in Django. Further, we will discuss the OWASP (Open Web Application Security Project) top 10 vulnerabilities in applications that may expose the application to security risks.


In order to practically follow this tutorial, I suggest the following before you start:

  1. MovieStore GitHub Repository – (DirectMe)
  2. View all the tutorials in this series at DirectMe
  3. Set your GitHub repo to MovieStore Part 5 using the following command:
Django-MovieStore$ git fetch origin b1e4c00f8d52ea85dce95ebe674a4607d12a7443

Uploading Image Files to MovieStore

We will first need to configure the file upload settings using the following steps

# Open /Django-MovieStore/MyMovies/movies/settings.py and add 

MEDIA_URL = '/uploaded/'
MEDIA_ROOT = os.path.join(BASE_DIR, '../media_root')

# create a media root directory as follows

(myenv)Django-MovieStore$ mkdir media_root

MEDIA_URL is the URL that will serve our uploaded images and MEDIA_ROOT is the path to the directory where Django should save the code.

We will now install a package name pillow that helps Django to validate whether the uploaded file is an image or not and create a MovieImage model.

 

# To install the package open requirements.txt and add Pillow<4.4.0

(myenv)Django-MovieStore$ pip3 install -r requirements.txt 

# open moviesApp/models.py and add the following 

from uuid import uuid4 # helps to select unique name for files

def movie_directory_path_with_uuid( # function to generate file name
        instance,
        filename):
    return '{}/{}.{}'.format(
            instance.movie_id,
            uuid4(),
            filename.split('.')[-1]
            )

class MovieImage(models.Model):
    image = models.ImageField(upload_to=movie_directory_path_with_uuid)
    uploaded = models.DateTimeField(auto_now_add=True)
    movie = models.ForeignKey('myMovie', on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.CASCADE)

# Finally, make and apply the migrations
(myenv)Django-MovieStore/MyMovies$ python3 manage.py makemigrations
(myenv)Django-MovieStore/MyMovies$ python3 manage.py migrate

After this, we need to create the MovieImageForm to let users upload the images as shown below:

# open moviesApp/forms.py and add the following

class MovieImageForm(forms.ModelForm):
    movie = forms.ModelChoiceField(
            widget=forms.HiddenInput,
            queryset=myMovie.objects.all(),
            disabled=True
            )
    user = forms.ModelChoiceField(
            widget=forms.HiddenInput,
            queryset=get_user_model().objects.all(),
            disabled=True,
            )

    class Meta:
        model = MovieImage
        fields = ('image', 'user', 'movie')

Let us now add the details of the above-created form in MovieDetail as shown below:

# open moviesApp/views.py and update the following in bold
 
class MovieDetail(DetailView):
    queryset = myMovie.objects.all_with_related_persons_and_score()

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['image_form'] = self.movie_image_form() # add this
        ...
        ...

    def movie_image_form(self):
        if self.request.user.is_authenticated:
            return MovieImageForm()
        return None

Now, we update the movie_detail template to allow the users to upload images to the movies as shown below:

# open vi moviesApp/templates/moviesApp/mymovie_detail.html and update the following

{% extends 'base.html' %}

{% block title %}
        {{ object.title }} - {{ block.super }}
{% endblock %}

{% block main %}
<h1> {{ object }} </h1>
<p class="lead">
{{ object.plot }}
</p>
<ul class="movie-image list-incline">
        {% for i in object.movieimage_set.all %}
        <li class="list-inline-item">
                <img src="{{ i.image.url }}">
        </li>
        {% endfor %}
</ul>

{% endblock %}

{% block sidebar %}
    <div>
        {% if vote_form %}
        ...
        ...
    </div>

{% if image_form %}
    <div>
            <h2> Upload New Image </h2>
            <form
                method="post"
                enctype="multipart/form-data"
                action="{% url 'moviesApp:MovieImageUpload' movie_id=object.id %}" >
                {% csrf_token %}
                {{ image_form.as_p }}
                <p>
                    <button
                        class="btn btn-primary">
                            Upload
                    </button>
                </p>
            </form>
    </div>
    {% endif %}

{% endblock %}

Let us now add a MovieImageUpload View

# open moviesApp/views.py and add

from moviesApp.forms import VoteForm, MovieImageForm

class MovieImageUpload(LoginRequiredMixin, CreateView):
    form_class = MovieImageForm

    def get_initial(self):
        initial = super().get_initial()
        initial['user'] = self.request.user.id
        initial['movie'] = self.kwargs['movie_id']
        return initial

    def render_to_response(self, context, **response_kwargs):
        movie_id = self.kwargs['movie_id']
        movie_detail_url = reverse(
            'moviesApp:MovieDetail',
            kwargs={'pk': movie_id})
        return redirect(
            to=movie_detail_url)

    def get_success_url(self):
        movie_id = self.kwargs['movie_id']
        movie_detail_url = reverse(
            'moviesApp:MovieDetail',
            kwargs={'pk': movie_id})
        return movie_detail_url

Finally, we will now route update the urls.py to route the requests to our uploaded image files as shown below:

# open moviesApp/urls.py 

from django.urls import path

from . import views

app_name = 'moviesApp'

urlpatterns = [
    path('movie/<int:movie_id>/image/upload',
         views.MovieImageUpload.as_view(),
         name='MovieImageUpload'),
    ...
    ...
]

# add the following to movies/urls.py to let Django understand how to serve the uploaded files

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

import moviesApp.urls
import user.urls

MEDIA_FILE_PATHS = static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)

urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include(user.urls, namespace='user')),
path('', include(moviesApp.urls, namespace='moviesApp')),
] + MEDIA_FILE_PATHS

OWASP top 10 vulnerabilities

The Open Web Application Security Project (OWASP) is a non-profit organization that produces freely-available guides, tools, and technologies in the field of web application security. The top 10 practical web application insecurities released in 2013 are discussed below with a highlight on how Django is developed to minimize and avoid these risks to the most possible extent.

  • Injection

This has always been the top vulnerability in OWASP top 10 list since its formation. It means to be able to inject the code in the one being executed by our system. A type of this is SQL injection. Django protects from SQL injection by providing us with the QuerySet class. It ensures that all the queries are parameterized so that the database is able to distinguish between our SQL codes and the values in the queries.

  • Broken Authentication and Session Management

This refers to the risk of someone to be able to authenticate as another user or take over another user’s session. Django auth app always hashes and salts the passwords. It also supports slow hashing algorithms that make the bruteforcing impractical. Furthermore, the Django session ID is never made available in the URL by default and it changes after the user login.

  • Cross-Site Scripting

The ability of someone to be able to display his JavaScript or HTML instead of the one by the developer(s). Django protects all the variables in templates with HTML encoding by default.

  • Insecure Direct Object References

It is when we insecurely expose the implementation details in our resource references without protecting the resources from illicit access. Django helps in this by not coupling the routing paths to views.

  • Security Misconfiguration

The risk of inappropriately deploying the proper security mechanisms. For example, setting DEBUG to True in production would expose too much information publically.

  • Sensitive Data Exposure

Without proper authorization, the accessibility of sensitive data leads to this threat. Django can help reduce the risks of inadvertent exposure from attackers using network sniffing by being configured to serve pages through HTTPS.

  • Missing Function Level Access Control

It is about not properly securing certain functionality. For example, UpdateVote in MovieStrore Part 4 without LoginRequireMixin class would allow everyone to send the HTTP request to change user votes.

  • Cross-Site Request Forgey

Django helps to mitigate the risk by providing csrf_token tag to make it easy to add CSRF token to a form.

  • Using Components with Known Vulnerabilities

Using frameworks and libraries having vulnerabilities can cause potential threats in a platform so it is always better to use LTS released versions of Django and its packages.

  • Unvalidated Redirects and Forwards

It is about our site if it can be used to redirect/forward a user to a third-party site automatically. Django protects us by making sure that the next parameter of LoginView will only forward the user’s URLs that are part of our project.


I hope you find this tutorial useful. If you find any errors or feel any need for improvement, let me know in your comments below.

Signing off for today. Stay tuned and I will see you next week! Happy learning.

LEAVE A REPLY

Please enter your comment!
Please enter your name here