gwillcox-r7 (556)

Last Login: August 09, 2022
Assessments
225
Score
556
1st Place

gwillcox-r7's Latest (20) Contributions

Sort by:
Filter by:
2
Ratings
Technical Analysis

Based on updated analysis from Maurizio Agazzini over at https://security.humanativaspa.it/zyxel-authentication-bypass-patch-analysis-cve-2022-0342/, it appears the impact of this vulnerability is a lot higher than initially anticipated. I think what our analysis and possibly others failed to realize is that a lot of this vulnerability is dependent on the actual port that you send the request to.

In the analysis listed above they noted that the following ports are associated with Apache:

  • 8008
  • 54088
  • 80
  • 4433
  • 443

And if we access the port 8008 we get a VPN Authorize authentication prompt, which appears to be related to 2FA. However note that unlike @jbaines-r7’s assessment, the prompt isn’t exactly the same and contains the text “VPN Authorize” at the top.

From this they then realized that since this must be related to 2FA, there is a configuration file at /var/zyxel/service_conf/httpd_twofa.conf which controls this. Looking at the configuration showed it was set up to listen on port 8008, the same port accessed earlier, and gave some more information on setup.

They they accessed port 54088 whilst exploring the other ports and noticed it contained what looked to be a block page. This is common on firewalling/website blocking apps where they will run a website on a port and redirect to that port to show the block page whenever a website that is deemed to be “bad” is attempted to be accessed by a user.

Looking at this and noticing it was a blockpage lead them to /var/zyxel/service_conf/cf_blockpage_https.conf which showed that port 54088 was being used for the cf_blockpage service,.

They then noticed both of these configuration files referenced /usr/local/apache/cgi-bin using the Directory configuration directive as described at https://httpd.apache.org/docs/2.4/mod/core.html#directory, which would have the configuration option SSLOptions +StdEnvVars which as described at https://httpd.apache.org/docs/trunk/mod/mod_ssl.xml#ssloptions, sets “the standard set of SSL related CGI/SSI environment variables are created. This per default is disabled for performance reasons, because the information extraction step is a rather expensive operation. So one usually enables this option for CGI and SSI requests only.” So this is interesting as it indicates we might be using CGI scripts on these endpoints.

At this point they then looked at /usr/local/zyxel-gui/httpd.conf and found that the /usr/local/apache/cgi-bin directory has a ScriptAlias for /cgi-bin/, meaning one can access the scripts in this directory by browsing to /cgi-bin/. Because this was configured in a global area though, this means that all the CGIs inside the /usr/local/apache/cgi-bin directory are accessible on every different virtual host the server provides.

As a final point they show that they could get around some of the authentication errors by sending a CGI request not to port 443, but instead to port 8008, and the same request worked fine, which allowed them to export the startup-config.conf file. This is just one example though as now they can essentially access any CGI endpoints on the server without authentication and are limited only by what the CGI script allows them to do.

1
Ratings
Technical Analysis

There is a nice writeup on this at https://medium.com/@frycos/searching-for-deserialization-protection-bypasses-in-microsoft-exchange-cve-2022-21969-bfa38f63a62d. The bug appears to be a deserialization bug that occurs when loading a specific file, however according to the demo video at https://gist.github.com/Frycos/a446d86d75c09330d77f37ca28923ddd it seems to be more of a local attack. That being said it would grant you an LPE to SYSTEM if you were able to trigger it. Furthermore Frycos mentions that he thinks Microsoft didn’t fix the root issue when he wrote the blog (as of January 12th 2022), so its possible the root issue wasn’t fixed, though Frycos mentioned he didn’t look into this further.

From https://twitter.com/MCKSysAr/status/1524518517990727683 it does seem like at the very least some exploitation attempts have been made to try exploit this although writing to C:\Program Files\Microsoft\Exchange Server\V15\UnifiedMessaging\voicemail to trigger the bug via making it process a voicemail has proven to be difficult to do. It does however note my tip, shown later in this writeup, of how to bypass the deny list by using System.Runtime.Remoting.ObjRef as was pointed out online, was valid.

What follows below is some of my notes that I wrote up a while back and never published. Hopefully they are of help to someone.

Overview

Background info

Deserialization vulnerability leading to RCE potentially.
Got a CVSS 3.1 score of 9.0 with a temporal score metric score of 7.8.

Interesting that it mentions the attack vector is Adjacent and the article notes that this may be only cause of the way that he exploited it and may indicate they didn’t fix the root issue.

Low attack complexity and low privileges required seems to indicate it may be authenticated but you don’t need many privileges??? I need to check on this further.

High impact on everything else suggest this is a full compromise; this would be in line with leaking the hash.

Affected

  • Microsoft Exchange Server 2019 Cumulative Update 11 prior to January 2022 security update.
  • Microsoft Exchange Server 2019 Cumulative Update 10 prior to January 2022 security update.
  • Microsoft Exchange Server 2016 Cumulative Update 22 prior to January 2022 security update.
  • Microsoft Exchange Server 2016 Cumulative Update 21 prior to January 2022 security update.
  • Microsoft Exchange Server 2013 Cumulative Update 23 prior to January 2022 security update.

Fixed By

KB5008631

Other vulns fixed in same patch

CVE-2022-21846 <– NSA reported this one.
CVE-2022-21855 <– Reported by Andrew Ruddick of MSRC.

Writeup Review

Original writeup: https://www.instapaper.com/read/1487196325

We have well known sinks in [[.NET]] whereby one can make deserialization calls from unprotected formatters such as BinaryFormatter. These formatters as noted in [[CVE-2021-42321]] don’t have any SerializationBinder or similar binders attached to them, which means that they are open to deserialize whatever they like, without any binder limiting them to what they can deserialize.

Initial search for vulnerabilities took place around Exchange’s Rpc functions, which use a binary protocol created by Microsoft for communication instead of using normal HTTP requests.

Looking around we can see Microsoft.Exchange.Rpc.ExchangeCertificates.ExchangeCertificateRpcServer contains several function prototypes:

// Microsoft.Exchange.Rpc.ExchangeCertificate.ExchangeCertificateRpcServer  
using System;  
using System.Security;  
using Microsoft.Exchange.Rpc;  
  
internal abstract class ExchangeCertificateRpcServer : RpcServerBase  
{  
    public unsafe static IntPtr RpcIntfHandle = (IntPtr)<Module>.IExchangeCertificate_v1_0_s_ifspec;  
  
    public abstract byte[] GetCertificate(int version, byte[] pInBytes);  
  
    public abstract byte[] CreateCertificate(int version, byte[] pInBytes);  
  
    public abstract byte[] RemoveCertificate(int version, byte[] pInBytes);  
  
    public abstract byte[] ExportCertificate(int version, byte[] pInBytes, SecureString password);  
  
    public abstract byte[] ImportCertificate(int version, byte[] pInBytes, SecureString password);  
  
    public abstract byte[] EnableCertificate(int version, byte[] pInBytes);  
  
    public ExchangeCertificateRpcServer()  
    {  
    }  
}

These are then implemented in Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer.

// Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer  
using System;  
using System.Security;  
using System.Security.AccessControl;  
using System.Security.Principal;  
using Microsoft.Exchange.Management.SystemConfigurationTasks;  
using Microsoft.Exchange.Rpc;  
using Microsoft.Exchange.Rpc.ExchangeCertificate;  
using Microsoft.Exchange.Servicelets.ExchangeCertificate;  
  
internal class ExchangeCertificateServer : ExchangeCertificateRpcServer  
{  
    internal const string RequestStoreName = "REQUEST";  
  
    private static ExchangeCertificateServer server;  
  
    public static bool Start(out Exception e)  
    {  
        e = null;  
        SecurityIdentifier securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);  
        FileSystemAccessRule accessRule = new FileSystemAccessRule(securityIdentifier, FileSystemRights.Read, AccessControlType.Allow);  
        FileSecurity fileSecurity = new FileSecurity();  
        fileSecurity.SetOwner(securityIdentifier);  
        fileSecurity.SetAccessRule(accessRule);  
        try  
        {  
            server = (ExchangeCertificateServer)RpcServerBase.RegisterServer(typeof(ExchangeCertificateServer), fileSecurity, 1u, isLocalOnly: false);  
            return true;  
        }  
        catch (RpcException ex)  
        {  
            RpcException ex2 = (RpcException)(e = ex);  
            return false;  
        }  
    }  
  
    public static void Stop()  
    {  
        if (server != null)  
        {  
            RpcServerBase.StopServer(ExchangeCertificateRpcServer.RpcIntfHandle);  
            server = null;  
        }  
    }  
  
    public override byte[] CreateCertificate(int version, byte[] inputBlob)  
    {  
        return ExchangeCertificateServerHelper.CreateCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);  
    }  
  
    public override byte[] GetCertificate(int version, byte[] inputBlob)  
    {  
        return ExchangeCertificateServerHelper.GetCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);  
    }  
  
    public override byte[] RemoveCertificate(int version, byte[] inputBlob)  
    {  
        return ExchangeCertificateServerHelper.RemoveCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);  
    }  
  
    public override byte[] ExportCertificate(int version, byte[] inputBlob, SecureString password)  
    {  
        return ExchangeCertificateServerHelper.ExportCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob, password);  
    }  
  
    public override byte[] ImportCertificate(int version, byte[] inputBlob, SecureString password)  
    {  
        return ExchangeCertificateServerHelper.ImportCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob, password);  
    }  
  
    public override byte[] EnableCertificate(int version, byte[] inputBlob)  
    {  
        return ExchangeCertificateServerHelper.EnableCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);  
    }  
}

