From Hijacked PBX to Stolen Trunk: INJ3CTOR3’s Campaign Evolves

Executive Summary

In May, Cyble Research & Intelligence Labs (CRIL) documented JOMANGY, a PHP webshell generation deployed by INJ3CTOR3 with self-healing persistence channels and a built-in toll-fraud dialer.

Back then, the fraud ran through the victim’s own PBX, so removing the implant would have stopped it. Artifacts we collected from the operator’s C2 staging servers during mid-September 2026 show that this is no longer true: the installer now sends credentials off the box on every run, and a second staging host serves it.

INJ3CTOR3’s FreePBX implant now uploads the victim’s SIP trunk, AMI, database, and mail-relay credentials to the operator, so disinfecting or rebuilding an infected host no longer ends the exposure. Anyone running FreePBX, Issabel, or Elastix who suspects compromise must rotate the secrets in 17 configuration files, and only after the host is clean.

The infection and persistence code is nearly unchanged; what the operator rotated are the identifiers that were available to defenders since the last time. Each section below pairs a finding with what to do about it.

Key Takeaways

  • Credentials leave on every run. k.php uploads 17 configuration files to both staging hosts every minute the host stays infected.
  • The fraud no longer needs your PBX. A stolen trunk secret lets the operator register and place calls as you from anywhere.
  • Last report’s identifiers have expired. Each build now carries its own watermark, and the webshell gate hash was rotated.
  • Two staging hosts. Plain-text commands now point to 160[.]119[.]69[.]4, while base64-encoded ones still use 45[.]95[.]147[.]178.
  • Clean-looking logs and consoles prove nothing. Requests through an encoded space reach the shell without being named in the access log, and trunks are written directly to /etc/asterisk; they do not appear in the FreePBX console.

The Campaign So Far

INJ3CTOR3 has been abusing Asterisk-based phone systems for toll fraud since at least 2020. Check Point Research named the operation that November, when it exploited CVE-2019-19006 in FreePBX and used a basic webshell to read amportal.conf and sip_additional.conf by hand. Unit 42 reported a related operation against Elastix in 2022, Fortinet documented the EncystPHP webshell in January 2026, and our May report covered JOMANGY.

The playbook has held for six years: scan for exposed PBXs, exploit or brute-force a way in, plant a password-gated webshell, and dial out at the victim’s expense. Figure 1 below shows the chain as it stands in the September build, now including the credential upload back to the staging hosts.

Figure 1: INJ3CTOR3 attack chain, September generation. (Source: 0x1da5 GitHub)
Figure 1: INJ3CTOR3 attack chain, September generation. (Source: 0x1da5 GitHub)

What Changed Since Our Last Report

We compared the April artifacts behind our May report with the 9 September collection from both staging hosts, after decoding every layer.

Area April September
Credential Exfiltration None 17 files to /hima_data/index.php on both hosts
Staging hosts 45[.]95[.]147[.]178 45[.]95[.]147[.]178 and 160[.]119[.]69[.]4
webshell watermark One value in every copy A different value per build
Webshell gate hash (JOMANGY and ZenharR) a2f6863413c4192a4abfa360577b9961 8b3a1d970fda0c17a0b785a990fb7b6a
/x takeover script 367 lines 620 lines, mostly added cleanup: removing rival actors’ implants and SSH keys
wor.php, people2.txt, _*.php status files Present Absent from the collection

Unchanged: the decoded webshell panel, the encoding chain (base64 in the shell script, base64 again in PHP, then ROT13), the persistence channels, the backdoor account password hash, and the borrowed timestamp. Behavioral detections built on our May report still apply.

Credential Exfiltration: The Implant Is No Longer the Product

