没错,NTLM就是你听说过的那个NTLM。是微软应用最广泛的认证协议之一。 NTLM是NT LAN Manager的缩写,这也说明了协议的来源。NTLM 是 Windows NT 早期版本的标准安全协议。Windows 2000内置三种基本安全协议之一。 NTLM适用范围非常广,既可用于域内的认证服务, 也可用于没有AD的环境,让两台独立电脑相互认证。

你可能每天都用到它而不自知,你也肯可能觉得你很熟悉它了,但是这里可能还有你所不知道的背后的秘密。  比如,你可能知道NTLM可以认证用户身份,但是你可能不知道NTLM可以提供会话安全服务(NTLM Session security)。  好了, 多的介绍我就不多说了。 网上的介绍非常多了。 我们直接主角。

本文也会分成几篇来写, 现在计划可能包含下面几个大方面的内容:

一.  NTLM协议的认证, 包括 NTLMv1  和 NTLMv2 两个版本。  涉及到NTLM的认证全过程,以及NTLM 的 EPA(Extended Protection for Authentication)实现。

二. NTLM Session Security, 即NTLM的会话安全,或者说是NTLM的SSPI实现。 这一部分内容可能不是很常用,能找到的资料也比较少。 这里先简单的说两句。 我们都知道NTLM可以用于认证,实际上,认证过后,NTLM还能提供安全服务。 比如提供对后续通信的安全签名,防篡改,或者对后续通信进行安全可信赖加密,防止被窃听。这一部分实际上是微软SSPI服务内容。 SSPI是Security Support Provider Interface(Microsoft安全支持提供器接口)。  SSPI是一个安全框架,很多协议都对它有实现。 比如我们熟知的Kerberos认证协议,它也有SSPI实现的部分。

三, NTLM消息的实例

这一部分,我们会找一个具体的实例来解释认证的全过程。

值得注意的是 NTLM 现在已经不光用于windows平台了,Linux 或者 java 平台也可以进行 NTLM 认证,使用 NTLM 的安全服务(但是Java平台对NTLM的会话安全支持貌似有问题)。

作为一个开始,我们先来介绍几个基础概念, 概念清楚了,然后再看技术细节。

1, 什么是认证。

认证就是承认和证明的意思。 就是你能证明你的身份。 比如你要访问一个受保护的资源,服务器需要认证你的身份。 你可以声称你是系统管理员, 但是怎么证明你就是管理员呢。 方法很多,这里有个简单直接的方法就是证明你知道管理员的密码。

认证的问题转化为: “怎么证明你知道你所声称的用户的密码?”这个问题了。

一个简单暴力的证明方法是,让你直接提供密码给服务器,然后服务器去数据库里面比对,看你提供的密码对不对。如果对,认证通过。否则失败。 常见的所谓Windows Forms认证,或者叫做windows basic 认证就是这种方式。 简单直接。 但是密码需要在网络上传输,安全问题就不说了,你懂的。

怎样在不直接提供密码的情况下,间接证明你知道密码呢?  NTLM就是干这个的了。

看起来很神奇,不是吗。  比如对面有两个人互相说话(通信), 说的都是明文,每一句你都能听懂。他们并没有说自己的密码就相互认证身份了,你听了半天,却不知道密码是什么。  更神奇的是, 他们认证之后,再说的话你可能就听不懂了(NTLM Session security,会话安全)。 (如果他们协商会话安全之后,后续的通信都是安全加密的。)

2. 什么是NTLM 认证。

前面已经说过了, NTLM是一种在不直接提供密码的情况下,间接证明客户端知道用户密码的方法。

NTLM认证最常见的应用场景恐怕就是用在浏览器(http协议)上的认证了。 但是实际上,NTLM 只规定了认证的流程,和认证消息格式。 并不跟具体的协议相关。 所以跟http就更没有必然联系了。 浏览器只是在http协议头上携带了NTLM的消息而已,通过了认证。 我们知道http通常是明文的,所以如果直接传输密码非常不安全,NTLM就有效的防止了这个问题。

