Skip to content

Commit

Permalink
Merge in machine ID into cookie name and pin generation
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Apr 14, 2016
1 parent 72b1a79 commit a7066a7
Showing 1 changed file with 76 additions and 9 deletions.
85 changes: 76 additions & 9 deletions werkzeug/debug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
:license: BSD, see LICENSE for more details.
"""
import os
import re
import sys
import uuid
import json
import time
import getpass
import hashlib
import mimetypes
from itertools import chain
from os.path import join, dirname, basename, isfile
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
from werkzeug.http import parse_cookie
Expand All @@ -40,6 +42,59 @@ def hash_pin(pin):
return hashlib.md5(pin + 'shittysalt').hexdigest()[:12]


_machine_id = None


def get_machine_id():
global _machine_id
rv = _machine_id
if rv is not None:
return rv

def _generate():
# Potential sources of secret information on linux. The machine-id
# is stable across boots, the boot id is not
for filename in '/etc/machine-id', '/proc/sys/kernel/random/boot_id':
try:
with open(filename, 'rb') as f:
f.readline().strip()

This comment has been minimized.

Copy link
@JordanMilne

JordanMilne Apr 15, 2016

Should there be a return here?

except IOError:
continue

# On OS X we can use the computer's serial number assuming that
# ioreg exists and can spit out that information.
from subprocess import Popen, PIPE
try:
dump = Popen(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'],
stdout=PIPE).communicate()[0]
match = re.match(r'"serial-number" = <([^>]+)', dump)
if match is not None:
return match.group(1)
except OSError:
pass

# On Windows we can use winreg to get the machine guid
wr = None
try:
import winreg as wr
except ImportError:
try:
import _winreg as wr
except ImportError:
pass
if wr is not None:
try:
with wr.OpenKey(wr.HKEY_LOCAL_MACHINE,
'SOFTWARE\\Microsoft\\Cryptography', 0,
wr.KEY_READ | wr.KEY_WOW64_64KEY) as rk:
return wr.QueryValueEx(rk, 'MachineGuid')[0]
except WindowsError:
pass

_machine_id = rv = _generate()
return rv


class _ConsoleFrame(object):

"""Helper class so that we can reuse the frame console code for the
Expand Down Expand Up @@ -85,30 +140,42 @@ def get_pin_and_cookie_name(app):
except ImportError:
username = None

bits = [
mod = sys.modules.get(modname)

# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
str(uuid.getnode()),
modname,
getattr(app, '__name__', getattr(app.__class__, '__name__')),
getattr(mod, '__file__', None),
]

mod = sys.modules.get(modname)
bits.append(getattr(mod, '__file__', None))
bits.append('cookiesalt')
# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [
str(uuid.getnode()),
get_machine_id(),
]

h = hashlib.md5()

This comment has been minimized.

Copy link
@JordanMilne

JordanMilne Apr 16, 2016

nitpick: If we're changing how these are generated anyway we may as well switch to sha256 for all hashes. Truncated SHA2 is much more collision resistant than md5

for bit in bits:
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, text_type):
bit = bit.encode('utf-8')
h.update(bit)
h.update('cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

This comment has been minimized.

Copy link
@JordanMilne

JordanMilne Apr 15, 2016

👍 for 20 chars


# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
if num is None:
h.update('pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

cookie_name = '__wzd' + h.hexdigest()[:12]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
if rv is None:
Expand Down Expand Up @@ -386,7 +453,7 @@ def __call__(self, environ, start_response):
response = self.log_pin_request()
elif self.evalex and cmd is not None and frame is not None \
and self.secret == secret and \
self.is_trusted(environ):
self.check_pin_trust(environ):
response = self.execute_command(request, cmd, frame)
elif self.evalex and self.console_path is not None and \
request.path == self.console_path:
Expand Down

0 comments on commit a7066a7

Please # to comment.