Entr'ouvert Lasso SAML库类型混淆漏洞深度解析

本文详细分析了Cisco Talos发现的Entr'ouvert Lasso库中的类型混淆漏洞(CVE-2025-47151)。该漏洞位于lasso_node_impl_init_from_xml函数,攻击者通过特制SAML响应可导致远程代码执行,CVSS评分为9.8分,影响2.5.1和2.8.2版本。

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)等安全改进。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计