I'm working on a project where the admin panel is just a shell that do some actions depending on what string you input.
By shell i mean an input box where you type for example
delete user 1
and the user with id 1 is deleted.
I have planned this for about 4 months and i have written all the commands that the app could manage.
I have some problem to make this system. I was thinking about this solution:
$c = explode(' ', $input);
if ($c[0] == 'delete' and $c[1] == 'user' and count($c) === 3)
{
$c[2] = $id;
delete_user_by_id($id);
}
But i think it is not that well designed and i'm sure that it could be improved.
I noticed that exists regular expression and that they could be better than this but i can't really figure out how to use them in the previous example.
Any idea?
{Notice that a part of the string is variable (delete user VARIABLE)}
Instead of a bunch of if statements, you should create a class for each command, which takes the information as an argument and does something. You just need to load the class when it's called.
$command = 'delete user 1';
$parsed = explode($command, ' ', 2);
load_class($parsed[0]); // hypothetical loader
if (class_exists($parsed[0])) {
$class = new $parsed[0]();
$class->execute($parsed[1]);
} else {
die('Sorry, invalid command');
}
I think exploding on spaces is cleaner than using a regex.
You might be able to clean up the code a bit with a switch statement, and trimming the input before you explode it:
$c explode(' ', trim($input));
switch(strtolower($c)) {
case 'delete' :
delete_user_by_id($c[2]);
break;
case 'update' :
update_user_by_id($c[2]);
break;
...
default :
echo 'Invalid command: '.$c;
}
Related
I wanted to know if it's possible to make a PHP mention system with usernames with space ?
I tried this
preg_replace_callback('##([a-zA-Z0-9]+)#', 'mentionUser', htmlspecialchars_decode($r['content']))
My function:
function mentionUser($matches) {
global $db;
$req = $db->prepare('SELECT id FROM members WHERE username = ?');
$req->execute(array($matches[1]));
if($req->rowCount() == 1) {
$idUser = $req->fetch()['id'];
return '<a class="mention" href="members/profile.php?id='.$idUser.'">'.$matches[0].'</a>';
}
return $matches[0];
It works, but not for the usernames with space...
I tried to add \s, it works, but not well, the preg_replace_callback detect the username and the other parts of the message, so the mention don't appear...
Is there any solution ?
Thanks !
I know you said that you just removed the ability to add a space, but I still wanted to post a solution. To be clear, I don't necessarily think you should use this code, because it probably is just easier to keep things simple, but I think it should work still.
Your major problem is that almost every mention will incur two lookups because #bob johnson went to the store could be either bob or bob johnson and there's no way to determine that without going to the databases. Caching will greatly reduce this problem, luckily.
Below is some code that generally does what you are looking for. I made a fake database using just an array for clarity and reproducibility. The inline code comments should hopefully make sense.
function mentionUser($matches)
{
// This is our "database" of users
$users = [
'bob johnson',
'edward',
];
// First, grab the full match which might be 'name' or 'name name'
$fullMatch = $matches['username'];
// Create a search array where the key is the search term and the value is whether or not
// the search term is a subset of the value found in the regex
$names = [$fullMatch => false];
// Next split on the space. If there isn't one, we'll have an array with just a single item
$maybeTwoParts = explode(' ', $fullMatch);
// Basically, if the string contained a space, also search only for the first item before the space,
// and flag that we're using a subset
if (count($maybeTwoParts) > 1) {
$names[array_shift($maybeTwoParts)] = true;
}
foreach ($names as $name => $isSubset) {
// Search our "database"
if (in_array($name, $users, true)) {
// If it was found, wrap in HTML
$ret = '<span>#' . $name . '</span>';
// If we're in a subset, we need to append back on the remaining string, joined with a space
if ($isSubset) {
$ret .= ' ' . array_shift($maybeTwoParts);
}
return $ret;
}
}
// Nothing was found, return what was passed in
return '#' . $fullMatch;
}
// Our search pattern with an explicitly named capture
$pattern = '##(?<username>\w+(?:\s\w+)?)#';
// Three tests
assert('hello <span>#bob johnson</span> test' === preg_replace_callback($pattern, 'mentionUser', 'hello #bob johnson test'));
assert('hello <span>#edward</span> test' === preg_replace_callback($pattern, 'mentionUser', 'hello #edward test'));
assert('hello #sally smith test' === preg_replace_callback($pattern, 'mentionUser', 'hello #sally smith test'));
Try this RegEx:
/#[a-zA-Z0-9]+( *[a-zA-Z0-9]+)*/g
It will find an at sign first, and then try to find one or more letter or numbers. It will try to find zero or more inner spaces and zero or more letters and numbers coming after that.
I am assuming the username only contains A-Za-z0-9 and space.
I have the following php code where I'm trying to generate a dynamic if condition using eval:
$categoryId = $_REQUEST['category_id'];
$locationId = $_REQUEST['location_id'];
$recordId = 1;
$criteria = [(!empty($categoryId) ? '(isInCategory($recordId, $categoryId))' : ''),
(!empty($locationId) ? '(isInLocation($recordId, $locationId))' : '')];
$check = implode(' && ', array_filter($criteria));
// $check will be
// (isInCategory($recordId, $categoryId)) && (isInLocation($recordId, $locationId))
// if $categoryId and $locationId are not empty
if(eval("return $check;"))
{
echo 'true';
}
else
{
echo 'false';
}
The problem is the entire code itself is within an eval statement so basically I'm ending up with an eval inside an eval which is throwing an error - can't really troubleshoot the error because all I get is a friendly error page so I'm assuming that is the case i.e. you cant use eval inside another eval???
I'm forced to use a propriety product so this is my limitation. Is there a way I can refactor the eval in the above if statement via some other technique.
I read some stuff about using call_user_func() but I'm new to this. Any help would be appreciated.
The title sums it up pretty well. What's a reliable way of preventing the execution of functions that interact with files? We are talking about evil eval().
I have full access to the string that is about to be evaluated. I was thinking about simply removing the backstick operator, and replacing file function names (along with exec) with ones that do effectively nothing, but I'm unsure how reliable this approach is.
Example:
PHP code
eval("file_get_contents('passwords.ini')");
Currently this is replaced by, before execution:
PHP code
eval("fn_zero('passwords.ini')");
EDIT
Concerning evil eval(). What I am doing here is the execution of admin-created, database stored PHP code in a module based content-management-system. Editing the code requires an additional level of authentication that is separate from the main admin login, it is not entirely untrusted user input.
The admin can butcher his/her site if that's his/her intention, that is not my concern. My concern is the prevention of viewing the underlying PHP code of the CMS.
Addendum: each site has its own webroot and database. They can't harm each other this way.
To answer the question as asked, "sanitizing" the eval input is not possible. Simple example:
eval("\$f=strrev('stnetnoc_teg_elif');\$f('passwords.ini');");
Viable options to execute user-provided code are sandboxing or a userland VM like https://github.com/ircmaxell/PHPPHP.
Next code will help to avoid executions of functions you want to avoid:
//Just a code example
//Array of functions which should be avoid (file functions)
$patterns = [
'chgrp',
'chmod',
'chown',
'clearstatcache',
'copy',
'delete',
'fclose',
'fflush',
'file_put_contents',
'flock',
'fopen',
'fputs',
'fwrite',
'ftruncate',
'lchgrp',
'lchown',
'link',
'mkdir',
'move_uploaded_file',
'parse_ini_file',
'pclose',
'popen',
'rename',
'rmdir',
'set_file_buffer',
'symlink',
'tempnam',
'tmpfile',
'touch',
'umask',
'unlink',
];
foreach ($patterns as $key => $value) {
$patterns[$key] = '/~' . $value . '\(.*\)/U'; //~ is mask here
}
//clear of dangerous file operations
$conditions = '~' . $conditions; //set mask
$conditions = str_replace(' ', '~', $conditions); //set mask
$conditions = preg_replace($patterns, '~true', $conditions);//here replaced to 'true' but you can use here your own custom function
$conditions = str_replace('~', ' ', $conditions); //remove mask
if (!empty($conditions)) {
eval($conditions);
}
Expression like "mkdir() && is_single(77) && copy('path') && mycopy('a') && copy2('b')" will be translated to: "true && is_single(77) && true && mycopy('a') && copy2('b')"
My PHP search form pulls data from a MySQL database. I am expecting users to sometimes fill the search box with a search term that has a slightly different spelling than my database entry, like "theater" instead of "theater." There are just a few of these that I expect to be very common, so I added an additional row to my database table that contains those alternative spellings, and my PHP search form searches this row of the database as well. It works well, but this will cause a lot of additional work when maintaining the database, so I'm wondering if there's something I can do within my PHP code to search for those predefined alternative spellings (I don't mean to give the user suggested spellings, but I want the search form to return, for example, entries that have "theatre" in it even though the user typed "theater." Is there an easy way to do this (without a search server)?
Yes you can easily do this work without database search,you need correct spellings so I suggest you do this work from PHP coding instead of databases search...
You can do this work with PHP Pspell module, PHP Pspell work like android keyboard whenever use type wrong spelling in search box it automatically check that spelling from dictionary and make it correct like if user type "theater" then it automatically correct it with "theatre".
before starting programming you have to check is Pspell module installed or not
<?php
$config_dic= pspell_config_create ('en');
Here is a small function to help you understand how Pspell works:
<?php
function orthograph($string)
{
// Suggests possible words in case of misspelling
$config_dic = pspell_config_create('en');
// Ignore words under 3 characters
pspell_config_ignore($config_dic, 3);
// Configure the dictionary
pspell_config_mode($config_dic, PSPELL_FAST);
$dictionary = pspell_new_config($config_dic);
// To find out if a replacement has been suggested
$replacement_suggest = false;
$string = explode('', trim(str_replace(',', ' ', $string)));
foreach ($string as $key => $value) {
if(!pspell_check($dictionary, $value)) {
$suggestion = pspell_suggest($dictionary, $value);
// Suggestions are case sensitive. Grab the first one.
if(strtolower($suggestion [0]) != strtolower($value)) {
$string [$key] = $suggestion [0];
$replacement_suggest = true;
}
}
}
if ($replacement_suggest) {
// We have a suggestion, so we return to the data.
return implode('', $string);
} else {
return null;
}
}
To use this function, it is sufficient to pass to it a string parameter:
<?php
$search = $_POST['input'];
$suggestion_spell = orthograph($search);
if ($suggestion_spell) {
echo "Try with this spelling : $suggestion_spell";
}
$dict = pspell_new ("en");
if (!pspell_check ($dict, "lappin")) {
$suggestions = pspell_suggest ($dict, "lappin");
foreach ($suggestions as $suggestion) {
echo "Did you mean: $suggestion?<br />";
}
}
// Suggests possible words in case of misspelling
$config_dic = pspell_config_create('en');
// Ignore words under 3 characters
pspell_config_ignore($config_dic, 3);
// Configure the dictionary
pspell_config_mode($config_dic, PSPELL_FAST);
$dictionary = pspell_new_config($config_dic);
$config_dic = pspell_config_create ('en');
pspell_config_personal($config_dic, 'path / perso.pws');
pspell_config_ignore($config_dic , 2);
pspell_config_mode($config_dic, PSPELL_FAST);
$dic = pspell_new_config($config_dic);
pspell_add_to_personal($dic, "word");
pspell_save_wordlist($dic);
?>
How to validate a substring is true in PHP for example if user1 is in the string it should be true?
textfile:
user1 : pass1
user2 : pass2
user3 : pass3
if(in_array($_SERVER['user1'] . "\r\n", $textfile)){ //not the way want this to be true
printf("Ok user1 is in this row somewhere");
}
I would advice against this kind of authentication system as is prone to errors or abuse. Use other system like ACL or database user/password hash check.
As those above have said, this is not a good approach as far as user authentication goes. If you want something basic, look at using HTTP Authentication or something at least.
That said, you can do what you have asked using PHP's file function, e.g.
function validUser($file, $user, $pass) {
// Check file exists
if (!is_file($file)) {
return FALSE;
}
// Read file
$lines = file($file);
if ($lines === FALSE) {
return FALSE;
}
// Go over the lines and check each one
foreach ($lines as $line) {
list($fuser, $fpass) = explode(':', trim($line));
if ($user == $fuser && $pass == $fpass) {
return TRUE;
}
}
// No user found
return FALSE;
}
if (validUser('passwords.txt', 'foo', 'bar')) {
echo 'The user was found';
}
Note that this assumes each line is of the form "username:password" with nothing else; you may need to adjust exactly how you match your lines depending on your format. An example file which would be validated by this would have a line such as
foo:bar
If you are using this for authentication; for the sake of your users consider a different (more secure) approach. By the way the reply to the OP is correct that just about nothing in that PHP code would work as appears to be intended.
However, if the idea is to use the value of an array by key $arr['key'] to look up configuration settings that need not be protected (for the world to see, basically) you can use the parse_ini_file() and friends. Again: this is not a good idea for truly sensitive data.
EDIT: Also, it is probably a good idea to use the PHP_EOL constant for end-of-line characters rather than assuming "\r\n".