Por que não devo usar funções mysql_ * no PHP?

2502

Quais são as razões técnicas pelas quais não se deve usar mysql_*funções? (por exemplo mysql_query(), mysql_connect()ou mysql_real_escape_string())?

Por que devo usar outra coisa, mesmo que ela funcione no meu site?

Se eles não funcionam no meu site, por que recebo erros como

Aviso: mysql_connect (): Esse arquivo ou diretório não existe

Fantasma de Madara
fonte
Erro para ser semelhante: Erro fatal: Erro não capturado: Chamada para a função indefinida mysql_connect () ...
Bimal Poudel / 02/17
21
Reprovado por si só é motivo suficiente para evitá-los
Sasa1234

Respostas:

2088

A extensão do MySQL:

  • Não está em desenvolvimento ativo
  • Foi oficialmente descontinuado a partir do PHP 5.5 (lançado em junho de 2013).
  • Foi totalmente removido a partir do PHP 7.0 (lançado em dezembro de 2015)
    • Isso significa que, em 31 de dezembro de 2018, ele não existia em nenhuma versão suportada do PHP. Se você estiver usando uma versão do PHP compatível, estará usando uma versão que não corrige os problemas de segurança.
  • Falta uma interface OO
  • Não suporta:
    • Consultas assíncronas sem bloqueio
    • Instruções preparadas ou consultas parametrizadas
    • Procedimentos armazenados
    • Várias declarações
    • Transações
    • O "novo" método de autenticação de senha (ativado por padrão no MySQL 5.6; requerido em 5.7)
    • Qualquer uma das novas funcionalidades do MySQL 5.1 ou posterior

Como está obsoleto, usá-lo torna seu código menos prova de futuro.

A falta de suporte para instruções preparadas é particularmente importante, pois elas fornecem um método mais claro e menos propenso a erros de escapar e citar dados externos do que escapar manualmente com uma chamada de função separada.

Veja a comparação de extensões SQL .

Quentin
fonte
287
Preterido por si só é motivo suficiente para evitá-los. Eles não estarão lá um dia e você não ficará feliz se confiar neles. O resto é apenas uma lista de coisas que o uso das extensões antigas impediu as pessoas de aprender.
Tim Post
111
A depreciação não é a bala mágica que todos parecem pensar que é. O PHP em si não estará lá um dia, mas contamos com as ferramentas que temos à nossa disposição hoje. Quando tivermos que trocar de ferramentas, faremos.
Lightness Races em órbita
133
@LightnessRacesinOrbit - A depreciação não é uma bala mágica, é uma bandeira que diz "Reconhecemos que isso é péssimo, então não vamos apoiá-lo por muito mais tempo". Embora tenha uma melhor prova futura de código seja um bom motivo para se afastar dos recursos obsoletos, ele não é o único (ou mesmo o principal). Troque as ferramentas porque existem ferramentas melhores, não porque você é forçado. (E mudar as ferramentas antes que você seja forçado a fazê-lo significa que você não está aprendendo as novas apenas porque seu código parou de funcionar e precisa ser corrigido ontem ... que é o pior momento para aprender novas ferramentas).
Quentin
18
Uma coisa que não vi mencionada sobre a falta de declarações preparadas é a questão do desempenho. Toda vez que você emite uma declaração, algo precisa compilá-la para que o daemon MySQL possa entendê-la. Com esta API, se você emitir 200.000 da mesma consulta em um loop, é 200.000 vezes a consulta que deve ser compilada para que o MySQL entenda. Com instruções preparadas, ele é compilado uma vez e, em seguida, os valores são parametrizados no SQL compilado.
Goldentoa11
20
@symcbean, certamente não suporta declarações preparadas. Essa é, de fato, a principal razão pela qual está obsoleta. Sem instruções preparadas (fáceis de usar), a extensão mysql geralmente é vítima de ataques de injeção de SQL.
Rustyx
1287

O PHP oferece três APIs diferentes para se conectar ao MySQL. Estes são os mysql(removidos a partir do PHP 7) mysqlie as PDOextensões.

As mysql_*funções costumavam ser muito populares, mas seu uso não é mais incentivado. A equipe de documentação está discutindo a situação de segurança do banco de dados e educar os usuários a se afastarem da extensão ext / mysql comumente usada faz parte disso (verifique em php.internals: deprecating ext / mysql ).

E a equipe de desenvolvedores PHP mais tarde tomou a decisão para gerar E_DEPRECATEDerros quando os usuários se conectar ao MySQL, seja por meio de mysql_connect(), mysql_pconnect()ou a funcionalidade de conexão implícita incorporado ext/mysql.

ext/mysqlfoi oficialmente descontinuado a partir do PHP 5.5 e foi removido a partir do PHP 7 .

Veja a caixa vermelha?

Quando você acessa qualquer mysql_*página do manual de funções, vê uma caixa vermelha explicando que não deve mais ser usada.

Por quê


Afastar- ext/mysqlse não é apenas segurança, mas também acesso a todos os recursos do banco de dados MySQL.

ext/mysqlfoi desenvolvido para o MySQL 3.23 e só recebeu muito poucas adições desde então, mantendo principalmente a compatibilidade com esta versão antiga, o que dificulta a manutenção do código. Recursos ausentes que não são suportados por ext/mysqlincluem: ( do manual do PHP ).

Razão para não usar a mysql_*função :

  • Não está em desenvolvimento ativo
  • Removido a partir do PHP 7
  • Falta uma interface OO
  • Não suporta consultas assíncronas e sem bloqueio
  • Não suporta instruções preparadas ou consultas parametrizadas
  • Não suporta procedimentos armazenados
  • Não suporta múltiplas instruções
  • Não suporta transações
  • Não suporta todas as funcionalidades do MySQL 5.1

Ponto acima citado na resposta de Quentin

A falta de suporte para instruções preparadas é particularmente importante, pois elas fornecem um método mais claro e menos propenso a erros de escapar e citar dados externos do que escapar manualmente com uma chamada de função separada.

Veja a comparação de extensões SQL .


Suprimindo avisos de descontinuação

Enquanto o código está sendo convertido para MySQLi/ PDO, os E_DEPRECATEDerros podem ser suprimidos configurando-se error_reportingno php.ini para excluirE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Observe que isso também ocultará outros avisos de descontinuação , que, no entanto, podem ser para outras coisas que não o MySQL. ( do manual do PHP )

O artigo DOP vs. MySQLi: Qual você deve usar? por Dejan Marjanovic irá ajudá-lo a escolher.

E uma maneira melhor é PDO, e agora estou escrevendo um PDOtutorial simples .


Um tutorial DOP simples e curto