怎么理解这个问题呢,举个夸张点的例子,  如果不嫌烦,客户端和服务端甚至可以通过传递小纸条的方式传递NTLM消息,来认证身份。 而纸条的内容是明文的, 中间传递者都可以随意查看,但是却无法知道密码,也无法伪造。

现在可以开始技术细节了, 我们还是采取由大及小的方式。 先从整体介绍,再逐步深入。

1. NTLM 的 认证消息,及认证流程。

前面说过了,NTLM消息并不和任何传输协议绑定, 它的认证消息理论上可以通过任何方式传递,所以我们的讨论都集中在协议本身,而不去关心下层的传输方式。

我们先看一个图:

NTLM认证共需要三个消息完成:

(1).  Type1 消息。 Negotiate 协商消息。

客户端在发起认证时,首先向服务器发送协商消息。 协商需要认证的主体,用户,机器以及需要使用的安全服务等等信息。 并通知服务器自己支持的协议内容,加密等级等等。

(2). Type2 消息。 Challenge 挑战消息。

服务器在收到客户端的协商消息之后, 会读取其中的内容,并从中选择出自己所能接受的服务内容,加密等级,安全服务等等。 并生成一个随机数challenge, 然后生成challenge消息返回给客户端。

(3). Type3 消息。 Authenticate认证消息。

客户端在收到服务端发回的Challenge消息之后, 读取熬服务端所支持的内容,和随机数challenge。  决定服务端所支持的内容是否满足自己的要求。 如果满足,则使用自己的密码以及服务器的随机数challenge通过复杂的运算,期间可能需要自己生成一个客户端随机数client challenge也加入运算, 并最终生成一个认证消息。并发回给服务器。

(4). 服务器在收到 Type3的消息之后, 回经过几乎同样的运算,并比较自己计算出的认证消息和服务端发回来的认证消息是否匹配。如果匹配,则证明客户端掌握了正确的密码,认证成功。 允许客户端使用后续服务。  如果不匹配,则认证失败。

2. NTLM 认证消息的结构.

NTLM的消息很简单, 只有三种, Type1,  Type2 和 Type3.   它们都有相似的结构。  认证消息都是二进制的,但是通常我们见到的都是它们的Base64的编码格式。 类似这种:

   1:  TlRMTVNTUAADAAAAGAAYAHAAAACSAJIAiAAAAAAAAAAAAAAAGgAaAEgAAAAOAA4AYgAAAAAAAAAAAAAABYKIogAAAAAAAAAPYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBOAEUASQBMAC0AUABDALZLpLeO2n6Sx1s9JjrAfQOqf2QsmfTeP9cjC86k7BsjZEsKzjOoYBcBAQAAAAAAAEDGE3IuR88Bqn9kLJn03j8AAAAAAgAEAEsAQQABAAoARgBTAFcARQBCAAQADABrAGEALgBjAG8AbQADABgAZgBzAHcAZQBiAC4AawBhAC4AYwBvAG0ABQAMAGsAYQAuAGMAbwBtAAcACAC0gtdyLkfPAQAAAAAAAAAA

所以,如果你看到这种形式不要吃惊,把他们用Base64解码即可。

协议中的数字都是采用小端的方式存储。

(1).  消息头

这三种消息都具有相似的消息头:

(2) Flags 标记。

这三种消息一般都会携带一个 4字节的int值, 作为消息的Flags。 Flags在三种消息中的位置不一样,所以没有当做消息头来介绍。 不过,这个flags非常重要。这里先单独来介绍:

(下面是我的代码片段,重点标志我做了注释,后面用到的时候会进一步介绍)

   1:        flagsExps[0x1] = "Unicode";  
   2:        flagsExps[0x2] = "OEM";
   3:        flagsExps[0x4] = "Request Target";
   4:        flagsExps[0x8] = "r10(must be zero)";
   5:   
   6:        flagsExps[0x10] = "Negotiate Sign"; // 需要协商签名服务
   7:        flagsExps[0x20] = "Negotiate Seal"; // 需要协商加密服务
   8:        flagsExps[0x40] = "Negotiate Datagram Style"; //UDP 非连接模式
   9:        flagsExps[0x80] = "Negotiate Lan Manager Key"; // 在某些特定的NTLMv1下使用 Lan manager key  后面会详细介绍
  10:   
  11:        //================================
  12:        flagsExps[0x100] = "Negotiate Netware(r9 must be zero)";
  13:        flagsExps[0x200] = "Negotiate NTLM";
  14:        flagsExps[0x400] = "r8(should be zero)";
  15:        flagsExps[0x800] = "Negotiate Anonymous"; //使用匿名登录
  16:   
  17:        flagsExps[0x1000] = "Negotiate Domain Supplied";
  18:        flagsExps[0x2000] = "Negotiate Workstation Supplied";
  19:        flagsExps[0x4000] = "Negotiate Local Call(r7)";
  20:        flagsExps[0x8000] = "Negotiate Always Sign";
  21:   
  22:   
  23:        //===============================
  24:        flagsExps[0x10000] = "Target Type is a Domain.";
  25:        flagsExps[0x20000] = "Target Type is a Server.";
  26:        flagsExps[0x40000] = "Target Type Share(r6)";
  27:        flagsExps[0x80000] = "Negotiate NTLM2 Key(EXTENDED_SESSIONSECURITY)"; //使用扩展会话安全
  28:   
  29:        flagsExps[0x100000] = "Request Init Response(NTLMSSP_NEGOTIATE_IDENTIFY)";
  30:        flagsExps[0x200000] = "Request Accept Response(r5, must be zero)";
  31:        flagsExps[0x400000] = "Request Non-NT Session Key";
  32:        flagsExps[0x800000] = "Negotiate Target Info"; //协商 携带 TargetInfo
  33:   
  34:        //===============================
  35:        flagsExps[0x1000000] = "r4(must be zero)";
  36:        flagsExps[0x2000000] = "NTLMSSP_NEGOTIATE_VERSION(协商携带操作系统版本号, 一般会忽略此项,仅供调试用途 )";
  37:        flagsExps[0x4000000] = "r3(must be zero)";
  38:        flagsExps[0x8000000] = "r2(must be zero)";
  39:   
  40:        flagsExps[0x10000000] = "r1(must be zero)";
  41:        flagsExps[0x20000000] = "Negotiate 128"; // 协商128位加密
  42:        flagsExps[0x40000000] = "Negotiate Key Exchange"; //协商交换key, 在会话安全中,使用交换key来加密内容,而不是直接使用会话key
  43:        flagsExps[0x80000000] = "Negotiate 56"; //协商56位加密

(3). Type 1 消息

我这里使用下面参考文档2中的一个图来介绍:

a . 前面首先是 8个字节+4个字节的 协议头。前面已经介绍过了。注意,消息类型为1

b . 然后是四个字节的Flags。 前面也介绍过了。  这个Flags中表达了客户端想要使用的NTLM的认证服务, 以及客户端自己所支持的服务。

c. 然后,是若干个可选的security buffer。

注意上图中的 security buffer, 安全缓冲区。 它是一个8字节固定大小的结构体。它看起来像是这个样子:

它其实是一个缓冲区指针。 它指向一个区域, 这个区域相对于Type1消息起始的偏移量为 offset, 这个区域的大小长度为 MaxLength, 其中的有效使用大小为:Length.

如果 Flags 中包含 0×1000 标记, 则需要提供域。把域名写入到这个缓冲区中。 字符集由Flags中的 OEM或者Unicode决定。

如果Flags中包含0×2000标记,则需要提供workstation的名字。  方法同上。

d. 然后是8字节的操作系统版本号。

如果Flags中包含0×2000000, 则需要提供 操作系统的版本号。