Examining these functions we can see a lot of them take a byte array input named byte[] inputBlob. If we follow the ImportCertificate() function here as an example we can see that the implementation will call into Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper, as is also true for the other functions.

// Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper  
using System;  
using System.Collections.Generic;  
using System.Management.Automation;  
using System.Security;  
using System.Security.Cryptography;  
using System.Security.Cryptography.X509Certificates;  
using System.Text;  
using Microsoft.Exchange.Data;  
using Microsoft.Exchange.Data.Common;  
using Microsoft.Exchange.Data.Directory;  
using Microsoft.Exchange.Data.Directory.Management;  
using Microsoft.Exchange.Data.Directory.SystemConfiguration;  
using Microsoft.Exchange.Extensions;  
using Microsoft.Exchange.Management.FederationProvisioning;  
using Microsoft.Exchange.Management.Metabase;  
using Microsoft.Exchange.Management.SystemConfigurationTasks;  
using Microsoft.Exchange.Management.Tasks;  
using Microsoft.Exchange.Net;  
using Microsoft.Exchange.Security.Cryptography.X509Certificates;  
using Microsoft.Exchange.Servicelets.ExchangeCertificate;  
  
internal class ExchangeCertificateServerHelper  
{  
   
... 
  
    public static byte[] ImportCertificate(ExchangeCertificateRpcVersion rpcVersion, byte[] inputBlob, SecureString password)  
    {  
        bool flag = false;  
        ExchangeCertificateRpc exchangeCertificateRpc = new ExchangeCertificateRpc(rpcVersion, inputBlob, null);  
        if (string.IsNullOrEmpty(exchangeCertificateRpc.ImportCert))  
        {  
            return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);  
        }  
        Server server = null;  
        ITopologyConfigurationSession topologyConfigurationSession = DirectorySessionFactory.Default.CreateTopologyConfigurationSession(ConsistencyMode.IgnoreInvalid, ADSessionSettings.FromRootOrgScopeSet(), 1159, "ImportCertificate", "d:\\dbs\\sh\\e19dt\\1103_100001\\cmd\\c\\sources\\Dev\\Management\\src\\ServiceHost\\Servicelets\\ExchangeCertificate\\Program\\ExchangeCertificateServer.cs");  
        try  
        {  
            server = ManageExchangeCertificate.FindLocalServer(topologyConfigurationSession);  
        }  
        catch (LocalServerNotFoundException)  
        {  
            flag = true;  
        }  
        if (flag || !ManageExchangeCertificate.IsServerRoleSupported(server))  
        {  
            return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.RoleDoesNotSupportExchangeCertificateTasksException, ErrorCategory.InvalidOperation);  
        }  
        X509Store x509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);  
        try  
        {  
            x509Store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);  
        }  
        catch (CryptographicException)  
        {  
            x509Store = null;  
        }  
        List<ServiceData> installed = new List<ServiceData>();  
        GetInstalledRoles(topologyConfigurationSession, server, installed);  
        try  
        {  
            byte[] array = null;  
            if (CertificateEnroller.TryAcceptPkcs7(exchangeCertificateRpc.ImportCert, out var thumbprint, out var untrustedRoot))  
            {  
                X509Certificate2Collection x509Certificate2Collection = x509Store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false);  
                if (x509Certificate2Collection.Count > 0)  
                {  
                    if (!string.IsNullOrEmpty(exchangeCertificateRpc.ImportDescription))  
                    {  
                        x509Certificate2Collection[0].FriendlyName = exchangeCertificateRpc.ImportDescription;  
                    }  
                    ExchangeCertificate exchangeCertificate = new ExchangeCertificate(x509Certificate2Collection[0]);  
                    UpdateServices(exchangeCertificate, server, installed);  
                    exchangeCertificateRpc.ReturnCert = exchangeCertificate;  
                }  
                return exchangeCertificateRpc.SerializeOutputParameters(rpcVersion);  
            }  
            if (untrustedRoot)  
            {  
                return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateUntrustedRoot, ErrorCategory.ReadError);  
            }  
            try  
            {  
                array = Convert.FromBase64String(exchangeCertificateRpc.ImportCert);  
            }  
            catch (FormatException)  
            {  
                return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateBase64DataInvalid, ErrorCategory.ReadError);  
            }  
            X509Certificate2 x509Certificate = null;  
            try  
            {  
                X509KeyStorageFlags x509KeyStorageFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet;  
                bool flag2 = password == null || password.Length == 0;  
                X509Certificate2Collection x509Certificate2Collection2 = new X509Certificate2Collection();  
                if (exchangeCertificateRpc.ImportExportable)  
                {  
                    x509KeyStorageFlags |= X509KeyStorageFlags.Exportable;  
                }  
                x509Certificate2Collection2.Import(array, flag2 ? null : password.AsUnsecureString(), x509KeyStorageFlags);  
                x509Certificate = ManageExchangeCertificate.FindImportedCertificate(x509Certificate2Collection2);  
            }  
            catch (CryptographicException)  
            {  
                return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);  
            }  
            if (x509Certificate == null)  
            {  
                return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);  
            }  
            if (!string.IsNullOrEmpty(exchangeCertificateRpc.ImportDescription))  
            {  
                x509Certificate.FriendlyName = exchangeCertificateRpc.ImportDescription;  
            }  
            if (x509Store.Certificates.Find(X509FindType.FindByThumbprint, x509Certificate.Thumbprint, validOnly: false).Count > 0)  
            {  
                return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateAlreadyExists(x509Certificate.Thumbprint), ErrorCategory.WriteError);  
            }  
            x509Store.Add(x509Certificate);  
            ExchangeCertificate exchangeCertificate2 = new ExchangeCertificate(x509Certificate);  
            UpdateServices(exchangeCertificate2, server, installed);  
            exchangeCertificateRpc.ReturnCert = exchangeCertificate2;  
        }  
        finally  
        {  
            x509Store?.Close();  
        }  
        return exchangeCertificateRpc.SerializeOutputParameters(rpcVersion);  
    }  
  
...

We can see from this that most functions appear to be calling Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.ExchangeCertificateRpc(). This has some interesting code relevant to deserialization:

// Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc  
using System.Collections.Generic;  
using Microsoft.Exchange.Rpc.ExchangeCertificate;  
  
public ExchangeCertificateRpc(ExchangeCertificateRpcVersion version, byte[] inputBlob, byte[] outputBlob)  
{  
    inputParameters = new Dictionary<RpcParameters, object>();  
    if (inputBlob != null)  
    {  
        switch (version)  
        {  
        case ExchangeCertificateRpcVersion.Version1:  
            inputParameters = (Dictionary<RpcParameters, object>)DeserializeObject(inputBlob, customized: false);  
            break;  
        case ExchangeCertificateRpcVersion.Version2:  
            inputParameters = BuildInputParameters(inputBlob);  
            break;  
        }  
    }  
    outputParameters = new Dictionary<RpcOutput, object>();  
    if (outputBlob != null)  
    {  
        switch (version)  
        {  
        case ExchangeCertificateRpcVersion.Version1:  
            outputParameters = (Dictionary<RpcOutput, object>)DeserializeObject(outputBlob, customized: false);  
            break;  
        case ExchangeCertificateRpcVersion.Version2:  
            outputParameters = BuildOutputParameters(outputBlob);  
            break;  
        }  
    }  
}

Here we can see that the byte[] inputBlob from earlier is passed to DeserializeObject(inputBlob, customized: false) in the case that ExchangeCertificateRpcVersion parameter passed in is ExchangeCertificateRpcVersion.Version1.

Okay so already we know we have one limitation in that we need to set the version parameter here to ExchangeCertificateRpcVersion.Version1 somehow.

Keeping this in mind lets explore further and look at the Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject(inputBlob, customized:false) call implementation.

// Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc  
using System.IO;  
using Microsoft.Exchange.Data.Serialization;  
using Microsoft.Exchange.Diagnostics;  
  
