How to create a web administration panel for MU Online
Build a secure web administration panel for your MU Online server, with strong authentication, permission control, account and character management, bans, item delivery and complete audit logs.
A moment comes in the life of every MU Online server when opening Navicat or SQL Server Management Studio for each ban, each item return and each manual reset stops being sustainable. It is slow, it is dangerous, it leaves no trail of who did what, and it is unthinkable to give that kind of access t
A moment comes in the life of every MU Online server when opening Navicat or SQL Server Management Studio for each ban, each item return and each manual reset stops being sustainable. It is slow, it is dangerous, it leaves no trail of who did what, and it is unthinkable to give that kind of access to a volunteer GM. The professional solution is a web administration panel: an application that exposes only the necessary operations, with validation, permission control and complete auditing. In this advanced tutorial you will design and build that panel from scratch, with special attention to the part most people ignore and that causes the most damage - security.
An administration panel is, by definition, the most valuable target on your server. Whoever compromises it can give infinite items, ban legitimate players, steal accounts and destroy the economy in minutes. That is why each section of this guide treats the panel as a hostile system by nature: assume it will be attacked and design it to resist. All table, column and structure names are typical examples and vary by version (Season 6, IGCN, MuEMU, Season 16+); confirm the real schema of your distribution before running any command.
Prerequisites
- Environment with PHP 8.1+ and PDO (the
sqlsrvdriver for SQL Server) or an equivalent stack (Node.js, .NET). - Access to the game database (SQL Server, common in MU) and, optionally, to the site database.
- A valid HTTPS certificate on the panel's domain (Let's Encrypt).
- Web server configured to serve the panel on a separate, protected route (subdomain or folder with its own rules).
- Knowledge of parameterized SQL, sessions and password hashing.
- A role plan: who is Admin, who is GM, what each one can do.
> Golden rule: the game database must never be directly accessible over the internet. The panel is the only bridge, and it needs to be hardened.
Architecture and security layers
Think of the panel as an onion of layers. An attacker has to pierce all of them to cause damage. Each layer is independent of the others.
- Network: the panel runs on its own subdomain, behind HTTPS, ideally with IP restriction or a VPN for administrators.
- Authentication: login with a strong hashed password (bcrypt/Argon2) and 2FA for admins.
- Authorization: each action checks the user's role (RBAC). A GM cannot access admin routes.
- Validation: every input is validated and all SQL is parameterized (never concatenated).
- Auditing: every sensitive action is recorded with who, what, when and from where.
| Layer | Threat it blocks | Mechanism |
|---|---|---|
| Network | Scanning and external access | HTTPS, IP restriction, subdomain |
| Authentication | Improper login | Password hashing, 2FA, rate limit |
| Authorization | Privilege escalation | Role-based RBAC |
| Validation | SQL Injection, XSS | Parameterized PDO, output escaping |
| Auditing | Internal abuse / denial | Immutable action log |
Modeling panel users and roles
The panel has its own users, separate from the game accounts. A panel administrator is not the same thing as a player account. Create in the site database:
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
);
And the audit table, the most important item of the whole panel:
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, -- target account/character
details TEXT NULL, -- JSON with before/after
ip VARCHAR(45) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_admin (admin_id, created_at)
);
Strong authentication and session
Never store a password in plain text. Use password_hash() with bcrypt or Argon2. On login, verify with password_verify() and regenerate the session to avoid 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;
}
// if there is 2FA, require the TOTP code before completing
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;
}
Adopt 2FA (TOTP) for all administrators - it is the difference between a password leak being a scare or a catastrophe. Apply rate limiting on login (e.g. block after 5 attempts in 10 minutes per IP) to contain brute force.
Permission control (RBAC)
Each panel route declares which role can access it. Centralize that check in middleware so you never forget a route.
<?php
function exigirPapel(array $permitidos): void {
if (empty($_SESSION['admin_id']) || !in_array($_SESSION['role'], $permitidos, true)) {
http_response_code(403);
exit('Access denied');
}
}
// at the top of a sensitive route:
exigirPapel(['admin']); // admin only
// or
exigirPapel(['admin', 'gm']); // admin and gm
A permission matrix makes it clear who does what. Adjust it to your team's trust level:
| Action | Admin | GM | Support |
|---|---|---|---|
| View accounts and characters | Yes | Yes | Yes |
| Ban / unban | Yes | Yes | No |
| Give items / zen | Yes | Yes | No |
| Edit resets / level | Yes | No | No |
| Manage panel users | Yes | No | No |
| View audit logs | Yes | No | No |
Account and character management module
The functional core of the panel is querying and editing accounts. Always use parameterized queries - concatenating strings in SQL is the main gateway to SQL Injection, and in an admin panel that is fatal.
<?php
// search account securely (column names VARY by version)
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;
}
The MEMB_INFO table and the bloc_code (block) and ctl1_code (account/GM type) columns are typical Season 6 examples. Your version may have different names and structures - always confirm.
Ban module
Banning normally means marking the account as blocked. Record the action in the audit log and, if possible, the reason and duration.
<?php
function banirConta(string $account, string $motivo, PDO $game, PDO $site): void {
exigirPapel(['admin', 'gm']);
// previous state for the 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,
]);
}
If the player is online, force the disconnection through your version's mechanism (or warn that the ban only takes effect on the next login), so they do not keep playing while banned.
Item and zen delivery module
This is the most dangerous module of the panel - it is where the economy can be broken. Two pitfalls: duplication (crediting twice) and conflict with the online player (the change is overwritten when the game saves the character). Best practices:
- Prefer applying it with the player offline, or use your distribution's official "gift"/warehouse queue, if it exists.
- Make the operation idempotent, with a unique identifier per delivery.
- Always audit the value, item and target.
<?php
function darZen(string $account, int $valor, PDO $game, PDO $site): void {
exigirPapel(['admin', 'gm']);
if ($valor <= 0 || $valor > 2000000000) throw new RuntimeException('invalid value');
// example: credit zen to the account's warehouse (column VARIES by version)
$game->prepare("UPDATE warehouse SET Money = Money + ? WHERE AccountID = ?")
->execute([$valor, $account]);
auditar($site, 'give_zen', $account, ['valor' => $valor]);
}
Never build the query with the value concatenated, and validate maximum limits - a GM (or an attacker with their session) should not be able to give billions without a cap.
Recording the audit log
The audit function is called by all the modules. It is your black box.
<?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'],
]);
}
Treat the log as immutable: no panel route should allow editing or deleting audit records. If a GM abuses their power, this is where you prove it. Consider replicating the logs to another destination (a file or separate database) in case the panel is compromised.
Front-end and usability
Keep the interface simple and search-oriented: a field to locate an account/character and clear actions with double confirmation on destructive operations.
// double confirmation before banning
function confirmarBan(account) {
if (confirm(`Ban the account "${account}"? This action will be logged.`)) {
// sends a POST with a CSRF token to the backend
}
}
Always include a CSRF token in every form that changes state, to stop a malicious site from forcing a logged-in admin to run actions. Escape all output in HTML to prevent XSS (for example, when displaying character names that came from the database). If this panel is part of a server you are building right now, also see the general guide on how to create a MU Online server to position the panel in the infrastructure.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Panel breached / duplicated items | Admin session hijacked, no 2FA | Enable 2FA, restrict IP, regenerate the session on login |
| SQL Injection in the search module | Query concatenated with input | Use exclusively parameterized PDO |
| Item disappears after giving to an online player | The character save overwrote the change | Apply it with the player offline or via the official queue |
| I don't know who banned a player | Missing auditing | Record every action in admin_audit |
| GM accessed the admin area | RBAC not checked on the route | exigirPapel middleware on every sensitive route |
| Brute force on login | No rate limit | Block by attempts/IP and add 2FA |
| XSS when displaying a char name | Output not escaped | HTML-escape all data coming from the database |
Defense in depth - practical summary
- Mandatory HTTPS; panel on its own subdomain, with IP restriction where possible.
- Passwords in bcrypt/Argon2; 2FA for all administrators.
- RBAC checked on the server, never only on the front-end (hiding a button is not security).
- All SQL parameterized; all output escaped; a CSRF token in forms.
- Game database isolated from the internet - only the panel talks to it.
- Immutable auditing of all sensitive actions, with external replication.
- Value limits on economic operations (zen, items) to contain abuse.
Launch checklist
- Panel served only over HTTPS, on a protected subdomain
admin_usersandadmin_audittables created- Passwords in a strong hash and 2FA active for admins
- Rate limiting on login implemented
- RBAC middleware applied on all sensitive routes
- All queries parameterized (manual review done)
- CSRF token on all write forms
- Real table/column names of the game DB confirmed for your version
- Item/zen module with limit and idempotency tested
- Auditing recording who/what/when/where on every action
- Game database inaccessible over the internet (firewall verified)
- Basic penetration test done (injection, XSS, role escalation)
With a well-built panel, you stop editing the database by hand, delegate tasks to your team safely, respond to incidents with real logs and protect the server's most valuable asset: the players' trust. Build it layer by layer, test each barrier and always treat the panel as if it were already under attack - because, sooner or later, it will be.
Frequently asked questions
Do I need a web panel or does Navicat do the job?
Editing directly in the database works at first, but it is risky and leaves no trail. A web panel gives permission control, validations, audit logs and lets you delegate tasks to GMs without handing over full database access.
Which language should I use for the administration panel?
PHP with PDO is the most common in the MU world since it runs in the same environment as the site, but you can use Node.js, Python or .NET. What matters is connecting securely to the game's SQL Server and never exposing the database to the internet.
How do I protect the panel from unauthorized access?
Use strong authentication with hashed passwords, 2FA for administrators, IP restriction, mandatory HTTPS, role-based permission control and an audit log of every sensitive action.
Is it safe to give items and zen through the panel while the player is online?
It is risky. Changes to the character while it is logged in can be overwritten when the game saves, or cause duplication. The ideal is to apply changes with the player offline or use your version's official warehouse/gift queue.
How do I know who made each change in the panel?
With an audit table that records the admin user, action, target, before/after data, IP and timestamp. Without an audit log you cannot investigate GM abuse or undo mistakes.