views:

381

answers:

5

And here i am again! ;)

Im developing (well, im still in planning phase) a web-app where i would give to other developer the possibility to write theyr own plugins/modules (in the way of CMS does, drupal, joomla, etc).

My problem is that i have to force the developers to use the methods i wrote for interact with databases, for many reasons (data integrity first). I dont need primarly to keep secret the database structure, but if it is possible too, is appreciate.

So, in short, my goal is:

  1. Keep 'protected' and not accessible from outside my classes the database connection;
  2. If possible (but should be a consequence of point 1) keep secret the real database structure;

The plugins/modules behavior will be implemented like the Drupal hooks.

So, an short example of my current situation:

<?php
    // File dbclass.php
class DbHandler{
    /*
    * As simply as i could ;)
    */
    private $dbLink;
    public function __construct(){
        $this->dbLink = pg_connect("host=127.0.0.1 user=myuser password=mypassword dbname=mydatabase");
    }
    public function __destruct(){
        pg_close($this->dbLink);
        unset($this->dbLink);
    }
}
?>

and the module call:

<?php
// An general situation
require('dbclass.php');
/*
* The DbHandler consctruct function open a database connection.
*/
$init = new DbHandler();
/*
* [..i'll do something here..]
*/
/*
* Lets say this is inside a function/hook of an enabled module;
* This is the behavior i want to avoid!
* How to keep the database connection 'within' my
* DbHandler object's instance?
* p.s: in real life i do use prepared stmt
*/
$qrUsers = pg_query('SELECT * FROM users');
if(pg_num_rows($qrUsers) != 0){
    echo '<h2>Query success!</h2>';
    //do something
}else{
    echo '<h2>Query Fail!</h2>';
}
//Actual Output: Query Success! [...]
//Desidered output: Warning: pg_query() [function.pg-query]: No PostgreSQL link opened yet in /var/www-lighttpd/dbtest.php on line xx Query Fail!
?>

I want that if the developer try to execute directly a query, it will fail, and in the example i posted, he is forced to use a method like (for example)

$users = new User();
$users -> get_all();

Im running on postgresql, but i suppose the problem is the same on Mysql.

As far as i thought, the only way i can do that is to close the connection before call all the modules hooks, and then reopen and reclose it every time a hooks need to CRUD the database. But i really dislike this solution, wont be efficient.

If someone have betters ideas, please share them with me!

p.s: my english is poor, i hope the question is clear.

EDIT: the Oddthinking solution seem to be the best for me: the opening and immediately closing the ghost connection will prevent every next pg_* or mysql_* function call. This solution, in couple with renaming the pg_* or mysql_* functions with runkit / apd with custom names will (i think) definitely avoid any uncontrolled access to the DB by the modules/plugins.

A: 

Maybe you've heard of the decorator pattern, which might suit your needs. It's idea is to actually create a proxy instance of your database object which behaves exactly like your real database. Therefore, your real database is hidden to others and only your decorater object knows where to find the real database.

moritz
No, never heard about that. I'll give it a look, thanks!
DaNieL
He is doing just that but in PHP you can query DB even if you don't have a valid reference to DB connection, PHP will be using last available one even if it is created somewhere else and stored as private member.
vava
Even the best patterns aren't able to win a fight against bad design..
moritz
@moritz: you think the behavior i need is a bad design?
DaNieL
contrary, i think it's a very good approach, just php is in your way. Many systems nowadays hide the real database connection from modules to be able to control access ( e.g. kill query if it takes to long )
moritz
+1  A: 

It's a bit of a hack, but:

Immediately after you do your pg_connect call, you could call pg_connect with some dud parameters. It will fail, but see if it also overrides the default connection to prevent others from using it in pg_query..

EDIT: It turns out (see comments) that calling pg_connect with dud parameters does NOT override the default connection.

Here is Plan B (untested):

$this->dbLink = pg_connect("host=127.0.0.1 user=myuser password=mypassword dbname=mydatabase");
$junkConnection = pg_connect("host=127.0.0.1 user=myuser password=mypassword dbname=mydatabase");
pg_close($junkConnection);