private object DeserializeObject(byte[] data, bool customized)  
{  
    if (data != null)  
    {  
        using (MemoryStream serializationStream = new MemoryStream(data))  
        {  
            bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);  
            return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics).Deserialize(serializationStream);  
        }  
    }  
    return null;  
}

Interesting so we can see that we create a new MemoryStream object from our byte[] data parameter and use this to create a serialization stream of type MemoryStream. We then check using Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus to see if DeserializeLocation.ExchangeCertificateRpc requires strict mode for deserialization or not and we set the boolean strictModeStatus to this result.

Finally we create a binary formatter using ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics) and then call its Deserialize() method on the serialized MemoryStream object we created earlier using byte[] data.

Note that before the November 2021 patch, this DeserializeObject function actually looked like this:

// Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc  
using System.IO;  
using Microsoft.Exchange.Data.Serialization;  
using Microsoft.Exchange.Diagnostics;  
  
private object DeserializeObject(byte[] data, bool customized)  
{  
    if (data != null)  
    {  
        using (MemoryStream serializationStream = new MemoryStream(data))  
        {  
            BinaryFormatter binaryFormatter = new BinaryFormatter();
			if (customized)
			{
				binaryFormatter.Binder = new CustomizedSerializationBinder();
			}
			return binaryFormatter.Deserialize(memoryStream);
        }  
    }  
    return null;  
}

As we can see the earlier code here was using BinaryFormatter to deserialize the payload without using a proper SerializationBinder or really any protection at all for that matter.

Looking At DeserializeObject() Deeper

Lets look at the November 2022 edition of Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject(inputBlob, customized:false) again:

// Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc  
using System.IO;  
using Microsoft.Exchange.Data.Serialization;  
using Microsoft.Exchange.Diagnostics;  
  
private object DeserializeObject(byte[] data, bool customized)  
{  
    if (data != null)  
    {  
        using (MemoryStream serializationStream = new MemoryStream(data))  
        {  
            bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);  
            return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics).Deserialize(serializationStream);  
        }  
    }  
    return null;  
}

What we want to check here now is the ExchangeBinaryFormatterFactor.CreateBinaryFormatter call. What does the code for this look like?

// Microsoft.Exchange.Diagnostics.ExchangeBinaryFormatterFactory  
using System.Runtime.Serialization.Formatters.Binary;  
  
public static BinaryFormatter CreateBinaryFormatter(DeserializeLocation usageLocation, bool strictMode = false, string[] allowList = null, string[] allowedGenerics = null)  
{  
    return new BinaryFormatter  
    {  
        Binder = new ChainedSerializationBinder(usageLocation, strictMode, allowList, allowedGenerics)  
    };  
}

Ah our good old friend ChainedSerializationBinder and BinaryFormatter. Looks like we will need to create a BinaryFormatter serialized payload and ChainedSerializationBinder will be the validator.

As mentioned in the article to bypass this logic we need to ensure that strictMode is set to False and that we are not using any fully qualified assembly name in the deny list defined in Microsoft.Exchange.Diagnostics.ChainedSerializationBinder.GlobalDisallowedTypesForDeserialization, which will pretty much kill all publicly known .NET deserialization gadgets from ysoserial.NET.

For reference this is the code for ChainedSerializationBinder in November 2021 Update:

// Microsoft.Exchange.Diagnostics.ChainedSerializationBinder  
using System;  
using System.Collections.Generic;  
using System.IO;  
using System.Linq;  
using System.Reflection;  
using System.Runtime.Serialization;  
using Microsoft.Exchange.Diagnostics;  
  
public class ChainedSerializationBinder : SerializationBinder  
{  
    private const string TypeFormat = "{0}, {1}";  
  
    private static readonly HashSet<string> AlwaysAllowedPrimitives = new HashSet<string>  
    {  
        typeof(string).FullName,  
        typeof(int).FullName,  
        typeof(uint).FullName,  
        typeof(long).FullName,  
        typeof(ulong).FullName,  
        typeof(double).FullName,  
        typeof(float).FullName,  
        typeof(bool).FullName,  
        typeof(short).FullName,  
        typeof(ushort).FullName,  
        typeof(byte).FullName,  
        typeof(char).FullName,  
        typeof(DateTime).FullName,  
        typeof(TimeSpan).FullName,  
        typeof(Guid).FullName  
    };  
  
    private bool strictMode;  
  
    private DeserializeLocation location;  
  
    private Func<string, Type> typeResolver;  
  
    private HashSet<string> allowedTypesForDeserialization;  
  
    private HashSet<string> allowedGenericsForDeserialization;  
  
    private bool serializationOnly;  
  
    protected static HashSet<string> GlobalDisallowedTypesForDeserialization { get; private set; } = BuildDisallowedTypesForDeserialization();  
  
  
    protected static HashSet<string> GlobalDisallowedGenericsForDeserialization { get; private set; } = BuildGlobalDisallowedGenericsForDeserialization();  
  
  
    public ChainedSerializationBinder()  
    {  
        serializationOnly = true;  
    }  
  
