1.简介
上篇文章中,主要简单的介绍了一下基本的概念,纠正了一下网络上现有文章存在的一些问题,接下来,我们来正视该体系下存在的问题。
2.会话签名 和 身份验证签名(MIC)
在详细说明该体系下存在的问题之前,我们先补充一个概念。
细心的同学们可能会注意到,在上篇文章中的ntml协议的数据包中少了点什么,具体少了什么呢?没错,就是MIC。
那么MIC具有什么作用呢?
MIC 保护了 NTLM 不会被修改。什么意思呢?
NTLM身份验证由3种消息类型组成:NTLM_NEGOTIATE,NTLM_CHALLENGE,NTLM_AUTHENTICATE。为了确保恶意行为者不在传输过程中处理消息,在NTLM_AUTHENTICATE消息中添加了一个额外的MIC(消息完整性代码)字段。MIC是使用会话密钥应用于所有3个NTLM消息的串联的HMAC_MD5,该会话密钥仅对启动认证的帐户和目标服务器是已知的。因此,试图篡改其中一条消息的攻击者(例如,修改签名协商)将无法生成相应的MIC,这将导致攻击失败。
翻译一下就是:会话签名决定了验证通过后,通讯会话是否需要加密。若通讯加密,因为无法获得加密的密钥,则无法完成后面的中间人攻击。
具体在认证过程中,数据包中是通过哪些字段来确认是否需要签名呢?
这里我总结了一下:
- 在谈判阶段,双方都表明他们的要求:其中之一是否需要签名?
- 在身份验证阶段,双方都表明他们支持什么。是否有签名能力?
-
在会话阶段,如果功能和要求兼容,则使用已协商的内容进行会话。
在 NTLM 协商阶段,NEGOTIATE_SIGN 若为 1 则表示有签名能力(但不是一定要签名)。
是否要启用签名主要取决于两个标志位 Signing enabled 和 Signing required
Signing enabled 表示是否支持签名
Signing required 表示是否需要签名
不同协议判定是否需要签名的条件也不同
● SMB:双方有一方的 Signing required 为 1 时,启用签名。也就是如果有一方明确表示了 需要签名 才会被启用。windows PC默认是不需要签名的,只有 server 版本默认需要签名(域控 2012 碰到过不开签名的情况,判断 SMB 是否需要签名,发送一次请求即可判断)。
● LDAP:协商签名,双方都支持签名则使用签名。这就是为啥在中继的时候不能从 SMB 中继到 LDAP 的原因。SMB 默认支持签名, 也就是 Signing enabled 字段默认为 1。从 SMB 到 LDAP 会触发 LDAP 签名导致中间人失败。
● HTTP:不支持签名。所以 可以从 HTTP 中继 LDAP 完成攻击。server2019 默认域控强制开启 LDAP 签名,让 HTTP 无法再中继到 LDAP,但是仍然可以中继到 LDAPS。
3.相关安全问题
3.1.PTH
pass the hash也叫做hash传递攻击,简称:PTH
根据前一篇文章我们知道,在type3计算response的时候,客户端是使用用户的hash进行计算的,而不是用户密码进行计算的。因此在模拟用户登录的时候。是不需要用户明文密码的,只需要用户hash。
Ps:在此不对pth的工具进行一一展示使用,后续会补上(挖坑),工具根本原理相同,不同的只是使用方法,这里仅选择一款展示效果
pth的效果:
说到PTH,就不得不提kb2871997这款补丁了,kb2871997这款补丁仅能暂缓pth,并不能杜绝pth。至于为什么,这里暂不讨论,后续补上(挖坑)
3.2.利用ntlm进行的信息收集
渗透测试的深度取决于信息收集的广度,那么,在打点阶段或者内网渗透阶段,能够有效获取目标主机的信息,就为拿下目标多一层可能。
这里直接看到type2阶段数据包:
在type2返回Challenge的过程中,同时返回了操作系统类型,主机名,netbios名等等。这也就意味着如果我们在能跟服务器进行ntlm 交流中,给服务器发送一个type1的请求,服务器返回type2的响应,这一步,我们就可以得到很多信息。
前面我们说过ntlm是一个嵌入式的协议,消息的传输依赖于使用ntlm的上层协议,比如SMB,LDAP,HTTP等。我们以SMB为例。在目标主机开放了445或者139的情况,通过给服务器发送一个type1的请求,然后解析type2的响应。就可以收集到一些信息。
这里直接贴一下网上找到的大牛的代码:
using System;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime;
using System.Runtime.InteropServices;
namespace Zcg.Tests
{
class smbver
{
static byte[] d1 ={
0x00, 0x00, 0x00, 0x85, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x53, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4E, 0x45, 0x54, 0x57, 0x4F,
0x52, 0x4B, 0x20, 0x50, 0x52, 0x4F, 0x47, 0x52, 0x41, 0x4D, 0x20, 0x31, 0x2E, 0x30, 0x00, 0x02,
0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6E, 0x64, 0x6F,
0x77, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x57, 0x6F, 0x72, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x70,
0x73, 0x20, 0x33, 0x2E, 0x31, 0x61, 0x00, 0x02, 0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30,
0x32, 0x00, 0x02, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x32, 0x2E, 0x31, 0x00, 0x02, 0x4E, 0x54,
0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00
};
static byte[] d2 ={
0x00, 0x00, 0x01, 0x0A, 0xFF, 0x53, 0x4D, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x18, 0x07, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x40, 0x00, 0x0C, 0xFF, 0x00, 0x0A, 0x01, 0x04, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0xA0, 0xCF, 0x00, 0x60,
0x48, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x02, 0xA0, 0x3E, 0x30, 0x3C, 0xA0, 0x0E, 0x30,
0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A, 0xA2, 0x2A, 0x04,
0x28, 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08,
0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x02, 0xCE, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00,
0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00,
0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00,
0x20, 0x00, 0x33, 0x00, 0x37, 0x00, 0x39, 0x00, 0x30, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00,
0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00,
0x33, 0x00, 0x20, 0x00, 0x35, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00
};
static byte[] d3={
0x81,0x00,0x00,0x44,0x20,0x43,0x4b,0x46,0x44,0x45,0x4e,0x45,0x43,0x46,0x44,0x45
,0x46,0x46,0x43,0x46,0x47,0x45,0x46,0x46,0x43,0x43,0x41,0x43,0x41,0x43,0x41,0x43
,0x41,0x43,0x41,0x43,0x41,0x00,0x20,0x43,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43
,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43,0x41,0x43
,0x41,0x43,0x41,0x43,0x41,0x41,0x41,0x00
};
static void Main(string[] args)
{
Console.WriteLine("SMB Version Detection tool 0.1");
Console.WriteLine("Part of GMH's fuck Tools, Code By zcgonvh.\r\n");
if (args.Length < 1) { Console.WriteLine("usage: smbver host [port]"); return; }
string host = args[0];
int port = 445;
try { port = int.Parse(args[1]); }
catch { }
try
{
byte[] buf = new byte[1024];
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(host, port);
if(port==139)
{
sock.Send(d3);
sock.Receive(buf);
}
sock.Send(d1);
sock.Receive(buf);
sock.Send(d2);
sock.Receive(buf);
int len = BitConverter.ToInt16(buf, 43);
string[] ss = Encoding.Unicode.GetString(buf, len + 47, buf.Length - len - 47).Split('\0');
Console.WriteLine("native os: " + ss[0]);
Console.WriteLine("native lan manager: " + ss[1]);
int off = 0;
for (int i = 47; i < len - 7; i++)
{
if (buf[i] == 'N' && buf[i + 1] == 'T' && buf[i + 2] == 'L' && buf[i + 3] == 'M' && buf[i + 4] == 'S' && buf[i + 5] == 'S' && buf[i + 6] == 'P') { off = i; break; }
}
byte[] ntlm = new byte[len];
Array.Copy(buf, off, ntlm, 0, len);
len = BitConverter.ToInt16(ntlm, 0xc);
off = BitConverter.ToInt16(ntlm, 0x10);
Console.WriteLine("negotiate target: " + Encoding.Unicode.GetString(ntlm, off, len));
Console.WriteLine("os major version: " + ntlm[off - 8]);
Console.WriteLine("os minor version: " + ntlm[off - 7]);
Console.WriteLine("os build number: " + BitConverter.ToInt16(ntlm, off - 6));
Console.WriteLine("ntlm current revision: " + ntlm[off - 1]);
off += len;
int type = BitConverter.ToInt16(ntlm, off);
while (type != 0)
{
off += 2;
len = BitConverter.ToInt16(ntlm, off);
off += 2;
switch (type)
{
case 1:
{
Console.WriteLine("NetBIOS computer name: " + Encoding.Unicode.GetString(ntlm, off, len));
break;
}
case 2:
{
Console.WriteLine("NetBIOS domain name: " + Encoding.Unicode.GetString(ntlm, off, len));
break;
}
case 3:
{
Console.WriteLine("DNS computer name: " + Encoding.Unicode.GetString(ntlm, off, len));
break;
}
case 4:
{
Console.WriteLine("DNS domain name: " + Encoding.Unicode.GetString(ntlm, off, len));
break;
}
case 5:
{
Console.WriteLine("DNS tree name: " + Encoding.Unicode.GetString(ntlm, off, len));
break;
}
case 7:
{
Console.WriteLine("time stamp: {0:o}", DateTime.FromFileTime(BitConverter.ToInt64(ntlm, off)));
break;
}
default:
{
Console.Write("Unknown type {0}, data: ", type);
for (int i = 0; i < len; i++)
{
Console.Write(ntlm[i + off].ToString("X2"));
}
Console.WriteLine();
break;
}
}
off += len;
type = BitConverter.ToInt16(ntlm, off);
}
}
catch (Exception ex)
{
Console.WriteLine("err: " + ex);
}
}
}
}
大家也可以仿造代码的形式,自己实现其他上层协议下的信息收集。
网图效果如下:
msf底下也有类似的模块
auxiliary/scanner/smb/smb_version
3.3.ntlm relay
作为一个在上世纪就被提出的安全问题,时至2022的今天,ntlm_relay仍然在远程命令执行。横向扩展,权限提升等方面发挥着巨大的作用。本篇文章剩余部门简单的介绍一些ntlm_relay相关的概念。
工具流程如下:
看图已经能够很清晰得理解ntlm_relay的一般过程,作为中间人,攻击者将来自客户端的包(type 1)转发给服务端,将来自服务端的challenge(type 2)转发给客户端,然后客户端计算完response 之后,再把response(type 3) 转发给服务端,服务端验证rsponse通过之后,授予攻击者访问的权限。
局限性:
中继的前提是目标 SMB 签名需要关闭,在 SMB 协议中,需要使用安全机制来保护服务器和客户端之间传输数据的完整性,而这种安全机制就是 SMB 签名和加密,如果关闭 SMB 签名,会允许攻击者拦截认证过程,并且将获得 hash 在其他机器上进行重放,从而获得权限。在工作组环境里面,工作组中的机器之间相互没有信任关系,每台机器的账号密码 Hash 只是保存在自己的 SAM 文件中,这个时候 Relay 到别的机器,除非两台机器的账号密码一样,不然没有别的意义了。
扩展:
由于NTLM协议为嵌入式协议,中继的方式并不局限于SMBtoSMB,也可以HTTPtoSMB,还有一个需要注意的地方就是,不同协议之间的特性,例如上面提到的SMB签名问题,又例如LDAP签名问题。
攻击效果:
本次攻击实验利用Inveigh做了一个局域网投毒+中继操作,(局域网投毒还涉及几个协议,这里放到下一篇来讲(挖坑))通过Inveigh监听中间人地址,当被投毒计算机进行某些操作时(可以发起NTLM请求的操作),就会直接将请求发送到中间人,中间人将请求再原封不动的发送给被中继机器。
Ps:局域网环境下,该操作其实并不常用,域环境下会遇到
4.总结
以上部分是针对局域网下的一些常用手法,也简单介绍了一下这些手法下的局限性,下一篇将讲讲本次未涉及的一些其他协议。不可去名上理会。须求其所以然。