2020-06-16

MZ-20-03 - New security advisory regarding vulnerabilities in .Net

Today, we publish a new advisory for some vulnerabilities, that have been found by our team-mate Nils Ole Timm (@firzen14).

Nils spent some time with .Net deserialization attacks and research. In April 2020 we already published an article about his Deserialization Attacks in .Net Games.

While the gaming industry thankfully fixed all of the reported issues, Microsoft elected to manage rather than fix the reported issues. For this advisory, two of them were not considered vulnerabilities by Microsoft as "by design". The third one was originally planned to be fixed, but a week before the disclosure deadline Microsoft informed us that they would only add a warning to their documentation.

Proof of Concept code is provided for each vulnerability right here:

The direct link to the advisory is https://www.modzero.com/advisories/MZ-20-03-Net-Deserialization.txt

---------------------------------------------------------------- v5 ---

modzero Security Advisory:
Multiple deserialization vulnerabilities in the .Net runtime [MZ-20-03]

-----------------------------------------------------------------------

-----------------------------------------------------------------------

1. Timeline

-----------------------------------------------------------------------

* 2020-02-14: This  advisory  has been  sent to the Microsoft  security
              team (security@microsoft.com).
* 2020-02-19: Microsoft  requests  that  the  three  vunerabilities are
              resubmitted individually.
* 2020-02-19: Vulnerabilities resubmitted individually.
* 2020-02-29: Microsoft closes 4.2 as "By Design".
* 2020-03-19: Microsoft accepts 4.1 as a security issue.
* 2020-03-19: Microsoft closes 4.3 as "By Design".
* 2020-04-07: Microsoft  informs  modzero of a planned patch release on
              June 9th.
* 2020-06-02: Microsoft informs modzero that the vulnerability  will be
              fixed with documentation only.
* 2020-06-08: modzero replies with concerns regarding the proposed fix.
* 2020-06-15: Microsoft replies that they will go through with the fix.
* 2020-06-16: modzero publishes this disclosure.

-----------------------------------------------------------------------

2. Summary

-----------------------------------------------------------------------

Vendor: Microsoft

* 4.1 Deserialization vulnerability in
      IsolatedStorageFileEnumerator::MoveNext via crafted  identity.dat
      file leading to arbitrary code execution
      modzero: CVSS:3.0/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H -> 8.2

* 4.2 Deserialization vulnerability in
      BinaryServerFormatterSink::ProcessMessage
      4.2.1 When configured with TypeFilterLevel.Low
           Denial of Service
           modzero: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H -> 7.5
           
      4.2.2 When configured with TypeFilterLevel.Full
           Remote code execution
           modzero: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H -> 9.0
  
* 4.3 Deserialization vulnerability in 
      System.Messaging.Message::get_Body() using a
      BinaryMessageFormatter   leading   to   remote   code   execution
      modzero: CVSS:3.0/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H -> 9.0

-----------------------------------------------------------------------

3.0 Introduction

-----------------------------------------------------------------------

modzero identified several critical vulnerabilities in the .Net runtime
which  can lead to  denial of service  or remote code execution attacks 
against services using standard built-in .NET features. A potential for
local  privilege  escalation  or persistence  using the IsolatedStorage 
vulnerability was also found.

Any  software  using  the  vulnerable  .Net  components is  potentially
affected.
Specifically:
    
    * Enumeration of IsolatedStorage spaces
    * .Net Remoting with binary serialization
    * .Net MSMQ with a BinaryMessageFormatter

modzero  identified  and  tested the  vulnerabilities to be present in:

.NET Framework
4.8 4.7.2 4.7.1 4.7 4.6.2 4.6.1 4.6 4.5.2 4.5.1 4.5

Earlier  versions  have not been tested, but are  likely to at least be
partially affected as well.

-----------------------------------------------------------------------

4. Details

-----------------------------------------------------------------------

4.1 Triggering Deserialization vulnerability in 
    IsolatedStorageFileEnumerator::MoveNext  via  crafted  identity.dat
    file leads to arbitrary code execution

