Reputation:
I need to process some user-provided code on the server using PHP. The code is about to cover some very basic programming capabilities, for example: variables, literals, (preferably) functions, and some associated operations.
An option is to use the dangerous function of eval()
. For my specific case, it's overwhelmingly & redundantly full featured, apart from its security issues and performance bottlenecks. Sanitizing the tokens using token_get_all()
protects against Murphy, not Machiavelli! Regardless of its downsides, it's truly capable of what I'm tiring to achieve.
I've also checked the Symphony's ExpressionLanguage; it has some shortcomings:
Alas! a more sophisticated ExpressionLanguage would've sufficed.
I'm looking for something that allows some very basic "programming" capability to the users. Is there such a thing, if so, what is it? (even though it's written in another language, but is utilizable somehow on a server.)
If such a thing is not around, then how should I treat the eval()
to not to drawn me?! Or, as a last-resort, how may I design a such a simple programming capability? (Please elaborate on the matters :)
if
) and a repetition construct (e.g: for
loop)Upvotes: 5
Views: 509
Reputation: 1675
As per your limited and "basic" expectations from the "code", in addition to the other approaches represented, you may "create" some sort of "assembly" to translate your predefined single-instructions (that may have arguments) into executable code.
The "assembly" is just a means to establish a strong correspondence between the instructions and the equivalent actions. For example, you may define a dec
instruction to declare a variable with an optional initial-value:
dec <variable> [initialValue]
Having the dec myVar 4
, translates into the following executable code (expressed in PHP notation):
$myVar = 4;
Or, an add
instruction with two operands to add the specified amount onto the provided variable:
add <variable> <amount>
The aforementioned instruction can be utilized as: add myVar 2
, and should translate into the following:
$myVar += 2;
You might even associate advanced control structures (such as conditionals or iterations)
if <criteria>
(consequent)
fi
This is the basic idea resulted in the diverse programming languages, which encounters you with some pretty advanced topics! Nevertheless, your specific needs enable you to impose some revealing restriction to avoid cumbersomeness! For the sake of simplicity, the "arguments" should only support single entities; either "literals" or "variables". This indeed causes verbosity and the lack of algebraic-expression, but it makes everything extremely simpler for "you" by keeping you away from the advanced topics involved in expression-evaluations!
dec needsMore false
gte needsMore myVar 5
if needsMore
add myVar 3
fi
this should translate into:
$needsMore = false;
$needsMore = $myVar >= 5;
if ($needsMore) {
$myVar += 3;
}
As another simplification, prohibit the re-assignment of the "guard variable" in the repetition constructs. This restriction also enables you to forbid the "infinite loops", or the expensive ones, beforehand! You may impose whatever rules that best fit your needs that will also sanitizes the user-code in a secure manner on your preferred way!
To help your non-coder users, you should also create a visual-aid to encapsulate the assembly for easing and to help them with the "instructions" and the corresponding arguments (to insert, modify or remove them); specially on the "selection" and "repetition" constructs.
Upvotes: 2
Reputation: 4373
One thing that comes to mind is Twig, which is the template engine used by Symfony. It has a sandbox mode that you can enable, making it safe to evaluate untrusted code. I used it before to allow users to write their own templates.
Take a look at these resources:
An example of what is possible:
So although it's meant for templating, I think you could adapt it to achieve what you want.
Good luck!
Upvotes: 1
Reputation: 135
Maybe can you take a look on Docker. For example you can copy user code into a file on your server (without execute it) and then run it inside a container. This will allow you to :
Some example :
docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./myScript.php
This will execute file myScript.php into a new container based on php 7.4.5, once done container will be deleted.
Same thing using another PHP version :
docker run -v "$PWD":/usr/src -w /usr/src --rm php:5.6 php ./myScript.php
Usefull links :
Edit regarding performances :
Obviously running code using a container will be longer than running code directly from PHP.
For example, we can test it running the following code :
<?php
function reverseArray(array $array): array {
for ($i = 0; $i < count($array) / 2; $i++) {
$tmp = $array[$i];
$array[$i] = $array[count($array) -1 - $i];
$array[count($array) - 1 - $i] = $tmp;
}
return $array;
}
$tabToReverse = [5, 8, 95, 10, 6, 17, 42, 20];
echo 'Reversed array : '."\n";
echo implode(' ', reverseArray($tabToReverse));
Same code with a syntax error :
<?php
// syntax error
function reverseArray($array: array): array {
for ($i = 0; $i < count($array) / 2; $i++) {
$tmp = $array[$i];
$array[$i] = $array[count($array) -1 - $i];
$array[count($array) - 1 - $i] = $tmp;
}
return $array;
}
$tabToReverse = [5, 8, 95, 10, 6, 17, 42, 20];
echo 'Reversed array : '."\n";
echo implode(' ', reverseArray($tabToReverse));
The following PHP code will compare both executions (plus one with some syntax error):
<?php
/**
* Run code using eval
*/
$start = microtime(true);
$code = str_replace('<?php', '', file_get_contents('./reverseArray.php'));
echo eval($code)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using eval $duration\n";
/**
* Run code using container
*/
$start = microtime(true);
$cmd = 'docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./reverseArray.php';
exec($cmd, $result);
echo implode("\n", $result)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using container $duration\n";
/**
* Run code using container with an error
*/
$start = microtime(true);
$cmd = 'docker run -v "$PWD":/usr/src -w /usr/src --rm php:7.4.5 php ./reverseArrayWithError.php';
exec($cmd, $resultWithError);
echo implode("\n", $resultWithError)."\n";
$end = microtime(true);
$duration = $end - $start;
echo "Duration using container $duration\n";
The result on my laptop are :
php ./runCode.php
Reversed array :
20 42 17 6 10 95 8 5
Duration using eval 0.00031089782714844
Reversed array :
20 42 17 6 10 95 8 5
Duration using container 0.79519391059875
Parse error: syntax error, unexpected ':', expecting ')' in /usr/src/reverseArrayWithError.php on line 3
Duration using container 0.81346988677979
As you can see running up the container took time. But the code has been executed in a specific area.
In all cases, the frontend part will be the same, it will use some Ajax query in order to POST data on the server, wait for result and display it.
Note 1 : even if code is executed in a specific container it must be sanitized before, as user input should never be trusted.
Note 2 : using this architecture require to manage running containers in order to prevent overload. What happend if 10 000 users submit there code at the same time ? But I think this is another topic.
Upvotes: 3