The September k.php ends with two new base64-encoded lines, each holding 17 curl commands that upload one configuration file apiece to /hima_data/index.php, one line per staging host (See Figure 2 below). The paths are typed out by hand and cover FreePBX, Issabel, and Elastix layouts. The collector path echoes hima, a backdoor account the takeover script creates. The name goes back further: Check Point’s 2020 INJ3CTOR3 report shows a Telegram post by a user called Hima sharing a tool that scans and cracks SIP ranges, saving its output as HimaResults.

Figure 2: k.php line 34, decoded into 17 file uploads
Figure 2: k.php line 34, decoded into 17 file uploads

Three of the cron beacons fetch and run k.php every minute (the others every two or three minutes), so the upload repeats every minute during the infection. Rotating credentials on a still-infected host hands the new secrets to the operator within minutes.

What it unlocks Files uploaded Rotate
SIP trunks and extensions (chan_sip) /etc/asterisk/sip.conf, sip_additional.conf, sip_registrations.conf, sip_general_additional.conf, sip_custom.conf Every trunk secret (with your carrier) and extension secret, changed in the FreePBX GUI, since the *_additional.conf files are regenerated
SIP trunks and extensions (PJSIP) /etc/asterisk/pjsip.auth.conf, pjsip.endpoint.conf, pjsip.aor.conf, pjsip.identify.conf, pjsip.transports.conf The same secrets; review IP-based identity rules
Call origination without SIP /etc/asterisk/manager.conf Every AMI user.
PBX database, AMI, and platform admin /etc/freepbx.conf, /etc/amportal.conf, /etc/asterisk/amportal.conf, /etc/issabel.conf, /etc/elastix.conf The database password (AMPDBPASS), the MySQL root password, and any AMI password stored there
Outbound mail /etc/postfix/sasl_passwd The SMTP relay account, with your mail provider

This opens a second route to fraud. Our May report covered calls placed through the webshell, which require the shell to be alive and to land in the victim’s own call records. With a stolen trunk secret, the operator can register to the carrier from any machine and call as the victim, leaving nothing on the host, unless the carrier also restricts the trunk to the victim’s IP addresses. A successful disinfection stops the first route; only rotation stops the second.

Detection

  • Network: outbound HTTP POSTs from a PBX to any /hima_data/ path or to either staging host. A PBX seldom needs outbound web access beyond vendor updates, so egress allow-listing closes this channel.
  • Carrier: alert on trunk registrations from outside your own IP ranges, and on premium or international destinations, off-hours calling, and unusual concurrency. Set spend caps and destination blocks where your carrier offers them.

For Law Enforcement Agencies and CERTs

By design, victims’ credentials were collected on the two staging hosts, which stopped responding in late September 2026. Going offline does not end exposure: hosting providers may still retain server data, and infected PBXs keep trying to upload their credentials every minute to addresses that could be reassigned to someone else.

Both are reasons for law enforcement and national CERTs, which have the legal standing that researchers lack, to work with the providers on preserving that data and sinkholing the addresses, which would also identify victims for notification. We saw the uploads in code; we did not capture one in transit and hold no victim list.

Two Staging Hosts, Split by Encoding

An infected host now talks to both staging hosts, and which one depends on whether the command was written in plain text or base64. The /x takeover script is byte-identical on both.

Reference Encoding Points to
k.php line 2 (re-runs /x), eight cron schedules in /x, wr.php’s callback Plain text 160[.]119[.]69[.]4
Camouflaged cron payloads, login-triggered stager Base64 45[.]95[.]147[.]178
Credential upload Base64 Both

In April, all of these pointed to 45[.]95[.]147[.]178. We assess that the operator replaced the address in plain text and never re-encoded the base64 strings; the credential upload, which names both hosts, appears deliberate. This host is distinct from the scanner 160[.]119[.]76[.]250, which is named in our May report.

Defend: block both hosts in both directions, and decode every base64-encoded string in a crontab or shell profile before deciding which hosts a host talks to. A plain-text review would miss every channel that still reaches 45[.]95[.]147[.]178.

The Webshell: Same Program, New Identifiers

