O maior portal de MU Online do Brasil — desde 2003
Tutorial Avançado Website

Como proteger o site do servidor de MU contra SQL injection

Entenda como o SQL injection ataca o site do seu servidor de MU Online e aplique defesas reais com prepared statements, validação, privilégios mínimos e monitoramento para blindar contas e itens.

GA Gabriel · Atualizado em 10 jul 2026 · ⏱ 16 min de leitura
Resposta rápida

O site é a porta de entrada do seu servidor de MU Online — e, por isso mesmo, o alvo preferido de quem quer atalho. Entre todas as falhas web, o SQL injection é a mais perigosa para um servidor de MU porque atinge diretamente o banco que guarda contas, senhas, saldos de créditos e itens. Uma única q

O site é a porta de entrada do seu servidor de MU Online — e, por isso mesmo, o alvo preferido de quem quer atalho. Entre todas as falhas web, o SQL injection é a mais perigosa para um servidor de MU porque atinge diretamente o banco que guarda contas, senhas, saldos de créditos e itens. Uma única query mal escrita num formulário de login pode permitir que um invasor entre sem senha, leia o banco inteiro, se dê itens e WCoin, promova a própria conta a GM ou simplesmente apague tudo. Não é exagero: a maioria dos "hacks" de servidores privados que viram história de fórum começou num campo de texto concatenado direto no SQL.

Este é um guia avançado, orientado à prática, para blindar o site contra esse ataque. Vamos ver como o injection realmente funciona, aplicar as defesas na ordem certa de importância — prepared statements, validação, privilégio mínimo, tratamento de erro e monitoramento — e revisar os pontos do site de MU que mais são atacados. Se você ainda está estruturando a base do projeto, vale começar por como criar um servidor de MU Online e voltar para endurecer a camada web.

Pré-requisitos

Para acompanhar e aplicar as correções você precisa de:

  • Acesso ao código-fonte do site (PHP na maioria dos casos, mas o conceito vale para qualquer linguagem).
  • Acesso administrativo ao banco (SQL Server ou MySQL/MariaDB) para ajustar usuários e privilégios.
  • Um ambiente de testes separado da produção para validar as mudanças sem risco.
  • Conhecimento básico de SQL e de como o site conecta ao banco (a connection string).
  • Backup completo e recente do banco antes de qualquer alteração.

> Aviso de segurança: nunca teste payloads de injection no banco de produção. Trabalhe sempre em uma cópia de teste. E nunca exponha, em código público ou prints, a sua connection string real.

Como o SQL injection funciona (o problema na raiz)

O ataque nasce de um erro conceitual simples: misturar comando (SQL) com dados (o que o usuário digitou). Veja um login vulnerável, do tipo que ainda circula em muitos sites de MU:

<?php
// CÓDIGO VULNERÁVEL — nunca use isto
$user = $_POST['user'];
$pass = $_POST['pass'];
$sql = "SELECT * FROM MEMB_INFO WHERE memb___id = '$user' AND memb__pwd = '$pass'";
$result = sqlsrv_query($conn, $sql);

O que o programador espera é que o usuário digite algo como guerreiro123. Mas o campo aceita qualquer texto. Se o invasor digitar no campo de usuário:

' OR '1'='1' --

A query montada vira:

SELECT * FROM MEMB_INFO WHERE memb___id = '' OR '1'='1' --' AND memb__pwd = '...'

O OR '1'='1' é sempre verdadeiro, e o -- comenta o resto da query (incluindo a checagem de senha). Resultado: o login retorna o primeiro usuário da tabela, e o invasor entra sem saber senha nenhuma. Variações mais graves usam UNION SELECT para ler outras tabelas, ou ; UPDATE / ; DROP para alterar e destruir dados.

O ponto central: o banco não tem como saber que ' OR '1'='1' era pra ser um nome de usuário e não parte do comando, porque tudo chegou misturado numa string só. A defesa raiz é separar as duas coisas.

Defesa 1 (a mais importante) — Prepared statements

