DEV Community 👩‍💻👨‍💻

Cover image for Hacking BeReal - A practical lesson on “Man in the Middle” attacks
Oscar
Oscar

Posted on • Originally published at blog.oscars.dev

Hacking BeReal - A practical lesson on “Man in the Middle” attacks

What is BeReal?

BeReal is a new social media app which is said to be more “in the moment” compared to conventional social media platforms. The premise of the app is that once per day, everyone gets a notification (at the same time) saying “It’s time to Be Real!” Everyone then has two minutes to take a picture of what they’re doing at that moment. The phone simultaneously snaps a front and back camera photo, before uploading it to their feed for all (friends) to see. There is no option to choose existing photos to upload or make edits to photos. You must post as you are, in the moment.

I wanted to see how much I could manipulate BeReal with the use of a software called Mitmproxy between my iPhone and my Macbook. This software allows me to see all unencrypted HTTPS requests made between my phone and the internet. With this tool, I have the ability to view, pause, edit and cancel any requests at my will. This software also has a Python API for writing custom scripts which I will touch on later.

Notable domains

I spent some time using the app and learning which endpoints were associated with which functions. With this information, I am able to build a profile of how this app works. I knew already that BeReal was built on top of Firebase but there seem to be an additional endpoint to handle certain requests.

  • firebasestorage.googleapis.com - Firebase cloud storage, used to upload files (images).
  • us-central1-alexisbarreyat-bereal.cloudfunctions.net - Firebase cloud function endpoint, used to create posts, delete posts, set post captions, send reactions, etc.
  • mobile.bereal.com - Secondary endpoint - Used to get posts feed, view friends, add friends.

Hijacking authentication tokens

The requests made from the phone are authenticated using a (JSON Web Token) JWT which is sent in the “Authorization” header of each request. this header expires after a few minutes, however until then, I can use this token to do whatever I like as an authenticated user.

BeReal’s most commonly fetched endpoint is mobile.bereal.com/api/feeds/friends which gets the list of friends’ posts on a user’s feed. If I call this endpoint from Postman without the authorization header, I instantly get a 403 - Forbidden status code which is expected, as I have no credentials to say who I am.

On the Mitmproxy feed, I can just copy and paste the “Authorization” JWT included in any of the BeReal API requests and use that for myself. Upon including this header in Postman, I am allowed through the security and I get greeted with a nice JSON of all of my friends’ posts with links to all of their Images, location data, how many retakes they took, etc.

Image description

I can use this same approach to set the caption of my BeReal post as many times as I like. Normally, you are only allowed to set it one time and then it is permanent; however, by making a POST request to the /setCaptionPost endpoint I can bypass this rule and set my caption over and over again at my leisure.

Image description

Custom RealMoji

To show appreciation in BeReal, instead of sending a “Like”, you send a “RealMoji” which is an image of your face as a reaction. This adds a level of personality to a reaction which is normally missed. The fact that you can retake RealMojis as much as you like makes it a good opportunity to insert custom images as reactions as we are not only limited to one per day, unlike the BeReal post. Let’s start by looking at what is happening here when we send a RealMoji.

A POST request is made to the firebase storage endpoint which initiates a file upload request.

Image description

A PUT request is made to firebase, using the upload ID returned from the previous request to upload the image. The actual image data is sent in the body of this request.

Image description

A final POST request is sent to a cloud function with details of the reaction to be sent (Image storage location, reaction type, etc). Atotal of three requests are necessary to send a RealMoji reaction.

Image description
Image description

Manipulating expected content size

Without fully understanding the workings of Firebase file uploads, I started by simply modifying the data of the PUT request, as that is when the image content is sent to the server.

I set a filter in Mitmproxy to pause the request as it comes through and then modified the body of the request to contain the data of my chosen Jpeg

Image description

Image description

I was then met with a 400 status error, stating that the size of the content did not match what it was expecting. Hmm…

Image description

I searched for this number 45243 and found it defined on the prior request. For my second attempt, I also modified this POST request in order to match the content size of the file to be uploaded.

Image description

After doing this, I received a 200 status on both requests. Success. My manipulated payload has been uploaded! I then refreshed the app to see my “FakeMoji” in all its glory.

Image description

Custom BeReal Post