P. A primeira pergunta em minha mente foi: o que é DOP?

R. “O PDO - PHP Data Objects - é uma camada de acesso ao banco de dados que fornece um método uniforme de acesso a vários bancos de dados.”

texto alternativo


Conectando ao MySQL

Com mysql_*function ou podemos dizer da maneira antiga (descontinuado no PHP 5.5 e superior)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

Com PDO: Tudo que você precisa fazer é criar um novo PDOobjeto. O construtor aceita parâmetros para especificar o PDOconstrutor da fonte de banco de dados geralmente usa quatro parâmetros que são DSN(nome da fonte de dados) e username, opcionalmente password,.

Aqui eu acho que você está familiarizado com tudo, exceto DSN; isso é novo no PDO. A DSNé basicamente uma série de opções que informam PDOqual driver usar e detalhes da conexão. Para referência adicional, consulte o PDO MySQL DSN .

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Nota: você também pode usar charset=UTF-8, mas às vezes causa um erro, por isso é melhor usá-lo utf8.

Se houver algum erro de conexão, ele lançará um PDOExceptionobjeto que pode ser capturado para ser manuseado Exceptionainda mais.

Boa leitura : Gerenciamento de conexões e conexões ¶

Você também pode passar várias opções de driver como uma matriz para o quarto parâmetro. Eu recomendo passar o parâmetro que coloca PDOno modo de exceção. Como alguns PDOdrivers não suportam instruções preparadas nativas, também PDOexecuta a emulação da preparação. Ele também permite que você ative manualmente essa emulação. Para usar as instruções preparadas do lado do servidor nativo, você deve defini-lo explicitamente false.

O outro é desativar a emulação de preparação, que é ativada no MySQLdriver por padrão, mas a emulação de preparação deve ser desativada para uso PDOcom segurança.

Mais tarde explicarei por que a emulação de preparação deve ser desativada. Para encontrar o motivo, verifique este post .

Só é utilizável se você estiver usando uma versão antiga da MySQLqual eu não recomendo.

Abaixo está um exemplo de como você pode fazer isso:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Podemos definir atributos após a construção da DOP?

Sim , também podemos definir alguns atributos após a construção do PDO com o setAttributemétodo:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Manipulação de erros


O tratamento de erros é muito mais fácil do PDOque mysql_*.

Uma prática comum ao usar mysql_*é:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()não é uma boa maneira de lidar com o erro, pois não podemos lidar com isso die. Ele terminará abruptamente o script e ecoará o erro na tela que você normalmente NÃO deseja mostrar aos usuários finais, e permitirá que hackers descubram o seu esquema. Como alternativa, os valores de retorno das mysql_*funções geralmente podem ser usados ​​em conjunto com mysql_error () para manipular erros.

PDOoferece uma solução melhor: exceções. Tudo o que fazemos com PDOdeve ser envolto em um try- catchbloco. Podemos forçar PDOum dos três modos de erro, definindo o atributo do modo de erro. Três modos de tratamento de erros estão abaixo.

  • PDO::ERRMODE_SILENT. É apenas definir códigos de erro e agir da mesma maneira mysql_*que você deve verificar cada resultado e depois ver $db->errorInfo();os detalhes do erro.
  • PDO::ERRMODE_WARNINGLevante E_WARNING. (Avisos em tempo de execução (erros não fatais). A execução do script não é interrompida.)
  • PDO::ERRMODE_EXCEPTION: Lança exceções. Representa um erro gerado pelo PDO. Você não deve lançar um PDOExceptiondo seu próprio código. Veja Exceções para obter mais informações sobre exceções no PHP. Ele age muito parecido or die(mysql_error());quando não é pego. Mas or die(), diferentemente , o PDOExceptionpode ser capturado e manuseado normalmente, se você optar por fazê-lo.

Boa leitura :

Gostar:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

E você pode envolvê-lo try- catchcomo abaixo:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Você não tem que lidar com try- catchagora. Você pode capturá-lo a qualquer momento apropriado, mas eu recomendo fortemente que você use try- catch. Também pode fazer mais sentido capturá-lo fora da função que chama o PDOmaterial:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Além disso, você pode lidar com or die()isso ou podemos dizer o mesmo mysql_*, mas será realmente variado. Você pode ocultar as mensagens de erro perigosas na produção girando display_errors offe apenas lendo seu log de erros.

Agora, depois de ler todas as coisas acima, você provavelmente está pensando: o que diabos é que, quando eu só quero começar inclinando-se simples SELECT, INSERT, UPDATE, ou DELETEdeclarações? Não se preocupe, aqui vamos nós:


Selecionando dados

DOP selecione a imagem

Então, o que você está fazendo mysql_*é:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Agora PDO, você pode fazer o seguinte:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Ou

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Nota : Se você estiver usando o método como abaixo ( query()), esse método retornará um PDOStatementobjeto. Então, se você deseja buscar o resultado, use-o como acima.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

No PDO Data, ele é obtido através do ->fetch()método de manipulação de instruções. Antes de chamar a busca, a melhor abordagem seria dizer à DOP como você deseja que os dados sejam buscados. Na seção abaixo, estou explicando isso.

Modos de busca

Observe o uso de PDO::FETCH_ASSOCno código fetch()e fetchAll()acima. Isso informa PDOpara retornar as linhas como uma matriz associativa com os nomes dos campos como chaves. Também existem muitos outros modos de busca, os quais explicarei um por um.

Antes de tudo, explico como selecionar o modo de busca:

 $stmt->fetch(PDO::FETCH_ASSOC)

No acima, eu tenho usado fetch(). Você também pode usar:

Agora chego ao modo de busca:

  • PDO::FETCH_ASSOC: retorna uma matriz indexada pelo nome da coluna, conforme retornado no seu conjunto de resultados
  • PDO::FETCH_BOTH (padrão): retorna uma matriz indexada pelo nome da coluna e pelo número da coluna indexada 0, conforme retornado no seu conjunto de resultados

Existem ainda mais opções! Leia sobre todos eles na PDOStatementdocumentação do Fetch. .

Obtendo a contagem de linhas :

Em vez de usar mysql_num_rowspara obter o número de linhas retornadas, você pode obter um PDOStatemente faça rowCount(), como:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Obtendo o último ID inserido

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Inserir e atualizar ou excluir instruções

Inserir e atualizar a imagem DOP

O que estamos fazendo em mysql_*função é:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

E no pdo, a mesma coisa pode ser feita por:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

Na consulta acima, PDO::execexecute uma instrução SQL e retorne o número de linhas afetadas.

Inserir e excluir serão abordados mais tarde.

