XSS at Adafruit

November 24, 2024 in security ‐ 8 min read

Note: This article has been reviewed by Adafruit prior to publication.

This article will highlight the process that lead to me finding a XSS (Cross site scripting) vulnerability on adafruit.com

As usual, I will not only show the vulnerability but also the methodology used and interesting things that happened along the way.

Step One: Keeping Your White Hat Clean

Before attacking any target, you need to make sure you are allowed to do so. That involves both your local laws and the target’s stance on vulnerability research.

I am not a lawyer so I will not comment on the first part. When it comes to checking your targets vulnerability disclosure policy a good place to look at is https://<TARGET>/.well-known/security.txt

If we check out https://www.adafruit.com/.well-known/security.txt we find that they have a bug bounty program. The disclosure guidelines and in-scope domains for Adafruit.com can be found at: https://www.adafruit.com/reportingsecurityissues. All reports should be submitted to security@adafruit.com

This policy is very important for us because we now know we can hunt for and what not. Also, we know where and where not to hunt. If in doubt, we can always come back here and check whether the action we intend to perform or report we intend to submit is covered by the responsible disclosure policy

Setup for Testing

Before starting to test you should try and avoid the risk of compromising some other users data. For this purpose you should set up two accounts. One that you intend to compromise and one that you perform the testing with.

Also, we want all of our traffic to be routed through a MITM proxy like burp. Once that is done, we can start hunting.

Get a Feel for Your Target

Before starting any automated testing it first makes sense to get a feel for your target. The vulnerabilities usually hide at the roads least traveled.

When creating your account you should realize this happens on accounts.adafruit.com. Thinking back to our scope, this target is fair game.

When looking at the top of the webshop we see a bunch of links.

Top bar of adafruit.com showing links

They point to

  • learn.adafruit.com
  • blog.adafruit.com
  • forums.adafruit.com
  • io.adafruit.com

So without any subdomain enumeration or spidering, we already found a bunch of attack surface.

From using the site a bit the most promising features in the shop seem to be:

  • Addresses: add, delete and modify saved shipping addresses
  • Order history: take a look at past orders
  • Wishlists: curate private or public wishlists
  • Shopping cart: save and buy items

Each of these features has a bunch of ways to be attacked.

Lots of Dead Ends

Thinking of shipping addresses, we might be able to smuggle something into our address that might get used insecurely somewhere else on the site. I created a bunch of addresses that contained HTML and Java Script. Some of the used characters get sanitized by the backend. I am unsure, whether they get sanitized before being saved in the database or when they are being used. I tried to bypass the sanitization by injecting null bytes, random data, double encoding and other tricks without any success.

Furthermore, I verified if my testing account was able to modify, delete or view the address of my main account via an IDOR (Insecure Direct Object reference). Whenever I found some place that would access address information in any way I would replay the request with the Address ID of the second account. No endpoint seemed to either reflect user input unsanitized nor did they seem to allow manipulation or data leakage of other users accounts.

The same goes for wishlists. I made sure that private wishlists were unaccessible even after having been made public once. I stuffed payloads at every place possible, using URL parameters and POST bodies.

None of my inputs got reflected insecurely nor was I able to manipulate, delete or access wishlists I was not supposed to. Adding items to the wishlist shows every item has a unique ID. I tried adding every possible item to my wishlist.

This could have resulted in

  • revealing of unlisted items (unlisted items, testing items with no or negative costs etc.)
  • integer overflow of the wishlist costs, resulting in wishlists with negative costs

This resulted in funny wishlist costs like 19.81654E… however an overflow was not able to be achived. I was also unable to find unlisted items, some out of stock items could be added or items with missing descriptions or images… however this is not a vulnerability.

When accessing the other subdomains like forums.adafruit.com I realized that they are using oauth. Oauth is notoriously easy to mess up when implementing, resulting in possible account takeovers. I tried to redirect the authentication flow using implicit grant types as described by Portswigger without any success.

I was hoping that I could upload a profile picture to maybe find a upload filter bypass that could lead to RCE. When checking out account.adafruit.com however I found that I can only choose from a set of avatars and select a background color.

Looking at the POST generated when changing the profile, I found that the color value gets reflected in the CSS. This could potentially lead to a CSS injection vulnerability. Theoretically one could implement a CSS Keylogger.

I changed the parameter from user[favorite_color]=#471010 to user[favorite_color]=#471010;position: absolute.

I already got my hopes up when I saw that the size of my avatar had changed.

A before and after view of the injected CSS

However, when checking the backend response, I saw that even though the backend returned a HTTP 200 (OK), error messages were embedded in the HTML.

Error messages visible in HTML

