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
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:
- MovieStore GitHub Repository – (DirectMe)
- View all the tutorials in this series at DirectMe
- 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.