Prepared statements (consultas parametrizadas) resolvem o problema na origem: você envia o comando com marcadores (? ou :nome) e, separadamente, os dados. O banco monta a query já sabendo o que é comando e o que é valor — o texto do usuário nunca é interpretado como SQL.

Veja o mesmo login, agora seguro, com PDO:

<?php
// CÓDIGO SEGURO — prepared statement com PDO
$stmt = $pdo->prepare(
    "SELECT memb_guid, memb___id FROM MEMB_INFO
     WHERE memb___id = :user AND memb__pwd = :pass"
);
$stmt->execute([
    ':user' => $_POST['user'],
    ':pass' => hash_senha($_POST['pass']), // senha nunca em texto puro
]);
$conta = $stmt->fetch(PDO::FETCH_ASSOC);

if ($conta) {
    // login válido
} else {
    // credenciais inválidas
}

Agora, se o invasor digitar ' OR '1'='1' --, esse texto vira literalmente o valor procurado no campo memb___id. Como não existe usuário com esse nome bizarro, o login falha — que é exatamente o comportamento correto. O mesmo vale para SQL Server via sqlsrv com parâmetros:

<?php
$sql = "SELECT memb_guid FROM MEMB_INFO WHERE memb___id = ?";
$params = [ $_POST['user'] ];
$stmt = sqlsrv_query($conn, $sql, $params); // parâmetros separados do comando

Regra de ouro: toda query que use qualquer valor vindo do usuário — $_POST, $_GET, $_COOKIE, cabeçalhos, dados de API — precisa ser parametrizada. Não existe exceção "esse campo é só número, tá seguro". Números também são injetáveis.

O caso especial: partes que não aceitam bind

Prepared statements parametrizam valores, mas não nomes de tabela, nomes de coluna nem a direção de um ORDER BY. Isso é comum em rankings de MU que ordenam por coluna escolhida pelo usuário:

<?php
// Perigoso: coluna e direção vindo do usuário
$col = $_GET['sort']; // ex.: "resets" — mas pode ser injection
$sql = "SELECT Name, Resets FROM Character ORDER BY $col DESC"; // VULNERÁVEL

A defesa aqui é uma allowlist (lista fechada de valores permitidos), nunca o valor cru:

<?php
$permitidas = ['Name' => 'Name', 'resets' => 'Resets', 'level' => 'cLevel'];
$col = $permitidas[$_GET['sort'] ?? 'resets'] ?? 'Resets'; // só valores conhecidos
$dir = ($_GET['dir'] ?? 'desc') === 'asc' ? 'ASC' : 'DESC';
$sql = "SELECT Name, Resets FROM Character ORDER BY $col $dir";

Assim, o usuário só consegue escolher entre colunas que você explicitamente autorizou; qualquer outra coisa cai no valor padrão.

Defesa 2 — Validação e sanitização de entrada

Prepared statements impedem o injection, mas validar a entrada é uma camada extra que corta ataques antes mesmo de chegarem ao banco e melhora a qualidade dos dados. A ideia é: aceite apenas o que faz sentido para cada campo.

<?php
// Nome de usuário do MU: normalmente 4 a 10 caracteres, letras e números
function validarLogin(string $u): bool {
    return (bool) preg_match('/^[A-Za-z0-9]{4,10}$/', $u);
}

if (!validarLogin($_POST['user'])) {
    // rejeita antes de tocar no banco
    exit('Usuário inválido');
}

Valide o tipo e o formato de tudo: IDs devem ser inteiros ((int)$_GET['id'] ou filter_var(..., FILTER_VALIDATE_INT)), e-mails com FILTER_VALIDATE_EMAIL, nomes de personagem contra o padrão da sua Season. Validação é sobre aceitar o esperado (allowlist), não sobre tentar bloquear o proibido (blocklist) — filtrar "palavras perigosas" é frágil e sempre tem como contornar.