An attacker  with write access  to the  identity.dat file can  inject a
deserialization payload, which will be  executed when the built-in .NET
method   IsolatedStorageFileEnumerator::MoveNext    is   called.   When
creating   an    IsolatedStorage   space    in   the    Machine   scope
(IsolatedStorageScope.Machine)  its  identity.dat  file  has  read  and
write permissions  for the  "Everyone" Windows-Group. An  attacker with
access to any account can  create a Machine scope IsolatedStorage space
and cause  the vulnerability  to trigger on  the next  enumeration. The
enumeration itself  does not  have to  be controlled  or issued  by the
attacker  and thus  the  execution  takes place  in  the context  where
enumeration occurs.
    
When    using    an    IsolatedStorageFileEnumerator    to    enumerate
IsolatedStorage spaces, the  MoveNext method will read  the contents of
each  space's  identity.dat  file  and  deserialize  them  without  any
security features enabled. The identity.dat  files in the Machine scope
have  read/write  permissions   for  the  Everyone  group   and  a  low
privileged user  can craft an  identity.dat file to execute  a standard
deserialization attack when another user enumerates storage spaces.
    
This for example affects the storeadm.exe tool.
    
The  following  code  snippets  demonstrate   how  the  data  from  the
identity.dat  file is  passed directly  into a  BinaryFormatter without
further sanitization or any security measures.

