一、前言

在漏洞挖掘工作中,攻击面的选择往往是一个非常重要的环节,一些意想不到的攻击面中往往存在大量开发测试人员疏忽的点。本文就关于一种有趣的攻击面进行探讨,将其起名为SaC(Server as Client)。

二、Client Server 架构

在带有网络通信的传统软件开发过程中,往往会使用一种叫做CS(Client Server)的架构,这个架构模型大致上是由提供服务的服务端(Server)以及请求服务资源的客户端(Client)组成。

2.1 CS架构的特征

服务端通常来说,具有如下的特点:

  • 具有高权限。为了方便的进行资源调度,权限控制等任务,大多数的服务端程序会以相当高的权限运行在机器上。
  • 可能为分布式。服务端为了减轻请求处理的压力,有时候会将服务以微服务部署在多个服务器上,或者在多个机器上部署的同时,使用调度工具对请求进行调度。
  • 持续监听,对请求反馈。服务端大多数为持续监听一个固定的对象,比如固定的某些端口,从而保证能够随时处理来自客户端的请求。
  • 对请求进行校验,防护完善。由于要对请求进行鉴权,所以有着更加严格的访问控制;同时由于服务端存放大量资源,一般会有非常严格的检查

而相对于服务端,客户端往往具有如下的特点:

  • 不一定为高权限进程。由于客户端往往只是请求某些资源,这个过程大多数情况下无需高权限访问。
  • 每次访问只维护单个会话。客户端的每次资源请求大多数只建立一次会话,不存在同时发起多个会话的情况。
  • 通常请求单个服务端资源。虽然客户端可能会请求多种不同类型的资源,但是每次请求基本上都有某一个固定的服务端进行响应处理。
  • 防护相对不到位。由于客户端本质上运行在客户机器上,加之运行权限不高,一般不会有特别复杂的防护机制
ServerClient
权限通常较高权限通常不高
可能为分布式单个端点请求资源
持续性监听单个会话访问
安全校验严格防护较为松散

而这类架构往往又具有如下的特点:

  • 双方在同一网络内
  • 请求由客户端发起,服务器端进行接受
  • 服务端具有大量的防护机制,包括但不限于
    • 权限控制
    • 数据安全边界检查
    • 软件本身代码安全标准高

三、漏洞模型分析

对于攻击者而言,大多数情况下服务端的资源为攻击目标,因此大部分的时候攻击都针对服务端发起,服务端也就在攻防对抗中逐渐加固;而客户端由于价值低,通常缺乏这类攻防事件,相对来说受到的攻击较少。那么,此时只要创建出一个场景,让服务端变为客户端,则此时服务端中存在的客户端的攻击面就会暴露出来,从而造成大量危害。

3.1 Server as Client

大多数情况下,服务端程序并不会仅仅作为提供资源的一方存在。在现代模型中,单个服务器能够完成的功能有限,在特定的情境下,服务端也会作为客户端向其他的服务端进行资源请求,例如如下的场景:

  • 访问不存在本服务器的资源
  • 远程路径访问
  • RPC调用
  • 代理访问

当服务端A去向服务端B发起请求的时候,此时的服务端A本质上是作为客户端存在的,此时的服务端身份就转换成了客户端。而客户端往往是防护较为薄弱的一环,服务端上的客户端部分也是如此。

这类攻击往往需要构造如下的场景:

  • 攻击者需要具有两台机器,一台攻击机(Hacker),以及受控的另一台伪造服务端(Fake Server);
  • 某些场景下,受控的伪造服务端要和受害主机(Victim Server)在同一个内网中。

此时,可以通过在Fake Server 上构造畸形的数据包,从而实现对受害主机的攻击。

四、实例:CVE-2021-43893

漏洞CVE-2021-43893为 Windows 上的EFSRPC服务,主要用于提供在远程服务器上进行数据的加解密。微软在修复漏洞的时候,存在疏忽,导致这个漏洞的生命周期中经历了如下两个阶段:

  • (漏洞一)最初漏洞本身通过直接窃取Net-NTML,从而实现NTML-Relay
  • (漏洞二)微软对其进行了简单的修复后,并未完全封堵,导致该漏洞可变形为未授权用户往Windows Server上传任意文件。

接下来会对两种漏洞的成因以及利用进行分析。

4.1 漏洞一成因

首先检查函数原型:

 long EfsRpcOpenFileRaw(
   [in] handle_t binding_h,
   [out] PEXIMPORT_CONTEXT_HANDLE* hContext,
   [in, string] wchar_t* FileName,
   [in] long Flags
 );

该函数的作用为一个RPC接口,用于打开位于RPC服务端的一个文件。这里的FileName根据MSDN的定义,可以支持UncPath

通常来说,目标服务端会指定一个目录,例如:

D:\ShareFiles

此时,这个目录会作为远程访问服务端的用户根目录,用户可以访问这个目录下的一些共享文件。

当用户访问远程服务端的时候,用户所在的客户端会使用本地的EFSRPC服务尝试调用服务器端的RPC接口,此时通过RPC,会调用服务端上的EfsRpcOpenFileRaw这类函数,这里的FileName为UNC路径,本意上为开发者想要兼容各类路径想到的,比如我们可以把FileName写成一些远程服务器上的路径,这样即使本机没有,我们也能要求服务端去访问远程文件:

\\10.1.0.1\file 

