Предположим, серверное ПО, получив запрос на поиск данных в новостях параметром 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();
Мдааа, здец какой-то вышел, на самом деле. Не самый удачный пример. Надеюсь, у кого-нибудь есть еще идеи.