Profile pictures are now handled by the application to mitigate possible directory traversals

to other sub-directories of the static directory (Admins/Staff with the right to edit user
accounts were able to set a path like ../static/favicon.png for the profile picture - this
isn't a "i'm in, now i have root access and can hack your mom"-vulnerability, but better fix
it before it evolves to one. or a dragon. it's too late for this crap.)
This commit is contained in:
W13R 2022-11-02 21:55:36 +01:00
parent 86ea7c0000
commit 9f270c12b4
8 changed files with 34 additions and 4 deletions

4
.gitignore vendored
View file

@ -4,6 +4,7 @@
/archive/* /archive/*
/logs/* /logs/*
/packages/* /packages/*
/profilepictures/*
/temp /temp
/tmp /tmp
__pycache__ __pycache__
@ -12,4 +13,5 @@ __pycache__
!/config/config.sample.sh !/config/config.sample.sh
!/config/Caddyfile !/config/Caddyfile
!/config/tls/ !/config/tls/
!.gitkeep !/profilepictures/default.svg
!.gitkeep

View file

@ -69,7 +69,7 @@
<ul class="userlist"> <ul class="userlist">
{% for user_ in user_list %} {% for user_ in user_list %}
<li class="userlistButton button" data-username="{{ user_.username }}"> <li class="userlistButton button" data-username="{{ user_.username }}">
<img src="{% static 'profilepictures/'|add:user_.profile_picture_filename %}"> <img src="{{ '/profilepictures?name='|add:user_.profile_picture_filename }}">
<div> <div>
{% if user_.first_name %} {% if user_.first_name %}

View file

@ -3,7 +3,7 @@
<div class="userPanel"> <div class="userPanel">
<div class="userInfo"> <div class="userInfo">
<img src="{% static 'profilepictures/'|add:user.profile_picture_filename %}"> <img src="{{ '/profilepictures?name='|add:user.profile_picture_filename }}">
<span> <span>
{% if user.first_name != "" %} {% if user.first_name != "" %}
{% translate "User" %}: {{ user.first_name }} {{ user.last_name }} ({{ user.username }}) {% translate "User" %}: {{ user.first_name }} {{ user.last_name }} ({{ user.username }})

View file

@ -16,9 +16,10 @@ urlpatterns = [
path('accounts/password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'), path('accounts/password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('accounts/password_change_done/', views.redirect_home, name='password_change_done'), path('accounts/password_change_done/', views.redirect_home, name='password_change_done'),
path('admin/', adminSite.urls), path('admin/', adminSite.urls),
# custom-handled resources
path('profilepictures', views.profile_pictures),
# API # # API #
path('api/order-drink', views.api_order_drink), path('api/order-drink', views.api_order_drink),
path('api/deposit', views.api_deposit), path('api/deposit', views.api_deposit),
path('api/supply', views.api_supply) path('api/supply', views.api_supply)
#path('api/get-statistics', views.api_get_statistics)
] ]

View file

@ -1,12 +1,16 @@
import json import json
import sys import sys
from pathlib import Path
from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth import login from django.contrib.auth import login
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.http.response import FileResponse
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.shortcuts import render from django.shortcuts import render
@ -20,6 +24,9 @@ from .models import Drink
from .models import Order from .models import Order
from .models import RegisterTransaction from .models import RegisterTransaction
#
profile_pictures_path = Path(settings.PROFILE_PICTURES).resolve()
# login view # login view
@ -112,6 +119,23 @@ def redirect_home(request):
return HttpResponseRedirect("/") return HttpResponseRedirect("/")
# Custom-Handled Resources
def profile_pictures(request):
if not "name" in request.GET:
return HttpResponse(b"", status=400)
print(request.GET["name"])
ppic_filepath = Path(profile_pictures_path / request.GET["name"]).resolve()
try:
ppic_filepath.relative_to(profile_pictures_path)
except:
return HttpResponse("No.", status=403)
if ppic_filepath.is_file():
return FileResponse(ppic_filepath.open('rb'))
else:
return FileResponse(b"", status=404)
# API for XHR requests # # API for XHR requests #
@login_required @login_required

View file

@ -175,3 +175,5 @@ try:
CURRENCY_SUFFIX = os.environ["CURRENCY_SUFFIX"] CURRENCY_SUFFIX = os.environ["CURRENCY_SUFFIX"]
except KeyError: except KeyError:
CURRENCY_SUFFIX = "$" CURRENCY_SUFFIX = "$"
PROFILE_PICTURES = os.environ["PROFILE_PICTURES"]

View file

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
export DJANGO_SK_ABS_FP="$(pwd)/config/secret_key.txt" export DJANGO_SK_ABS_FP="$(pwd)/config/secret_key.txt"
export PROFILE_PICTURES="$(pwd)/profilepictures/"
export STATIC_FILES="$(pwd)/static/" export STATIC_FILES="$(pwd)/static/"
export APP_VERSION="12" export APP_VERSION="12"
export PYTHONPATH="$(pwd)/packages/" export PYTHONPATH="$(pwd)/packages/"

View file

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

Before After
Before After