Skip to main content
MSRC

Hey Yara, find some vulnerabilities

Intro

Finding vulnerabilities in software is no easy task by itself.  Doing this at cloud scale is very challenging to perform manually, and we use tools to help us identify patterns or vulnerability signatures.  Yara is one of those tools.

Yara is a very popular tool with Blue teams, malware researchers, and for good reason. Once a malicious pattern has been identified, it’s quite easy to write rules to detect it in huge collections of files.

In this article, we will discuss another way that Yara can be used from an AppSec/Red Team approach. We’ll look at how we can create rules that will match different types of software vulnerabilities. Among the examples, we’ll have deserialization vulnerabilities that can lead to arbitrary code execution, command injection vulnerabilities, and even loose regular expressions that can be bypassed and may lead to SSRFs, just to name a few.

This is not intended to be an exhaustive list.

Examples

C#

Below we’ll look at several examples of potentially vulnerable C# code.

Binary Formatter

Let’s say we encounter a code similar to the one below:

static string GetClientEmail()
{
    string json = GetData();
    BinaryFormatter formatter = new BinaryFormatter();
    FileStream fs = File.Open(@"client.txt", FileMode.Open);
    object obj = formatter.Deserialize(fs);
    Account acobj = (Account)obj;
    return acobj.Email;
}

Any use of BinaryFormatter with arbitrary/untrusted data (such as in the example above) is insecure.  The usage cannot be made secure because its Deserialize() method will check first the type provided in the file stream and then do any casts. An arbitrary crafted payload will exploit this functionality and execute malicious code.  Unlike other formatters, this cannot be mitigated by implementing a  SerializationBinder.  .NET considers this behavior as by design and will not issue a mitigation to modify it.

NewtonSoft.Json

The next vulnerability targets deserialization of an untrusted JSON payload using the Newtonsoft JSON library.  When TypeNameHandling has been set to “Objects” or “All”, this allows the JSON.NET library to check for the object type in the data provided:

private static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings 
{ 
    TypeNameHandling = TypeNameHandling.All;
    // As a side note, this also works if we use Objects instead of All. 
    TypeNameHandling = TypeNameHandling.Objects;
};

If an attacker is able to modify the serialized data, then this may allow malicious object types to be added which can be used to execute arbitrary commands. If this setting has been set to ‘None’, or not been specified at all (in which case it will default to ‘None’), that will mean that the DeserializeObject function in the examples below does not check the type of the string supplied as input (‘json’ in the examples below), which ensures no unsafe types given as user input are deserialized.

Example 1:

static string GetClientEmail()
{
    string json = GetData();
    var acobj = JsonConvert.DeserializeObject<Object>(json, jsonSerializerSettings);
    Account account = (Account)acobj;
    return account.Email;
}

Example 2:

static string GetClientEmail()
{
    string json = GetData();
    var acobj = (Account)JsonConvert.DeserializeObject<Object>(json, jsonSerializerSettings);
    Account account = (Account)acobj;
    return account.Email;
}

Both the examples above try to convert the object to our custom type ‘Account’: however, this isn’t successful as the malicious payload already executes before that happens. This is because whenever we have generic types such as ‘var’ or <Object>, the JsonConvert function looks into the payload string and tries to guess the type and then tries to deserialize it. When deserializing certain objects such as the generic ones in this example, their constructors get called first which can contain malicious executables.

Example 3:

static string GetClientEmail()
{
    string json = GetData();
    Account acobj = (Account)JsonConvert.DeserializeObject<Account>(json, jsonSerializerSettings);
    return acobj.Email;
}

The code above can still be vulnerable to unsafe deserialization if our type ‘Account’ has any public generic objects, or contains any classes that may in turn contain generic objects:

public class Account
{
    public string Email { get; set; }
    public string Active { get; set; }
    public object Details;
    public XYZ x;
}

public class XYZ
{
    public object z;
}

Code injection

Here is an example in C# of when user input in the GET parameter ‘FilePath’ is unsafely (there’s no sanitization to detect/remove things like “../” , “&&”, or “|”) passed to System.Diagnostics.ProcessStartInfo(), leading to command injection:

public async Task<IActionResult> compressAndDownloadFile()
{
  string filePath = Request.Query["filePath"];
  processStartInfo = new System.Diagnostics.ProcessStartInfo(
    "/bin/bash",
    $"-c \"cat {filePath} | gzip > {filePath}.gz\""
  );
  System.Diagnostics.Process proc = new System.Diagnostics.Process();
  proc.StartInfo = processStartInfo;
  proc.Start();
  
  return File($"{filePath}.gz", "application/gzip");
}

Loose regular expression

Having loose regex in code is a particularly interesting scenario as it could lead to varying vulnerabilities. For example, if you have the code shown below to allow the user to only visit particular subdomains in Azure or any domain of your choice, this validation can be circumvented:

Example 1:

string url_pattern = @".*yourdomain.com"; // Create a Regex
Regex url_check = new Regex(url_pattern);

This allows subdomains of the following patterns, which can lead to the input attacker-supplied prefixes:

  1. “.*azure.com”
  2. “.*.azure.com”
  3. “.*.azure.com”

Here, (1) and (2) can be used to navigate to malicious domains like maliciousazure.com

Additionally, (3) can be used to get to malicious.com/.azure

Example 2:

We sometimes also observe loose regular expressions being abused when they’ve been used in client certificates which are used for Service to Service (S2S) authentication. If the code has a containment check instead of an equality check on the common names in a client certificate, this results in any trusted certificate with the correct prefix being authorized, such as:

Approved Common Name: guest.azure.com

Bypassed Common Name: guest.azure.com.malicious-domain.com

PHP

Of course, Yara isn’t limited to detecting only vulnerable C# code. Below, we’ll see a few examples of vulnerable PHP code snippets.

Let’s say you encounter a code similar to the one below where a POST parameter is used to build a SQL query. There is no sanitization being done, so this is a classic example of SQL injection.

Example 1

