Hi all,

Something disturbing is happening... I make an AJAX call to a page and it returns unprocessed PHP, not HTML. However, when I navigate to the page manually, the PHP is processing as expected. This must be a huge security vulnerability?

My AJAX call is:

$('#forgot').click(function(e){
    e.preventDefault();
    lFormContainer.load("ajax/?page=authenticate/username");
});

The PHP code itself is too huge to paste here, so it's difficult to see what is relevant. However, the AJAX call returns only the content for index.php, and none of the code referenced in the classes index.php makes calls to.

I have pasted index.php below, it may reveal something:

<?php

session_start();

unset($_SESSION);

define("FRAMEWORK_PATH", dirname(__FILE__) . "/");

require __DIR__ . '/vendor/autoload.php';

require('registry/registry.php');
$registry = new Registry();

//Setup our core registry objects
$registry->createAndStoreObject('template', 'template');
$registry->createAndStoreObject('mysql', 'db');
$registry->createAndStoreObject('authenticate', 'authenticate');
$registry->createAndStoreObject('urlprocessor', 'url');
$registry->createAndStoreObject('mailout', 'mail');

//Database settings
include (FRAMEWORK_PATH . 'config.php');

//Create database connection
$registry->getObject('db')->newConnection($config['mysql_host'], $config['mysql_user'], $config['mysql_pass'], $config['mysql_name']);

//Process URL
$registry->getObject('url')->getURLData();

//Process Authentication
$registry->getObject('authenticate')->checkForAuthentication();

//Store settings in our registry
$settingsSQL = "SELECT * FROM settings";
$registry->getObject('db')->executeQuery($settingsSQL);
while($setting = $registry->getObject('db')->getRows())
{
    $registry->storeSetting($setting['value'], $setting['key']);
}

$registry->getObject('template')->getPage()->addTag( 'siteurl', $registry->getSetting('siteurl') );
$registry->getObject('template')->buildFromTemplates('header.php', 'main.php', 'footer.php');

//Is the user authenticated?
if($registry->getObject('authenticate')->isLoggedIn())
{

}

else
{
    // Grab Templates for no-login
    $registry->getObject('template')->buildFromTemplates('header-no_log.php', 'main-no_log.php', 'footer.php');

    //Set default greeting
    $registry->getObject('template')->getPage()->addTag( 'greeting', '<h1 class="text-center" id="greeting">hello</h1>' );
}

$controllers = array();
$controllersSQL = "SELECT * FROM controllers WHERE active=1";
$registry->getObject('db')->executeQuery( $controllersSQL );
while( $controller = $registry->getObject('db')->getRows() )
{
    $controllers[] = $controller['controller'];
}


//Which controller should we delegate to? 
//?page=$controller
$controller = $registry->getObject('url')->getURLBit(0);


if( in_array( $controller, $controllers ) )
{
    require_once( FRAMEWORK_PATH . 'controllers/' . $controller . '/controller.php');
    $controllerInc = $controller.'controller';
    $controller = new $controllerInc( $registry, true );
}
else
{
    // default controller, or pass control to CMS type system?
}


if(!isset($_POST['process_registration']))
{
    $registry->getObject('template')->parseOutput();
    print $registry->getObject('template')->getPage()->getContentToPrint();
}


?>

So you are seeing <?php ... ?> as the responseText of the ajax call? Are you using htaccess for a redirect? Does your rule apply here?

Correct, I am getting the PHP file as depicted above.

There is a rewrite rule in place with NGINX, could this be causing it? The strange thing for me is that I can navigate to the page manually and get HTML, but when making the call through AJAX I get the PHP code returned.

If it's relevant, below if my NGINX config:

server {
        listen 80 default_server;
        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.php;

        server_name server.org;

        error_page 404 /index.php;

        location /
        {
                if (!-e $request_filename)
                {
                rewrite ^(.*)$ /index.php?page=$1 break;
                }
        }
        location ~ \.php$ {

                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;

                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        }

        location ~ /\.ht {
                deny all;
        }
}

You ever get this figured out? If not, do you have a sample page where this is occuring?

It's peculiar. I eventually figured out that my AJAX call was flawed due to PHP logic (problems with the POST data processing). For some reason, when my PHP logic was incorrect, AJAX was returning the raw PHP code.

I can't duplicate the issue any more since fixing my PHP, but I remain quite concerned over this. It makes me think that my code is somehow really insecure.

