Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

So basically jqueryphp is a jQuery plugin that allows you to call any PHP function within client-side JavaScript: jqueryphp@github

I wrote it as a response to projects such as PHP.JS and other JavaScript emulated PHP function libraries. I realize that my solution will only be useful in contexts where latency is not much of an issue since each PHP function call requires an AJAX request and response.

With that in mind, I'm extremely interested to hear if anyone thinks this plugin is a viable idea or not? And outside of the security implications, why?

Also I'm interested in any possible optimizations I could make in any of my code?

Here's the frontend: jqueryphp.js

(function( $ ) {

var config = {
    'path' : 'function_request.php'
};

var methods = {


    /*
     * This method initializes the default path configuration 
     * variable required to process any function requests.
     */ 
    init : function( options ) {
        var settings = $.extend(config, options);
    },

    /*
     * This method handles all calls to pre-existing PHP functions
     * regardless of whether they are native or user defined.
     */
    call : function( func, callback ) {
        /*
         * We subtract 2 from the arglen variable so when building
         * our args string to pass to the server we are not sending
         * the func string or the callback object to be interpreted
         * as a argument to be passed to a PHP function. 
         */ 
        var self = this, 
            arglen = arguments.length - 2,
            args = arguments;

        /*
         * Here we build a JSON object containing the arguments (if
         * any) to be passed to the PHP function. We offset our index
         * by 2 so we don't pass the function string or the callback
         * object to the server.
         */
        var jsonObj = [];
        for (var i=0; i<arglen; i++) {
            var argId = i + 2;
            jsonObj.push( args[argId] );
        }

        /*
         * We stringify our arguments list to pass it through to
         * the server for processing.
         */
        var args = JSON.stringify(jsonObj);

        /*
         * Finally the request object is built and sent to the server
         * for handling.
         */
        var request = $.ajax({
            url: config.path,
            type: "POST",
            data: {'method': 'call', 'func': func, 'args': args},
            dataType: "text",
            success: function(data) {
                callback(data, self);
            }
        });

        return this;
    },

    /*
     * This method allows for running PHP code written within 
     * JavaScript. 
     */
    exec: function( code, callback ) {
        var self = this;

        /*
         * Finally the request object is built and sent to the server
         * for handling.
         */
        var request = $.ajax({
            url: config.path,
            type: "POST",
            data: {'method': 'exec', 'code': code},
            dataType: "text",
            success: function(data) {
                callback(data, self);
            }
        });

        return this;
    }
};


/*
 * So the user of this plugin doesn't have to constantly pass in 
 * the 'call' parameter, being as how it will be the most used 
 * method, for convenience we make it so it is assumed when no
 * method parameter string is passed the 'call' method is used.
 */
$.fn.php = function( method ) {     
    if ( methods[method] ) {
        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( method !== 'init' && method !== 'exec' ) {
        return methods[ 'call' ].apply( this, Array.prototype.slice.call( arguments ));
    } else if ( typeof method === 'object' ) {
        return methods.init.apply( this, arguments );
    } else {
        $.error( 'Method ' + method + ' does not exist on jQuery.php' );
    }
};

})( jQuery );

Here's the backend: func_request.php

<?php
/*
 * While the jqueryphp plugin allows for the execution of 
 * arbitrary code via the 'exec' method, this functionality
 * is disabled by default. The plugin request 'exec' uses
 * the eval() construct which is very dangerous. If it is 
 * used pay special attention not to pass any user provided
 * data without proper validation in place.
 */
$config = array(
    'EXEC'          => false,
    'SEC_MODE'      => 'blacklist'
);

/*
 * The blacklist array is used when the SEC_MODE configuration
 * variable is set to blacklist. In such a case all PHP functions 
 * are allowed except those found in the array.
 */
$blacklist = array(
    '`',
    'create_function',
    'escapeshellcmd',
    'exec',
    'include',
    'include_once',
    'passthru',
    'pcntl_exec',
    'phpinfo',
    'popen',
    'require',
    'require_once',
    'shell_exec',
    'system'
);

/*
 * The whitelist array is used when the EXCLUSIVE configuration
 * variable is set to whitelist. In such a case all PHP functions are
 * disallowed except for those found in the array.
 */
$whitelist = array(
    // 'strlen',            // (e.g. Allowing the strlen function)
    // 'highlight_string'   // (e.g. Etc...)
);

/*
 * The first data passed from the client is which method request
 * is being made. For instance are they making a 'call' to a PHP
 * function or are they attempting to run PHP code written in 
 * JavaScript via 'exec'.
 */
$method_request = $_POST['method'] ? $_POST['method'] : false;
if ( $method_request ) {

    switch ( $method_request ) {

        /*
         * This method handles all calls to pre-existing PHP functions
         * regardless of whether they are native or user defined.
         */
        case 'call' :
            /*
             * We receive the function requested and arguments that
             * are to be passed to it.
             */
            $func_request = $_POST['func'] ? $_POST['func'] : false;
            $func_args = $_POST['args'] ? $_POST['args'] : false;

            /*
             * Based on the EXCLUSIVE configuration variable we attempt to
             * build our function call.
             */
            switch ( $config['SEC_MODE'] ) {
                case 'blacklist' :
                    if ( function_exists($func_request) 
                            && !in_array($func_request, $blacklist) ) {
                        $function = $func_request;
                    } else {
                        $function = false;
                    }
                    break;

                case 'whitelist' :
                    if ( function_exists($func_request) 
                            && in_array($func_request, $whitelist) ) {
                        $function = $func_request;
                    } else {
                        $function = false;
                    }
                    break;
            }

            /*
             * Next we take our $func_args which should contain a JSON
             * encoded string and convert it into a PHP associative array.
             */
            $args_arr = json_decode($func_args, false);

            /*
             * If the user requested function exists and is allowed
             * we proceed to call that function, passing any arguments
             * given with the requested function.
             */
            if ( $function !== false ) {
                $call = $function;
                echo call_user_func_array($call, $args_arr);
            }
            break;

        /*
         * This method handles the execution of user passed PHP strings
         * to the server.
         */ 
        case 'exec' :
            if ( $config['EXEC'] === true ) {
                /*
                 * We receive the code to be executed from the user.
                 */
                 $code_string = $_POST['code'] ? $_POST['code'] : false;

                 echo eval( $code_string );
            } else {
                echo "Usage of the 'exec' method has been disabled.";
            }
            break;
    }

}
?>

Here's a demo usage page:

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>jQuery PHP</title>
<script type="text/javascript" src="lib/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="lib/jqueryphp.js"></script>
<style>
body {
    margin:40px;
}
.results { 
    border:1px dashed green;
    width:100%;
    margin-bottom:10px;
}
</style>
</head>
<script>
$(document).ready(function() {

    // Initialize our path
    $.fn.php('init', {'path': 'http://www.mydomain.com/jqueryphp/lib/func_request.php'});

    // Example of calling the PHP strlen() function
    $("#results1").php('strlen', function(data, self) {
        $(self).html(data);
    }, 'A test string!');

    /*
     * Example of calling a disabled PHP function while also
     * demonstrating jQuery's method chaining is still in tact.
     */
    $("#results2").php('phpinfo', function(data, self) {
        $(self).html(data);
    }).css({border: '1px dashed red'});

    // Example of calling highlight_string()
    $("#results3").php('highlight_string', function(data, self) {
        $(self).html(data);
    }, "<?php phpinfo(); ?>");

    /*
     * Example demonstrating the usage of jQuery.php's exec
     * method to pass PHP code to the backend and return the
     * result. This method is disabled by default.
     */
    var code = "$a = 2; $b = 2; $c = $a + $b; echo $c;"
    $("#results4").php('exec', code, function(data, self) {
        $(self).html(data);
    });

});
</script>
<body>
<p><b>jQuery.php Demonstrations:</b></p>
Returned results from the PHP strlen() function
<div id="results1" class="results"></div>
Example of calling a disabled PHP function
<div id="results2" class="results"></div>
Returned results from the PHP strlen() function
<div id="results3" class="results"></div>
Returned results using jQuery.php's 'exec' method.
<div id="results4" class="results"></div>
</body>
</html>
share|improve this question
    
Use a whitelist instead of a blacklist. You will miss something important in the blacklist. –  ThiefMaster Dec 16 '12 at 17:52
    
I appreciate the comment. I allow for the usage of either. Though I should probably rename the variable arrays that define those lists because the current naming convention I'm using is confusing. –  Xaxis Dec 16 '12 at 17:53
add comment

3 Answers

I could see the sec_mode whitelist being useful for someone who wants a simple ajax controller. However, such a controller would not scale well with complexity, because one would only be able to modify functionality via adding/removing functions and parameters.

The exec mode and sec_mode blacklist mode look impractical to secure, and they probably should not exist. For example, exec mode would enable someone to inspect the server configuration or execute infinite loops. Blacklist mode relies on the developer (1) to blacklist all of the possible unsafe functions on a given server configuration and library setup and (2) to update the blacklist as functions are added throughout the lifetime of the project.

share|improve this answer
    
Good thoughts and points. I agree entirely that the blacklist mode and exec mode are basically hugely impractical for production development. I just wanted to include that functionality for quick demonstration purposes. For example: it's easier to install the plugin and run a test on a function without requiring a user to first qualify that function in the whitelist. Perhaps instead of not existing I can just have the blacklist mode disabled by default like the exec mode is. –  Xaxis Dec 16 '12 at 19:24
    
No, you shouldn't do this. If the function can be enabled, it WILL be enabled by more inexperienced developers - and it is YOUR responsibility to let them know they will hurt themselves, because with the blacklist prefilled, you suggest that this will be safe because "there is a blacklist". Better not include this in any working software. –  Sven Dec 19 '12 at 0:34
add comment

It's an interesting idea but I'm struggling to see any real-world use for it at such a simple level. Most of the relevant inbuilt PHP functions have javascript counterparts which would be preferred. For instance, there's no reason you'd ever want to use an ajax request just to do a string replace.

In my opinion, to make this a beneficial and useful I'd like to be able to transparently call methods on PHP objects using javascript. var total = order.getTotal() for instance which would fire off a javascript request that, on the server did something like:

$order = new Order($_GET['id']);
return $order->{$_GET['method']}();

Having said that, because of the nature of ajax requests and callbacks, I'm not sure you'll be able to create a clean enough interface to make it worthwhile.

share|improve this answer
    
One usage scenario that has been striking me as potentially interesting would be access to PHP GD functions or other graphics processing functions. As you say, most core PHP functions are indeed emulated in JavaScript and one can fairly easy go out and find those functions and include them in a JavaScript lib. With that said, perhaps it would simply be easier in some instances to just call whatever PHP function you like without finding the JS equivalent. I agree with your suggestion about a more transparent method of calling and using PHP within JavaScript. I've been looking into that. –  Xaxis Dec 16 '12 at 18:22
    
GD is a good example, but it would require you to maintain the reference to the image data between requests. You could do this with sessions but it would be horribly inefficient compared with using a standard javascript ajax call to a script which did everything in one go. You could use your code to call a user-defined function but this is essentially what sajax already did 6 years ago. –  Tom B Dec 16 '12 at 18:31
    
Yeah I've been on the fence regarding the practical applications of this little experiment. ... I guess I mainly wrote it because there are so many functions in PHP I'm always wanting to use when coding in JavaScript and I was getting tired of recreating or finding that equivalent code in JS. ... Anyhow, I appreciate your comments: they're definitely good food for thought. Cheers. –  Xaxis Dec 16 '12 at 18:38
add comment

You are doomed!

Basically you try to allow an attacker to define which code should be executed, and you try to find out if you are smarter in detecting malicious code than him injecting and hiding it.

The "EXEC" mode is completely insecure, we need not discuss this. But I doubt the blacklist mode is of good use either. I feel it to be insecure as well, but I cannot prove it in 5 minutes. For example, I can try to read any file on the webserver by calling file_get_contents(). I can try to overwrite any file by using file_put_contents(). I could install my multipurpose script this way that is called a second later, and you get owned.

In the end, only the whitelist mode seems reasonably secure, but this is just a generalized form of AJAX RPC calling - and a very dumb one, because it is limited to single PHP functions, you cannot do anything more sophisticated.

And if you really think about it: There are already plenty of working solutions to allow Ajax calls to do more useful stuff within one single call. Some of them are called "restful webservice".

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.