DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Elevate your data management. Join a lively chat to learn how streaming data can improve real-time decision-making, and how to reduce costs.

Platform Engineering: Enhance the developer experience, establish secure environments, automate self-service tools, and streamline workflows

Build Cloud resilience. High-profiled cloud failures have shown us one thing – traditional approaches aren't enough. Join the discussion.

Data Engineering: The industry has come a long way from organizing unstructured data to adopting today's modern data pipelines. See how.

Related

  • Hibernate Validator vs Regex vs Manual Validation: Which One Is Faster?
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • Functional Approach To String Manipulation in Java
  • Unveiling Vulnerabilities via Generative AI

Trending

  • Build a Multilingual Chatbot With FastAPI and Google Cloud Translation
  • Will GenAI Force Coders to Switch Careers?
  • Good Refactoring vs. Bad Refactoring
  • Understanding AI-Driven Adaptive Consistency in Distributed Systems
  1. DZone
  2. Data Engineering
  3. Databases
  4. Converting String to Enum at the Cost of 50 GB: CVE-2020-36620

Converting String to Enum at the Cost of 50 GB: CVE-2020-36620

In this article, readers are going to use a tutorial to learn how to convert a string to an enum at the cost of 50 GB with the CVE-2020-36620 vulnerability.

By 
Sergey Vasiliev user avatar
Sergey Vasiliev
·
Mar. 23, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
2.5K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, we’re going to discuss the CVE-2020-36620 vulnerability and see how a NuGet package for converting string to enum can make a C# application vulnerable to DoS attacks.

Enum

Imagine a server application that interacts with a user. In one of the scenarios, the application receives data from the user in a string representation and converts it into enumeration elements (string -> enum).

To convert a string into an enumeration element, we can use standard .NET tools:

 
String colorStr = GetColorFromUser();
if (Enum.TryParse(colorStr, out ConsoleColor parsedColor))
{
  // Process value...
}


Or we can find some NuGet package and try to do the same with it. For example, EnumStringValues.

Since we have a sense of adventure (right?), let’s take the EnumStringValues 4.0.0 package to convert strings. The exclamation point in front of the package version makes it even more intriguing.

Here’s what the code using the package’s API might look like:

 
static void ChangeConsoleColor()
{
  String colorStr = GetColorFromUser();

  if (colorStr.TryParseStringValueToEnum<ConsoleColor>(
        out var parsedColor))
  {
    // Change console color...
  }
  else
  {
    // Error processing
  }
}


Let’s see what's going on here:

  • The data from the user is written to the colorStr variable.
  • With the help of the library’s API, the EnumStringValues string is converted to an instance of the ConsoleColor enumeration.
  • If the conversion is successful (then-branch), the color of the console changes.
  • If otherwise, (else-branch), an error warning is issued.

Strings are converted, and the application is running. Everything seems good... but there’s one thing. It turns out that the application can behave as follows:

Behave

Oh, wait, we have a package marked with an “exclamation point,” right? Let’s try to figure out why there’s such a high memory consumption. The following code will help us:

 
while (true)
{
  String valueToParse = ....;
  _ = valueToParse.TryParseStringValueToEnum<ConsoleColor>(
        out var parsedValue);
}


Using the library, the code infinitely parses strings—nothing unusual. If valueToParse takes values of string representations of the ConsoleColor elements ("Black", "Red", etc.), the application will behave as expected:

Behavior 2

The issues appear when we write unique strings to valueToParse. For example, as follows:

 
String valueToParse = Guid.NewGuid().ToString();


In this case, the application starts to consume memory uncontrollably:

Consume Memory

Let’s try to figure out what’s going on. Take a look at the TryParseStringValueToEnum<T> method:

 
public static bool 
TryParseStringValueToEnum<TEnumType>(
  this string stringValue, 
  out TEnumType parsedValue) where TEnumType : System.Enum
{
  if (stringValue == null)
  {
    throw new ArgumentNullException(nameof(stringValue), 
                                    "Input string may not be null.");
  }

  var lowerStringValue = stringValue.ToLower();
  if (!Behaviour.UseCaching)
  {
    return TryParseStringValueToEnum_Uncached(lowerStringValue, 
                                              out parsedValue);
  }

  return TryParseStringValueToEnum_ViaCache(lowerStringValue, 
                                            out parsedValue);
}


Well, that’s interesting. It turns out there’s a caching option under the hood — Behavior.UseCaching. Since we didn’t explicitly use the caching option, let’s look at the default value:

 
/// <summary>
/// Controls whether Caching should be used. Defaults to false.
/// </summary>
public static bool UseCaching
{
  get => useCaching;
  set { useCaching = value; if (value) { ResetCaches(); } }
}

private static bool useCaching = true;


If the comment to the property is true, caches are disabled by default. Actually, they are enabled (useCaching — true).

You can already guess what the issue is. However, to be sure, let’s dive deeper into the code.

With the knowledge obtained, we return to the TryParseStringValueToEnum method. Depending on the caching option, one of two methods will be called:

  1. TryParseStringValueToEnum_Uncached
  2. TryParseStringValueToEnum_ViaCache:
 
if (!Behaviour.UseCaching)
{
  return TryParseStringValueToEnum_Uncached(lowerStringValue, 
                                            out parsedValue);
}

return TryParseStringValueToEnum_ViaCache(lowerStringValue, 
                                          out parsedValue);


