cPanel Addon Domains vs. Reseller Accounts: They Look Alike, but Privilege Separation Is a Different Thing

Addon domains in the same account, separate accounts under a reseller, Plesk subscriptions and CageFS: where the real boundary between one site and another lies.

On the left, a single block with three domains and one user, with a red crack running through all of them; on the right, three separate blocks, each with its own user, connected at the top to a key labeled WHM that opens them all

“The sites are separate, each one has its own domain and its own folder.” We hear this often when we ask how a hosting account with several WordPress sites is organized, and almost every time the sentence describes what the control panel shows, not what the operating system sees. Ten domains added to the same cPanel account have ten different folders but a single owner. Ten accounts created by a reseller have ten distinct owners, but a single person who can log into all of them. In the panel the two situations look alike; during an infection they behave in opposite ways.

The boundary that matters is not the domain’s folder but the Unix user PHP runs as. Anything that user can write, a webshell can write.

In the article on cross-contamination on shared hosting we saw how an infection jumps from one site to another when they share the system user. Here we go one step further: we compare the cPanel and WHM models, the Plesk equivalent and CloudLinux isolation, and look at how to tell which situation you are in and how to plan the migration from addon domains to separate accounts without carrying the malware along.

Addon domains: one user, many sites

In cPanel every account corresponds to a Linux user with its own home directory, normally /home/username. From the Domains interface the account owner can create additional domains, the addon domains, and subdomains: each gets its own document root, often a subfolder of the home directory or of public_html, but they all stay inside the same account. The panel shows them as separate sites; to the kernel they are files with the same owner.

The consequences go well beyond files. The user has a single crontab, so a scheduled task planted by a compromised site runs with the permissions needed to rewrite all the others, as we describe in the article on backdoors hidden in cron jobs. The databases all share the same prefix, username_, and often the same MySQL user, created once and reused for convenience. Mailboxes live in /home/username/mail, readable by the same user. Authorized SSH keys, the .bashrc and .profile files, temporary folders and PHP sessions, unless configured otherwise, are common to all the domains in the account.

The PHP-FPM pool can also be defined per domain, but it still runs as the account user: it separates PHP resources and settings, not permissions. The same goes for open_basedir, which restricts PHP’s file functions but not external commands launched with exec() or system(). In practice, a cPanel account with fifteen addon domains is a single site from a security standpoint, with fifteen entry points: one vulnerable plugin in any of them is enough to expose all the others.

The reseller: separate accounts, one shared key

A reseller, as defined in the cPanel guide to reseller accounts, is the owner of an account who in turn sells cPanel accounts to others. Every site created from WHM as its own account gets its own Linux user, its own home directory, its own crontab, its own databases with a different prefix and its own mailboxes. Here the privilege separation is real: a webshell on customer A’s site runs as user A and cannot write to customer B’s files, barring wrong permissions or a system vulnerability.

The weak point moves higher up. From WHM’s List Accounts interface, the reseller can open the cPanel of any of their accounts with one click, using their own password, thanks to a temporary session created by the single sign-on system. It is a convenient feature for anyone managing customers’ sites, and it is also the reason a stolen WHM credential is worth as much as all the individual account credentials put together. From there files are uploaded, cron jobs are created and passwords are reset in every account owned.

The same cPanel guide is explicit about the privileges not to grant: it advises against enabling the All Features option of the Root Access category for resellers, which bypasses the limits and lets them see all the accounts on the server, and the so-called Super Privileges. A reseller with those privileges is no longer a boundary between customers; it is a server administrator under another name. For an agency the reseller is the right model only if WHM access is treated as administrative: a unique password, a second factor, no sharing among colleagues. We cover the second factor in the article on two-factor authentication; credentials saved in clients and stolen by infostealers, which also apply to WHM, are the subject of the article on stolen FTP credentials.

Three models side by side. On the left, a single cPanel account with the user cliente and uid 1001 containing three domains: they all share home directory, crontab, database prefix and mail, and a webshell in one domain writes to the other two. In the middle, a reseller in WHM with three separate accounts, each with its own user and uid: the webshell stays confined to its account, but above the three accounts sits the reseller login, which can open the cPanel of all of them. On the right, the same separate accounts enclosed in CageFS cages, with LVE shown as a resource limit.
Three models that look alike in the panel: with addon domains the perimeter is the account; with a reseller it is the single account, but the WHM login opens them all; with CageFS each account is also invisible to the others.

Plesk: subscriptions and additional domains

Plesk uses different words for the same distinction. The customer documentation says that for every hosting subscription Plesk creates a system user, and that domains added to the same subscription share its resources. A subscription with five domains is therefore the equivalent of a cPanel account with five addon domains: one user, one perimeter. To truly separate two sites you have to put them in two subscriptions, even if they belong to the same customer, and the documentation cites the wish not to share resources between sites as a reason to buy an additional plan.