Decoded, the September JOMANGY panel is identical to April’s except for the values defenders were told to look for.

Per-build watermark. Our May report advised hunting for trace_e1ebf9066a951be519a24140711839ea, present in every April copy. The September builds carry trace_bd42a8a0d23ae16c85dede7d2abaec05 (45[.]95[.]147[.]178) and trace_7544725ebaaadac217dfaa7ca8e8fe7e (160[.]119[.]69[.]4), so that hunt now misses current infections.

One gate for two generations. JOMANGY and ZenharR share a single hardcoded MD5 gate hash, rotated together from a2f6863413c4192a4abfa360577b9961 to 8b3a1d970fda0c17a0b785a990fb7b6a. Our May report listed the April value under ZenharR only.

A forged administrator session. Behind the gate, the FreePBX button builds an admin session using FreePBX’s own ampuser class and setAdmin(); in our lab, it opened the full console without any password. Therefore, changing the FreePBX admin password affects neither the gate nor this session. (See Figure 3 below)

Figure 3: The panel's Freepbx and Elastix buttons forge an admin session without a password
Figure 3: The panel’s Freepbx and Elastix buttons forge an admin session without a password

Because each of these identifiers changes between builds, the detection approach later in this report focuses on who put a file on disk rather than on what the file contains.

The .htaccess Door and the Access-Log Gap

k.php writes a seven-line .htaccess to /var/www/html/admin/views/. It does not hide the webshell; it unblocks it, and it lets requests reach a copy without naming it.

RewriteEngine On
# enable symbolic links
Options +FollowSymLinks
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^s+ config.php [L]

Request path Status What answered
/dir/zzz 404 Nothing
/dir/%20zzz, /dir/%09zzz (space, tab, etc) 200 config.php
/dir/+zzz 404 Nothing

Simulating an operator and a victim in an isolated environment, we confirmed that a request to a URL containing an encoded space returned a working webshell for the attacker. The access log records only the requested path, never the file that answered, so searching logs for the webshell by its filename can return nothing on a host that is serving it.

Defend: check every .htaccess across the web root for who put it there; one that no package, FreePBX module, or member of your team accounts for is suspect, whatever it contains. Because the takeover script wipes logs, treat log hunting as secondary: requests with %20, %09, or %0A directly after a slash that return 200 are worth a look where logs survive.

What the FreePBX Console Won’t Show You

FreePBX lists the trunks it created from its own database; Asterisk reads whatever is in its configuration files. In our lab, a trunk we wrote directly into /etc/asterisk/pjsip_custom.conf carried a call while the FreePBX Trunks page reported no records (See Figure 4 below). We did not see the campaign plant one, but its root-equivalent accounts and stolen trunk secrets give the operator everything needed to do so.

Figure 4: Our lab trunk, known to Asterisk and carrying calls, invisible in the FreePBX console
Figure 4: Our lab trunk, known to Asterisk and carrying calls, invisible in the FreePBX console

Defend: audit from the Asterisk CLI (pjsip show endpoints, pjsip show registrations, sip show peers, sip show registry) and compare with what the console lists. Review the *_custom.conf files for trunks or contexts that nobody on your team added.

What Keeps It There

Before detecting or cleaning, it helps to see the whole apparatus. Everything in Figure 5 is read from /x and k.php: ways the implant stays on the box, and doors allowing the operator to come back, through a system account or a single web request. Each piece can rebuild/repair all others under a minute, which is why removing any one of them on its own fails. (See Figure 5 below)

Figure 5: Persistence channels that rebuild each other, and the backdoors they stay open
Figure 5: Persistence channels that rebuild each other, and the backdoors they stay open

Detecting It Without IOCs

The operator rotates hashes, watermarks, gate values, and hosts between builds, so asking whether a file is known-bad quickly stops working. The better question is who put the file there: compare what is on disk with what the package manager or a clean install says it should be.

