
Introduction to Malware Binary Triage (IMBT) Course
Looking to level up your skills? Get 10% off using coupon code: MWNEWS10 for any flavor.
Enroll Now and Save 10%: Coupon Code MWNEWS10
Note: Affiliate link – your enrollment helps support this platform at no extra cost to you.
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)
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
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
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
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
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)
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:
- 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.
- 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.
- 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
- 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.
- Login and boot triggers. Remove the base64 stager from /root/.bash_profile, /root/.bashrc, and /etc/rc.local.
- 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.
- Web application. Remove the 12 webshell copies, the .htaccess, the freepbxusers row in ampusers, and /var/www/html/admin/modules/freepbx_ha/license.php.
- 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
- Cyble Research & Intelligence Labs. JOMANGY: INJ3CTOR3's Self-Healing FreePBX Toll Fraud Campaign, 21 May 2026.
- Fortinet FortiGuard Labs. Unveiling the Weaponized Web Shell: encystPHP, 28 January 2026.
- Palo Alto Networks Unit 42. ZenharR webshell and CVE-2021-45461 FreePBX exploitation, 2022.
- SANS Internet Storm Center. ISC Diary #32892, 13 April 2026.
- watchTowr Labs. CVE-2025-57819: FreePBX Endpoint module pre-authentication SQL injection, 2025.
- FreePBX Security Advisory. GHSA-vm9p-46mv-5xvw: CVE-2025-64328.
- The Hacker News, reporting Shadowserver Foundation data. 900 Sangoma FreePBX instances compromised, February 2026.
- beagle.sh: github.com/0x1da5/malware-campaigns
The post From Hijacked PBX to Stolen Trunk: INJ3CTOR3’s Campaign Evolves appeared first on Cyble.
Article Link: From Hijacked Pbx To Stolen Trunk Inj3ctor3s Campaign