miketeo/pysmb

pysmb-provided MD4 implementation for Python3 is broken

zentarim opened this issue · 6 comments

If there is no MD4 hash in hashlib then pysmb provides it's own pure Python class instead. It produces a wrong hash what render the lib unusable.

Test case:

import hashlib
from smb.utils.md4 import MD4


if __name__ == '__main__':
    test_bytes = b"The quick brown fox jumps over the lazy dog"
    md4_pysmb = MD4()
    md4_pysmb.update(test_bytes)
    digest_pysmb = md4_pysmb.digest()

    md4_builtin = hashlib.new('md4')
    md4_builtin.update(test_bytes)
    digest_builtin = md4_builtin.digest()

    print(digest_pysmb.hex())               # 1b00ee006900a4006b00a800110018005c00190047006200ab00ae00ae009000
    print(digest_builtin.hex())             # 1bee69a46ba811185c194762abaeae90
    assert digest_pysmb == digest_builtin   # Fails

pysmb version:
1.2.7

Python 3.8.13

I was able to make it work, although the code does look weird for me. Seems that s and padding in MD4.update() are intended to represent "lists of bytes". Moreover, they are initialized as such. Next both of them get filled with U32 objects which, I believe, represents an unsigned int 4 bytes long. It makes no sense for me.

As far as I can see, all it works because U32 objects always return a number within 1 byte range.

Since 9c472d42e3ac193c2e43d947b9c9c0c50aa5bd95 the code could not work properly any more, because a 16-bytes hex string result additionally getting encoded as 'UTF-16LE' and becoming 32-bytes long. Moreover, after 9c472d42e3ac193c2e43d947b9c9c0c50aa5bd95 all NTLM tests have broken:

$ nose2 -v tests.test_ntlm
tests.test_ntlm.test_NTLMv1_with_extended_security ... FAIL
tests.test_ntlm.test_NTLMv1_without_extended_security ... FAIL
tests.test_ntlm.test_NTLMv2 ... FAIL

======================================================================
FAIL: tests.test_ntlm.test_NTLMv1_with_extended_security
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/zentarim/py/pysmb/python3/tests/test_ntlm.py", line 27, in test_NTLMv1_with_extended_security
    assert binascii.hexlify(nt_challenge_response).lower() == b'75 37 f8 03 ae 36 71 28 ca 45 82 04 bd e7 ca f8 1e 97 ed 26 83 26 72 32'.replace(b' ', b'')  # [MS-NLMP]: 4.2.3.2.2
AssertionError

======================================================================
FAIL: tests.test_ntlm.test_NTLMv1_without_extended_security
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/zentarim/py/pysmb/python3/tests/test_ntlm.py", line 14, in test_NTLMv1_without_extended_security
    assert binascii.hexlify(nt_challenge_response).lower() == b'67 c4 30 11 f3 02 98 a2 ad 35 ec e6 4f 16 33 1c 44 bd be d9 27 84 1f 94'.replace(b' ', b'')  # [MS-NLMP]: 4.2.2.2.1
AssertionError

======================================================================
FAIL: tests.test_ntlm.test_NTLMv2
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/zentarim/py/pysmb/python3/tests/test_ntlm.py", line 47, in test_NTLMv2
    assert binascii.hexlify(lm_challenge_response).lower() == b'86 c3 50 97 ac 9c ec 10 25 54 76 4a 57 cc cc 19 aa aa aa aa aa aa aa aa'.replace(b' ', b'')  # [MS-NLMP]: 4.2.4.2.1
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=3)

A pull-request is proposed in #198

After it, the tests start to pass again:

$ nose2 -v tests.test_ntlm
tests.test_ntlm.test_NTLMv1_with_extended_security ... ok
tests.test_ntlm.test_NTLMv1_without_extended_security ... ok
tests.test_ntlm.test_NTLMv2 ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.006s

OK

I forgot to mention the root cause:

I hit this issue in Ubuntu 22.04 which has TLS 3 installed where MD4 algorithm is deprecated.
So, the embedded in your lib pure python version is used.

looking into this too. __int__() support seems to be dropped in python 3.10, maybe? We're experimenting with this fix:

def int_array2str(array):
    str = ""
    for i in array:
        if isinstance(i, U32):
            str = str + chr(int(i.truth()))
        else:
            str = str + chr(i)
    return str

FWIW, we think this was the breaking change in 3.10. from the change docs: "Builtin and extension functions that take integer arguments no longer accept Decimals, Fractions and other objects that can be converted to integers only with a loss (e.g. that have the int() method but do not have the index() method). (Contributed by Serhiy Storchaka in bpo-37999.)
"

from the change docs: "Builtin and extension functions that take integer arguments no longer accept Decimals, Fractions an

This int_array2str function is utterly a Python2 one. I see no need for it in Python3.

pysmb 1.2.8 has been released which might relieve this issue.

It works.

Thanks, @miketeo !