    public ChainedSerializationBinder(DeserializeLocation usageLocation, bool strictMode = false, string[] allowList = null, string[] allowedGenerics = null)  
    {  
        this.strictMode = strictMode;  
        allowedTypesForDeserialization = ((allowList != null && allowList.Length != 0) ? new HashSet<string>(allowList) : null);  
        allowedGenericsForDeserialization = ((allowedGenerics != null && allowedGenerics.Length != 0) ? new HashSet<string>(allowedGenerics) : null);  
        typeResolver = typeResolver ?? ((Func<string, Type>)((string s) => Type.GetType(s)));  
        location = usageLocation;  
    }  
  
    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)  
    {  
        string text = null;  
        string text2 = null;  
        InternalBindToName(serializedType, out assemblyName, out typeName);  
        if (assemblyName == null && typeName == null)  
        {  
            assemblyName = text;  
            typeName = text2;  
        }  
    }  
  
    public override Type BindToType(string assemblyName, string typeName)  
    {  
        if (serializationOnly)  
        {  
            throw new InvalidOperationException("ChainedSerializationBinder was created for serialization only.  This instance cannot be used for deserialization.");  
        }  
        Type type = InternalBindToType(assemblyName, typeName);  
        if (type != null)  
        {  
            ValidateTypeToDeserialize(type);  
        }  
        return type;  
    }  
  
    protected virtual Type InternalBindToType(string assemblyName, string typeName)  
    {  
        return LoadType(assemblyName, typeName);  
    }  
  
    protected Type LoadType(string assemblyName, string typeName)  
    {  
        Type type = null;  
        try  
        {  
            type = Type.GetType($"{typeName}, {assemblyName}");  
        }  
        catch (TypeLoadException)  
        {  
        }  
        catch (FileLoadException)  
        {  
        }  
        if (type == null)  
        {  
            string shortName = assemblyName.Split(',')[0];  
            try  
            {  
                type = Type.GetType($"{typeName}, {shortName}");  
            }  
            catch (TypeLoadException)  
            {  
            }  
            catch (FileLoadException)  
            {  
            }  
            if (type == null)  
            {  
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();  
                IEnumerable<Assembly> source = assemblies.Where((Assembly x) => shortName == x.FullName.Split(',')[0]);  
                Assembly assembly = (source.Any() ? source.First() : null);  
                if (assembly != null)  
                {  
                    type = assembly.GetType(typeName);  
                }  
                else  
                {  
                    Assembly[] array = assemblies;  
                    foreach (Assembly assembly2 in array)  
                    {  
                        try  
                        {  
                            type = assembly2.GetType(typeName);  
                            if (!(type != null))  
                            {  
                                continue;  
                            }  
                            return type;  
                        }  
                        catch  
                        {  
                        }  
                    }  
                }  
            }  
        }  
        return type;  
    }  
  
    protected virtual void InternalBindToName(Type serializedType, out string assemblyName, out string typeName)  
    {  
        assemblyName = null;  
        typeName = null;  
    }  
  
    protected void ValidateTypeToDeserialize(Type typeToDeserialize)  
    {  
        if (typeToDeserialize == null)  
        {  
            return;  
        }  
        string fullName = typeToDeserialize.FullName;  
        bool flag = strictMode;  
        try  
        {  
            if (!strictMode && (allowedTypesForDeserialization == null || !allowedTypesForDeserialization.Contains(fullName)) && GlobalDisallowedTypesForDeserialization.Contains(fullName))  
            {  
                flag = true;  
                throw new InvalidOperationException($"Type {fullName} failed deserialization (BlockList).");  
            }  
            if (typeToDeserialize.IsConstructedGenericType)  
            {  
                fullName = typeToDeserialize.GetGenericTypeDefinition().FullName;  
                if (allowedGenericsForDeserialization == null || !allowedGenericsForDeserialization.Contains(fullName) || GlobalDisallowedGenericsForDeserialization.Contains(fullName))  
                {  
                    throw new BlockedDeserializeTypeException(fullName, BlockedDeserializeTypeException.BlockReason.NotInAllow, location);  
                }  
            }  
            else if (!AlwaysAllowedPrimitives.Contains(fullName) && (allowedTypesForDeserialization == null || !allowedTypesForDeserialization.Contains(fullName) || GlobalDisallowedTypesForDeserialization.Contains(fullName)) && !typeToDeserialize.IsArray && !typeToDeserialize.IsEnum && !typeToDeserialize.IsAbstract && !typeToDeserialize.IsInterface)  
            {  
                throw new BlockedDeserializeTypeException(fullName, BlockedDeserializeTypeException.BlockReason.NotInAllow, location);  
            }  
        }  
        catch (BlockedDeserializeTypeException ex)  
        {  
            DeserializationTypeLogger.Singleton.Log(ex.TypeName, ex.Reason, location, (flag || strictMode) ? DeserializationTypeLogger.BlockStatus.TrulyBlocked : DeserializationTypeLogger.BlockStatus.WouldBeBlocked);  
            if (flag)  
            {  
                throw;  
            }  
        }  
    }  
  
    private static HashSet<string> BuildDisallowedGenerics()  
    {  
        return new HashSet<string> { typeof(SortedSet<>).GetGenericTypeDefinition().FullName };  
    }  
  
    private static HashSet<string> BuildDisallowedTypesForDeserialization()  
    {  
        return new HashSet<string>  
        {  
            "Microsoft.Data.Schema.SchemaModel.ModelStore", "Microsoft.FailoverClusters.NotificationViewer.ConfigStore", "Microsoft.IdentityModel.Claims.WindowsClaimsIdentity", "Microsoft.Management.UI.Internal.FilterRuleExtensions", "Microsoft.Management.UI.FilterRuleExtensions", "Microsoft.Reporting.RdlCompile.ReadStateFile", "Microsoft.TeamFoundation.VersionControl.Client.PolicyEnvelope", "Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource", "Microsoft.VisualStudio.Editors.PropPageDesigner.PropertyPageSerializationService+PropertyPageSerializationStore", "Microsoft.VisualStudio.EnterpriseTools.Shell.ModelingPackage",  
            "Microsoft.VisualStudio.Modeling.Diagnostics.XmlSerialization", "Microsoft.VisualStudio.Publish.BaseProvider.Util", "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", "Microsoft.VisualStudio.Web.WebForms.ControlDesignerStateCache", "Microsoft.Web.Design.Remote.ProxyObject", "System.Activities.Presentation.WorkflowDesigner", "System.AddIn.Hosting.AddInStore", "System.AddIn.Hosting.Utils", "System.CodeDom.Compiler.TempFileCollection", "System.Collections.Hashtable",  
            "System.ComponentModel.Design.DesigntimeLicenseContextSerializer", "System.Configuration.Install.AssemblyInstaller", "System.Configuration.SettingsPropertyValue", "System.Data.DataSet", "System.Data.DataViewManager", "System.Data.Design.MethodSignatureGenerator", "System.Data.Design.TypedDataSetGenerator", "System.Data.Design.TypedDataSetSchemaImporterExtension", "System.Data.SerializationFormat", "System.DelegateSerializationHolder",  
            "System.Drawing.Design.ToolboxItemContainer", "System.Drawing.Design.ToolboxItemContainer+ToolboxItemSerializer", "System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler", "System.IdentityModel.Tokens.SessionSecurityToken", "System.IdentityModel.Tokens.SessionSecurityTokenHandler", "System.IO.FileSystemInfo", "System.Management.Automation.PSObject", "System.Management.IWbemClassObjectFreeThreaded", "System.Messaging.BinaryMessageFormatter", "System.Resources.ResourceReader",  
            "System.Resources.ResXResourceSet", "System.Runtime.Remoting.Channels.BinaryClientFormatterSink", "System.Runtime.Remoting.Channels.BinaryClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.BinaryServerFormatterSink", "System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider", "System.Runtime.Remoting.Channels.CrossAppDomainSerializer", "System.Runtime.Remoting.Channels.SoapClientFormatterSink", "System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.SoapServerFormatterSink", "System.Runtime.Remoting.Channels.SoapServerFormatterSinkProvider",  
            "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", "System.Runtime.Serialization.Formatters.Soap.SoapFormatter", "System.Runtime.Serialization.NetDataContractSerializer", "System.Security.Claims.ClaimsIdentity", "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", "System.Security.Principal.WindowsPrincipal", "System.Security.SecurityException", "System.Web.Security.RolePrincipal", "System.Web.Script.Serialization.JavaScriptSerializer",  
            "System.Web.Script.Serialization.SimpleTypeResolver", "System.Web.UI.LosFormatter", "System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem", "System.Web.UI.ObjectStateFormatter", "System.Windows.Data.ObjectDataProvider", "System.Windows.Forms.AxHost+State", "System.Windows.ResourceDictionary", "System.Workflow.ComponentModel.Activity", "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", "System.Xml.XmlDataDocument",  
            "System.Xml.XmlDocument"  
        };  
    }  
  
    private static HashSet<string> BuildGlobalDisallowedGenericsForDeserialization()  
    {  
        return new HashSet<string>();  
    }  
}

Interesting to note that this doesn’t seem to contain the entries for System.Runtime.Remoting.ObjectRef which was a new gadget chain just added with https://github.com/pwntester/ysoserial.net/pull/115 that relies on a rouge .NET remoting server like https://github.com/codewhitesec/RogueRemotingServer. There is a writeup on this at https://codewhitesec.blogspot.com/2022/01/dotnet-remoting-revisited.html that explains more but this would allow RCE via a serialized payload attached to the rouge .NET remoting server.

Anyway so from earlier we know that the strict mode is determined via the line bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc); so this provides our other bypass.

Lets check if the result of this is False or not:

So from here we can likely supply a System.Runtime.Remoting.ObjectRef, take advantage of the lack of strict checking on this, and get the whole exploit to work. The problem now is finding the whole chain to reach this vulnerable call and then trigger the deserialization.

January 2022 Patch Analysis

  • No adjustments to the ChainedSerializationBinder deny list at all.

Here is the Jan 2022 version of the deny list:

    private static HashSet<string> BuildDisallowedTypesForDeserialization()  
    {  
        return new HashSet<string>  
        {  
            "Microsoft.Data.Schema.SchemaModel.ModelStore", "Microsoft.FailoverClusters.NotificationViewer.ConfigStore", "Microsoft.IdentityModel.Claims.WindowsClaimsIdentity", "Microsoft.Management.UI.Internal.FilterRuleExtensions", "Microsoft.Management.UI.FilterRuleExtensions", "Microsoft.Reporting.RdlCompile.ReadStateFile", "Microsoft.TeamFoundation.VersionControl.Client.PolicyEnvelope", "Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource", "Microsoft.VisualStudio.Editors.PropPageDesigner.PropertyPageSerializationService+PropertyPageSerializationStore", "Microsoft.VisualStudio.EnterpriseTools.Shell.ModelingPackage",  
            "Microsoft.VisualStudio.Modeling.Diagnostics.XmlSerialization", "Microsoft.VisualStudio.Publish.BaseProvider.Util", "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", "Microsoft.VisualStudio.Web.WebForms.ControlDesignerStateCache", "Microsoft.Web.Design.Remote.ProxyObject", "System.Activities.Presentation.WorkflowDesigner", "System.AddIn.Hosting.AddInStore", "System.AddIn.Hosting.Utils", "System.CodeDom.Compiler.TempFileCollection", "System.Collections.Hashtable",  
            "System.ComponentModel.Design.DesigntimeLicenseContextSerializer", "System.Configuration.Install.AssemblyInstaller", "System.Configuration.SettingsPropertyValue", "System.Data.DataSet", "System.Data.DataViewManager", "System.Data.Design.MethodSignatureGenerator", "System.Data.Design.TypedDataSetGenerator", "System.Data.Design.TypedDataSetSchemaImporterExtension", "System.Data.SerializationFormat", "System.DelegateSerializationHolder",  
            "System.Drawing.Design.ToolboxItemContainer", "System.Drawing.Design.ToolboxItemContainer+ToolboxItemSerializer", "System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler", "System.IdentityModel.Tokens.SessionSecurityToken", "System.IdentityModel.Tokens.SessionSecurityTokenHandler", "System.IO.FileSystemInfo", "System.Management.Automation.PSObject", "System.Management.IWbemClassObjectFreeThreaded", "System.Messaging.BinaryMessageFormatter", "System.Resources.ResourceReader",  
            "System.Resources.ResXResourceSet", "System.Runtime.Remoting.Channels.BinaryClientFormatterSink", "System.Runtime.Remoting.Channels.BinaryClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.BinaryServerFormatterSink", "System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider", "System.Runtime.Remoting.Channels.CrossAppDomainSerializer", "System.Runtime.Remoting.Channels.SoapClientFormatterSink", "System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.SoapServerFormatterSink", "System.Runtime.Remoting.Channels.SoapServerFormatterSinkProvider",  
            "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", "System.Runtime.Serialization.Formatters.Soap.SoapFormatter", "System.Runtime.Serialization.NetDataContractSerializer", "System.Security.Claims.ClaimsIdentity", "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", "System.Security.Principal.WindowsPrincipal", "System.Security.SecurityException", "System.Web.Security.RolePrincipal", "System.Web.Script.Serialization.JavaScriptSerializer",  
            "System.Web.Script.Serialization.SimpleTypeResolver", "System.Web.UI.LosFormatter", "System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem", "System.Web.UI.ObjectStateFormatter", "System.Windows.Data.ObjectDataProvider", "System.Windows.Forms.AxHost+State", "System.Windows.ResourceDictionary", "System.Workflow.ComponentModel.Activity", "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", "System.Xml.XmlDataDocument",  
            "System.Xml.XmlDocument"  
        };  
    }