The injection worked only in the frontend due to the HTTP 200, however the injected color did not get saved in the backend.

The errors are

  • Favorite color is the wrong length (should be 7 characters)
  • Favorite color is invalid

I tried finding valid shorter colors and short CSS for a POC and confusing the parsing in the backend. Both without any success.

Honorable mentions

When checking out the wishlist I found that I can have adafruit generate me a quote.

A button labeled &lsquo;get quote&rsquo; being clicked. A popup labeled &lsquo;Generate quote&rsquo; that lets the user enter a coupon code and select from a drop down menu a billing address, a shipping address and a shipping method.

Using this feature results in a POST to quote.php

POST /quote.php HTTP/2
Host: www.adafruit.com
[...]

coupon=&wid=599267&shipto=1433403&billto=1433403&shipping_text=DHL+Express+%281+x+0.17lbs%29+%28Express+Worldwide%29&shipping_cost=%2443.77

The shipto and billto, as anywhere else that I found, were IDOR proof. However some parameters seemed odd.

  • shipping_text - The text displayed on the final quote, possible attack vector for reflected DOM / XSS vulnerabilities
  • shipping_cost - The shipping cost, this should definitely not be at the users control

When modifying these values, I found that indeed the modified values get reflected in the DOM.

Modified values visible on the website. Shipping Text is now Ori Express. Shipping cost is -999999999$. Total is -999999984.05$.

Turns out those values get passed into Javascript. I tried injecting HTML or JavaScript using many tricks, but I was unable to turn this into an XSS. I was aware that this is out of scope but thought Adafruit might still be interested in knowing about this.

  • 16.11.2024: Reported to Adafruit
  • 19.11.2024: Adafruit asked for additional information as my initial report was not detailed enough
  • 19.11.2024: Sent additional details to Adafruit
  • 22.11.2024: Adafruit determined that this behavior does not constitute a security vulnerability, but my findings helped them improve their endpoint validation processes. As a token of appreciation for my thorough research and contribution, they offered me $25 in Adafruit store credit. I accepted thankfully.

When checking the endpoint again after the 22nd of November I found that the POST to quote.php now contains a quote_verification parameter that looks like some sort of hash. When modifying the shipping_text or shipping_cost the request fails.

Finding the XSS

While trying to find less tested functionalities, I started digging into the order history logic of adafriut. I was hoping there might be some IDOR here or a place that reflects user input unsanitized for example in PDFs…

Order history page showing past orders with the option to view order details

When checking out the order details I found that the address is reflected. Also, there is a “view printable invoice” function that also reflects the address.

Printable invoice reflecting the address

In order to inject payloads into the order history logic I actually needed to order things. The cheapest product I was able to find is the 5$ gift card because it has no shipping.

Upon buying a gift card I found that I could input “special Instructions”. These will be seen by the recipient of the gift card. As has become second nature, I injected some HTML in there. Pretty much throwing things at the wall to see what sticks.

Injecting HTML into the fields Recipient Name, From and Message

I was hoping this would be put unsanitized into a Mail sent to the recipient or displayed on using the gift card on another account. This was not the case.

However when I checked the “Printable invoice”, I found that indeed my injected HTML was part of the invoice and got rendered.

Printable invoice showing injected HTML being rendered

WAF Bypass

From looking at the URL I found that the endpoint reflecting my user input in the DOM is PHP.

https://www.adafruit.com/invoice.php?order_id=XXXXXXXXXX

I was hoping to get an RCE(remote code execution) PoC by injecting something like <? php phpinfo(); ?>.

Error Page showing that Cloudflare has blocked the request

I have spent a lot of time researching how to bypass the Cloudflare WAF. One way is to expose the actual IP address of the webshop behind cloudflare. I had to use OSINT methods as I had not yet found an SSRF (Server Side Request Forgery) or another way to leak the real IP of the webshop. All IPs I was able to find this way did not host Adafruits Webshop.

So I had to find a way to confuse Cloudflares WAF. After lots of research and not working payloads, I finally was able to sneak past an XSS payload.

“><img only src=1 onerror=alert()>

Printable invoice page showing triggering XSS payload

Reporting the Vulnerability

  • 17.11.2024: Vulnerability got reported to Adafruit
  • 19.11.2024: Adafruit asked for additional details missing in my report
  • 19.11.2024: Additional details sent to Adafruit
  • 22.11.2024: Adafruit has validated the finding, confirmed the vulnerability and fixed it
  • 26.11.2024: Adafruit has paid me a bugbounty of $50 and added me to https://www.adafruit.com/responsibledisclosurethanks/

All my reports have been handled very fast.
The tone was friendly and my efforts felt welcome.
10/10 would pentest again 👍

Cheers, Ori