Skip to content
This repository has been archived by the owner on Sep 15, 2020. It is now read-only.

Commit

Permalink
Added initial NTLM authentication support
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdevalias committed Nov 12, 2013
1 parent cce42a1 commit d9142e2
Show file tree
Hide file tree
Showing 15 changed files with 1,528 additions and 0 deletions.
133 changes: 133 additions & 0 deletions ntlm/HTTPNtlmAuthHandler.py
@@ -0,0 +1,133 @@
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.

import urllib2
import httplib, socket
from urllib import addinfourl
import ntlm

class AbstractNtlmAuthHandler:
def __init__(self, password_mgr=None, debuglevel=0):
if password_mgr is None:
password_mgr = HTTPPasswordMgr()
self.passwd = password_mgr
self.add_password = self.passwd.add_password
self._debuglevel = debuglevel

def set_http_debuglevel(self, level):
self._debuglevel = level

def http_error_authentication_required(self, auth_header_field, req, fp, headers):
auth_header_value = headers.get(auth_header_field, None)
if auth_header_field:
if auth_header_value is not None and 'ntlm' in auth_header_value.lower():
fp.close()
return self.retry_using_http_NTLM_auth(req, auth_header_field, None, headers)

def retry_using_http_NTLM_auth(self, req, auth_header_field, realm, headers):
user, pw = self.passwd.find_user_password(realm, req.get_full_url())
if pw is not None:
# ntlm secures a socket, so we must use the same socket for the complete handshake
headers = dict(req.headers)
headers.update(req.unredirected_hdrs)
auth = 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(user)
if req.headers.get(self.auth_header, None) == auth:
return None
headers[self.auth_header] = auth

host = req.get_host()
if not host:
raise urllib2.URLError('no host given')
h = None
if req.get_full_url().startswith('https://'):
h = httplib.HTTPSConnection(host) # will parse host:port
else:
h = httplib.HTTPConnection(host) # will parse host:port
h.set_debuglevel(self._debuglevel)
# we must keep the connection because NTLM authenticates the connection, not single requests
headers["Connection"] = "Keep-Alive"
headers = dict((name.title(), val) for name, val in headers.items())
h.request(req.get_method(), req.get_selector(), req.data, headers)
r = h.getresponse()
r.begin()
r._safe_read(int(r.getheader('content-length')))
if r.getheader('set-cookie'):
# this is important for some web applications that store authentication-related info in cookies (it took a long time to figure out)
headers['Cookie'] = r.getheader('set-cookie')
r.fp = None # remove the reference to the socket, so that it can not be closed by the response object (we want to keep the socket open)
auth_header_value = r.getheader(auth_header_field, None)
(ServerChallenge, NegotiateFlags) = ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value[5:])
user_parts = user.split('\\', 1)
DomainName = user_parts[0].upper()
UserName = user_parts[1]
auth = 'NTLM %s' % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, UserName, DomainName, pw, NegotiateFlags)
headers[self.auth_header] = auth
headers["Connection"] = "Close"
headers = dict((name.title(), val) for name, val in headers.items())
try:
h.request(req.get_method(), req.get_selector(), req.data, headers)
# none of the configured handlers are triggered, for example redirect-responses are not handled!
response = h.getresponse()
def notimplemented():
raise NotImplementedError
response.readline = notimplemented
infourl = addinfourl(response, response.msg, req.get_full_url())
infourl.code = response.status
infourl.msg = response.reason
return infourl
except socket.error, err:
raise urllib2.URLError(err)
else:
return None


class HTTPNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler):

auth_header = 'Authorization'

def http_error_401(self, req, fp, code, msg, headers):
return self.http_error_authentication_required('www-authenticate', req, fp, headers)


class ProxyNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler):
"""
CAUTION: this class has NOT been tested at all!!!
use at your own risk
"""
auth_header = 'Proxy-authorization'

def http_error_407(self, req, fp, code, msg, headers):
return self.http_error_authentication_required('proxy-authenticate', req, fp, headers)


if __name__ == "__main__":
url = "http://ntlmprotectedserver/securedfile.html"
user = u'DOMAIN\\User'
password = 'Password'

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, user , password)
auth_basic = urllib2.HTTPBasicAuthHandler(passman)
auth_digest = urllib2.HTTPDigestAuthHandler(passman)
auth_NTLM = HTTPNtlmAuthHandler(passman)

# disable proxies (just for testing)
proxy_handler = urllib2.ProxyHandler({})