O método acima é útil apenas quando você não está usando variáveis ​​na consulta. Mas quando você precisar usar uma variável em uma consulta, nunca tente como o descrito acima e existe a instrução preparada ou a instrução parametrizada .


Declarações Preparadas

P. O que é uma declaração preparada e por que preciso deles?
R. Uma instrução preparada é uma instrução SQL pré-compilada que pode ser executada várias vezes enviando apenas os dados para o servidor.

O fluxo de trabalho típico do uso de uma instrução preparada é o seguinte ( citado na Wikipedia em três pontos ):

  1. Preparar : o modelo de instrução é criado pelo aplicativo e enviado ao sistema de gerenciamento de banco de dados (DBMS). Certos valores são deixados não especificados, chamados parâmetros, espaços reservados ou variáveis ​​de ligação (identificadas ?abaixo):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. O DBMS analisa, compila e executa a otimização de consulta no modelo de instrução e armazena o resultado sem executá-lo.

  3. Executar : posteriormente, o aplicativo fornece (ou vincula) valores para os parâmetros e o DBMS executa a instrução (possivelmente retornando um resultado). O aplicativo pode executar a instrução quantas vezes quiser com valores diferentes. Neste exemplo, ele pode fornecer 'Bread' para o primeiro parâmetro e 1.00para o segundo parâmetro.

Você pode usar uma instrução preparada incluindo espaços reservados no seu SQL. Existem basicamente três sem espaços reservados (não tente isso com a variável acima de um), um com espaços reservados sem nome e outro com espaços reservados nomeados.

P. Então, agora, quais são os espaços reservados nomeados e como os uso?
A. Espaços reservados nomeados. Use nomes descritivos precedidos por dois pontos, em vez de pontos de interrogação. Não nos preocupamos com a posição / ordem do valor no nome do local:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Você também pode vincular usando uma matriz de execução:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Outro recurso interessante para os OOPamigos é que os espaços reservados nomeados têm a capacidade de inserir objetos diretamente no seu banco de dados, assumindo que as propriedades correspondam aos campos nomeados. Por exemplo:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

P. Então, agora, o que são espaços reservados sem nome e como os uso?
A. Vamos dar um exemplo:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

e

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

No acima, você pode vê-los em ?vez de um nome, como em um marcador de lugar. Agora, no primeiro exemplo, atribuímos variáveis ​​aos vários espaços reservados ( $stmt->bindValue(1, $name, PDO::PARAM_STR);). Em seguida, atribuímos valores a esses espaços reservados e executamos a declaração. No segundo exemplo, o primeiro elemento da matriz vai para o primeiro ?e o segundo para o segundo ?.

NOTA : Nos espaços reservados não nomeados , devemos cuidar da ordem correta dos elementos na matriz que estamos passando para o PDOStatement::execute()método.


SELECT, INSERT, UPDATE, DELETEPreparado consultas

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();

NOTA:

No entanto PDOe / ou MySQLinão são completamente seguros. Verifique a resposta As instruções preparadas pelo DOP são suficientes para impedir a injeção de SQL? por ircmaxell . Além disso, estou citando parte da resposta dele:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
NullPoiиteя
fonte
15
O que a boa leitura acima deve mencionar: afirmação preparada tira qualquer uso significativo da IN (...) construct.
Eugen Rieck
24
A questão era "Por que não devo usar funções mysql_ * no PHP"? Essa resposta, apesar de impressionante e cheia de informações úteis, fica MUITO fora do escopo e, como @trejder diz - 8 em cada 10 pessoas perderão essas informações simplesmente porque não têm quatro horas para gastar tentando trabalhar com elas. isto. Isso seria muito mais valioso, dividido e usado como resposta a várias perguntas mais precisas.
Alex McMillan
Persoanlly eu prefiro mysqli e DOP. Mas para o manuseio de matrizes, tentei a alternativa de exceção. function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();Ele funciona para lançar exceções.
Kuldeep.kamboj
você lista Doesn't support non-blocking, asynchronous queriescomo um motivo para não usar o mysql_ - você também deve listá-lo como um motivo para não usar o DOP, porque o DOP também não suporta. (mas MySQLi suporta)
hanshenrik
é possível usar Charset utf8mb4_unicode_ci como eu tenho um banco de dados que está usando isso?
Ryan Stone
301

Primeiro, vamos começar com o comentário padrão que damos a todos:

Por favor, não use mysql_*funções no novo código . Eles não são mais mantidos e são oficialmente descontinuados . Veja a caixa vermelha ? Aprenda sobre as instruções preparadas e use o DOP ou o MySQLi - este artigo o ajudará a decidir quais. Se você escolher a DOP, aqui está um bom tutorial .

Vamos passar por isso, sentença por sentença, e explicar:

  • Eles não são mais mantidos e são oficialmente descontinuados

    Isso significa que a comunidade PHP está gradualmente dispensando suporte para essas funções muito antigas. É provável que eles não existam em uma versão futura (recente) do PHP! O uso continuado dessas funções pode quebrar seu código em um futuro não muito distante.

    NOVO! - ext / mysql agora está oficialmente obsoleto a partir do PHP 5.5!

    Mais novo! ext / mysql foi removido no PHP 7 .

  • Em vez disso, você deve aprender sobre declarações preparadas

    mysql_*A extensão não suporta instruções preparadas , o que é (entre outras coisas) uma contramedida muito eficaz contra a injeção de SQL . Ele corrigiu uma vulnerabilidade muito séria nos aplicativos dependentes do MySQL, que permite que os invasores tenham acesso ao seu script e realizem qualquer consulta possível no seu banco de dados.

    Para obter mais informações, consulte Como impedir a injeção de SQL no PHP?

  • Veja a caixa vermelha?

    Quando você acessa qualquer mysqlpágina do manual de funções, vê uma caixa vermelha explicando que não deve mais ser usada.

  • Use DOP ou MySQLi

    Existem alternativas melhores, mais robustas e bem construídas, o PDO - PHP Database Object , que oferece uma abordagem OOP completa à interação com o banco de dados, e o MySQLi , que é uma melhoria específica do MySQL.

