使用Yara规则挖掘软件漏洞
引言
在软件中寻找漏洞本身并非易事。在云规模下手动执行此操作非常具有挑战性,因此我们使用工具来帮助识别模式或漏洞签名。Yara就是其中之一。
Yara在蓝队、恶意软件研究人员中非常流行,原因充分。一旦识别出恶意模式,编写规则以在大量文件集合中检测它就相当容易。
在本文中,我们将讨论Yara的另一种使用方式,从AppSec/红队角度出发。我们将研究如何创建规则来匹配不同类型的软件漏洞。示例包括可导致任意代码执行的反序列化漏洞、命令注入漏洞,甚至可能被绕过并导致SSRF的松散正则表达式,仅举几例。这并非详尽列表。
示例
C#
假设遇到类似以下代码:
1
2
3
4
5
6
7
8
9
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 ;
}
任何使用BinaryFormatter处理任意/不可信数据(如上述示例)都是不安全的。无法使其安全,因为其Deserialize()方法将首先检查文件流中提供的类型,然后进行任何转换。精心构造的负载将利用此功能执行恶意代码。与其他格式化程序不同,这无法通过实现SerializationBinder来缓解。.NET认为此行为是按设计进行的,不会发布修改它的缓解措施。
NewtonSoft.Json
下一个漏洞针对使用Newtonsoft JSON库反序列化不可信JSON负载。当TypeNameHandling设置为“Objects”或“All”时,这允许JSON.NET库检查所提供数据中的对象类型:
1
2
3
4
5
6
private static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling . All ;
// 注意,如果使用Objects而不是All,这也有效。
TypeNameHandling = TypeNameHandling . Objects ;
};
如果攻击者能够修改序列化数据,则这可能允许添加恶意对象类型,用于执行任意命令。如果此设置设置为“None”,或根本未指定(在这种情况下将默认为“None”),则意味着下面示例中的DeserializeObject函数不检查作为输入提供的字符串类型(下面示例中的“json”),这确保没有作为用户输入提供的不安全类型被反序列化。
示例1:
1
2
3
4
5
6
7
static string GetClientEmail ()
{
string json = GetData ();
var acobj = JsonConvert . DeserializeObject < Object >( json , jsonSerializerSettings );
Account account = ( Account ) acobj ;
return account . Email ;
}
示例2:
1
2
3
4
5
6
7
static string GetClientEmail ()
{
string json = GetData ();
var acobj = ( Account ) JsonConvert . DeserializeObject < Object >( json , jsonSerializerSettings );
Account account = ( Account ) acobj ;
return account . Email ;
}
上述两个示例都尝试将对象转换为我们的自定义类型“Account”:然而,这不成功,因为恶意负载在此之前已经执行。这是因为每当我们有泛型类型如“var”或时,JsonConvert函数会查看负载字符串并尝试猜测类型,然后尝试反序列化它。当反序列化某些对象(如此示例中的泛型对象)时,它们的构造函数首先被调用,这可能包含恶意可执行文件。
示例3:
1
2
3
4
5
6
static string GetClientEmail ()
{
string json = GetData ();
Account acobj = ( Account ) JsonConvert . DeserializeObject < Account >( json , jsonSerializerSettings );
return acobj . Email ;
}
如果我们的类型“Account”有任何公共泛型对象,或包含任何可能反过来包含泛型对象的类,上述代码仍然可能容易受到不安全反序列化的攻击:
1
2
3
4
5
6
7
8
9
10
11
12
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 ;
}
代码注入
以下是C#中的示例,其中GET参数“FilePath”中的用户输入不安全地(没有检测/删除“../”、“&&”或“|”等内容的清理)传递给System.Diagnostics.ProcessStartInfo(),导致命令注入:
1
2
3
4
5
6
7
8
9
10
11
12
13
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" );
}
松散正则表达式
代码中存在松散正则表达式是一个特别有趣的场景,因为它可能导致各种漏洞。例如,如果您有下面显示的代码以允许用户仅访问Azure中的特定子域或您选择的任何域,此验证可以被绕过:
示例1:
1
2
string url_pattern = @".*yourdomain.com" ; // 创建正则表达式
Regex url_check = new Regex ( url_pattern );
这允许以下模式的子域,这可能导致输入攻击者提供的前缀:
“.azure.com”
“. .azure.com”
“.*.azure.com”
这里,(1)和(2)可用于导航到恶意域,如maliciousazure.com
此外,(3)可用于到达malicious.com/.azure
示例2:
我们有时还观察到松散正则表达式在用于服务到服务(S2S)认证的客户端证书中被滥用。如果代码对客户端证书中的公用名进行包含检查而不是相等检查,这导致任何具有正确前缀的可信证书被授权,例如:
批准的公用名:guest.azure.com
绕过的公用名:guest.azure.com.malicious-domain.com
PHP
当然,Yara不仅限于检测易受攻击的C#代码。下面,我们将看到一些易受攻击的PHP代码片段示例。
假设您遇到类似以下代码,其中POST参数用于构建SQL查询。没有进行清理,因此这是SQL注入的经典示例。
示例1
1
2
3
public function get_translation (){
[ … ]
$query = "SELECT id, type, status FROM `" . $_POST [ “language_code” ] . [ … ]
易受攻击的代码可以使用如下规则检测:
1
$s1 = /SELECT[^\"]+"\s*\.[^\"]+\$_POST/
这将检测此特定易受攻击的代码和一些其他变体,但仍然非常具体。如果参数通过GET发送而不是POST请求怎么办?或者如果SQL查询不是SELECT而是INSERT怎么办?
我们可以添加更多类似规则,或调整我们已经开发的规则以处理其他情况:
1
$s1 = /\w+\s*=\s*"(SELECT|INSERT|UPDATE)[^\"]+"\s*\.[^\"]+\$_(GET|POST|REQUEST)/
在第二个示例中,一切都取决于路径变量。
示例2
1
2
3
4
$path = $_POST [ “filepath” ];
[ … ]
$command = " $path xtag 2>&1" ;
exec ( $command , $output , $result );
这是命令注入的经典示例,因为攻击者控制filepath POST参数。创建Yara规则来检测此特定情况相对容易,但当您处理执行外部程序的函数时,有时最好逐个审查它们。
例如,如果我们想检测执行命令的PHP函数并将变量作为第一个参数:
1
/(escapeshellcmd|exec|system|passthru|popen)\s*\([^\(,]*\$/
如果您发现误报太多,可以调整并添加更多检查(例如查看是否存在$_GET、$_POST或$_REQUEST字符串等)。
示例3(账户接管)
此检测严重依赖于实现。这里我们特别关注以当前时间作为参数的哈希函数。根据其用途,这可能导致账户接管。
假设网站具有重置用户密码的功能(以防他们忘记)。如果攻击者知道临时函数是通过使用类似于下面示例的实现生成的,那么攻击者可以在本地生成密码,因为他们已经知道何时发出请求,强制重置任意账户,然后使用临时密码登录。
1
2
3
function reset_password () {
[ … ]
$temp_pass = md5 ( time () );
示例4
1
2
3
4
public function renderNav ()
{
[ … ]
echo '<input type="hidden" name="pagename" value="' . $_GET [ 'page' ] . '"/>' ;
这是经典的XSS(跨站脚本)示例。
页面GET变量的值直接呈现在HTML代码中。没有任何东西阻止攻击者注入任意HTML或JS代码(假设没有CORS)。
JavaScript
缺失跨源资源共享验证
Window.postMessage提供了一种安全的跨源通信机制,绕过了同源策略。其语法包括:
1
2
Window . postMessage ( message , targetOrigin )
Window . postMessage ( message , targetOrigin , transfer )
但是,如果我们将targetOrigin指定为“*”,这可能将我们从网站发送的数据暴露给任何试图拦截我们流量的恶意网站:
1
2
Window . postMessage ( message , "*" )
Window . postMessage ( message , "*" , transfer )
Yara规则
下面我们将展示一些Yara规则来检测我们已经讨论过的一些漏洞。
所有规则将遵循此基本格式:
1
2
3
4
5
6
7
8
9
rule <Rule_Name>
{
strings:
$<string_name1> = “<string_to_detect>”
$<string_name2> = “<another_string_to_detect>”
condition:
any of ($< string_name>*)
}
您可以通过添加注释、元数据、新变量名(您可能必须更改条件以反映这些名称)等轻松扩展此模板。有非常好的文档说明如何做到这一点。
狩猎愉快!
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
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
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
rule PHP_SQLinj
{
strings :
// 匹配 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 * )
}