On the host: triage with beagle.sh

PBX admins can run beagle.sh, a report-only script that makes no changes to the host, to check a PBX server for signs of compromise. (See Figure 6 below)

Check What it flags
Packed shells Several identical, same-size PHP files across unrelated directories
Locked files chattr +i or +a under the web root or in hidden directories
Unowned .htaccess An .htaccess no installed package accounts for
Self-rebuilding cron A cron line that downloads a file and hands it to a shell
Accounts An extra UID-0 account, or a full-permission FreePBX admin, nobody created
Blocklist Addresses dropped in and out by iptables outside fail2ban’s chains

Figure 6: beagle.sh (Source: 0x1da5 GitHub)
Figure 6: beagle.sh (Source: 0x1da5 GitHub)

The script separated one infected server from one clean server of the same FreePBX version, a sample of one, so expect some legitimate findings. However, it cannot tell you whether the credentials have already been left: that happens during the first cron run, within a minute of infection, and long before anyone scans. The upload leaves no file behind, and the takeover script wipes what might have recorded it: the web server, Asterisk, cron and audit logs, plus shell and MySQL histories, which makes forensic timelines hard to reconstruct.

With telemetry

The most distinctive actions leave little on disk; in our detonation, backdoor accounts were created and deleted 27 seconds apart. These eleven behaviors are detection strategies to build into your own tooling:

Layer Behavior What it catches
Host crond spawns a shell that downloads a file and runs it The takeover script on its single run, before persistence and before credentials leave
Host The web server account spawns a shell The webshell’s command box in use
Host A cron child rewrites the crontab every minute without reading it first Beacon reinstallation
Host chattr +i or touch -r on a file in the web root The lock and the borrowed timestamp
Host Twelve PHP files were written to the web root in the same second The webshell copies, as they are written; fan-out
Network The PBX contacts staging host roughly every 60 seconds The beacon’s rhythm, whatever the address
Network Many configuration files read, then outbound POST requests Credentials leaving
Web A request with leading encoded whitespace returns 200 for a path not on disk The .htaccess door
Decoy Any read of a decoy config file that the implant reads, but nothing legitimate does Credential collection, with no false positives by design
Phone Channels originate over AMI or the CLI outside your dial plan A call asking for nothing legitimate
Phone CDRs with premium destinations, off-hours calling and unusual concurrency The fraud itself

The last two catch the fraud rather than the machinery, whatever the files are called, and whether you ever find the shell. The host rows need process telemetry that PBX appliances rarely have, which is part of why so many stay infected.

Why Cleanup Fails, and the Order That Works

First move Why it fails on its own
Delete the webshell Cron re-runs k.php within minutes and rewrites all 12 copies, the .htaccess, and the immutable flags.
Patch FreePBX Sangoma’s advisory for CVE-2025-57819 states the fix prevents new exploitation but does not clean a compromised system.
Change the admin password The gate and the forged session never use it, and the backdoor accounts bypass the FreePBX login.

Shadowserver data showed more than 900 compromised FreePBX instances in February 2026; over 700 of them were still compromised in May. Rebuilding from a clean image is the most reliable fix; if you clean in place, work from the system layer up, because it replants everything above it.

Preserve first

The takeover script truncates logs, then deletes them, and clears shell and database history, so evidence shrinks with every run:

  1. Cut the host off from the internet, or at least block all outbound traffic except to your SIP carrier. Blocking only the two known staging hosts is not enough if the operator has moved. With no route out, the minute-by-minute downloads fail, and nothing more is uploaded. One local loop survives: a watchdog cron line re-runs the cached copy of k.php at /var/lib/asterisk/bin/zen2 every minute, rebuilding the webshells offline, which is why cron comes first in the cleanup below.
  2. Capture all crontabs and the eight backups listed below, /etc/passwd, /etc/shadow, shell profiles, and the web root with lsattr output and change times.
  3. Export the FreePBX ampusers table, the call records database, and the Asterisk full log, and ask your carrier to preserve CDRs and registration logs.

