CVE-2025-47151
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.
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
Lasso - https://lasso.entrouvert.org/
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-843 - Access of Resource Using Incompatible Type (‘Type Confusion’)
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.
==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)
2025-05-13 - Initial Vendor Contact
2025-05-14 - Vendor Disclosure
2025-08-12 - Vendor Patch Release
2025-11-05 - Public Release
Discovered by Keane O'Kelley of and another member of Cisco Advanced Security Initiative Group