Looking at this in [[Meld]] shows that the deny list for ChainedSerializationBinder did not change between November 2021 and January 2022. So we could use System.Runtime.Remoting.ObjRef to bypass this deny list, potentially also allowing RCE on the latest version.

  • Removed Microsoft.Exchange.DxStore.Common.DxBinarySerializationUtil which seemed to have some options for doing unsafe deserialization.
using System;
using System.IO;
using FUSE.Weld.Base;
using Microsoft.Exchange.Diagnostics;
using Microsoft.Exchange.DxStore.Server;

namespace Microsoft.Exchange.DxStore.Common;

public static class DxBinarySerializationUtil
{
	private static readonly string[] allowedTypes = new string[101]
	{
		typeof(ExceptionUri).FullName,
		typeof(Ranges).FullName,
		typeof(Range).FullName,
		typeof(Target).FullName,
		typeof(CommonSettings).FullName,
		typeof(DataStoreStats).FullName,
		typeof(DxStoreAccessClientException).FullName,
		typeof(DxStoreAccessClientTransientException).FullName,
		typeof(DxStoreAccessReply).FullName,
		typeof(DxStoreAccessReply.CheckKey).FullName,
		typeof(DxStoreAccessReply.DeleteKey).FullName,
		typeof(DxStoreAccessReply.DeleteProperty).FullName,
		typeof(DxStoreAccessReply.ExecuteBatch).FullName,
		typeof(DxStoreAccessReply.GetAllProperties).FullName,
		typeof(DxStoreAccessReply.GetProperty).FullName,
		typeof(DxStoreAccessReply.GetPropertyNames).FullName,
		typeof(DxStoreAccessReply.GetSubkeyNames).FullName,
		typeof(DxStoreAccessReply.SetProperty).FullName,
		typeof(DxStoreAccessRequest).FullName,
		typeof(DxStoreAccessRequest.CheckKey).FullName,
		typeof(DxStoreAccessRequest.DeleteKey).FullName,
		typeof(DxStoreAccessRequest.DeleteProperty).FullName,
		typeof(DxStoreAccessRequest.ExecuteBatch).FullName,
		typeof(DxStoreAccessRequest.GetAllProperties).FullName,
		typeof(DxStoreAccessRequest.GetProperty).FullName,
		typeof(DxStoreAccessRequest.GetPropertyNames).FullName,
		typeof(DxStoreAccessRequest.GetSubkeyNames).FullName,
		typeof(DxStoreAccessRequest.SetProperty).FullName,
		typeof(DxStoreAccessServerTransientException).FullName,
		typeof(DxStoreBatchCommand).FullName,
		typeof(DxStoreBatchCommand.CreateKey).FullName,
		typeof(DxStoreBatchCommand.DeleteKey).FullName,
		typeof(DxStoreBatchCommand.DeleteProperty).FullName,
		typeof(DxStoreBatchCommand.SetProperty).FullName,
		typeof(DxStoreBindingNotSupportedException).FullName,
		typeof(DxStoreClientException).FullName,
		typeof(DxStoreClientTransientException).FullName,
		typeof(DxStoreCommand).FullName,
		typeof(DxStoreCommand.ApplySnapshot).FullName,
		typeof(DxStoreCommand.CreateKey).FullName,
		typeof(DxStoreCommand.DeleteKey).FullName,
		typeof(DxStoreCommand.DeleteProperty).FullName,
		typeof(DxStoreCommand.DummyCommand).FullName,
		typeof(DxStoreCommand.ExecuteBatch).FullName,
		typeof(DxStoreCommand.PromoteToLeader).FullName,
		typeof(DxStoreCommand.SetProperty).FullName,
		typeof(DxStoreCommand.UpdateMembership).FullName,
		typeof(DxStoreCommand.VerifyStoreIntegrity).FullName,
		typeof(DxStoreCommand.VerifyStoreIntegrity2).FullName,
		typeof(DxStoreCommandConstraintFailedException).FullName,
		typeof(DxStoreInstanceClientException).FullName,
		typeof(DxStoreInstanceClientTransientException).FullName,
		typeof(DxStoreInstanceComponentNotInitializedException).FullName,
		typeof(DxStoreInstanceKeyNotFoundException).FullName,
		typeof(DxStoreInstanceNotReadyException).FullName,
		typeof(DxStoreInstanceServerException).FullName,
		typeof(DxStoreInstanceServerTransientException).FullName,
		typeof(DxStoreInstanceStaleStoreException).FullName,
		typeof(DxStoreManagerClientException).FullName,
		typeof(DxStoreManagerClientTransientException).FullName,
		typeof(DxStoreManagerGroupNotFoundException).FullName,
		typeof(DxStoreManagerServerException).FullName,
		typeof(DxStoreManagerServerTransientException).FullName,
		typeof(DxStoreReplyBase).FullName,
		typeof(DxStoreRequestBase).FullName,
		typeof(DxStoreSerializeException).FullName,
		typeof(DxStoreServerException).FullName,
		typeof(DxStoreServerFault).FullName,
		typeof(DxStoreServerTransientException).FullName,
		typeof(HttpReply).FullName,
		typeof(HttpReply.DxStoreReply).FullName,
		typeof(HttpReply.ExceptionReply).FullName,
		typeof(HttpReply.GetInstanceStatusReply).FullName,
		typeof(HttpRequest).FullName,
		typeof(HttpRequest.DxStoreRequest).FullName,
		typeof(HttpRequest.GetStatusRequest).FullName,
		typeof(HttpRequest.GetStatusRequest.Reply).FullName,
		typeof(HttpRequest.PaxosMessage).FullName,
		typeof(InstanceGroupConfig).FullName,
		typeof(InstanceGroupMemberConfig).FullName,
		typeof(InstanceGroupSettings).FullName,
		typeof(InstanceManagerConfig).FullName,
		typeof(InstanceSnapshotInfo).FullName,
		typeof(InstanceStatusInfo).FullName,
		typeof(LocDescriptionAttribute).FullName,
		typeof(PaxosBasicInfo).FullName,
		typeof(PaxosBasicInfo.GossipDictionary).FullName,
		typeof(ProcessBasicInfo).FullName,
		typeof(PropertyNameInfo).FullName,
		typeof(PropertyValue).FullName,
		typeof(ReadOptions).FullName,
		typeof(ReadResult).FullName,
		typeof(WcfTimeout).FullName,
		typeof(WriteOptions).FullName,
		typeof(WriteResult).FullName,
		typeof(WriteResult.ResponseInfo).FullName,
		typeof(GroupStatusInfo).FullName,
		typeof(GroupStatusInfo.NodeInstancePair).FullName,
		typeof(InstanceMigrationInfo).FullName,
		typeof(KeyContainer).FullName,
		typeof(DateTimeOffset).FullName
	};

	private static readonly string[] allowedGenerics = new string[6] { "System.Collections.Generic.ObjectEqualityComparer`1", "System.Collections.Generic.EnumEqualityComparer`1", "System.Collections.Generic.EqualityComparer`1", "System.Collections.Generic.GenericEqualityComparer`1", "System.Collections.Generic.KeyValuePair`2", "System.Collections.Generic.List`1" };

	public static void Serialize(MemoryStream ms, object obj)
	{
		ExchangeBinaryFormatterFactory.CreateSerializeOnlyFormatter().Serialize(ms, obj);
	}

