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

Предположим, серверное ПО, получив запрос на поиск данных в новостях параметром 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();

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: