Home Private Messages Search
CPG Dragonfly™ CMS Dedicated Server & Bandwidth Sponsored by DedicatedNOW
 
Wiki ⇒ v9 Developer's Manual ⇒ Porting Applications to CPG Dragonfly CMS™ ⇒ Part Two of Porting Applications

Wikiv9 Developer's ManualPorting Applications to CPG Dragonfly CMS™ ⇒ Part Two of Porting Applications  

Glossary
The Project
Install
Upgrading or Switching
Dragonfly admininistration
Dragonfly public view
Dragonfly Themes
Build local server
Running Dragonfly CVS
Tips and Tricks
Rules & Regulations
v9 Developer's Manual
v10 Developer's manual
13.4.2: Part Two of Porting Applications Parent

Section 5: Cookies and Users

It's common practice in PHP-Nuke modules to use the $cookie[] array to retrieve information about the currently logged in user. In DragonflyCMS™, this has been replaced with a very helpful variable: $userinfo[] (array).

$userinfo[] contains the information from the user's entry in the cms_users database table. In many PHP-Nuke apps, the $cookie[] variable is first decoded to get the user's name, then a database query is run to extract information from the database on that user. Relax. It's already taken care of with $userinfo[]. Any field in the _users table is available in the $userinfo[] array.

For Example:

PHP:
function getyourdetails()
{
    global
$cookie;

    
cookiedecode($user);
    
$name = $cookie[1];

    
$result = sql_query("select uid, name from ".$user_prefix."_users where uname='$check'", $dbi);
     list (
$uid, $username) = sql_fetchrow($result, $dbi);
     echo
"Your nick is $name, your user id is $uid and your name is $username\n";
}

We would re-write that as follows:

PHP:
    global $userinfo; // needs to be declared global if inside a function.
    
echo "Your nick is " . $userinfo['name'] . ", your user id is " . $userinfo['user_id'] . ", and your name is " . $userinfo['username'] . "\n";

That's correct. No cookie decoding, no database queries... You just use the data as it is supplied to you. Simple, huh?

There should be no need to touch the $user or $admin cookies in DragonflyCMS™ unless you ABSOLUTELY know what you are doing.






Section 6: Basic Security Precautions

One of CPG Dragonfly CMS™'s major advantages over other CMS's is security. Since you will be inserting what is probably someone else's code into someone else's website, this should be high up on your priorities list also.

Some of the biggest avoidable security issues in CMS's today are:

* SQL Injection.
* Cross site scripting.
* Full path disclosure.
* Direct file access

SQL Injection
SQL Injection is relatively easy to avoid. You simply check all of your variables before inserting the into an SQL statement. To accomplish this, you simply need to know the type of data in the database fields and where that data is coming from in the application you are porting.

Imagine a table with two fields, "pid" and "title". "pid" is a field of integer type, and "title" is of character type (char, varchar, text, etc...)

Now we have a simple piece of SQL query:

PHP:
function get_title($pid, $title)
{
    global
$db;
    
$sql = "SELECT title " .  
           
"FROM cms.MyApp_data " .
           
"WHERE pid = $pid OR title='$title'";

    
$db->sql_query($sql);
    
$row = $db->sql_fetchrow($result);
    return
$row['title'];
}


What do we do here? Well, we know that pid is an integer value, so we should make sure that it is. Or perhaps die trying. We also know that title is a character value, so we need to make sure that it has all of it's quotes (" or ') properly "slashed" before it goes into the query. CPG Dragonfly CMS™ has a nice function for the latter of the two called "Fix_Quotes()".

When invoked with one parameter, Fix_Quotes() will check to see if the current PHP environment has the setting "gpc_magic_quotes" set to "on." If it doesn't, it will "slash" the variable that it is passed for you. This is because when gpc_magic_quotes in "on", PHP will automagically slash quotes in variables coming from HTTP_GET, HTTP_POST, and HTTP_COOKIE variables.

If you are *sure* that the variable is coming straight from a web page request into your query, you should run Fix_Quotes($variable); on that variable to assure proper quote handling. If you are unsure, or there is a possibility that the variable may be coming from several sources, you can always use the PHP function addslashes() and stripslashes().