The flow of uploading a BeReal and sending a RealMoji is mostly identical. After working out how to manipulate the firebase upload, creating a custom post was basically no different.

Image description

Automating

Mitmproxy also has a Python API which is great for manipulating HTTP requests automatically. Based on the process above, I wrote a script which automates the process of intercepting the request and replacing the data.

import json
import os
import mitmproxy.http

BASE_PATH = os.path.dirname(os.path.abspath(__file__))
FAKEMOJI = os.path.join(BASE_PATH, 'fakemoji.jpg')

def read_content(file_path):
       with open(file_path, 'rb') as f:
            data = f.read()
            f.close()
            return data   

class Interceptor:
    def __init__(self):
        self.upload_type = None

    # http request trigger
    def request(self, flow: mitmproxy.http.HTTPFlow):

        method  = flow.request.method
        url     = flow.request.url

        # Firebase upload domain
        if ("firebasestorage.googleapis.com" in url):

            # handle upload request (POST)
            if method == "POST":
                # content body
                json_body = json.loads(flow.request.content)

                # record upload type (necessary for following PUT request)
                self.upload_type = json_body["metadata"]["type"]

                # replace content length definition
                flow.request.headers["x-goog-upload-content-length"] = os.stat(FAKEMOJI).st_size

            # handle sending file content (PUT)
            elif method == "PUT":

                # only replace image if realmoji upload
                if (self.upload_type == "realmoji"):

                    # replace file content
                    flow.request.content = read_content(FAKEMOJI)

addons = [
    Interceptor()
]
Enter fullscreen mode Exit fullscreen mode

The script listens out for Firebase requests with a metadata type of “RealMoji” and then intercepts and manipulates them accordingly. The expected content size for the upload is altered and the content of the PUT request is automatically replaced with the data of a local file: fakemoji.jpg.

How to protect yourself from a MITM attack

If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests.

In reality, performing this man-in-the-middle attack on someone else’s phone maliciously would be easier said than done. To get it working for me required configuration of the iPhone’s proxy settings as well as approval of certain device certificates which would not be practical for an attacker to do remotely; however, with a compromised device, something like this would be more than possible.

If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests. Obviously, this can’t be said for all companies but it can be hard to tell how “big brother” they are and how much of a grasp they have on your data and internet usage.

Use a VPN.

In order to prevent such attacks, you can try to protect your physical device from malicious fingers and for an added layer of security, use a VPN! (Shameless referral link here. Get 30 days free!). Yes, VPNs are not perfect, and 90% of the time they are advertised to the general public incorrectly but for this instance, they are effective at masking your actions from prying network eyes.

When I turn my VPN on and then send a RealMoji again, nothing is detected by Mitmproxy. I would include a screenshot to example but there is literally nothing to see. The requests which were shown before simply do not appear, as if the phone is not doing anything at all.

Closing thoughts

This fun experiment shows how you can manipulate your own device HTTP traffic and see what requests apps are making behind the curtain. It is just another proof of how you can never trust the front end, even from a native app. As a developer, in order to have real security in place, you must install effective security measures on your back end to prevent misuse; otherwise, your production app is not much better than a sandbox environment!

Top comments (20)

Collapse
 
xmdb profile image
Matt • Edited on

Learnt a lot, thank you :) I used mitmproxy to try this out for myself, however it seems like it complains about it already being finalised..?
Image description
This is the request:
Image description
Is it trying to cancel the upload?
Keep in mind that the "upload command" for the requests that succeeded was to upload and finalize.
Image description
Do you have any ideas? :D

Collapse
 
ozcap profile image
Oscar Author

Awesome! That looks to me like you are trying to resend a request which has already been sent. You need to make sure you intercept it before it has a chance to reach the server. You can do this by adding the domain to the "intercept" input.
Image description
Once it is paused you can edit the payload and press the green play button to execute the request. Let me know how you get on!

Collapse
 
xmdb profile image
Matt

That would be the app trying to retry. You can see in the image below that the first PUT request succeeds but then it tries again and fails with HTTP 400 "Upload has already been finalized" as seen in my first comment.

Image description

Thread Thread
 
ozcap profile image
Oscar Author

But if you add the domain into the intercept filter then both of those requests should have been paused. I noticed that the app seems to try every request twice. You only need to let one of them through after editing.