Fantasma de Madara
fonte
6
Há mais uma coisa: acho que essa função ainda existe no PHP por apenas um motivo - compatibilidade com sistemas antigos, desatualizados, mas ainda executando CMS, comércio eletrônico, quadros de avisos etc. Finalmente, ela será removida e você terá que reescrever seu aplicação ...
Kamil
4
@ Kamil: Isso é verdade, mas não é realmente uma razão pela qual você não deve usá-lo. A razão para não usá-lo é porque ele é antiga, inseguro, etc. :)
O fantasma de Madara
4
@ Mario - os desenvolvedores do PHP têm um processo e acabaram de votar a favor de desaprovar formalmente o ext / mysql a partir do 5.5. Não é mais uma questão hipotética.
SDC
2
Adicionar algumas linhas extras com uma técnica comprovada, como DOP ou MySQLi, ainda oferece a facilidade de uso que o PHP sempre ofereceu. Espero, pelo bem do desenvolvedor, que ele / ela saiba que ver essas funções horríveis do mysql_ * em qualquer tutorial realmente prejudique a lição e informe o OP que esse tipo de código é muuuuito 10 anos atrás - e deve questionar o relevância do tutorial também!
FredTheWebGuy
1
O que a resposta deve mencionar: afirmação preparada tira qualquer uso significativo da IN (...) construct.
Eugen Rieck
217

Fácil de usar

Os motivos analíticos e sintéticos já foram mencionados. Para os novatos, há um incentivo mais significativo para parar de usar as funções mysql_ datadas.

APIs de banco de dados contemporâneas são apenas mais fáceis de usar.

São principalmente os parâmetros vinculados que podem simplificar o código. E com excelentes tutoriais (como visto acima), a transição para o DOP não é excessivamente árdua.

Reescrever uma base de código maior de uma só vez, no entanto, leva tempo. Raison d'être para esta alternativa intermediária:

Pdo_ * equivalente funciona no lugar do mysql_ *

Usando o < pdo_mysql.php > você pode alternar das funções antigas do mysql_ com o mínimo esforço . Ele adiciona pdo_wrappers de funções que substituem seus mysql_equivalentes.

  1. Simplesmente em cada script de chamada que precisa interagir com o banco de dados. include_once("pdo_mysql.php");

  2. Remova o mysql_prefixo da função em qualquer lugar e substitua-o por pdo_.

    • mysql_connect() torna-se pdo_connect()
    • mysql_query() torna-se pdo_query()
    • mysql_num_rows() torna-se pdo_num_rows()
    • mysql_insert_id() torna-se pdo_insert_id()
    • mysql_fetch_array() torna-se pdo_fetch_array()
    • mysql_fetch_assoc() torna-se pdo_fetch_assoc()
    • mysql_real_escape_string() torna-se pdo_real_escape_string()
    • e assim por diante...

  3. Seu código funcionará da mesma forma e ainda terá a mesma aparência:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }

Et voilà.
Seu código está usando o DOP.
Agora é hora de realmente utilizá- lo.

Parâmetros vinculados podem ser fáceis de usar

Você só precisa de uma API menos pesada.

pdo_query()adiciona suporte muito fácil a parâmetros vinculados. A conversão de código antigo é simples:

Mova suas variáveis ​​para fora da string SQL.

  • Adicione-os como parâmetros de função delimitados por vírgula pdo_query().
  • Coloque pontos de interrogação ?como espaços reservados onde as variáveis ​​estavam antes.
  • Livre-se de 'aspas simples que incluíam anteriormente valores / variáveis ​​de string.

A vantagem se torna mais óbvia para códigos mais longos.

Frequentemente, as variáveis ​​de sequência não são apenas interpoladas para o SQL, mas concatenadas com chamadas de escape no meio.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

Com os ?marcadores aplicados, você não precisa se preocupar com isso:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Lembre-se de que pdo_ * ainda permite um ou outro .
Apenas não escape de uma variável e ligue-a na mesma consulta.

  • O recurso de espaço reservado é fornecido pelo DOP real por trás dele.
  • Assim, também é permitido :namedlistas de marcadores posteriores.

Mais importante, você pode passar as variáveis ​​$ _REQUEST [] com segurança para trás de qualquer consulta. Quando os <form>campos enviados correspondem exatamente à estrutura do banco de dados, é ainda mais curto:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Tanta simplicidade. Mas vamos voltar a mais algumas recomendações de reescrita e razões técnicas sobre por que você pode se livrar mysql_e escapar.

Corrija ou remova qualquer sanitize()função oldschool

Depois de converter todas as mysql_chamadas para pdo_querycom parâmetros associados, remova todas as pdo_real_escape_stringchamadas redundantes .

Em particular, você deve corrigir qualquer sanitizeou cleanou filterThisou clean_datafunções como anunciado por tutoriais datadas de uma forma ou de outra:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

O bug mais flagrante aqui é a falta de documentação. Mais significativamente, a ordem da filtragem estava exatamente na ordem errada.

  • A ordem correta teria sido: descontinuada stripslashescomo a chamada mais interna, depois trim, posteriormente strip_tags, htmlentitiespara o contexto de saída e, por último, apenas _escape_stringcomo sua aplicação deve preceder diretamente a interseção SQL.

  • Mas como primeiro passo, basta se livrar da_real_escape_string chamada.

  • Pode ser necessário manter o restante de sua sanitize()função por enquanto, se o fluxo de banco de dados e aplicativo esperar seqüências de caracteres seguras ao contexto HTML. Adicione um comentário que aplique apenas o escape HTML a partir de agora.

  • A manipulação de string / valor é delegada ao PDO e suas instruções parametrizadas.

  • Se houver alguma menção stripslashes()em sua função de higienização, isso pode indicar uma supervisão de nível superior.

    Nota histórica em magic_quotes. Esse recurso está corretamente obsoleto. No entanto , muitas vezes é retratado incorretamente como um recurso de segurança com falha . Porém, as magic_quotes são um recurso de segurança com falha, assim como as bolas de tênis como fonte de nutrição. Simplesmente não era o propósito deles.

    A implementação original no PHP2 / FI a introduziu explicitamente com apenas " aspas serão automaticamente escapadas, facilitando a transmissão de dados do formulário diretamente para consultas msql ". Notavelmente, era acidentalmente seguro usar com mSQL , pois isso suportava apenas ASCII.
    Então o PHP3 / Zend reintroduziu magic_quotes para MySQL e o documentou mal. Mas, originalmente, era apenas um recurso de conveniência , não pretendendo segurança.

Como as declarações preparadas diferem

Quando você embaralha variáveis ​​de seqüência de caracteres nas consultas SQL, isso não fica mais complicado para você seguir. Também é um esforço estranho para o MySQL segregar código e dados novamente.

As injeções de SQL simplesmente são quando os dados sangram no contexto do código . Um servidor de banco de dados não pode mais tarde identificar onde o PHP originalmente colou variáveis ​​entre as cláusulas de consulta.

Com parâmetros vinculados, você separa o código SQL e os valores de contexto SQL no seu código PHP. Mas ele não é embaralhado novamente nos bastidores (exceto com DOP :: EMULATE_PREPARES). Seu banco de dados recebe os comandos SQL não variados e os valores das variáveis ​​1: 1.

