<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Santos Gallegos (Posts about security)</title><link>https://stsewd.dev/</link><description></description><atom:link href="https://stsewd.dev/categories/security.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:stsewd@proton.me"&gt;Santos Gallegos&lt;/a&gt; </copyright><lastBuildDate>Tue, 19 May 2026 07:03:31 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Finding security-related commits on GitHub</title><link>https://stsewd.dev/posts/github-search-security-commits/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;Did you know you can easily search for commits that fix security issues on GitHub?
Just search for commits with "Merge commit from fork" or "Merge pull request from GHSA".&lt;/p&gt;
&lt;p&gt;Why this works?
When there is a security issue, some projects use &lt;a class="reference external" href="https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability"&gt;GitHub Advisories&lt;/a&gt; to manage them.
GitHub allows you to create private pull requests to fix the issue in private.
However, when the pull request is merged,
GitHub strips the original commit message and replaces it with "Merge commit from fork", this can't be changed when using the GitHub UI.
In the past, it used to include a link to the advisory, so you can also search for "Merge pull request from GHSA", which is less generic.&lt;/p&gt;
&lt;p&gt;How can this be useful?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If you're a nerd like me who enjoys reading code and learning about security issues, you can find interesting commits to read. Some big projects appear in the results ;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you're a government-backed hacker looking for 0-days, you can monitor commits with these messages to find security issues that are not yet disclosed. Please don't...&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try it:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Search for &lt;a class="reference external" href="https://github.com/search?q=%22Merge+commit+from+fork%22&amp;amp;type=commits"&gt;Merge commit from fork&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search for &lt;a class="reference external" href="https://github.com/search?q=%22Merge+pull+request+from+GHSA%22&amp;amp;type=commits"&gt;Merge pull request from GHSA&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: Did you find a commit from me? Don't worry, those are already fixed and disclosed issues ;)&lt;/p&gt;
&lt;p&gt;If you are paranoid about people monitoring your project for commits like this,
you can merge the private pull request via the command line to preserve your original commit message.
Hopefully GitHub will allow customizing the commit message in the future from the UI.
This is the workflow I use when handling fixes from a security advisory:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Create a private fork from the advisory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the fork as a remote: &lt;code class="docutils literal"&gt;git remote add &lt;span class="pre"&gt;security-{advisory-id}&lt;/span&gt; &lt;span class="pre"&gt;&amp;lt;advisory-fork-url&amp;gt;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new branch: &lt;code class="docutils literal"&gt;git checkout &lt;span class="pre"&gt;-b&lt;/span&gt; &lt;span class="pre"&gt;fix-security-issue-{advisory-id}&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push the branch to the advisory fork: &lt;code class="docutils literal"&gt;git push &lt;span class="pre"&gt;-u&lt;/span&gt; &lt;span class="pre"&gt;security-{advisory-id}&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a pull request in the advisory fork.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the fix is ready to be merged, do it via the command line:
&lt;code class="docutils literal"&gt;git checkout main &amp;amp;&amp;amp; git merge &lt;span class="pre"&gt;security-{advisory-id}&lt;/span&gt; &amp;amp;&amp;amp; git push origin main&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's all, hope you found this useful!&lt;/p&gt;</description><category>github</category><category>security</category><guid>https://stsewd.dev/posts/github-search-security-commits/</guid><pubDate>Thu, 13 Nov 2025 05:00:00 GMT</pubDate></item><item><title>Exploiting a bad implementation of OAuth2</title><link>https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;In this post I'm going to share how I exploited a bad implementation of OAuth2 to take over user accounts with a single click.&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This vulnerability was reported in a private bug bounty program,
so I won't disclose the name of the company or details that could lead to its identification.&lt;/p&gt;
&lt;/aside&gt;
&lt;nav class="contents local" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#oauth2" id="toc-entry-1"&gt;OAuth2&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#the-state-parameter" id="toc-entry-2"&gt;The state parameter&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#the-vulnerability" id="toc-entry-3"&gt;The vulnerability&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#from-login-csrf-to-account-takeover" id="toc-entry-4"&gt;From login CSRF to account takeover&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#exploitation" id="toc-entry-5"&gt;Exploitation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#mitigation" id="toc-entry-6"&gt;Mitigation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/#timeline" id="toc-entry-7"&gt;Timeline&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="oauth2"&gt;
&lt;h2&gt;OAuth2&lt;/h2&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;I won't explain OAuth2 in detail, as there are plenty of resources online that do a better job than I could :)&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Whenever you see a log in button that says "Log in with Google" or "Log in with Facebook", that's OAuth2 in action.
A common OAuth2 flow looks like this:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;User goes to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://example.com&lt;/span&gt;&lt;/code&gt; and clicks "Log in with Google".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user is redirected to Google's login/authorization page (&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://accounts.google.com/o/oauth2/auth?client_id=1234&amp;amp;scope=email&amp;amp;state=4321&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user logs in and authorizes the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google redirects the user back to the application (callback URL) with an authorization code and state parameter (&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://example.com/login/callback?code=5678&amp;amp;state=4321&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application exchanges the authorization code for an access token (usually done server-side).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application uses the access token to access the user's resources (profile, email, etc).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="the-state-parameter"&gt;
&lt;h3&gt;The state parameter&lt;/h3&gt;
&lt;p&gt;When the user is redirected to the OAuth2 provider (e.g., Google),
the application can include a state parameter in the URL (as you can see in step 2 above),
which is then returned by the OAuth2 provider in the callback URL (as you can see in step 4 above).&lt;/p&gt;
&lt;p&gt;What's the point of returning the same value in the callback URL? you might ask.
The state parameter is used to prevent &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;CSRF&lt;/a&gt; attacks,
in other words, it ensures that the user who initiated the OAuth2 flow is the same user who completed it,
preventing an attacker from tricking a user into logging in with a different account
(&lt;a class="reference external" href="https://support.detectify.com/support/solutions/articles/48001048951-login-csrf"&gt;login CSRF&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;In order to do that, the application must verify that the state parameter in the callback URL matches the one it sent.
The state should be unique for each OAuth2 flow, hard to guess, and bound to the user that initiated the flow,
as the &lt;a class="reference external" href="https://www.rfc-editor.org/rfc/rfc6749#section-10.12"&gt;OAuth2 RFC&lt;/a&gt; states.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="the-vulnerability"&gt;
&lt;h2&gt;The vulnerability&lt;/h2&gt;
&lt;p&gt;The site was generating a random state value for each OAuth2 flow, and correctly verifying it later in the callback URL.
But there was a small problem, the state wasn't bound to the user that initiated the flow.&lt;/p&gt;
&lt;p&gt;The OAuth2 flow looked like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;User starts the OAuth2 flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application generates a random value and stores it in the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application redirects the user to the OAuth2 provider with the state parameter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The OAuth2 provider redirects the user back to the application with the state parameter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application retrieves the state parameter from the URL and checks that the value
exists in the database but doesn't check which user it belongs to.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows for an attacker to start an OAuth2 flow and then trick the victim into completing it,
leading to a login CSRF attack.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="from-login-csrf-to-account-takeover"&gt;
&lt;h2&gt;From login CSRF to account takeover&lt;/h2&gt;
&lt;p&gt;The bad implementation of the state parameter allows for a login CSRF attack,
but depending on the application, this type of attack might not be very impactful.
But the application had the following behavior:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If the user isn't logged in and starts the OAuth process with a new provider,
a new account is created and linked to that provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the user is logged in and starts the OAuth process with a new provider,
that provider is linked to the existing account.
Allowing the user to log in with any of the linked providers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the user, logged in or not, starts the OAuth process with a provider already linked to another account,
the application logs the user out, and logs him into the account linked to that provider.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If an attacker tricks the victim into completing the OAuth2 flow from another provider while logged in,
the attacker can link the victim's account to its own.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="exploitation"&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Let's say the application allows users to log in with Google and Facebook,
and the victim is logged in with Facebook.
The attacker starts the OAuth2 flow with Google but doesn't complete it;
instead, it intercepts the response. The attack would look like this:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;The attacker goes to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://example.com&lt;/span&gt;&lt;/code&gt; and clicks "Log in with Google".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The attacker is redirected to Google's login/authorization page
with a state parameter generated by the application (e.g., &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://accounts.google.com/o/oauth2/auth?client_id=1234&amp;amp;scope=email&amp;amp;state=c3VwZXItc2VjcmV0&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The attacker logs in and authorizes the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google redirects the attacker back to the application with an authorization code.
But the attacker intercepts the response and doesn't let the browser follow the redirect.
The intercepted redirect would look like this: &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;https://example.com/login/callback?code=12345&amp;amp;state=c3VwZXItc2VjcmV0&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since the redirect wasn't followed, the application didn't create a new account linked to Google,
but it kept track of the generated state in the database, waiting to be validated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;While logged in with his Facebook account, the victim follows the attacker's link.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application retrieves the state parameter from the URL and checks that the value exists in the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then it uses the code from the URL to exchange it for an access token of the attacker's Google account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since the Google account doesn't exist in the application and the victim is already logged in,
the application will link the victim's account to the attacker's Google account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The attacker can now log in with his Google account and gain access to the victim's account.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;If the user is logged out, the attacker can also exploit this vulnerability,
but it requires more interaction from the user.&lt;/p&gt;
&lt;p&gt;This would be done by first exploiting the login CSRF vulnerability to log the user into the attacker's Google account,
and then tricking the user into linking a new provider to the attacker's account (e.g., Facebook).
The exact steps are left as an exercise to the reader :)&lt;/p&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="mitigation"&gt;
&lt;h2&gt;Mitigation&lt;/h2&gt;
&lt;p&gt;To prevent this,
the application should bind the state parameter to the user who initiated the OAuth2 flow.
This can be done by storing the state parameter in the user's session instead of a separate database.
This way, the intercepted state from one user wouldn't be valid for another user.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="timeline"&gt;
&lt;h2&gt;Timeline&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;12/02/2024&lt;/strong&gt;: Reported the vulnerability to the company.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;13/02/2024&lt;/strong&gt;: The company acknowledged the report.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;28/02/2024&lt;/strong&gt;: The company asked for clarification on how the vulnerability could be exploited.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;01/03/2024&lt;/strong&gt;: The company confirmed the vulnerability and awarded a bounty of $1000.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;13/03/2024&lt;/strong&gt;: The vulnerability was fixed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</description><category>authentication</category><category>oauth2</category><category>security</category><guid>https://stsewd.dev/posts/exploiting-a-bad-implementation-of-oauth2/</guid><pubDate>Thu, 20 Feb 2025 05:00:00 GMT</pubDate></item><item><title>XSS in django-impersonate 1.9.3 and django-gravatar2 1.4.4</title><link>https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;This post details two cross-site scripting (XSS) vulnerabilities I discovered in &lt;a class="reference external" href="https://pypi.org/project/django-impersonate/"&gt;django-impersonate&lt;/a&gt;,
and &lt;a class="reference external" href="https://pypi.org/project/django-gravatar2/"&gt;django-gravatar2&lt;/a&gt;.
I'm writing about them together because they share the same vulnerability,
and are similar in other aspects that I'll explain below.&lt;/p&gt;
&lt;nav class="contents local" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#background" id="toc-entry-1"&gt;Background&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#django-impersonate" id="toc-entry-2"&gt;django-impersonate&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#the-vulnerability" id="toc-entry-3"&gt;The vulnerability&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#exploitation" id="toc-entry-4"&gt;Exploitation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#proof-of-concept" id="toc-entry-5"&gt;Proof of concept&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#mitigation" id="toc-entry-6"&gt;Mitigation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#timeline" id="toc-entry-7"&gt;Timeline&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#django-gravatar2" id="toc-entry-8"&gt;django-gravatar2&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#the-vulnerability-1" id="toc-entry-9"&gt;The vulnerability&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#exploitation-1" id="toc-entry-10"&gt;Exploitation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#proof-of-concept-1" id="toc-entry-11"&gt;Proof of concept&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#mitigation-1" id="toc-entry-12"&gt;Mitigation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#timeline-1" id="toc-entry-13"&gt;Timeline&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#more-in-common-than-you-think" id="toc-entry-14"&gt;More in common than you think&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#acknowledgements" id="toc-entry-15"&gt;Acknowledgements&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;At &lt;a class="reference external" href="https://about.readthedocs.com/"&gt;Read the Docs&lt;/a&gt;, we use both packages.
While waiting for my PRs to be reviewed, and taking a break from coding,
I decided to do a quick security audit of some of our dependencies,
since both packages have a relatively small codebase,
they were good candidates for a quick review.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="django-impersonate"&gt;
&lt;h2&gt;django-impersonate&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pypi.org/project/django-impersonate/"&gt;django-impersonate&lt;/a&gt; allows you to impersonate other users,
really useful for debugging and support.&lt;/p&gt;
&lt;section id="the-vulnerability"&gt;
&lt;h3&gt;The vulnerability&lt;/h3&gt;
&lt;p&gt;After grepping the codebase for common vulnerable patterns,
I found &lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/browse/impersonate/helpers.py?rev=fa5d1a703960#L28"&gt;this line of code&lt;/a&gt; that caught my attention:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-1" name="rest_code_badb05188725403eb99ba48fd1310240-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_redir_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-2" name="rest_code_badb05188725403eb99ba48fd1310240-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;redirect_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REDIRECT_FIELD_NAME&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-3" name="rest_code_badb05188725403eb99ba48fd1310240-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redirect_field_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-4" name="rest_code_badb05188725403eb99ba48fd1310240-4" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-4"&gt;&lt;/a&gt;        &lt;span class="n"&gt;nextval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redirect_field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-5" name="rest_code_badb05188725403eb99ba48fd1310240-5" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-5"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-6" name="rest_code_badb05188725403eb99ba48fd1310240-6" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-6"&gt;&lt;/a&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mark_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-7" name="rest_code_badb05188725403eb99ba48fd1310240-7" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-7"&gt;&lt;/a&gt;               &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;input type="hidden" name="&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s1"&gt;" value="&lt;/span&gt;&lt;span class="si"&gt;{1}&lt;/span&gt;&lt;span class="s1"&gt;"/&amp;gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-8" name="rest_code_badb05188725403eb99ba48fd1310240-8" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-8"&gt;&lt;/a&gt;                  &lt;span class="n"&gt;redirect_field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-9" name="rest_code_badb05188725403eb99ba48fd1310240-9" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-9"&gt;&lt;/a&gt;               &lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-10" name="rest_code_badb05188725403eb99ba48fd1310240-10" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-10"&gt;&lt;/a&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_badb05188725403eb99ba48fd1310240-11" name="rest_code_badb05188725403eb99ba48fd1310240-11" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_badb05188725403eb99ba48fd1310240-11"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can see that the application is building an HTML input field with the value of a query parameter (if the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;IMPERSONATE["REDIRECT_FIELD_NAME"]&lt;/span&gt;&lt;/code&gt; setting is defined),
and marking it as safe with &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.safestring.mark_safe"&gt;mark_safe&lt;/a&gt;
(Django won't escape it when including it in a template).
The problem arises as the query parameter is controlled by the user, and isn't escaped before being included in the string.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="exploitation"&gt;
&lt;h3&gt;Exploitation&lt;/h3&gt;
&lt;p&gt;Searching for the usage of the &lt;code class="docutils literal"&gt;get_redir_field&lt;/code&gt; function,
I found it was used in two views related to listing users:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/browse/impersonate/views.py?rev=ed7f09b3bb9f2168888c15562e29471ea82373c2#L106"&gt;/impersonate/views.py:106 (list_users)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/browse/impersonate/views.py?rev=ed7f09b3bb9f2168888c15562e29471ea82373c2#L134"&gt;/impersonate/views.py:134 (search_users)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But only the &lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/browse/impersonate/templates/impersonate/search_users.html?rev=ed7f09b3bb9f2168888c15562e29471ea82373c2#L11"&gt;template&lt;/a&gt;
rendered from the &lt;code class="docutils literal"&gt;search_users&lt;/code&gt; view includes the result of the function.&lt;/p&gt;
&lt;!-- rstcheck: ignore-next-code-block --&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-1" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# impersonate/views.py (search_users)&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-2" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-3" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-4" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-4" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-5" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-5" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-5"&gt;&lt;/a&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-6" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-6" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-6"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-7" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-7" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-7"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'paginator'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paginator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-8" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-8" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-8"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-9" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-9" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-9"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'page_number'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-10" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-10" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-10"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-11" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-11" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-11"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'redirect'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_redir_arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-12" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-12" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-12"&gt;&lt;/a&gt;        &lt;span class="s1"&gt;'redirect_field'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_redir_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-13" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-13" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-13"&gt;&lt;/a&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;a id="rest_code_07205a00cb9349fcb5a995fa66c03bb3-14" name="rest_code_07205a00cb9349fcb5a995fa66c03bb3-14" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_07205a00cb9349fcb5a995fa66c03bb3-14"&gt;&lt;/a&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-1" name="rest_code_c2140b86310f43eab160be13b4401f7c-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-1"&gt;&lt;/a&gt;&lt;span class="cm"&gt;&amp;lt;!-- impersonate/templates/impersonate/search_users.html --&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-2" name="rest_code_c2140b86310f43eab160be13b4401f7c-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-2"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'impersonate-search' %}"&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-3" name="rest_code_c2140b86310f43eab160be13b4401f7c-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-3"&gt;&lt;/a&gt;   Enter Search Query:&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-4" name="rest_code_c2140b86310f43eab160be13b4401f7c-4" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-4"&gt;&lt;/a&gt;   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"q"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{% if query %}{{ query }}{% endif %}"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-5" name="rest_code_c2140b86310f43eab160be13b4401f7c-5" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-5"&gt;&lt;/a&gt;   {{redirect_field}}
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-6" name="rest_code_c2140b86310f43eab160be13b4401f7c-6" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-6"&gt;&lt;/a&gt;   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_c2140b86310f43eab160be13b4401f7c-7" name="rest_code_c2140b86310f43eab160be13b4401f7c-7" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_c2140b86310f43eab160be13b4401f7c-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Assuming the application defined the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;IMPERSONATE["REDIRECT_FIELD_NAME"]&lt;/span&gt;&lt;/code&gt; setting as &lt;code class="docutils literal"&gt;next&lt;/code&gt;,
the URL used to exploit the vulnerability would be &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/impersonate/search/?next={payload}&lt;/span&gt;&lt;/code&gt;.
Where &lt;code class="docutils literal"&gt;{payload}&lt;/code&gt; can be:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_bf2ecd617dcf430f97ef419190e00657-1" name="rest_code_bf2ecd617dcf430f97ef419190e00657-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_bf2ecd617dcf430f97ef419190e00657-1"&gt;&lt;/a&gt;"/&amp;gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this does is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Uses a &lt;code class="docutils literal"&gt;"/&amp;gt;&lt;/code&gt; to close the &lt;code class="docutils literal"&gt;input&lt;/code&gt; tag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Injects a script that shows an alert with the current domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Opens a new tag so the rest of the HTML is not shown as broken.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The payload injected into the template would look like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-1" name="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-2" name="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-2"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-3" name="rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_78348a36bbbe4c9aaf284d9f565cc0a4-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="proof-of-concept"&gt;
&lt;h3&gt;Proof of concept&lt;/h3&gt;
&lt;p&gt;I created a &lt;a class="reference external" href="https://github.com/stsewd/poc-xss-django-impersonate"&gt;proof of concept&lt;/a&gt; to demonstrate the vulnerability, so you can see it in action,
you just need to have Python and &lt;a class="reference external" href="https://docs.astral.sh/uv/getting-started/installation/"&gt;uv&lt;/a&gt; installed:&lt;/p&gt;
&lt;p&gt;It consists of a simple Django project with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;django-impersonate==1.9.3&lt;/span&gt;&lt;/code&gt; installed,
with the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;IMPERSONATE["REDIRECT_FIELD_NAME"]&lt;/span&gt;&lt;/code&gt; setting defined as &lt;code class="docutils literal"&gt;next&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-1" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-1"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/stsewd/poc-xss-django-impersonate
&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-2" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-2"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc-xss-django-impersonate
&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-3" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-3"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-4" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-4" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-4"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Create a user to log into the application.&lt;/span&gt;
&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-5" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-5" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-5"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;createsuperuser
&lt;a id="rest_code_1a7c378dff5249a5ad28f5051a239f49-6" name="rest_code_1a7c378dff5249a5ad28f5051a239f49-6" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_1a7c378dff5249a5ad28f5051a239f49-6"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;runserver
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/admin/login/&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in with the user you created&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/impersonate/search/?next=?next="&amp;gt;&amp;lt;script&amp;gt;alert(document.domain)&amp;lt;/script&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="pre"&gt;type="hidden&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A pop-up with the domain of the page should appear&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Showing an alert is just a simple example,
but an attacker can execute any JavaScript code in the context of the user's session.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="mitigation"&gt;
&lt;h3&gt;Mitigation&lt;/h3&gt;
&lt;p&gt;You should never use &lt;code class="docutils literal"&gt;mark_safe&lt;/code&gt; with user-controlled content,
if you need to build HTML with user-controlled data outside of a template,
you can use the &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.html.format_html"&gt;format_html&lt;/a&gt; function,
as you can see in the two commits that fixed the vulnerability:
&lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/rev/06991a735f290884eec08effb3fa31ed45cc26e5"&gt;06991a735f29&lt;/a&gt;,
&lt;a class="reference external" href="https://hg.code.netlandish.com/~petersanchez/django-impersonate/rev/33cb8c77262a474869ab94bcb82c5446baf3c228"&gt;33cb8c77262a&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="timeline"&gt;
&lt;h3&gt;Timeline&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;11/06/2024&lt;/strong&gt;: Found and reported the vulnerability to the maintainer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;13/06/2024&lt;/strong&gt;: Maintainer replied and confirmed the vulnerability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;14/06/2024&lt;/strong&gt;: Maintainer released version 1.9.4 with the fix.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="django-gravatar2"&gt;
&lt;h2&gt;django-gravatar2&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pypi.org/project/django-gravatar2/"&gt;django-gravatar2&lt;/a&gt; allows you to integrate &lt;a class="reference external" href="https://gravatar.com/"&gt;Gravatar&lt;/a&gt; in your project,
so you can show the user's avatar based on their email.&lt;/p&gt;
&lt;section id="the-vulnerability-1"&gt;
&lt;h3&gt;The vulnerability&lt;/h3&gt;
&lt;p&gt;After grepping the codebase for common vulnerable patterns,
I found this code that caught my attention:&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:457px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Ftwaddington%2Fdjango-gravatar%2Fblob%2Fed123f849b5207e11efdfb1b2b0235baa41df356%2Fdjango_gravatar%2Ftemplatetags%2Fgravatar.py%23L24-L41&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;You can see that the application is building an HTML &lt;code class="docutils literal"&gt;img&lt;/code&gt; tag with several attributes,
like CSS class, alt text, size, and the URL of the Gravatar image,
and marking it as safe with &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.safestring.mark_safe"&gt;mark_safe&lt;/a&gt;
(Django won't escape it when including it in a template).
Of all these attributes, only the URL is being &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.html.escape"&gt;escaped&lt;/a&gt;,
all other values are used as is.&lt;/p&gt;
&lt;p&gt;I found that the function is used as a &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/howto/custom-template-tags/"&gt;template tag&lt;/a&gt; to render the Gravatar image:&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:100px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Ftwaddington%2Fdjango-gravatar%2Fblob%2Fed123f849b5207e11efdfb1b2b0235baa41df356%2Fdjango_gravatar%2Ftemplatetags%2Fgravatar.py%23L56&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;For example, you can use it in a template like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_f521820c5c474d8b82222bf1d87eeba2-1" name="rest_code_f521820c5c474d8b82222bf1d87eeba2-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f521820c5c474d8b82222bf1d87eeba2-1"&gt;&lt;/a&gt;{% load gravatar from gravatar %}
&lt;a id="rest_code_f521820c5c474d8b82222bf1d87eeba2-2" name="rest_code_f521820c5c474d8b82222bf1d87eeba2-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f521820c5c474d8b82222bf1d87eeba2-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_f521820c5c474d8b82222bf1d87eeba2-3" name="rest_code_f521820c5c474d8b82222bf1d87eeba2-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f521820c5c474d8b82222bf1d87eeba2-3"&gt;&lt;/a&gt;{% gravatar user 50 "User profile" %}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, the size and the alt text are hardcoded,
so there is no way for an attacker to inject arbitrary HTML.
But what happens if the size or alt text come from the user?
Then we have a problem, as the values are not escaped before being included in the template.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_ff197f3f36894d049e181e2d8c1973f7-1" name="rest_code_ff197f3f36894d049e181e2d8c1973f7-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_ff197f3f36894d049e181e2d8c1973f7-1"&gt;&lt;/a&gt;{% load gravatar from gravatar %}
&lt;a id="rest_code_ff197f3f36894d049e181e2d8c1973f7-2" name="rest_code_ff197f3f36894d049e181e2d8c1973f7-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_ff197f3f36894d049e181e2d8c1973f7-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_ff197f3f36894d049e181e2d8c1973f7-3" name="rest_code_ff197f3f36894d049e181e2d8c1973f7-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_ff197f3f36894d049e181e2d8c1973f7-3"&gt;&lt;/a&gt;{% gravatar user 50 user.name %}
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="exploitation-1"&gt;
&lt;h3&gt;Exploitation&lt;/h3&gt;
&lt;p&gt;Since the vulnerability is in a template tag,
exploiting the vulnerability will depend if the application uses the template tag with user-controlled content.
We can assume that a common alt text is the user's name.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-1" name="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-1"&gt;&lt;/a&gt;{% load gravatar from gravatar %}
&lt;a id="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-2" name="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-3" name="rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_33b22a77e52342f3aeaf3a9e39b39a0d-3"&gt;&lt;/a&gt;{% gravatar user 50 user.first_name %}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then the attacker can inject the payload in the user's name.
A simple payload could be:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_f876fb109e1c4f6bada6953dd7248de3-1" name="rest_code_f876fb109e1c4f6bada6953dd7248de3-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f876fb109e1c4f6bada6953dd7248de3-1"&gt;&lt;/a&gt;"/&amp;gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this does is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Uses a &lt;code class="docutils literal"&gt;"/&amp;gt;&lt;/code&gt; to close the &lt;code class="docutils literal"&gt;img&lt;/code&gt; tag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Injects a script that shows an alert with the current domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Opens a new tag so the rest of the HTML is not shown as broken.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The payload injected into the template would look like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_454eae7ebe76432a8599a36621e4beb8-1" name="rest_code_454eae7ebe76432a8599a36621e4beb8-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_454eae7ebe76432a8599a36621e4beb8-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"gravatar"&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://www.gravatar.com/"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_454eae7ebe76432a8599a36621e4beb8-2" name="rest_code_454eae7ebe76432a8599a36621e4beb8-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_454eae7ebe76432a8599a36621e4beb8-2"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_454eae7ebe76432a8599a36621e4beb8-3" name="rest_code_454eae7ebe76432a8599a36621e4beb8-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_454eae7ebe76432a8599a36621e4beb8-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that's very similar to the previous example,
so let's assume that the application uses the user's email as the alt text instead.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-1" name="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-1"&gt;&lt;/a&gt;{% load gravatar from gravatar %}
&lt;a id="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-2" name="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-3" name="rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_43a1e7cbdde04ae0bbbf325c51efcb09-3"&gt;&lt;/a&gt;{% gravatar user 50 user.email %}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You may think that's the same as the previous example,
but now the payload needs to be a valid email.
And if you try to create an email with the previous payload, it won't work,
as the Django user model will validate the email format.&lt;/p&gt;
&lt;p&gt;Making the payload a valid email is not as simple as just adding &lt;code class="docutils literal"&gt;@example.com&lt;/code&gt; at the end,
as the part before the &lt;code class="docutils literal"&gt;@&lt;/code&gt; (local part) can't contain special characters like &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;"&amp;lt;&amp;gt;()&lt;/span&gt;&lt;/code&gt;,
which are part of the payload.&lt;/p&gt;
&lt;p&gt;Luckily, the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Email_address#Local-part"&gt;spec says that the local part can contain any ASCII characters if it's quoted&lt;/a&gt;,
and coincidentally, our payload has already quotes around it, so it's just a matter adding &lt;code class="docutils literal"&gt;@example.com&lt;/code&gt; at the end!
Or almost... Django's email validator does allow the local part to be quoted, but it doesn't allow spaces,
luckily HTML is very forgiving, so we can add almost anything instead of the spaces, and our payload will still work&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_d509d77cd9b248628c17e23b55a05f5c-1" name="rest_code_d509d77cd9b248628c17e23b55a05f5c-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_d509d77cd9b248628c17e23b55a05f5c-1"&gt;&lt;/a&gt;"/&amp;gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@example.com&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You could also leave the tag unclosed, but that will break the rest of the HTML in the template.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_a48a231ca86d4e3cb46c6edebb1ffe9e-1" name="rest_code_a48a231ca86d4e3cb46c6edebb1ffe9e-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_a48a231ca86d4e3cb46c6edebb1ffe9e-1"&gt;&lt;/a&gt;"/&amp;gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;"@example.com
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="proof-of-concept-1"&gt;
&lt;h3&gt;Proof of concept&lt;/h3&gt;
&lt;p&gt;I created a &lt;a class="reference external" href="https://github.com/stsewd/poc-xss-django-gravatar2"&gt;proof of concept&lt;/a&gt; to demonstrate the vulnerability, so you can see it in action,
you just need to have Python and &lt;a class="reference external" href="https://docs.astral.sh/uv/getting-started/installation/"&gt;uv&lt;/a&gt; installed:&lt;/p&gt;
&lt;p&gt;It consists of a simple Django project with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;django-gravatar2==1.4.4&lt;/span&gt;&lt;/code&gt; installed,
it shows the Gravatar of a user given its email.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-1" name="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f89a2c73e6cc4ac693eda1b178d88c71-1"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/stsewd/poc-xss-django-gravatar2
&lt;a id="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-2" name="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-2" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f89a2c73e6cc4ac693eda1b178d88c71-2"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc-xss-django-gravatar2
&lt;a id="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-3" name="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-3" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f89a2c73e6cc4ac693eda1b178d88c71-3"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;a id="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-4" name="rest_code_f89a2c73e6cc4ac693eda1b178d88c71-4" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_f89a2c73e6cc4ac693eda1b178d88c71-4"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;runserver
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the form enter &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;"/&amp;gt;&amp;lt;script&amp;gt;alert(document.domain)&amp;lt;/script&amp;gt;&amp;lt;img&lt;/span&gt; src="&lt;/code&gt; as the name,
or &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;"/&amp;gt;&amp;lt;script&amp;gt;alert(document.domain)&amp;lt;/script&amp;gt;&amp;lt;img/src="@example.com&lt;/span&gt;&lt;/code&gt; as the email.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the "Submit" button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A pop-up with the domain of the page should appear.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Showing an alert is just a simple example,
but an attacker can execute any JavaScript code in the context of the user's session.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="mitigation-1"&gt;
&lt;h3&gt;Mitigation&lt;/h3&gt;
&lt;p&gt;As the previous vulnerability,
you should never use &lt;code class="docutils literal"&gt;mark_safe&lt;/code&gt; with user-controlled content,
if you need to build HTML with user-controlled data outside of a template,
you can use the &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.html.format_html"&gt;format_html&lt;/a&gt; function.&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The maintainer chose to &lt;a class="reference external" href="https://github.com/twaddington/django-gravatar/commit/b08820112f062b40521c6f07fb9657f4204f6cf1"&gt;escape the alt text only&lt;/a&gt;,
as he considered the size and CSS class should be validated by the developer.
If you are using the &lt;code class="docutils literal"&gt;gravatar&lt;/code&gt; template tag with user-controlled content
in the size or CSS class, you should escape it as show in the following example:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_7196231689be4deba086ea0a9b0e1d04-1" name="rest_code_7196231689be4deba086ea0a9b0e1d04-1" href="https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/#rest_code_7196231689be4deba086ea0a9b0e1d04-1"&gt;&lt;/a&gt;{% gravatar user size|escape "User profile" class|escape %}
&lt;/pre&gt;&lt;/div&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="timeline-1"&gt;
&lt;h3&gt;Timeline&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;21/06/2024&lt;/strong&gt;: Found and reported the vulnerability to the maintainer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;21/06/2024&lt;/strong&gt;: Maintainer replied and confirmed the vulnerability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;29/08/2024&lt;/strong&gt;: Maintainer released version 1.4.5 with the fix.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="more-in-common-than-you-think"&gt;
&lt;h2&gt;More in common than you think&lt;/h2&gt;
&lt;p&gt;Apart from sharing the same vulnerability, there are other similarities between the two packages:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Widely used packages.
At the time of writing, &lt;a class="reference external" href="https://pypistats.org/packages/django-impersonate"&gt;django-impersonate had 220K downloads in the last month&lt;/a&gt;,
and &lt;a class="reference external" href="https://pypistats.org/packages/django-gravatar2"&gt;django-gravatar2 had 32K downloads in the last month&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mostly maintained by a single person.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not actively maintained.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the functionality that both packages provide is very specific,
they may be considered complete and stable without the need for active development.
But as with any software, there is always room for improvement,
or updates to keep up with the latest versions of Python and Django.&lt;/p&gt;
&lt;p&gt;If you or your company use these packages,
please consider contributing to them in any way you can.
Another thing these packages have in common is that they are looking for maintainers,
so if you have the time and knowledge, consider helping them.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="acknowledgements"&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Thanks to &lt;a class="reference external" href="https://petersanchez.com/"&gt;Peter Sanchez&lt;/a&gt; (maintainer of django-impersonate),
and &lt;a class="reference external" href="https://github.com/twaddington"&gt;Tristan Waddington&lt;/a&gt; (maintainer of django-gravatar2)
for their quick responses and fixes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's also great I have the support at Read the Docs to spend part of my work time on security audits on packages we use.
Even if the vulnerabilities don't affect our systems directly,
it's nice to have the chance to give back to the community.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;</description><category>django</category><category>python</category><category>security</category><category>xss</category><guid>https://stsewd.dev/posts/xss-in-djang-impersonate-and-django-gravatar2/</guid><pubDate>Sat, 08 Feb 2025 05:00:00 GMT</pubDate></item><item><title>XSS in django-allauth 0.63.5</title><link>https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;This post details a Cross-Site Scripting (XSS) vulnerability I discovered in &lt;a class="reference external" href="https://allauth.org/"&gt;django-allauth&lt;/a&gt;, a popular Django package for authentication.
This vulnerability affected the Facebook provider only, and it was fixed in version &lt;a class="reference external" href="https://allauth.org/news/2024/07/django-allauth-0.63.6-released/"&gt;0.63.6&lt;/a&gt; on July 12, 2024.&lt;/p&gt;
&lt;section id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Before I found this vulnerability, I already reported another one to django-allauth,
a login CSRF vulnerability in its SAML provider, which was fixed in version &lt;a class="reference external" href="https://docs.allauth.org/en/latest/release-notes/recent.html#id34"&gt;0.63.3&lt;/a&gt;
(maybe I'll write a post about it if people are interested in more posts like this).&lt;/p&gt;
&lt;p&gt;At &lt;a class="reference external" href="https://about.readthedocs.com/"&gt;Read the Docs&lt;/a&gt;, we use django-allauth for user authentication.
I was in charge of integrating SAML into our authentication system, while working on that I noticed the CSRF vulnerability.
After reporting it and seeing how quick it was fixed, I decided to do a quick security audit of the project.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-vulnerability"&gt;
&lt;h2&gt;The vulnerability&lt;/h2&gt;
&lt;p&gt;After grepping the codebase for common vulnerable patterns, I found this line of code that caught my attention:&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:121px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fpennersr%2Fdjango-allauth%2Fblob%2F1512ac4fe0353d7a8d795c5e8b89a07f3a9a31f5%2Fallauth%2Fsocialaccount%2Fproviders%2Ffacebook%2Fprovider.py%23L179-L180&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;django-allauth &lt;a class="reference external" href="https://docs.allauth.org/en/latest/socialaccount/providers/facebook.html"&gt;allows using Facebook as a provider for social authentication&lt;/a&gt;,
and allows using the regular form (&lt;code class="docutils literal"&gt;oauth2&lt;/code&gt; method) or the Facebook JavaScript SDK (&lt;code class="docutils literal"&gt;js_sdk&lt;/code&gt; method) to login.
When using the &lt;code class="docutils literal"&gt;js_sdk&lt;/code&gt; method, the &lt;code class="docutils literal"&gt;fb_data&lt;/code&gt; variable is passed to the template to be used in the frontend.&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:163px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fpennersr%2Fdjango-allauth%2Fblob%2F1512ac4fe0353d7a8d795c5e8b89a07f3a9a31f5%2Fallauth%2Fsocialaccount%2Fproviders%2Ffacebook%2Ftemplates%2Ffacebook%2Ffbconnect.html&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;Which is then used in the &lt;a class="reference external" href="https://github.com/pennersr/django-allauth/blob/1512ac4fe0353d7a8d795c5e8b89a07f3a9a31f5/allauth/socialaccount/providers/facebook/static/facebook/js/fbconnect.js#L32"&gt;fbconnect.js&lt;/a&gt;
script to initialize the Facebook SDK.&lt;/p&gt;
&lt;p&gt;So, what's the problem here?
The &lt;code class="docutils literal"&gt;fb_data&lt;/code&gt; variable is &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.safestring.mark_safe"&gt;marked as safe&lt;/a&gt;
(Django won't escape it when including it in a template)
after being transformed into a JSON string.
Since &lt;code class="docutils literal"&gt;json.dumps&lt;/code&gt; doesn't escape HTML characters,
it's possible to inject arbitrary HTML and JavaScript code into the template.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="exploitation"&gt;
&lt;h2&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Using &lt;code class="docutils literal"&gt;mark_safe&lt;/code&gt; by itself is not a vulnerability,
as long as the content is trusted and doesn't contain user input.
So the next step was to find a way to inject user-controlled content into the &lt;code class="docutils literal"&gt;fb_data&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;As shown below, most of the content in the &lt;code class="docutils literal"&gt;fb_data&lt;/code&gt; variable is static:&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:394px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fpennersr%2Fdjango-allauth%2Fblob%2F1512ac4fe0353d7a8d795c5e8b89a07f3a9a31f5%2Fallauth%2Fsocialaccount%2Fproviders%2Ffacebook%2Fprovider.py%23L164-L178&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;Except for &lt;code class="docutils literal"&gt;loginOptions&lt;/code&gt;, which includes the value from the &lt;code class="docutils literal"&gt;scope&lt;/code&gt; query parameter:&lt;/p&gt;
&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:205px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fpennersr%2Fdjango-allauth%2Fblob%2F1512ac4fe0353d7a8d795c5e8b89a07f3a9a31f5%2Fallauth%2Fsocialaccount%2Fproviders%2Ffacebook%2Fprovider.py%23L139-L144&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;iframe frameborder="0" scrolling="no" style="width:100%; height:268px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fpennersr%2Fdjango-allauth%2Fblob%2Fc11e1429d90aa12373fb97705e18b1d8c602c417%2Fallauth%2Fsocialaccount%2Fproviders%2Foauth2%2Fprovider.py%23L83-L91&amp;amp;style=default&amp;amp;type=code&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showFullPath=on&amp;amp;showCopy=on"&gt;&lt;/iframe&gt;&lt;p&gt;With that, we now can inject arbitrary HTML and JavaScript using the &lt;code class="docutils literal"&gt;scope&lt;/code&gt; query parameter.&lt;/p&gt;
&lt;p&gt;But wait... In which page can we control the &lt;code class="docutils literal"&gt;scope&lt;/code&gt; query parameter?
By following the code, I found that &lt;code class="docutils literal"&gt;media_js&lt;/code&gt; is used in the &lt;code class="docutils literal"&gt;providers_media_js&lt;/code&gt; template tag,
which is called in the &lt;a class="reference external" href="https://github.com/pennersr/django-allauth/blob/0.63.5/allauth/templates/socialaccount/snippets/login_extra.html"&gt;login_extra.html&lt;/a&gt; snippet,
and furthermore that snippet is included anywhere the social providers are listed,
like the login page (&lt;code class="docutils literal"&gt;/accounts/login/&lt;/code&gt;), and the social account connections page (&lt;code class="docutils literal"&gt;/accounts/3rdparty/&lt;/code&gt;).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="payload"&gt;
&lt;h2&gt;Payload&lt;/h2&gt;
&lt;p&gt;To exploit this vulnerability, an attacker could inject the following content in the &lt;code class="docutils literal"&gt;scope&lt;/code&gt; query parameter:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code html"&gt;&lt;a id="rest_code_6c44123dfede49efa956cc90a5a5d5e5-1" name="rest_code_6c44123dfede49efa956cc90a5a5d5e5-1" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_6c44123dfede49efa956cc90a5a5d5e5-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this does is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Closes the &lt;code class="docutils literal"&gt;script&lt;/code&gt; tag containing the &lt;code class="docutils literal"&gt;fb_data&lt;/code&gt; variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Injects a script that shows an alert with the current domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Opens a new &lt;code class="docutils literal"&gt;script&lt;/code&gt; tag, so the rest of the JSON content is not shown as plain text.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="proof-of-concept"&gt;
&lt;h2&gt;Proof of concept&lt;/h2&gt;
&lt;p&gt;I created a &lt;a class="reference external" href="https://github.com/stsewd/poc-xss-django-allauth"&gt;proof of concept&lt;/a&gt; to demonstrate the vulnerability, so you can see it in action,
you just need to have Python and &lt;a class="reference external" href="https://docs.astral.sh/uv/getting-started/installation/"&gt;uv&lt;/a&gt; installed:&lt;/p&gt;
&lt;p&gt;It consists of a simple Django project with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth==0.63.5&lt;/span&gt;&lt;/code&gt; installed, and a Facebook provider configured using the JavaScript SDK.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-1" name="rest_code_532ec74f9c26484c926550bb40624f54-1" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-1"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/stsewd/poc-xss-django-allauth
&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-2" name="rest_code_532ec74f9c26484c926550bb40624f54-2" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-2"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc-xss-django-allauth
&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-3" name="rest_code_532ec74f9c26484c926550bb40624f54-3" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-3"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-4" name="rest_code_532ec74f9c26484c926550bb40624f54-4" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-4"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Create a user to log into the application.&lt;/span&gt;
&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-5" name="rest_code_532ec74f9c26484c926550bb40624f54-5" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-5"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;createsuperuser
&lt;a id="rest_code_532ec74f9c26484c926550bb40624f54-6" name="rest_code_532ec74f9c26484c926550bb40624f54-6" href="https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/#rest_code_532ec74f9c26484c926550bb40624f54-6"&gt;&lt;/a&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;runserver
&lt;/pre&gt;&lt;/div&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;XSS in login page:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;While logged out, go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/accounts/login/?scope=&amp;lt;/script&amp;gt;&amp;lt;script&amp;gt;alert(document.domain)&amp;lt;/script&amp;gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;dt&gt;XSS in social connections page:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/accounts/login/&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in with the user you created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000/accounts/3rdparty/?scope=&amp;lt;/script&amp;gt;&amp;lt;script&amp;gt;alert(document.domain)&amp;lt;/script&amp;gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Showing an alert is just a simple example,
but an attacker can execute any JavaScript code in the context of the user's session.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="mitigation"&gt;
&lt;h2&gt;Mitigation&lt;/h2&gt;
&lt;p&gt;You should never mark user-controlled content as safe,
but if you find yourself wanting to include JSON content in a template,
escaping will break the JSON format.&lt;/p&gt;
&lt;p&gt;Luckily, Django has a built-in template filter to include JSON content in a template safely,
&lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/templates/builtins/#json-script"&gt;json_script&lt;/a&gt;.
Sadly, that filter wasn't available at the moment the allauth code was written, but it's been available since Django 2.1,
since allauth supports newer versions of Django,
it was possible to use it, as you can see in the &lt;a class="reference external" href="https://github.com/pennersr/django-allauth/commit/8fead343c1d3e75cc842e0ee1e21a39c6d145155"&gt;fix&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="timeline"&gt;
&lt;h2&gt;Timeline&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;11/07/2024:&lt;/strong&gt; Found and reported the vulnerability to django-allauth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;12/07/2024:&lt;/strong&gt; Maintainer confirmed the vulnerability and released version 0.63.6 with the fix.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="acknowledgements"&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;I'm always surprised by how quickly open source maintainers fix security vulnerabilities
(so much faster than commercial software vendors), kudos to &lt;a class="reference external" href="https://github.com/pennersr/"&gt;Raymond Penners&lt;/a&gt;, maintainer of django-allauth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's also great I have the support at Read the Docs to spend part of my work time on security audits on packages we use.
Even if the vulnerabilities don't affect our systems directly (we don't use the Facebook provider),
it's nice to have the chance to give back to the community.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are you still using django-allauth &lt;code class="docutils literal"&gt;&amp;lt;0.63.6&lt;/code&gt;? The fix was released more than 6 months ago, please update your dependencies!
Thank you for reading, and let me know if you'd like to see more posts like this!&lt;/p&gt;
&lt;/section&gt;</description><category>django</category><category>python</category><category>security</category><category>xss</category><guid>https://stsewd.dev/posts/xss-in-django-allauth-fb-provider/</guid><pubDate>Sun, 19 Jan 2025 05:00:00 GMT</pubDate></item><item><title>Securing your development environment</title><link>https://stsewd.dev/posts/securing-your-dev-environment/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;If you are a developer, chances are that you handle private code or production secrets.
If someone has access to those, you may be in serious problems with your bosses and clients.
I'll share with you some tips on how to secure your development environment &lt;strong&gt;from your computer to your terminal&lt;/strong&gt;.&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;In this post I'd recommend some tools,
for their usage please check their respective documentation,
or search for alternatives for your Operating System.
For some tools I have my configuration available at &lt;a class="reference external" href="https://github.com/stsewd/dotfiles"&gt;https://github.com/stsewd/dotfiles&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;nav class="contents local" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#passwords" id="toc-entry-1"&gt;Passwords&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#multi-factor-authentication" id="toc-entry-2"&gt;Multi factor authentication&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#full-disk-encryption" id="toc-entry-3"&gt;Full disk encryption&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#securing-your-code" id="toc-entry-4"&gt;Securing your code&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#signing-your-commits" id="toc-entry-5"&gt;Signing your commits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#securing-your-terminal" id="toc-entry-6"&gt;Securing your terminal&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#securing-your-files-and-environment-variables" id="toc-entry-7"&gt;Securing your files and environment variables&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#securing-your-browser" id="toc-entry-8"&gt;Securing your browser&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#email" id="toc-entry-9"&gt;Email&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#use-a-secure-channel-for-communication" id="toc-entry-10"&gt;Use a secure channel for communication&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#extra-paranoia" id="toc-entry-11"&gt;Extra paranoia&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://stsewd.dev/posts/securing-your-dev-environment/#conclusions" id="toc-entry-12"&gt;Conclusions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="passwords"&gt;
&lt;h2&gt;Passwords&lt;/h2&gt;
&lt;p&gt;Using an strong password is good, but using the same password for each login isn't good,
if one system is compromised, &lt;strong&gt;all your&lt;/strong&gt; accounts could be compromised.
The best password is the one that not even you know,
a password manager can help you to generate strong and unique passwords for each login.&lt;/p&gt;
&lt;p&gt;Using a password manager requires you to have one master password,
this one you do need to remember,
so make sure to choose a strong one, and rotate that password every year or so.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;a class="reference external image-reference" href="https://xkcd.com/936/"&gt;
&lt;img alt="/images/securing-your-dev-environment/password_strength.png" src="https://stsewd.dev/images/securing-your-dev-environment/password_strength.png"&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;I recommend &lt;a class="reference external" href="https://bitwarden.com/"&gt;Bitwarden&lt;/a&gt; as password manager, it's Open Source.
The free version gives you a lot of features that usually require
a premium account in other services, and even the premium version is cheap.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="multi-factor-authentication"&gt;
&lt;h2&gt;Multi factor authentication&lt;/h2&gt;
&lt;p&gt;But even using a password manager isn't enough to protect your accounts,
the generated password could be exposed without you knowing it,
or your master password could be compromised.
Multi factor authentication (MFA) to the rescue!&lt;/p&gt;
&lt;p&gt;MFA is about using two or more pieces of evidence (factors) on authentication to be able to access a website or application &lt;a class="brackets" href="https://stsewd.dev/posts/securing-your-dev-environment/#mfa" id="footnote-reference-1" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;1&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;.
Factors are: something you know (like a password), something you have (like a phone), and something you are (like your fingerprints).
A common way of MFA is combining a password (something you know) with something you have, like:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;SMS&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This is using your phone number to receive a code via SMS to be able to authenticate.
&lt;strong&gt;Please don't use this method&lt;/strong&gt;, it's easy for an attacker to hijack your phone number &lt;a class="brackets" href="https://stsewd.dev/posts/securing-your-dev-environment/#sim-hijack" id="footnote-reference-2" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;2&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;,
and if you are traveling, you won't be able to receive the codes.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;App&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This is using an application in your phone that generates an OTP &lt;a class="brackets" href="https://stsewd.dev/posts/securing-your-dev-environment/#otp" id="footnote-reference-3" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;3&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;
valid for 30 seconds for each login.
Using an app is an excellent way of MFA,
and you don't need Internet connection on your phone to be able to access the tokens.&lt;/p&gt;
&lt;p&gt;An app that I recommend is &lt;a class="reference external" href="https://github.com/andOTP/andOTP"&gt;andOTP&lt;/a&gt;,
it's Open Source, and has several features like using a PIN to unlock the codes,
and making encrypted offline backups.&lt;/p&gt;
&lt;p&gt;When choosing an app, &lt;strong&gt;don't use those that sync your codes to the cloud&lt;/strong&gt;,
that kind of breaks the rule about something you (and only you) have.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Hardware keys&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;They are similar to an app, they can provide an OTP,
but the good thing is that they aren't attached to your phone.
Popular hardware keys are &lt;a class="reference external" href="https://www.yubico.com/"&gt;YubiKeys&lt;/a&gt;.
&lt;strong&gt;If you decide to get one, buy two!&lt;/strong&gt;,
the second key will act as your backup in case you lose or damage the other one.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;a class="reference external image-reference" href="https://commons.wikimedia.org/wiki/File:YubiKey-4-keychain-and-YubiKey-4-Nano.png"&gt;
&lt;img alt="/images/securing-your-dev-environment/yubikey.png" src="https://stsewd.dev/images/securing-your-dev-environment/yubikey.png" style="width: 50%;"&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Not all services support MFA, but make sure to take some minutes to set it for those that do.
Most services will give you recovery codes in case you lose access to your MFA device,
&lt;strong&gt;save them offline and in a secure location&lt;/strong&gt;!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="full-disk-encryption"&gt;
&lt;h2&gt;Full disk encryption&lt;/h2&gt;
&lt;p&gt;If someone steals your computer,
they can access all your files without having to know your user's password.
To prevent this, make use of full disk encryption on your computer.&lt;/p&gt;
&lt;p&gt;On Linux systems, this is usually an option on installation
You could even make use of your YubiKey to protect your disk
(your password + an static password from your YubiKey).&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Is also a good idea to automatically lock your computer on inactivity.&lt;/p&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="securing-your-code"&gt;
&lt;h2&gt;Securing your code&lt;/h2&gt;
&lt;p&gt;Even if you use full disk encryption,
if someone steals your computer while you are logged in,
they will have access to all your files and active sessions.
Sure, you can revoke your active sessions,
but doing so could take some time, and isn't possible to revoke access to your files.&lt;/p&gt;
&lt;p&gt;In addition to full disk encryption you can encrypt individual directories, and set a lifetime.
This way your files will be secure even if someone has access to your un-locked computer.
A simple tool to archive this is &lt;a class="reference external" href="https://github.com/vgough/encfs"&gt;encFS&lt;/a&gt; (check for the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-i&lt;/span&gt;&lt;/code&gt; option).&lt;/p&gt;
&lt;p&gt;Is common to use SSH authentication with your version control system (VCS) provider
to avoid entering your password every time,
but this leaves the door open for anyone with access to your computer.
&lt;strong&gt;Protect your private key with a passphrase,
and set a lifetime to your SSH agent&lt;/strong&gt; (&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-t&lt;/span&gt;&lt;/code&gt; option, see &lt;code class="docutils literal"&gt;man &lt;span class="pre"&gt;ssh-agent&lt;/span&gt;&lt;/code&gt;),
this way you'll need to re-enter your passphrase every &lt;code class="docutils literal"&gt;t&lt;/code&gt; minutes/hours.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="signing-your-commits"&gt;
&lt;h2&gt;Signing your commits&lt;/h2&gt;
&lt;p&gt;Using a VCS like Git for your code is great
(you do have your code under a VCS, right?),
it allows you to keep track of your changes, revert changes, and more!
It is also useful to know who changed a particular piece of code and when,
which is great when doing audits over your code base.&lt;/p&gt;
&lt;p&gt;But in fact, anyone can say to be you when committing changes,
Git for example makes use of a configuration file to set your name and email,
you don't need to provide anything else to say that you are that person!&lt;/p&gt;
&lt;p&gt;This means that any of your coworkers could impersonate you,
or an attacker with access to your VCS provider could do so as well.
You don't want to be responsible for changes that you didn't make!&lt;/p&gt;
&lt;p&gt;Luckily, Git allows you to sign your commits with a GPG key.
Someone could still use your email for their commits,
but they won't be able to sign those commits with your private GPG key.
GitLab has a great guide on how to sign your commits with GPG
&lt;a class="reference external" href="https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/"&gt;https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/&lt;/a&gt;.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;a class="reference external image-reference" href="https://stsewd.dev/images/securing-your-dev-environment/signed-commit.png"&gt;
&lt;img alt="/images/securing-your-dev-environment/signed-commit.png" src="https://stsewd.dev/images/securing-your-dev-environment/signed-commit.png"&gt;
&lt;/a&gt;
&lt;figcaption&gt;
&lt;p&gt;Commit signed (verified) on GitHub&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Same as SSH, protect your private GPG key with a passphrase,
and set a lifetime to your GPG agent (&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;default-cache-ttl&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;max-cache-ttl&lt;/span&gt;&lt;/code&gt; options, see &lt;code class="docutils literal"&gt;man &lt;span class="pre"&gt;gpg-agent&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="securing-your-terminal"&gt;
&lt;h2&gt;Securing your terminal&lt;/h2&gt;
&lt;p&gt;The terminal is a great friend,
and navigating the history with &lt;code class="docutils literal"&gt;↑&lt;/code&gt; &lt;code class="docutils literal"&gt;↓&lt;/code&gt; save you some typing,
but that history can also contain sensitive information.&lt;/p&gt;
&lt;p&gt;The default number of history entries is usually high,
as a quick experiment, you can check how many entries you have with:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_2c7f80ef95504374b7ad6999ed05a829-1" name="rest_code_2c7f80ef95504374b7ad6999ed05a829-1" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_2c7f80ef95504374b7ad6999ed05a829-1"&gt;&lt;/a&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HISTFILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wc&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And how many of those entries possibly have secrets with:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_5a8c28b6a8264c559bddbc619e9b7055-1" name="rest_code_5a8c28b6a8264c559bddbc619e9b7055-1" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_5a8c28b6a8264c559bddbc619e9b7055-1"&gt;&lt;/a&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'(token)|(pass)|(secret)'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HISTFILE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Depending on the Shell you are using,
you can control the max number of entries with environment variables,
for zsh this is done with:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_2daa0d7699c4423b9b74392811c13f44-1" name="rest_code_2daa0d7699c4423b9b74392811c13f44-1" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_2daa0d7699c4423b9b74392811c13f44-1"&gt;&lt;/a&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SAVEHIST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;
&lt;a id="rest_code_2daa0d7699c4423b9b74392811c13f44-2" name="rest_code_2daa0d7699c4423b9b74392811c13f44-2" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_2daa0d7699c4423b9b74392811c13f44-2"&gt;&lt;/a&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$SAVEHIST&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Choose the number of entries at your discretion,
not big enough that will keep things for a long period of time,
and not so small to not save you some typing.&lt;/p&gt;
&lt;p&gt;Some times you need to enter secrets in your terminal,
but you don't want to save them in your history.
You can avoid adding your commands to the history
by prefixing them with a space.&lt;/p&gt;
&lt;p&gt;Another way to enter into &lt;em&gt;incognito mode&lt;/em&gt; is by un-setting the &lt;code class="docutils literal"&gt;$HISTFILE&lt;/code&gt; environment variable
(thanks &lt;a class="reference external" href="https://github.com/WhiteHatTux"&gt;@WhiteHatTux&lt;/a&gt; for this tip!).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_4a5d3fe0d6c74ac29021eca550887048-1" name="rest_code_4a5d3fe0d6c74ac29021eca550887048-1" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_4a5d3fe0d6c74ac29021eca550887048-1"&gt;&lt;/a&gt;&lt;span class="nb"&gt;unset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;HISTFILE
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="securing-your-files-and-environment-variables"&gt;
&lt;h2&gt;Securing your files and environment variables&lt;/h2&gt;
&lt;p&gt;If you have files with sensitive information
that you can't encrypt because it needs to be readable (like configuration files),
at least give access only the appropriate users/processes.&lt;/p&gt;
&lt;p&gt;To remove access from all users except yours, you can use:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code bash"&gt;&lt;a id="rest_code_52ddf68c9e734eb58e2127896d33a5cd-1" name="rest_code_52ddf68c9e734eb58e2127896d33a5cd-1" href="https://stsewd.dev/posts/securing-your-dev-environment/#rest_code_52ddf68c9e734eb58e2127896d33a5cd-1"&gt;&lt;/a&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;og-rwx&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;file&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For extra protection, check the &lt;a class="reference external" href="https://selinuxproject.org/page/Main_Page"&gt;SELinux&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;If you need to expose some environment variables with secrets to your commands.
You can use &lt;a class="reference external" href="https://direnv.net/"&gt;direnv&lt;/a&gt; with an encrypted directory.
This way the environment variables will be set only when you are on that directory.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="securing-your-browser"&gt;
&lt;h2&gt;Securing your browser&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If you are using a network that you don't have control over,
use a VPN to connect to the Internet.
&lt;a class="reference external" href="https://protonvpn.com/"&gt;ProtonVPN&lt;/a&gt; is a good free option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take some time hardening your browser settings,
for Firefox for example,
I use &lt;a class="reference external" href="https://github.com/stsewd/dotfiles/blob/master/firefox-about.txt"&gt;these&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use extra extensions to secure your browser:
like disabling cookies and JS for unknown sites.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a separate &lt;a class="reference external" href="https://support.mozilla.org/en-US/kb/profile-manager-create-remove-switch-firefox-profiles"&gt;profile&lt;/a&gt; for work.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="email"&gt;
&lt;h2&gt;Email&lt;/h2&gt;
&lt;p&gt;Don't load external content by default,
an attacker can use this for something &lt;em&gt;harmless&lt;/em&gt; like tracking you,
to something more sophisticated like exploiting a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;CSRF&lt;/a&gt; vulnerable site.
This is usually an option in your email client,
&lt;a class="reference external" href="https://protonmail.com/"&gt;Protonmail&lt;/a&gt; has this option enabled by default.&lt;/p&gt;
&lt;figure class="align-center"&gt;
&lt;a class="reference external image-reference" href="https://stsewd.dev/images/securing-your-dev-environment/gmail-disable-display-external-images.png"&gt;
&lt;img alt="/images/securing-your-dev-environment/gmail-disable-display-external-images.png" src="https://stsewd.dev/images/securing-your-dev-environment/gmail-disable-display-external-images.png"&gt;
&lt;/a&gt;
&lt;figcaption&gt;
&lt;p&gt;Option to disable loading external images on Gmail.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="use-a-secure-channel-for-communication"&gt;
&lt;h2&gt;Use a secure channel for communication&lt;/h2&gt;
&lt;p&gt;Sometimes you'll need to share passwords or private information with other coworkers.
Use a secure channel with end to end encryption to do so (and delete the messages after you are done),
or use a password manager for your team,
or encrypt the secrets with their public GPG key.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="extra-paranoia"&gt;
&lt;h2&gt;Extra paranoia&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Avoid wireless devices when possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search for security related settings on every application you use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be careful with the personal information you share with others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use two phone numbers and two computers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shred any document before throwing it to the trash.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Everything is about encryption and lifetimes.&lt;/p&gt;
&lt;p&gt;In perfect conditions, you should have a dedicated computer for work,
and connect to the Internet using a secure network,
but this isn't always possible or provided by your employer.
Still, it's always good to have several layers of protection when handling sensitive information.&lt;/p&gt;
&lt;p&gt;Did you already knew some of these tips?
Or do you have more to share?
Let me know in the comments!&lt;/p&gt;
&lt;hr class="docutils"&gt;
&lt;aside class="footnote-list brackets"&gt;
&lt;aside class="footnote brackets" id="mfa" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="https://stsewd.dev/posts/securing-your-dev-environment/#footnote-reference-1"&gt;1&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;https://en.wikipedia.org/wiki/Multi-factor_authentication&lt;/a&gt;&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="footnote brackets" id="sim-hijack" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="https://stsewd.dev/posts/securing-your-dev-environment/#footnote-reference-2"&gt;2&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/SIM_swap_scam"&gt;https://en.wikipedia.org/wiki/SIM_swap_scam&lt;/a&gt;&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="footnote brackets" id="otp" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="https://stsewd.dev/posts/securing-your-dev-environment/#footnote-reference-3"&gt;3&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/One-time_password"&gt;https://en.wikipedia.org/wiki/One-time_password&lt;/a&gt;&lt;/p&gt;
&lt;/aside&gt;
&lt;/aside&gt;
&lt;/section&gt;</description><category>development</category><category>security</category><guid>https://stsewd.dev/posts/securing-your-dev-environment/</guid><pubDate>Sat, 24 Jul 2021 05:00:00 GMT</pubDate></item><item><title>A tale about security in web applications, or how I helped to save a bank from bankruptcy</title><link>https://stsewd.dev/posts/a-tale-about-security-in-web-applications/</link><dc:creator>Santos Gallegos</dc:creator><description>&lt;p&gt;Hi friend, today I'm going to tell you a little story.
Some things may look familiar, or even you could feel related to some situations.
I assure you, &lt;strong&gt;it's mere coincidence&lt;/strong&gt;.&lt;/p&gt;
&lt;section id="a-new-experience"&gt;
&lt;h2&gt;A new experience&lt;/h2&gt;
&lt;p&gt;This tale takes place in Ecuador.
It was during the lockdown that some friends contacted me to help them with a security audit.
They were forming a team (with special abilities?),
and there was missing someone with programming/web skills.&lt;/p&gt;
&lt;p&gt;I was a little nervous to say yes,
since I haven't had much experience with security in the field.
But it was comforting to know that my teammates had vast experience in it.
I was excited to learn new things from them, and how the world of security, auditing, and banking works.
Oh, and I would get one of those fancy permits (salvoconducto) to go outside during lockdown.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Wait, "banking"?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Oh, sorry I didn't tell you, the security audit was for a small bank.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="first-flight"&gt;
&lt;h2&gt;First flight&lt;/h2&gt;
&lt;p&gt;And &lt;em&gt;the day&lt;/em&gt; arrived.
We started with their homepage, and some small web applications.
It didn't take me much time to start finding small security bugs.
And then not so small, but not &lt;em&gt;that&lt;/em&gt; critical either,
mostly things returning too much data or returning data when they shouldn't.
And it was from some old applications, so there wasn't much surprise in there,
and the data exposed wasn't from all their clients.&lt;/p&gt;
&lt;p&gt;After reporting some of those bugs that seemed important to get fixed soon
we moved on to our next target: &lt;strong&gt;the online banking application&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We started with a black box test (no user).
It wasn't long since we started to find minor security bugs.
And we found some indications that the API may not be doing an appropriate check for authorization,
but without a user, it wasn't easy to guess (yet!).&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;What indications?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Well, most of their APIs required a user ID to be present in the request.
So, instead of relying on some session cookie or authorization token previously validated by the server,
&lt;strong&gt;they were asking the client to explicitly pass the user's ID&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Oh, I see, that smells bad.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="access-granted"&gt;
&lt;h2&gt;Access granted&lt;/h2&gt;
&lt;p&gt;After we got tired of guessing what some APIs would do and their parameters,
we created a new user and started our gray box testing.
The first thing we did was to check those precious API requests,
the parameters being sent, cookies, etc.
And then, the test we all were waiting for.
Changing the user from the request to another one...
Bingo! We got access. I mean, &lt;strong&gt;damn, we got access???&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After that shocking reveal, we kept digging for more endpoints to test.
The obvious one was to try to transfer money from/to other accounts, but there was a little problem.
An &lt;a class="reference external" href="https://en.wikipedia.org/wiki/One-time_password"&gt;OTP&lt;/a&gt; was required for each transaction.
So we first made a real transaction to check how the API works...
We realized that the OTP wasn't needed at all! &lt;strong&gt;It was being validated from the client side!&lt;/strong&gt;
So, yes, we were able to transfer money to any account &lt;strong&gt;and&lt;/strong&gt; from any account.
The user IDs were all incremental,
which means that you could easily steal money from everyone in the bank.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Damn! THAT IS SHOCKING! Someone could have easily emptied the accounts of everyone!
But it would have been very obvious if it has happened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or just steal 50 cents from everyone in the bank each month.
Do you really know exactly how much money do you have in your bank account?
Would you even care if only 50 cents were missing?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That's a low and long game to play, but still easy to track.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or you could just buy things, and pay with a wire transfer.
That way the money goes to several people instead of just one.
There are more ways you could exploit this without anyone noticing it, for sure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="interlude-about-boxes"&gt;
&lt;h2&gt;Interlude - about boxes&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;So, you did a black and gray box tests, is there a white one? and what those colors mean?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The color thing is about how much information and access you have to the application.
In a black box test, you don't know anything about the system internals.
In a gray box test, you have some knowledge of the application, like the architecture,
technologies being used and intern access to the application (like a user!).
And finally the white one, you have access to the source code.
But we didn't do that one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="consequences"&gt;
&lt;h2&gt;Consequences&lt;/h2&gt;
&lt;p&gt;At this point more than shocked, &lt;strong&gt;I was scare and disappointed&lt;/strong&gt;.
Scare of knowing this vulnerability existed and was &lt;strong&gt;easy to exploit&lt;/strong&gt;.
The money from all the people was in danger, maybe even their lives!
And disappointed to know that this application has been sold to this bank (and others!) for a lot of money,
and the company behind it has been doing so for more than 15 years.
And also, that all these banks have been for many security audits,
and &lt;strong&gt;NO ONE&lt;/strong&gt; found any of the things we reported.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;What do you mean with their lives were in danger?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You could disagree with me on this one,
personally I think that a bank is a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Safety-critical_system"&gt;SCS&lt;/a&gt;,
Nothing good can happen
if something goes wrong with people's money,
or all their savings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No one found that after several audits? WOW! Why do you think they missed them?
And how the developers missed that!?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sadly. Laziness, inexperience and negligence.
Here people don't like to go very &lt;em&gt;technical&lt;/em&gt;,
but instead go with degrees and certifications ($$$).
And when doing the audit, they only run automated tools
without doing any manual checking, or trust that the apps are secure,
or everyone in the team knows only about networking.
And from the developers side, what can I say?
Most developers here don't know what a unit test is,
or are afraid to say "I'm not qualified to build this, we need someone else".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="not-surprised"&gt;
&lt;h2&gt;Not surprised&lt;/h2&gt;
&lt;p&gt;I can't say this was &lt;em&gt;that&lt;/em&gt; shocking,
I mean, I have seen things like this on several apps in my country before,
but seeing this happening in a bank... that's another thing.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;So, what happened after you reported this?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Well, the company behind the application negated that the bug existed,
but we provided enough proofs in our report that invalidated all those claims.
And they even charged the bank for fixing those vulnerabilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WTF? That's unethical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yeah, but sadly I learned that's how the banking/enterprise world works.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We found more shocking things&lt;/strong&gt;, but I may tell you that in another occasion.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-end"&gt;
&lt;h2&gt;The end&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If you are a developer&lt;/strong&gt;, please always make sure to write tests for you code,
especially if you are dealing with sensitive data.
If you feel you may not have enough experience to work on something,
don't be afraid to say so, and ask for help.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are doing a security audit&lt;/strong&gt;,
please bring a diverse team,
or be clear with the client about what things you may not be dealing with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are looking to do a security audit at your company&lt;/strong&gt;,
I may know a great team with experts in different areas that &lt;a class="reference external" href="https://stsewd.dev/about"&gt;you could hire&lt;/a&gt;.&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This tale may have been inspired by a real situation,
and some things may have been exaggerated (or maybe not?).&lt;/p&gt;
&lt;/aside&gt;
&lt;/section&gt;</description><category>experience</category><category>security</category><guid>https://stsewd.dev/posts/a-tale-about-security-in-web-applications/</guid><pubDate>Fri, 18 Jun 2021 05:00:00 GMT</pubDate></item></channel></rss>