Olá Habr!
Segurança é um assunto sério. E muitas vezes os problemas nessa área surgem inesperadamente e têm conseqüências extremamente desagradáveis. Portanto, o conhecimento neste tópico é extremamente importante para todo desenvolvedor web.
Farei uma reserva imediatamente - estou longe de ser profissional, mas luto por isso. Portanto, terei prazer em criticar, mas apenas objetivo. Este material é para iniciantes que desejam aumentar seu profissionalismo e valor como especialista.
E, no entanto, mostro a implementação de código mais simples possível. Conheço exceções, conheço o ORM, a proteção fornecida em estruturas. Meu objetivo é mostrar claramente para que todos entendam.
E assim, é hora de terminar a introdução e começar a praticar.
O caminho da implementação de um iniciante para qualquer resultado são
Não estou acostumado a trabalhar com teoria. Minha alma anseia por praticar. Portanto, falando sobre o tópico de segurança, consideraremos quase todos os tipos de ataques do ponto de vista prático - como implementar e como se proteger. Sim, você pode dizer que ensinar pirataria não é bom, mas, sem saber como o ataque ocorre, não podemos construir uma defesa competente.
Xss
Ok, o primeiro tipo de ataque é XSS. Sim, o bom e velho XSS que todo mundo já ouviu falar. XSS (Cross Site Scripting) é um tipo de ataque que visa os visitantes do site. Como isso acontece: através do campo de entrada, o invasor grava código malicioso que entra no banco de dados e faz seu trabalho. Geralmente, dessa forma, os cookies são roubados dos usuários, o que lhes permite fazer login em suas contas sem senha e login.
Estamos implementando um exemplo mais inofensivo.
Nosso desenvolvedor fez um formulário simples para adicionar comentários:
Arquivo Index.php<?php $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); $query = $pdo->prepare("SELECT * FROM `comments`"); $query->execute(); $comments = $query->fetchAll(); if ($_POST) { $username = trim($_POST['name']); $comment = trim($_POST['comment']); $query = $pdo->prepare("INSERT INTO `comments` (`username`,`message`) VALUES ('$username', '$comment')"); $query->execute(); if ($query) { echo ' !'; header("Location: index.php"); } else { echo ' !'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>XSS</title> </head> <body> <form method="POST" class="addComment"> <input type="text" name="name" placeholder="Username"> <textarea name="comment"></textarea> <input type="submit" value=" "> </form> <div class="h2"></div> <div class="comments"> <?php if ($comments): foreach ($comments as $comment):?> <div class="comment"> <div class="comment_username"><?php echo $comment['username'];?></div> <div lass="comment_comment"><?php echo $comment['message'];?></div> </div> <?php endforeach;?> <?php else:?> <div class="no_comments"> </div> <?php endif;?> </div> </body> </html>
O código é muito simples e não precisa de explicação.
Há um intruso - John. John ficou entediado e tropeçou no site do nosso desenvolvedor.
John escreve a seguinte mensagem no formulário:
<script>document.body.style.backgroundColor = "#000";</script>
E agora todos os usuários do site têm um fundo preto. John está satisfeito e o desenvolvedor ganhou experiência e repreensão.
O que aconteceu?
John adicionou um comentário com código JavaScript. Ao enviar dados para uma página, um comentário de texto é convertido em código html. O código html, vendo o uso da tag de script, adicionou-o à marcação e o intérprete já executou o código JavaScript. Ou seja, John simplesmente adicionou seu trecho de código js ao código do site existente.
Como vamos consertar isso?
Para corrigir esse mal-entendido, a função htmlspecialcars foi criada. A essência de seu trabalho é que ela substitui caracteres como aspas e colchetes por caracteres especiais. Por exemplo, o caractere "<" será substituído pelo seu código de caractere correspondente. Com essa função, processamos os dados do formulário e agora o código js de John não pode mais prejudicar nosso site. Obviamente, se este for o único formulário no site.
Alterações no código terão a seguinte aparência:
Arquivo Index.php <?php if ($_POST) { $username = htmlspecialchars(trim($_POST['name'])); $comment = htmlspecialchars(trim($_POST['comment']));
Injeção de SQL
Outro dos tipos mais comuns de ataques, que já começaram a ser esquecidos. Eles esquecem porque existem consultas e estruturas preparadas.
Falaremos sobre os pedidos preparados.
Qual é a essência do ataque: o invasor insere parte da consulta SQL no campo de entrada e envia o formulário. Durante a execução da consulta, os dados recebidos são adicionados ao banco de dados. Mas como o código contém o código, ao adicionar registros, ele modifica a lógica do nosso script.
Ok, simule uma situação. Nosso desenvolvedor protegeu o formulário contra ataques XSS. E John continua a usar seu conhecimento, apontando as falhas do infeliz desenvolvedor.
Ou seja, continuaremos trabalhando com o mesmo formulário para adicionar comentários.
Vamos fazer algumas mudanças:
1) Pré-moderação dos comentários.
A linha inferior é que apenas os comentários que o moderador aprovou serão exibidos na página. Implementamos a pré-moderação em sua forma mais simples, para não nos distrair da parte principal do artigo.
Para implementar a ideia, adicione o campo "is_moderate" à tabela com comentários, que terá dois valores - "1" (exibir o comentário) ou "0" (não exibir). Por padrão, é claro, "0".
2) Mude a solicitação.
Isto é para maior clareza. Deixe a solicitação para adicionar comentários ter esta aparência:
"INSERT INTO `comments` SET `username`='$username', `message`='$comment'"
Agora o código do formulário é o seguinte:
Arquivo Index.php <?php $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); $query = $pdo->prepare("SELECT * FROM `comments` WHERE `is_moderate`='1'"); $query->execute(); $comments = $query->fetchAll(); if ($_POST) { $username = htmlspecialchars(trim($_POST['name'])); $comment = htmlspecialchars(trim($_POST['comment'])); $query = $pdo->prepare("INSERT INTO `comments` SET `username`='$username', `message`='$comment'"); $query->execute(); if ($query) { echo ' !'; } else { echo ' !'; } } ?>
Ok, John faz logon no site e, vendo que os comentários começaram a ser moderados, ele decidiu zombar do desenvolvedor. Além disso, a forma de ataques XSS agora reflete com sucesso e John já foi privado da oportunidade de se divertir. Ele deixa um comentário desse tipo: "LOL ', is_moderate =' 1" e ignora a moderação.
Porque
Quando você substitui o comentário de John em nossa consulta, as aspas são quebradas. Ou seja, como mencionado acima, John conseguiu executar código SQL arbitrário.
Ao executar uma solicitação com o comentário de John, a solicitação é a seguinte:
"INSERT INTO `comments` SET `username`='John', `message`='LOL', `is_moderate`='1'"
Além disso, a injeção de SQL pode ser implementada não apenas ao enviar um formulário. Isso também pode acontecer ao receber registros pelo identificador, processar o formulário de pesquisa e outras situações não tão óbvias.
Como consertar isso?
O método para resolver o problema é conhecido há muito tempo, por consultas preparadas. Solicitações preparadas são solicitações que passam por um processamento especial antes de serem executadas. O processamento consiste em escapar aspas adicionais. Você deve ter ouvido falar desse recurso. No PHP, é implementado assim: "\ '".
A solução mais popular é a DOP. O DOP é uma interface para trabalhar com um banco de dados. O que tem uma interface bastante conveniente. Use-o apenas com sabedoria.
O PDO fornece a capacidade de usar máscaras e espaços reservados para implementar consultas preparadas.
Em seguida, nossa solicitação ao usar máscaras ficará assim:
Arquivo Index.php <?php $query = $pdo->prepare("INSERT INTO `comments` SET `username`=:username, `message`=:comment"); $params = ['username' => $username,'comment' => $comment]; $query->execute($params);
E ao usar espaços reservados como este:
Arquivo Index.php <?php $query = $pdo->prepare("INSERT INTO `comments` SET `username`=?, `message`=?"); $params = [$username,$comment]; $query->execute($params);
Agora, o ataque de John não é mais relevante. Pelo menos para este formulário.
A propósito, nossa moderação, mesmo desta forma, já protege contra outro tipo de ataque - SPAM. Todos nós ouvimos falar dele. SPAM é o envio de qualquer mensagem em que, com a ajuda do conhecimento em engenharia social, os atacantes realizem seus ataques. Agora, o único que será atacado é o moderador. E então, se ele não for tão estúpido, ele excluirá o lixo do banco de dados ou, se for preguiçoso, rejeitará a publicação e é isso.
Ataque CSRF
CSRF - falsificação de solicitação entre sites. É perigoso porque poucas pessoas sabem disso. Embora fazer isso seja bastante simples.
Como isso acontece: um invasor de outro site cria um formulário e força a vítima a segui-lo. Ou seja, uma solicitação POST é enviada. Assim, uma solicitação HTTP é falsificada e uma ação maliciosa é executada no site da vítima.
Por exemplo, um invasor pode enviar uma carta ao seu amigo em VK em seu nome, mas você não saberá.
Parece um pouco confuso. Proponho considerar na prática.
Nosso desenvolvedor fez a forma, ele é bem feito. Ela já sabe como se defender contra a injeção XSS, SQL e mantém a pressão do Spam. É assim:
O arquivo index.php no site do desenvolvedor <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CSRF</title> </head> <body> <form action="action.php" method="POST"> <input type="text" name="username"> <textarea name="message"></textarea> <input type="submit" value=" "> </form> </body> </html>
Mas John não é tão simples. Ele recebe o código do formulário (simplesmente do código-fonte do site no navegador) e adiciona o formulário ao site.
Arquivo index.php de John <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CSRF</title> <style> form input[type=submit]{ padding: 15px; font-size: 20px; color:
Observe que, no site de John, o local em que o formulário é processado é um arquivo do site de nosso desenvolvedor. Portanto, agora qualquer usuário que clicar no botão enviará bons comentários.
Este é um ataque CSRF. Na versão mais simples, é claro.
O desenvolvedor voltou a ter problemas ...
Como corrigir uma vulnerabilidade?
Em um momento, o desenvolvedor pesquisará o que é csrf e a lógica de proteção será a seguinte: para proteção, você precisa criar um token csrf (um conjunto de letras e números) e pendurá-lo no formulário. Também é necessário corrigir o mesmo token para o usuário (por exemplo, através de uma sessão). E então, ao processar o formulário, compare esses tokens. Se eles corresponderem, podemos adicionar um comentário.
Nós implementamos isso:
O arquivo index.php no site do desenvolvedor <?php session_start(); $token = ''; if (function_exists('mcrypt_create_iv')) { $token = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); } else { $token = bin2hex(openssl_random_pseudo_bytes(32)); } $_SESSION['token'] = $token; ?> ... <form action="action.php" method="POST"> <input type="text" name="username"> <textarea name="message"></textarea> <input type="hidden" name="csrf_token" value="<?php echo $token;?>"> <input type="submit" value=" "> </form>
Arquivo action.php <?php session_start(); if ($_POST) { if ($_SESSION['token'] == $_POST['csrf_token']) { echo ' !'; } else { echo '!'; } }
Força Bruta e Senhas Publick
Talvez o tipo mais famoso de ataque. Ele ouviu quase todos os primeiros filmes sobre hackers e coisas do gênero.
Qual é o objetivo: existe um formulário para autorização no painel de administração do site. Precisamos de um nome de usuário e senha que John não saiba. Mas ele tem um arquivo com nomes de usuário e senhas populares. E ele corre alegremente para experimentá-los em nosso site.
O que um desenvolvedor pode se opor? Por exemplo, uma restrição no número de tentativas de autorização em um determinado período de tempo.
Deixe a versão inicial do formulário de autorização ter esta aparência:
Arquivo Index.php <?php $opt = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); if (isset($_POST['autoriz'])) { $username = htmlspecialchars(trim($_POST['login'])); $password = htmlspecialchars(trim($_POST['password'])); $query = $pdo->prepare("SELECT * FROM `users` WHERE `username`=:username AND `password`=:password"); $query->execute(['username' => $username,'password' => $password]); $find_user = $query->fetchAll(); if ($find_user) { echo ' !'; } else { echo ' !'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Brute Force Public Passwords</title> </head> <body> <form method="POST"> <input type="text" name="login" placeholder="Login"> <input type="password" name="password" placeholder="Password"> <input type="submit" value="" name="autorize"> </form> </body> </html>
Como consertar: a opção mais simples, como já mencionada, é limitar o número de tentativas de autorização por período de tempo.
Para fazer isso, quando tentarmos fazer login, adicionaremos o valor atual do tempo ao usuário em cookies. E agora, ao tentar fazer login, veremos que o usuário não pode fazer login mais de uma vez em 5 segundos. Além disso, se a senha ou o login não for digitado corretamente, aumentaremos o tempo limite até a próxima tentativa em 5 segundos.
Arquivo Index.php <?php $count_next_minit = $_COOKIE['count_try'] ? $_COOKIE['count_try'] : 1; $seconds_to_new_try = 5; $opt = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); if (isset($_POST['autorize'])) { if ($_COOKIE['last_try']) { if ($_COOKIE['last_try'] < time() - $seconds_to_new_try * $count_next_minit) { $username = htmlspecialchars(trim($_POST['login'])); $password = htmlspecialchars(trim($_POST['password'])); $query = $pdo->prepare("SELECT * FROM `users` WHERE `username`=:username AND `password`=:password"); $query->execute(['username'=>$username,'password'=>$password]); $find_user = $query->fetchAll(); setcookie('last_try', time(), time() + 3600); if ($_COOKIE['count_try']) { $old_value = (int)$_COOKIE['count_try']; setcookie('count_try', $old_value + 1, time() + 3600); } else { setcookie('count_try', 1, time() + 3600); } if ($find_user) { var_dump(' !'); } else { var_dump(' !'); } }else{ var_dump(' ! ' . $seconds_to_new_try * $count_next_minit . ' '); } }else{ setcookie('last_try', time(), time() + 3600); } } ?>
Backtrace
O Backtrace é uma maneira de atacar através de mensagens de erro do sistema. Isso é MySQL e PHP.
Por exemplo, John inseriu um URL incorreto e recebeu um erro dizendo que não há registro no banco de dados com esse ID (se o registro for recebido via ID da barra de endereço - site.ru/article?id=12). Existem até os chamados "idiotas" - padrões específicos de endereços de sites, ao inserir os erros do usuário. E isso abre a possibilidade de John usar o bot para acessar esta lista de endereços e tentar encontrar essa vulnerabilidade no seu site.
Como consertar isso? Nesse caso, faremos sem exemplos, porque o problema é resolvido simplesmente fechando a saída de erro. Geralmente, isso é implementado pela hospedagem, fornecendo no painel do administrador a gravação de logs em um arquivo separado, mas não será supérfluo limitar a saída dos erros.
Isso pode ser feito usando a função error_reporting ().
Entre os argumentos necessários, estão: E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_ALL. Os nomes falam por si.
Por exemplo, se você usar error_reporting (E_NOTICE), todos os erros serão ocultados, exceto os erros do tipo Aviso (avisos, por exemplo, que não há dados na matriz $ _POST).
Para desativar a saída de todos os erros (dos quais realmente precisamos), você precisa usar esta função da seguinte maneira: error_reporting (0)
Erros lógicos
Erros lógicos são alguns dos piores. Porque estes são erros por descuido. Eles aparecem inesperadamente e, às vezes, nem sabemos onde está a raiz do problema. Esses são erros na lógica do site.
Bem, por exemplo, você pode esquecer de verificar a presença de dados de autorização na sessão e os cookies de uma das páginas do painel de administração. Então eles abriram o acesso a esta página para qualquer usuário.
Nesse caso, apenas uma coisa irá salvá-lo - ao escrever o código do programa, pense em como você pode cortá-lo.
DDOS
O DOS é um tipo de ataque a uma técnica, em particular um computador. O ataque visa desativar a máquina devido a sobrecarga. O DDOS difere apenas no fato de um número maior de computadores estar envolvido no ataque.
Ou seja, John liga para seus amigos e eles juntos começam a enviar solicitações para o site. O que a botnet tem a ver com isso? Uma botnet é um monte de computadores infectados, a peculiaridade é que um invasor pode controlar seu trabalho até certo ponto (iniciar processos etc.). Eles enviam tantas solicitações que o servidor não consegue suportar a carga e, na melhor das hipóteses, começa a funcionar muito lentamente ou se recusa por um período não especificado.
A proteção contra esses ataques é fornecida pelos próprios hosts ou por serviços especiais como o Cloudflare.
Como a proteção funciona: o serviço Cloudflare fornece seus próprios servidores DNS pelos quais o tráfego passará. Lá é filtrado, passando por algoritmos conhecidos apenas pelos proprietários e desenvolvedores do serviço. E depois disso, o usuário acessa o seu site. Sim, é claro, há operação do servidor, geração de páginas e tudo mais, mas não estamos falando sobre isso agora.
Além disso, falando em DDOS, não se pode deixar de mencionar o bloqueio de endereço IP que o Cloudflare também fornece.
Sim, tudo isso não dará 100% de garantia de proteção, mas às vezes aumentará as chances de seu site permanecer flutuando no momento do ataque.
MITM
Man In The Middle é um tipo de ataque quando um invasor intercepta seus pacotes e os falsifica. Todos ouvimos que os dados são transmitidos pela rede em pacotes. Portanto, ao usar o protocolo http, os dados são transmitidos da forma usual, não criptografada.
Por exemplo, você escreve “olá” a um amigo e ele recebe “me envie dinheiro, aqui está a carteira”.
Foi para resolver esse problema que o protocolo https foi criado. Ao usá-lo, os dados serão criptografados e John não poderá fazer nada com o tráfego recebido.
E para obter o protocolo https para o site, você precisa obter um certificado SSL. Bem, ou TLS. Em geral, o TLS é essencialmente um receptor SSL, porque é baseado no SSL 3.0. Não há diferenças significativas em seu trabalho.
O certificado SSL fornece o mesmo Cloudflare e de graça.
Backdoor
Um pouco mais de teoria. Para este tipo de ataque pode ser implementado de várias maneiras e você só precisa entender a essência. Backdoor é um tipo de ataque secreto no qual o próprio script faz algo em segundo plano. Na maioria das vezes, esses são plugins ou temas do WordPress baixados de um torrent. O próprio plugin / tema funcionará adequadamente, mas uma certa parte do script adicionada ao código do plugin / tema secretamente fará alguma coisa. O mesmo SPAM, por exemplo. Esse é o motivo de todos os avisos sobre a indesejabilidade de baixar arquivos de um torrent.
Em conclusão
Sim, é claro, esse não é o conjunto de ataques. Mas esse conhecimento já aumentará a segurança de seus projetos.