On second thought, I will experiment with making a bogus AJAX request that deliverabley contains POST data that my PHP code won't know how to handle. I'll report what that comes back with.

More on this. Using the follow javascript:

$('a[href="sentiment"]').click(function(e){

    var id = $(this).attr('id');
    var sentiment = $(this).attr('class');

    e.preventDefault();
    alert(id + ' ' + sentiment);    
}); 

And the following HTML:

                        <a href="sentiment " id="{status_id}" class="hope">
                        <div class="box">
                        Hope
                        </div>
                        </a>

You'll note a space in the HREF attribute. In combination with the JS, clicking this link returns the RAW PHP of my index.php file.

When the space is correct the JS behaves as expected.

This must be a serious vulnerability. If someone spoofed the HTML to add a tiny space in the link, they could get their hands on server side code... Seriously, what is going on here?

Out of curiosity, do you have an extension on your php file?

Secondly, I may wager a guess that this is your problem:

if (!-e $request_filename)
{
    rewrite ^(.*)$ /index.php?page=$1 break;
}

If not that directly, it may be something else in that file. I am more familliar with htaccess than module rewrites, so maybe someone better suited can look and see if anything is glaringly obvious.

These links may also be of interest:
http://stackoverflow.com/questions/15714706/nginx-rewrite-to-php-file-file-is-being-downloaded
http://serverfault.com/questions/465607/nginx-document-rootfastcgi-script-name-vs-request-filename

It is entirely possible that somehow when using ajax, the space is accidentally making it possible to access the file as if it were a directory, and turning it into a valid file, therefore giving it binary headers as opposed to processing through the CGI.

What is the content type of your request? If it's text/txt or a mime type that typically doesn't engage the PHP preprocessor, than you would see the behavior you're getting.

What happens if you add the following to your ajax? (where lformController is defined)

$.ajax({
 . . .
 contentType: "text/html"
 . . .
 });

There is definitely an extension on all the PHP files.

I've looked at the rewrite rule, and I am still somewhat a beginner wit NGINX, but my understanding of that rule is:

IF (file does not exist)
{
    rewrite URL to index.php/?page=$file
}

Using this, I tried to call files I know exist in my URL, for example my style.css file stored on the server.... and it replicated the issue! Calling http://link.org/style.css returned the raw PHP content!

So that concludes that the issue is nothing to do with JavaScript, but how the web server handles requests... However, I have no idea how to diagnose the issue further. I am quite suprised that the web server isn't processing the PHP like it should.

the issue is nothing to do with JavaScript, but how the web server handles requests

This is what I was attempting to get at by adding the "text/html" content type in your ajax request. Unfortunately, I'm not too familiar with Nginx so, I can't provide much value in configuration there.

I am however, thoroughly convinced that your ajax request is either sending the wrong content type (text/txt) or none at all and Nginx is defaulting to text/txt.

For troubleshooting purposes I would not mess with the rewrite rules. They'll just complicate things. Remember that Ajax requests are redirects themselves. I would investigate the headers of your GET / POST ajax requests (in developer tools or firebug) and at the very least, rule out the content-type requested. Then, look around for resources on how Nginx handles or assigns default content types.

P.S I've had this very same issue with apache.

I disagree that changing the header type of the AJAX call is the ONLY change that needs to be changed. The reality is, as long as a request without headers (or default headers) is being returned as plain text, it is a HUGE security vulnerability.

For my 2 cents, I would encourage you to look MORE into the nginx configuration to see if there is a way to set default headers.

After browsing through this:
https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/

You may also want to make a few other changes, as it seems to be a very straight forward primer for nginx configuration do's and don'ts..

from what I see you are already using try_files, and you should probably use that in the check for the file existence before the redirect as well. It is also possible that since you are not doing a "return" or stopping execution after the redirect, it is continuing to process further down the config file and try_files is actually returning the file you asked for, so behind the scenes you are getting a quick redirect, then a read of the file you just processed.

You may also be interested in this answer: http://serverfault.com/a/329970

commented: +1 for try_files Nginx config +14

Well, I've fixed the issue, insomuch as it's no longer replicatable.

I altered my NGINX config after some research and got rid of using an IF statement to check for existing files, instead opting for: try_files $uri $uri/ /index.php?page=$uri;

This means that if the file doesn't exist as a file or as a directory, it tries as a GET request which I can then handle through my PHP script. It's no longer downloading RAW php.

Despite this, it seems like there is a lot more to learn with NGINX and what I've currently got would not be secure enought to go live.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.