In Plesk too the upper level is a shared key. The administrator’s guide describes, for example, the Log In as Reseller button, which lets the administrator enter a reseller’s panel and change its plans, customers and subscriptions, while the reseller in turn manages their customers’ subscriptions without knowing their passwords. The reasoning is the same as for WHM: separation between subscriptions protects one site from another, not anyone from whoever has administrative access to the panel.

CloudLinux: what CageFS and LVE add

Many cPanel-based shared hosts use CloudLinux, which adds two components that are often confused. The first is CageFS, a virtualized file system that, according to the CloudLinux documentation, locks each user in a cage of their own: the user cannot see other users or their names, cannot access server configuration files such as Apache’s, has a restricted view of /proc and cannot see other people’s processes, has only binaries considered safe, and has a /tmp folder of their own. Against a compromised site trying to explore the server or read other accounts’ files, it is a concrete defense.

The second is LVE, and this is where the misunderstanding is common. The documentation on limits describes it as a cgroup-based kernel technology whose purpose is to keep a single site from bringing the server to its knees: it limits CPU, physical memory, I/O, operations per second, entry processes and number of processes. It is a resource limit, not a security boundary. It serves, for example, to stop a cryptominer in one account from slowing down all the others, but it does not stop a webshell from writing to its own user’s files.

Above all, neither CageFS nor LVE separates the domains inside the same account. The cage is per user: ten addon domains of the same user sit in the same cage, with the same crontab and the same files. CloudLinux strengthens the boundary between accounts; it does not create a new one where there was none before.

Table with nine resource rows and three columns. The rows are Unix user and files, crontab, databases and MySQL user, mail, SSH keys and shell files, PHP-FPM pool, tmp and sessions, visibility of other users and processes, access from the upper level. Column for addon domains in the same account: everything shared, in red, except the PHP pool, which can be separated but runs as the same user. Column for separate accounts under a reseller: everything separate, in green, except tmp and visibility, which depend on the server configuration, and access from the upper level, in red, because the reseller enters all the accounts. Column for separate accounts with CageFS: everything separate, including tmp and visibility, with reseller access always in red.
What sites share in the three models. The last row stays red everywhere: whoever controls the upper level, root or reseller, gets into every account.

How to tell which situation you are in

Before planning a cleanup or a migration you need a verified map, not a remembered one. With SSH access to the account, three commands say almost everything: which user you are working as, which domains belong to it according to cPanel, and which WordPress installations are actually on disk.

# User and groups you are working as (the same one PHP runs as)
id
# Account domains according to cPanel: main, addon, subdomains, aliases
uapi --output=jsonpretty DomainInfo list_domains
# WordPress installations actually present in the home directory
find ~ -name wp-config.php -not -path '*/node_modules/*' 2>/dev/null
# The user's scheduled tasks: they apply to all the account's domains
crontab -l

Whoever administers the server sees the full picture. cPanel keeps the association between each domain and its owning user in /etc/userdomains, and the association between each account and whoever owns it, that is root or a reseller, in /etc/trueuserowners. Counting domains per user quickly reveals the accounts that gather many sites; filtering by owner shows each reseller’s perimeter, that is how many sites are exposed if that credential is stolen.

# As root: how many domains per user (busiest first)
awk -F': ' '{print $2}' /etc/userdomains | sort | uniq -c | sort -rn | head
# All the domains of one user
awk -F': ' '$2=="cliente" {print $1}' /etc/userdomains
# Accounts owned by a reseller: its perimeter if the login is stolen
awk -F': ' '$2=="agenzia" {print $1}' /etc/trueuserowners
# The same via the WHM API
whmapi1 listaccts want=user,domain,owner
# On CloudLinux: users with CageFS enabled
cagefsctl --list-enabled

Two checks complete the map. The first concerns databases: if several sites in the same account use the same MySQL user, the password read from one wp-config.php opens all the databases, and a compromise can pass through the database even when the files are separate, for example by creating ghost administrators. The second concerns home directory permissions: on a server without CageFS, a home directory with 755 permissions lets any user list its contents, and a world-readable configuration file nullifies any separation between accounts.

# As root: which MySQL users have access to the account's databases
mysql -e "SELECT User, Host, Db FROM mysql.db WHERE Db LIKE 'cliente\\_%' ORDER BY User"
# World-readable home directories and wp-config.php readable by "others"
stat -c '%a %U %n' /home/*/ | awk '$1 !~ /^7[15]0$|^711$/'
find /home/*/ -name wp-config.php -perm -o=r 2>/dev/null

From addon domains to separate accounts