	public static object DeserializeUnsafe(Stream s)
	{
		return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.HttpBinarySerialize).Deserialize(s);
	}

	public static object Deserialize(Stream s)
	{
		return DeserializeSafe(s);
	}

	public static object DeserializeSafe(Stream s)
	{
		return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.SwordFish_AirSync, strictMode: false, allowedTypes, allowedGenerics).Deserialize(s);
	}
}
  • Added in Microsoft.Exchange.DxStore.Common.IDxStoreDynamicConfig.cs which has the following code:
namespace Microsoft.Exchange.DxStore.Common;

public interface IDxStoreDynamicConfig
{
	bool IsRemovePublicKeyToken { get; }

	bool IsSerializerIncompatibleInitRemoved { get; }

	bool EnableResolverTypeCheck { get; }

	bool EnableResolverTypeCheckException { get; }
}

Exploit Chain

Lets start at the deserialization chain and work backwards.

Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject
Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.ExchangeCertificateRpc(ExchangeCertificateRpcVersion version, byte[] inputBlob, byte[] outputBlob)
Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper.GetCertificate(int version, byte[] inputBlob)
Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer.GetCertificate(int version, byte[] inputBlob)

We can then use the Get-ExchangeCertificate commandlet from https://docs.microsoft.com/en-us/powershell/module/exchange/get-exchangecertificate?view=exchange-ps and set a breakpoint inside Microsoft.Exchange.ExchangeCertificateServicelet.dll specifically within the Microsoft.Exchange.Servicelets.ExchangeCertificate.GetCertificate handler.

Unfortunately it seems like the current way things work we are sending a ExchangeCertificateRpcVersion rpcVersion with a version of Version2.

Exploited process is Microsoft.Exchange.ServiceHost.exe which runs as NT AUTHORITY\SYSTEM.

3
Ratings
Technical Analysis

Looks like this was a heap buffer overflow in WebRTC which could allow for a drive by attack that would grant attackers RCE on a target system. No news as to whether or not this was used with a sandbox escape though, It was reported by Jan Vojtesek from the Avast Threat Intelligence team on 2022-07-01 according to https://chromereleases.googleblog.com/2022/07/stable-channel-update-for-desktop.html, yet interestingly https://chromereleases.googleblog.com/2022/07/chrome-for-android-update.html also note it affects Chrome for Android.

There is a real world exploit for this out in the wild but given the generally tight lipped news around this and that it was found from a threat intelligence team, I would imagine this may have been used in more targeted attacks, but still widely enough that a threat intelligence team picked up on it. Bit hard to tell though since I hadn’t heard about the Avast Threat Intelligence team prior to this; I imagine its possible one of their customers was targeted selectively and then they found out and notified Google.

With heap overflow bugs I generally err on the side of “well these things are harder to exploit” however with browsers you typically have access to a much wider arsenal to use for crafting the heap into a state that is desirable for exploitation purposes, so the risk is a bit higher here. That being said exploitation of such bugs tends to be a little more complex in most cases, particularly given recent mitigations. I’d still recommend patching this one if you can, but if not then you should try to disable WebRTC on your browsers until you can patch given in the wild exploitation.

1
Ratings
Technical Analysis

This is a bypass for CVE-2022-21919 which is in turn a bypass for CVE-2021-34484. As noted at https://twitter.com/billdemirkapi/status/1508527492285575172, CVE-2022-21919 was already being exploited in the wild by using the binary from https://github.com/klinix5/ProfSvcLPE/blob/main/DoubleJunctionEoP/Release/UserProfileSvcEoP.exe.

The vulnerability, near as I can tell, occurs due to the CreateDirectoryJunction() function inside profext.dll not appropriately validating things before creating a directory junction between two directories. This can allow an attacker to create a directory junction between a directory they have access to and another directory that they should not have access to, thereby granting them the ability to plant files in sensitive locations and or read sensitive files.

The exploit code for this, which was originally at https://github.com/klinix5/SuperProfile but which got taken down, is now available at https://github.com/rmusser01/SuperProfile and its associated forks. I have taken this code and updated it and touched it up a bit into a Metasploit exploit module that is now available at https://github.com/rapid7/metasploit-framework/pull/16382.

This exploit code utilizes this vulnerability to plant a malicious comctl32.dll binary in a location that the Narrator.exe program will try to load the DLL from when it starts. By utilizing the ShellExecute command with the runas option, we can force a UAC prompt to come up that will run the consent.exe program to run. If the PromptOnSecureDesktop setting is set to 1 which is the default, this will result in consent.exe running as SYSTEM on the secure desktop, and a new narrator.exe instance will also spawn as SYSTEM on the secure desktop, which will then load the malicious comctl32.dll DLL and allow us to execute our code as SYSTEM.

Note that if PromptOnSecureDesktop is set to 0 under the key HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System, then this LPE will not be possible as the UAC prompt will spawn as the current user vs as SYSTEM on the restricted desktop, and therefore we will not achieve privilege elevation, so this is a workaround for the vulnerability whilst it is not patched.

It should be noted that as this stands the current exploit requires valid credentials for another user on the system who is a non-admin user and who has permissions to log into the target computer. They must also have a profile under C:\Users for the exploit to function in its current state. There has been some rumors that it might be possible to do this without a secondary login, however nothing concrete has been found so far, so we are considering this a prerequisite for exploitation for the time being.

We, aka Rapid7, have reported this vulnerability to Microsoft and have given KLINIX5, who originally found this vulnerability and wrote the original exploit code, full credit for the discovery, however Microsoft have only given us this CVE number and have not provided a timeline on when they expect a fix for this vulnerability at this time. It is therefore recommended to use the mitigation above until an appropriate fix is developed.

1
Technical Analysis

Appears there may have been some confusion here. As noted at https://twitter.com/wdormann/status/1508555477491269638 and at https://twitter.com/BillDemirkapi/status/1508527487655067660/photo/1, the attackers tried to download UserProfileSvcEoP.exe from https://github.com/klinix5/ProfSvcLPE/blob/main/DoubleJunctionEoP/Release/UserProfileSvcEoP.exe. If you look at https://github.com/klinix5/ProfSvcLPE/blob/main/write-up.docx you can see this is actually a patch bypass for CVE-2021-34484, and was later fixed by CVE-2022-21919.

Ironically enough this later got another patch bypass in the form of CVE-2022-26904 which at the time of writing is still unpatched.

All of these vulnerabilities exploit a logic flaw whereby the User Profile Service had a CreateDirectoryJunction() function that did not appropriately validate its input to ensure it wasn’t using symbolic links along any point of the path prior to creating a directory junction between two directories. This could be abused by attackers manipulating paths along the file path to gain code execution as the SYSTEM user by planting a DLL in a sensitive location which would then be loaded by a privileged process.

1
Technical Analysis

CVE-2020-11899 (one of the Ripple20 bugs) has now been reported as exploited in the wild as per https://www.cisa.gov/known-exploited-vulnerabilities-catalog, No evidence that other bugs have been exploited though as of the time of writing.

1
Ratings
Technical Analysis

Seems after this analysis the risk was updated around July 2019 to be a high severity vulnerability. Technically this is a heap overflow with the potential side effect of SSL VPN web service termination for logged in users, however the bug may also result in remote code execution. @wwoolwine-r7’s assessment in my opinion fails to appropriately take this into account as it considers the side effect the main impact of this bug, rather than the fact that this can and has been exploited in the wild for remote code execution.

A full technical writeup of this bug can be found at https://blog.orange.tw/2019/08/attacking-ssl-vpn-part-2-breaking-the-fortigate-ssl-vpn.html where it is noted that this is a post auth vulnerability that occurs due to a memcpy into a heap buffer using the code memcpy(buffer, js_buf, js_buf_len);. It also notes that buffer is a fixed 0x2000 byte long buffer however the length of js_buf is not limited and can be as long as the attacker wants it to be. They also note that to trigger this bug, an attacker would need to host their own HTTP server. They would then use the SSL VPN web-mode, which allows users to connect to various resources such as HTTP, FTP, RDP, etc via their web browser and will result in the SSL VPN server to requesting resources on their behalf, to connect to the malicious HTTP server and fetch their exploit on their behalf, which will result in the heap overflow.

Exploitation of this vulnerability can be prevented by ensuring all users have secure passwords with a mix of alphanumberic, uppercase, lowercase, and symbols of at least 20 characters or more. Remember that in general whilst having a mix of characters is good, length generally tends to help more than the mix of characters, however it is still highly recommended to use a mix of characters wherever possible.

Exploitation can also be prevented by disabling SSL VPN web-mode, and using SSL VPN tunnel-mode instead as it is not impacted.

1
Ratings
Technical Analysis

