python.django.security.hashids-with-django-secret.hashids-with-django-secret

profile photo of semgrepsemgrep
Author
unknown
Download Count*

The Django secret key is used as salt in HashIDs. The HashID mechanism is not secure. By observing sufficient HashIDs, the salt used to construct them can be recovered. This means the Django secret key can be obtained by attackers, through the HashIDs.

Run Locally

Run in CI

Defintion

rules:
  - id: hashids-with-django-secret
    languages:
      - python
    message: The Django secret key is used as salt in HashIDs. The HashID mechanism
      is not secure. By observing sufficient HashIDs, the salt used to construct
      them can be recovered. This means the Django secret key can be obtained by
      attackers, through the HashIDs.
    metadata:
      category: security
      subcategory:
        - vuln
      cwe:
        - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
      owasp:
        - A02:2021 – Cryptographic Failures
      references:
        - https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-SECRET_KEY
        - http://carnage.github.io/2015/08/cryptanalysis-of-hashids
      technology:
        - django
      likelihood: LOW
      impact: HIGH
      confidence: HIGH
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Cryptographic Issues
    pattern-either:
      - pattern: hashids.Hashids(..., salt=django.conf.settings.SECRET_KEY, ...)
      - pattern: hashids.Hashids(django.conf.settings.SECRET_KEY, ...)
    severity: ERROR

Examples

hashids-with-django-secret.py

# https://github.com/crowdresearch/daemo/blob/36e3b70d4e2c06b4853e9209a4916f8301ed6464/crowdsourcing/serializers/task.py#L435-L437
from django.conf import settings
from hashids import Hashids
# ruleid: hashids-with-django-secret
identifier = Hashids(salt=settings.SECRET_KEY, min_length=settings.ID_HASH_MIN_LENGTH)

# https://github.com/pythonitalia/pycon-quiz/blob/7fe11ab96815edad4cf1ed0bdd8ba52d9438ffa0/backend/django_hashids/hashids.py
from django.conf import settings
from hashids import Hashids


def get_hashids():
# ruleid: hashids-with-django-secret
    return Hashids(
        salt=settings.SECRET_KEY, min_length=4, alphabet="abcdefghijklmnopqrstuvwxyz"
    )

# https://github.com/made-with-future/django-common/blob/dc68c93209a71c63dbf0241b997ab8e67697b3a5/common/models.py#L45
class UIDMixin(models.Model):

    objects = UIDManager()

    _hashids = None

    def __init__(self, *args, **kwargs):
        super(UIDMixin, self).__init__(*args, **kwargs)

    @classmethod
    def hashids(cls):
        if not cls._hashids:
            md5 = hashlib.md5()
            md5.update('{}{}'.format(settings.SECRET_KEY, cls.__name__))
# ok: hashids-with-django-secret
            cls._hashids = Hashids(salt=md5.hexdigest(), min_length=16)
        return cls._hashids

# https://github.com/duthaho/aicontest/blob/f6bdcc785b66842be65a8086938d198d65f27650/coding/services/util.py
from contextlib import suppress
import random
import string

from django.conf import settings
from hashids import Hashids


def get_random_string(length: int) -> str:
    # choose from all lowercase letter
    letters = string.ascii_lowercase + string.digits
    return "".join(random.choice(letters) for i in range(length))


def id_to_hash(id: int, length: int = 6) -> str:
    alphabet = string.ascii_letters + string.digits
# ruleid: hashids-with-django-secret
    return Hashids(settings.SECRET_KEY, min_length=length, alphabet=alphabet).encrypt(
        id
    )


def safe_int(num: any, default: int = 0) -> int:
    with suppress(Exception):
        return int(num)
    return default