Media Exposure Vulnerability

Currently, we have a configuration in Django to serve media files (these are the files uploaded by users in the system).

We have a URL for this where we capture all the files in the media directory and redirect them to the serve_media view.

re_path(r"^media/(?P<path>.*)$", cms.views.serve_media),

This is the serve_media view:

def serve_media(request, path=""):
    if request.user.is_authenticated:
        user = request.user
    else:
        return redirect("/%s?next=%s" % (settings.LOGIN_URL, "/media/" + path))

    usuario = Usuario.objects.get(usuario=user)

    # Search for the file by its path in the media folder and check if the user
    # has permission to access it
    allowed_to_serve = False
    try:
        arquivo = Arquivo.objects.get(arquivo__contains=path)
        # Public image of a project on the website
        for projeto in Projeto.objects.exclude(id_imagem_publica=None):
            if projeto.arquivo_in_id_imagem_publica(arquivo.id) is True:
                allowed_to_serve = True
                break
        if user == arquivo.criado_por or usuario.permissao == Usuario.SUPER:
            allowed_to_serve = True
        # Entidade file
        if arquivo.entidade is not None and allowed_to_serve is False:
            if (
                    usuario.permissao == Usuario.ENTIDADE
                    and user == arquivo.entidade.usuario.usuario
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.AGENCIA
                    and arquivo.entidade.projeto_set.all()
                            .filter(cidade_atuacao=usuario.cidade_atuacao)
                            .count()
                    > 0
            ):
                allowed_to_serve = True
        # Projeto file
        if arquivo.projeto is not None and allowed_to_serve is False:
            if (
                    usuario.permissao == Usuario.ENTIDADE
                    and user == arquivo.projeto.entidade.usuario.usuario
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.AGENCIA
                    and arquivo.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.EMBAIXADOR
                    and arquivo.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
        # Prestacao contas file
        if arquivo.prestacao_contas is not None and allowed_to_serve is False:
            if (
                    usuario.permissao == Usuario.ENTIDADE
                    and user == arquivo.prestacao_contas.projeto.entidade.usuario.usuario
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.AGENCIA
                    and arquivo.prestacao_contas.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.EMBAIXADOR
                    and arquivo.prestacao_contas.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
        # Embaixador opinion file
        if arquivo.parecer is not None and allowed_to_serve is False:
            if (
                    usuario.permissao == Usuario.AGENCIA
                    and arquivo.parecer.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
            elif (
                    usuario.permissao == Usuario.EMBAIXADOR
                    and arquivo.parecer.projeto.cidade_atuacao == usuario.cidade_atuacao
            ):
                allowed_to_serve = True
    except Exception:
        pass

    if allowed_to_serve is False:
        return HttpResponseForbidden("")

    return serve(request, path, settings.MEDIA_ROOT)

On localhost, this control works as intended, restricting access to certain files only for logged-in users and allowing other files to be accessible, such as public project photos.

Our problem is that on the server, the files are being served by nginx, and it ends up “overriding” the Django configuration, serving the files directly without validation, making internal system files available for public access.

This is our current configuration in nginx. Note that we have already blocked some extensions and pointed to the project’s media directory. However, the final step of forwarding the request to the serve_media view is missing, and it is currently being ignored.

location /media  {
  location ~* .(html|js|css|php|bak|swp|tmp|zip|tar.gz)$ {
    return 403;
  }
  alias /home/django/fundo/fundo/media;
  add_header 'Access-Control-Allow-Origin' 'fundo.url.com.br';
  add_header 'X-Content-Type-Options' 'nosniff';
  add_header 'Cache-Control' 'max-age=600, s-maxage=600, max-stale=600, must-revalidate, proxy-revalidate';
}

If this is your root objective, then take advantage of the facilities that nginx provides for you.

Briefly, nginx provides directives that will cause it to send the requested url to a view for validation, while still allowing nginx to serve the file itself.

See Show external folder html content in django - #2 by KenWhitesell and Securing Uploaded Files - #2 by KenWhitesell that each contain some links with options.

1 Like