Then clean, in this order

  1. Cron and its spares. Clear root’s crontab in one go, so the restore and watchdog lines are removed. Then kill any running zen2, devnull2, or serv processes; killed earlier, the watchdog restarts them within a minute. Remove the k.php beacons (/var/lib/asterisk/bin/zen2, devnull2, zen222, devnull312, devnull212, /tmp/.cache/update, /dev/shm/.systemd/kernel). Unlock (chattr -i, -a) and delete the eight crontab backups: /usr/lib/.systemd/.cache, /usr/share/.fonts/.config, /var/lib/.dbus/.data, /var/tmp/.X11/.Xauthority, /opt/.local/.bashrc, /var/cache/.fontconfig/.cfg, /etc/.ssl/.cert, and /root/.config/.pref.
  2. Login and boot triggers. Remove the base64 stager from /root/.bash_profile, /root/.bashrc, and /etc/rc.local.
  3. Accounts and SSH. Remove every extra UID-0 account and any account whose /etc/shadow hash begins with $1$nRz1Cbtk$ (legitimate service accounts with that hash need new passwords, not deletion). Restore sshd_config, where root login was re-enabled.
  4. Web application. Remove the 12 webshell copies, the .htaccess, the freepbxusers row in ampusers, and /var/www/html/admin/modules/freepbx_ha/license.php.
  5. Credentials. Only now rotate the secrets in the 17 files and across every account on the host.

Conclusion

The hard part of the campaign used to be getting the implant off the box, and that has not changed. What has changed is that the box is no longer the whole problem: an organization that rebuilds but does not rotate has removed the operator’s calling platform and handed over its carrier account.

The code ties this generation to the actor in our May report through the takeover script, backdoor hash, and staging host, and a gate rotated in step across JOMANGY and ZenharR.

The operator is now rotating exactly the strings defenders were given, so detections built on provenance and behavior will outlast this build, and detections built on its hashes will not. CRIL continues to monitor INJ3CTOR3’s infrastructure.

Indicators of Compromise

Hashes and strings rotate between builds; use them to confirm a finding, not as your only detection.

Value Type Context
45[.]95[.]147[.]178 IPv4 Staging host since April
160[.]119[.]69[.]4 IPv4 Second staging host and credential collector, new
hxxp://45[.]95[.]147[.]178/hima_data/index.php URL Credential upload endpoint
hxxp://160[.]119[.]69[.]4/hima_data/index.php URL Credential upload endpoint
c6e64453ea8bc0856333b6f57567c9efd8c440410787cae135d1c4a248c07c0d SHA256 /x takeover script, identical on both hosts
38edbfc4ac89619d2bdb3f4bb2626deaaf422868d6d7af640f59d7959903f838 SHA256 k.php, 45[.]95[.]147[.]178
1c14a761620691a43b1a554a9eccd38d21c15d59463945250d4f57b10d20fce9 SHA256 k.php, 160[.]119[.]69[.]4
b54a8d9e98ff36dfe63b2aea97a2003b670f7311595bb4dd072850fed5900a0b SHA256 wr.php, 45[.]95[.]147[.]178
aa6dfd7ee90d9aea5af29ae39c14ff70cf8687118abadd7aa4fc98923f01ffd1 SHA256 wr.php, 160[.]119[.]69[.]4
adeb015fdb37814edc3ed45230f48d2b0e87bb195a5a7e4487c01549b47c744c SHA256 JOMANGY webshell on disk, 45[.]95[.]147[.]178 build
d345671e688ee807e1bb9b6fc45e6bb0611bbb1331c33888c8030ec2db47e672 SHA256 JOMANGY webshell on disk, 160[.]119[.]69[.]4 build
trace_bd42a8a0d23ae16c85dede7d2abaec05, trace_7544725ebaaadac217dfaa7ca8e8fe7e String JOMANGY watermarks, one per build
8b3a1d970fda0c17a0b785a990fb7b6a MD5 Webshell gate hash, JOMANGY and ZenharR
$1$nRz1Cbtk$ Hash prefix Password hash set on backdoor and service accounts

