Из названий было не видно, какая панель лежит в основе. Теперь версия стоит в H1 всех документов и галерей, а в начале README (обоих) добавлена строка об оригинале: HostinPL 5.6, авторы Samir Shelenko и Alexander Zemlyanoy, писалась под Debian 9 / PHP 7.0. Нумерация версии оставлена от оригинала. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10 KiB
Русский · English
HostinPL 5.6 · Security audit: what we found and what we fixed
An audit of the HostinPL 5.6 build (a "nulled" copy). We looked for hidden backdoors and webshells, obfuscation, data being sent to third-party hosts, SQL injections, RCE, authorisation bypasses, password storage, and problems in the installer and the Dockerfile.
Maintained and developed with REDL.IO — AI-powered hosting.
The short version
The fork claimed: "all shells/holes have been cut out of the panel". Our check shows that claim is only partly true. We found no working backdoors, and there is no obfuscation in the panel code. But we did find:
- proof that webshells really were present in this lineage — one was found, already defanged;
- two SQL injections reachable without authentication — not mentioned anywhere in the fork's description;
- user passwords written to the database in plaintext;
- code being installed onto game nodes over plain HTTP with no signature verification.
We fixed the first three. Details below.
1. A webshell in the build's lineage — defanged
File: panel/application/public/js/proxy/proxy.php — all 7 lines of it:
<?php
if(isset($_GET['cmd'])){
print('Backdoor fixed by Xopowblu-4EJlOBEK aka Und3X (und3x.ru)');
}else{
header("Location: /");
}
?>
The file takes a ?cmd= parameter — the classic signature of a command-accepting webshell. Here the
body has been replaced with a message, so this particular shell is defanged. But the implication
is unambiguous: backdoors were planted deliberately in this build's distribution chain, and one of
them sat in a scripts folder nobody normally looks at.
Our decision: the file is kept as evidence. It executes nothing. If you do not want it:
rm panel/application/public/js/proxy/proxy.php.
Takeaway: any other copy of this panel obtained anywhere but this repository may contain a working shell. Before installing someone else's build, check at least this much:
grep -rIn --include=*.php -E "eval\(|assert\(|base64_decode|gzinflate|\\\$_REQUEST\[" .
find . -path ./engine/libs -prune -o -name '*.php' -print | grep -E '(assets|public|tmp)/'
2. What we did not find (the good news)
Checked specifically, result negative:
- No obfuscation in the panel code. Zero occurrences of
eval,assert,create_function,gzinflate,$$or$_REQUESToutside vendor libraries. Not a single long base64 blob. - No stray PHP files in the asset, upload or temporary directories — apart from the
proxy.phpdescribed above. - No data leaking to the author's hosts.
vipadmin.clubappeared only in the keywords meta tag and the mail sender address;osmp.gawas a footer link. Nothing sends passwords, licence data or database credentials anywhere. - The admin gate is intact. Every controller under
application/controllers/admin/**performs agetAccessLevel()check (level 2 for the admin area, 3 for games and locations). Not one file without it. - The panel never invokes a shell directly. A naive
grepreports 54execand 8system, but all of them are inside vendor libraries: in phpseclib,exec()is an SSH method rather than a process launcher, and the rest is in elFinder. The panel's own code calls no shell.
3. Two pre-authentication SQL injections — FIXED
The most serious finding, and one the fork's description says nothing about.
File: panel/application/models/users.php, function createAuthLog().
The function records login attempts. It used to interpolate values straight into SQL:
$query=$this->db->query("INSERT INTO `authlog` (... ,`ip`, ... ,`password`)
VALUES (NULL, '".$userid."', '".$ip."', ... , '".$password."');");
Vector 1 — via the login form. $password is the raw password from $_POST, unescaped. It is
called from account/login.php on every login attempt, including failed ones — that is, before
any authentication. Anybody off the street could inject SQL through the login form.
Vector 2 — via a forged header. $ip came from getRealIpAdress() (engine/main/user.php),
which returned the CF-Connecting-IP header with no validation at all — the early return sat
before the filter_var block:
if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
return $_SERVER["HTTP_CF_CONNECTING_IP"]; // as-is, unvalidated
}
That header is supplied by the client, so an attacker fully controlled the value reaching SQL.
The same unvalidated IP was also sent to the external ip-api.com service.
What we did:
- every value in the
INSERTnow goes through$this->db->escape()or is cast to(int); CF-Connecting-IPis validated withfilter_var($ip, FILTER_VALIDATE_IP)and otherwise ignored in favour ofREMOTE_ADDR;- the
ip-api.comrequest only runs for a valid IP and usesurlencode().
Verified on a live installation: injection attempts through the password field and through a
forged header no longer get through, the authlog table is intact, and all 27 tables are present.
4. Plaintext passwords — FIXED
account/login.php passed the real password from the form into createAuthLog():
$this->usersModel->createAuthLog($userid['user_id'], $ip, '1', $password); // successful login
$this->usersModel->createAuthLog($userid['user_id'], $ip, '0', $password); // and failed ones too
So the authlog table accumulated every user's password in plaintext — including typos and
passwords for other services that people enter by accident. Anyone with database access could read
them, and given the injection in section 3, so could an outsider.
What we did: the password is no longer passed; an empty string is stored instead. Verified: after both a successful and a failed login, the column is empty.
5. XSS in the registration form — FIXED
panel/application/views/common/loginheader.php: the ?ref= parameter (referral code) was echoed
into a hidden form field without escaping — <?echo $_GET['ref']?>.
htmlspecialchars(..., ENT_QUOTES, 'UTF-8') was added, and the controller casts the value to (int).
6. Passwords stored as unsalted MD5 — NOT fixed
login.php:114 — md5($password), column user_password varchar(32).
Unsalted MD5 is brute-forced at billions of hashes per second on a consumer GPU, and rainbow tables for common passwords are freely available. If the database leaks, treat every user password as known.
We did not change this: moving to password_hash() touches login, registration, recovery and
password changes, and requires migrating existing users — that is a project, not a one-line edit.
What to do: if the panel is used with real people, migrate to password_hash() transparently —
when a user logs in successfully against the old MD5 hash, re-save it with the new algorithm.
7. Code installed onto game nodes over HTTP — source removed
panel/engine/games/game_settings.php:54-69 — 18 Node.js modules for RAGE:MP are downloaded
over plain HTTP from a third-party host, mc.hostinpl.ru, as zip archives with no signature and
no checksum verification.
Any intermediary on the path — or the owner of that host — can replace the contents, and the code
then runs on your game nodes. The original installer likewise pulled game server builds from
dl.und3x.ru and vipadmin.club.
What we did: our node installer no longer downloads builds from those hosts — you place the
files into /home/cp/gameservers/files/<game_code>/ yourself. The lines in game_settings.php were
left alone (that is panel functionality), but you should not rely on them: either replace the URLs
with your own over HTTPS, or deploy the modules by hand.
Node.js in our docker/Dockerfile is installed from NodeSource over HTTPS with repository key
verification.
8. The panel connects to nodes with the root password — by design
The locations table stores location_user and location_password (varchar(32)) in plaintext,
and the connection uses ssh2_auth_password(). The panel's commands (useradd, docker,
chown /home) require root, so in practice the panel logs into the node as root with a password.
This cannot be changed without reworking the panel: it supports neither keys nor sudo. Measures
that reduce the risk:
# on the node: SSH and MySQL reachable only from the panel's address
ufw allow from PANEL_IP to any port 22 proto tcp
ufw allow from PANEL_IP to any port 3306 proto tcp
ufw deny 3306
The node installer prints these commands when it finishes. Additionally: use a separate password per
node, and keep access to the panel's database to a minimum — whoever reads locations gets root on
every node.
9. MariaDB on the node listens on all interfaces — the panel requires it
The panel creates game server databases on the node itself and connects to them over the network,
so bind-address = 0.0.0.0 is mandatory. Our installer does that but prints a warning and the
firewall commands — unlike the original, which changed the setting silently.
10. The registration e-mail contains the password
panel/application/views/mail/account/register.php sends the new user their password in plaintext by
e-mail. That is the original design; we did not change it so as not to break the registration and
activation flow. Bear in mind the message stays in the mailbox and on intermediate servers.
Overall assessment
| Item | Status |
|---|---|
| Working backdoors in the panel code | none found |
| Webshell in the build's lineage | found already defanged, kept as evidence |
| Obfuscation, data exfiltration | none found |
| Admin authorisation bypass | none found |
| Pre-auth SQL injections (2) | fixed |
| Plaintext passwords in the database | fixed |
| XSS in the registration form | fixed |
| Unsalted MD5 | remains, needs separate work |
| Code installed over HTTP onto nodes | source removed from the installer |
| Root password used for node access | by design, mitigate with a firewall |
| Password inside the registration e-mail | remains |
This panel should not be used for commercial hosting with real clients and payments — because of its legal status (see the README), because of the MD5 passwords, and because the provenance of the code does not deserve trust. For study, internal tasks and auditing, it is fine.
Maintained and developed with REDL.IO — AI-powered hosting.