Embora essa resposta enfatize que você deve se preocupar com as vantagens de legibilidade da queda mysql_. Ocasionalmente, há também uma vantagem de desempenho (INSERTs repetidos com apenas valores diferentes) devido a essa separação visível e técnica de dados / códigos.

Cuidado que a ligação de parâmetro ainda não é uma solução completa e mágica contra todas as injeções de SQL. Ele lida com o uso mais comum de dados / valores. Mas não é possível colocar na lista de permissões os identificadores de nome / tabela da coluna, ajudar na construção de cláusulas dinâmicas ou apenas listas de valores de matriz simples.

Uso de DOP híbrido

Essas pdo_*funções de invólucro fazem uma API stop-gap amigável para codificação. (É praticamente o que MYSQLIpoderia ter sido se não fosse a mudança de assinatura da função idiossincrática). Eles também expõem o DOP real na maioria das vezes.
A reescrita não precisa parar de usar os novos nomes de função pdo_. Você pode fazer uma transição de cada pdo_query () para uma chamada $ pdo-> prepare () -> execute () simples.

É melhor começar a simplificar novamente. Por exemplo, a busca comum de resultados:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Pode ser substituído por apenas uma iteração foreach:

foreach ($result as $row) {

Ou melhor ainda, uma recuperação direta e completa da matriz:

$result->fetchAll();

Na maioria dos casos, você receberá avisos mais úteis do que o PDO ou o mysql_ geralmente fornecem após consultas com falha.

Outras opções

Esperamos que isso tenha visualizado algumas razões práticas e um caminho que vale a pena abandonar mysql_.

Apenas mudando para não é suficiente. pdo_query()também é apenas uma interface para isso.

A menos que você também introduza a ligação de parâmetro ou possa utilizar outra coisa da API mais agradável, é uma opção inútil. Espero que seja retratado suficientemente simples para não promover o desânimo para os recém-chegados. (A educação geralmente funciona melhor que a proibição.)

Embora se qualifique para a categoria de coisa mais simples que poderia funcionar, também é um código muito experimental. Acabei de escrever no fim de semana. Há uma infinidade de alternativas no entanto. Basta pesquisar no Google para abstração de banco de dados PHP e navegar um pouco. Sempre houve e haverá muitas bibliotecas excelentes para essas tarefas.

Se você deseja simplificar ainda mais sua interação com o banco de dados, vale a pena tentar mapeadores como Paris / Idiorm . Assim como ninguém mais usa o DOM agradável em JavaScript, você não precisa cuidar de uma interface de banco de dados bruta hoje em dia.

mario
fonte
8
Tenha cuidado com a pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);função - ie:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
rickyduck 22/01
@ Tom Claro, embora não seja mantido muito (0.9.2 foi o último), você pode criar uma conta fóssil , adicionar ao wiki ou registrar um relatório de erro (sem registro IIRC).
Mario
pdo_real_escape_string() <- Isso é mesmo uma função real, não consigo encontrar nenhuma documentação para isso? Por favor, poste uma fonte para isso.
Ryan Stone
144

As mysql_funções:

  1. estão desatualizados - não são mais mantidos
  2. não permita que você se mova facilmente para outro back-end do banco de dados
  3. não suporta declarações preparadas, portanto
  4. incentivar os programadores a usar concatenação para criar consultas, levando a vulnerabilidades de injeção de SQL
Alnitak
fonte
18
# 2 é igualmente verdade sobremysqli_
eggyal 12/12
16
para ser justo, dadas as variações no dialeto SQL, mesmo o DOP não fornece o número 2 com nenhum grau de certeza. Você precisaria de um wrapper ORM adequado para isso.
SDC
a mysql_*função é um shell nas funções mysqlnd para versões mais recentes do PHP. Assim, mesmo que a biblioteca cliente antigo não é mantida mais, mysqlnd é mantida :)
hakre
O problema é não muitos web-provedores de hospedagem pode suportar tal estilo orientada a objeto do projeto devido a versão php ultrapassada
Raju yourPepe
@RajuGujarati, encontre um host que possa. Se o seu host não, as chances de ele estar vulnerável a ataques em seus servidores são muito altas.
Alnitak
106

Falando de razões técnicas , existem apenas algumas, extremamente específicas e raramente usadas. Muito provavelmente você nunca os usará em sua vida.
Talvez eu seja muito ignorante, mas nunca tive a oportunidade de usá-las como

  • consultas assíncronas sem bloqueio
  • procedimentos armazenados retornando vários conjuntos de resultados
  • Criptografia (SSL)
  • Compressão

Se você precisar deles - essas são, sem dúvida, razões técnicas para deixar a extensão mysql em direção a algo mais elegante e com aparência moderna.

No entanto, existem também alguns problemas não técnicos, que podem tornar sua experiência um pouco mais difícil

  • o uso adicional dessas funções nas versões modernas do PHP gerará avisos de nível obsoleto. Eles simplesmente podem ser desligados.
  • em um futuro distante, eles poderão ser removidos da compilação PHP padrão. Também não é grande coisa, já que o mydsql ext será transferido para o PECL e todos os hosts terão prazer em compilar o PHP com ele, pois não querem perder clientes cujos sites estejam funcionando há décadas.
  • forte resistência da comunidade Stackoverflow. Sempre que você menciona essas funções honestas, é-lhe dito que elas estão sob rigoroso tabu.
  • sendo um usuário PHP comum, provavelmente sua idéia de usar essas funções é suscetível a erros e incorreta. Só por causa de todos esses inúmeros tutoriais e manuais que ensinam o caminho errado. Não as funções em si - preciso enfatizar -, mas a maneira como são usadas.

Esta última questão é um problema.
Mas, na minha opinião, a solução proposta também não é melhor.
Parece-me um sonho idealista demais que todos esses usuários de PHP aprendam a lidar com consultas SQL corretamente de uma só vez. Muito provavelmente eles mudariam o mysql_ * para mysqli_ * mecanicamente, deixando a abordagem da mesma forma . Especialmente porque o mysqli faz uso de declarações preparadas inacreditavelmente doloroso e problemático.
Sem mencionar que instruções nativas preparadas não são suficientes para proteger contra injeções de SQL e nem o mysqli nem o PDO oferecem uma solução.

Portanto, em vez de combater essa extensão honesta, prefiro combater práticas erradas e educar as pessoas da maneira certa.

