Tres Seaver
2011-03-13 f8ef8169680239824eb5fb11c7ce5a54938aed1c
refs
author Tres Seaver <tseaver@palladion.com>
Sunday, March 13, 2011 00:23 +0100
committer Tres Seaver <tseaver@palladion.com>
Sunday, March 13, 2011 00:23 +0100
commitf8ef8169680239824eb5fb11c7ce5a54938aed1c
tree a8fdc52c2b2392be3c5363584eae58af4c1d7355 tree | zip | gz
parent dd481232ba25cc6011bacc78b3702078d4708064 view | diff
Defend timing-based attacks against htpasswd.

diff --git a/CHANGES.txt b/CHANGES.txt
index 9724da1..b8695a1 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,12 @@
repoze.who Changelog
====================

+Unreleased
+----------
+
+
+
2.0a4 (2011-02-02)
------------------

@@ -35,6 +41,7 @@ repoze.who Changelog
otherwise need to use private methods of the API, and reach down into
its plugins.

+
2.0a3 (2010-09-30)
------------------

@@ -71,6 +78,7 @@ repoze.who Changelog
(added missing ``global_config`` argument). See
http://bugs.repoze.org/issue114

+
2.0a2 (2010-03-25)
------------------

@@ -88,6 +96,7 @@ Backward Incompatibilities
to ``debug``.

+
2.0a1 (2010-02-24)
------------------

@@ -153,6 +162,7 @@ Backward Incompatibilities
- ``verify``

+
1.0.18 (2009-11-05)
-------------------

@@ -161,6 +171,7 @@ Backward Incompatibilities
``Expires`` attributes of those cookies.

+
1.0.17 (2009-11-05)
-------------------

@@ -169,6 +180,7 @@ Backward Incompatibilities
file).

+
1.0.16 (2009-11-04)
-------------------

diff --git a/repoze/who/plugins/htpasswd.py b/repoze/who/plugins/htpasswd.py
index f21bb0e..13c418a 100644
--- a/repoze/who/plugins/htpasswd.py
+++ b/repoze/who/plugins/htpasswd.py
@@ -1,3 +1,5 @@
+import itertools
+
from zope.interface import implements

from repoze.who.interfaces import IAuthenticator
@@ -31,6 +33,7 @@ class HTPasswdPlugin(object):
'file %s' % self.filename)
return None

+ result = None
for line in f:
try:
username, hashed = line.rstrip().split(':', 1)
@@ -38,20 +41,30 @@ class HTPasswdPlugin(object):
continue
if username == login:
if self.check(password, hashed):
- return username
- return None
+ result = username
+ # Don't bail early: leaks information!!
+ return result

def __repr__(self):
return '<%s %s>' % (self.__class__.__name__,
id(self)) #pragma NO COVERAGE

+PADDING = ' ' * 1000
+
+def _same_string(x, y):
+ match = True
+ for a, b, ignored in itertools.izip_longest(x, y, PADDING):
+ match = a == b and match
+ return match
+
def crypt_check(password, hashed):
from crypt import crypt
salt = hashed[:2]
- return hashed == crypt(password, salt)
+ return _same_string(hashed, crypt(password, salt))

def plain_check(password, hashed):
- return hashed == password
+ return _same_string(password, hashed)
+

def make_plugin(filename=None, check_fn=None):
if filename is None:
@@ -60,5 +73,3 @@ def make_plugin(filename=None, check_fn=None):
raise ValueError('check_fn must be specified')
check = resolveDotted(check_fn)
return HTPasswdPlugin(filename, check)
-
-
2 files modified
35 ■■■■ changed files
CHANGES.txt 12 ●●●●● diff | view | raw | blame | history
repoze/who/plugins/htpasswd.py 23 ●●●● diff | view | raw | blame | history