TALOS-2025-2193:Entr’ouvert Lasso lasso_node_impl_init_from_xml 类型混淆漏洞
CVE编号:CVE-2025-47151
漏洞概述:在Entr’ouvert Lasso 2.5.1和2.8.2版本的lasso_node_impl_init_from_xml功能中存在类型混淆漏洞。攻击者发送特制的SAML响应可触发此漏洞,导致远程代码执行。
确认受影响的版本:
- Entr’ouvert Lasso 2.5.1
- Entr’ouvert Lasso 2.8.2
产品链接:Lasso - https://lasso.entrouvert.org/
CVSSv3评分:9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE分类:CWE-843 - 使用不兼容类型访问资源(“类型混淆”)
技术细节
Lasso SAML库是安全断言标记语言(SAML)标准的开源实现,主要用于在Web应用程序中实现单点登录(SSO)功能。它提供SAML身份验证、处理断言、元数据解析以及服务提供商(SP)和身份提供商(IdP)交互的工具。
当解析攻击者控制的SAMLResponse时,lasso_node_impl_init_from_xml触发一系列函数调用,导致g_hash_table_insert被调用时使用了攻击者控制的字符串,而不是预期的GHashTable*值。
漏洞代码分析
从lassso/sml/xml.c的第1495行开始:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* Collect special snippets like SNIPPET_COLLECT_NAMESPACES, SNIPPET_ANY, SNIPPET_ATTRIBUTE
* or SNIPPET_SIGNATURE, and initialize class_list in reverse. */
while (class && LASSO_IS_NODE_CLASS(class)) {
...
for (snippet = class->node_data->snippets; snippet && snippet->name; snippet++) {
type = snippet->type & 0xff;
if (snippet->name && snippet->name[0] == '\0' && type ==
SNIPPET_COLLECT_NAMESPACES) {
snippet_collect_namespaces = snippet;
g_type_collect_namespaces = g_type;
} else if (type == SNIPPET_SIGNATURE) {
snippet_signature = snippet;
} else if (type == SNIPPET_ATTRIBUTE && snippet->type & SNIPPET_ANY) {
g_type_any_attribute = g_type;
snippet_any_attribute = snippet; // 这里
|
注意,类型为SNIPPET_ATTRIBUTE并与SNIPPET_ANY掩码的片段在第1514行保存为snippet_any_attribute供后续使用。
稍后,发生类似的循环:
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
|
for (class_iter = class_list; class_iter; class_iter = class_iter->next) {
class = class_iter->data;
for (snippet = class->node_data->snippets;
snippet && snippet->name; snippet++) {
type = snippet->type & 0xff;
/* 如果属性名称与片段名称相同,则分配属性内容:
* - 片段和属性都没有命名空间
* - 片段没有命名空间但属性具有与节点相同的命名空间
* - 片段和节点都有命名空间,且它们相等
*/
if (type != SNIPPET_ATTRIBUTE)
continue;
if (! lasso_strisequal((char*)attr->name, (char*)snippet->name))
continue;
if (attr->ns) {
gboolean same_namespace, given_namespace;
same_namespace = lasso_equal_namespace(attr->ns,
xmlnode->ns) && ! snippet->ns_uri;
given_namespace = snippet->ns_uri &&
lasso_strisequal((char*)attr->ns->href,
snippet->ns_uri);
if (! same_namespace && ! given_namespace)
break;
}
snippet_set_value(node, class, snippet, content); // 发生strdup调用的地方
ok = 1;
break;
}
|
之前在snippet_any_attribute中保存的相同片段被传递给snippet_set_value:
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
|
static void
snippet_set_value(LassoNode *node, LassoNodeClass *class, struct XmlSnippet *snippet, xmlChar *content) {
void *value;
GType g_type = G_TYPE_FROM_CLASS(class);
/* 如果没有偏移量,意味着它由临时的init_from_xml处理 */
if (! snippet->offset && ! (snippet->type & SNIPPET_PRIVATE)) {
return;
}
value = SNIPPET_STRUCT_MEMBER_P(node, g_type, snippet);
if (snippet->type & SNIPPET_INTEGER) {
int val = strtol((char*)content, NULL, 10);
if (((val == INT_MIN || val == INT_MAX) && errno == ERANGE)
|| errno == EINVAL || val < 0) {
if (snippet->type & SNIPPET_OPTIONAL_NEG) {
val = -1;
} else {
val = 0;
}
}
(*(int*)value) = val;
} else if (snippet->type & SNIPPET_BOOLEAN) {
int val = 0;
if (strcmp((char*)content, "true") == 0) {
val = 1;
} else if (strcmp((char*)content, "1") == 0) {
val = 1;
}
(*(int*)value) = val;
} else {
if (lasso_flag_memory_debug == TRUE) {
fprintf(stderr, " setting prop %s/%s to value %p: %s\n",
G_OBJECT_TYPE_NAME(node), snippet->name, *(void**)value, (char*)content);
}
}
|
然后,片段类型在第1367-1372行被当作字符串处理。strdup分配一个包含有效载荷的新缓冲区,指向该缓冲区的指针被写入value的内存地址。
在上述循环完成后,代码检查snippet_any_attribute是否被设置为任何值(第1593行,如下所示)。如果是,则处理该片段。第1597行与上述第1347行是相同的操作,因此any_attribute指向value曾经指向的相同内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
if (! ok && attr->ns && snippet_any_attribute) {
GHashTable **any_attribute;
gchar *key;
any_attribute = SNIPPET_STRUCT_MEMBER_P(node, g_type_any_attribute,
snippet_any_attribute);
if (*any_attribute == NULL) {
*any_attribute = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
}
if (lasso_equal_namespace(attr->ns, xmlnode->ns)) {
key = g_strdup((char*)attr->name);
} else {
key = g_strdup_printf("{%s}%s", attr->ns->href, attr->name);
}
g_hash_table_insert(*any_attribute, key, g_strdup((char*)content)); // 崩溃
|
由于上面调用了snippet_set_value,在那里放置了一个指向字符串的指针,所以它不会为空。结果,第1608行调用了g_hash_table_insert,使用strdup的结果,但这里实际上是攻击者控制的字符串,而不是预期的GHashTable*。
最终,这导致在攻击者控制的地址上进行调用指令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
0x7ffff7ec09fa <g_hash_table_insert+001a> je 0x7ffff7ec0b00 <g_hash_table_insert+288>
0x7ffff7ec0a00 <g_hash_table_insert+0020> mov rbp, rdi
0x7ffff7ec0a03 <g_hash_table_insert+0023> mov rdi, rsi
0x7ffff7ec0a06 <g_hash_table_insert+0026> call QWORD PTR [rbp+0x38]
0x7ffff7ec0a09 <g_hash_table_insert+0029> mov rsi, QWORD PTR [rbp+0x28]
0x7ffff7ec0a0d <g_hash_table_insert+002d> mov r15d, eax
0x7ffff7ec0a10 <g_hash_table_insert+0030> mov eax, 0x2
0x7ffff7ec0a15 <g_hash_table_insert+0035> cmp r15d, eax
0x7ffff7ec0a18 <g_hash_table_insert+0038> cmovb r15d, eax
=====
*[rbp+0x38] (
$rdi = 0x0000555555af6430 → "{http://www.w3.org/2001/XMLScEEEEEEEEEEEEEEEEEEEEE[...]",
$rsi = 0x0000555555af6430 → "{http://www.w3.org/2001/XMLScEEEEEEEEEEEEEEEEEEEEE[...]",
$rdx = 0x0000555555af6a20 → "xs:string"
)
gef➤ x/8xg $rbp+0x38
0x555555af63c8: 0x4141414141414141 0x4141414141414141
0x555555af63d8: 0x4141414141414141 0x4241414141414141
0x555555af63e8: 0x4242424242424242 0x4242424242424242
0x555555af63f8: 0x4242424242424242 0x0000004242424242
|
攻击者发送精心构造的畸形SAML响应可以导致在g_hash_table_insert操作期间发生类型混淆,最终可能导致远程代码执行。
崩溃信息
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
|
==466583== Command: ./repro ./args-files/saml-metadata.xml ./args-files/server.pem ./args-files/idp-metadata.xml ./args-files/pub.pem ./args-files/ca-cert.pem CSCwo73884-type-confusion_pure
(process:466583): Lasso-WARNING **: 22:31:12.113: 2025-05-05 22:31:12 Could not read KeyInfo from signing KeyDescriptor
==466583== Warning: set address range perms: large range [0x7ab4040, 0x198ce340) (undefined)
setting original xmlnode (at 0x792c360) on node LassoSamlp2Response:0x7925640
allocation of LassoSaml2Assertion (for xmlNode 0x792a8f0) : 0x7930340
setting original xmlnode (at 0x7930600) on node LassoSaml2Assertion:0x7930340
setting prop LassoSaml2Assertion/ID to value 0x7932590: ID_03371036-a6cb-48cd-86eb-6792f33e96cd
setting prop LassoSaml2Assertion/Version to value 0x7932710: 2.0
allocation of LassoSaml2AttributeStatement (for xmlNode 0x792b040) : 0x79350b0
allocation of LassoSaml2Attribute (for xmlNode 0x792b280) : 0x7935e40
setting prop LassoSaml2Attribute/Name to value 0x79360f0: Magic
setting prop LassoSaml2Attribute/NameFormat to value 0x79361c0: urn:oasis:names:tc:SAML:2.0:attrname-format:basic
allocation of LassoSaml2AttributeValue (for xmlNode 0x792b850) : 0x7936ec0
setting original xmlnode (at 0x7937120) on node LassoSaml2AttributeValue:0x7936ec0
setting prop LassoSaml2AttributeValue/any_attributes to value 0x7937ce0: CCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB
==466583== Jump to the invalid address stated on the next line
==466583== at 0x4242414141414141: ???
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
==466583== by 0x488AD23: lasso_node_new_from_xmlNode_with_type (xml.c:2492)
==466583== by 0x488AA36: _lasso_node_new_from_xmlNode (xml.c:2417)
==466583== by 0x488AC4E: lasso_node_new_from_xmlNode_with_type (xml.c:2478)
==466583== by 0x4888635: lasso_node_impl_init_from_xml (xml.c:1712)
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
==466583== by 0x488AD23: lasso_node_new_from_xmlNode_with_type (xml.c:2492)
==466583== by 0x488AA36: _lasso_node_new_from_xmlNode (xml.c:2417)
==466583== by 0x488AC4E: lasso_node_new_from_xmlNode_with_type (xml.c:2478)
==466583== by 0x4888635: lasso_node_impl_init_from_xml (xml.c:1712)
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
==466583== Address 0x4242414141414141 is not stack'd, malloc'd or (recently) free'd
==466583==
==466583==
==466583== Process terminating with default action of signal 11 (SIGSEGV)
==466583== Bad permissions for mapped region at address 0x4242414141414141
==466583== at 0x4242414141414141: ???
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
==466583== by 0x488AD23: lasso_node_new_from_xmlNode_with_type (xml.c:2492)
==466583== by 0x488AA36: _lasso_node_new_from_xmlNode (xml.c:2417)
==466583== by 0x488AC4E: lasso_node_new_from_xmlNode_with_type (xml.c:2478)
==466583== by 0x4888635: lasso_node_impl_init_from_xml (xml.c:1712)
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
==466583== by 0x488AD23: lasso_node_new_from_xmlNode_with_type (xml.c:2492)
==466583== by 0x488AA36: _lasso_node_new_from_xmlNode (xml.c:2417)
==466583== by 0x488AC4E: lasso_node_new_from_xmlNode_with_type (xml.c:2478)
==466583== by 0x4888635: lasso_node_impl_init_from_xml (xml.c:1712)
==466583== by 0x4885643: lasso_node_init_from_xml (xml.c:717)
|
时间线
- 2025-05-13 - 首次联系供应商
- 2025-05-14 - 向供应商披露
- 2025-08-12 - 供应商发布补丁
- 2025-11-05 - 公开发布
致谢
由思科高级安全计划组的Keane O’Kelley和其他成员发现。
相关链接
Lasso官方网站:https://lasso.entrouvert.org/
注意:Lasso 2.9.0版本已修复此漏洞,修复内容包括"防止在任意属性内分配属性值"(xml: prevent assignment of attribute value inside any attribute)等安全改进。