Campo típico do site MURegra recomendada
Login / usuárioAllowlist regex, tamanho fixo (ex.: 4-10 alfanumérico)
SenhaTamanho mínimo, hash antes de comparar, nunca em texto puro
ID numérico (personagem, pedido)Inteiro estrito com FILTER_VALIDATE_INT
E-mail (recuperação)FILTER_VALIDATE_EMAIL
Coluna de ordenação (ranking)Allowlist fechada de colunas
Nome de personagemPadrão da Season (comprimento e caracteres)

Defesa 3 — Privilégio mínimo no usuário do banco

Mesmo que uma falha passe, você quer limitar o estrago. O site nunca deve conectar ao banco com um usuário sa (SQL Server) ou root (MySQL). Crie um usuário dedicado com apenas os privilégios necessários.

-- MySQL: usuário só com o que o site precisa
CREATE USER 'web_mu'@'localhost' IDENTIFIED BY 'senha-forte-aqui';
GRANT SELECT, INSERT, UPDATE ON muonline.* TO 'web_mu'@'localhost';
-- Repare: sem DROP, sem DELETE amplo, sem ALTER, sem GRANT
FLUSH PRIVILEGES;

Com esse usuário, um DROP TABLE injetado simplesmente falha por falta de permissão. Se determinado formulário só lê dados (um ranking, por exemplo), o ideal é que ele use uma conexão apenas com SELECT. Separe, quando possível, conexões de leitura das de escrita. Isso transforma uma catástrofe em um incidente contido.

Defesa 4 — Tratamento de erros sem vazar informação

Sites vulneráveis costumam entregar o mapa do banco de graça: quando uma query falha, exibem a mensagem de erro completa do SQL na tela, revelando nomes de tabela, colunas e a estrutura. Isso é ouro para o atacante (a técnica se chama error-based injection).

Nunca mostre erro de banco ao usuário em produção:

<?php
// Em produção: registre o erro internamente, mostre mensagem genérica
try {
    $stmt->execute($params);
} catch (PDOException $e) {
    error_log('DB error: ' . $e->getMessage()); // vai para o log, não para a tela
    http_response_code(500);
    exit('Ocorreu um erro. Tente novamente mais tarde.'); // genérico
}

No PHP, garanta em produção display_errors = Off no php.ini, mantendo log_errors = On. Assim você continua vendo os erros nos logs para depurar, mas o visitante nunca vê a estrutura interna.

Defesa 5 — Camadas extras: WAF e monitoramento

Depois de corrigir o código, adicione camadas de vigilância. Um WAF (Web Application Firewall) — como o ModSecurity com o conjunto de regras OWASP CRS — barra padrões de ataque conhecidos antes de chegarem à aplicação. Ele não substitui a correção no código, mas dá uma rede de segurança e tempo de reação.

Monte também um monitoramento simples para detectar tentativas:

<?php
// Detecção leve de payloads suspeitos para alerta (NÃO como defesa principal)
function pareceInjection(string $v): bool {
    return (bool) preg_match(
        '/(\bUNION\b|\bSELECT\b.+\bFROM\b|--|\bOR\b\s+\d+=\d+|;\s*DROP)/i', $v
    );
}
foreach ($_REQUEST as $k => $v) {
    if (is_string($v) && pareceInjection($v)) {
        error_log("Tentativa suspeita em '$k' do IP {$_SERVER['REMOTE_ADDR']}: $v");
        // dispare alerta (e-mail/Discord) e considere bloquear o IP
    }
}

Ative os logs de query do banco em pontos sensíveis e configure alertas para picos de erro. Detectar cedo é a diferença entre bloquear um IP e descobrir, semanas depois, que meio servidor recebeu itens ilegais.

Pontos do site de MU que mais são atacados

Priorize a revisão nestes fluxos, do mais crítico ao menos:

  1. Login — o alvo número um; bypass dá acesso a contas.
  2. Cadastro — injeção pode criar contas privilegiadas ou corromper dados.
  3. Recuperação de senha — costuma montar queries por e-mail/pergunta secreta.
  4. Ranking e listagens — o ORDER BY dinâmico é um clássico esquecido.
  5. Loja / doação — mexe com saldo e itens; injection aqui vira fraude direta.
  6. Painel do jogador — troca de senha, e-mail, transferências de personagem.

Revise cada um garantindo prepared statements, validação e o usuário de banco com privilégio mínimo.

Erros comuns e soluções

Erro / má práticaRiscoCorreção
Concatenar $_POST direto na queryBypass de login, roubo de bancoMigrar para prepared statement
Confiar que "campo numérico é seguro"Injection por parâmetro numéricoValidar inteiro e ainda assim parametrizar
ORDER BY $coluna do usuárioInjection na cláusula ORDER BYAllowlist fechada de colunas
Site conecta como sa/rootUm DROP injetado apaga tudoUsuário dedicado com privilégio mínimo
Exibir erro de SQL na telaVaza estrutura (error-based)display_errors Off, mensagem genérica
Filtrar só "palavras perigosas"Contornável, falsa sensação de segurançaAllowlist de formato, não blocklist
Senha comparada em texto puroRoubo direto de credenciaisHash forte e comparação por hash
Sem logs nem alertasAtaque descoberto tarde demaisMonitorar payloads e picos de erro

Checklist de lançamento

  • Backup completo do banco feito antes de qualquer alteração
  • Todas as queries com dados do usuário migradas para prepared statements
  • Cláusulas ORDER BY / nomes de coluna dinâmicos com allowlist
  • Validação de tipo e formato em todos os campos de entrada
  • Usuário do banco dedicado, com privilégio mínimo (sem DROP/ALTER/GRANT)
  • Conexões de leitura separadas das de escrita quando possível
  • display_errors desligado em produção, log_errors ligado
  • Mensagens de erro genéricas para o usuário, detalhes só no log
  • Senhas sempre com hash, nunca comparadas em texto puro
  • WAF (ex.: ModSecurity + OWASP CRS) avaliado como camada extra
  • Monitoramento de payloads suspeitos e alertas configurados
  • Fluxos críticos revisados: login, cadastro, recuperação, ranking, loja
  • Tudo testado em ambiente separado antes de subir para produção

SQL injection é uma ameaça séria, mas totalmente evitável. A base é sempre a mesma: separe comando de dados com prepared statements, valide o que entra, dê ao site o mínimo de poder no banco e vigie o que acontece. Aplicando essas camadas, você tira do atacante o atalho mais fácil e protege de verdade as contas e os itens dos seus jogadores.

Perguntas frequentes

SQL injection é realmente comum em site de MU Online?

É uma das falhas mais exploradas em servidores privados. Muitos sites usam scripts antigos que concatenam login e senha direto na query. Um invasor consegue burlar o login, roubar contas, dar itens ou até apagar o banco por um formulário vulnerável.

Prepared statements sozinhos resolvem tudo?

Resolvem a maior parte, pois separam comando de dados. Mas há casos que exigem cuidado extra: nomes de tabela/coluna dinâmicos e cláusulas ORDER BY não aceitam bind e precisam de allowlist. Combine prepared statements com validação de entrada e privilégio mínimo.

Preciso trocar todo o meu site antigo para me proteger?

Nem sempre, mas todo ponto que toca o banco precisa ser revisado. Formulários de login, cadastro, ranking, recuperação de senha e loja são os alvos principais. Migre cada query para prepared statement e valide as entradas.

Um WAF substitui a correção do código?

Não. Um WAF (firewall de aplicação) ajuda a barrar ataques conhecidos e ganha tempo, mas é uma camada extra, não a solução. A correção real é no código, com prepared statements e privilégio mínimo no usuário do banco.

Como sei se meu site já foi atacado por SQL injection?

Procure por padrões suspeitos nos logs (aspas, UNION SELECT, OR 1=1, comentários -- em parâmetros), contas criadas ou alteradas sem origem, itens que apareceram do nada e picos de erro no banco. Ative logs de query e alertas para reagir cedo.

GA
Editor de guias e builds

Gabriel cobre gameplay, builds de classes, PvP e progressão. Testa cada estratégia em servidor antes de publicar.

Continue lendo

Artigos relacionados