Talos Vulnerability Report

TALOS-2025-2193

Entr'ouvert Lasso lasso_node_impl_init_from_xml type confusion vulnerability

November 5, 2025
CVE Number

CVE-2025-47151

SUMMARY

A type confusion vulnerability exists in the lasso_node_impl_init_from_xml functionality of Entr'ouvert Lasso 2.5.1 and 2.8.2. A specially crafted SAML response can lead to an arbitrary code execution. An attacker can send a malformed SAML response to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Entr’ouvert Lasso 2.5.1
Entr’ouvert Lasso 2.8.2

PRODUCT URLS

Lasso - https://lasso.entrouvert.org/

CVSSv3 SCORE

9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-843 - Access of Resource Using Incompatible Type (‘Type Confusion’)

DETAILS

The Lasso SAML Library is an open-source implementation of the Security Assertion Markup Language (SAML) standard, primarily used for enabling single sign-on (SSO) functionality across web applications. It provides tools for SAML authentication, handling assertions, metadata parsing, and service provider (SP) and identity provider (IdP) interactions.

When parsing an attacker-controlled SAMLResponse, lasso_node_impl_init_from_xml triggers a set of function calls that results in g_hash_table_insert being called with an attacker-controlled string as opposed to what is expected to be a GHashTable* value.

From lassso/sml/xmlc.:

1495     /* Collect special snippets like SNIPPET_COLLECT_NAMESPACES, SNIPPET_ANY, SNIPPET_ATTRIBUTE
1496      * or SNIPPET_SIGNATURE, and initialize class_list in reverse. */
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;   // here

Note that the snippet with type SNIPPET_ATTRIBUTE and masking with SNIPPET_ANY is saved off for later in snippet_any_attribute line 1514.

Later, a similar loop happens:

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                 /* assign attribute content if attribute has the same name as the
1567                  * snippet and:
1568                  * - the snippet and the attribute have no namespace
1569                  * - the snippet has no namespace but the attribute has the same
1570                  *   namespace as the node
1571                  * - the snippet and the node have a namespace, which are equal.
1572                  */
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); // call where strdup happens
1589                 ok = 1;
1590                 break;
1591             }

The same snippet that was saved off in snippet_any_attribute above is passed to snippet_set_value.

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     /* If not offset, it means it is handled by an adhoc 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 {    
1368         lasso_assign_string((*(char**)value), (char*)content); // submitter note: compiler optimizes to `g_strdup`
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     }

Then, the snippet type is treated as a string lines 1367- 1372. strdup allocates a new buffer containing the payload, and the pointer to said buffer is written to the memory address for value. Finally, after the above loop finishes, the code checks if snippet_any_attribute is set to anything line 1593 (below).
If so, it processes that snippet. Line 1597 is the same operation as line 1347 above, so any_attribute points to the same memory that value once did.

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)); // crash

Since snippet_set_value was called above, putting a pointer to a string in there, it will not be null. As a result, g_hash_table_insert is called line 1608 with the strdup result with what it thinks is a GHashTable*, but this is actually the attacker-controlled string.

Finally, this results in a call instruction on an attacker-controlled address.

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

An attacker sending a precisely crafted malformed SAML response can cause type confusion during the g_hash_table_insert operation, which might ultimately result in remote code execution.

Crash Information

==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/IssueInstant to value 0x7932660: 2025-03-06T15:25:53.175Z
   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)
TIMELINE

2025-05-13 - Initial Vendor Contact
2025-05-14 - Vendor Disclosure
2025-08-12 - Vendor Patch Release
2025-11-05 - Public Release

Credit

Discovered by Keane O'Kelley of and another member of Cisco Advanced Security Initiative Group