Git Product home page Git Product logo

ntlm-auth's Introduction

ntlm-auth

Build StatusBuild statusCoverage Status

This library is deprecated in favour of pyspnego

An example of an NTLM auth exchange in pyspnego is as follows:

import spnego

def exchange_data(data):
    # INSERT CODE TO TRANSFER TO SERVER AND RETURN RESPONSE
    return b""

client = spnego.client('username', 'password', protocol='ntlm')
negotiate = client.step()

challenge = exchange_data(negotiate)
authenticate = client.step(challenge)
exchange_data(authenticate)

This library also supports Kerberos and Negotiate/SPNEGO authentication. While ntlm-auth is not going away, no new features will be added.

About this library

This library handles the low-level details of NTLM authentication for use in authenticating with a service that uses NTLM. It will create and parse the 3 different message types in the order required and produce a base64 encoded value that can be attached to the HTTP header.

The goal of this library is to offer full NTLM support including signing and sealing of messages as well as supporting MIC for message integrity and the ability to customise and set limits on the messages sent. Please see Features and Backlog for a list of what is and is not currently supported.

Features

  • LM, NTLM and NTLMv2 authentication
  • NTLM1 and NTLM2 extended session security
  • Set the The NTLM Compatibility level when sending messages
  • Channel Binding Tokens support, need to pass in the SHA256 hash of the certificate for it to work
  • Support for MIC to enhance the integrity of the messages
  • Support for session security with signing and sealing messages after authentication happens

Installation

ntlm-auth supports Python 2.6, 2.7 and 3.3+

To install, use pip:

pip install ntlm-auth

To install from source, download the source code, then run:

python setup.py install

Usage

Almost all users should use requests-ntlm instead of this library. The library requests-ntlm is a plugin that uses this library under the hood and provides an easier function to use and understand.

If you are set on using ntlm-auth directly to compute the message structures this is a very basic outline of how it can be done. The code examples are psuedocode and should be adapted for your purpose.

When initliasing the ntlm context you will have to supply the NTLM compatibility level. The key difference between the different auth levels are the ntlm_compatibility variable supplied when initialising Ntlm. An overview of what each sets is below;

  • 0 - LM Auth and NTLMv1 Auth
  • 1 - LM Auth and NTLMv1 Auth with Extended Session Security (NTLM2)
  • 2 - NTLMv1 Auth with Extended Session Security (NTLM2)
  • 3 - NTLMv2 Auth (Default Choice)
  • 4 - NTLMv2 Auth
  • 5 - NTLMv2 Auth

Level 3 to 5 are the same from a client perspective but differ with how the server handles the auth which is outside this project's scope. This setting is set independently on that server so choosing 3, 4 or 5 when calling Ntlm will make no difference at all. See LmCompatibilityLevel for more details.

Extended Session Security is a security feature designed to increase the security of LM and NTLMv1 auth. It is no substitution for NTLMv2 but is better than nothing and should be used if possible when you need NTLMv1 compatibility.

The variables required are outlined below;

  • username - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\USER
  • password - The password of the user to authenticate with
  • domain - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment
  • workstation - The workstation you are running on. Can be blank if you do not wish to send this
  • cbt_data - (NTLMv2 only) The gss_channel_bindings.GssChannelBindingsStruct used to bind with the auth response. Can be None if no binding needs to occur

LM Auth/NTLMv1 Auth

LM and NTLMv1 Auth are older authentication methods that should be avoided where possible. Choosing between these authentication methods are almost identical expect where you specify the ntlm_compatiblity level.

import socket

from ntlm_auth.ntlm import NtlmContext

username = 'User'
password = 'Password'
domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info

ntlm_context = NtlmContext(username, password, domain, workstation, ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth
negotiate_message = ntlm_context.step()

# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']

authenticate_message = ntlm_context.step(challenge_message)

# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1

NTLMv2

NTLMv2 Auth is the newest NTLM auth method from Microsoft and should be the option chosen by default unless you require an older auth method. The implementation is the same as NTLMv1 but with the addition of the optional server_certificate_hash variable and the ntlm_compatibility is not specified.

import base64
import socket

from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
from ntlm_auth.ntlm import NtlmContext

username = 'User'
password = 'Password'
domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info

# create the CBT struct if you wish to bind it with the auth response
server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18'
certificate_digest = base64.b16decode(server_certificate_hash)
cbt_data = GssChannelBindingsStruct()
cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest

ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3)
negotiate_message = ntlm_context.step()

# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']

authenticate_message = ntlm_context.step(challenge_message)

# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1

Signing/Sealing

All version of NTLM supports signing (integrity) and sealing (confidentiality) of message content. This function can add these improvements to a message that is sent and received from the server. While it does encrypt the data if supported by the server it is only done with RC4 with a 128-bit key which is not very secure and on older systems this key length could be 56 or 40 bit. This functionality while tested and conforms with the Microsoft documentation has yet to be fully tested in an integrated environment. Once again this has not been thoroughly tested and has only passed unit tests and their expections.

import base64
import socket

from ntlm_auth.ntlm import NtlmContext

username = 'User'
password = 'Password'
domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info

# create the CBT struct if you wish to bind it with the auth response
server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18'
certificate_digest = base64.b16decode(server_certificate_hash)
cbt_data = GssChannelBindingsStruct()
cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest

ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3)
negotiate_message = ntlm_context.step()

# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']

authenticate_message = ntlm_context.step(challenge_message)

# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1

# Encrypt the message with the wrapping function and send the message
enc_message = ntlm_context.wrap("Message to send", encrypt=True)
request.body = msg_data
request.send

# Receive the response from the server and decrypt
response_msg = response.content
response = ntlm_context.unwrap(response_msg)

Backlog

  • Automatically get windows version if running on windows, use default if not that case
  • Add param when initialising the ntlm context to throw an exception and cancel auth if the server doesn't support 128-bit keys for sealing
  • Add param when initialising the ntlm context to not send the MIC structure for older servers
  • Add param to independently verify the target name returned from the server and the value passed in

ntlm-auth's People

Contributors

fredtrot avatar jborean93 avatar jobec avatar nnam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ntlm-auth's Issues

Can this library compute Type 2 and the final authentication result messages?

I am working on creating a server side NTLM authentication component that will help in testing of NTLM clients. Since this server side component is only meant for testing purposes, it will not integrate with Active Directory or Domain Controller, but will simply authenticate using a hard-coded list of credentials. However, it will support all types of NTLM authentication - LM, NTLM, LMv2, NTLMv2, NTLM2 Session Response, NTLM1 Signing & Sealing, etc. This will provide a way to test the NTLM capabilities of the clients. It will be an NTLM equivalent to http://httpbin.org/basic-auth/user/passwd which can be used to test basic authentication.

My question is - does this library have - (1) support to compute Type 2 (Challenge) messages by taking a Type 1 message as input and (2) support to compute the authentication result message by taking a Type 3 message? Or is this only a client-side library that will help in generating Type 1 and Type 3 messages?

I took a look at the code and I think this library only helps to compute Type 1 and Type 3 messages. But can you please confirm? (I am new to Python and I might have missed something.)

NTLM authentication fails because MD4 and MD5 are not available

When using ntlm_auth and hash md4 and md5 are not available the code fails. We can no longer use md4 and md5 due to security concerns. Is there are work around or update to use sha1?

We are receiving unsupported hash types when executing the code.
ValueError: unsupported hash type md4

License Incompatibility

Hello!

As part of packaging this for Debian, I found a license incompatibility (and a mismarking!).

The file ntlm_auth/des_c.py, ntlm_auth/des.py, and ntlm_auth/des_data.py are copyright Dmitry A. Rozmanov and licensed it as part of the NTLMAPS project under the GPLv2. The header marking shows that it's LGPLv3+, which is incorrect.

Unfortunately, GPLv2 (aka GPLv2 only) isn't compatible with LGPLv3+. If you hold the copyright to all other code, you could relicense the entire project under the LGPLv2.1+, which would effectively cause it to be distributed under GPLv2 itself. The other option would be removing those files from the project and replacing them.

Please let me know.

example doesn't work properly

hi!
The example provided doesn't work out-of-box. It gives a error on this line:

challenge_message = http.response.headers['HEADERFIELD']

Pip install fails on CentOS6

When I try to install this package with Pip on CentOS6 in a new virtualenv I get the following error:

[vagrant@localhost ~]$ source test/bin/activate
(test)[vagrant@localhost ~]$ pip list
pip (1.4.1)
setuptools (0.9.8)
(test)[vagrant@localhost ~]$ pip install ntlm-auth
Downloading/unpacking ntlm-auth
  Downloading ntlm-auth-1.0.4.tar.gz
  Running setup.py egg_info for package ntlm-auth
    error in ntlm-auth setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers
    Complete output from command python setup.py egg_info:
    error in ntlm-auth setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in /home/vagrant/test/build/ntlm-auth
Storing complete log in /home/vagrant/.pip/pip.log

It looks like a problem in just the most recent version (1.0.4) because when I specify a version manually the package installs without any difficulty.

(test)[vagrant@localhost ~]$ pip install ntlm-auth==1.0.3
Downloading/unpacking ntlm-auth==1.0.3
  Downloading ntlm-auth-1.0.3.tar.gz
  Running setup.py egg_info for package ntlm-auth
Downloading/unpacking six (from ntlm-auth==1.0.3)
  Downloading six-1.10.0.tar.gz
  Running setup.py egg_info for package six
    no previously-included directories found matching 'documentation/_build'
Installing collected packages: ntlm-auth, six
  Running setup.py install for ntlm-auth
  Running setup.py install for six
    no previously-included directories found matching 'documentation/_build'
Successfully installed ntlm-auth six
Cleaning up...

Thanks!

Generating md4 hash fails

After upgrading openssl on my OS, generating a md4 hash doesn't work anymore and I get the following error:

Traceback (most recent call last):
  File "/home/erik/projects/exchange/.venv/lib/python3.10/site-packages/ntlm_auth/compute_hash.py", line 66, in _ntowfv1
    digest = hashlib.new('md4', password.encode('utf-16-le')).digest()
  File "/usr/lib/python3.10/hashlib.py", line 166, in __hash_new
    return __get_builtin_constructor(name)(data)
  File "/usr/lib/python3.10/hashlib.py", line 123, in __get_builtin_constructor
    raise ValueError('unsupported hash type ' + name)
ValueError: unsupported hash type md4

I found a solution here
Would you be interested in a PR?

ntlm-auth installs ordereddict on python 3

Although ordereddict is part of python 3, this package still installs it. Issue occurred with python 3.4.6 on CentOS 7.

Collecting ordereddict (from ntlm-auth>=1.0.2->requests_ntlm==1.0.0->-r requirements.txt (line 40))

It looks like the syntax in setup.py is incorrect.

install_requires=[
    "six",
    "ordereddict ; python_version<'2.7'"
],

While the documentation hints it should be:

install_requires=[
    'six',
    'ordereddict;python_version<"2.7"'
],

(didn't test it though...)

messages.py target_info_raw == b'' throws exception

With our corporate Proxy the line

self.target_info.unpack(target_info_raw)
raises an exception because there the byte array target_info_raw has a len of 0. Can you please add a workaround for such a proxy configuration (since it's only an info)?

I tested a quick fix for the target Info where the if clause always runs in the else: branch. Afterwards the communication works correctly.

Missing copyright notice

I cannot find any copyright notice in this package, but I can see that multiple authors are involved due to the fork history on github. The LGPL license requires a clear copyright notice to be valid, as does the Debian project before I can package this (which is needed for requests-ntlm, which is needed for pywinrm, which is needed for ansible on windows, which I would like to see working in Debian).

Please could you include a copyright notice in somewhere obvious, like the PKG-INFO file? It's likely that all the past contributed would need to be listed, unless they were doing the work for hire.

Thanks!

Tag 1.0.6

There is ntlm-auth 1.0.6 on PyPI but there is no tag for it. There should be a tag.

Version unpack exception

Hi,

When using via requests-ntlm 2.17.3 (which uses the default ntlm_compatibility=3), I get an exception with my proxy:

  File "C:\...\lib\site-packages\ntlm_auth\messages.py", line 144, in __init__
    self.version = struct.unpack("<q", msg2[48:56])[0]
struct.error: unpack requires a string argument of length 8

I checked len(msg2) == 48 (which would be why unpack fails)

If I explicitly set the ntlm_compatibility = 0, then it correctly authenticates.

Any tips? Is our ntlm proxy doing something unexpected?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.