Looks like this is a LPE in win32k that is being exploited in the wild according to Microsoft to let attackers escalate their privileges to SYSTEM. Attack complexity on this is high which is understandable given the history of win32k and the complexities regarding its architecture which was built before modern security mitigations were implemented. With that being said though the finder of this bug, at https://twitter.com/b2ahex/status/1481233350840893442, notes that exploitation is easy and that this is a patch bypass for CVE-2021-1732, which was a window object type confusion leading to an OOB (out-of-bounds) write as noted by McAfee’s technical writeup at https://www.mcafee.com/blogs/enterprise/mcafee-enterprise-atr/technical-analysis-of-cve-2021-1732/.

Of particular note here is that they credit Big CJTeam of Tianfu Cup and RyeLv aka @b2ahex on Twitter for finding this vulnerability. They note that this was exploited in the wild but the mention of Tianfu Cup is interesting as it suggests this was also reported to China’s government via the Chinese Tianfu Cup hacking competition.

1
Ratings
Technical Analysis

Looks like this is your fairly typical maliciously crafted document exploit for Microsoft Office. These bugs are used all the time by APTs and other groups simply cause its relatively easy to convince people to open documents given the right context, and even though some people will be fairly vigilant, all it takes is compromising one user to get an initial foothold into a target network.

This bug appears to affect all Microsoft Office versions since 2013 up to and including the latest Microsoft Office online solutions and also including Microsoft Sharepoint Servers from 2013 onwards, meaning that it has quite a wide range of potential targets. User interaction is required though in the form of opening a malicious document,

Given the supposedly low complexity of exploiting this vulnerability combined with the wide range of target that it can exploit, I’d expect to see exploits for this vulnerability in the wild over the coming few months.

2
Ratings
Technical Analysis

Update: There appears to be some initial patch analysis on this vulnerability at https://piffd0s.medium.com/patch-diffing-cve-2022-21907-b739f4108eee which seems to suggest the patched functions are UlFastSendHttpResponse, UlpAllocateFastTracker UlpFastSendCompleteWorker, UlpFreeFastTracker, and UlAllocateFastTrackerToLookaside. They also note that based on their analysis a safe assumption may be that the vulnerable code path is hit first in UlFastSendHttpResponse and some of the fixup / mitigations were applied to memory chunks in the other functions. Analysis is still ongoing though.

There has been a lot of confusion r.e this vulnerability, which is a RCE in the HTTP Trailer Support feature of the http.sys component which is responsible for the HTTP Protocol stack used by several high privileged Windows components. The best writeup I was able to find was at https://isc.sans.edu/diary/28234 however note that investigation is still ongoing and its likely that things will change over time.

First off, to be clear, despite http.sys appearing to be associated with IIS, this is not in itself an IIS vulnerability. As noted at https://isc.sans.edu/diary/28234, you can find which components are using http.sys by running the command netsh http show servicestate. You’ll likely find more components using it then you thought, for example Intel components use this for some odd built in HTTP server (yeah I’m not sure either but there you go).

Secondly, whilst the vulnerability affects Windows 10 1809 and Windows Server 2019 and later, by default, and only on Windows 10 1809 and Windows Serve r 2019, HKLM:\System\CurrentControlSet\Services\HTTP\Parameter\EnableTrailerSupport is set to 0 by default, thus disabling the vulnerable trailers feature. This means these versions are not vulnerable out of the box, however if the HKLM:\System\CurrentControlSet\Services\HTTP\Parameter\EnableTrailerSupport registry key is set to 1 then they are. All other affected versions of Windows are vulnerable using their default settings.

As this is a kernel level vulnerability and it being exploited remotely I imagine now would be a good time to remind people that RCE bugs in the Windows kernel have become increasingly hard to exploit. Whilst Windows 7 was easier to exploit due to lack of a number of mitigations, with Windows 10 and Windows 11, several mitigations have been implemented into the Windows kernel specifically to prevent RCE kernel exploits and from my experience they work very well to this effect (local privilege escalation attacks are another story which still needs improvement though).

Finally as for those wondering what Trailer support is anyway (like myself), https://isc.sans.edu/diary/28234 notes that RFC7230 specifies the protocol for trailer support, noting that it only makes sense if Transfer-Encoding: chunked is used in a request to a server. This allows a requestor to essentially chunk the request up into several smaller packets and then only send the headers for the request after the request body has been sent. The original idea behind this was that the request body may be generated over time and we want to start sending data as it becomes available to speed things up and ensure quicker operations.

Hopefully that helps, still not a lot of detail on this right now and there will likely need to be some patch diffing going on before people are able to better determine the root cause of this issue, but for now I’d say patch if you can whilst also keeping in mind a working exploit will likely take time to develop if its possible given Microsoft’s kernel level mitigations for Windows 10.

1
Ratings
Technical Analysis

Of all the bugs in January 2022’s Patch Tuesday analysis, I think this one has to be hands down the most complex one to exploit. There is very little information from the advisory on what this bug is however here is what I can piece together from the limited bits of information that are available.

Supposively this affects virtual machines, although its a little confusing cause if we look at the list of affected products we can see this goes all the way back to Windows 7, Hyper-V was released in 2008 with Windows Server 2008, which means that Windows 7 SP1 would also make sense since Windows 7 was released on October 22, 2019. This makes me believe that this is likely related to HyperV, but I cannot confirm this 100% at this time.

The second interesting thing is that the vulnerability is in IDE. Looking this up we can find https://www.techtarget.com/searchstorage/definition/IDE where its noted that IDE stands for Integrated Drive Electronics and is a standard that defines the connection between a bus line on a motherboard and the computer’s disk storage devices.

Therefore if I had to take a stab at this I’d say this is some sort of vulnerability in HyperV that is related to how disk drives interface with the Virtual Machine, specifically in how HyperV virtualizes the IDE protocol when interacting with virtual disks.

Overall as this was found by Microsoft Research and Engineering and the attack complexity is listed as High, I doubt any public details will be coming out on this anytime soon, however if your organization uses Hyper-V and advanced technical attackers are within your risk profile, then I’d recommend patching this vulnerability ASAP. Otherwise, do try to patch this vulnerability when you can, but note that there are plenty of other vulns that are likely higher risk than this one in this months advisory.

1
Ratings
Technical Analysis

Huh so this one is a bit of a doozy. On the one hand we have Microsoft Exchange Server, which, unless you have been living under a rock, has been exploited many times in the past, as evidenced here, here, and here. Basically Microsoft Exchange Server has a giant target on its back and attackers are all too happy to exploit it given any opportunity to do so.

What makes this bug interesting though is that unlike most of the other vulnerabilities which were exploitable remotely, this one not only requires authentication, but also requires local network access of some type. Its also interesting to note that the Scope section of Microsoft’s advisory is marked as Changed, which they take as meaning An exploited vulnerability can affect resources beyond the security scope managed by the security authority of the vulnerable component. In this case, the vulnerable component and the impacted component are different and managed by different security authorities..

This raises a few questions as it seems to suggest that the initial component used to exploit the vulnerability exists in one security context separate from the Exchange Server security context, which when combined with the Adjacent factor, suggests a rather unusual way of exploiting this vulnerability via some local access, presumably though some component with a different security boundary, which then interacts with the Exchange Server.

Exploitation is however listed as easy and the bug does give you high level permissions on the Exchange Server, so I can see this as being useful for internal attacks once an attacker has gotten initial access into a network. As per usual, it is always advisable to assume that your network has been compromised when considering what to patch; I have personally seen that its often the little vulnerabilities that were ignored instead of being patched combined together that can lead to some of the most unexpected and dangerous impacts to companies.

Overall I’d say this is likely lower on your patch list than other RCE bugs, however given the impact and number of previous exploits for this target, I’d still recommend patching this as soon as possible, presumably once all your RCE bugs have been patched.

1
Ratings
Technical Analysis

Privilege escalation in Active Directory Domain Services that allows for elevation of privilege across a network.

Given how popular Active Directory is this is pretty serious and I imagine this will likely be a very popular way to escalate privileges in internal engagements should an exploit come out for this.

Bug is noted as being related to Active Directory Domain Service environments that have incoming trusts, and that the bug allows attackers to escalate privileges across the trust boundary under certain conditions.

The fact that this is labeled as “under certain conditions” leads me to believe that this may not be a default configuration which is why I have labeled it as such in my report. However I would also provide a slight counterpoint to this in that AD networks are often sprawling and complex so there is a good likelihood that this may be enabled in some part of the network even if you are not aware of it.

Definitely one to patch if you are running AD networks.

2
Ratings
Technical Analysis

Writeup for this vulnerability is rather interesting and I think a few people may have read the Microsoft advisory somewhat incorrectly.

The attack vector for this is listed as local which is odd given this is listed as an RCE vulnerability in the Windows Security Center API.

However looking at Microsoft’s description closer we can see that the Attack Vector value of Local is also applied if the attacker relies on User Interaction by another person to perform actions required to exploit the vulnerability.

