HTML Injection to Stored XSS and Account Takeover
文章描述了一个复杂的漏洞链攻击过程:通过HTML注入结合jQuery的replaceWith方法,在目标网站上实现了存储型XSS,并最终导致账户接管。攻击者利用Cloudinary图片服务特性,在图片中嵌入恶意代码,窃取用户令牌并接管账户。 2025-4-30 14:11:41 Author: medium.com(查看原文) 阅读量:9 收藏

Renwa

bug chains :)

HTML Injection to Stored XSS and Account Takeover This bug was really a nice bug that had many parts and took a lot of effort to build the payload so I would like to share the journey with you. I will refer to it as target domain, I have been hunting on the application a quite long time and i knew about most of the parts. Application is a shop and item selling website in your shop bio there is a very limited HTML injection which is sanitized by a servers-side sanitizer like DOMPurify, looking at the JS files of the application I found this code snippet:

$(document).on('click', '.remote-pagination-container .pagination a', function (e) {
e.preventDefault();

if (isDisabled(e.target)) {
return;
}

const containerSelector = '.remote-pagination-container';
let $container = $(containerSelector);
const updatingContainerClass = $container.data('updating-container-class');
const url = $(this).prop('href');

// ensure that requests go to the current origin
const u = new URL(url, window.origin) // use the current host if relative
if (u.origin !== window.origin) {
return;
}

if (updatingContainerClass) {
$container.spin();
$container.addClass(updatingContainerClass);
}

$('html, body').animate({ scrollTop: 0 }, 400);

APIRequest.get(url).done((html) => {
$container.replaceWith(html);

Let’s look what this code does

  1. Create a Click event listener on remote-pagination-container .pagination a
  2. The jQuery selector will look for an element that has remote-pagination-container then looks for the child to have pagination class finally it will pick the element that is child of this elements and it's <a> tag
  3. Using const url = $(this).prop('href'); it will get the href of the <a> tag
  4. After getting the href it will check if the url is same origin target.com and send an ajax request to it using APIRequest.get(url).done((html)
  5. If the request is success and CORS allowed it will assign its response to the container tag using $container.replaceWith(html);

Most developers don't know jQuery .replaceWith() is same as .html() now we have a potential attack vector. Let's start exploring how we can abuse this, all we need is a response on the site with our controlled data, the content type doesn't matter. First thing came to mind was using API endpoints, the app used REST style API to get the data and showed in application/json format. I used /api/shops/renwashop which will return a JSON data with all my shop details without sanitization, sadly it didn't work because the app or jQuery will check response content-type if it's JSON it will parse it then in my cause it will be .html([Object object])Second thing I tried was using graphql which returns an array but again the app will parse the data to array and we can't get the XSS. Next thing I remembered about an endpoint which the response is text/plain and if we send a new line to it will through an error with our data like this:

Using it and it didn’t work because we made the app throw an error the response code was 400 and the app will only consider 200 OK as a successful request, all other response types will fail. Next thing I tried was the app had a feature to export your items as a CSV file, you will request a file and it will generate a /exports/file/<token> which will redirect to an AWS S3 bucket to your CSV file, tried it and didn't work because the S3 bucket was a cross origin and didn't have Access-Control-Allow-Origin header which blocks the app from reading the response.

All these things failed but I didn’t stop exploring the app for potentially anything we can use, I looked at how images are uploaded in the app. It used a third party company called Cloudinary and a separate domain images.target.com but I found something Interesting. In the app there was some type of images which was loaded locally like this /cloudinary/images/image_id?options[something]=something then it will redirect to images.target.com/image/upload/s--zuNenGNo--/image_id but there was a problem the image was converted to a smaller one in dimension , looking at cloudinary documentation https://cloudinary.com/documentation/image_transformations#delivery_types I found out if you supply options[delivery_type]=upload it will load the original image without compressing and conversion.

Then I grabbed exiftools and tried to add my XSS payload to a comment or artist header in the image but sadly all those headers were removed, I had to go the hard way. Looked at jpeg file type structure I found a location which you can insert your payload inside it without harming the image, I used online tool https://hexed.it/ and put my payload inside it like this

Then putting it all together:

<div class="remote-pagination-container"><br><div class="pagination"><br><a href="/cloudinary/images/image_id?options%5Bdelivery_type%5D=upload">Click Here<br><br><br><br></a></div></div>

The app used a token and communication with api.target.com so using the XSS I stole the token inside local storage and redirected to my domain to steal all the user info and showing it, video POC:


文章来源: https://medium.com/@renwa/html-injection-to-stored-xss-and-account-takeover-e54a31343ce8?source=rss-3f8ae70e3957------2
如有侵权请联系:admin#unsafe.sh