Why all of your server-side redirects are insecure

The Common Misconception

Many developers rely on server-side redirects as a primary security measure for protecting sensitive pages like admin panels. The conventional wisdom suggests that since server-side redirects happen before page load, users can’t access the protected content. However, this assumption is incorrect.

Why all server-side redirects are bypassable

How Server-Side Redirects Actually Work In Bubble

  1. When a page is requested, Bubble typically checks redirect conditions before sending page data
  2. However, the page data itself (including elements and workflows) can also be accessed directly
  3. Malicious users can bypass the redirect mechanism by:
    • Downloading the page data directly
    • Rendering the content locally
    • Preventing redirect execution in their browser

Real-World Attack Scenario

Consider a marketplace application with an admin panel containing a user management table. Even with server-side redirects in place, an attacker could:

  1. Bypass the redirect to load the admin page
  2. Access the user table’s underlying functionality
  3. Execute administrative actions like:
    • Sending emails through your application
    • Deleting user accounts
    • Accessing sensitive user data

The attack succeeds because many developers assume the page itself is inaccessible, leading them to omit crucial workflow-level security checks.

Security Best Practices

1. Use privacy rules on non-public data always, without exception

  • Implement privacy rules as your primary security mechanism
  • Never rely solely on server-side redirects
  • Add security checks at every level of your application
  • This alone is insufficient, as just because someone can see a Thing (e.g a User) doesn’t mean they should be able to take actions on it (e.g deleting it).
  • Do not forget ‘is not empty’ conditions to protect orphaned data
  • Test them to ensure they work as expected (NQU Secure can help with this, details at bottom of thread)

2. Make elements invisible by default on sensitive pages

  • Set page elements to hidden by default
  • Only reveal elements after confirming user permissions
  • Remember that hidden elements can still be exposed via JavaScript but it at least makes it a little harder