Separating the sites is the structural fix, and cPanel offers a dedicated tool: Convert Addon Domain to Account, in WHM’s Transfers section. According to the documentation it moves to the new account the contents of the document root, the virtual host include files, the SSL certificate, mailboxes, forwarders and autoresponders, the databases (with a choice between copying and moving), the database users, the FTP and Web Disk accounts and custom DNS records. It requires the rearrange-accts privilege, free space equal to several times the size of the domain, and prior deletion of the addon domain’s subdomains.

Two details in the documentation have direct security consequences. The new account applies its own prefix to the databases, so names and users change and wp-config.php must be updated. And the original data stays in the source account until it is deleted by hand: a complete copy of the site, with any backdoors it contains, keeps living next to the other domains.

The most common mistake is converting before cleaning. The converter copies files and databases as they are, so an infected site becomes a separate but still infected account, and a backdoor hidden in the shared home directory, outside the document root, stays where it was, available to the other sites. That is why the order we follow is always the same:

  1. Map and freeze. Inventory of domains, installations, databases and MySQL users; suspension of the user’s crontab and running processes, as we explain in the article on PHP processes that stay in memory.
  2. Clean the whole account. All sites in the same working window, together with the home directory: .ssh, shell files, .htaccess and .user.ini in the parent folders.
  3. Convert. One account per site, with distinct MySQL users, checking that each site works in the new account before moving on.
  4. Remove the copies. Deletion of the original data left in the source account and of installations that are no longer needed.
  5. Credentials. New passwords for databases, WordPress administrators, FTP, cPanel and WHM, new keys and salts in every wp-config.php.
  6. Upper-level access. Second factor on the reseller or panel administrator, no unnecessary Root Access or Super privileges.
Two paths compared. At the top, in red, the wrong path: an account with three addon domains, one of them infected, is converted immediately into three accounts; the infection is copied into the new account and a copy of the original data stays in the source account with the backdoor in the home directory. At the bottom, in green, the correct path in six steps: map and freeze, clean the whole account, convert to separate accounts with distinct MySQL users, remove the original copies, change credentials, second factor on the reseller's WHM login.
Converting an addon domain into a separate account copies everything, infection included, and leaves the original data where it was: clean first, then separate.

What changes for the cleanup

The hosting model decides the scope of the work before a single file is opened. With addon domains the perimeter is the whole account: all the sites must be considered compromised until proven otherwise, and cleaned together, because any site left behind can reinfect the others within minutes. With separate accounts the perimeter narrows to the single account, provided you verify that the boundary held: no files owned by another user, no 777 permissions, no world-readable home directories, no MySQL user shared between accounts.

When the entry point is the reseller login, on the other hand, the perimeter is again everything the reseller owns, and the investigation changes nature. You no longer look for the vulnerable plugin but for actions taken with valid credentials: files uploaded from the File Manager, cron jobs created from the interface, passwords reset. The cPanel and WHM access logs, in /usr/local/cpanel/logs/access_log, and the File Manager logs become the main source; the reconstruction techniques are those described in the article on the audit log. A restore from backup, in this scenario, does little good if the login stays in the attacker’s hands, as we explain when discussing malware that survives a restore.

When each model makes sense

Addon domains make sense for sites belonging to the same entity, maintained the same way, that would accept going down together: the main site and its blog, for example. They do not make sense for different customers’ sites, for an e-commerce store next to old, unpatched brochure sites, or for test installations nobody follows. The reseller, or one account per site created directly by the administrator, is the right choice for an agency, as long as its login is treated as the key to all the sites that it is. On shared servers, CageFS adds a useful layer between accounts; on a dedicated server or VPS, one user and one PHP-FPM pool per site achieve the same principle even without a panel, as described in the post-cleanup hardening checklist.

If you are handling an infection on an account with many domains, or on several accounts of a reseller, the first job is to work out how far the perimeter extends. When we step in we map users, domains, databases and panel access, along with cron jobs and in-memory processes, and we clean every WordPress installation in the account in the same working window, with a guaranteed cleanup in 6, 12 or 24 hours per installation. If you would like an opinion on your situation, write to us.

THE NEXT STEP

Is your site
infected?

Every hour counts: the malware keeps hitting visitors and customers, and Google, your host and the blacklists won’t wait. Choose a plan, pay online and share your access: the cleanup starts right away, at a fixed price and with guaranteed times.

Request a cleanup

Takes you to the plans: cleanup within 6, 12 or 24 hours of receiving access.

MANAGED SERVER S.R.L.info@malwarecleanup.com +39 02 5656 9681

Monday – Friday · 9:30am – 7:30pm Italian time (CET)

Prefer an online form?
Contact us at managedserver.it ↗