Como criar um painel de administração web para MU Online
Construa um painel de administração web seguro para o seu servidor de MU Online, com autenticação forte, controle de permissões, gestão de contas e personagens, banimentos, envio de itens e logs de auditoria completos.
Chega um momento na vida de todo servidor de MU Online em que abrir o Navicat ou o SQL Server Management Studio para cada banimento, cada devolução de item e cada reset manual deixa de ser sustentável. É lento, é perigoso, não deixa rastro de quem fez o quê, e é impensável dar esse tipo de acesso a
Chega um momento na vida de todo servidor de MU Online em que abrir o Navicat ou o SQL Server Management Studio para cada banimento, cada devolução de item e cada reset manual deixa de ser sustentável. É lento, é perigoso, não deixa rastro de quem fez o quê, e é impensável dar esse tipo de acesso a um GM voluntário. A solução profissional é um painel de administração web: uma aplicação que expõe apenas as operações necessárias, com validação, controle de permissões e auditoria completa. Neste tutorial avançado você vai desenhar e construir esse painel do zero, com atenção especial à parte que mais gente ignora e que mais causa estrago — a segurança.
Um painel de administração é, por definição, o alvo mais valioso do seu servidor. Quem o compromete pode dar itens infinitos, banir jogadores legítimos, roubar contas e destruir a economia em minutos. Por isso, cada seção deste guia trata o painel como um sistema hostil por natureza: assuma que ele será atacado e projete-o para resistir. Todos os nomes de tabela, coluna e estrutura são exemplos típicos e variam por versão (Season 6, IGCN, MuEMU, Season 16+); confirme o schema real da sua distribuição antes de rodar qualquer comando.
Pré-requisitos
- Ambiente com PHP 8.1+ e PDO (driver
sqlsrvpara SQL Server) ou stack equivalente (Node.js, .NET). - Acesso ao banco do jogo (SQL Server, comum em MU) e, opcionalmente, ao banco do site.
- HTTPS válido no domínio do painel (Let's Encrypt).
- Servidor web configurado para servir o painel em uma rota separada e protegida (subdomínio ou pasta com regras próprias).
- Conhecimento de SQL parametrizado, sessões e hashing de senha.
- Um plano de papéis: quem é Admin, quem é GM, o que cada um pode fazer.
> Regra de ouro: o banco de dados do jogo nunca deve estar acessível diretamente pela internet. O painel é a única ponte, e ela precisa ser blindada.
Arquitetura e camadas de segurança
Pense no painel como uma cebola de camadas. Um atacante precisa furar todas para causar dano. Cada camada é independente das outras.
- Rede: o painel roda em subdomínio próprio, atrás de HTTPS, idealmente com restrição de IP ou VPN para administradores.
- Autenticação: login com senha em hash forte (bcrypt/Argon2) e 2FA para admins.
- Autorização: cada ação verifica o papel do usuário (RBAC). Um GM não pode acessar rotas de admin.
- Validação: toda entrada é validada e todo SQL é parametrizado (nunca concatenado).
- Auditoria: cada ação sensível é registrada com quem, o quê, quando e de onde.
| Camada | Ameaça que bloqueia | Mecanismo |
|---|---|---|
| Rede | Varredura e acesso externo | HTTPS, restrição de IP, subdomínio |
| Autenticação | Login indevido | Hash de senha, 2FA, rate limit |
| Autorização | Escalada de privilégio | RBAC por papel |
| Validação | SQL Injection, XSS | PDO parametrizado, escape de saída |
| Auditoria | Abuso interno / negação | Log imutável de ações |
Modelagem de usuários e papéis do painel
O painel tem seus próprios usuários, separados das contas do jogo. Um administrador do painel não é a mesma coisa que uma conta de jogador. Crie no banco do site:
CREATE TABLE admin_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(40) UNIQUE NOT NULL,
pass_hash VARCHAR(255) NOT NULL, -- bcrypt/argon2
role ENUM('admin','gm','support') NOT NULL DEFAULT 'support',
totp_secret VARCHAR(64) NULL, -- 2FA
last_ip VARCHAR(45) NULL,
last_login DATETIME NULL,
active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
E a tabela de auditoria, o item mais importante do painel inteiro:
CREATE TABLE admin_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
admin_id INT NOT NULL,
action VARCHAR(60) NOT NULL, -- 'ban_account', 'give_item'...
target VARCHAR(60) NULL, -- conta/personagem alvo
details TEXT NULL, -- JSON com antes/depois
ip VARCHAR(45) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_admin (admin_id, created_at)
);
Autenticação forte e sessão
Nunca armazene senha em texto puro. Use password_hash() com bcrypt ou Argon2. No login, verifique com password_verify() e regenere a sessão para evitar fixation.
<?php
session_start();
function login(string $user, string $pass, PDO $db): bool {
$stmt = $db->prepare("SELECT id, pass_hash, role, active, totp_secret
FROM admin_users WHERE username = ?");
$stmt->execute([$user]);
$u = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$u || !$u['active'] || !password_verify($pass, $u['pass_hash'])) {
registrarTentativaFalha($user, $_SERVER['REMOTE_ADDR']);
return false;
}
// se houver 2FA, exigir o código TOTP antes de concluir
if ($u['totp_secret']) { $_SESSION['pending_2fa'] = $u['id']; return true; }
session_regenerate_id(true);
$_SESSION['admin_id'] = $u['id'];
$_SESSION['role'] = $u['role'];
return true;
}
Adote 2FA (TOTP) para todos os administradores — é a diferença entre um vazamento de senha ser um susto ou uma catástrofe. Aplique rate limiting no login (ex.: bloquear após 5 tentativas em 10 minutos por IP) para conter força bruta.
Controle de permissões (RBAC)
Cada rota do painel declara qual papel pode acessá-la. Centralize essa checagem em um middleware para não esquecer nenhuma rota.
<?php
function exigirPapel(array $permitidos): void {
if (empty($_SESSION['admin_id']) || !in_array($_SESSION['role'], $permitidos, true)) {
http_response_code(403);
exit('Acesso negado');
}
}
// no topo de uma rota sensível:
exigirPapel(['admin']); // só admin
// ou
exigirPapel(['admin', 'gm']); // admin e gm
Uma matriz de permissões deixa claro quem faz o quê. Ajuste conforme a confiança da sua equipe:
| Ação | Admin | GM | Suporte |
|---|---|---|---|
| Ver contas e personagens | Sim | Sim | Sim |
| Banir / desbanir | Sim | Sim | Não |
| Dar itens / zen | Sim | Sim | Não |
| Editar resets / level | Sim | Não | Não |
| Gerenciar usuários do painel | Sim | Não | Não |
| Ver logs de auditoria | Sim | Não | Não |
Módulo de gestão de contas e personagens
O núcleo funcional do painel é consultar e editar contas. Sempre use consultas parametrizadas — concatenar string em SQL é a porta principal para SQL Injection, e num painel de admin isso é fatal.
<?php
// buscar conta com segurança (nomes de coluna VARIAM por versão)
function buscarConta(string $account, PDO $game): ?array {
$stmt = $game->prepare(
"SELECT memb___id, memb_name, mail_addr, bloc_code, ctl1_code
FROM MEMB_INFO WHERE memb___id = ?"
);
$stmt->execute([$account]);
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}
A tabela MEMB_INFO e as colunas bloc_code (bloqueio) e ctl1_code (tipo de conta/GM) são exemplos típicos de Season 6. Sua versão pode ter nomes e estruturas diferentes — confirme sempre.
Módulo de banimento
Banir normalmente significa marcar a conta como bloqueada. Registre a ação na auditoria e, se possível, o motivo e a duração.
<?php
function banirConta(string $account, string $motivo, PDO $game, PDO $site): void {
exigirPapel(['admin', 'gm']);
// estado anterior para o log
$antes = buscarConta($account, $game);
$game->prepare("UPDATE MEMB_INFO SET bloc_code = '1' WHERE memb___id = ?")
->execute([$account]);
auditar($site, 'ban_account', $account, [
'antes' => $antes['bloc_code'] ?? null,
'depois' => '1',
'motivo' => $motivo,
]);
}
Se o jogador estiver online, force a desconexão pelo mecanismo da sua versão (ou avise que o ban só surte efeito no próximo login), para ele não continuar jogando banido.
Módulo de envio de itens e zen
Este é o módulo mais perigoso do painel — é onde a economia pode ser quebrada. Duas armadilhas: duplicação (creditar duas vezes) e conflito com o jogador online (a mudança é sobrescrita quando o jogo salva o personagem). Boas práticas:
- Prefira aplicar com o jogador offline, ou use a fila oficial de "gift"/warehouse da sua distribuição, se existir.
- Torne a operação idempotente, com um identificador único por envio.
- Sempre audite valor, item e alvo.
<?php
function darZen(string $account, int $valor, PDO $game, PDO $site): void {
exigirPapel(['admin', 'gm']);
if ($valor <= 0 || $valor > 2000000000) throw new RuntimeException('valor inválido');
// exemplo: creditar zen no warehouse da conta (coluna VARIA por versão)
$game->prepare("UPDATE warehouse SET Money = Money + ? WHERE AccountID = ?")
->execute([$valor, $account]);
auditar($site, 'give_zen', $account, ['valor' => $valor]);
}
Nunca construa a query com o valor concatenado, e valide limites máximos — um GM (ou um invasor com a sessão dele) não deveria conseguir dar bilhões sem trava.
Registrando a auditoria
A função de auditoria é chamada por todos os módulos. Ela é a sua caixa-preta.
<?php
function auditar(PDO $site, string $action, ?string $target, array $details): void {
$stmt = $site->prepare(
"INSERT INTO admin_audit (admin_id, action, target, details, ip)
VALUES (?, ?, ?, ?, ?)"
);
$stmt->execute([
$_SESSION['admin_id'] ?? 0,
$action,
$target,
json_encode($details, JSON_UNESCAPED_UNICODE),
$_SERVER['REMOTE_ADDR'],
]);
}
Trate o log como imutável: nenhuma rota do painel deve permitir editar ou apagar registros de auditoria. Se um GM abusar, é aqui que você prova. Considere replicar os logs para outro destino (arquivo ou banco separado) para o caso de o painel ser comprometido.
Front-end e usabilidade
Mantenha a interface simples e orientada a busca: um campo para localizar conta/personagem e ações claras com confirmação dupla nas operações destrutivas.
// confirmação dupla antes de banir
function confirmarBan(account) {
if (confirm(`Banir a conta "${account}"? Esta ação será registrada.`)) {
// envia POST com token CSRF ao backend
}
}
Inclua sempre um token CSRF em cada formulário que altera estado, para impedir que um site malicioso force um admin logado a executar ações. Escape toda saída em HTML para evitar XSS (por exemplo, ao exibir nomes de personagem que vieram do banco). Se este painel faz parte de um servidor que você está montando agora, veja também o guia geral de como criar um servidor de MU Online para posicionar o painel na infraestrutura.
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| Painel invadido / itens duplicados | Sessão de admin sequestrada, sem 2FA | Ative 2FA, restrinja IP, regenere sessão no login |
| SQL Injection no módulo de busca | Query concatenada com entrada | Use exclusivamente PDO parametrizado |
| Item some após dar para jogador online | Personagem salvo sobrescreveu a mudança | Aplique com jogador offline ou via fila oficial |
| Não sei quem baniu um jogador | Falta de auditoria | Registre toda ação em admin_audit |
| GM acessou área de admin | RBAC não checado na rota | Middleware exigirPapel em toda rota sensível |
| Força bruta no login | Sem rate limit | Bloqueie por tentativas/IP e adicione 2FA |
| XSS ao exibir nome de char | Saída não escapada | Escape HTML de todo dado vindo do banco |
Segurança em profundidade — resumo prático
- HTTPS obrigatório; painel em subdomínio próprio, com restrição de IP quando possível.
- Senhas em bcrypt/Argon2; 2FA para todos os administradores.
- RBAC checado em servidor, nunca só no front-end (esconder botão não é segurança).
- Todo SQL parametrizado; toda saída escapada; token CSRF em formulários.
- Banco do jogo isolado da internet — só o painel fala com ele.
- Auditoria imutável de todas as ações sensíveis, com replicação externa.
- Limites de valor em operações econômicas (zen, itens) para conter abuso.
Checklist de lançamento
- Painel servido apenas via HTTPS, em subdomínio protegido
- Tabelas
admin_userseadmin_auditcriadas - Senhas em hash forte e 2FA ativo para admins
- Rate limiting no login implementado
- Middleware de RBAC aplicado em todas as rotas sensíveis
- Todas as queries parametrizadas (revisão manual feita)
- Token CSRF em todos os formulários de escrita
- Nomes reais de tabela/coluna do game DB confirmados para sua versão
- Módulo de itens/zen com limite e idempotência testado
- Auditoria gravando quem/o quê/quando/onde em cada ação
- Banco do jogo inacessível pela internet (firewall verificado)
- Teste de invasão básico feito (injection, XSS, escalada de papel)
Com um painel bem construído, você deixa de mexer no banco na mão, delega tarefas à sua equipe com segurança, responde a incidentes com logs reais e protege o ativo mais valioso do servidor: a confiança dos jogadores. Construa camada por camada, teste cada barreira e trate o painel sempre como se ele já estivesse sob ataque — porque, mais cedo ou mais tarde, ele estará.
Perguntas frequentes
Preciso de um painel web ou o Navicat resolve?
Editar direto no banco funciona no início, mas é arriscado e não deixa rastro. Um painel web dá controle de permissões, validações, logs de auditoria e permite delegar tarefas a GMs sem entregar acesso total ao banco.
Qual linguagem usar para o painel de administração?
PHP com PDO é o mais comum no mundo MU por rodar no mesmo ambiente do site, mas você pode usar Node.js, Python ou .NET. O importante é conectar com segurança ao SQL Server do jogo e nunca expor o banco à internet.
Como proteger o painel de acesso não autorizado?
Use autenticação forte com senha em hash, 2FA para administradores, restrição por IP, HTTPS obrigatório, controle de permissões por papel e registro de auditoria de todas as ações sensíveis.
É seguro dar itens e zen pelo painel enquanto o jogador está online?
É arriscado. Alterações no personagem enquanto ele está logado podem ser sobrescritas quando o jogo salva, ou causar duplicação. O ideal é aplicar mudanças com o jogador offline ou usar a fila oficial de warehouse/gift da sua versão.
Como saber quem fez cada alteração no painel?
Com uma tabela de auditoria que registra usuário admin, ação, alvo, dados antes/depois, IP e horário. Sem log de auditoria você não consegue investigar abuso de GM nem reverter erros.