Problem with products that can be ordered when they should no longer be available


Fraudulent customer is able to order tickets which are not meant to be ordered anymore.
Resource ID 1532 has been unpublished, archived and is crossed-out red in the archive.
I know there are ways to make orders check for the published state. I have just not been able to find any documentation on how/what. This is why I am asking here if anyone can point me in the right direction.

The resource itself is not reachable, or able to be seen by anyone (not even admins). But the ID is used for placing orders by copying a real order URL and replacing the ID with this one.

Step to reproduce

Due to the fact this is an open security issue, I cannot publicly share this link.
This link allows you to order resource 1532, which should no longer be able to be ordered (free return tickets for certain transport services).

Observed behaviour

Able to order non-existing/active product as long as PID(this can be changed as soon as you go to page 2 when your contact info is already known to the website, or directly once your known in the cache) is known to customer even though they should not be able to.
The actual resource itself is not reachable, but still able to be ordered with ID.

Expected behaviour

It should no longer be reachable/being open to being ordered.


MODX 2.6.5, Apache 2.4.51, Browser unknown but I know for a fact I can reproduce with Chrome, MariaDB 10.5.11

Thank you in advance for your time and effort.

Extra information:

With reproducing it (actually buying myself a ticket for free) I found out the exact method he has to be using:

  1. You try and go to the order page of the ID (which somehow is allowed to be reached, but then when you put in the leave and return dates, the check we already have in place kicks in and says ''these dates are not available for this product")

  2. What then? Then you find out the step 2 URL for another product, replace the ID-related bits, and replace these with the ID you want to order. Then you hit enter, and the website allows you into the 2nd stage.

  3. Once there → no checks are in place anymore, and you can order it for free.

  4. 1 drawback (which is suitable for our customer and us but not for the people trying to abuse this) → it does not allow for leave & return date on the ticket given. It gives only one day. This means the ticket is only valid/usable for that one day. Unfortunately, this does not solve the issue for our customer and therefor us.

Seems the check for published/unpublished in step one is not enough. By manipulating the url it still allows this to happen.

In all your information I can’t find what extra/package you use for your shop.
Or is this all custom built?

Custom built/3rd party indeed. There are no extra’s installed on this modx environment.

But the issue is, we have a check in place in the template being used(abused). Based on published status. But I am currently not sure how/where to put an extra check. Hence I am looking for someone who can point me to (or provide) documentation to look for a solution.

I am not looking for a direct answer (aka, here is the code, and there you go), but just a nudge in the right direction so I can come up with one. Although if someone knows exactly how to deal with this I wouldn’t turn it down either haha.

You say that there are multiple steps involved. There seems to be a check on step 1 but no check on step 2.
How many steps are there? Do these steps represent different pages? How is information shared between the steps? Only with GET-parameters in the URL?

Here is some general code that you might use or adapt to your needs.

It assumes that the product is a normal MODx resource and that the product-id is in a GET-parameter “product”

$is_product_valid = 0;
if (isset($_GET['product'])) {
    $product_id = intval($_GET['product']);
    $product_resource = $modx->getObject('modResource', $product_id);
    if ($product_resource) {
        $deleted = $product_resource->get('deleted');
        $published = $product_resource->get('published');
        if ($published && !$deleted){
            $is_product_valid = 1;

return $is_product_valid;

Unpublished pages shouldn’t ever be viewable, unless you’re viewing them from the Manager and have the view_unpublished permission. They should generate a 404. MODX won’t even load the content from the DB.

Maybe you could generate a unique token with PHP’s uniqid() on each page, save it in a current_token member of the $_SESSION array and also pass it to the next page in the $_POST. At the top of the next page, if the two don’t match, forward the user to an error or unauthorized page.

A smart user will be able to see the $_POST value, but they shouldn’t be able to see the $_SESSION variable it needs to match.

Another way to go would be to store a hash of the product ID in the $_SESSION array and check it on the final page against the ID of the product being ordered. If you want to be really careful, you could use two $_SESSION variables and do both.

Hi Bobray,

It is not viewable. But as the PID (Resource ID) is also the ID linked to the product, as long as the page exists somewhere in MODX (in the current setting) in whatever form (deleted, archived or otherwise) it seems to still be able to be ordered.

Hoping I will get permission today to simply delete the entire resource, but that will only be a temporary fix until he figures out the next one.

Also I went to talk privately with Harry yesterday, and I found some more code in the background relating to this issue. Turned out we have the check for the unpublished/published for step 1. But there are extra things in place using tokens for step 1 & 2 at least. But still by manipulating the URL he can look like a valid customer (receiving a token for a different product up until step 2) but then changing the PID in the URL once verified. Then when he presses to go to step 3 or 4 (depending on the fact it was registered/seen as a free product or not) the person pressing the button will be forwarded to the thanking page immediately skipping the paying part because the system sees him as 1. Validated customer, 2. Customer ordering something for free. 3. It doesn’t notice a PID or price change when on step 2 and then moving on.

I think something can be won by figuring out what is in place now (between 2 & 3 and 2 & 4) and then if not there yet, indeed perhaps try adding another session token.

Also if anything of what I am saying is not making any sense, do ask for clarification. I am still too new at this too match myself with professionals like yourselves (Harry & Bobray).

In any case, thank you for spitballing ideas at me. Very much appreciated, it gives me ideas on how to fix this.

If you’ve deleted this resource, go to the trash manager (click the trash icon) and purge it completely to remove all traces of it.

It sounds like your scripting is not checking for the published and deleted status of resources when it fetches info from them. It would be worth tracking that down and changing it.

Yeah we got permission today to do this from the customer. But that is only a temporary solution.