Looking further down the assessment we can see the complexity is considered Low, no privileges are required, however User Interaction is marked as Required.

This suggests that it is possible to somehow exploit this vulnerability by either sending the target a request which then opens a prompt that they have to interact with, or by sending them some malicious document which then triggers the vulnerability.

Given this has a high impact on both Confidentiality, Integrity, and Availability I would say this likely gives you pretty high level access should you be able to exploit it successfully.

The patch was likely applied to the wscsvc.dll file given the modification dates and info on the web about how the Windows Security Service works, but I’ll have to do a more in depth analysis to determine what exactly was changed. Hopefully this information is useful for now though.

2
Ratings
Technical Analysis

This appears to be a bug in Windows Event Tracing which is a kernel level tracing facility in Windows that allows you to log kernel events or application defined events to a log file. The bug occurs due to something related to Discretionary Access Control Lists or DACLS for short. DACLs in Windows are an access control mechanism made up of a bunch of ACE, or Access Control Entries, put together into a list. Note however that if a DACL does not exist on an object, everyone is allowed access to it and if a DACL is set on an object but not ACE entries are added to the DACL, no one is allowed access to it.

My guess is that somehow it is possible to create a DACL on a specific file or object as any authenticated user that does not have any ACE entries associated with it and thereby cause a component to stop working completely due to its need to access that file. Adding to the fact that this only causes a DoS, is the fact that this bug supposedly only works on Windows 10 1809 and Windows Server 2019, which further limits its impact.

Overall a pretty low impact bug in my opinion.

2
Ratings
Technical Analysis

This appears to be a vulnerability in the Windows Platform Binary Table verification, also known as WPBT verification for short. It appears this was originally discovered back in September 2021 by Mickey Shkatov and the researchers of Eclypsium, who published a paper on this titled Everyone Gets a Rootkit where they detailed this bug in greater detail.

Simply put, WPBT was introduced in Windows 8 that is an extension to an earlier protocol known as ACPI, or Advanced Configuration and Power Interface, which was originally designed to efficiently manage energy consumption in PCs. A flaw was found in WPBT verification whereby expired or compromised signatures could still be used to sign a WPBT binary, as these drivers were not in the kernel driver block list, which is stored as a file named driver.stl.

By signing a WPBT binary of the attackers creation with one of these expired or compromised certificates, authenticated attackers could get malicious code to load with kernel privileges when the target device boots up.

WPBT binaries are particularly powerful as they allow OEMS to modify the host operating system during boot. This is often needed to supply vendor-specific drivers, applications and content. As a result, compromising the integrity of this stage of the Windows OS loading process means that an attacker can install a rootkit onto the target system to easily maintain stealthy and persistent access to the target machine.

Its also important to note that this attack works even with Secured-core PCs running with the latest boot protections and mitigations. Therefore this vulnerability fundamentally this undercuts a lot of the new mitigations that were introduced with Windows 10 and Windows 11 to try prevent supply chain compromise and rootkit installation, which is a serious compromise of trust..

Overall I have rated this vulnerability as high on attacker value since this is essentially the highest form of privileges you can possibly get on a Windows OS, going beyond even SYSTEM level access to strike at the very heart of the OS itself, however the exploitability is somewhat lower as you would need to craft a valid WPBT binary, something that would take some time to research since I imagine not many people are familiar with how to do that. Signing it with a compromised/expired certificate though shouldn’t be that hard to do though :)

2
Ratings
Technical Analysis

Update: As predicted there is a patch bypass for this, now labled as CVE-2022-26904

According to https://twitter.com/KLINIX5/status/1480996599165763587 this appears to be a patch for the code blogged about at https://halove23.blogspot.com/2021/10/windows-user-profile-service-0day.html. The details on this bug can be found at https://github.com/klinix5/ProfSvcLPE/blob/main/write-up.docx but I’ll summarize them here for brevity.

The original incomplete patch, aka CVE-2021-34484 is explained best by Mitja Kolsek at https://blog.0patch.com/2021/11/micropatching-incompletely-patched.html where he notes that bug was originally considered to be an arbitrary directory deletion bug that allowed a logged on user to delete a folder on the computer.

However upon reviewing the fix KLINUX5 found that it was possible to not only bypass the fix, but also make the vulnerability more impactful.

Specifically by abusing the User Profile Service’s code which creates a temporary user profile folder (to protect against the original user profile folder being damaged etc), and then copies folders and files from the original profile folder to the backup, one can instead place a symbolic link. When this symbolic link is followed, it can allow the attacker to create attacker-writeable folders in a protected location and then perform a DLL hijacking attack against high privileged system processes.

Unfortunately when patching this bug, Microsoft correctly assumed that one should check that the temporary user folder (aka C:\Users\TEMP), is not a symbolic link, but didn’t check to see if any of the folders under C:\Users\TEMP contains a symbolic link.

Note that as noted in https://blog.0patch.com/2021/11/micropatching-incompletely-patched.html this bug does require winning a race condition so exploitation is 100% reliable however there are ways to win the race condition as was shown in the code for the patch bypass published at https://github.com/klinix5/ProfSvcLPE/tree/main/DoubleJunctionEoP.

I’d keep an eye on this one as KLINIX5 has a habit of finding patch bypasses for his bugs and if he says Microsoft has messed things up again, more than likely there will be another patch bypass for this bug. I’m still looking into exactly what was patched here though.

2
Ratings
Technical Analysis

This looks to be a Use-After-Free bug in libarchive 3.4.1 through 3.5.1 that was only recently patched by Microsoft in January 2021, though the details on this bug were public as early as June 2021 in https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=32375. It remains unclear if this was fully fixed though as https://github.com/libarchive/libarchive/issues/1554 is still open which references https://github.com/libarchive/libarchive/pull/1491 as being the fix, yet that PR is on hold as of today (January 11th 2021), and that relies on https://github.com/libarchive/libarchive/pull/1492 which is in turn dependent on https://github.com/libarchive/libarchive/pull/1493. All of this leads to a bit of a confusing mess as to if this bug has truely been fixed or not.

This bug occurs in copy_string which is in turn called from do_uncompress_block and process_block. These functions exist within the libarchive/libarchive/archive_read_support_format_rar5.c file, as can be seen by looking at https://github.com/libarchive/libarchive/blob/411284e3f5819a5726622f3f129ebf2859f2d46b/libarchive/archive_read_support_format_rar5.c, and are related to parsing RAR5 archive files.

So what is RAR5 archive files? Well turns out according to https://www.remosoftware.com/info/differences-between-rar-and-rar5-compression that RAR4 was the default archive compression mechanism for RAR files. RAR5 is the new compression algorithm that is trying to rival 7ZIP and similar compression formats and is an evolution of the RAR4 format. The article also notes that right now WinRAR is the most likely program to open these newer file formats.

From this we can conclude that this bug most likely occurs when sending a user a RAR5 file and a Windows program that uses the system’s version of the libarchive library attempts to extract the RAR5 file, which will cause a UAF condition that, if controlled, could allow the attacker to gain RCE on a users computer.

2
Ratings
Technical Analysis

Original report for this vulnerability can be found at https://curl.se/docs/CVE-2021-22947.html

This vulnerability affects curl 7.20.0 to 7.78.0 inclusive, and occurs due to the commit made at https://github.com/curl/curl/commit/ec3bb8f727405.

The bug occurs as when curl connects to a IMAP, POP3, SMTP, or FTP server using STARTTLS to upgrade the connection to a TLS connection. In these scenarios the server can send multiple responses prior to the TLS upgrade, which are then cached by curl.

Unfortunately, when upgrading to TLS, curl would not flush this queue of cached responses and instead would treat these responses as part of the TLS handshake themselves as if they were authenticated.

Attackers could use this to inject fake response data via a man in the middle (MITM) attack when the connection uses POP3 or IMAP as noted by the curl developers.

It is interesting to note that this bug was disclosed via HackerOne in September 2021 but was only fixed by Microsoft in January 2021 as noted at https://www.zerodayinitiative.com/blog/2022/1/11/the-january-2022-security-update-review, meaning there was at least a 3 month gap between the bug being public knowledge and it being fixed.

As for the exploitability of this bug, it is fairly low due to the need to be able to conduct a MITM attack against a target user. Additionally using implicit TLS instead of using STARTTLS negates this issue so attackers would have to find a connection specifically using STARTTLS.

It should be noted though that may applications use libcurl, the affected library, even if they don’t explicitly advertise it, so there is a good possibility that a fair number of apps on Windows would use this in some manner.

As a final note, its not directly clear to me why Microsoft rates this as a RCE bug but I imagine they likely found a connection between an attacker MITM’ing a specific connection for one of their apps and forging a fake response that can then be used to trigger some form of RCE. No details are provided on which app this might be though, so the specifics of this remain to be seen.