We change the code to:

PHP:
function get_title($pid, $title)
{
   global
$db;

   
$pid = intval($pid); // assure that $pid is an integer.
//   if (!is_numeric($pid)) {
//      die ("invalid pid parameter specified!");
//   }


   
$title = Fix_Quotes($title);
// $title = addslashes(stripslashes($title)); // alternately, we could do this.

   
$sql = "SELECT title ".
          
"FROM cms.MyApp_data ".
          
"WHERE pid = $pid AND title = '$title'";

   
$db->sql_query($sql);
   
$row = $db->sql_fetchrow($result);
   return
$title;
}


That's much better. We can either force $pid to be an integer with "$pid = intval($pid);" or check to see if it's an integer and scream bloody murder if it is not using the "if (!is_numeric($pid)) {" code.

For the character variables, we chose to use Fix_Quotes() on the $title variable since we're sure that by the time we see $title in the function, it hasn't been changed elsewhere in the program. If we weren't sure, or just completely paranoid, we could do the slash-twins on the variable, but that would add extra overhead to the program that Fix_Quotes() does not.



Cross-Site Scripting (XSS)

Cross Site Scripting, or XSS, is essentially finding ways to inject HTML, Javascript, or whatever code into someone else's web browser. It's as simple as that. The fix is nearly as simple.

In CPG Dragonfly CMS™, we have the function Fix_Quotes(). It actually accepts more than one parameter, the second of which is good for preventing XSS code from getting injected into your application.

Imagine this micro module, called "Quote":

PHP:
<?php
// the never ending quote application
function addquote() {
    global
$quote, $db;

    
$db->sql_query("INSERT INTO ".$prefix."myquotes VALUES (0, '$quote')");
    echo
"your quote is in the database, thanks!<br>";
}

function
display_quote($id) {
    global
$db;

    
$id = intval($id);  // we're not THAT stupid.

    
$db->sql_query("SELECT quote FROM ".$prefix."_myquotes WHERE id = $id");

    echo
"Your quote is '" . $quote . "'<br>";
    echo
"Click <a href=\"" . getlink('&id='.$id+1) . "\">here</a> for the next.\n";
}

switch (
$op) {
    case
"display":
        
display_quote($id);
        break;

    case
"add":
        
add_quote();
        break;
};


In the quote_add() function, we are blindly adding the $quote variable both into an SQL query as well as the database itself. In the quote_display() function, we also simply toss the contents of the variable into the user's web browser. This is how XSS happens. To remedy this, we will use the Fix_Quotes() function again and change the add_quote() function to read:

PHP:
function addquote() {
    global
$quote, $db;

    
Fix_Quotes($quote, 1);

    
$db->sql_query("INSERT INTO ".$prefix.:"myquotes VALUES (0, '$quote')");
    echo
"your quote is in the database, thanks!<br>";
}


By adding the second parameter "1" to Fix_Quotes(), we are telling Fix_Quotes() that not only would we like variable slashed, we would also like to strip any HTML or PHP code. Fix_Quotes() does this using the strip_tags() function of PHP.



Full Path Disclosure

Full path disclosure, by itself, is not a security issue. However, allowing someone with evil intent to figure out where certain files reside on your web server can be used in conjunction with other attacks to open large security holes.

Full path disclosure in PHP generally comes from warning errors. For example:

Code:
Warning: Division by zero in /home/www/html/tmp/foo.php on line 4

That would tell an attacker where on the server your web files are stored.

Since it is quite easy to turn of error reporting in the configuration of PHP, this won't be covered much here. Simply, when debugging your ported app, run it with the "error_reporting(E_ALL);" function call and browse over the errors (chances are, there will be many). Silence those that you can.

In the end, it is primarily the web developer's responsibility to make sure that error messages are not output to the users' screen on a production site.


Direct File Access

The problem with direct file access is exactly as it sounds -- letting people run PHP code on your website without first passing through the front doors (index.php or admin.php).

In PHP-Nuke, this is generally accomplished with something similar to the following code at the top of each .php file:

PHP:
if (!eregi("modules.php", $_SERVER['PHP_SELF'])) die("Access Denied");


What that says (in english) is "if the current file being accessed when this chunk of PHP code is being run does not contain 'modules.php', then die."

In practice, that almost works well. But there are ways around this that we will not discuss here.

As a better security measure, DragonflyCMS™ defines some variables that may be checked to make sure that if a PHP file is getting executed, it's only being executed from within a proper section of the site, and definitely not being directly accessed.

For example, we would re-code the above code snippet as:

PHP:
if (!defined("CPG_NUKE")) { exit; }


You see, CPG_NUKE is defined() in includes/cmsinit.php. So... If your code is being executed and includes/cmsinit.php has not already been loaded, your code will halt execution.

There is also another nice variable that is define()d when the administrator is working from the admin panel. That is ADMIN_PAGES.

So... All of your adminstrator code should be protected like this:

PHP:
if (!defined("ADMIN_PAGES")) { exit; }


Administrator code is generally located in the admin/modules folder.

If the code you are porting is to be cross-ported (the same code running in DragonflyCMS™ as well as PHP-Nuke), this CAN be done without making your code DragonflyCMS™ specific. You would change the first piece of code to read like this:

PHP:
if (eregi("block-My_Block.php", $_SERVER['SCRIPT_NAME'])) die("Access Denied");


Notice we're using SCRIPT_NAME instead of PHP_SELF. We are basically saying "If the name of this file is in the full file/pathname of the script that is running, then die."

This method is much more risky as a simple typo renders it useless (block-My_Block.php vs. block-My_Bolck.php).

And just to "toot" DragonflyCMS's™ horn a bit, I should add that this is actually the second level of multi-tiered security against direct file access. On a properly installed and configured DragonflyCMS™ system, processing would never get this far.

Section 7: Register Globals - Importing Data


When on, register_globals will inject your scripts with all sorts of variables, like request variables from HTML forms. This coupled with the fact that PHP doesn't require variable initialization means writing insecure code is that much easier. CPG Dragonfly CMS™ runs with register_globals disabled.

When on, people use variables yet really don't know for sure where they come from and can only assume. Internal variables that are defined in the script itself get mixed up with request data sent by users and disabling register_globals changes this. Let's demonstrate with an example misuse of register_globals:

PHP:
<?php
// define $authorized = true only if user is authenticated
if (authenticated_user()) {
   
$authorized = true;
}

// Because we didn't first initialize $authorized as false, this might be
// defined through register_globals, like from GET auth.php?authorized=1
// So, anyone can be seen as authenticated!
if ($authorized) {
   include
"/highly/sensitive/data.php";
}
?>

When register_globals = on, our logic above may be compromised. When off, $authorized can't be set via request so it'll be fine.

There is one caveat. By turning off register_globals, you can no longer access passed variables with their given name. They MUST be imported with either $_GET or $_POST, depending on where the data is coming from. $_GET is, generally, used with variables passed in links, while $_POST is used for variables coming from a posted form.

For example, say we are trying to get data passed from a simple form. The old, PHP-Nuke way is to simply access the variables by their given name.

PHP:
    // update the database with posted variables
    
global $dbi, $prefix;

    
$sql = "INSERT INTO ".$prefix."_mytable SET myphone='".$myphone."', myaddress='".$myaddress."', mybirthdate='".$mybirthdate."'";
    
$result = sql_query($sql, $dbi);

With CPG Dragonfly CMS™ you must import these variables prior to using them, for reasons discussed above. We would rewrite the above as;

PHP:
    // update the database with posted variables
    
global $db, $prefix;

    
$myphone = Fix_Quotes($_POST['myphone'], 1);
    
$myaddress = Fix_Quotes($_POST['myaddress'], 1);
    
$mybirthdate = Fix_Quotes($_POST['mybirthdate'], 1);

    
$sql = "INSERT INTO ".$prefix."_mytable SET myphone='".$myphone."', myaddress='".$myaddress."', mybirthdate='".$mybirthdate."'";
    