然而这个远程调用没有表面看上去这么简单,假设这个10.1.0.1 IP 对应的机器为我们可控的情况下,会发现EfsRpcOpenFileRaw这个API为了能够进行认证,会尝试在发起获取远程文件的请求时,将当前服务端的 Net-NTLM 发送给10.1.0.1。而由于此时的10.1.0.1 为我们假冒的,因此我们可以轻易的截获Net-NTLM。一旦获取受害机器的Net-NTLM,我们就能够使用这个值去伪装认证,最终将自己伪装成受害机器。具体来说流程如下:

这种泄露Net-NTLM的具体利用细节可以参考这里的文章,最终效果便是能够作为受害主机的权限,在内网中进行资源访问。

4.2 漏洞二成因

微软意识到这个问题后,给 EFSRPC 服务的对应API增加了身份认证,这样当我们访问远程机器的时候,首先要进行身份认证,这就限制了使用这个问题进行远程访问的问题(虽然在此期间,微软拉扯了很多次,导致漏洞修复的比较缓慢,这就是后话了)。

但是又有新的安全人员根据这个漏洞受到启发。原先在EfsRpcOpenFileRaw这类函数使用的时候,会检查路径的权限,例如显然是不允许访问服务端的C:这个目录的,但是为了支持远程路径,在遇到\开头的路径时,一律按照远程文件访问处理,这就导致其忘记检查形如\\.\C:的路径,这种形式的写法本质上还是在访问本地磁盘,从而导致这个API可以存在一个路径穿越问题。

这个漏洞的使用范围会比上一个小很多,因为这种情况下,RPC接口会保证我们的请求是在低权限下执行,但是通过一些技巧,我们也是有机会提权的,具体可直接参考文章。

4.3 CVE-2021-43893 引发的思考

通过学习这个漏洞,我们能够收获如下的启发:

  • 软件中同样存在着类似web安全中的SSRF问题,同样有可能实现越权访问
  • 服务端程序在某些特定的请求下,会转换成客户端请求
  • 客户端部分在实现的时候,安全性往往不如服务端严格

五、实例:CVE-2022-26809

上个漏洞中提及了RPC服务,于是有很多安全人员开始进一步的研究RPC协议调用过程中会发生的问题,并且发现了其中的一些问题。

5.1 RPC简介

RPC全程为远程过程调用(Remote Procedure Call),分为Server端和Client端。其调用模式如图:

有些场景我们希望某些功能在指定的服务端实现,例如上个例子中的文件共享,此时我们会希望相关的逻辑是现在服务端上,而客户端使用API直接请求对应的数据,这个时候就能够使用RPC来完成我们的功能。

RPC有点类似现在的web服务,其也分为客户端和服务端。简单来说,这两者在RPC中主要作用如下:

服务端:

  • 将被调用的函数绑定到服务中
  • 将自己注册到RPC管理器中
  • 处理来自客户端的请求

客户端:

  • 申请使用RPC调用
  • 向服务端发起请求,并且调用对应的API

5.2 漏洞介绍

这个漏洞出现在RPC的客户端逻辑中,在函数OSF_CASSOCIATION::ProcessBindAckOrNak中存在这样的逻辑:

这里的BufferLength为我们传入的Buffer长度,根据逆向分析后可以知道,RPC结构体分为两部分: Header和Body,其中根据收到的回复类型的不同,RPC会使用不同类型的Header,一般来说分为其中两种:

  • bind_ack: 接受RPC调用,包头部长度为26字节。
  • alter_context_resp: RPC调用发生变化,重新进行握手等动作,包头部长度为26字节。存在额外数据。

根据分析可知,虽然上述的Header长度一致,但是alter_context_resp会额外包含一些数据,所以在计算body位置的时候,会多减去两个字节。而上述代码中,程序只做了如下的检查

  • 数据包长度至少为26字节(代码中未展示)
  • 接受的数据包是否为bind_ack,计算body的长度

可以看到,这里并没有确保收到的数据包一定是来自于alter_context_resp这种类型。于是当我们此时申明我们数据包来自alter_context_resp,但是长度仅为26字节的时候,即可通过上述的检查,而代码中这里就存在一个可以造成整数溢出的点。

else
{
    bodyLength = BufferLength - 28; // BufferLength可以只为26
    offset = 2i64;
}

5.3 漏洞场景

这个漏洞是在收到回复包的情况下触发,所以正常的发包是无法进入对应的逻辑中的,但是我们可以通过如下的方式诱发漏洞:

同样,此漏洞也需要通过伪造服务端来实现漏洞的诱发。

六、模型总结

上述的例子中,不但出现了狭义上的客户端,即单纯的向服务端请求数据,并且对数据进行接收的模块,还出现了广义上的客户端,也就是自己发起请求后,接受对端的回应数据包。

在漏洞挖掘过程中,广义上的客户端往往会成为人们忽略的要点。就像是RPC漏洞中,用于处理ACK数据包的模块。从直觉上理解,ACK数据包一定是来自于不受控的服务端,但是在实际攻击场景中,服务端本身也是允许伪造的,此时处理ACK数据包的处理端就能视为成客户端。

基于上述思路,我们可以列举出一些常见的场景:

  • 文件共享的时候,负责接受文件的客户端
  • 代理访问,进行代理确认的代理端
  • 加密操作时,负责接受加密数据的接收端
  • 负责接受邮件的邮件客户端

在上述场景中,大多数的场景会被我们忽略,因为常理上讲,无法对目标造成损失——毕竟这些操作通常发生在我们自己的机器上,而执行操作的进程大概率也不是高权限进程。然而,通过远程路径访问,代理转发,以及对端加密,或者以服务存在的高权限邮件对邮件进行解析等操作,是具备将这些客户端漏洞转化为高价值漏洞的。在进行软件安全性分析的时候,不妨以这些面为切入点,也能找到一些有趣的漏洞。