Skip to content
A tool for downloading, compiling and deploying PHP versions to chroot jails.
PHP Hack
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config
http
src
tests
.gitignore
README.md
TODO.md
_config.yml
composer.json
composer.lock
index.php
install

README.md

System Requirements

  • build-essential
  • pkg-config
  • libcurl4-openssl-dev
  • libxml2-dev
  • libtidy-dev

Optional

  • bison
  • re2c

Quick install

$ ./install <(int)version> <(bool)debug>

Downloading & Compiling

<?php

use Versyx\Jail\Downloader;
use Versyx\Jail\Compiler;

require __DIR__ . '/../config/bootstrap.php';

$debug = true;
$version = "7.1.30";
init(new Downloader($debug), new Compiler($debug), $version);

function init(Downloader $downloader, Compiler $compiler, string $version)
{
    try {
        $php = $downloader->setVersion($version)->download();
        $compiler->compile($php->getVersion(), $php->getTarget());
    } catch (\Exception $e) {
        echo $e->getMessage();
    }
}

Deploying

Creating the chrooted Jail

The jailer module is incomplete, in the meantime please follow these instructions:

Create the jail:

$ mkdir /opt/phpjail
$ cd /opt/phpjail

Create the filesystem

$ mkdir bin etc dev lib lib64 usr
$ mkdir etc/alternatives

Create mount points for PHP versions

$ mkdir php-7.0.33 php-7.1.30 php-7.2.19...

Set permissions

$ chmod -R 0711 /opt/phpjail
$ chown -R root:root /opt/phpjail

Mount filesystem in read-only mode

mount -o bind,ro /bin /opt/phpjail/bin
mount -o bind,ro /dev /opt/phpjail/dev
mount -o bind,ro /lib /opt/phpjail/lib
mount -o bind,ro /lib64 /opt/phpjail/lib64
mount -o bind,ro /usr /opt/phpjail/usr
mount -o bind,ro /etc/alternatives /opt/phpjail/etc/alternatives
mount -o bind,ro /tmp/php-7.0.33 /opt/phpjail/php-7.0.33
mount -o bind,ro /tmp/php-7.1.30 /opt/phpjail/php-7.1.30
mount -o bind,ro /tmp/php-7.2.19 /opt/phpjail/php-7.2.19

Enabling the worker

You'll need to allow www-data to run the worker script as a privileged user, add entries for each compiled version to /etc/sudoers like so:

www-data ALL =(ALL) NOPASSWD: /opt/phpjail/php-7.3.6/bin/php /var/www/php-jailer/http/worker.php 7.3.6
www-data ALL =(ALL) NOPASSWD: /opt/phpjail/php-7.0.33/bin/php /var/www/php-jailer/http/worker.php 7.0.33

This will restrict www-data's sudo privileges to only running the worker.

How it Works

The PHP code and version is base64 encoded and submitted to http/manager.php, the manager then base64 decodes the data and runs a check on the code input against disabled functions, if the check comes back clean, a new process is created with stream resources:

$proc = proc_open("sudo /opt/phpjail/php-$ver/bin/php /var/www/php-jailer/http/worker.php $ver", [
    0 => ["pipe", "rb"],
    1 => ["pipe", "wb"],
    2 => ["pipe", "wb"]
], $pipes);

The PHP code is passed to http/worker.php from the manager via STDIN, the worker then creates a temporary file in /opt/phpjail, sets its permissions to 0444 and then executes the file using the selected PHP version instance, which is chrooted to /opt/phpjail as user nobody. If the code takes longer than five seconds to execute, the process will terminate.

$starttime = microtime(true);
$unused = [];
$ph = proc_open('chroot --userspec=nobody /opt/phpjail /php-' . $argv[1] .'/bin/php ' . escapeshellarg(basename($file)), $unused, $unused);
$terminated = false;
while (($status = proc_get_status($ph)) ['running']) {
    usleep(100 * 1000);
    if (!$terminated && microtime(true) - $starttime > MAX_RUNTIME_SECONDS) {
        $terminated = true;
        echo 'max runtime reached (' . MAX_RUNTIME_SECONDS . ' seconds), terminating...';
        pKill($status['pid']);
    }
}

proc_close($ph);

Example Deployment

Tested on Ubuntu 16.04 using Apache 2.4 and PHP 7.2, 7.3

example deployment

You can’t perform that action at this time.