$result = $db->sql_query($sql);
    
$db->sql_freeresult($sql);

Alternatively, you could re-write it like this, also mentioned above;

PHP:
    // update the database with posted variables
    
global $db, $prefix;

    
$sql = "INSERT INTO ".$prefix."_mytable SET myphone='".Fix_Quotes($_POST['myphone'], 1)."', myaddress='".Fix_Quotes($_POST['myaddress'
], 1)."', mybirthdate='".Fix_Quotes($_POST['mybirthdate'], 1)."'";
    
$result = $db->sql_query($sql);
    
$db->sql_freeresult($sql);

The first method, in my opinion, is simply easier to read and work with. It is also easier to check whether these variables are set, before trying to assign them to another variable. For example, the routine could again be rewritten as;

PHP:
    // update the database with posted variables
    
global $db, $prefix;

    
$myphone = isset($_POST['myphone']) ? Fix_Quotes($_POST['myphone'], 1) : '';
    
$myaddress = isset($_POST['myaddress']) ? Fix_Quotes($_POST['myaddress'], 1) : '';
    
$mybirthdate = isset($_POST['mybirthdate']) ? Fix_Quotes($_POST['mybirthdate'], 1) : '';

    
$sql = "INSERT INTO ".$prefix."_mytable SET myphone='".$myphone."', myaddress='".$myaddress."', mybirthdate='".$mybirthdate."'";
    
$result = $db->sql_query($sql);
    
$db->sql_freeresult($sql);

Basically, what this is saying (in English) is -- if the variable $_POST['myphone'] is set (contains data) THEN Fix_Quotes on the data in $_POST['myphone'] (and strip_tags) and assign it to $myphone, ELSE set $myphone to nothing (NULL).

This also holds true to switches used within many modules. For example;

PHP:
switch($mode) {
    case
"something":
        
something ();
        break;

    case
"somethingelse":
        
somethingelse ();
        break;
}

With this code, $mode is never set, hence no function is called. The proper way would be;

PHP:
$mode = isset($_POST['mode']) ? $_POST['mode'] : 'something';
switch(
$mode) {
    case
"something":
        
something ();
        break;

    case
"somethingelse":
        
somethingelse ();
        break;
}

This is assuming that you have data being passed from a posted form. If you have a switch that will be used with both $_GET and $_POST, you could use the following;

PHP:
$mode = isset($_POST['mode']) ? $_POST['mode'] : (isset($_GET['mode']) ? $_GET['mode'] : 'something');
switch(
$mode) {
    case
"something":
        
something ();
        break;

    case
"somethingelse":
        
somethingelse ();
        break;
}

There are many ways to do this, above is just one example. Notice that I have chosen a default routine to run, if $mode is not set. You could also set a default case to run and set mode to empty if it is not set.

PHP:
$mode = isset($_POST['mode']) ? $_POST['mode'] : (isset($_GET['mode']) ? $_GET['mode'] : '');
switch(
$mode) {
   default:
    case
"something":
        
something ();
        break;

    case
"somethingelse":
        
somethingelse ();
        break;
}




References:
Functions / special variables mentioned in this document and their source code/documentation:

$userinfo[] - includes/cmsinit.inc
getlink() - includes/functions/linking.php
adminlink() - includes/functions/linking.php
Fix_Quotes() - includes/cmsinit.inc
addslashes() - www.php.net/manual/en/...lashes.php
stripslashes() - www.php.net/manual/en/...lashes.php
strip_tags() - www.php.net/manual/en/...p-tags.php

 
Updated: Thursday, November 17, 2005 (15:40:17) by tuta
Created:  Saturday, November 05, 2005 (19:52:58) by tuta

You are seeing squares or questionmarks on this page?

All content of this website is copyrighted by the Creative Commons NC-SA
The logos and trademarks used on this site are the property of their respective owners
We are not responsible for comments posted by our users, as they are the property of the poster.
Our server runs on a P3 1.2GHz with 512MB RAM with no accelerators
Support GoPHP5.org
Interactive software released under GNU GPL, Code Credits, Privacy Policy