Thread Thread
 
xmdb profile image
Matt

That way doesn't work for me. It keeps re-requesting a new upload (POST)

Thread Thread
 
ozcap profile image
Oscar Author

But either way those requests should also be paused, no?

Thread Thread
 
xmdb profile image
Matt

Yes they are paused but the photo does not go through on the client and it is probably expecting different upload IDs

Thread Thread
 
xmdb profile image
Matt

Image description
Here you can see the modified PUT request returns a success but the client requests to start another 3 uploads, 2 of them being the one just returned as a success.

Thread Thread
 
ozcap profile image
Oscar Author

That PUT request looks ok to me actually. Looks like a decent amount of data was sent and it was a 200 status. If you close and open the app now, is your custom RealMoji there?

Thread Thread
 
xmdb profile image
Matt

Nope. I’m trying to send a BeReal photo though, but you said it acts relatively the same. If I close and open the app, it just tries to upload again.

Thread Thread
 
ozcap profile image
Oscar Author

Ah but if you post a normal BeReal then you upload two pictures (front and back) so the app is making two consecutive requests followed by a bereal API request to say where the photos are stored. Have a play around with sending a RealMoji and see if you can get that to work!

Thread Thread
 
xmdb profile image
Matt

Ah yeah, a RealMoji worked with the script. However both manually and automatically I can't upload a fake normal BeReal. What am I getting wrong?

Collapse
 
leob profile image
leob

Okay, so:

"This software allows me to see all unencrypted HTTPS requests made between my phone and the internet"

So this means that BeReal is NOT consistently using HTTPS/SSL for everything it does, and if so, why not? As it's one of the most basic 'rules' out there ...

And if they did, wouldn't that make MITM right away impossible?

Or am I completely missing something here?

Collapse
 
leob profile image
leob

P.S. but I suppose that that's exactly what the MITM proxy (which obviously needs to be installed in the phone itself) does, somehow: insert itself into the "network stack" BEFORE the beginning of the HTTPS/SSL endpoint? Obviously the BeReal devs do use HTTPS for their stuff ... cool article!

Collapse
 
ozcap profile image
Oscar Author

That is correct, the proxying of the connection via mitmproxy results in the HTTPS connection only being established from the intercepting computer!

Collapse
 
polterguy profile image
Thomas Hansen

I think your question is irrelevant, since it's the ability to "fake" their API invocations. As long as you can create something that mimics the client, you can pretend to be a client, while you're really not. Since the BL is implemented mostly on the client for simplicity reasons, this implies you can bypass their business logic, right ...?

For instance, if we imagine something similar for Facebook ...

  1. Create new Facebook post
  2. Intersect the invocation
  3. Change the "isAd" flag to true
  4. Change the "paidBudget" to 1 trillion dollars
  5. You're now running a trillion dollar marketing campaign on Facebook for free

With stuff that allows you to dynamically build CRUD queries on the client, such as Firebase, GraphQL, and PostgREST, the above becomes a legitimate concern, and although I realise it is possible to fix and secure, by using "schemas" or "resolvers", and in addition adding "trigger functions" (or whatever) - At this point you've lost most of the simplicity these things gives you, or ...?

Am I wrong here or ...?

@oscar ...?

Collapse
 
leob profile image
leob • Edited on

Yes my question was based on a misunderstanding ... and we agree that if an app has its business logic on the client, it's vulnerable to these kinds of attacks and to abuse - the reason why BeReal gets away with it is because nobody's going to jump through hoops and put in that much effort to fake their actions in the app, it's just too trivial, so the incentive isn't really there :)

Thread Thread
 
polterguy profile image
Thomas Hansen

Thx mate. Just checking. I'm not always correct, so it helps to confirm my assumptions every now and then 😊

Collapse
 
jackmcbride98 profile image
Jack McBride

Awesome post! Very interesting. I did not know it was possible to intercept and modify http requests, great to know :D

Collapse
 
leob profile image
leob

So am I right that a BeReal user could (if they wanted) install this proxy on their phone themselves and then go on and manipulate how the app works for them? Just goes to show how fragile and unsafe it is to put "business" logic in the frontend ... but well, I'm sure that the makers of BeReal don't really care, they're probably laughing all the way to the bank :)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