Help - Search - Members - Calendar
Full Version: Encrypt PHP files
Hostony Board > General Support > PHP/MySQL
jgalt1
Does anyone know a way to encrypt php files under hostony hosting ?

Becasue it is a shared hosting with users having shell access, there is a need to hide/encrypt mysql login/passwords from other users, not simply through the web server.
Alexandre
QUOTE
Creating a Secure PHP Login Script

Posted by martin on 9 Jul 2002, last updated on 25 Aug 2002.

Explains how to create a secure PHP login script that will allow safe authentication. Features remember-me function using cookies, validates logins on each request to prevent session stealing.
How does this work

This is a short explanation why I have chosen these authentication methods.

Users with shell access to the web server can scan valid session id's if the default /tmp directory is used to store the session data.

The protection against this kind of attack is the IP check.

Somebody who has a site (on a shared host with you) can generate valid session for your site.

This is why the checkSession method is used and the session id is recorded in the database.

Somebody may sniff network traffic and catch the cookie.

The IP check should eliminate this problem too.
Preparation

You need first to decide what information to store about members, the examples provided will assume almost nothing to make it easier to read.

I will use the PHP 4.1 super global arrays like $_SESSION, $_GET, etc. If you want to make it work on an earlier version of PHP you will have to substitute these with $GLOBALS['HTTP_SESSION_VARS'].
Database schema

This is only an example bare structure suitable for online administration, if you want to have registered members you should add more columns.

The schema is somewhat MySQL specific, I have yet to use another database other than MySQL and PostgreSQL but if you are using PostgreSQL you can convert the schema with the example script provided in my article Converting a database schema from MySQL to PostgreSQL.

CREATE TABLE member (
  id int NOT NULL auto_increment,
  username varchar(20) NOT NULL default '',
  password char(32) binary NOT NULL default '',
  cookie char(32) binary NOT NULL default '',
  session char(32) binary NOT NULL default '',
  ip varchar(15) binary NOT NULL default '',
  PRIMARY KEY  (id),
  UNIQUE KEY username (username)
);

The password and cookie fields are md5 hashes which are always 32 octets long. Cookie is the cookie value that is sent to the user if he/she requests to be remembered, session and ip are respectively the session id and the current IP of the visitor.
Connecting to the database

function &db_connect() {
require_once 'DB.php';

PEAR::setErrorHandling(PEAR_ERROR_DIE);

$db_host = 'localhost';
$db_user = 'shaggy';
$db_pass = 'password';
$db_name = 'shaggy';

$dsn = "mysql://$db_user:$db_pass@unix+$db_host/$db_name";

$db = DB::connect($dsn);

$db->setFetchMode(DB_FETCHMODE_OBJECT);
return $db;
}

This function connects to the database returning a pointer to a PEAR database object.
Session variables

To ease access to the current user's information we register it as session variables but to prevent error messages and set some defaults we use the following function.

function session_defaults() {
$_SESSION['logged'] = false;
$_SESSION['uid'] = 0;
$_SESSION['username'] = '';
$_SESSION['cookie'] = 0;
$_SESSION['remember'] = false;
}

... with a check like:

if (!isset($_SESSION['uid']) ) {
session_defaults();
}

to set the defaults. Of course session_start must be called before that.
To the core of the script

To allow easier integration with other scripts and make things more modular the core script is an object with very simple interface.