3. Add conditions to workflow events

  • Add explicit conditions to ALL workflow events that you wouldn’t want everyone to run
    Example: Current User's Role is Admin
    
  • Apply conditions even on “protected” pages
  • These should not reference page elements (e.g, you should reference Current User's Role, not var - user role, as var - user role can be changed.

4. Static data in the front-end (inc. workflows) is always accessible

  • Never store sensitive data (API keys, credentials) in page elements
  • Assume all static content that’s not in the backend is potentially accessible
  • Move sensitive operations to backend workflows

The Bigger Picture

The security landscape for server-side redirects isn’t as straightforward as many developers assume. Very little documentation exists about these vulnerabilities (though @petter updated the manual today :slight_smile: ), which has logically led to widespread misconceptions about best practices in the Bubble community. While Bubble could theoretically check permissions for every page element, this would significantly slow down page loading times – hence their choice to prioritize performance.

Auditing your apps

I’m building the most advanced security tool for Bubble - and it’s already reached that level. I’m going to be implementing checks for this security issue. You can sign up @ https://secure.notquiteunicorns.xyz for free point-in-time audits that detail issues that existing tools can’t find, and manual reviews miss. Almost every app has at least one critical issue.

We also have the only privacy rule debugger that exists for Bubble, which allows you to test privacy rules for any user type, not just logged out users.

Search and explore the data accessible to that user. No Data API or collaborator access needed.

22 Likes

Very nice, thanks for the write-up. I now have more work to do to fix some apps.

That’s when I know that Bubble is a real programming languages: it gives you powerful foot-guns.

4 Likes

Crikey. Checking the user role on every admin action …. Yikes. That’s going to create a flurry of activity. :face_with_spiral_eyes:

3 Likes

Lets say a user edits his profile page, does attackers access that workflow too ?

An attacker can likely edit a profile page that is not theirs if you have no conditions on workflows, and their privacy rules permit them to access that user.

The WUs will probably be affected?

1 Like

Sharing Petter’s original LinkedIn post about this for extra info:
https://www.linkedin.com/posts/petteramlie_some-discussions-in-the-bubble-forum-and-activity-7268583997373366272-uHjd

7 Likes

Can attacker access to that workflow without seeing the button that triggers it ? I don’t get it. Lets say it is a marketplace like Amazon, a firm listed its product. Can an attacker act like the owner of the product and change the workflow

Assume that an attacker can run any workflow, unless there are conditions on the workflow that restrict it (e.g Current User’s Role is Admin).

So an attacker, comes in to my website and triggers all the workflows, costing millions of WU. How can they attack?

Well, the idea is that they won’t trigger the workflows because you’ll have conditions on your workflows that stop them running and are limited to the WU cost of the conditions themselves. But WU is the smallest problem that results from this issue :wink:

3 Likes

Absolute legend @georgecollier. This is unreal.

I was helping someone work through the challenge of a condition based redirect for not signed in users yesterday, concerned with even half a second of visibility before it redirected. Revealing elements after confirming user permissions is always a great shout.

Same for storing of sensitive data - massively overlooked with payment and card elements and not enough is spoken about in terms of the liability of a product owner when it comes to this stuff.

1 Like

I updated my dashboard thanks to you, but I don’t get attackers, can they be tracked ? Do they login ? or not login as a best practice or how often they do them. Can an attacker access other peoples sessions? You can give me a link or topic name I will search the behaviour of them. Anything helps

I was building out some custom script the other day and I started to wonder about code executed in a run JavaScript vs code vs html element vs server script element… do you about gore secure each of these are or are not. Can attackers change script?
Additional to this I have a code editor I built in the project so I access the code by doing a search for code_blocks code. I’m starting to think maybe I need to privacy rule this up and only do the search from a backend workflow?

For themselves, not for others.

Any ‘run javascript’ action in the front-end is manipulateable. They could edit the script itself, or just the results.

A server script initiated on the front-end can also be modified (the content in ‘Node script’ is client-side and can be modified by the user).

You could do that, just make sure you don’t run into this issue re. backend workflows at the same time :slight_smile:

Here’s the Bubble Manual page on Page Security: Page Security

Has a lot of other details.

1 Like

It might suffice to just put them onto the triggers and not every action.

The link @ihsanzainal84 provided is very helpful. The manual would seem to imply, in order to make the most use of the conditions on the triggers, to use ‘user is logged in’ as the beginning of the expression to force a server side evaluation.

I’d imagine just using 'current user is logged in and current user role is X' on the trigger and not every action will have the effect of securing the entire series of actions associated with the trigger, forcing a server side conditional evaluation to make it even more secure, and only costs 0.02 WUs as an individual data request, likely for the current user portion of the conditional (WU logs do not go into detail about the individual data request so I can not confirm this was for the conditional evaluation or if it is just standard first load of current user that I believe takes place when a page is loaded.)

Actually, when doing a simple test, I have a trigger with conditions and a single action to create a new thing.

First time running this, by pressing the single button on the page, I got the following WU usage.


To test if the individual data request came from the conditional evaluation on the trigger, in the form of an individual data request to fetch the current user, I added another button to page with a simple condition of current user role is x.

In that test, I did not press the button with the trigger. On that test, simply got a page load WU and no individual data request charge.

When I ran the test a third time, with the two buttons, and pressing the button with trigger, I got same result as first time, which was page is loaded and individual data request. Now, since the second test still had a condition to evaluate if the current user role is X on the button, my assumption was that the conditional evaluation of current user role is X would have incurred an individual data request, but it did not.

So, I ran fourth test, which was same as third (ie: two buttons, click one with trigger), however, I removed the action to create a blog post (which didn’t run in first test because of the condition on the trigger). And of course, this fourth test uncovered something that is strange to me in how WUs are charged.

In the fourth test, with a trigger with conditionals on a button click, but no action to create a thing, and pressing the button, there was no individual data request charge, only page load charge. This seems to be a bug of some sort in that we get charged for an individual data request when an action to create a thing doesn’t run because the condition on the trigger evaluates to false.

3 Likes

In this case, could it not have been charged because the button click was not even triggered as it did not contain any action in the event?

I thought about that. The reason I’m thinking that would have been cause for an individual data request charge, is that I do not think the action to create a thing would cause an individual data request charge even when the button was clicked since the button click trigger has the condition that evaluates to false and therefore the create a thing doesn’t run.

I just did another test, where instead of create a thing, I did show an alert, and still have the condition on the trigger that evaluates to false. In that test, the alert doesn’t show as expected and the WU charges are just for page load, so no individual data request. So, I assume the individual data request is for the create a thing, that doesn’t run and the thing doesn’t exist.

Any ideas on what might be the cause if it is not either a bug or a charge that Bubble doesn’t list that I might be overlooking?

1 Like

Current User is loaded every time the page is loaded so the data is already available and not charged again.

1 Like