I wanted to write something up for people specifically looking to know how deep someone can dive into your app. Let’s poke around the https://bubble.io app to find out. This isn’t a post to attack Bubble, but to educate people as to what’s actually shared by Bubble to the end user as it isn’t really talked about in most developer circles, and whether you like it or not, this is how it is so be aware of it. This list isn’t exhaustive (you can also see plugins, the Swagger file is enabled by default etc) but some cool stuff to find here and any additional insights in the comments are appreciated.
Meta
Every app has a Meta exposed. You can find it at https://bubble.io/api/1.1/meta (replacing bubble.io with your own domain or the yourapp.bubbleapps.io domain).
GET
This is a list of all data types exposed by your Data API. These are protected by privacy rules. If you do not have a privacy rule configured on a data type, you can simply visit https://yourdomain.com/api/1.1/obj/datatypename
in your browser to view all of the data available. Data API exposed + no privacy rules is the death combination. Data types will only show up here if the data API is enabled for them in the API settings of your app.
POST
These are your backend workflow endpoints that have ‘Expose as a public API workflow’ checked. It details the parameters the endpoint takes, and whether authentication is necessary. If you check ‘This workflow can be run without authentication’, then this will reflect in the meta page as “auth_unecessary”: true
. Let me repeat that - anyone can visit your meta page, Ctrl+F “auth_unecessary”: true
and be shown all of your unauthenticated backend workflows. Anyone can call an unauthenticated backend workflow without restriction. Heck, all of Bubble’s Stripe workflows are accessible without authentication (that doesn’t necessarily make them insecure - see this thread). But this public information gives attackers information to play with if you don’t secure them appropriately.
Takeaways
- Do not leave endpoints unauthenticated unless you have another method within the endpoint that verifies the integrity of the request it is receiving
- Avoid exposing the Data API on types you don’t need, so that if you make a mistake, it’s harder for someone to find out (this isn’t sufficient security but is a reasonable approach to minimise risk)
- Configure privacy rules on all data types that you don’t want to be publicly viewable (duh).
Database
Your database structure is public, no matter how you set it up. It can be derived from console.log(app). The actual data (not the structure) can be protected by privacy rules. You can of course work this out from the editor, but @rico.trevisan made a tool to generate a DBML file which can be used to generate a detailed database diagram for your app. That discussion is over here.
If you just want a brief look, here’s an expanded view of Bubble’s ‘negotiated price’ data type. This seems to be their Enterprise price tracking method. So, we can tell that DB things has (or had) an influence on price, and whatever the hell MUDV is. I have a hunch this data type is pre-pricing changes though, so maybe stuff’s changed. Whilst hardly critical, it goes to show you can see a fair bit about how a tool works and work out some useful stuff from that.
Takeaways
- All field names can be seen publicly
- All data relationships can be seen
- Knowing the structure isn’t inherently insecure but can make it easier for an attacker to identify areas to try exploiting
Option Sets
All option sets are public, no matter how you set them up. All option sets are loaded on page load, even if they are not used. It can be derived from console.log(app).
Hey, it looks like Bubble is testing out different free trial lengths:
Aww, here’s Bubble telling us everything we can’t build:
Takeaways
- Don’t store sensitive data in option sets.
- Option sets are loaded on page load. Very few uses cases will fall into this category but don’t store oodles and oodles of data on them as it might slow down the page load.
API Connector
All API calls are public. Data within API calls can be protected. Anything that’s not a parameter with ‘private’ checked can be worked out by an attacker. In addition, the API responses when you initialise API calls are public, which can sometimes include sensitive data. @gaimed at Coalias developed a convenient tool to check through this all yourself which will show you more than I could here. Here’s some of what we can see in Bubble’s API (emails that were used during initialisation, for example).
App Texts
I’d hope most people knew this already, but any app text is publicly visible. Not much else interesting to share on that front.
Takeaways
- Don’t store sensitive data in app texts.
- Don’t store inappropriate texts in app texts that were intended to be development placeholders…
Pages
Every page is visible (even if it’s not ‘accessible’ or a sitemap isn’t expose). You can’t just make an unsecured page called admin13097193805715 and hope nobody finds it because you don’t expose the sitemap. Bubble sure has a lot of them!
Any additional contributions or clarifications are welcome
If you’re interested, I’m doing audits of individual Bubble apps here. I’ve done 20+ audits now and almost all have at least one critical security issue (leaking database data) and major issues (exploitable API endpoints/logic etc). If I don’t find one critical or major security issue, I’ll refund your audit cost! All of the information needed to keep the information shared above secure is in the post, but if you want a second pair of eyes feel free to get in touch
EDIT: Now I’m thinking about it, if you take ONE thing away from this post it’s that obfuscation / assuming a user won’t be able to wind something is not effective at all. Hopefully that was already well known among the community but this might help people understand why…