Além disso, existem alguns motivos falsos ou não significativos, como

  • Não suporta procedimentos armazenados (estávamos usando mysql_query("CALL my_proc");por idades)
  • Não suporta transações (o mesmo que acima)
  • Não suporta declarações múltiplas (quem precisa delas?)
  • Não está em desenvolvimento ativo (e daí? Isso afeta você de alguma maneira prática?)
  • Falta uma interface OO (para criar uma é uma questão de várias horas)
  • Não suporta instruções preparadas ou consultas parametrizadas

O último é um ponto interessante. Embora o mysql ext não suporte instruções preparadas nativamente , elas não são necessárias para a segurança. Podemos facilmente falsificar declarações preparadas usando espaços reservados manipulados manualmente (assim como a DOP):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

voila , tudo é parametrizado e seguro.

Mas tudo bem, se você não gosta da caixa vermelha no manual, surge um problema de escolha: mysqli ou DOP?

Bem, a resposta seria a seguinte:

  • Se você entende a necessidade de usar uma camada de abstração de banco de dados e procurar por uma API para criar uma, o mysqli é uma escolha muito boa, pois realmente suporta muitos recursos específicos do mysql.
  • Se, como a grande maioria das pessoas em PHP, você estiver usando chamadas brutas de API diretamente no código do aplicativo (que é uma prática essencialmente errada) - a DOP é a única opção , pois essa extensão finge não ser apenas API, mas um semi-DAL, ainda incompleto, mas oferece muitos recursos importantes, com dois deles distingue criticamente o DOP do mysqli:

    • ao contrário do mysqli, o PDO pode vincular espaços reservados por valor , o que viabiliza consultas construídas dinamicamente sem várias telas de código bastante confuso.
    • Ao contrário do mysqli, o PDO sempre pode retornar o resultado da consulta em uma matriz usual simples, enquanto o mysqli pode fazê-lo apenas nas instalações do mysqlnd.

Portanto, se você é um usuário PHP médio e deseja poupar muitas dores de cabeça ao usar instruções nativas, o DOP - novamente - é a única opção.
No entanto, a DOP também não é uma bala de prata e tem suas dificuldades.
Então, eu escrevi soluções para todas as armadilhas comuns e casos complexos no wiki da tag DOP

No entanto, todo mundo falando sobre extensões sempre perde os 2 fatos importantes sobre Mysqli e DOP:

  1. Declaração preparada não é uma bala de prata . Existem identificadores dinâmicos que não podem ser vinculados usando instruções preparadas. Existem consultas dinâmicas com um número desconhecido de parâmetros, o que dificulta a criação de consultas.

  2. Nem as funções mysqli_ * nem PDO deveriam aparecer no código do aplicativo.
    Deve haver uma camada de abstração entre eles e o código do aplicativo, que fará todo o trabalho sujo de vinculação, loop, manipulação de erros etc. no interior, tornando o código do aplicativo SECO e limpo. Especialmente para casos complexos, como criação de consultas dinâmicas.

Portanto, apenas mudar para DOP ou mysqli não é suficiente. É preciso usar um ORM, um construtor de consultas ou qualquer classe de abstração de banco de dados em vez de chamar funções de API brutas em seu código.
E pelo contrário - se você tem uma camada de abstração entre o código do aplicativo e a API do mysql - na verdade, não importa qual mecanismo é usado. Você pode usar o mysql ext até que fique obsoleto e reescreva facilmente sua classe de abstração para outro mecanismo, mantendo todo o código do aplicativo intacto.

Aqui estão alguns exemplos baseados na minha classe safemysql para mostrar como essa classe de abstração deve ser:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

Compare esta única linha com a quantidade de código necessária com o DOP .
Em seguida, compare com a quantidade louca de código que você precisará com instruções preparadas cruas do Mysqli. Observe que o tratamento de erros, a criação de perfil e o log de consultas já estão integrados e em execução.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

Compare-o com as inserções usuais do PDO, quando cada nome de campo é repetido seis a dez vezes - em todos esses numerosos espaços reservados, ligações e definições de consulta.

Outro exemplo:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Você dificilmente encontra um exemplo para a DOP lidar com esse caso prático.
E será muito prolixo e provavelmente inseguro.

Portanto, mais uma vez - não é apenas um driver bruto que deve ser sua preocupação, mas também uma classe de abstração, útil não apenas para exemplos tolos do manual do iniciante, mas para resolver quaisquer problemas da vida real.

Seu senso comum
fonte
20
mysql_*torna as vulnerabilidades muito fáceis de encontrar. Como o PHP é usado por muitos usuários iniciantes, mysql_*é ativamente prejudicial na prática, mesmo que em teoria possa ser usado sem problemas.
Ghost de Madara
4
everything is parameterized and safe- pode ser parametrizado, mas sua função não usa instruções reais preparadas.
uınbɐɥs
6
Como é Not under active developmentapenas para esse '0,01%' inventado? Se você criar algo com essa função parada, atualizar sua versão do mysql em um ano e acabar com um sistema não operacional, tenho certeza de que muitas pessoas repentinamente estão '0,01%'. Eu diria isso deprecatede not under active developmentestão intimamente relacionados. Você pode dizer que "não há uma razão [digna]]", mas o fato é que, quando é oferecida uma escolha entre as opções, no active developmenté quase tão ruim quanto deprecatedeu diria?
Nanne
1
@MadaraUchiha: Você pode explicar como as vulnerabilidades são muito fáceis de encontrar? Especialmente nos casos em que essas mesmas vulnerabilidades não afetam o DOP ou o MySQLi ... Porque não conheço nenhuma que você fale.
precisa saber é o seguinte
4
@ ShaquinTrifonoff: claro, ele não usa instruções preparadas. Mas o PDO também não , recomendado pela maioria das pessoas pelo MySQLi. Portanto, não tenho certeza de que tenha um impacto significativo aqui. O código acima (com um pouco mais de análise) é o que DOP faz quando você preparar uma declaração por padrão ...
ircmaxell
97

Há muitas razões, mas talvez a mais importante seja que essas funções incentivem práticas de programação inseguras porque não suportam declarações preparadas. Instruções preparadas ajudam a impedir ataques de injeção SQL.

Ao usar mysql_*funções, lembre-se de executar os parâmetros fornecidos pelo usuário mysql_real_escape_string(). Se você esquecer apenas um local ou se escapar de apenas parte da entrada, seu banco de dados poderá estar sujeito a ataque.

O uso de instruções preparadas PDOou mysqlifará com que seja mais difícil cometer esses tipos de erros de programação.

Trott
fonte
3
Infelizmente, o suporte deficiente no MySQLi_ * para passar um número variável de parâmetros (como quando você deseja passar uma lista de valores a serem verificados em uma cláusula IN) incentiva o não uso de parâmetros, incentivando o uso exatamente das mesmas consultas concatenadas que deixe o MySQL_ * chama vulnerável.
Kickstart
5
Mas, mais uma vez, a insegurança não é um problema inerente às funções mysql_ *, mas um problema de uso incorreto.
Agamemnus 02/02
2
@ Agamemnus O problema é que o mysql_ * facilita a implementação desse "uso incorreto", especialmente para programadores inexperientes. As bibliotecas que implementam instruções preparadas tornam mais difícil cometer esse tipo de erro.
Trott 02/02
75

Porque (entre outras razões) é muito mais difícil garantir a limpeza dos dados de entrada. Se você usa consultas parametrizadas, como se faz com o DOP ou o mysqli, você pode evitar completamente o risco.

Como exemplo, alguém poderia usar "enhzflep); drop table users"como um nome de usuário. As funções antigas permitirão executar várias instruções por consulta, para que algo assim desagradável possa excluir uma tabela inteira.

Se alguém usasse o PDO do mysqli, o nome do usuário acabaria sendo "enhzflep); drop table users".

Veja bobby-tables.com .

enhzflep
fonte
10
The old functions will allow executing of multiple statements per query- não, eles não vão. Esse tipo de injeção não é possível com o ext / mysql - a única maneira que esse tipo de injeção é possível com PHP e MySQL é ao usar o MySQLi e a mysqli_multi_query()função. A injeção de tipo possível com ext / mysql e strings sem escape é como ' OR '1' = '1extrair dados do banco de dados que não deveriam estar acessíveis. Em certas situações, é possível injetar subconsultas, no entanto, ainda não é possível modificar o banco de dados dessa maneira.
DaveRandom
64

Esta resposta foi escrita para mostrar o quão trivial é ignorar o código de validação do usuário PHP mal escrito, como (e usando o que) esses ataques funcionam e como substituir as funções antigas do MySQL por uma declaração segura preparada - e, basicamente, por que os usuários do StackOverflow (provavelmente com muitos representantes) estão latindo para novos usuários fazendo perguntas para melhorar seu código.

Primeiramente, sinta-se à vontade para criar este banco de dados mysql de teste (chamei minha preparação):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

Com isso feito, podemos mudar para o nosso código PHP.

Vamos supor que o script a seguir seja o processo de verificação para um administrador em um site (simplificado, mas funcionando se você o copiar e usar para teste):

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Parece bastante legítimo à primeira vista.

O usuário precisa digitar um login e senha, certo?

Brilhante, não entre no seguinte:

user: bob
pass: somePass

e envie.

A saída é a seguinte:

You could not be verified. Please try again...

Super! Trabalhando como esperado, agora vamos tentar o nome de usuário e a senha reais:

user: Fluffeh
pass: mypass

Surpreendente! Olá, cinco anos, o código verificou corretamente um administrador. É perfeito!

Bem, na verdade não. Vamos dizer que o usuário é uma pessoa pequena e inteligente. Vamos dizer que a pessoa sou eu.

Digite o seguinte:

user: bob
pass: n' or 1=1 or 'm=m

E a saída é:

The check passed. We have a verified admin!

Parabéns, você acabou de me permitir inserir sua seção apenas de administradores superprotegidos, digitando um nome de usuário falso e uma senha falsa. Sério, se você não acredita em mim, crie o banco de dados com o código que forneci e execute esse código PHP - que, à primeira vista, REALMENTE parece verificar o nome de usuário e a senha de maneira bastante agradável.

Então, em resposta, é por isso que você está sendo gritado.

Então, vamos dar uma olhada no que deu errado e por que eu acabei de entrar na sua caverna de morcego super-administrador. Peguei um palpite e presumi que você não estava sendo cuidadoso com suas entradas e simplesmente as transmitiu diretamente ao banco de dados. Eu construí a entrada de uma maneira que alteraria a consulta que você estava realmente executando. Então, o que deveria ser e o que acabou sendo?

select id, userid, pass from users where userid='$user' and pass='$pass'

Essa é a consulta, mas quando substituímos as variáveis ​​pelas entradas reais que usamos, obtemos o seguinte:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Veja como eu construí minha "senha" para fechar primeiro as aspas simples em torno da senha e introduzir uma comparação completamente nova? Então, por questão de segurança, adicionei outra "string" para que a citação única ficasse fechada conforme o esperado no código que tínhamos originalmente.

No entanto, não se trata de pessoas gritando com você agora, mas sim de mostrar como tornar seu código mais seguro.

Ok, então o que deu errado e como podemos corrigi-lo?

Este é um ataque clássico de injeção de SQL. Um dos mais simples para esse assunto. Na escala dos vetores de ataque, esta é uma criança atacando um tanque - e vencendo.

Então, como protegemos sua seção de administrador sagrado e a tornamos agradável e segura? A primeira coisa a fazer será parar de usar essas mysql_*funções realmente antigas e obsoletas . Eu sei, você seguiu um tutorial que encontrou on-line e funciona, mas é antigo, está desatualizado e, no espaço de alguns minutos, acabei de ultrapassá-lo sem nem mesmo suar a camisa.

Agora, você tem as melhores opções para usar mysqli_ ou PDO . Pessoalmente, sou um grande fã da DOP, portanto usarei a DOP no restante desta resposta. Existem prós e contras, mas pessoalmente acho que os profissionais superam os contras. É portátil em vários mecanismos de banco de dados - esteja você usando o MySQL ou Oracle ou qualquer coisa sangrenta - apenas alterando a cadeia de conexão, possui todos os recursos sofisticados que queremos usar e é agradável e limpo. Eu gosto de limpar.

Agora, vamos dar uma olhada nesse código novamente, desta vez escrito usando um objeto PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

As principais diferenças são que não há mais mysql_*funções. Tudo é feito por meio de um objeto DOP; em segundo lugar, ele está usando uma instrução preparada. Agora, o que é uma declaração pré-preparada que você pergunta? É uma maneira de informar ao banco de dados antes da execução de uma consulta qual é a consulta que vamos executar. Nesse caso, dizemos ao banco de dados: "Oi, vou executar uma instrução select que deseja id, userid e pass dos usuários da tabela em que o userid é uma variável e o pass também é uma variável".

Em seguida, na instrução execute, passamos ao banco de dados uma matriz com todas as variáveis ​​que ele espera agora.

Os resultados são fantásticos. Vamos tentar as combinações de nome de usuário e senha anteriores:

user: bob
pass: somePass

O usuário não foi verificado. Impressionante.

E se:

user: Fluffeh
pass: mypass

Ah, fiquei um pouco empolgado, funcionou: o cheque passou. Temos um administrador verificado!

