TALOS-2025-2193 || Cisco Talos情报组 - 全面威胁情报
漏洞概述
Entr’ouvert Lasso库的lasso_node_impl_init_from_xml功能中存在类型混淆漏洞。特制的SAML响应可导致任意代码执行。攻击者可通过发送畸形SAML响应触发此漏洞。
受影响的版本
以下版本经Talos测试或供应商确认存在漏洞:
- 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*值。
从lasso/sml/xml.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
1495 /* 收集特殊代码片段,如SNIPPET_COLLECT_NAMESPACES、SNIPPET_ANY、SNIPPET_ATTRIBUTE
1496 * 或SNIPPET_SIGNATURE,并反向初始化class_list */
1497 while (class && LASSO_IS_NODE_CLASS(class)) {
...
1503 for (snippet = class->node_data->snippets; snippet && snippet->name; snippet++) {
1504 type = snippet->type & 0xff;
1505
1506 if (snippet->name && snippet->name[0] == '\0' && type ==
1507 SNIPPET_COLLECT_NAMESPACES) {
1508 snippet_collect_namespaces = snippet;
1509 g_type_collect_namespaces = g_type;
1510 } else if (type == SNIPPET_SIGNATURE) {
1511 snippet_signature = snippet;
1512 } else if (type == SNIPPET_ATTRIBUTE && snippet->type & SNIPPET_ANY) {
1513 g_type_any_attribute = g_type;
1514 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
|
1561 for (class_iter = class_list; class_iter; class_iter = class_iter->next) {
1562 class = class_iter->data;
1563 for (snippet = class->node_data->snippets;
1564 snippet && snippet->name; snippet++) {
1565 type = snippet->type & 0xff;
1566 /* 如果属性名称与代码片段名称相同,则分配属性内容,且:
1567 * - 代码片段和属性都没有命名空间
1568 * - 代码片段没有命名空间但属性具有与节点相同的命名空间
1569 * - 代码片段和节点都有命名空间,且相等
1570 */
1573 if (type != SNIPPET_ATTRIBUTE)
1574 continue;
1575 if (! lasso_strisequal((char*)attr->name, (char*)snippet->name))
1576 continue;
1577 if (attr->ns) {
1578 gboolean same_namespace, given_namespace;
1579
1580 same_namespace = lasso_equal_namespace(attr->ns,
1581 xmlnode->ns) && ! snippet->ns_uri;
1582 given_namespace = snippet->ns_uri &&
1583 lasso_strisequal((char*)attr->ns->href,
1584 snippet->ns_uri);
1585 if (! same_namespace && ! given_namespace)
1586 break;
1587 }
1588 snippet_set_value(node, class, snippet, content); // 发生strdup调用的地方
1589 ok = 1;
1590 break;
1591 }
|
之前保存在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
|
1338 static void
1339 snippet_set_value(LassoNode *node, LassoNodeClass *class, struct XmlSnippet *snippet, xmlChar *content) {
1340 void *value;
1341 GType g_type = G_TYPE_FROM_CLASS(class);
1342
1343 /* 如果没有偏移量,意味着由临时init_from_xml处理 */
1344 if (! snippet->offset && ! (snippet->type & SNIPPET_PRIVATE)) {
1345 return;
1346 }
1347 value = SNIPPET_STRUCT_MEMBER_P(node, g_type, snippet);
1348 if (snippet->type & SNIPPET_INTEGER) {
1349 int val = strtol((char*)content, NULL, 10);
1350 if (((val == INT_MIN || val == INT_MAX) && errno == ERANGE)
1351 || errno == EINVAL || val < 0) {
1352 if (snippet->type & SNIPPET_OPTIONAL_NEG) {
1353 val = -1;
1354 } else {
1355 val = 0;
1356 }
1357 }
1358 (*(int*)value) = val;
1359 } else if (snippet->type & SNIPPET_BOOLEAN) {
1360 int val = 0;
1361 if (strcmp((char*)content, "true") == 0) {
1362 val = 1;
1363 } else if (strcmp((char*)content, "1") == 0) {
1364 val = 1;
1365 }
1366 (*(int*)value) = val;
1367 } else {
1369 if (lasso_flag_memory_debug == TRUE) {
1370 fprintf(stderr, " setting prop %s/%s to value %p: %s\n",
1371 G_OBJECT_TYPE_NAME(node), snippet->name, *(void**)value, (char*)content);
1372 }
1373 }
|
然后,代码片段类型在第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
|
1593 if (! ok && attr->ns && snippet_any_attribute) {
1594 GHashTable **any_attribute;
1595 gchar *key;
1596
1597 any_attribute = SNIPPET_STRUCT_MEMBER_P(node, g_type_any_attribute,
1598 snippet_any_attribute);
1599 if (*any_attribute == NULL) {
1600 *any_attribute = g_hash_table_new_full(g_str_hash, g_str_equal,
1601 g_free, g_free);
1602 }
1603 if (lasso_equal_namespace(attr->ns, xmlnode->ns)) {
1604 key = g_strdup((char*)attr->name);
1605 } else {
1606 key = g_strdup_printf("{%s}%s", attr->ns->href, attr->name);
1607 }
1608 g_hash_table_insert(*any_attribute, key, g_strdup((char*)content)); // 崩溃
|
由于上面调用了snippet_set_value,在那里放置了一个指向字符串的指针,它不会为null。因此,在第1608行调用了g_hash_table_insert,使用了strdup结果,它认为这是一个GHashTable*,但这实际上是攻击者控制的字符串。
最终,这导致在攻击者控制的地址上执行调用指令。
1
2
3
4
|
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]
|
攻击者发送精确构造的畸形SAML响应可在g_hash_table_insert操作期间导致类型混淆,最终可能导致远程代码执行。
崩溃信息
1
2
3
4
5
6
7
8
|
==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)
|
时间线
- 2025-05-13 - 初始供应商联系
- 2025-05-14 - 供应商披露
- 2025-08-12 - 供应商补丁发布
- 2025-11-05 - 公开发布
致谢
由Cisco高级安全倡议组的Keane O’Kelley和另一名成员发现