This creates a second connection to the database, but immediately closes it. I seriously doubt that would leave the default parameters reverting to the first connection.

Oddthinking
Yes, i thought this solution too, simply calling `pg_query("SET search_path TO another_useless_schema");` before the modules calling, and then when needed switching back to the original one and so on.. but didnt looks as the best solution for me ;)
DaNieL
My solution is *slightly* cleaner than that. There's no need to set and re-set the search path with each (legitimate) call to the database.By calling pg_connect, the default connection is overwritten. Now, the client can't access it via pg_query, but your copy in dbLink is ready to use whenever you want.
Oddthinking
Your solution wold be simply perfect: i create another DB with no table, an user that can access only to that db and cant do nothing, and the trick is done... but this will double my database connection, becose i'll have to keep the *ghost* connection alive (i've tryed, if the pg_connect fail it doesnt work, php still use the first connection *working* avaiable).Now, this problem could be solved if there is an easy way to have 2 postgresql *instances* on the same machine, so the *ghost* connection wont interfree with the *real* one (number of total connections, etc...)
DaNieL
Can PHP be compiled/configured in order to have the connection argument in pg_connect (and other pg_* functions) **mandatory**? This will solve all my problems (db-related)
DaNieL
I've added a Plan B to deal with the fact that my original idea failed in practice.
Oddthinking
@DaNieL, I don't see any mention of such a compilation option. Of course, you could always re-write the PHP code, but it seems a bit extreme. If a hostile client wants to access your encapsulated connection, they will be able to. Encapsulation != Security.
Oddthinking
..i tryed to edit the pgsql.c in the /ext/pgsql pgp source folder, then recompilatet and reinstalled it.. this way works, but i dont like it much.. dont sound like reliable ;) I admid that i didnt try to close the 'ghost connection' just after open it, i'll try, thanks!
DaNieL
I tryed you Plan B, ant it works! Open a second connection and reclose immediatly, will fail others pg_* function calls!Thanks man, this seem to be the way i'll take, but i'll keep the bounty open for a little bit... maybe some others idea will come
DaNieL
A: 

Hm, maybe you could solve this at the DB-level?

You could have two user accounts on the db. One that has proper priviliges called app-main or something, that you connect with—and one that has zero priviliges called app-plugin. Then, you could call your plugin like this:

pg_query("SET ROLE app-plugin;"); //precaution--use low-privilege-user
$plugin->doStuff();
pg_query("SET ROLE NONE;"); //Reset role back to default

This would have the priviliges lowered to that of the app-plugin-user for the current session. Of course, the plugin could call "SET ROLE NONE;" to raise priviliges again itself, so it's not "safe". It would just be like a precaution. But the connection would at least be kept alive and normal queries would fail. (Of course if the plugin gets called like 50 times during a session it might impact performance.. who knows?)

Anyway, I have never tried this mysqlf so I don't know anything about this, really..

Docs:
http://www.postgresql.org/docs/8.4/interactive/sql-set-role.html

0scar
but the problem will be that i'll have to 'switch' the role everytime a plugin call an allowed method, `$users->get_all();` and then reswitch again afther the method.. its very similar to the `pg_query("SET search_path TO another_useless_schema");` solution i've postend in the comment to Oddthinking answer, but i dont think can really be a solution; Those modules/plugins will able to handle every 'action' separately, becose everytime i 'enable' and 'disable' the connection.
DaNieL
A: 

I might be missing something but as long as users can read your code they can copy & paste the information needed to make their own connection. Are you really allowing users to connect to your database?!

Lets say this is inside a function/hook of an enabled module;

This is the behavior i want to avoid!

SELECT * ...

This is not your problem. My guess is users are going to host your app in their own enviroment/database(s), therefor they are taking damage of bad practice when you provided better alternatives.

