Внедрение в строковые параметры

Предположим, серверное ПО, получив запрос на поиск данных в новостях параметром search_text, использует его в следующем SQL-запросе (здесь параметры экранируются кавычками):

…$search_text = $_REQUEST['search_text'];$res = mysql_query("SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption LIKE('%$search_text%')");

 

Сделав запрос вида http://example.org/script.php?search_text=Test мы получим выполнение следующего SQL-запроса:

SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption LIKE('%Test%')

Но, внедрив в параметр search_text символ кавычки (который используется в запросе), мы можем кардинально изменить поведение SQL-запроса. Например, передав в качестве параметра search_text значение ')+and+(news_id_author='1, мы вызовем к выполнению запрос:

SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption LIKE('%') AND (news_id_author='1%')

 

Получили примерное представление о том, что представляет собой данная угроза. Начинаем продумывать архитуктуру приложения с учетом полученной информации, одновременно стараясь максимально упростить процесс работы с системой.

Один из вариантов исполнения задачи

В случае, если все параметры для составления БД будут связаны в единое целое из одиночных параметров, вероятность проведения любой атаки будет нецелесообразной, поскольку единственный меняющийся параметр WHERE будет динамически фильтроваться. Чтобы применить все возможности ООП, можем реализовать запросы вида:

$database = Database::getInstance();

$database->query()

->select( '*' )

->from( 'table' )

->where(

array(

'tName' => $_GET[ 'n' ],

'&tSurname' => $_GET[ 's' ]

)

)

->orderBy( 'tID', 'DESC' )

->limit( 10 )

->build();

 

 

Начинаем реализовывать приложение:

<?php

 

