Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix l33t vulnerability by enforcing max password length #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ Output:
}],
}

To override the default maximum password length of 72 characters, set the
``ZXCVBN_MAX_LENGTH`` environment variable:

.. code-block:: bash

export ZXCVBN_MAX_LENGTH=128

.. warning::
We strongly advise against setting ``ZXCVBN_MAX_LENGTH`` to a value greater than 72,
as it can lead to long processing times and may leave server-side applications open
to denial-of-service scenarios.


Custom Ranked Dictionaries
--------------------------
Expand All @@ -121,6 +133,7 @@ In order to support more languages or just add password dictionaries of your own
These lists will be added to the current ones, but you can also overwrite the current ones if you wish.
The lists you add should be in order of how common the word is used with the most common words appearing first.


CLI
~~~

Expand Down
12 changes: 12 additions & 0 deletions tests/l33t_exploit_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pytest
from zxcvbn import zxcvbn

# Test ACsploit-generated password targeting zxcvbn's l33t matching algorithm
# (see https://github.com/GoSimpleLLC/nbvcxz/issues/60)
def test_l33t_exploit():

password = "4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/"

# Function should raise ValueError for input exceeding default MAX_LENGTH of 72 chars
with pytest.raises(ValueError, match="Password length exceeds 72 characters"):
zxcvbn(password, user_inputs=[None])
5 changes: 4 additions & 1 deletion tests/zxcvbn_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import pytest
from zxcvbn import zxcvbn


Expand All @@ -23,7 +24,9 @@ def test_long_password():
input_ = None
password = "weopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioej"

zxcvbn(password, user_inputs=[input_])
# Function should raise ValueError for input exceeding default MAX_LENGTH of 72 chars
with pytest.raises(ValueError, match="Password length exceeds 72 characters"):
zxcvbn(password, user_inputs=[input_])


def test_dictionary_password():
Expand Down
8 changes: 8 additions & 0 deletions zxcvbn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import os
from datetime import datetime

from . import matching, scoring, time_estimates, feedback

DEFAULT_MAX_LENGTH = 72
MAX_LENGTH = int(os.environ.get('ZXCVBN_MAX_LENGTH', DEFAULT_MAX_LENGTH))

def zxcvbn(password, user_inputs=None):
# Throw error if password exceeds max length
if len(password) > MAX_LENGTH:
raise ValueError(f"Password length exceeds {MAX_LENGTH} characters.")

try:
# Python 2 string types
basestring = (str, unicode)
Expand Down