操作系统版本号的写法,请参考 参考文档1 的 2.2.2.10 节。

e. 然后,就是携带的前面security buffer中所指向的数据了。

在Type1的消息中,只有前面的 a 和 b项,是必须的, 后面的都是可选的。

(4) Type2 消息

服务端在收到 Type1消息之后, 会生成Type2消息返回给客户端。

这个也比较简单,我们还是用 参考文档2 中的一个图来介绍:

a . 前面首先还是 8个字节+4个字节的 协议头。前面已经介绍过了。 注意此时的消息类型已经是 2了。

b . 然后是四个字节的Flags。 前面也介绍过了。  这个Flags中表达了服务端所能接受的服务。

c. 上图中的TargetName 是要访问的机器名。 存储方法是security buffer. 前面介绍过了,不多说了。

d. Flags 不多说了。

e. Challenge, 这是一个服务端生成的8字节的随机数, 客户端会用来计算key. 后面会详细介绍用法。

f. Context, 这是一个8字节的值,其实是两个连续的32位值。 表示一个内部handle。 当服务端发现客户端就是自身的时候(在用一台电脑上),就会生成一个内部的安全handle,填充到这个字段。并且在Flags中设置  0×4000标记。 表示这是一个本地认证。

g. TargetInformation 也是一个security buffer。 其存储方式前面介绍过。 但是它的buffer中的内容需要介绍一下。基本上,TargetInformation是由一系列连续的 AV_PAIR 结构体连接起来的。

而AV_PAIR的结构如下:

(请参考下面 参考文档1 的 2.2.2.1节)

i) 先是两个字节的int16值, 表示 id, 表示这个AV结构的类型。

目前有如下类型:

ii) 然后是两个字节的int16值,表示值的长度。

iii) 然后就是值。 它的长度在上面指定的。

这个TargetInformation 结构非常重要。 后面Type3中还会用到。

h. 然后是8字节的操作系统版本号。 Type1中已经介绍过了。 不多说了。

然后后面就是要携带的数据了。

到这里先告一段落吧, 这里介绍了 NTLM 的概念以及认证流程。 并详细介绍了Type1和Type2消息, 以及Flags每个标志位的意义。 这大概覆盖了NTLM内容的30%左右的内容。  Type3消息是整个认证中最为复杂和关键的部分,我们将在下一篇中独立介绍。

希望大家多多支持。

欢迎访问我的个人独立博客: http://byNeil.com , 希望和大家多多交流共同进步。

参考文档:

1. MS-NLMP-NT-LAN-Manager-NTLM-Authentication-Protocol-Specification

2. The NTLM Authentication Protocol and Security Support Provider

3. freebsd-freebsd  源码实现

4. Mozilla Firefox source code Developer Guide – MDN-firefox 源码

5. Heimdal NTLM 开源工程

6. samba.org-pub-unpacked-samba_3_current-librpc-idl-ntlmssp.idl

byNeil
byNeil.com