opener = urllib2.build_opener(proxy_handler, auth_NTLM) #, auth_digest, auth_basic)

urllib2.install_opener(opener)

response = urllib2.urlopen(url)
print(response.read())

Binary file added ntlm/HTTPNtlmAuthHandler.pyc
Binary file not shown.
113 changes: 113 additions & 0 deletions ntlm/U32.py
@@ -0,0 +1,113 @@
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
#
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.


C = 0x1000000000L

def norm(n):
return n & 0xFFFFFFFFL


class U32:
v = 0L

def __init__(self, value = 0):
self.v = C + norm(abs(long(value)))

def set(self, value = 0):
self.v = C + norm(abs(long(value)))

def __repr__(self):
return hex(norm(self.v))

def __long__(self): return long(norm(self.v))
def __int__(self): return int(norm(self.v))
def __chr__(self): return chr(norm(self.v))

def __add__(self, b):
r = U32()
r.v = C + norm(self.v + b.v)
return r

def __sub__(self, b):
r = U32()
if self.v < b.v:
r.v = C + norm(0x100000000L - (b.v - self.v))
else: r.v = C + norm(self.v - b.v)
return r

def __mul__(self, b):
r = U32()
r.v = C + norm(self.v * b.v)
return r

def __div__(self, b):
r = U32()
r.v = C + (norm(self.v) / norm(b.v))
return r

def __mod__(self, b):
r = U32()
r.v = C + (norm(self.v) % norm(b.v))
return r

def __neg__(self): return U32(self.v)
def __pos__(self): return U32(self.v)
def __abs__(self): return U32(self.v)

def __invert__(self):
r = U32()
r.v = C + norm(~self.v)
return r

def __lshift__(self, b):
r = U32()
r.v = C + norm(self.v << b)
return r

def __rshift__(self, b):
r = U32()
r.v = C + (norm(self.v) >> b)
return r

def __and__(self, b):
r = U32()
r.v = C + norm(self.v & b.v)
return r

def __or__(self, b):
r = U32()
r.v = C + norm(self.v | b.v)
return r

def __xor__(self, b):
r = U32()
r.v = C + norm(self.v ^ b.v)
return r

def __not__(self):
return U32(not norm(self.v))

def truth(self):
return norm(self.v)

def __cmp__(self, b):
if norm(self.v) > norm(b.v): return 1
elif norm(self.v) < norm(b.v): return -1
else: return 0

def __nonzero__(self):
return norm(self.v)
Binary file added ntlm/U32.pyc
Binary file not shown.
Empty file added ntlm/__init__.py
Empty file.
Binary file added ntlm/__init__.pyc
Binary file not shown.
92 changes: 92 additions & 0 deletions ntlm/des.py
@@ -0,0 +1,92 @@
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
#
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.

import des_c

#---------------------------------------------------------------------
class DES:

des_c_obj = None

#-----------------------------------------------------------------
def __init__(self, key_str):
""
k = str_to_key56(key_str)
k = key56_to_key64(k)
key_str = ''
for i in k:
key_str += chr(i & 0xFF)
self.des_c_obj = des_c.DES(key_str)

#-----------------------------------------------------------------
def encrypt(self, plain_text):
""
return self.des_c_obj.encrypt(plain_text)

#-----------------------------------------------------------------
def decrypt(self, crypted_text):
""
return self.des_c_obj.decrypt(crypted_text)

#---------------------------------------------------------------------
#Some Helpers
#---------------------------------------------------------------------

DESException = 'DESException'

#---------------------------------------------------------------------
def str_to_key56(key_str):
""
if type(key_str) != type(''):
#rise DESException, 'ERROR. Wrong key type.'
pass
if len(key_str) < 7:
key_str = key_str + '\000\000\000\000\000\000\000'[:(7 - len(key_str))]
key_56 = []
for i in key_str[:7]: key_56.append(ord(i))

return key_56

#---------------------------------------------------------------------
def key56_to_key64(key_56):
""
key = []
for i in range(8): key.append(0)

key[0] = key_56[0];
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);
key[7] = (key_56[6] << 1) & 0xFF;

key = set_key_odd_parity(key)

return key

#---------------------------------------------------------------------
def set_key_odd_parity(key):
""
for i in range(len(key)):
for k in range(7):
bit = 0
t = key[i] >> k
bit = (t ^ bit) & 0x1
key[i] = (key[i] & 0xFE) | bit

return key
Binary file added ntlm/des.pyc
Binary file not shown.

0 comments on commit d9142e2

Please sign in to comment.