February 3, 2025

Client-Side Web Fundamentals Every Web Security Learner Should Know

From CSR to DOM, CORS to postMessage — this guide covers the essential web concepts you need to master before diving into XSS and other client-side vulnerabilities.

Ahmed Qaramany

Ahmed Qaramany

Featured Image

Web development involves creating websites and web applications. It includes the frontend (what users see) and the backend (how it works). The frontend uses HTML, CSS, and JavaScript, while the backend uses languages like Python, PHP, or Node.js. Web developers build, fix, and improve websites.

  1. Frontend – The part users see and interact with, built using HTML, CSS, and JavaScript.
  2. Backend – The behind-the-scenes part that handles data, user requests, and logic, using languages like Python, PHP, or Node.js.

Difference between Server-Side and Client-Side

Client-Side (Frontend)

  • Runs in the user's browser (e.g., Chrome, Firefox).
  • Uses HTML, CSS, and JavaScript to display content and handle interactions.
  • Does not interact with databases directly.
  • Example: Clicking a button to open a popup without reloading the page.

Server-Side (Backend)

  • Runs on the web server (e.g., Apache, Nginx).
  • Uses languages like Python, PHP, Node.js, or Java to process requests.
  • Handles databases, authentication, and business logic.
  • Example: Logging in to a website, where the server checks your credentials.

In short, Client-Side is what users see, and Server-Side is where the website's logic and data handling happen.

Every piece of code a developer writes can introduce bugs, depending on their prior knowledge. Code written on the client side (such as HTML, CSS, JavaScript, React, Angular, Bootstrap, etc.) can be exploited by attackers, just like server-side code (such as PHP, MySQL, MongoDB, Node.js, etc.) if not properly secured.

If the code is not properly secured, it can lead to bugs or vulnerabilities.

  • A bug is a coding error that causes unintended behavior. For example, a login page mistakenly rejecting correct passwords due to a logic flaw.
  • A vulnerability is a security weakness that attackers can exploit. For example, if a login page doesn’t sanitize inputs properly, an attacker could use SQL Injection to bypass authentication.

In the next parts of this blog, we’ll go over the basics you need to understand Client-Side bugs. It’s important to know how things work before jumping into the vulnerabilities.

For example, you won’t fully understand XSS if you don’t know how the payload is sent, where it goes, whether it’s handled by a client-side or server-side function, or if the DOM processes it.

What happens if there’s a Content Security Policy (CSP) or if the HTTPOnly flag is set? These are key things to learn, and we’ll cover everything you need to study Client-Side vulnerabilities properly.

CSR vs SSR vs SSG

First: CSR (Client-Side Rendering)

Imagine you go to a restaurant, but instead of getting a ready meal, the restaurant gives you all the ingredients and the recipe to cook the food yourself at home. This is like CSR.

In the CSR approach:

  • A user requests a site.
  • The server sends an initial HTML file with JavaScript links.
  • The browser (client-side) JavaScript code fetches data from the server, starts parsing the HTML, and constructs the DOM.
  • The browser downloads the CSS and JS files.
  • The browser executes the JavaScript code.

You're almost there, but let's refine the flow to make it more accurate in the Client-Side Rendering (CSR) approach:

  • User requests a site:
    The user enters a URL or clicks a link, and the browser sends an HTTP request to the server.
  • Server sends an initial HTML file with JavaScript links:
    The server responds with a minimal HTML file. This file usually contains a root <div> (e.g., <div id="root"></div>) and links to the JavaScript bundles.
  • Browser downloads the CSS and JS files:
    As the browser parses the initial HTML, it starts downloading the linked CSS and JavaScript files.
  • Browser executes the JavaScript code:
    Once the JavaScript is downloaded, the browser executes it. This script is responsible for rendering the app in the DOM. Frameworks like React, Angular, or Vue typically handle this process.
  • JavaScript fetches data from the server (API calls):
    After the app initializes, the JavaScript may make API calls to fetch dynamic data (using fetch, axios, etc.).
  • Browser parses the fetched data and constructs the DOM (Virtual DOM in some cases):
    The fetched data is used to update the UI. The JavaScript framework/library constructs and updates the DOM dynamically, often using techniques like the Virtual DOM in React.

This makes it a good fit for applications like chat apps and social media platforms.

Advantages of CSR:

  • Provides a very interactive user experience.
  • Easy to develop and update.
  • Reduces the load on the server.

Disadvantages of CSR:

  • Slow to load the first time.
  • Not good for SEO (making the site appear in search results) because search engines may struggle to index JavaScript-rendered content.
  • Uses a lot of the user’s device resources.

I’m focusing on the client side in this article as much as possible. So, if you’re interested in hunting XSS, you’ll probably find this type of rendering interesting. Why? Because most page updates happen in the browser using JavaScript. This gives more chances to find bugs like DOM-based XSS since JavaScript changes the page content (DOM) a lot.

Here’s an example of the source code from a website that uses CSR.

Belongs to prismic.io

Belongs to prismic.io

Second: SSR (Server-Side Rendering)
Now imagine going to the same restaurant, but this time, the food is ready, hot, and served on a plate. This is like SSR.

In SSR, the server prepares the whole page with all its content and sends it to the browser. So when you request the page, the server sends a full HTML file with everything inside.

  • User requests a site:
    The user types a website link or clicks on one, and the browser sends a request to the server.
  • Server creates the full HTML page:
    The server gets the request, collects any needed data (like fetching data from a database), and builds a full HTML page with all the content already in it.
  • Server sends the complete HTML to the browser:
    The server sends back the ready-made HTML page to the browser. The user can see the content right away.
  • Browser downloads CSS and JavaScript files:
    While showing the page, the browser also downloads any CSS and JavaScript files.

Belongs to prismic.io

SSR application built with Next.js. The HTML file was rendered on the server, so it contains all the web page’s content, HTML elements, and styles.

Advantages of SSR:

  • Faster first-time loading.
  • Better for SEO.
  • Good for websites with content that changes often.

Disadvantages of SSR:

  • Needs hydration (I'll explain this next).
  • More pressure on the server.
  • Interaction with the page might be a bit slower.

In SSR, most of the work is done on the server before the page goes to the browser. This means there are fewer chances to find DOM-based bugs because JavaScript does less in the browser. However, you can still look for server-side bugs.

Third: SSG (Static Site Generation)
Now imagine the restaurant cooks all the meals at once, packs them in boxes, and stores them. When someone orders food, they just give them a ready box. This is like SSG.

In SSG, the pages are built one time during the build process and saved as ready HTML files. Every request gives back the ready file immediately.

Advantages of SSG:

  • Very fast to show content.
  • High security.
  • Cheap to host.

Disadvantages of SSG:

  • Not good for content that changes often.
  • Any change requires rebuilding the whole site.
  • Limited interactivity.

So, to sum it up:

  • CSR is good for very interactive apps, like web games.
  • SSR is good for websites with content that changes a lot, like news sites.
  • SSG is good for static websites, like blogs and company sites.

Caching

Caching is an important concept you will likely encounter. A cache is temporary storage that saves data to help websites load faster when you revisit them. The cache provides identical responses to users who make similar requests.

We'll dive deeper into WAFs later in the blog, but for now, let's assume you've heard of Cloudflare and Akamai and think they only function as WAFs. You might notice they block some of your requests when testing websites configured with them, such as XSS, SQLi, XXE, or SSTI attacks. However, it's important to know that they also serve as CDNs.

A CDN (Content Delivery Network) stores copies of your website's content on servers around the world. When someone visits your site, the CDN delivers the content from the server closest to them. This makes the website load faster and reduces the load on the main server.

Many other companies also offer both CDN and WAF services, including Amazon CloudFront, Microsoft Azure, Google Cloud, Fastly, Imperva, and more.

There are different types of caching systems:

Client-side (local): This means the browser saves some data on the user’s computer, so the website can load faster the next time they visit.

Server-side: This means the server saves the responses to some requests for a short or long time. When many users ask for the same thing, the server can give them the saved response instead of processing the request again. This helps the website run faster because the server doesn’t get too busy.

In simple terms, the cache gives the same response to users who make the same request. These saved (cached) responses are kept for a short time.

But what if a bad (malicious or sensitive) response from the site gets saved in the cache, and then this malicious or sensitive response is shown to users who visit the site afterward?

This could lead to different attacks like Information Disclosure, DoS, XSS, and more.

But how does the cache determine if a request is similar?

This is where Cache Keys come into play. When the cache receives a request, it looks at certain details (like the request line, host, and some headers) and compares them to the ones it has already saved.

You can think of these details—called cache keys—as creating a unique fingerprint for each request. If the cache finds a matching fingerprint, it delivers the saved response. If there’s no match, it sends the request to the web server to get a new response.

Here are examples of common cache keys that caching systems use to decide if a request is the same as one they've already saved:

  • Request URL: The full address of the page or resource being requested (e.g., https://example.com/page).
  • Host Header: Specifies the domain name of the server (e.g., example.com).
  • HTTP Method: The type of request made, like GET, POST, PUT, or DELETE. Usually, only GET requests are cached.
  • Query Parameters: Any additional data in the URL after a question mark (e.g., ?id=123&sort=asc).
  • Cookies: Small pieces of data stored by the browser, which can affect the content shown.

This is a good first step to help you get familiar with Cache Deception and Cache Poisoning bugs.

DOM

The DOM (Document Object Model) is a programming interface for web documents. It represents the structure of a web page as a tree of objects, where each object corresponds to a part of the page, such as elements, attributes, or text.

We talked about CSR (Client-Side Rendering) and mentioned DOM-based vulnerabilities. But how exactly is the DOM connected to CSR?

How the DOM is Connected to CSR:

Dynamic DOM Manipulation:
In CSR, JavaScript frameworks like React, Angular, or Vue.js manipulate the DOM dynamically after the page loads. This means the content you see on the page isn’t coming directly from the server-rendered HTML but is being created or updated in real-time through the DOM API.

This is important because it allows scripts (like JavaScript) to dynamically access and update the content, structure, and style of a document.

The DOM Tree:

The DOM represents HTML as a tree structure with nodes like:

  • Document Node: The root of the tree.
  • Element Nodes: Represent HTML tags.
  • Text Nodes: Represent the text inside elements.
  • Attribute Nodes: Represent attributes of elements.

Example:

html
<html>   
  <body>     
    <h1>Hello World!</h1>   
  </body>
</html>


Manipulating the DOM with JavaScript:

  • Selecting Elements:
    Methods like getElementById, querySelector, getElementsByClassName, etc.
  • Changing Content and Attributes:
    • element.innerHTML, element.textContent
    • element.setAttribute('class', 'new-class')
  • Adding and Removing Elements:
    • appendChild, removeChild, createElement

When talking about DOM-based vulnerabilities, two key concepts you need to understand are Sources and Sinks. These help identify how and where user input can lead to security issues.

Sources are the places where user input enters the application. These are points where data comes into the web page, often from the browser or user actions. For example, an HTTP parameter in the URL is considered a source.

Common Sources in the DOM:

  • location.href
  • location.search
  • location.hash
  • document.URL
  • document.referrer
  • window.name
  • localStorage, sessionStorage, cookies

Example:

const userInput = location.hash; // Source: Getting data from the URL hash

Sinks are the places in the code where the user input is inserted or executed. If the input is not properly sanitized, it can cause harmful effects like executing malicious scripts.

Common Sinks in the DOM:

  • innerHTML
  • outerHTML
  • document.write()
  • eval()
  • setAttribute()
  • innerText / textContent (if misused)
  • window.location (for redirects)

Example:

document.getElementById('output').innerHTML = userInput; // Sink: Inserting data into the DOM

When data flows from a source to a sink without proper validation or sanitization, it can lead to DOM-based XSS or other security issues.

If user input isn’t handled properly when updating the DOM, it can cause attacks like adding harmful JavaScript code to the page. This is a simple example of XSS (Cross-Site Scripting).

We won’t go deep into XSS here since we're focusing on the basics you should know before learning about specific bugs. But it’s important to understand that letting attackers run scripts on a page is dangerous, so we need ways to stop this from happening.

One of the best ways to prevent harmful scripts from running is by using Content Security Policy (CSP).

What is CSP?
CSP is like a set of rules for your website that tells the browser which scripts are allowed to run and which are not. Even if an attacker manages to inject a script, CSP can block it from running.

You add a special header to your website that looks something like this:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-source.com;

This means:

  • Only scripts from your own site ('self') and trusted sources (https://trusted-source.com) are allowed. Any other script, even if injected by an attacker, will be blocked.

It’s important to note that not having a CSP isn’t considered a vulnerability or a bug. It’s more of a security enhancement that helps strengthen your site’s protection.

Inspect Elements and Viewing Source Code

Sometimes, when you try to see the page source code using CTRL + U or by typing view-source:https://target.com in the URL bar, you might not be able to see the full content. Let’s explore why this happens.

This is related to the topic we discussed about SSR and CSR.

In CSR:

  • When you press CTRL + U or use view-source:, you’ll see just a basic HTML skeleton, maybe with a <div id="app"></div>.
  • When you press F12, the DOM will show the fully built page after JavaScript has finished running.

In SSR:

  • When you press CTRL + U or use view-source:, you’ll see most or all of the content because it’s rendered on the server.
  • The DOM will look very similar to the source code, with only minor changes from JavaScript.

Why Can’t You See Everything in the Page Source?

When you use CTRL + U or view-source:, you're only seeing the original HTML sent by the server. However, many modern websites use JavaScript to change or load content dynamically after the page loads. This means:

  • The source code doesn’t show:
    • Content loaded via APIs or AJAX.
    • Dynamic elements generated by JavaScript.
    • Any updates from user interactions.

Why Inspecting the DOM with F12 Helps:

When you press F12 and inspect the page, you're looking at the live DOM, which reflects all the changes made by scripts running on the page. This is useful because it shows the actual structure and content as the user experiences it, including dynamically loaded elements.

Cross-Site Requests

You’ve probably heard of client-side vulnerabilities like CSRF (Cross-Site Request Forgery), CORS (Cross-Origin Resource Sharing), or XSS (Cross-Site Scripting). In this discussion, we'll explore why these issues are referred to as Cross-Site or Cross-Origin and the concepts behind them.

Understanding Origins

Let’s start by looking at how browsers handle requests between different sites. How does this process work? For example, how does Facebook send a request to YouTube? And when you share a YouTube link in a Facebook post, why does a thumbnail appear?

Before anything else, you need to understand the concept of Origin.

In web security, an Origin is defined by three components: the protocol (like http or https), the domain (like example.com), and the port (like :80 or :443). If any of these components are different, the origins are considered different. For example:

  • https://example.com and http://example.com have different origins because the protocol is different.
  • https://example.com and https://sub.example.com have different origins because the domains are different.
  • https://example.com:443 and https://example.com:8080 have different origins because the ports are different.

Now that you understand the concept of Origin, it's important to note that the HTTP Origin request header specifies the origin (including the scheme, hostname, and port) that initiated the request.

The Role of the Same-Origin Policy (SOP)

We need Origin headers and the Same-Origin Policy (SOP) to protect users and websites from security threats like unauthorized data access and malicious attacks.

Same-Origin Policy (SOP):
This is a security measure that restricts how documents or scripts loaded from one origin can interact with resources from another origin. This policy helps prevent malicious websites from accessing sensitive data from other sites you might be logged into.

In short, SOP strengthens the security of websites. Now, let’s go back to our example.

When Facebook fetches data from YouTube to display a thumbnail, it's making a cross-origin request.

In this case, Facebook isn't using your browser to make the request. Instead, Facebook's servers send a request directly to YouTube's servers to fetch the necessary information, like the video title, description, and thumbnail. This process bypasses the browser's Same-Origin Policy because it's happening server-to-server.

Here, YouTube allows Facebook to access its thumbnails, and they are okay with displaying them on Facebook.

If YouTube wanted to restrict who can access their resources, they could:

  • Use authentication to protect content (e.g., requiring API keys).
  • Set restrictive CORS headers for browser-based requests.
  • Check the referrer or origin and deny requests from specific platforms.

What is CORS (Cross-Origin Resource Sharing)?

On the other hand, if a website tries to make a cross-origin request from your browser, it needs permission through something called CORS (Cross-Origin Resource Sharing). If the server allows it, it will send the correct headers to let the browser know it's safe to share the data. If not, the browser will block the request to protect your data.

You might think that CORS is a vulnerability, but it's not. CORS is a feature that allows websites to share resources with trusted origins. As we mentioned with the Same-Origin Policy (SOP), it strengthens the security of a website by restricting access to resources from different origins.

However, CORS can weaken security if it's misconfigured. Why? Because if a website allows access from untrusted or malicious origins, it can expose sensitive data and make the site vulnerable to attacks.

Example of a Misconfigured CORS:

Let’s take an example: if you have safe-company.com, as part of your security workflow, you should prevent other origins from accessing your site by default, following the Same-Origin Policy (SOP) concept. If you allow requests from other origins without proper restrictions, this could lead to data leakage through unauthorized HTTP requests.

If you use the CORS feature but misconfigure it, you might accidentally trust untrusted origins. That’s why it’s important to carefully manage your CORS configurations to avoid potential security risks.

We're not done with Cross-Site Requests yet; we'll connect this to CSRF and CORS misconfigurations in another section of this blog, inshallah.

AJAX Request

AJAX is a way for web pages to send and receive data from a server without reloading the page. It allows you to update parts of a webpage (like showing new content or data) dynamically, making the user experience smoother and faster.

For example, when you click "like" on a post or load new comments without refreshing the whole page, that's AJAX in action.

Fetching Data with AJAX

html
<!DOCTYPE html>
<html>
<head>
    <title>Simple AJAX Example</title>
</head>
<body>
    <h1>Click the Button to Load Data</h1>
    <button id="loadDataBtn">Load Data</button>

    <div id="output"></div> <!-- This is where the data will appear -->

    <script>
        document.getElementById('loadDataBtn').addEventListener('click', function() {
            // Create a new AJAX request
            const xhr = new XMLHttpRequest();

            // Set up the request: GET method, target URL
            xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);

            // What happens when data is successfully loaded
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const data = JSON.parse(xhr.responseText); // Convert JSON to object
                    document.getElementById('output').innerHTML = `
                        <h3>${data.title}</h3>
                        <p>${data.body}</p>
                    `;
                } else {
                    document.getElementById('output').innerHTML = 'Error loading data.';
                }
            };

            // Send the request
            xhr.send();
        });
    </script>
</body>
</html>

What Happens Here?

  • When you click the "Load Data" button, an AJAX request is sent to a free API (https://jsonplaceholder.typicode.com/posts/1).
  • The server responds with JSON data (a post's title and body).
  • The webpage displays the data inside the <div> without reloading the page.

Expected Output (after clicking the button):

sunt aut facere repellat provident occaecati excepturi optio reprehenderit
quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto

This is the data from https://jsonplaceholder.typicode.com/posts/1.

You might be wondering why you didn't get a CORS error when fetching data from https://jsonplaceholder.typicode.com/posts/1. The reason is that the server allows cross-origin requests by setting the correct CORS headers.

When Would You See a CORS Error?

You’d see a CORS error if:

  • The Server Doesn’t Allow Cross-Origin Requests:
    • If the server doesn’t include the Access-Control-Allow-Origin header, the browser will block the request.
  • The Server Allows Only Specific Origins:
    • If a server only allows certain websites to access its data (e.g., Access-Control-Allow-Origin: https://trusted-site.com), and your website isn’t on the list, you’ll get a CORS error.
  • Custom Headers or Methods Trigger a Preflight:
    • If you use methods like PUT, DELETE, or custom headers, the browser sends a preflight request (OPTIONS). If the server doesn’t respond correctly, you’ll get a CORS error.

Example of a CORS Error:

If you try to fetch data from a server that doesn’t allow cross-origin requests, you’ll see this error in your browser’s console:

Access to fetch at 'https://some-restricted-api.com/data' from origin 'https://your-website.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Preflight Request

As we mentioned before, cross-origin requests can be a security risk if not handled properly. Browsers enforce the same-origin policy, which restricts web pages on one origin (A) from making requests to a different origin (B). However, there are times when cross-origin requests are necessary, such as fetching data from APIs hosted on different domains. This is where CORS (Cross-Origin Resource Sharing) comes into play.

A preflight request is an additional HTTP request that a web browser sends to the server before making a cross-origin AJAX request. The purpose of a preflight request is to check with the server whether the actual request (the one that fetches the resource) is safe to send.

When Does a Preflight Request Happen?

When a web browser makes a cross-origin AJAX request, it determines whether the request qualifies as a simple request or a preflighted request.

  • Simple Requests:
    These typically use standard HTTP methods like GET, POST, or HEAD and are limited to specific content types (like application/x-www-form-urlencoded, multipart/form-data, or text/plain). These requests bypass the preflight process and are sent directly to the server.
  • Preflighted Requests:
    If the AJAX request uses methods other than GET, POST, or HEAD, includes custom headers, or uses non-standard content types, the browser automatically sends an HTTP OPTIONS request to the server before the actual request. This is the preflight request.

What’s Included in a Preflight Request?

The preflight request contains extra headers, such as:

  • Origin: Specifies the source of the request (e.g., https://your-website.com).
  • Access-Control-Request-Method: Indicates the HTTP method intended for the actual request (e.g., PUT, DELETE).
  • Access-Control-Request-Headers: Lists any custom headers that will be sent in the actual request (e.g., Authorization, X-Custom-Header).

How the Server Responds to a Preflight Request

When the server receives the preflight request, it needs to respond with the correct CORS headers to indicate whether the actual request is allowed. These usually include:

  • Access-Control-Allow-Origin: Specifies which origin(s) are allowed to access the resource.
  • Access-Control-Allow-Methods: Lists the HTTP methods the server permits (e.g., GET, POST, PUT, DELETE).
  • Access-Control-Allow-Headers: Defines which headers can be included in the actual request (e.g., Content-Type, Authorization).

If the server responds with the correct CORS headers, the browser proceeds to send the actual request. If not, the browser blocks the request and displays a CORS error in the console.

simplelocalize.io

AJAX and XHR, Fetch Request

Let’s start by explaining some definitions, beginning with AJAX. When you open Facebook and search for someone named "Mohamed" or search on Google for "how to ...", you see suggestions appear as you type without the page reloading. This is made possible by AJAX, a technology used to update parts of a web page without refreshing the whole page.

We’ve previously seen an example of an AJAX request, but now we need to dive deeper into XMLHttpRequest (XHR), which is at the core of AJAX requests. It’s the tool that allows web pages to communicate with servers without reloading.

What is XMLHttpRequest (XHR)?

XMLHttpRequest (XHR) is a tool in JavaScript that lets you send HTTP or HTTPS requests to a server and receive data back without reloading the page. Even though its name mentions "XML," XHR can be used to fetch any type of data, not just XML. You can use it to get JSON, HTML, or plain text as well.

How Does AJAX Work?

In short, you should now understand that AJAX is a technique used to update parts of a web page without reloading the entire page. It allows web pages to communicate with servers in the background. But how does it do this?

By using XHR, which is a tool or object in JavaScript that makes AJAX possible. XHR is the method used to send requests to the server and receive responses.

A Simple Analogy

Think of AJAX like making a phone call to get information without leaving your house. In this analogy, XHR is the phone you use to make that call.

What About the Fetch API?

Today, many developers prefer using the Fetch API instead of XHR because it’s simpler and more modern. However, it’s still considered AJAX because the concept is the same: fetching data from a server without reloading the page.

javascript
function loadUser() {
    fetch('user.json') //Get data from `user.json`
        .then(res => res.json())  // Convert response to JSON
        .then(data => { //Show the data on the webpage
            document.getElementById('user').innerHTML = `
                <h3>Name: ${data.name}</h3>
                <p>Email: ${data.email}</p>
                <p>Age: ${data.age}</p>
            `;
        })
        .catch(() => {
            document.getElementById('user').innerHTML = 'Error loading data';
        });
}

postMessage

Since we are talking about the browser and how powerful JavaScript is in its role to send requests between origins, and we mentioned SOP a lot in the blog, we need a way of communication that is secure and safe. Basically, it lets different pages or windows send messages to each other even if they’re from different origins.

postMessage() has built-in safety features:

  • When sending a message, you can specify the target origin (the website you want to send the message to).
javascript
iframe.contentWindow.postMessage('Hello!', 'https://trusted-site.com');
  • When receiving messages, you can check the origin of the message to make sure it came from a trusted source.
javascript
window.addEventListener('message', function(event) {
    if (event.origin === 'https://trusted-site.com') {
        console.log('Safe message:', event.data);
    } else {
        console.log('Blocked message from:', event.origin);
    }
});

I guess now you understand why it can cause some problems, right?
If you don’t check the message’s origin when receiving data, it can lead to security risks like DOM-based XSS.

Let's understand more with a real example:

1. A Page Talking to a Popup It Opened

Imagine you're on a website, and you click "Login with Google". A popup window opens where you log in. Once you're logged in, the popup needs to tell the original page that you're logged in.

  • Without postMessage():
    The main page and the popup can't communicate because they are from different websites (your site and Google's).
  • With postMessage():
    The popup can send a message back to the main page to say, "Hey, the user logged in successfully!"

2. A Page Talking to an Iframe

Let’s say you embed a YouTube video on your blog using an iframe. The YouTube player might need to send messages to your blog, like telling it when the video starts playing or when it’s paused.

  • Without postMessage():
    Your page and the YouTube iframe can’t share data because they come from different origins.
  • With postMessage():
    The YouTube iframe can send messages to your blog page, like "The video has started playing."

There are more examples of this.

While postMessage is designed for secure communication, if not used properly, it can be exploited in DOM-based XSS attacks. This happens when a web page blindly trusts messages received via postMessage without validating the origin or content.

Let's suppose we have an index.html file, which we’ll call the parent page.

javascript
<iframe id="myFrame" src="child.html" style="width:300px; height:200px;"></iframe>

<script>
// Listen for messages from the iframe
window.addEventListener('message', function(event) {
    // No origin check, blindly trusting any incoming message
    document.body.innerHTML = event.data;  // Directly injecting message into the DOM (vulnerable)
});
</script>

attacker create malicious Iframe (`child.html`) we call it child page

javascript
<button onclick="sendMaliciousMessage()">Send Malicious Script</button>

<script>
function sendMaliciousMessage() {
    // Sending malicious script to the parent page
    parent.postMessage('<script>alert("XSS Attack!")<\/script>', '*');
}
</script>

Last Thing


This doesn’t cover all the components you might encounter while learning web security, but I’ve shared some key definitions that are essential for a better understanding. We might create another part to dive deeper. Thanks for reading!

background
Let's hack you before real hackers do

Stay secure with DeepStrike penetration testing services. Reach out for a quote or customized technical proposal today

Contact Us