NTLM认证协议及SSPI的NTLM实现的更多相关文章

  1. 域渗透基础之NTLM认证协议

    域渗透基础的两个认证协议ntlm和Kerberos协议是必须总结的~ 这篇简单总结下ntlm协议 晚上写下kerberos 0x01 NTLM简介 NTLM使用在Windows NT和Windows ...

  2. 聊聊NTLM认证协议

    近期发现多家安全媒体发布NTLM协议漏洞的文章.他们越说越术语,越说越官方,如此这般下去,他们写出来到底给谁看?大雅就是俗,让我来一篇俗文.啥是NTLM呢?微软windows系统的用户账号存储密码哈希 ...

  3. [转]关于NTLM认证的.NET,php,python登录

    本文转自:http://www.cnblogs.com/myx/archive/2013/03/25/php-ntlm-python-net.html 早期SMB协议在网络上传输明文口令.后来出现 L ...

  4. iOS进行Basic认证与NTLM认证

    一.iOS进行Basic认证 只需要在NSMutableURLRequest的Header中添加认证所需的Username和password. NSMutableURLRequest *webReq ...

  5. 域渗透基础之Kerberos认证协议

     本来昨晚就该总结整理,又拖到今天早上..6点起来赶可还行 0x01 Kerberos前言 Kerberos 是一种由 MIT(麻省理工大学)提出的一种网络身份验证协议.它旨在通过使用密钥加密技术为客 ...

  6. kerberos认证协议爱情故事

    0x01.kerberos简介 kerberos是一种域内认证协议,Kerberos的标志是三头狗,狗头分别代表以下角色: Client Server KDC(Key Distribution Cen ...

  7. [na]802.1x协议无线认证协议&dot1x有线认证实验

    以前搞无线时候,会涉及到无线client接入方式的认证协议. 认证方式+加密方式+有线的dot1x. 注:以前都是doc粘贴到博客的,加上没写博客的习惯,因此会比较乱. EAP(扩展认证协议)是什么? ...

  8. kerberos认证协议分析

    Kerberos认证协议分析 Kerberos认证协议流程 如上图: * 第一步:client和认证服务器(AS)通信完成认证过程,如果认证成功AS返回给client一个TGT(用来向TGS获取tic ...

  9. Radius 认证协议介绍-兼rfc导读

    老规矩, 先看维基: 远端用户拨入验证服务(RADIUS, Remote Authentication Dial In User Service)是一个AAA协议,意思就是同时兼顾验证(authent ...

随机推荐

  1. appium -ios-安卓 获取元素时 配置参数的方法

    iPhone_5S{ "automationName": "XCUITest", "platformName": "iOS&quo ...

  2. 根据插件Swipe,结合jQuery封装成的新的jQuery插件

    swipe支持电脑上的自动滑动,也支持手机端的滑动效果.但是每次调用只能支持一个效果或者说一个页面出现n个这样的效果,我们就得调用n次这个插件. 我使用swipe+jQuery使得swip变得方便使用 ...

  3. 【BZOJ】3191 [JLOI2013]卡牌游戏(概率dp)

    题目 传送门:QWQ 分析 算是概率dp不错的题. $ dp[i][j] $表示有i个人时,这i个人中的第j个获胜的概率. 我们把i从1推到n,那么答案就是$ dp[n][i] $ 然后我们规定,第一 ...

  4. C++ 并发编程 01 线程api

    1.使用多线程的好处: 提高性能,分离关注点  2. 多线程所在头文件 <thread> 3. 使用线程方式为std::thread(functioncall),如: #include & ...

  5. PG覆盖率检查

    覆盖率检查 需要gcov和lcov工具,gcov在gcc中自带,lcov需要自行下载安装 重新编译 ./configure --prefix=`pwd`/install --with-perl --w ...

  6. 解决Linux下MySQL登录ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using passwor)问题

    两种方案: 一: 1.修改mysql配置文件:vim /etc/my.cnf 2.找到[mysqld]下添加 skip-grant-tables 配置 3.重启服务service mysqld res ...

  7. MVC-READ2

    框架设计模式 契约式设计.元编程.元数据驱动设计.管道模型.远程代理模式.提供程序模型:

  8. Beetlsql自定义生成entity,mapper,md代码

    三个模板文件 mapper.btl package ${package}; import org.beetl.sql.core.annotatoin.*; import org.beetl.sql.c ...

  9. UNITY录制视屏解决方案 - ShareREC For Unity3D

    注意buildSetting里面,AutoGraphic改成opengl es2,否则魅蓝手机上容易出现1/4屏 一.导入项目 1.到Mob官网下载ShareREC For Unity3D 的SDK包 ...

  10. 二叉树叶子顺序遍历 · binary tree leaves order traversal

    [抄题]: 给定一个二叉树,像这样收集树节点:收集并移除所有叶子,重复,直到树为空. 给出一个二叉树: 1 / \ 2 3 / \ 4 5 返回 [[4, 5, 3], [2], [1]]. [暴力解 ...