open_basedir confines PHP file operations to a configured path tree. Like disable_functions, it is enforced inside the interpreter's file API, so anything that reaches the filesystem outside that API, or that confuses the path check, escapes it. This matters when a foothold needs to read config/secrets or drop a payload beyond the allowed directory.
In-interpreter tricks#
Some bypasses stay within PHP by abusing how the check resolves paths:
chdir()plus relative traversal: walking in and out of permitted directories with sequences ofchdir()and..has historically desynchronized the resolved base from the checked base on some versions, allowing access outside the tree.- Symlink races (TOCTOU), not plain symlinks: PHP resolves a symlink to its real target before applying
open_basedir, so a static link under an allowed directory that points outside it is rejected like any outside path. The version-specific vector is a race: swap the path from a legitimate in-base target to a symlink pointing outside between PHP's check and its open. This needs a concrete resolution or race bug on the target's PHP build; do not expect a plain symlink to work. glob://and wrapper quirks: enumeration wrappers sometimes list entries the direct check would deny.
These are version-sensitive; test against the exact PHP build.
Going outside the file API#
The robust escapes leave the interpreter's file layer entirely:
- Subprocess: if any command-execution path is available (see disable_functions bypass), run
cat/cpas a child process;open_basedirdoes not apply to the spawned process. - FFI: call libc
fopen/open/readdirectly throughFFI::cdef, bypassing PHP's checked file functions. - Loadable native code: an
LD_PRELOADlibrary or loaded extension reads and writes the real filesystem.
Exploitation notes#
-
Check the current confinement and your position first:
echo ini_get('open_basedir'); echo getcwd(); -
open_basediranddisable_functionsare independent; a host may set one and not the other. If command execution is available, the subprocess route solves both at once. -
The in-interpreter traversal tricks are the only option when no execution and no FFI exist, so keep them in reserve for tightly locked PHP-only footholds.
Tools#
- Version-matched
open_basedirbypass snippets; FFI one-liners.
References#
- PHP manual: open_basedir, FFI
- Public open_basedir bypass writeups (version-specific)