class Database {

private $queryInfo = array();

private $connection = null;

 

// TRUE для вывода запросов без обращений к БД; FALSE для подключения к БД и выполнения запросов

const DebugMode = true;

 

public function __construct( $server, $username, $password, $database ) {

if( self::DebugMode === true ) return true;

 

if( ! $this->connection = mysql_connect( $server, $username, $password ) ) trigger_error( 'Unable to connect to MySQL' );

if( ! mysql_select_db( $database, $this->connection ) ) trigger_error( 'Unable to connect to database' );

 

return true;

}

 

public function __call( $function, $args ) {

switch( strtolower( $function ) ) {

case 'select':

case 'update':

case 'delete':

case 'insert':

$this->queryInfo[ 'command' ] = strtolower( $function );

if( strtolower( $function ) == 'update' ) $this->queryInfo[ 'table' ] = strtolower( $args[ 0 ] );

break;

 

case 'select':

$this->queryInfo[ 'what' ] = strtolower( $args[ 0 ] );

break;

 

case 'from':

case 'to':

$this->queryInfo[ 'table' ] = strtolower( $args[ 0 ] );

break;

 

case 'limit':

$this->queryInfo[ 'limit' ] = (int) $args[ 0 ];

break;

 

case 'orderby':

$this->queryInfo[ 'orderBy' ] = $args[ 0 ];

$this->queryInfo[ 'orderMethod' ] = $args[ 1 ] == 'DESC' ? 'DESC' : 'ASC';

break;

 

case 'query':

$this->queryInfo = array();

break;

 

case 'where':

$where = null;

foreach( $args[ 0 ] as $tableName => $value ) {

$value = str_replace( '\'', '\\\'', $value );

$code = substr( $tableName, 0, 1 );

if( $code == '|' ) {

$tableName = substr( $tableName, 1 );

$where .= " OR $tableName='$value'";

} elseif( $code == '&' ) {

$tableName = substr( $tableName, 1 );

$where .= " AND $tableName='$value'";

} else {

$where .= " $tableName='$value'";

}

}

$this->queryInfo[ 'where' ] = $where;

break;

 

case 'set':

$setData = "";

foreach( $args[ 0 ] as $tableName => $value ) {

$value = str_replace( '\'', '\\\'', $value );

$setData .= " $tableName='$value',";

}

$this->queryInfo[ 'set' ] = substr( $setData, 0, strlen( $setData ) - 1 );

break;

 

case 'data':

$dataCols = "";

$dataValues = "";

foreach( $args[ 0 ] as $columnName => $value ) {

$value = str_replace( '\'', '\\\'', $value );

$dataCols .= "$columnName,";

$dataValues .= "'$value',";

}

$this->queryInfo[ 'data' ] = '(' . substr( $dataCols, 0, strlen( $dataCols ) - 1 ) . ') VALUES (' . substr( $dataValues, 0, strlen( $dataValues ) - 1 ) . ')';

break;

 

case 'build':

$builders = array(

'select' => "SELECT %what% FROM %table% %addit%",

'update' => "UPDATE %table% SET %set% %addit%",

'delete' => "DELETE FROM %table% %addit%",

'insert' => "INSERT INTO %table% %data%"

);

 

if( $this->queryInfo[ 'command' ] == null ) trigger_error( 'Command is not set' );

elseif( $this->queryInfo[ 'command' ] == 'select' ) {

if( $this->queryInfo[ 'table' ] == null ) trigger_error( 'Table to select data from is not set' );

 

$where = ( $this->queryInfo[ 'where' ] ? " WHERE {$this->queryInfo[ 'where' ]}" : null );

$limit = ( $this->queryInfo[ 'limit' ] ? " LIMIT {$this->queryInfo[ 'limit' ]}" : null );

$orderBy = ( $this->queryInfo[ 'orderBy' ] ? " ORDER BY {$this->queryInfo[ 'orderBy' ]} {$this->queryInfo[ 'orderMethod' ]}" : null );

$additional = "$where $orderBy $limit";

$replaces = array(

'%table%' => $this->queryInfo[ 'table' ],

'%what%' => ( $this->queryInfo[ 'what' ] ? $this->queryInfo[ 'what' ] : '*' ),

'%addit%' => $additional,

);

 

$this->sendQuery( strtr( $builders[ $this->queryInfo[ 'command' ] ], $replaces ) );

} elseif( $this->queryInfo[ 'command' ] == 'update' ) {

if( $this->queryInfo[ 'table' ] == null ) trigger_error( 'Table to update data is not set' );

if( $this->queryInfo[ 'set' ] == null ) trigger_error( 'Data to update is not set' );

 

$where = ( $this->queryInfo[ 'where' ] ? " WHERE {$this->queryInfo[ 'where' ]}" : null );

$additional = "$where";

$replaces = array(

'%table%' => $this->queryInfo[ 'table' ],

'%set%' => $this->queryInfo[ 'set' ],

'%addit%' => $additional,

);

 

$this->sendQuery( strtr( $builders[ $this->queryInfo[ 'command' ] ], $replaces ) );

} elseif( $this->queryInfo[ 'command' ] == 'insert' ) {

if( $this->queryInfo[ 'table' ] == null ) trigger_error( 'Table to update data is not set' );

if( $this->queryInfo[ 'data' ] == null ) trigger_error( 'Data to insert is not set' );

 

$replaces = array(

'%table%' => $this->queryInfo[ 'table' ],

'%data%' => $this->queryInfo[ 'data' ]

);

 

$this->sendQuery( strtr( $builders[ $this->queryInfo[ 'command' ] ], $replaces ) );

} elseif( $this->queryInfo[ 'command' ] == 'delete' ) {

if( $this->queryInfo[ 'table' ] == null ) trigger_error( 'Table to delete data from is not set' );

 

$where = ( $this->queryInfo[ 'where' ] ? " WHERE {$this->queryInfo[ 'where' ]}" : null );

$additional = "$where";

$replaces = array(

'%table%' => $this->queryInfo[ 'table' ],

'%addit%' => $additional

);

 

$this->sendQuery( strtr( $builders[ $this->queryInfo[ 'command' ] ], $replaces ) );

}

break;

}

 

return $this;

}

 

private function sendQuery( $query ) {

if( self::DebugMode === true ) print "$query<br />";

else return mysql_query( $query );

}

}

 

// Примеры команд

$database = new Database(

'127.0.0.1',

'root',

'test',

'test'

);

 

$database->query()

->select( '*' )

->from( 'table' )

->where(

array(

'tName' => $_GET[ 'n' ],

'&tSurname' => $_GET[ 's' ]

)

)

->orderBy( 'tID', 'DESC' )

->limit( 10 )

->build();

 

$database->query()

->update( 'table' )

->set(

array(

'tName' => '041',

'tSurname' => '854'

)

)

->where(

array(

'tName' => $_GET[ 'n' ],

'&tSurname' => $_GET[ 's' ]

)

)

->build();

 

$database->query()

->insert()

->to( 'table' )

->data(

array(

'tName' => '041',

'tSurname' => '854'

)

)

->build();

 

$database->query()

->delete()

->from( 'table' )

->where(

array(

'tName' => '041',

'|tSurname' => '854'

)

)

->build();

 

 

Мдааа, здец какой-то вышел, на самом деле. Не самый удачный пример. Надеюсь, у кого-нибудь есть еще идеи.