Agora, vamos tentar os dados que um sujeito inteligente digitaria para tentar superar nosso pequeno sistema de verificação:

user: bob
pass: n' or 1=1 or 'm=m

Desta vez, temos o seguinte:

You could not be verified. Please try again...

É por isso que você está sendo criticado ao postar perguntas - é porque as pessoas podem ver que seu código pode ser ignorado sem sequer tentar. Por favor, use esta pergunta e resposta para melhorar seu código, torná-lo mais seguro e usar as funções atuais.

Por fim, isso não quer dizer que esse seja um código PERFEITO. Você pode fazer muito mais para aprimorá-lo, usar senhas com hash, por exemplo, garantir que, ao armazenar informações sensíveis no banco de dados, não as armazene em texto simples, tenha vários níveis de verificação - mas, na verdade, se você acabou de alterar seu código antigo propenso a injeção para isso, estará bem no caminho de escrever um bom código - e o fato de ter chegado tão longe e ainda estar lendo me dá uma sensação de esperança de que você não apenas implementará esse tipo de código ao escrever seus sites e aplicativos, mas você pode pesquisar e pesquisar outras coisas que acabei de mencionar - e mais. Escreva o melhor código possível, não o mais básico que mal funciona.

Fluffeh
fonte
2
Obrigado pela sua resposta! Tenha o meu +1! Vale a pena notar que mysql_*por si só não é inseguro, mas promove código inseguro por meio de tutoriais ruins e a falta de uma API adequada prepara a API.
Madara's Ghost
2
senhas cortadas, oh, que horror! Caso contrário, +1 para obter uma explicação detalhada.
Cryptic #
33

A extensão MySQL é a mais antiga das três e era a maneira original que os desenvolvedores costumavam se comunicar com o MySQL. Essa extensão agora está sendo preterida em favor das outras duas alternativas devido a melhorias feitas nas versões mais recentes do PHP e do MySQL.

  • O MySQLi é a extensão 'aprimorada' para trabalhar com bancos de dados MySQL. Ele tira proveito dos recursos que estão disponíveis nas versões mais recentes do servidor MySQL, expõe ao desenvolvedor uma interface orientada a funções e uma interface orientada a objetos e faz algumas outras coisas bacanas.

  • O PDO oferece uma API que consolida a maior parte da funcionalidade anteriormente espalhada pelas principais extensões de acesso ao banco de dados, como MySQL, PostgreSQL, SQLite, MSSQL, etc. A interface expõe objetos de alto nível para o programador trabalhar com conexões, consultas e bancos de dados. conjuntos de resultados e drivers de baixo nível executam comunicação e manipulação de recursos com o servidor de banco de dados. Muita discussão e trabalho estão entrando no DOP e é considerado o método apropriado de trabalhar com bancos de dados em código profissional moderno.

Alexander
fonte
21

Acho as respostas acima muito longas, para resumir:

A extensão mysqli tem vários benefícios, os principais aprimoramentos sobre a extensão mysql sendo:

  • Interface orientada a objetos
  • Suporte para declarações preparadas
  • Suporte para várias declarações
  • Suporte para transações
  • Recursos aprimorados de depuração
  • Suporte ao servidor incorporado

Fonte: Visão geral do MySQLi


Como explicado nas respostas acima, as alternativas ao mysql são mysqli e PDO (PHP Data Objects).

  • A API suporta instruções preparadas do lado do servidor: suportadas pelo MYSQLi e PDO
  • A API suporta declarações preparadas do lado do cliente: suportadas apenas pelo PDO
  • API suporta procedimentos armazenados: MySQLi e PDO
  • A API suporta declarações múltiplas e todas as funcionalidades do MySQL 4.1+ - suportado pelo MySQLi e principalmente também pelo DOP

O MySQLi e o PDO foram introduzidos no PHP 5.0, enquanto o MySQL foi introduzido antes do PHP 3.0. Um ponto a ser observado é que o MySQL está incluído no PHP5.x, mas foi descontinuado em versões posteriores.

Ani Menon
fonte
2
Sua resposta é muito longa, enquanto o resumo real é "mysql ext is no more". Isso é tudo
Seu senso comum
1
@YourCommonSense Minha resposta é por que o mysqli substituiu o mysql. O ponto não é dizer que o Mysqli existe hoje, então use-o .. Todo mundo sabe disso!
Ani Menon
1
Bem, além do fato de ninguém perguntar por que o mysqli substituiu o mysql, ele também não responde a essa pergunta. Ele responde por que o mysqli foi introduzido. Mas não explica porque mysql e mysqli não foram autorizados a viver em paralelo
Seu senso comum
@YourCommonSense Também a pergunta do OP é "Por que devo usar outra coisa, mesmo que ela funcione no meu site?" e foi por essa razão que apontei as mudanças e melhorias. Você pode olhar para todas as outras respostas, pois são longas, então pensei em resumir.
Ani Menon
6

É possível definir quase todas as mysql_*funções usando mysqli ou PDO. Apenas inclua-os no topo do seu aplicativo PHP antigo, e ele funcionará no PHP7. Minha solução aqui .

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}
Pavel Tzonkov
fonte
Em vez de mostrar o link da sua solução, adicione-os aqui como resposta.
Amarnath
1

As funções que são tão semelhantes a este mysql_connect(), mysql_query()tipo são a versão anterior PHP ou seja (PHP 4) funções e agora não em utilização.

Eles são substituídos por mysqli_connect(), da mysqli_query()mesma forma no PHP5 mais recente.

Esta é a razão por trás do erro.

Assassino
fonte
2
O PHP 5 não é mais recente há mais de 2 anos.
Madara's Ghost
1

O MySQL foi descontinuado no PHP 5.5.0 e removido no PHP 7.0.0. Para um aplicativo grande e antigo, é difícil pesquisar e substituir cada função.

Podemos usar as funções do MySQL criando uma função de wrapper para cada código abaixo em execução. Clique aqui

Vin
fonte
-9

As funções mysql_ * foram descontinuadas (a partir do PHP 5.5 ), devido ao fato de que melhores funções e estruturas de código foram desenvolvidas. O fato de a função ter sido descontinuada significa que não será necessário mais esforço para aprimorá-la em termos de desempenho e segurança, o que significa que é menos prova de futuro .

Se você precisar de mais motivos:

  • As funções mysql_ * não suportam instruções preparadas.
  • As funções mysql_ * não suportam a ligação de parâmetros.
  • As funções mysql_ * não possuem funcionalidade para Programação Orientada a Objetos.
  • A lista continua ...
Webeng
fonte
18
Esta resposta está desatualizada. Além disso, não acrescenta nada útil às respostas que já existem.
Seu senso comum