Application-Level Admin Tool with Inconsistent View of Underlying Operating System

Description

Application-Level Admin Tool with Inconsistent View of Underlying Operating System occurs when an administrative application fails to accurately represent the current state of the underlying OS, creating a mismatch between what the tool displays and actual OS conditions. Web-based management interfaces often track user information separately from the actual OS user database. When these representations diverge, attackers can exploit the gap by creating "ghost" accounts—OS-level user accounts invisible to administrators because the management tool doesn't reflect them.

Risk

Inconsistent admin tool views have severe security implications. Ghost accounts may be created that administrators cannot see. Persistent backdoor accounts may be established. Unauthorized users may gain system access. Audit trails may be incomplete. Security reviews may miss active threats. Compliance violations may occur undetected. Attackers may hide their presence. System integrity may be silently compromised.

Solution

Regularly refresh the admin tool's OS model and cross-check against configuration files for inconsistencies. Implement real-time synchronization between admin tools and OS state. Use direct OS queries rather than cached data. Add integrity checks between tool state and OS state. Alert on inconsistencies between representations. Log all system changes regardless of source. Implement periodic reconciliation audits.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Unauthorized Access - Ghost accounts allow unauthorized access that is hidden from administrators.
AccountabilityScope: Accountability

Hidden Activities - Actions by ghost accounts escape normal accountability mechanisms.
IntegrityScope: Integrity

Unexpected State - System state differs from what administrators believe it to be.

Example Code

Vulnerable Code

# Vulnerable: Admin interface maintains separate user database

import os

class VulnerableAdminPanel:
    def __init__(self):
        # Admin tool maintains its own user list
        # VULNERABLE: Not synchronized with OS
        self.managed_users = self.load_user_database()

    def load_user_database(self):
        # Loads from admin tool's database, NOT from OS
        return database.query("SELECT username FROM admin_users")

    def list_users(self):
        # VULNERABLE: Only shows users the admin tool knows about
        # Ghost accounts in /etc/passwd won't appear
        return self.managed_users

    def create_user(self, username, password):
        # Create in admin database AND OS
        self.managed_users.append(username)
        database.insert("admin_users", username)
        os.system(f"useradd {username}")

    def delete_user(self, username):
        # Delete from admin database AND OS
        self.managed_users.remove(username)
        database.delete("admin_users", username)
        os.system(f"userdel {username}")

    # VULNERABLE: No way to detect ghost accounts
    # Attacker can add user directly to /etc/passwd
    # Admin tool won't show it


# Attack: Creating a ghost account
def create_ghost_account():
    # Attacker has gained command execution
    # Create user directly in OS, bypassing admin tool

    # Add to /etc/passwd
    os.system("echo 'ghostuser:x:1001:1001::/home/ghostuser:/bin/bash' >> /etc/passwd")

    # Add to /etc/shadow
    os.system("echo 'ghostuser:$6$salt$hashedpassword:18000:0:99999:7:::' >> /etc/shadow")

    # Create home directory
    os.makedirs("/home/ghostuser", exist_ok=True)

    # Ghost account is now active but invisible to admin panel
<!-- Vulnerable: Web admin panel with cached user list -->
<?php

class VulnerableUserManager {
    private $userCache = [];

    public function __construct() {
        // VULNERABLE: Load users into cache at startup
        $this->refreshCache();
    }

    private function refreshCache() {
        // Load from admin database
        $result = $this->db->query("SELECT * FROM users");
        while ($row = $result->fetch_assoc()) {
            $this->userCache[$row['username']] = $row;
        }
    }

    public function listUsers() {
        // VULNERABLE: Returns cached data, not live OS state
        return array_keys($this->userCache);
    }

    public function userExists($username) {
        // VULNERABLE: Only checks cache
        return isset($this->userCache[$username]);
    }

    // VULNERABLE: No detection of OS-level changes
    // Ghost accounts won't appear in cache
}

// Attack scenario
// 1. Attacker uses command injection: `; useradd attacker`
// 2. Admin panel cache is not updated
// 3. attacker user exists in OS but not visible to admins
?>
// Vulnerable: Admin service with separate state

public class VulnerableAdminService {
    private Set<String> knownUsers = new HashSet<>();
    private Database adminDb;

    public VulnerableAdminService() {
        // VULNERABLE: Initialize from admin database only
        loadUsersFromDatabase();
    }

    private void loadUsersFromDatabase() {
        List<User> users = adminDb.getAllUsers();
        for (User u : users) {
            knownUsers.add(u.getUsername());
        }
    }

    public List<String> getAllUsers() {
        // VULNERABLE: Returns admin tool's view, not OS reality
        return new ArrayList<>(knownUsers);
    }

    public boolean deleteUser(String username) {
        // Delete from admin DB
        adminDb.deleteUser(username);
        knownUsers.remove(username);

        // Delete from OS
        Runtime.getRuntime().exec("userdel " + username);
        return true;
    }

    // VULNERABLE: No reconciliation with actual OS users
    // No detection of unauthorized accounts
}

Fixed Code

# Fixed: Admin interface with OS synchronization

import os
import pwd
import spwd
import hashlib

