Author Topic: Using Sprintf and Sanitizing data automagically  (Read 934 times)

Offline karnedge

  • Level 17
  • *
  • Posts: 170
  • Reputation: +4/-0
  • ctrlHack provides the server, you bring the skill.
    • View Profile
    • ctrl://Hack.game
Using Sprintf and Sanitizing data automagically
« on: May 13, 2009, 07:32:53 PM »
I'm setting up my own database object and it's rather simple minded because I hate CURD methods everyone seems to use cuz it limits ORDER BY, not to mention it's a lot more typing I think.

Code: [Select]
if(is_array($data)){
foreach($data as $k => $v){
$data[$k] = $this->sanitize($v);
}
}
$query = @sprintf($queryString,$data[0],$data[1],$data[2],$data[3],$data[4],$data[5],$data[6]);
$this->result = @mysql_query($query,$this->connection);

So, as an example, you would do...
$db->query("SELECT id FROM users WHERE username = '%s' AND password = '%s',array($username,md5($password)));
etc...

I tried doing loops with the data array to count how many keys there are and place them into the sprintf but it hates me doing that...

Anyone know of a better way to do this?
ctrlHack - Hacking simulation RPG in development.
Latest blog: Back on Track
bbgFramework v0.1.3

Offline Harkins

  • Level 28
  • **
  • Posts: 424
  • Reputation: +11/-2
  • Coder, blogger, entrepreneur.
    • View Profile
    • Push CX - Blog
Re: Using Sprintf and Sanitizing data automagically
« Reply #1 on: May 13, 2009, 09:02:11 PM »
It looks like you want data to be either scalar or an array. So that's why the weirdness on the first line here:

Code: [Select]
$data = (is_array($data)) ? $arr : Array($data);
$query = vsprintf($query, $data);
$this->result = @mysql_query($query, $this->connection);

Visit #bbg on irc.freenode.net to talk browser games anytime.

Offline codestryke

  • Administrator
  • Level 33
  • *****
  • Posts: 589
  • Reputation: +22/-0
    • View Profile
    • eXtremeCast Games
Creating online addictions, one game at a time:

Offline JGadrow

  • Level 35
  • **
  • Posts: 1,133
  • Reputation: +23/-2
    • View Profile
Re: Using Sprintf and Sanitizing data automagically
« Reply #3 on: May 14, 2009, 06:27:15 AM »
As a drop-in replacement for the code that you already have, instead of directly calling the function (because you have a variable number of parameters), you need to call it through call_user_func_array ().

So, to convert your code (comments added to explain logic):
Code: [Select]
// sanitize data
if(is_array($data)){
foreach($data as $k => $v){
$data[$k] = $this->sanitize($v);
}
}

// prepend array with known parameters
array_unshift ($data, $queryString);

// dynamically call sprintf
$query = @call_user_func_array ('sprintf', $data);

// perform query
$this->result = @mysql_query($query,$this->connection);
Idiocy - Never underestimate the power of stupid people in large groups.


Offline karnedge

  • Level 17
  • *
  • Posts: 170
  • Reputation: +4/-0
  • ctrlHack provides the server, you bring the skill.
    • View Profile
    • ctrl://Hack.game
Re: Using Sprintf and Sanitizing data automagically
« Reply #4 on: May 14, 2009, 04:40:04 PM »
As a drop-in replacement for the code that you already have, instead of directly calling the function (because you have a variable number of parameters), you need to call it through call_user_func_array ().

So, to convert your code (comments added to explain logic):
Code: [Select]
// sanitize data
if(is_array($data)){
foreach($data as $k => $v){
$data[$k] = $this->sanitize($v);
}
}

// prepend array with known parameters
array_unshift ($data, $queryString);

// dynamically call sprintf
$query = @call_user_func_array ('sprintf', $data);

// perform query
$this->result = @mysql_query($query,$this->connection);

Makari, you are my hero. haha that call_user_func_array() was exactly what I needed. i took out the array_unshift() part and used func_get_args() instead... which means the QueryString is already at the beginning of the array already. Not to mention this allows me to make my Query usage even simplier...

An Update to my Example:
$db->query("SELECT id FROM users WHERE username = '%s' AND password = '%s'",$username,md5($password));

so now you dont even need to do an array() for the second argument, it will automagically get all the data it needs.

Thanks Makari and everyone else!


Here's the code. I think I could have it skip the first array a bit simpler (otherwise it will sanitize the query itself) but my mind isn't in the right place at the moment:
Code: [Select]
$data = func_get_args();
foreach($data as $k => $v){
if($k != 0) $data[$k] = $this->sanitize($v);
}
$query = @call_user_func_array('sprintf',$data);
$this->result = @mysql_query($query,$this->connection);
ctrlHack - Hacking simulation RPG in development.
Latest blog: Back on Track
bbgFramework v0.1.3

Offline karnedge

  • Level 17
  • *
  • Posts: 170
  • Reputation: +4/-0
  • ctrlHack provides the server, you bring the skill.
    • View Profile
    • ctrl://Hack.game
Re: Using Sprintf and Sanitizing data automagically
« Reply #5 on: May 14, 2009, 05:32:40 PM »
Thanks codestryke for the link as well, that class helped remind me of a couple of errors to handle.

Let me know what you think or even if something is vulnerable or susceptible to error.
Code: [Select]
<?php
/**
 * @version 1.0.3
 * @package ctrlGames Framework
 * @copyright Copyright (C) 2009 - ctrlGames, Inc. All rights reserved.
 * @license GNU Affero General Public License, see LICENSE.PHP
 */
 
// no direct access
defined('_CGFW') or die('Restricted Access');

class 
Database {
private $connection; // Active Connection
public $result; // Results retrieved and saved
public $queryCounter 0; // Number of queries made
public $totalTime 0; // Amount of time queries took
private $debug true; // Debug displays the queries

public function __construct(){
global $error;
$this->connection = @mysql_connect(DB_HOST,DB_USER,DB_PASS);
if(!is_resource($this->connection)) $error->setError('mysql',"Unable to connect to MySQL.");
if(!@mysql_select_db(DB_NAME,$this->connection)){
@mysql_close($this->connection);
$error->setError('mysql',"Unable to select database.");
}
}

/**
 * Queries the database with the string provided
 * @return  void
 */
public function query($queryString){
global $error;
$startTime $this->getMicroTime();
$this->queryCounter++;
$data func_get_args();
unset($data[0]);
foreach($data as $k => $v){
$data[$k] = $this->sanitize($v);
}
$query vsprintf($queryString,$data);
$this->result = @mysql_query($query,$this->connection);
$this->totalTime += $this->getMicroTime() - $startTime;
if(mysql_errno($this->connection)) $error->setError('mysql',mysql_error($this->connection)."<br /><blockquote>$query</blockquote>");
if($this->debug) echo $this->queryCounter." (".round($this->totalTime,6)."ms): $query <br />";
}

/**
 * Results
 * @params $how = 'i' for numeric array, 'a' for associative array, null for both
 * @return  (array) fetch array for Select query
 */
public function getResults($how null){
switch($how){
case 'i': return @mysql_fetch_row($this->result);
case 'a': return @mysql_fetch_assoc($this->result);
default: return @mysql_fetch_array($this->result);
}
}

/**
 * Affected
 * @return  (int) affected_rows for last Insert, Update or Delete query
 */
public function getAffected(){
return @mysql_affected_rows($this->connection);
}

/**
 * Rows
 * @return  (int) num_rows for last Select query
 */
public function getRows(){
return @mysql_num_rows($this->result);
}

/**
 * Id
 * @return  (int) auto-increment ID for last Insert query
 */
public function getId(){
return @mysql_insert_id($this->connection);
}

/**
 * MicroTime
 * @return  (float) actual time in milliseconds
 */
private function getMicroTime() {
list($usec,$sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}

/**
 * Santizes data to keep out the SQL injections
 * @return  (string) $data
 */
private function sanitize($data){
$data trim($data);  // Remove whitespace
if(get_magic_quotes_gpc()){ // Stripslashes if magic_quotes_gpc is enabled
$data stripslashes($data);

return mysql_real_escape_string($data);
}
}
?>

With this you can use it very easily (keep in mind I do have my error handling class in there as well):
Quote
$db = new Database;
$db->query("SELECT id FROM users WHERE id = '%s' AND username = '%s'",1,'admin');
$db->query("UPDATE users SET email = '%s' WHERE id = '1'",$newemail);
$db->query("INSERT INTO users(username,password,time) VALUES ('%s','%s','%s')",$username,md5($password),time())

OR you can just only one argument and it wont spazz out:
$db->query("SELECT COUNT(id) FROM users WHERE time > 500");

So now you can use the query without changing your conversion assignments. (ie; %f for float, %u for integers... whatever, i'll probably never use anything other than %s)

If you like it, I'll post it in the Scripts Vault (when I have access ha) or the Articles forum.
« Last Edit: May 14, 2009, 05:57:34 PM by karnedge »
ctrlHack - Hacking simulation RPG in development.
Latest blog: Back on Track
bbgFramework v0.1.3

Offline Harkins

  • Level 28
  • **
  • Posts: 424
  • Reputation: +11/-2
  • Coder, blogger, entrepreneur.
    • View Profile
    • Push CX - Blog
Re: Using Sprintf and Sanitizing data automagically
« Reply #6 on: May 14, 2009, 05:42:46 PM »
I think vsprintf is a drop-in replacement for sprintf that would allow you to do away with the call_user_func.

Visit #bbg on irc.freenode.net to talk browser games anytime.

Offline karnedge

  • Level 17
  • *
  • Posts: 170
  • Reputation: +4/-0
  • ctrlHack provides the server, you bring the skill.
    • View Profile
    • ctrl://Hack.game
Re: Using Sprintf and Sanitizing data automagically
« Reply #7 on: May 14, 2009, 06:14:37 PM »
I think vsprintf is a drop-in replacement for sprintf that would allow you to do away with the call_user_func.

Wow, I totally didn't see your post... you're right, that's much cleaner to use that instead so I updated my code and the previous post as well.

added the $queryString back to the arguments and set it up with the following (basically):
Code: [Select]
public function query($queryString){
$data = func_get_args();
unset($data[0]);
foreach($data as $k => $v){
$data[$k] = $this->sanitize($v);
}
$query = vsprintf($queryString,$data);
$this->result = @mysql_query($query,$this->connection);
}

wham, bam, thank you ma'am.
ctrlHack - Hacking simulation RPG in development.
Latest blog: Back on Track
bbgFramework v0.1.3

Offline JGadrow

  • Level 35
  • **
  • Posts: 1,133
  • Reputation: +23/-2
    • View Profile
Re: Using Sprintf and Sanitizing data automagically
« Reply #8 on: May 15, 2009, 06:08:40 AM »
I think vsprintf is a drop-in replacement for sprintf that would allow you to do away with the call_user_func.
Yup, I missed the post too :P Much better solution, calling in that way will make the script perform slightly better since it doesn't need to lookup the function first :)

Good call, Harkins!
Idiocy - Never underestimate the power of stupid people in large groups.


Offline Harkins

  • Level 28
  • **
  • Posts: 424
  • Reputation: +11/-2
  • Coder, blogger, entrepreneur.
    • View Profile
    • Push CX - Blog
Re: Using Sprintf and Sanitizing data automagically
« Reply #9 on: May 15, 2009, 08:07:07 AM »
Rule of thumb for c-like languages: there's always another printf function. :)

Visit #bbg on irc.freenode.net to talk browser games anytime.

 


SimplePortal 2.3.3 © 2008-2010, SimplePortal