XSS Intigriti challenge
2023-6-5 10:43:54 Author: infosecwriteups.com(查看原文) 阅读量:23 收藏

Allam Rachid (zhero_)

InfoSec Write-ups

Hello hunters, let me explain how did I overcome this XSS challenge set up by the bug bounty platform Intigriti. It may be a source of inspiration for some of you during your research.

source: somewhere on twitter
  • The challenge runs from the 23rd of May until the 29th of May, 11:59 PM CET (the challenge is therefore over at the time you read this)
  • Should work on the latest version of Chrome and FireFox
  • Should execute alert(document.domain)
  • Should leverage a cross-site scripting vulnerability on challenge-0523.intigriti.io domain
  • Shouldn’t be self-XSS or related to MiTM attacks
  • Should require no user interaction

It’s pretty clear, all we need to do is roll up our sleeves.

Source : https://app.intigriti.com/researcher/programs/intigriti/challenge0523/detail

Challenge page

The challenge page is quite minimalist: an input and a submit button supposed to pop up an alert window containing document.domain.

As you can imagine, this is a challenge and it won’t be as simple as submitting its alert() function directly. There are bound to be constraints, let’s take a look at the JavaScript code:

(()=>{
opener=null;
name='';
const xss = new URL(location).searchParams.get("xss") || '';
const characters = /^[a-zA-Z,'+\\.()]+$/;
const words =/alert|prompt|eval|setTimeout|setInterval|Function|location|open|document|script|url|HTML|Element|href|String|Object|Array|Number|atob|call|apply|replace|assign|on|write|import|navigator|navigation|fetch|Symbol|name|this|window|self|top|parent|globalThis|new|proto|construct|xss/i;
if(xss.length<100 && characters.test(xss) && !words.test(xss)){
script = document.createElement('script');
script.src='data:,'+xss
document.head.appendChild(script)
}
else{
console.log("try harder");
}
})()

The code is clear and easily understandable, the xss parameter retrieves our payload and must meet three conditions to be taken into consideration and therefore, to be concatenated to the ‘data:,’ value of the src attribute of the newly created script tag. Let’s take a closer look at these three conditions ;

  1. The payload length must be less than 100 characters.
xss.length<100 

2. The “characters” constant contains a regular expression acting here as a whitelist. If our payload contains a character that is not included in the regex then the condition will not be met.

const characters = /^[a-zA-Z,'+\\.()]+$/;
characters.test(xss)

The accepted characters are:

  • Alphabetical
  • The characters + , ( ) ‘ . \

3. The “words” constant here acts as a blacklist, if any of the words from our payload is there, the condition will not be met. Bad luck, these are the most interesting JavaScript keywords!

const words =/alert|prompt|eval|setTimeout|setInterval|Function|location|open|document|script|url|HTML|Element|href|String|Object|Array|Number|atob|call|apply|replace|assign|on|write|import|navigator|navigation|fetch|Symbol|name|this|window|self|top|parent|globalThis|new|proto|construct|xss/i;

If one of these conditions is not met, then the character string — motivating at the beginning, frustrating at the end — “try harder” appears in the console.

When I first came across Intigriti’s tweet announcing the start of the challenge, a first hint had already been revealed by the team :

The team had announced that it would reveal a hint every 100 likes, it was the first

They’re cool, the hint is self-explanatory: part of the secret is in the ECMA6 documentation.

You don’t know what ECMA is? Go to your favorite search engine! In the meantime, a small definition from the Mozilla documentation :

Ecma International (formally European Computer Manufacturers Association) is a non-profit organization that develops standards in computer hardware, communications, and programming languages.

On the web it is famous for being the organization which maintain the ECMA-262 specification (aka. ECMAScript) which is the core specification for the JavaScript language. Source: developer.mozilla.org

After some time reading the docs, I think I found something useful in our case, it is the Reflect object, two of its methods are interesting get and set, a getter and a setter therefore. I take a look at the blacklist and it’s encouraging: the words Reflect, get and set are not there!

How Reflect() works?

The two methods we are interested in are get and set :

  • Reflect.get() : Acts as an accessor, the first parameter is the target object, and the second parameter is the property. The method returns the value of the property in question.
  • Reflect.set() : Acts as a setter, the first parameter is the target object, the second parameter the property to modify (or create) and the third parameter the (new) value to assign.

Both methods have an additional optional parameter which we are not interested in here.

Ok it’s very interesting for us and it will do the job. I spare you my many trial and error and we move on to creating the payload.

As you could see earlier, most of the interesting keywords are blacklisted, but whether it’s in the context of a challenge like here or in the real world on a program, no matter how robust the security is, it’s enough an oversight or negligence to create a gaping hole in the security of the platform/website.

During my research phase, I noticed that the keyword “frames” was not blacklisted, which is very interesting because “frames” returns the “window” that was initially on the list!

frames property returns the Window object

With the Window object, the first thing that came to my mind was to change the value of “location” via the “set” method of Reflect().

It was a bit complicated because it was necessary to have the character colon (:) to specify the protocol (javascript:), but not being whitelisted (regex) it was impossible to use it directly. After several attempts, I had a payload that worked but was longer than 100 characters (106!) :

Reflect.set(frames,'locatio'+'n','javasc'+'ript'+origin.at('zhero'.length)+'ale'+'rt(do'+'cument.domain)')
  • Concatenations bypass the blacklist:
    - ‘location’+’n’
    - ‘javasc’+’ript’
    - ‘ale’+’rt(do’
    - ‘cument.domain)’
  • The at() method is used to retrieve the character located at the index specified in the parameter and origin returns ‘https://....', so the colon character (:) is at index 5. As the use of numbers is prohibited (whitelist), I provide a character string with a length of 5 on which I call the length() method so that it returns me the number that will serve as a parameter for the at() method.

Close to the goal but it does not pass for the challenge.

Change of strategy and end of the suspense: the window object — returned by “frames” — has the alert() method… It looks much simpler than expected. What if we used the Reflect getter to call alert via a concatenation?

Reflect.get(frames,'aler'+'t')(Reflect.get(frames,'docum'+'ent').domain)

It works, with 72 characters!

XSS

A brief explanation of the payload

  • Reflect.get(frames, ‘aler’+’t’) ➜ retrieves the alert method from frames (and therefore from window)
  • (Reflect.get(frames, ‘docum’+’ent’).domain) ➜ Parentheses are used to execute the alert method. Opening a simple alert window is not enough, you have to execute document.domain in it. For this, we call -again- the getter of Reflect() to have the “document” property of “frames” (and therefore window) then -once the property is returned- we directly access the “domainproperty!

Ok the challenge is validated, it’s good. But what about this payload in the real world? You will agree, the victim does not have too much to worry about. Let’s see if we can do better for a real context.

Source: somewhere on twitter

I managed to get an arbitrary XSS via a payload in the URL, for this I took advantage of the fact that the various filters only check the “xss parameter and not the whole URL.

To do this, I placed an anchor in the URL containing the javascript code to be executed via “javascript:. The anchor is not subject to filters, any javascript code can be executed without any limitation.

The value of the “xss” parameter contains the JS code that allows you to retrieve the value of the anchor, and assign it to the “location” property of the frames object :

https://challenge-0523.intigriti.io/challenge/xss.html?xss=Reflect.set(frames%2C%27locatio%27%2B%27n%27%2CReflect.get(frames%2C%27locatio%27%2B%27n%27).hash.split(%27\\%27).pop())#\javascript:alert('Im finally free from my shackles, saying "javascript", "eval" and "document" doesn`t scare me anymore!')
Arbitrary XSS

Explanation of the payload

1️⃣ https://challenge-0523.intigriti.io/challenge/xss.html?xss= ➜ I think it’s clear

2️⃣ Reflect.set(frames,’locatio’+’n’, ➜ As explained previously we indicate that we want to assign a new value to the “location” property of “frames

3️⃣ Reflect.get(frames,’locatio’+’n’).hash.split(‘\\’).pop()) ➜ This part is the third parameter of the “set method of Reflect(), so it will be the value to assign. It will allow us to retrieve the value of the anchor in the URL:

We start by retrieving “location” via the Reflect getter, then we access “hash” which retrieves the “#” in the URL followed by the fragment identifier of the URL.

I used a backslash (\) between the “#” character and my payload to act as a separator. I just have to use the split method followed by the pop method to get the part that interests me.

4️⃣ #\javascript:alert(‘Im finally free from my shackles, saying “javascript”, “eval” and “document” doesn`t scare me anymore!’) ➜ The anchor, followed by “javascript:” and the code to execute. As explained earlier it is possible to execute anything there without character limit. The fragment is not being checked by the various filters.

source: somewhere on twitter

It was my first Intigriti challenge, I enjoyed it.

🥷🏽Update: My write-up has been selected and I am one of the three winners, thanks to Intigriti.

Thank you for reading me, if you have any questions do not hesitate to let me know. Happy hunting 🏹

My Twitter account : https://twitter.com/blank_cold


文章来源: https://infosecwriteups.com/xss-intigriti-challenge-dae2dba1cb4c?source=rss----7b722bfd1b8d--bug_bounty
如有侵权请联系:admin#unsafe.sh