class SecureAdminPanel:
    def __init__(self):
        self.managed_users = set()

    def list_users(self):
        # FIXED: Always query OS directly
        os_users = self.get_os_users()
        admin_users = self.get_admin_database_users()

        # Check for inconsistencies
        ghost_accounts = os_users - admin_users
        if ghost_accounts:
            self.alert_ghost_accounts(ghost_accounts)

        missing_from_os = admin_users - os_users
        if missing_from_os:
            self.alert_missing_users(missing_from_os)

        return os_users

    def get_os_users(self):
        # FIXED: Read directly from OS
        users = set()
        for user in pwd.getpwall():
            if user.pw_uid >= 1000 and user.pw_uid < 65534:  # Normal users
                users.add(user.pw_name)
        return users

    def get_admin_database_users(self):
        return set(database.query("SELECT username FROM admin_users"))

    def reconcile_users(self):
        # FIXED: Periodic reconciliation
        os_users = self.get_os_users()
        admin_users = self.get_admin_database_users()

        # Detect ghost accounts
        for username in os_users:
            if username not in admin_users:
                self.log_security_event(f"Ghost account detected: {username}")
                self.disable_account(username)

        # Detect removed accounts
        for username in admin_users:
            if username not in os_users:
                self.log_security_event(f"User missing from OS: {username}")

    def alert_ghost_accounts(self, accounts):
        for account in accounts:
            self.log_security_event(f"ALERT: Ghost account detected: {account}")
            # Disable the ghost account
            self.disable_account(account)
            # Notify security team
            self.send_security_alert(f"Unauthorized account: {account}")

    def verify_system_integrity(self):
        # FIXED: Verify critical files haven't been tampered with
        passwd_hash = self.hash_file("/etc/passwd")
        shadow_hash = self.hash_file("/etc/shadow")

        if passwd_hash != self.known_passwd_hash:
            self.alert_file_modification("/etc/passwd")

        if shadow_hash != self.known_shadow_hash:
            self.alert_file_modification("/etc/shadow")

    def create_user(self, username, password):
        # Create in OS
        result = os.system(f"useradd -m {username}")
        if result != 0:
            raise Exception("Failed to create OS user")

        # Verify creation
        if not self.os_user_exists(username):
            raise Exception("User creation verification failed")

        # Create in admin database
        database.insert("admin_users", username)

        # Verify consistency
        self.reconcile_users()

    def os_user_exists(self, username):
        try:
            pwd.getpwnam(username)
            return True
        except KeyError:
            return False
<!-- Fixed: Web admin panel with live OS queries -->
<?php

class SecureUserManager {
    private $db;

    public function __construct($db) {
        $this->db = $db;
    }

    public function listUsers() {
        // FIXED: Get users from both sources and compare
        $osUsers = $this->getOSUsers();
        $dbUsers = $this->getDBUsers();

        $ghostAccounts = array_diff($osUsers, $dbUsers);
        $missingAccounts = array_diff($dbUsers, $osUsers);

        if (!empty($ghostAccounts)) {
            $this->alertGhostAccounts($ghostAccounts);
        }

        if (!empty($missingAccounts)) {
            $this->alertMissingAccounts($missingAccounts);
        }

        // Return complete list with status
        return [
            'users' => $osUsers,
            'ghost_accounts' => $ghostAccounts,
            'inconsistencies' => $missingAccounts
        ];
    }

    private function getOSUsers() {
        // FIXED: Read directly from OS
        $users = [];
        $passwd = file('/etc/passwd');
        foreach ($passwd as $line) {
            $parts = explode(':', $line);
            $uid = intval($parts[2]);
            if ($uid >= 1000 && $uid < 65534) {
                $users[] = $parts[0];
            }
        }
        return $users;
    }

    private function getDBUsers() {
        $users = [];
        $result = $this->db->query("SELECT username FROM users");
        while ($row = $result->fetch_assoc()) {
            $users[] = $row['username'];
        }
        return $users;
    }

    private function alertGhostAccounts($accounts) {
        foreach ($accounts as $account) {
            error_log("SECURITY ALERT: Ghost account detected: $account");
            $this->logSecurityEvent('ghost_account', $account);

            // Automatically disable ghost account
            exec("usermod -L " . escapeshellarg($account));
        }
    }

    public function userExists($username) {
        // FIXED: Check OS directly
        exec("id " . escapeshellarg($username), $output, $returnCode);
        return $returnCode === 0;
    }

    public function periodicReconciliation() {
        // FIXED: Run this via cron for continuous monitoring
        $this->listUsers();  // This will detect and alert on inconsistencies

        // Also check for file modifications
        $this->checkCriticalFiles();
    }

    private function checkCriticalFiles() {
        $files = ['/etc/passwd', '/etc/shadow', '/etc/group'];
        foreach ($files as $file) {
            $currentHash = md5_file($file);
            $knownHash = $this->db->query("SELECT hash FROM file_hashes WHERE path='$file'")->fetch_assoc()['hash'];

            if ($currentHash !== $knownHash) {
                $this->logSecurityEvent('file_modified', $file);
            }
        }
    }
}
?>

CVE Examples

Ghost account vulnerabilities have been found in various web-based management interfaces where attackers could create system accounts that remained invisible to administrators.


  • CWE-1250: Improper Preservation of Consistency Between Independent Representations of Shared State (parent)

References

  1. MITRE Corporation. "CWE-1249: Application-Level Admin Tool with Inconsistent View of Underlying Operating System." https://cwe.mitre.org/data/definitions/1249.html
  2. Tony Martin's "Ghost in the Shell Weakness" (2020)