mirror of
https://github.com/whyour/qinglong.git
synced 2025-12-13 07:25:05 +08:00
Fix subprocess bypass by wrapping child_process and subprocess modules
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
68d06acf6c
commit
38d1f67301
|
|
@ -212,7 +212,112 @@ if (sandboxEnabled) {
|
|||
}
|
||||
}
|
||||
|
||||
// Prevent requiring the original fs module to bypass sandbox
|
||||
// Wrap child_process to prevent sandbox bypass via subprocesses
|
||||
let childProcessWrapped = false;
|
||||
if (sandboxEnabled) {
|
||||
// We need to get child_process before wrapping Module.prototype.require
|
||||
const childProcess = require('child_process');
|
||||
const originalSpawn = childProcess.spawn;
|
||||
const originalExec = childProcess.exec;
|
||||
const originalExecSync = childProcess.execSync;
|
||||
const originalExecFile = childProcess.execFile;
|
||||
const originalExecFileSync = childProcess.execFileSync;
|
||||
const originalFork = childProcess.fork;
|
||||
|
||||
// Helper to ensure NODE_OPTIONS and PYTHONPATH are set for subprocesses
|
||||
function ensureSandboxEnv(options = {}) {
|
||||
const env = { ...process.env, ...options.env };
|
||||
|
||||
// Ensure NODE_OPTIONS includes the sandbox
|
||||
const sandboxPreload = path.join(__dirname, 'sandbox.js');
|
||||
if (!env.NODE_OPTIONS) {
|
||||
env.NODE_OPTIONS = '';
|
||||
}
|
||||
if (!env.NODE_OPTIONS.includes(sandboxPreload)) {
|
||||
env.NODE_OPTIONS = `-r ${sandboxPreload} ${env.NODE_OPTIONS}`.trim();
|
||||
}
|
||||
|
||||
// Ensure PYTHONPATH includes the sandbox directory
|
||||
if (!env.PYTHONPATH) {
|
||||
env.PYTHONPATH = '';
|
||||
}
|
||||
if (!env.PYTHONPATH.includes(__dirname)) {
|
||||
env.PYTHONPATH = `${__dirname}:${env.PYTHONPATH}`;
|
||||
}
|
||||
|
||||
return { ...options, env };
|
||||
}
|
||||
|
||||
// Wrap spawn
|
||||
childProcess.spawn = function(...args) {
|
||||
if (args[2]) {
|
||||
args[2] = ensureSandboxEnv(args[2]);
|
||||
} else if (args.length >= 3) {
|
||||
args[2] = ensureSandboxEnv({});
|
||||
}
|
||||
return originalSpawn.apply(childProcess, args);
|
||||
};
|
||||
|
||||
// Wrap exec
|
||||
childProcess.exec = function(...args) {
|
||||
const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined;
|
||||
const optionsIndex = callback ? args.length - 2 : args.length - 1;
|
||||
|
||||
if (args[optionsIndex] && typeof args[optionsIndex] === 'object') {
|
||||
args[optionsIndex] = ensureSandboxEnv(args[optionsIndex]);
|
||||
} else if (optionsIndex > 0) {
|
||||
args.splice(optionsIndex, 0, ensureSandboxEnv({}));
|
||||
}
|
||||
return originalExec.apply(childProcess, args);
|
||||
};
|
||||
|
||||
// Wrap execSync
|
||||
childProcess.execSync = function(...args) {
|
||||
if (args[1]) {
|
||||
args[1] = ensureSandboxEnv(args[1]);
|
||||
} else {
|
||||
args[1] = ensureSandboxEnv({});
|
||||
}
|
||||
return originalExecSync.apply(childProcess, args);
|
||||
};
|
||||
|
||||
// Wrap execFile
|
||||
childProcess.execFile = function(...args) {
|
||||
const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined;
|
||||
const optionsIndex = callback ? args.length - 2 : args.length - 1;
|
||||
|
||||
if (args[optionsIndex] && typeof args[optionsIndex] === 'object') {
|
||||
args[optionsIndex] = ensureSandboxEnv(args[optionsIndex]);
|
||||
} else if (optionsIndex > 1) {
|
||||
args.splice(optionsIndex, 0, ensureSandboxEnv({}));
|
||||
}
|
||||
return originalExecFile.apply(childProcess, args);
|
||||
};
|
||||
|
||||
// Wrap execFileSync
|
||||
childProcess.execFileSync = function(...args) {
|
||||
if (args[2]) {
|
||||
args[2] = ensureSandboxEnv(args[2]);
|
||||
} else if (args.length >= 3) {
|
||||
args[2] = ensureSandboxEnv({});
|
||||
}
|
||||
return originalExecFileSync.apply(childProcess, args);
|
||||
};
|
||||
|
||||
// Wrap fork
|
||||
childProcess.fork = function(...args) {
|
||||
if (args[2]) {
|
||||
args[2] = ensureSandboxEnv(args[2]);
|
||||
} else if (args.length >= 3) {
|
||||
args[2] = ensureSandboxEnv({});
|
||||
}
|
||||
return originalFork.apply(childProcess, args);
|
||||
};
|
||||
|
||||
childProcessWrapped = true;
|
||||
}
|
||||
|
||||
// Prevent requiring the original fs or child_process modules to bypass sandbox
|
||||
const originalRequire = Module.prototype.require;
|
||||
Module.prototype.require = function(id) {
|
||||
const module = originalRequire.apply(this, arguments);
|
||||
|
|
@ -222,6 +327,9 @@ Module.prototype.require = function(id) {
|
|||
return fs;
|
||||
}
|
||||
|
||||
// For child_process, we already wrapped it above, so just return it
|
||||
// (no need to re-require as that would cause recursion)
|
||||
|
||||
return module;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -324,3 +324,85 @@ if sandbox_enabled:
|
|||
Path.chmod = sandboxed_path_chmod
|
||||
except:
|
||||
pass
|
||||
|
||||
# Wrap subprocess to prevent sandbox bypass via subprocesses
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
# Helper to ensure PYTHONPATH is set for subprocesses
|
||||
def ensure_sandbox_env(env=None):
|
||||
if env is None:
|
||||
env = os.environ.copy()
|
||||
else:
|
||||
env = env.copy()
|
||||
|
||||
# Ensure PYTHONPATH includes the sandbox directory
|
||||
sandbox_dir = os.path.dirname(__file__)
|
||||
if 'PYTHONPATH' not in env:
|
||||
env['PYTHONPATH'] = ''
|
||||
if sandbox_dir not in env['PYTHONPATH']:
|
||||
env['PYTHONPATH'] = f"{sandbox_dir}:{env['PYTHONPATH']}"
|
||||
|
||||
return env
|
||||
|
||||
# Store original functions
|
||||
original_popen = subprocess.Popen
|
||||
original_run = subprocess.run
|
||||
original_call = subprocess.call
|
||||
original_check_call = subprocess.check_call
|
||||
original_check_output = subprocess.check_output
|
||||
|
||||
# Wrap Popen
|
||||
class SandboxedPopen(subprocess.Popen):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'env' in kwargs:
|
||||
kwargs['env'] = ensure_sandbox_env(kwargs['env'])
|
||||
else:
|
||||
kwargs['env'] = ensure_sandbox_env()
|
||||
original_popen.__init__(self, *args, **kwargs)
|
||||
|
||||
subprocess.Popen = SandboxedPopen
|
||||
|
||||
# Wrap run
|
||||
def sandboxed_run(*args, **kwargs):
|
||||
if 'env' in kwargs:
|
||||
kwargs['env'] = ensure_sandbox_env(kwargs['env'])
|
||||
else:
|
||||
kwargs['env'] = ensure_sandbox_env()
|
||||
return original_run(*args, **kwargs)
|
||||
|
||||
subprocess.run = sandboxed_run
|
||||
|
||||
# Wrap call
|
||||
def sandboxed_call(*args, **kwargs):
|
||||
if 'env' in kwargs:
|
||||
kwargs['env'] = ensure_sandbox_env(kwargs['env'])
|
||||
else:
|
||||
kwargs['env'] = ensure_sandbox_env()
|
||||
return original_call(*args, **kwargs)
|
||||
|
||||
subprocess.call = sandboxed_call
|
||||
|
||||
# Wrap check_call
|
||||
def sandboxed_check_call(*args, **kwargs):
|
||||
if 'env' in kwargs:
|
||||
kwargs['env'] = ensure_sandbox_env(kwargs['env'])
|
||||
else:
|
||||
kwargs['env'] = ensure_sandbox_env()
|
||||
return original_check_call(*args, **kwargs)
|
||||
|
||||
subprocess.check_call = sandboxed_check_call
|
||||
|
||||
# Wrap check_output
|
||||
def sandboxed_check_output(*args, **kwargs):
|
||||
if 'env' in kwargs:
|
||||
kwargs['env'] = ensure_sandbox_env(kwargs['env'])
|
||||
else:
|
||||
kwargs['env'] = ensure_sandbox_env()
|
||||
return original_check_output(*args, **kwargs)
|
||||
|
||||
subprocess.check_output = sandboxed_check_output
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user