In this case, the UseCaching property has the true value, the TryParseStringValueToEnum_ViaCache method gets control. You can view its code below, but you don’t have to delve deeper into it—further on, we’re going to analyze the method step by step:

 
/// <remarks>
/// This is a little more complex than one might hope, 
/// because we also need to cache the knowledge 
/// of whether the parse succeeded or not.
/// We're doing that by storing `null`, 
/// if the answer is 'No'. And decoding that, specifically.
/// </remarks>
private static bool 
TryParseStringValueToEnum_ViaCache<TEnumType>(
  string lowerStringValue, out TEnumType parsedValue) where TEnumType 
                                                        : System.Enum
{
  var enumTypeObject = typeof(TEnumType);

  var typeAppropriateDictionary 
    = parsedEnumStringsDictionaryByType.GetOrAdd(
        enumTypeObject, 
        (x) => new ConcurrentDictionary<string, Enum>());

  var cachedValue 
    = typeAppropriateDictionary.GetOrAdd(
        lowerStringValue, 
        (str) =>
        {
          var parseSucceededForDictionary =       
                TryParseStringValueToEnum_Uncached<TEnumType>(
                  lowerStringValue, 
                  out var parsedValueForDictionary);

          return   parseSucceededForDictionary 
                 ? (Enum) parsedValueForDictionary 
                 : null;
        });

  if (cachedValue != null)
  {
    parsedValue = (TEnumType)cachedValue;
    return true;
  }
  else
  {
    parsedValue = default(TEnumType);
    return false;
  }
}


Let’s analyze what happens in the method:

 
var enumTypeObject = typeof(TEnumType);

var typeAppropriateDictionary 
  = parsedEnumStringsDictionaryByType.GetOrAdd(
      enumTypeObject, 
      (x) => new ConcurrentDictionary<string, Enum>());


In the parsedEnumStringsDictionaryByType dictionary:

  • The key is the type of enumeration that is used.
  • The value is the cache of strings and the results of their parsing. 

So, we get the following cache scheme: 

Cache <Enumeration type -> Cache <Source string -> Parsing result>>

parsedEnumStringsDictionaryByType is a static field:

 
private static 
ConcurrentDictionary<Type, ConcurrentDictionary<string, Enum>> 
parsedEnumStringsDictionaryByType;


Thus, typeAppropriateDictionary stores a reference to the cache of values for the enumeration type that we are working with (enumTypeObject).

Then the code parses the input string and saves the result in the typeAppropriateDictionary:

 
var cachedValue 
  = typeAppropriateDictionary.GetOrAdd(lowerStringValue, (str) =>
    {
      var parseSucceededForDictionary 
        = TryParseStringValueToEnum_Uncached<TEnumType>(
            lowerStringValue, 
            out var parsedValueForDictionary);

      return   parseSucceededForDictionary 
             ? (Enum) parsedValueForDictionary 
             : null;
    });


In the end, the method returns the success flag and writes the resulting value to the out parameter:

 
if (cachedValue != null)
{
  parsedValue = (TEnumType)cachedValue;
  return true;
}
else
{
  parsedValue = default(TEnumType);
  return false;
}


The key problem is described in the method’s comment: 

This is a little more complex than one might hope, because we also need to cache the knowledge of whether the parse succeeded or not. We’re doing that by storing null, if the answer is “No.” And decoding that, specifically.

Even if the input string could not be parsed, it will still be saved to the typeAppropriateDictionary cache: the null value will be written as the result of parsing. Since typeAppropriateDictionary is a reference from the parsedEnumStringsDictionaryByType dictionary stored statically; objects exist between method calls (this makes sense because they are caches). 

So, here’s what happens: If attackers can send unique strings (that are parsed with the help of the library’s API) to the application, they have an opportunity to “spam” the cache with all the consequences it implies. 

Cache

The unique string parsing makes the typeAppropriateDictionary dictionary bigger. The debugger confirms that the cache is "spammed":

Cash Spam

Additional Information

So, we’ve just discussed the CVE-2020-36620 vulnerability. Here’s some additional information:

  • An entry in NVD.
  • An entry in the GitHub Advisory database.

The fix is simple—the parsing of input values was removed (the commit). 

Previously, the typeAppropriateDictionary was filled in as data was obtained:

  • If the input string is "Yellow", the { "yellow", ConsoleColor.Yellow } pair is written to the cache. 
  • If the input string is "Unknown", the { "unknown", null } pair is written to the cache, and so on.

Now, typeAppropriateDictionary is filled in advance. The dictionary initially stores the relationships of string representations of enumeration elements to the actual values:

Values

Input values are not written to the dictionary—there’s only an attempt to extract them:

 
if (typeAppropriateDictionary.TryGetValue(lowerStringValue, 
                                          out var cachedValue))
  ....


This fix made the cache no longer vulnerable to clogging with unique strings. 

The library 4.0.1 already includes the fix, but the corresponding NuGet package is marked as vulnerable. Apparently, the information is taken from the GitHub Advisory. It states that 4.0.2 is the secure version. However, the same entry contains links to data from NVD and vuldb. It indicates the package has been secured since the 4.0.1 version, not 4.0.2. So, there’s some confusion.

Here’s another interesting thing: the vulnerability was closed at the end of May 2019, and information about it appeared in the databases 3.5 years later—at the end of December 2022.

As long as the information about the vulnerability is not recorded in public databases, some tools will not be able to issue warnings about a security defect in the package. On one hand, such a delay is understandable—the project has three forks and sixteen stars, it can be classified as “personal.” On the other hand, the project has 200K package downloads in total—that’s a significant number.

Conclusion

At this point, we have finished the review of the CVE-2020-36620 vulnerability. I hope you enjoyed this article and have taken away some helpful information in case you come across the CVE-2020-36620 vulnerability in the future. Feel free to like and share this article. 

API NuGet Vulnerability Strings

Published at DZone with permission of Sergey Vasiliev. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Hibernate Validator vs Regex vs Manual Validation: Which One Is Faster?
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • Functional Approach To String Manipulation in Java
  • Unveiling Vulnerabilities via Generative AI

Partner Resources


Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: