<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>XSS on an's security blog</title><link>https://panman4040.github.io/training/portswigger/xss/</link><description>Recent content in XSS on an's security blog</description><generator>Hugo</generator><language>en</language><lastBuildDate>Wed, 15 Jul 2026 08:36:00 +0700</lastBuildDate><atom:link href="https://panman4040.github.io/training/portswigger/xss/index.xml" rel="self" type="application/rss+xml"/><item><title>Reflected XSS protected by CSP, with CSP bypass | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_expert3/</link><pubDate>Fri, 03 Jul 2026 13:32:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_expert3/</guid><description>&lt;p>Let&amp;rsquo;s try testing the search function. As usual, we insert our canary string and see what sticks:
&lt;img src="https://panman4040.github.io/images/1302189d-c841-40a8-ac6d-3048a11175ac.jpg" alt="">
Unlike previous labs, this lab has CSP enabled, with very strict directives too. But just to be sure, we should test the waters to see if those directives are all bark but no bite. Let&amp;rsquo;s try a simple &lt;code>alert&lt;/code> payload:
&lt;img src="https://panman4040.github.io/images/b594c923-754e-4b85-bf95-e53ef5ff087c.jpg" alt="">
Looks like the parser recognises the payload as valid HTML! But unfortunately the CSP is one step ahead by blocking every inline script instances. The good news for us is that we can assume &lt;em>all tags and events&lt;/em> are not filtered.&lt;/p></description></item><item><title>Reflected XSS protected by very strict CSP, with dangling markup attack | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner16/</link><pubDate>Wed, 01 Jul 2026 10:10:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner16/</guid><description>&lt;p>At first, I was struggling to make use of dangling markups to obtain the CSRF token needed for exfil. What I didn&amp;rsquo;t realise is that Chrome has long banned dangling markup exploitation by essentially blocking certain characters such as angle brackets. So that&amp;rsquo;s bust.&lt;/p>
&lt;p>Coming back to the problem, I first tried tinkering around the comment functionality to see what sticks. However, all the useful characters are encoded, and there are no vulnerable client-side JS vectors to bypass these unlike last labs:
&lt;img src="https://panman4040.github.io/images/297d466e-b485-4c67-917f-de3bf777899a.jpg" alt="">&lt;/p></description></item><item><title>Exploiting cross-site scripting to capture passwords | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner15/</link><pubDate>Wed, 01 Jul 2026 08:52:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner15/</guid><description>&lt;p>I first struggled to solve this because I was leaning towards a solution that does &lt;em>not&lt;/em> require user interaction. Something like a list of autofilled passwords being defined somewhere in the DOM, tried Googling to hell and back but no budge.&lt;/p>
&lt;p>Turns out I had to swallow my pride and come up with a solution that requires interaction. Turns out it&amp;rsquo;s simpler that it sounds. All you have to do is wait for the browser to fill the passwords, detect it using &lt;code>onchange&lt;/code> then grab the credentials.&lt;/p></description></item><item><title>Exploiting XSS to bypass CSRF defenses | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner14/</link><pubDate>Tue, 30 Jun 2026 16:20:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner14/</guid><description>&lt;p>In the spirit of &lt;a href="https://panman4040.github.io/training/portswigger/xss/xss_practitioner13/">this problem&lt;/a>, we can simply set up a similar payload as shown here:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">setTimeout&lt;/span>(&lt;span style="color:#66d9ef">function&lt;/span>(){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">token&lt;/span> &lt;span style="color:#f92672">=&lt;/span> document.&lt;span style="color:#a6e22e">getElementsByName&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;csrf&amp;#39;&lt;/span>)[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#a6e22e">value&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">data&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#a6e22e">FormData&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">data&lt;/span>.&lt;span style="color:#a6e22e">append&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;csrf&amp;#39;&lt;/span>, &lt;span style="color:#a6e22e">token&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">data&lt;/span>.&lt;span style="color:#a6e22e">append&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;email&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;hijacked@test.com&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">fetch&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/my-account/change-email&amp;#39;&lt;/span>, {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">method&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#39;POST&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">body&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#a6e22e">data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}, &lt;span style="color:#ae81ff">100&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Exploiting cross-site scripting to steal cookies | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner13/</link><pubDate>Tue, 30 Jun 2026 14:19:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner13/</guid><description>&lt;p>(dont rlly like this lab because of paywall)
First, to steal cookies we must know the attributes of them. By using the Developer Tool, we can see that our cookie &lt;code>session&lt;/code> does not have &lt;code>HttpOnly&lt;/code> flagged. This means client-side JS can read cookies via &lt;code>document.cookie&lt;/code>, making our job easier.
&lt;img src="https://panman4040.github.io/images/5848ca55-38c1-4ef1-966a-d8581126acf7.jpg" alt="">&lt;/p>
&lt;p>As usual, we try to insert our canary and see what&amp;rsquo;s up:
&lt;img src="https://panman4040.github.io/images/700db3c5-13bb-413e-8d7f-a8b8ad68704b.jpg" alt="">
This is a bit of a logic jump but I tried inserting angled brackets and found out that it wasn&amp;rsquo;t encoded like other labs. I then attempted to insert a simple &lt;code>alert&lt;/code> payload, lo and behold the parser recognises the payload as HTML. We can &lt;em>assume&lt;/em> that there are no encoding filter or WAF blocks, meaning we can build our payloads no problemo.&lt;/p></description></item><item><title>Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner12/</link><pubDate>Tue, 30 Jun 2026 13:48:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner12/</guid><description>&lt;p>(lab&amp;rsquo;s name is a bit of a handful)&lt;/p>
&lt;p>As usual, let&amp;rsquo;s first insert our canary into the search form to see what&amp;rsquo;s the buzz:
&lt;img src="https://panman4040.github.io/images/8b3f77d0-ea39-4bcc-9cd8-58dfb8b7bfaa.jpg" alt="">&lt;/p>
&lt;p>While all our special characters are encoded thus no traditional payloads, notice that the page is trying to build the message dynamically using client-side JS. And &lt;strong>backticks&lt;/strong> are used rather than single or double quotes, meaning we can probably inject JavaScript template literals within the message itself.&lt;/p></description></item><item><title>Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner11/</link><pubDate>Tue, 30 Jun 2026 09:25:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner11/</guid><description>&lt;p>Since the only useful functionality on this lab is commenting, we begin by inserting our canary into the comment form and some special characters to see what sticks. Good practice.
&lt;img src="https://panman4040.github.io/images/63c911eb-5ee7-4b89-b391-d2cd053986e4.jpg" alt="">&lt;/p>
&lt;p>While checking the source, we see that everything is HTML-encoded, even the backslash is escaped. This entry is bust.&lt;/p>
&lt;p>But we haven&amp;rsquo;t checked out how our comment is rendered in Elements yet. Maybe this will lead to something interesting?
&lt;img src="https://panman4040.github.io/images/99f4fe8e-679f-481a-b3d4-a70fd63f621e.jpg" alt="">&lt;/p>
&lt;p>Notice that our controllable website input is reflected inside a JavaScript quoted tag attribute. We can infer that it tracks traffic to our website or something, that doesn&amp;rsquo;t matter. What matters is that we now know there &lt;em>is&lt;/em> a way to break away the existing JS code and insert our payloads.&lt;/p></description></item><item><title>Reflected XSS in a JavaScript URL with some characters blocked | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_expert2/</link><pubDate>Mon, 29 Jun 2026 10:23:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_expert2/</guid><description>&lt;p>Let&amp;rsquo;s try insert our canary string into the comments, with some special characters to boot to see what sticks
&lt;img src="https://panman4040.github.io/images/8a34fb60-4faa-4670-995c-2164092d1121.jpg" alt="">
As reflected in the source, almost everything save for &lt;code>;$%()&lt;/code> are URL encoded, severely limiting our payload capability. Checking the HTML content in Elements also reveal that all our input is treated as &lt;strong>text&lt;/strong>.&lt;/p>
&lt;p>After much tinkering, I realised I have been focusing too much on comments while ignoring the little Back to Blog button at the end of the page. This might be interesting.
&lt;img src="https://panman4040.github.io/images/2a124eb8-cc07-43af-9627-daa5e3163e54.jpg" alt="">&lt;/p></description></item><item><title>Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner10/</link><pubDate>Mon, 29 Jun 2026 09:45:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner10/</guid><description>&lt;p>As usual, we first try to insert our canary string along with special characters to see what sticks:
&lt;img src="https://panman4040.github.io/images/0a5de1db-44bb-4add-910a-a29d0b5b7f1d.jpg" alt="">&lt;/p>
&lt;p>Notice all of the characters are encoded, save for &lt;em>one trailing backslash&lt;/em> at the end. Since single quotes are escaped, we should expect the same for backslashes and see &lt;em>two of them&lt;/em> rather than one. This means backslashes are not escaped, and we can use this to our advantage.&lt;/p>
&lt;p>We can insert our own backslash character before a single quote to neutralize the backslash that is added by the application.&lt;/p></description></item><item><title>Reflected XSS into a JavaScript string with single quote and backslash escaped | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner9/</link><pubDate>Mon, 29 Jun 2026 09:11:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner9/</guid><description>&lt;p>We first try to insert our canary string into the search form on the main page, and view the result in DOM Invader
&lt;img src="https://panman4040.github.io/images/acbc3a70-7aa5-420f-bd42-74cd0a1e85b5.jpg" alt="">&lt;/p>
&lt;p>As we can see, the canary falls into a &lt;code>document.write&lt;/code> sink. Upon closer inspection of the source code, we can discover this JS snippet:
&lt;img src="https://panman4040.github.io/images/07e91fab-3fe6-4d53-a5d9-177a1650366f.jpg" alt="">&lt;/p>
&lt;p>Notice that the single quote is escaped. Double quotes and angle brackets are URL encoded, evident on the query string, before being written into the HTML. But whatever input we type will be assigned to the variable &lt;code>searchTerms&lt;/code> first, without any encoding save for single quotes.&lt;/p></description></item><item><title>Reflected XSS in canonical link tag | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner8/</link><pubDate>Fri, 26 Jun 2026 16:22:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner8/</guid><description>&lt;p>First, we&amp;rsquo;ll need to understand what are &lt;em>canonical tags&lt;/em>. In HTML, it always lives in &lt;code>head&lt;/code> and looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">link&lt;/span> &lt;span style="color:#a6e22e">rel&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;canonical&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">href&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://example.com/main-site&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>example.com&lt;/code> has many endpoints other than the main one, a search engine bot splits up the SEO ranking power, which is what dictates the visibility of a site, across all these URLs. To fix this, developers usually add a canonical tag to all those pages that points back to the main URL, which essentially means telling the search engine bot to give &lt;em>all visibility&lt;/em> to the main site instead.&lt;/p></description></item><item><title>Stored XSS into anchor href attribute with double quotes HTML-encoded | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice6/</link><pubDate>Fri, 26 Jun 2026 15:40:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice6/</guid><description>&lt;p>After tinkering with the comment functionality, notice that the backend will directly takes whatever inside the website input box and attach it to the &lt;code>href&lt;/code> anchor as shown here. One thing to note is that the &lt;code>http:&lt;/code> prepend filter is not present for this lab:
&lt;img src="https://panman4040.github.io/images/7e1d1201-af4b-495d-8be4-f647067eb219.jpg" alt="">&lt;/p>
&lt;p>So we can simply use the payload &lt;code>javascript:alert(1)&lt;/code> to solve the lab. Simple&lt;/p></description></item><item><title>Reflected XSS into attribute with angle brackets HTML-encoded | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice5/</link><pubDate>Fri, 26 Jun 2026 15:13:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice5/</guid><description>&lt;p>As with any, we try inserting our canary into the form to see what comes. In this case we will try searching for &lt;code>ihvkjueb&amp;quot;&amp;lt;&amp;gt;&amp;amp;&lt;/code>
&lt;img src="https://panman4040.github.io/images/6fa9d2f7-ee31-40e8-b661-faadbbfa33bf.jpg" alt="">
Notice that while the angle brackets are encoded, &lt;code>&amp;quot;&lt;/code> isn&amp;rsquo;t. And we can escape the &lt;code>value&lt;/code> attribute and add it this payload:&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;#34; autofocus onfocus=&amp;#39;alert(1)&amp;#39;
&lt;/code>&lt;/pre>&lt;p>This will autofocus the input form right after the page loads, which will trigger &lt;code>alert&lt;/code> calls indefinitely.&lt;/p></description></item><item><title>Reflected XSS with some SVG markup allowed | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner7/</link><pubDate>Fri, 26 Jun 2026 14:19:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner7/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/content/training/portswigger/xss/xss_expert1.md">this problem&lt;/a>, we first try to find out which tags are allowed and which are not using Intruder as follow:
&lt;img src="https://panman4040.github.io/images/0846e732-2482-4b38-9e18-3d6cb61048d1.jpg" alt="">
The tags available are &lt;code>animateTransform&lt;/code>, &lt;code>image&lt;/code>, and &lt;code>svg&lt;/code>. &lt;code>title&lt;/code> is also allowed but it doesn&amp;rsquo;t assist much in delivering payloads so we will skip it for now.&lt;/p>
&lt;p>So we are given three building blocks. One may craft a payload as follow:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#a6e22e">svg&lt;/span>&lt;span style="color:#f92672">&amp;gt;&amp;lt;&lt;/span>&lt;span style="color:#a6e22e">image&lt;/span> &lt;span style="color:#a6e22e">width&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#a6e22e">height&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#a6e22e">onerror&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#a6e22e">alert&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>)&lt;span style="color:#f92672">&amp;gt;&amp;lt;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">/svg&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But it doesn&amp;rsquo;t work. Using &lt;code>animateTransform&lt;/code> to add the event indirectly also does not work. Using other events does not work. Once again, we use Intruder to brute our way to which events are accepted and which aren&amp;rsquo;t:
&lt;img src="https://panman4040.github.io/images/14de7ba5-1454-40a4-bf66-00937ae7bc62.jpg" alt="">
All but one event is blocked, and that is &lt;code>onbegin&lt;/code>. It is essentially &lt;code>onload&lt;/code> but for SVG animation elements. So we can craft a payload as follow:&lt;/p></description></item><item><title>Reflected XSS with event handlers and href attributes blocked | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_expert1/</link><pubDate>Fri, 26 Jun 2026 10:03:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_expert1/</guid><description>&lt;p>Similar to previous problems, first we shall pass through Intruder to see which tags are accepted
&lt;img src="https://panman4040.github.io/images/6c22fc95-c607-4ae9-ab57-bf67430d6620.jpg" alt="">
Some of the valid tags are &lt;code>&amp;lt;a&amp;gt;&lt;/code>, &lt;code>&amp;lt;animate&amp;gt;&lt;/code>, &lt;code>&amp;lt;image&amp;gt;&lt;/code>, and &lt;code>&amp;lt;svg&amp;gt;&lt;/code>. This is a huge flag for an &lt;code>svg&lt;/code> tags ecosystem. Since the &lt;code>href&lt;/code> attribute is blocked by the WAF, perhaps somehow we can sneak that through indirectly without alerting the firewall?&lt;/p>
&lt;p>The answer is using the &lt;code>&amp;lt;animate&amp;gt;&lt;/code> tag, with its convenient &lt;code>attributeName&lt;/code> and &lt;code>values&lt;/code> attributes, it allows us to indirectly modify the parent tag&amp;rsquo;s attributes and their values. Thus we can craft a payload as follow:&lt;/p></description></item><item><title>Reflected XSS into HTML context with all tags blocked except custom ones | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner6/</link><pubDate>Fri, 26 Jun 2026 09:41:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner6/</guid><description>&lt;p>When we launch Intruder to zero in which tags are accepted, all of them returns a 400 which makes common payloads obsolete
&lt;img src="https://panman4040.github.io/images/7e55ad38-7364-4cab-be90-1f6b086bfab9.jpg" alt="">
However, we haven&amp;rsquo;t tested for &lt;strong>custom tags&lt;/strong> yet. Whenever we invent a tag, such as &lt;code>&amp;lt;test&amp;gt;&lt;/code>, the browser treats it as an &lt;code>HTMLUnknownElement&lt;/code>. In the DOM, &lt;code>HTMLUnknownElement&lt;/code> is a direct child of &lt;code>HTMLElement&lt;/code> interface, meaning it inherits &lt;strong>every Global Attribute&lt;/strong> that standard HTML elements (like &lt;code>&amp;lt;div&amp;gt;&lt;/code> or &lt;code>&amp;lt;span&amp;gt;&lt;/code>) have.&lt;/p></description></item><item><title>Reflected XSS into HTML context with most tags and attributes blocked | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner5/</link><pubDate>Thu, 25 Jun 2026 14:59:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner5/</guid><description>&lt;p>If we try a standard XSS payloads using &lt;code>img&lt;/code> or &lt;code>script&lt;/code>, the built in WAF will block the request. We can try other tags manually, or we can use Burp Intruder to first do a sweep of which tags are accepted, and which aren&amp;rsquo;t. You can find a list of common XSS payloads on PortSwigger&amp;rsquo;s XSS Cheat Sheet
&lt;img src="https://panman4040.github.io/images/c71ce16b-f3bc-46b5-8ae4-00b2f9511b7d.jpg" alt="">
Notice the only the &lt;code>body&lt;/code> tag returns a 200. We shall use this information and narrow our attribute range for the tag.
&lt;img src="https://panman4040.github.io/images/fd571378-8707-4aef-be61-878579a44ada.jpg" alt="">
There are a few attributes accepted, notably being &lt;code>onresize&lt;/code>. Without needing user interaction, this is a holy grail among attributes used for XSS payloads. With that said, we can craft a payload as follow:&lt;/p></description></item><item><title>Stored DOM XSS | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner4/</link><pubDate>Thu, 25 Jun 2026 14:30:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner4/</guid><description>&lt;p>Really took a long time with this one due to not knowing enuf JS syntax&lt;/p>
&lt;p>Upon closer inspection of the source, the comment body seems to be added into an &lt;code>innerHTML&lt;/code> sink for display, which is a huge red flag opening up to Stored XSS payloads.
&lt;img src="https://panman4040.github.io/images/a83c7806-d24b-405e-9716-0acb106d9cd6.jpg" alt="">
There is a peculiar custom function &lt;code>escapeHTML()&lt;/code> which does as it says on the tin. It filters out &lt;code>&amp;lt;&lt;/code> and &lt;code>&amp;gt;&lt;/code>, effectively blocking most XSS payloads, at least in theory.
&lt;img src="https://panman4040.github.io/images/f5d66836-c862-4e7b-b27c-1bbd9f609be1.jpg" alt="">
This only encodes the first occurences of those two brackets, which mean our payload can simply be:&lt;/p></description></item><item><title>Reflected DOM XSS | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner3/</link><pubDate>Thu, 25 Jun 2026 09:31:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner3/</guid><description>&lt;p>As per the usual, when we try inserting our canary into the search functionality, the result falls into the &lt;code>eval&lt;/code> sink which is &lt;em>very dangerous&lt;/em>.
&lt;img src="https://panman4040.github.io/images/45abd775-613f-4bd9-b035-cde16bc4a436.jpg" alt="">&lt;/p>
&lt;p>Upon closer inspection of the stack trace, we find out the function that manages sending our search requests. The developer here did not use &lt;code>JSON.parse()&lt;/code> safely but dump the raw response text from the server directly into &lt;code>eval&lt;/code>. This opens up opportunities for XSS.
&lt;img src="https://panman4040.github.io/images/fad6b6e5-025b-4451-924a-7fe7109bb46e.jpg" alt="">&lt;/p>
&lt;p>To exploit this, we should think of a way to escape this JSON string. One might try to append &lt;code>&amp;quot;}&lt;/code>, but &lt;code>&amp;quot;&lt;/code> is safely encoded as &lt;code>\&amp;quot;&lt;/code> so it won&amp;rsquo;t work. However, we can add another backslash, &lt;code>\\&amp;quot;&lt;/code>, the two backslashes in a row will confuse the parser and interpret this as an attempt to print a literal backslash, completely ignoring &lt;code>&amp;quot;&lt;/code>!&lt;/p></description></item><item><title>DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner2/</link><pubDate>Wed, 24 Jun 2026 15:40:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner2/</guid><description>&lt;p>&lt;img src="https://panman4040.github.io/images/41abdac2-8803-4c04-8c78-6a1d785450cf.jpg" alt="">
First, we try to punch the search functionality by inputting our canary string into the search bar. Notice that our string will be written inside the HTML source, which opens up room for XSS payloads.&lt;/p>
&lt;p>There is also an &lt;code>ng-app&lt;/code> attribute inside the &lt;code>body&lt;/code> tag, which means when the webpage loads AngularJS will be in control of whatever inside that tag. When a directive is added to the HTML code, we can execute JavaScript expressions within double curly braces. This technique is useful when angle brackets are being encoded.&lt;/p></description></item><item><title>DOM XSS in jQuery selector sink using a hashchange event | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice4/</link><pubDate>Wed, 24 Jun 2026 15:16:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice4/</guid><description>&lt;p>&lt;img src="https://panman4040.github.io/images/3c818507-97ea-4483-902a-4152a64b5dc2.jpg" alt="">
Once we check the source for our homepage, we see that there is a peculiar JS code snippet. It is a &lt;code>$()&lt;/code> selector sink that picks up whatever inside &lt;code>window.location.hash&lt;/code>, then try to find an &lt;code>h2&lt;/code> element under the &lt;code>blog-list&lt;/code> class containing that value.&lt;/p>
&lt;p>If value is found, then it scrolls down to that. Here in our example is an &lt;code>h2&lt;/code> tag being scrolled down to, notice &lt;code>#Wedding&lt;/code> in the URL. One notable thing is that the backend doesn&amp;rsquo;t filter elements such as &lt;code>&amp;lt;&lt;/code>, &lt;code>&amp;gt;&lt;/code>, or HTML tags such as &lt;code>script&lt;/code>, &lt;code>img&lt;/code>. We can try this by inserting those into the hash and view the response.&lt;/p></description></item><item><title>DOM XSS in jQuery anchor href attribute sink using location.search source | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice3/</link><pubDate>Wed, 24 Jun 2026 09:21:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice3/</guid><description>&lt;p>When we check out the Submit Feedback, notice that there is a &lt;code>returnPath&lt;/code> query string hanging out in the URL.
&lt;img src="https://panman4040.github.io/images/488824d4-a929-4163-8456-4581174e9e13.jpg" alt="">&lt;/p>
&lt;p>If we look closer to the source code, we find out that this &lt;code>returnPath&lt;/code> handles the redirect after clicking on the Back button, evident by its &lt;code>#backLink&lt;/code> ID.&lt;/p>
&lt;p>The JS code naively takes whatever value of &lt;code>returnPath&lt;/code> inside the &lt;code>window.location.search&lt;/code> sink and add that as an attribute of the &lt;code>a&lt;/code> tag. We can then insert this payload inside the URL:&lt;/p></description></item><item><title>DOM XSS in innerHTML sink using source location.search | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice2/</link><pubDate>Tue, 23 Jun 2026 15:56:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice2/</guid><description>&lt;p>We can manually inspect the JS source, or use the DOM invader to help locate our sinks as follow:
&lt;img src="https://panman4040.github.io/images/33b12886-db6b-49c9-a22f-67f35702273d.jpg" alt="">&lt;/p>
&lt;p>Here our sink is &lt;code>innerHTML&lt;/code>, note that the innerHTML sink &lt;strong>doesn&amp;rsquo;t accept&lt;/strong> &lt;code>script&lt;/code> elements on any modern browser, nor will &lt;code>svg onload&lt;/code> events fire. This means we will need to use alternative elements like &lt;code>img&lt;/code> or &lt;code>iframe&lt;/code>. Event handlers such as &lt;code>onload&lt;/code> and &lt;code>onerror&lt;/code> can be used in conjunction with these elements.&lt;/p></description></item><item><title>DOM XSS in document.write sink using source location.search inside a select element | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_practitioner1/</link><pubDate>Tue, 23 Jun 2026 14:57:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_practitioner1/</guid><description>&lt;p>&lt;img src="https://panman4040.github.io/images/a3aa6e42-5a76-45ee-8f10-20e2e44b8582.jpg" alt="">
Upon viewing the source, we see that this script has a &lt;code>document.write()&lt;/code> sink that writes out the select form in the HTML. It queries for the value of &lt;code>storeId&lt;/code> param and attaches it to an &lt;code>&amp;lt;option&amp;gt;&lt;/code> tag as shown.&lt;/p>
&lt;p>Upon attaching a &lt;code>storeId&lt;/code> query param into the URL, the DOM Invader captures our random string present within the HTML, and confirming that the sink in question is indeed &lt;code>document.write()&lt;/code>:
&lt;img src="https://panman4040.github.io/images/b6a5e5a0-24fc-46ea-afff-c4a4674c1a94.jpg" alt="">&lt;/p></description></item><item><title>DOM XSS in document.write sink using source location.search | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/xss/xss_apprentice1/</link><pubDate>Tue, 23 Jun 2026 14:38:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/xss/xss_apprentice1/</guid><description>&lt;p>This is quite a straightforward lab, but I want to emphasize on the use of Burp Browser&amp;rsquo;s DOM Invader.&lt;/p>
&lt;p>&lt;img src="https://panman4040.github.io/images/8f9f7604-672e-475b-b7d3-7a72f6021903.jpg" alt="">&lt;/p>
&lt;p>The &amp;ldquo;canary&amp;rdquo; here is &lt;code>pp591rlt&lt;/code>, which is an unpredictable and uncommon string that we can inject into different sources to see which sinks they flow into. Included in the string are special characters &lt;code>&amp;quot;&lt;/code>, &lt;code>&amp;lt;&lt;/code>, and &lt;code>&amp;gt;&lt;/code>. This helps test whether the browser will sanitize the quotes and brackets or not.&lt;/p></description></item></channel></rss>