class User {
var $db = null; // PEAR:biggrin.gifB pointer
var $failed = false; // failed login attempt
var $date; // current date GMT
var $id = 0; // the current user's id

function User(&$db) {
  $this->db = $db;
  $this->date = $GLOBALS['date'];

  if ($_SESSION['logged']) {
  $this->_checkSession();
  } elseif ( isset($_COOKIE['mtwebLogin']) ) {
  $this->_checkRemembered($_COOKIE['mtwebLogin']);
  }
}

This is the class definition and the constructor of the object. OK it's not perfectly modular but a date isn't much of a problem. It is invoked like:

$date = gmdate("'Y-m-d'");
$db = db_connect();
$user = new User($db);

Now to clear the code purpose, we check if the user is logged in. If he/she is then we check the session (remember it is a secure script), if not and a cookie named just for example mtwebLogin is checked - this is to let remembered visitors be recognized.
Logging in users

To allow users to login you should build a web form, after validation of the form you can check if the user credentials are right with $user->_checkLogin('username', 'password', remember). Username and password should not be constants of course, remember is a boolean flag which if set will send a cookie to the visitor to allow later automatic logins.

function _checkLogin($username, $password, $remember) {
  $username = $this->db->quote($username);
  $password = $this->db->quote(md5($password));

  $sql = "SELECT * FROM member WHERE " .
  "username = $username AND " .
  "password = $password";

  $result = $this->db->getRow($sql);

  if ( is_object($result) ) {
  $this->_setSession($result, $remember);
  return true;
  } else {
  $this->failed = true;
  $this->_logout();
  return false;
  }
}

The function definition should be placed inside the User class definition as all code that follows. The function uses PEAR:biggrin.gifB's quote method to ensure that data that will be passed to the database is safely escaped. I've used PHP's md5 function rather than MySQL's because other databases may not have that.

The WHERE statement is optimized (the order of checks) because username is defined as UNIQUE.

No checks for a DB_Error object are needed because of the default error mode set above. If there is a match in the database $result will be an object, so set our session variables and return true (successful login). Otherwise set the failed property to true (checked to decide whether to display a login failed page or not) and do a logout of the visitor.

The logout method just executes session_defaults().
Setting the session

function _setSession(&$values, $remember, $init = true) {
  $this->id = $values->id;
  $_SESSION['uid'] = $this->id;
  $_SESSION['username'] = htmlspecialchars($values->username);
  $_SESSION['cookie'] = $values->cookie;
  $_SESSION['logged'] = true;

  if ($remember) {
      $this->updateCookie($values->cookie, true);
  }

  if ($init) {
      $session = $this->db->quote(session_id());
      $ip = $this->db->quote($_SERVER['REMOTE_ADDR']);

      $sql = "UPDATE member SET session = $session, ip = $ip WHERE " .
        "id = $this->id";
      $this->db->query($sql);
  }
}

This method sets the session variables and if requested sends the cookie for a persistent login, there is also a parameter which determines if this is an initial login (via the login form/via cookies) or a subsequent session check.
Persistent logins

If the visitor requested a cookie will be send to allow skipping the login procedure on each visit to the site. The following two methods are used to handle this situation.

function updateCookie($cookie, $save) {
  $_SESSION['cookie'] = $cookie;
  if ($save) {
      $cookie = serialize(array($_SESSION['username'], $cookie) );
      set_cookie('mtwebLogin', $cookie, time() + 31104000, '/directory/');
  }
}

Checking persistent login credentials

If the user has chosen to let the script remember him/her then a cookie is saved, which is checked via the following method.

function _checkRemembered($cookie) {
list($username, $cookie) = @unserialize($cookie);
if (!$username or !$cookie) return;

$username = $this->db->quote($username);
$cookie = $this->db->quote($cookie);

$sql = "SELECT * FROM member WHERE " .
  "(username = $username) AND (cookie = $cookie)";

$result = $this->db->getRow($sql);

if (is_object($result) ) {
  $this->_setSession($result, true);
}
}

This function should not trigger any error messages at all. To make things more secure a cookie value is saved in the cookie not the user password. This way one can request a password for areas which require even higher security.
Ensuring valid session data

function _checkSession() {
$username = $this->db->quote($_SESSION['username']);
$cookie = $this->db->quote($_SESSION['cookie']);
$session = $this->db->quote(session_id());
$ip = $this->db->quote($_SERVER['REMOTE_ADDR']);

$sql = "SELECT * FROM member WHERE " .
  "(username = $username) AND (cookie = $cookie) AND " .
  "(session = $session) AND (ip = $ip)";

$result = $this->db->getRow($sql);

if (is_object($result) ) {
  $this->_setSession($result, false, false);
} else {
  $this->_logout();
}
}

So this is the final part, we check if the cookie saved in the session is right, the session id and the IP address of the visitor. The call to setSession is with a parameter to let it know that this is not the first login to the system and thus not update the IP and session id which would be useless anyway.


Related links:

http://www.evolt.org/article/Creating_a_Lo...PHP_4/17/19661/
http://www.evolt.org/article/Creating_a_lo...rt_II/17/27093/
http://martin.f2o.org/download/php-login-script/?offset=-120
http://martin.f2o.org/php/login-script-faq?offset=-120
http://martin.f2o.org/php/login-script-guide?offset=-120
jgalt1
Thanks for the excellent and quick post. However, I don't think this will work for me because I am not trying to authenticate users.

I am doing a "behind the scene" login and want to protect my php script that has the login/password. In the example provided it would be the part in the db_connect function that has the following:

$db_host = 'localhost';
$db_user = 'shaggy';
$db_pass = 'password';
$db_name = 'shaggy';

That is what I need to protect. Any php file/script that has that in it must be world readable on the filesystem so that it can be read by the web server. That is the need for the encryption. I know that there is something called "zend encoder" that will do this but it's $$$$ and needs to be installed on the server.

It is easy to make it not servable by the web server but when there is shell access one cannot prevent user read access via shell AND allow it to be served by the web server. That's the dilema.
Niakie
You can put these varriables into a file that is below the public_html folder and have the php script include it that should protect the files and the information (to the best of my knowledge anyhow)
JasonJones
You can use Zend Studio to encrypt the php file. I would run them like someone said as included portions, so modification to your scripts will be needed to run a require encryptedpart.php etc...

On the linux hosting it is difficult from the shell to traverse as the uid across other peoples homedirs, so protecting it from the command prompt is not the major concern, but when someone writes a phpsript to fetch your files across lines it works fine as the www user has read/write access to all /home(1-3) ... So by telling a script to look and fetch any config.php files from /home/* /home2/* would not work from the command line (as it would run as your UID which is in a jailshell) but if you ran it from the web, it will run as user (www) and should work just fine...

Encrypt them with Zend if you need to, you can also just strip those variables out of the scripts replace with $db_user=newsecretcode_user, do a require ~/newsecretscript, then make a file ~/newsecretscript with the variables $newsecretcode_user = 'shaggy'; etc...

That would be security by obscurity... But it would work farily well. Someone would need to access the file calling the variables directly.. read it.. then go through and fetch the other file to get the variables... if you then just encrypted the newsecretscript file all would be safe... So long as they didnt just copy the files over to their dir, and execute them with modified code... You will not be able to protect the data within the database this way, you will only protect the "user" and "pass" as plain text. Now, you could write a routine to check that the newsecretscript file exists ONLY in ~/shaggy and if not refuses to assign the variables, also have it check that the script that is calling it has a checksum as well, some encrypted "bits" in the file... This will keep someone from putting a script in your homedir and then calling the newsecretscript for a user/pass but run their own script to modify your databases. Encrypt it all up and it would be more secure that way..
jgalt1
Thanks for your reply as well.

How do I use/access "Zend Studio". I'm very new to php (am an old c programmer) so is this something that I need to buy or is it part of hostony hosting ?

I think your correct that the shell access might not be the major problem I though it was. I didn't realize that there is a shell jail setup so that we can't just cd into others directories. That's good. I just looked at world readable permisson needed to run the php files. I'm not so worried about it now.

I appeciate everyone's ideas. Thanks again.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Invision Power Board © 2001-2024 Invision Power Services, Inc.
IPS Driver Error

IPS Driver Error

There appears to be an error with the database.
You can try to refresh the page by clicking here