IsolatedStorageFileEnumerator::MoveNext calls
this.GetIDStream(twoPaths.Path1,  out  stream)  to  retrieve  the  file
contents of  the associated  identity.dat file of  each IsolatedStorage
space into a stream variable.
    
    
    public bool MoveNext()
    {
      while (this.m_fileEnum.MoveNext())
      {
        [...]
        if (flag)
        {
          if (!this.GetIDStream(twoPaths.Path1, out stream) || !this.GetIDStream(twoPaths.Path1 + "\\" + twoPaths.Path2, out stream2))
          [...]
        }
        else if (IsolatedStorageFile.NotAppFilesDir(twoPaths.Path2))
        {
          if (!this.GetIDStream(twoPaths.Path1, out stream2))
          [...]
          stream2.Position = 0L;
        }
        else
        {
          if (!this.GetIDStream(twoPaths.Path1, out stream3))
          [...]
          stream3.Position = 0L;
        }


The  previously   populated  stream  variable  holding   the  possibliy
malicious identity.dat file's  content is passed to an  overload of the
InitStore method as documented in the followind code section.


    if (isolatedStorageFile.InitStore(scope, stream, stream2, stream3, domainName, assemName, appName) && isolatedStorageFile.InitExistingStore(scope)) 
    {
      this.m_Current = isolatedStorageFile;
      return true;
    }
                
                
The InitStore method then passes  the MemoryStream of the file contents
into a BinaryFormatter without enabling any security features on it.
    
    
    internal bool InitStore(IsolatedStorageScope scope, Stream domain, Stream assem, Stream app, string domainName, string assemName, string appName)
    {
      BinaryFormatter binaryFormatter = new BinaryFormatter();
      [...]
        this.m_AppIdentity = binaryFormatter.Deserialize(app);
      [...]
        this.m_AssemIdentity = binaryFormatter.Deserialize(assem);
      [...]
          this.m_DomainIdentity = binaryFormatter.Deserialize(domain);

    
This  allows   execution  of  arbitrary  code   by  utilizing  standard
BinaryFormatter deserialization  gadgets; payloads  can for  example be
generated using the ysoserial.net tool.  This can be used for privilege
escalation, especially since enumeration  of isolated storage spaces is
typically only performed during administrative tasks.
    
When creating an IsolatedStorage space scoped  to a user with a roaming
profile,   the  modified   identity.dat  file   may  be   automatically
transferred  across  an Active  Directory  network.  In this  case  the
vulnerability  may  spread across  the  network  if an  enumeration  of
storage  spaces  is  regularly  performed. A  transferred  payload  can
infect another computers  Machine scope which can in  turn infect other
users and their roaming scope.



4.2 Deserialization vulnerability in
    BinaryServerFormatterSink::ProcessMessage   leading  to  Denial  of
    Service (DoS)
    
    
By sending  a crafted message  to a .Net  remoting channel a  denial of
service or remote  code execution can be triggered if  the channel uses
a  BinaryServerFormatterSink in  its SinkChain,  which it  does in  the
default  configuation.  Wether or  not  the  DoS  or RCE  will  trigger
depends  on what  the  BinaryServerFormatterSink's TypeFilterLevel  has
been set to.
    
The default  is Low, in  which case only the  Denial of Service  can be
triggered. If  it has been set  to Full instead, remote  code execution
can be performed.

When  using Remoting  in .Net  the incoming  and outgoing  messages are
processed by SinkChains, which are  essentially a linked list of sinks.
These sinks  are passed the  current data, perform some  processing and
pass the updated data on to  the next chain for further processing. One
of  these  sinks  is   the  BinaryServerFormatterSink  which  processes
incoming messages which have been serialized to a binary format.
    
When an incoming  message is received, ProcessMessage is  called on the
BinaryServerFormatterSink  instance, the  requestStream that  is passed
to it contains the serialized message  that the sink is ment to decode.
If  the TypeFilterLevel  property of  the BinaryFormatterSink  has been
set to  Low it  will restrict  the security context  to only  grant the
SerializationFormatter permission.
    
If the  TypeFilterLevel is set to Full,  the  security context won't be
restricted.
(https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.typefilterlevel?view=netframework-4.8)
Afterwards  it  will  call  CoreChannel.DeserializeBinaryRequestMessage
with the requestStream it has been called with.
    
    
    if (this.TypeFilterLevel != TypeFilterLevel.Full)
    {
      permissionSet = new PermissionSet(PermissionState.None);
      permissionSet.SetPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));
    }
    try
    {
      if (permissionSet != null)
      {
        permissionSet.PermitOnly();
      }
      requestMsg = CoreChannel.DeserializeBinaryRequestMessage(text5, requestStream, this._strictBinding, this.TypeFilterLevel);
    }
    finally
    {
      if (permissionSet != null)
      {
        CodeAccessPermission.RevertPermitOnly();
      }
    }
            
            
CoreChannel.DeserializeBinaryRequestMessage    then    initializes    a
BinaryFormatter   and   sets   its   FilterLevel   according   to   the
BinaryServerFormatterSink's.  It then  calls  UnsafeDeserialize on  the
BinaryFormatter.
    
    
    internal static IMessage DeserializeBinaryRequestMessage(string objectUri, Stream inputStream, bool bStrictBinding, TypeFilterLevel securityLevel)
    {
      BinaryFormatter binaryFormatter = CoreChannel.CreateBinaryFormatter(false, bStrictBinding);
      binaryFormatter.FilterLevel = securityLevel;
      CoreChannel.UriHeaderHandler @object = new CoreChannel.UriHeaderHandler(objectUri);
      return (IMessage)binaryFormatter.UnsafeDeserialize(inputStream, new HeaderHandler(@object.HeaderHandler));
    }
            
            
The UnsafeDeserialize call then gets  passed down to a Deserialize call
which   instantiates    an   ObjectReader   with    the   corresponding
TypeFilterLevel.  It then  calls ObjectReader::Deserialize  on the  new
ObjectReader instance.
    
    
    internal object Deserialize(Stream serializationStream, HeaderHandler handler, bool fCheck, bool isCrossAppDomain, IMethodCallMessage methodCallMessage)
    {
      [...]
      internalFE.FEsecurityLevel = this.m_securityLevel;
      ObjectReader objectReader = new ObjectReader(serializationStream, this.m_surrogates, this.m_context, internalFE, this.m_binder);
      objectReader.crossAppDomainArray = this.m_crossAppDomainArray;
      return objectReader.Deserialize(handler, new __BinaryParser(serializationStream, objectReader), fCheck, isCrossAppDomain, methodCallMessage);
    }
            
            
ObjectReader::Deserialize    then    performs   the    deserialization.
Additional security checks are performed if IsRemoting is true.
    
    
    internal void CheckSecurity(ParseRecord pr)
    {
      Type prdtType = pr.PRdtType;
      if (prdtType != null && this.IsRemoting)
      {
        [...]
        FormatterServices.CheckTypeSecurity(prdtType, this.formatterEnums.FEsecurityLevel);
      }
    }
            
            
IsRemoting  is  true if either  bMethodCall or  bMethodReturn  is true.

    
    private bool IsRemoting
    {
      get
      {
        return this.bMethodCall || this.bMethodReturn;
      }
    }
            
            
These two values  (bMethodCall and bMethodReturn) are only  set to true
by the SetMethodCall and SetMethodReturn methods respectively.

    
    
    internal void SetMethodCall(BinaryMethodCall binaryMethodCall)
    {
      this.bMethodCall = true;
      this.binaryMethodCall = binaryMethodCall;
    }

    internal void SetMethodReturn(BinaryMethodReturn binaryMethodReturn)
    {
      this.bMethodReturn = true;
      this.binaryMethodReturn = binaryMethodReturn;
    }
            
            
Those         two        methods         are        only         called
from__BinaryParser::ReadMethodObject,   which  is   only  called   from
__BinaryParser::Run
    
    
    case BinaryHeaderEnum.MethodCall:
    case BinaryHeaderEnum.MethodReturn:
      this.ReadMethodObject(binaryHeaderEnum);
                
                
Therefore additional security checks are  only performed if an IMessage
is being deserialized.  An attacker can bypass  the additional security
checks  by  submitting  a  crafted  stream of  data  that  contains  no
IMessage object and triggers execution of deserialization gadgets.
    
Using   the   standard   TypeConfuseDelegate    gadget,   a   call   to
System.Diagnostics.Process::Start(string,string) can be performed.

In the  case of TypeFilterLevel  being set to  Low (4.2.1) the  call to
Process.Start  then causes  an  uncaught  SecurityException, since  the
security   context    is   restricted.   The   exception    occurs   in
System.Diagnostics.ShellExecuteHelper::ShellExecuteFunction     in    a
separate                thread                spawned                by
System.Diagnostics.ShellExecuteHelper::ShellExecuteOnSTAThread.     The
uncaught exception  then causes termination  of the process  leading to
Denial of Service.
    
In the  case of TypeFilterLevel being  set to Full (4.2.2)  the call to
Process.Start passes all security checks  and a new process is started.
In this  case arbitrary code  can be executed  remotely as long  as the
channel is accessible.

Because there are no restrictions  imposed on the deserialization, when
using  an HTTP  channel 4.2.2  can also  be exploited  by generating  a
payload file  with the  ysoserial.net tool  and a  curl request  of the
form:
    
    
    curl -X POST -H "Content-Type: application/octet-stream" --data-binary "@payload" http://serveraddress/Service


4.3 Deserialization vulnerability in
    System.Messaging.Message::get_Body() using a BinaryMessageFormatter
    
When  using  Microsoft  Message  Queueing (MSMQ)  with  .Net,  messages
retrieved  from the  Queue are  processed into  a Message  object. This
object  contains an  IMessageFormatter property  and in  the case  of a
retrieved  messageit contains  a BodyStream  that holds  the serialized
body of  the message.  This BodyStream is  not parsed  immediately, but
will instead be  deserialized only when the Body's  getter is accessed.
The getter then calls  Formatter.Read on its IMessageFormatter instance
to create the actual Body object.
    
    
    public object Body
    {
      get
      {
        if (this.filter.Body)
        {
          if (this.cachedBodyObject == null)
          {
            [...]
            this.cachedBodyObject = this.Formatter.Read(this);
          }
          return this.cachedBodyObject;
        }
                    
                    
If the  IMessageFormatter is a BinaryMessageFormatter,  its Read method
checks    if   the    BodyType   is    compatible   and    then   calls
BinaryFormatter::Deserialize  on  a  default  BinaryFormatter  instance
with no additional security features enabled.
    
    
    public BinaryMessageFormatter()
    {
      this.formatter = new BinaryFormatter();
    }

    public object Read(Message message)
    {
      [...]
      int bodyType = message.BodyType;
      if (bodyType == 768)
      {
        Stream bodyStream = message.BodyStream;
        return this.formatter.Deserialize(bodyStream);
                   
                    
This allows  the use  of arbitrary deserialization  gadgets and  can be
used to  execute arbitrary code  when somebody retrieves  messages from
the message queue.

-----------------------------------------------------------------------

5. Proof of Concept exploits

-----------------------------------------------------------------------

PoC  exploits are  provided  as separate  git  repos containing  Visual
Studio Solutions.

The  IsolatedStorageVulnerability  solution demonstrates  vulnerability
4.1.

After executing the PoC, any vulnerable program enumerating the Machine
scope will  execute a program  when deserializing the  payload. Running
"storeadm /List"  in the  Visual Studio  Developer Console  for example
will trigger the vulnerability.


The RemotingVulnerability solution demonstrates vulnerabilities 4.2.

It contains two projects:
    * RemotingService - a bare bones Remoting server
    * RemotingExploit - the actual exploit
When the server is running  and configured with TypeFilterLevel.Low the
exploit will crash  the server process. When the server  is running and
configured  with TypeFilterLevel.Full  the  exploit  will trigger  code
execution in the server process.


The MSMQ solution demonstrates vulnerability 4.3.

It contains two projects:
    * MSMQ Reader - a small program polling messages from an MSMQ
    * MSMQ Exploit - the actual exploit
When the  reader is running  the exploit  will cause code  execution to
occur as  soon as  the getter of  the Body property  of the  Message is
accessed.


All  projects use  the TypeConfuseDelegate  gadget with  SortedSet`1 to
reach code execution from the deserialization vulnerability.

Please find the PoC projects at GitHub:

* https://github.com/modzero/MZ-20-03_PoC_IsolatedStorage
* https://github.com/modzero/MZ-20-03_PoC_NetRemoting
* https://github.com/modzero/MZ-20-03_PoC_MSMQ_BinaryMessageFormatter

-----------------------------------------------------------------------

6. Workarounds

-----------------------------------------------------------------------

For 4.2, restricting  access to  the remoting  channel will  reduce the
potential  attack vectors.  When  using Tcp  or  Icp channels, enabling
authentication can  mitigate some  risks as  well. If  possible setting
TypeFilterLevel to  Low will  mitigate the  RCE to  a DoS  but business
cases might require using TypeFilterLevel.Full

For 4.3, if possible, restrict access to any queue unless necessary.

-----------------------------------------------------------------------

7. Fix

-----------------------------------------------------------------------

Currently, no fixes are available.

-----------------------------------------------------------------------

8. Credits

-----------------------------------------------------------------------

 * Nils Ole Timm

-----------------------------------------------------------------------

9. About modzero

-----------------------------------------------------------------------

The  independent  Swiss-German  company modzero  assists  clients  with
security analysis  in the  complex areas  of  computer  technology. The
focus  lies  on   highly  detailed  technical   analysis  of  concepts,
software   and hardware   components as  well as   the development   of
individual   solutions.  Colleagues  at  modzero   work exclusively  in
practical, highly  technical computer-security  areas and  can  draw on
decades  of  experience  in  various  platforms,  system  concepts, and
designs.

https://www.modzero.com 
contact@modzero.com

modzero  follows  coordinated  disclosure  practices  described   here:
https://www.modzero.com/static/modzero_Disclosure_Policy.pdf.

This  policy  should have  been  sent to  the  vendor along  with  this
security advisory.


-----------------------------------------------------------------------

10. Disclaimer

-----------------------------------------------------------------------

The information in the advisory is  believed to be accurate at the time
of  publishing based  on currently  available information.  Use of  the
information  constitutes acceptance  for  use in  an  AS IS  condition.
There are  no warranties with  regard to this information.  Neither the
author  nor  the  publisher  accepts  any  liability  for  any  direct,
indirect,  or consequential  loss or  damage  arising from  use of,  or
reliance on, this information.


Posted by Thorsten Schroeder | Permanent link | File under: security, software, github, hacking, exploit, re, advisory