⟵ Advisories

[MZ-20-03] Deserialization in the .Net runtime

ID: MZ-20-03

Release: June 16, 2020

Credits: Nils Ole Timm

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
---------------------------------------------------------------- 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.

Other News

All news ⟶