diff --git a/lib/core/option.py b/lib/core/option.py index 920d4d95c..bb915197d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1515,15 +1515,8 @@ def _setHTTPAuthentication(): authHandler = _urllib.request.HTTPDigestAuthHandler(kb.passwordMgr) elif authType == AUTH_TYPE.NTLM: - try: - from ntlm import HTTPNtlmAuthHandler - except ImportError: - errMsg = "sqlmap requires Python NTLM third-party library " - errMsg += "in order to authenticate via NTLM. Download from " - errMsg += "'https://github.com/mullender/python-ntlm'" - raise SqlmapMissingDependence(errMsg) - - authHandler = HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(kb.passwordMgr) + from lib.request.ntlm import HTTPNtlmAuthHandler + authHandler = HTTPNtlmAuthHandler(kb.passwordMgr) else: debugMsg = "setting the HTTP(s) authentication PEM private key" logger.debug(debugMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 6df31db99..95cd32667 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.54" +VERSION = "1.10.7.55" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/request/ntlm.py b/lib/request/ntlm.py new file mode 100644 index 000000000..1b8881294 --- /dev/null +++ b/lib/request/ntlm.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free NTLM-over-HTTP authentication (MS-NLMP / MS-NTHT), replacing the ancient +# and unmaintained 'python-ntlm' third-party library. Pure standard library + the in-tree pyDes for +# the DES core (hashlib's MD4 is unavailable under OpenSSL 3's unloaded legacy provider, so MD4 is +# implemented here per RFC 1320). Python 2.7 / 3.x. Crypto validated against the [MS-NLMP] test vectors. + +import base64 +import binascii +import os +import re +import socket +import ssl +import struct + +from lib.core.convert import getBytes +from lib.core.convert import getText +from thirdparty.pydes.pyDes import des +from thirdparty.pydes.pyDes import ECB +from thirdparty.six.moves import http_client as _http_client +from thirdparty.six.moves import urllib as _urllib + +# ---------- MD4 (RFC 1320) ---------- +def _md4(data): + data = bytearray(data) + n = len(data) + data.append(0x80) + while len(data) % 64 != 56: + data.append(0) + data += struct.pack("> (32 - s))) & M + + for off in range(0, len(data), 64): + X = struct.unpack("<16I", bytes(data[off:off + 64])) + A, B, C, D = a, b, c, d + F = lambda x, y, z: (x & y) | (~x & z) + G = lambda x, y, z: (x & y) | (x & z) | (y & z) + H = lambda x, y, z: x ^ y ^ z + + for k in (0, 4, 8, 12): + A = rot((A + F(B, C, D) + X[k]) & M, 3) + D = rot((D + F(A, B, C) + X[k + 1]) & M, 7) + C = rot((C + F(D, A, B) + X[k + 2]) & M, 11) + B = rot((B + F(C, D, A) + X[k + 3]) & M, 19) + for k in (0, 1, 2, 3): + A = rot((A + G(B, C, D) + X[k] + 0x5a827999) & M, 3) + D = rot((D + G(A, B, C) + X[k + 4] + 0x5a827999) & M, 5) + C = rot((C + G(D, A, B) + X[k + 8] + 0x5a827999) & M, 9) + B = rot((B + G(C, D, A) + X[k + 12] + 0x5a827999) & M, 13) + for k in (0, 2, 1, 3): + A = rot((A + H(B, C, D) + X[k] + 0x6ed9eba1) & M, 3) + D = rot((D + H(A, B, C) + X[k + 8] + 0x6ed9eba1) & M, 9) + C = rot((C + H(D, A, B) + X[k + 4] + 0x6ed9eba1) & M, 11) + B = rot((B + H(C, D, A) + X[k + 12] + 0x6ed9eba1) & M, 15) + + a, b, c, d = (a + A) & M, (b + B) & M, (c + C) & M, (d + D) & M + + return struct.pack("<4I", a, b, c, d) + +# ---------- DES core (in-tree pyDes) with the NTLM 7->8 byte parity key expansion ---------- +def _str_to_key(chunk): + s = bytearray(chunk) + k = [s[0] >> 1, + ((s[0] & 0x01) << 6) | (s[1] >> 2), + ((s[1] & 0x03) << 5) | (s[2] >> 3), + ((s[2] & 0x07) << 4) | (s[3] >> 4), + ((s[3] & 0x0f) << 3) | (s[4] >> 5), + ((s[4] & 0x1f) << 2) | (s[5] >> 6), + ((s[5] & 0x3f) << 1) | (s[6] >> 7), + s[6] & 0x7f] + return bytes(bytearray((_ << 1) & 0xff for _ in k)) + +def _des(key7, block8): + return des(_str_to_key(key7), ECB).encrypt(block8) + +# ---------- negotiate flags ---------- +NTLM_NegotiateUnicode = 0x00000001 +NTLM_NegotiateOEM = 0x00000002 +NTLM_RequestTarget = 0x00000004 +NTLM_NegotiateNTLM = 0x00000200 +NTLM_NegotiateOemDomainSupplied = 0x00001000 +NTLM_NegotiateOemWorkstationSupplied = 0x00002000 +NTLM_NegotiateAlwaysSign = 0x00008000 +NTLM_NegotiateExtendedSecurity = 0x00080000 +NTLM_NegotiateTargetInfo = 0x00800000 +NTLM_NegotiateVersion = 0x02000000 +NTLM_Negotiate128 = 0x20000000 +NTLM_Negotiate56 = 0x80000000 +NTLM_MsvAvTimestamp = 7 + +NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode | NTLM_NegotiateOEM | NTLM_RequestTarget | NTLM_NegotiateNTLM | + NTLM_NegotiateOemDomainSupplied | NTLM_NegotiateOemWorkstationSupplied | + NTLM_NegotiateAlwaysSign | NTLM_NegotiateExtendedSecurity | NTLM_NegotiateVersion | + NTLM_Negotiate128 | NTLM_Negotiate56) +NTLM_TYPE2_FLAGS = (NTLM_NegotiateUnicode | NTLM_RequestTarget | NTLM_NegotiateNTLM | + NTLM_NegotiateAlwaysSign | NTLM_NegotiateExtendedSecurity | NTLM_NegotiateTargetInfo | + NTLM_NegotiateVersion | NTLM_Negotiate128 | NTLM_Negotiate56) + +_HASH_RE = re.compile(r"\A[0-9a-fA-F]{32}:[0-9a-fA-F]{32}\Z") + +# ---------- password hashes / challenge responses ---------- +def create_LM_hashed_password_v1(password): + if _HASH_RE.match(password): + return binascii.unhexlify(password.split(':')[0]) + pw = (getBytes(password.upper()) + b"\0" * 14)[:14] + return _des(pw[0:7], b"KGS!@#$%") + _des(pw[7:14], b"KGS!@#$%") + +def create_NT_hashed_password_v1(password, user=None, domain=None): + if _HASH_RE.match(password): + return binascii.unhexlify(password.split(':')[1]) + return _md4(password.encode("utf-16-le")) + +def calc_resp(password_hash, server_challenge): + ph = password_hash + b"\0" * (21 - len(password_hash)) + return _des(ph[0:7], server_challenge) + _des(ph[7:14], server_challenge) + _des(ph[14:21], server_challenge) + +def ntlm2sr_calc_resp(nt_hash, server_challenge, client_challenge): + import hashlib + lm_response = client_challenge + b"\0" * 16 + session = hashlib.md5(server_challenge + client_challenge).digest() + nt_response = calc_resp(nt_hash, session[0:8]) + return (nt_response, lm_response) + +# ---------- messages ---------- +def create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags=NTLM_TYPE1_FLAGS): + workstation = getBytes(socket.gethostname().upper()) + domain = getBytes(user.split('\\', 1)[0].upper()) + body = 40 + msg = b"NTLMSSP\0" + struct.pack("