public function get_translation(){ 
[] 
    $query = "SELECT id, type, status FROM `" .  $_POST[“language_code”] . []

The vulnerable code can be detected with a rule like the following:

$s1 = /SELECT[^\"]+"\s*\.[^\"]+\$_POST/

This will detect this particular vulnerable code and some other variants, but it’s still very specific. What happens if instead of a POST request, the parameter is sent via GET? Or what if the SQL query is not a SELECT but an INSERT?

We can either add more, similar rules or tweak the one we already developed to handle other cases as well:

$s1 = /\w+\s*=\s*"(SELECT|INSERT|UPDATE)[^\"]+"\s*\.[^\"]+\$_(GET|POST|REQUEST)/

In the second example everything depends on the path variable.

Example 2

$path = $_POST[“filepath”]; 
[] 
$command = "$path xtag 2>&1";  
exec($command, $output, $result);

This is a classic example of command injection as the attacker controls the filepath POST parameter. It’s relatively easy to create a Yara rule to detect this specific case, but when you’re dealing with functions that execute external programs, it’s sometimes better to review them on a case-by-case basis.

For example, if we want to detect PHP functions that execute commands and have a variable as the first parameter:

/(escapeshellcmd|exec|system|passthru|popen)\s*\([^\(,]*\$/

If you find there are too many false positives, you can tweak and add more checks (like see if $_GET, $_POST, or $_REQUEST strings are present and so on).

Example 3 (account takeover)

This detection depends heavily on the implementation. Here we’re looking specifically at a hash function that takes the current time as an argument. Depending on what this is used for, it can lead to an account takeover.

Let’s say that a website has a functionality to reset a user’s password (in case they forgot it). If the attacker knows that a temporary function is generated by using an implementation similar to the example below, then the attacker can generate the password locally, as they already know when they made the request, force a reset for an arbitrary account, and then login with the temporary password.

function reset_password() { 
[] 
$temp_pass          = md5( time() );

Example 4

public function renderNav() 
{ 
    [] 
    echo '<input type="hidden" name="pagename" value="' . $_GET['page'] . '"/>';

This is a classic XSS (cross site scripting) example.

The value of the page GET variable is directly rendered in the HTML code. There is nothing preventing an attacker from injecting arbitrary HTML or JS code (assuming no CORS).

JavaScript

Missing Cross-Origin Resource Sharing Validation

Window.postMessage provides a secure mechanism for cross origin communication which circumvents the Same Origin Policy. The syntax for this consists of:

Window.postMessage(message, targetOrigin)
Window.postMessage(message, targetOrigin, transfer)

However, if we specify the targetOrigin to be “*”, this could expose the data being sent from our website to any malicious site trying to intercept our traffic:

Window.postMessage(message, "*")
Window.postMessage(message, "*", transfer)

Yara rules

Below we’ll present a few Yara rules to detect some of the vulnerabilities we’ve already discussed.

All the rules will follow this basic format:

rule <Rule_Name>
{
      strings:
             $<string_name1> = “<string_to_detect>”
             $<string_name2> = “<another_string_to_detect>”

      condition:
             any of ($< string_name>*)
}

You can easily expand this template by adding comments, metadata, new variable names (you may have to change the condition to reflect those names), etc. There is  very good documentation on how to do that.

Happy hunting!

rule ProcessStart_Method
{
    strings:
        $s1 = "Process.Start"
        $s2 = "ProcessStartInfo"
        $s3 = "UseShellExecute"
        $s4 = ".Shell"
        $s5 = "system("
        $s6 = "ProcessEx"
    condition:
        any of ($s*)
}
rule has_LooseRegex
{
    strings:
        $s1 = ".*"
    condition:
        any of ($s*)
}
rule BinaryFormatter_Deserialization
{
    strings:
        $s1 = "new BinaryFormatter("
        $s2 = "TextFormattingRunProperties"
    condition:
        any of ($s*)
}
rule LosFormatter_Deserialization
{
    strings:
        $s1 = "new LosFormatter("
    condition:
        any of ($s*)
}
rule SoapFormatter_Deserialization
{
    strings:
        $s1 = "new SoapFormatter("
    condition:
        any of ($s*)
}
rule NetDataContractSerializer_Deserialization
{
    strings:
        $s1 = "new NetDataContractSerializer("
    condition:
        any of ($s*)
}
rule ObjectStateFormatter_Deserialization
{
    strings:
        $s1 = "new ObjectStateFormatter("
    condition:
        any of ($s*)
}
rule JsonConvert_Deserialization_newtonsoft
{
    strings:
        $s1 = "JsonConvert.DeserializeObject<Object"
    condition:
        any of ($s*)
}

PHP

rule PHP_SQLinj
{
    strings:
        // match var = "SELECT FROM x WHERE pass=" . $_
        $s1 = /\w+\s*=\s*"(SELECT|INSERT|UPDATE)[^\"]+"\s*\.[^\"]+\$_(GET|POST|REQUEST)/
 
    condition:
        any of ($s*)
}
rule PHP_XSSConcat
{
    strings:
        $s1 = /echo\s[^\r\n]*\.[^\"\r\n\(]*\$_(GET|POST|REQUEST)/
 
    condition:
        any of ($s*)
}
rule PHP_RCE
{
    strings:
        $s1 = /\w+\s*=\s*(escapeshellcmd|exec|system|passthru|popen)\s*\([^\(,]*\$/
        $s2 = /[\r\n]+\s*(escapeshellcmd|exec|system|passthru|popen)\s*\([^\(,]*\$/
 
    condition:
        any of ($s*)
}
rule PHP_Time_Hash
{
    strings:
        $s1 = /(md5|sha\d+)\(\s*time\(\)\s*\);/
    condition:
        any of ($s*)
}
rule PHP_FileInclusion
{
    strings:
        $s1 = /include\([^\)]*\$\_(REQUEST|GET|POST|COOKIE|SERVER|FILES)\[/
 
    condition:
        any of ($s*)
}
rule missingCORS_js
{
strings:
   $s1 = /Window\.postMessage\s*\(\w+,\s*["']+\*["']+\s*[,\)]+/ nocase
condition:
         any of ($s*)
}

Related Posts