MITRE ATT&CK® Techniques

Mapped from the September /x and k.php to MITRE ATT&CK v20; the last three rows are new in this generation.

Technique ID Tactic Name Evidence
T1059.004 Execution Command and Scripting Interpreter: Unix Shell /x and k.php run as bash scripts fetched by cron
T1105 Command and Control Ingress Tool Transfer wget of /x, k.php and wr.php from the staging hosts
T1053.003 Persistence Scheduled Task/Job: Cron Minute-level beacons, a restore line and a watchdog
T1505.003 Persistence Server Software Component: Web Shell JOMANGY and ZenharR, 12 copies across the web root
T1546.004 Persistence Event Triggered Execution: Unix Shell Configuration Modification Stager added to /root/.bash_profile and /root/.bashrc
T1037.004 Persistence Boot or Logon Initialization Scripts: RC Scripts Stager added to /etc/rc.local
T1136.001 Persistence Create Account: Local Account Backdoor accounts, several at UID 0
T1098 Persistence Account Manipulation Shared password hash set on existing accounts; FreePBX admin row inserted
T1027 Stealth Obfuscated Files or Information base64, base64 and ROT13 layers around the webshell; base64 cron payloads
T1564.001 Stealth Hide Artifacts: Hidden Files and Directories Crontab spares in dot-paths such as /var/tmp/.X11/.Xauthority
T1070.006 Stealth Indicator Removal: Timestomp touch -r copies footer.php’s timestamp onto the webshell
T1070.003 Stealth Indicator Removal: Clear Command History Shell and MySQL histories cleared
T1222.002 Defense Impairment File and Directory Permissions Modification: Linux and Mac Permissions chattr +i and +a on webshells and crontab spares
T1685.006 Defense Impairment Disable or Modify Tools: Clear Linux or Mac System Logs Web server, Asterisk, cron, secure and audit logs truncated, then deleted
T1686 Defense Impairment Disable or Modify System Firewall iptables rules dropping rival actors’ addresses
T1496 Impact Resource Hijacking Victim’s PBX and trunks used to place premium-rate calls
T1552.001 Credential Access Unsecured Credentials: Credentials In Files k.php reads 17 Asterisk, FreePBX, Issabel, Elastix and Postfix configuration files holding secrets
T1119 Collection Automated Collection The 17 files are collected by hardcoded commands on every k.php run
T1041 Exfiltration Exfiltration Over C2 Channel Multipart HTTP POST of each file to /hima_data/index.php on both staging hosts

References

  1. Cyble Research & Intelligence Labs. JOMANGY: INJ3CTOR3’s Self-Healing FreePBX Toll Fraud Campaign, 21 May 2026.
  2. Fortinet FortiGuard Labs. Unveiling the Weaponized Web Shell: encystPHP, 28 January 2026.
  3. Palo Alto Networks Unit 42. ZenharR webshell and CVE-2021-45461 FreePBX exploitation, 2022.
  4. SANS Internet Storm Center. ISC Diary #32892, 13 April 2026.
  5. watchTowr Labs. CVE-2025-57819: FreePBX Endpoint module pre-authentication SQL injection, 2025.
  6. FreePBX Security Advisory. GHSA-vm9p-46mv-5xvw: CVE-2025-64328.
  7. The Hacker News, reporting Shadowserver Foundation data. 900 Sangoma FreePBX instances compromised, February 2026.
  8. beagle.sh: github.com/0x1da5/malware-campaigns

The post From Hijacked PBX to Stolen Trunk: INJ3CTOR3’s Campaign Evolves appeared first on Cyble.