chelmertz
No, they wont read my source code, never.They will only access to some classes and methods i wroted for them, lets say a some kind of API, but they'll never know the database name, user, host or password, or whatever of the customer.Do you know the user/pswd/dbname of everyone who use drupal when you develope a drupal module? The 'foreign' developers will only write plugins/module, sorry if in the example i posted this wasnt so clear ;)
DaNieL
Ok, I guess I earned that -1 :). If you're going to approach the situation with an API (which I like) I think you should keep the client code (the plugins/modules) on one host and the responding REST/SOAP/XML-RPC on another host. (With 'host' being a virtual server or anything you'd like.) That way you never expose your connection, which sounds very insecure.
chelmertz
Yes, can be a solution and i agree that will be more secure, but it raise me another dilemma: the hooks behavior should works little different from theyre now: actually they just check if any of the enabled modules have the $functionName_hook_name() function and then execute it; using your approach, the module itself act as an API, when it is enabled have just to tell how the request must be structured.. Mhhh, i gotta make some test ;) p.s: can you edit your answer explaining this solution? thanks!
DaNieL
After a really fast test, using the modules exactly as REST or SOAP API would be really problematic, becose when the hooks are called, i have to raise an request to the RESTful module api, sending all the necessary data, but the module could need more data than what i'm sending them; so the module should do others request to collect the necessary data, handle them, and then re-send another request to my app, then wait and handle the response. Before discard this solution, i obviously have to test with a medium-high traffic and requests, but doesnt look like an concrete approach...
DaNieL
Sorry, but the modules cant be handled completely as REST/SOAP API, or at least, they cant be handled like API in an *efficient* way..The code must be on my server, called by hook_ functions, exactly ho drupal did... i know this raise many security issue, and the database connection is the first of them, and this is why i posted this question ;)Thanks anyway for the idea!
DaNieL
+3  A: 

Two options spring to mind:

  1. Enforce this in documentation/standards instead of code (it sounds like this is insufficient for your case?)

  2. Use PDO pgsql instead of the pg_* API.

Hacks like opening/closing dummy DB connections will work but I would consider this as a last resort.

It sounds like you want to assure the quality of your system and its plug-ins. I think these hacks defeat that purpose. No doubt I am making some assumptions here, but generally speaking, I think the best way is to offer solid examples/standards to your plug-in writers and good documentation. If you will be hosting an authoritative repository for the plug-ins, then you could also scan their code for violations like this and suggest that the maintainers fix them.

oops
+1 for enforcing this via standards/docs. You can't force this in code -- there'll always be a way around it. e.g., what's to stop a disobedient programmer calling pg_connect() directly himself?
Frank Farmer
Yes, i will absolutely do my best to offer an efficent documentation, but i need to be sure that anyone will call pg_* function, for every kind of reason (lazy developer, doesnt have time to read my documentation, or just someone who is trying to hack or stole the data in the database, etc...).I will try the PDO way, but, if i open a db connection with PDO at the begin of a script, and few lines before call pg_query(), it will fail?
DaNieL
That's correct, pg_query() won't see/use the PDO connection. Also, please be aware that this is by no means appropriate as a security measure. It's useful if you want to warn good plugin writers when they're "doing it wrong" but it won't stop a malicious plugin writer and nor will any other trick like this. If you need to do that, then you need to sandbox your plugin writers and/or only allow your users to run plugins that you have reviewed/signed.
oops
Why do you say that this isnt a security measure? Surely this will not be the **only** one, but if those plugin cant connect/query directly the DB but they are forced to use the class/methods i wrote (and the developers can edit those methods), i think it should be a good security measure for the DB issue... or not?
DaNieL
I confirm that using PDO will work, but i dont like to use PDO for now (im using another db class)
DaNieL
It's not a security measure because plugin writers still have access to call any function they like. This is enough for a determined user to poke around, discover DB credentials wherever they may be stored, and make their own connection.
oops
I just saw your edit re: runkit/apd. You're on the right track but I think whitelisting is a better approach to sandboxing than blacklisting. Also, simply renaming the pg_* and mysql_* functions is not enough.
oops
Absolutely agree with you that renaming the functions is not enough, but this is the first issue i faced planning the module/plugins system; Soon i would googled/asked other question about, as you point out, to 'limit' the php behavior for the modules/plugins.. i cant rename all the 'potentially dangerous' functions ;)So, for this particular question i guess Oddthinking's answer has the deal, but you earned an upvote for lighting me about the sandbox technique!Thanks man!
DaNieL