From Hijacked PBX to Stolen Trunk: INJ3CTOR3’s Campaign Evolves
Executive SummaryIn May, Cyble Research & Intelligence Labs (CRIL) documented 2026-10-28 06:1:2 Author: cyble.com(查看原文) 阅读量:29 收藏

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.

AreaAprilSeptember
Credential ExfiltrationNone17 files to /hima_data/index.php on both hosts
Staging hosts45[.]95[.]147[.]17845[.]95[.]147[.]178 and 160[.]119[.]69[.]4
webshell watermarkOne value in every copyA different value per build
Webshell gate hash (JOMANGY and ZenharR)a2f6863413c4192a4abfa360577b99618b3a1d970fda0c17a0b785a990fb7b6a
/x takeover script367 lines620 lines, mostly added cleanup: removing rival actors’ implants and SSH keys
wor.php, people2.txt, _*.php status filesPresentAbsent 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 unlocksFiles uploadedRotate
SIP trunks and extensions (chan_sip)/etc/asterisk/sip.conf, sip_additional.conf, sip_registrations.conf, sip_general_additional.conf, sip_custom.confEvery 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.confThe same secrets; review IP-based identity rules
Call origination without SIP/etc/asterisk/manager.confEvery AMI user.
PBX database, AMI, and platform admin/etc/freepbx.conf, /etc/amportal.conf, /etc/asterisk/amportal.conf, /etc/issabel.conf, /etc/elastix.confThe database password (AMPDBPASS), the MySQL root password, and any AMI password stored there
Outbound mail/etc/postfix/sasl_passwdThe 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.

ReferenceEncodingPoints to
k.php line 2 (re-runs /x), eight cron schedules in /x, wr.php’s callbackPlain text160[.]119[.]69[.]4
Camouflaged cron payloads, login-triggered stagerBase6445[.]95[.]147[.]178
Credential uploadBase64Both

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 pathStatusWhat answered
/dir/zzz404Nothing
/dir/%20zzz, /dir/%09zzz (space, tab, etc)200config.php
/dir/+zzz404Nothing

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)

CheckWhat it flags
Packed shellsSeveral identical, same-size PHP files across unrelated directories
Locked fileschattr +i or +a under the web root or in hidden directories
Unowned .htaccessAn .htaccess no installed package accounts for
Self-rebuilding cronA cron line that downloads a file and hands it to a shell
AccountsAn extra UID-0 account, or a full-permission FreePBX admin, nobody created
BlocklistAddresses 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:

LayerBehaviorWhat it catches
Hostcrond spawns a shell that downloads a file and runs itThe takeover script on its single run, before persistence and before credentials leave
HostThe web server account spawns a shellThe webshell’s command box in use
HostA cron child rewrites the crontab every minute without reading it firstBeacon reinstallation
Hostchattr +i or touch -r on a file in the web rootThe lock and the borrowed timestamp
HostTwelve PHP files were written to the web root in the same secondThe webshell copies, as they are written; fan-out
NetworkThe PBX contacts staging host roughly every 60 secondsThe beacon’s rhythm, whatever the address
NetworkMany configuration files read, then outbound POST requestsCredentials leaving
WebA request with leading encoded whitespace returns 200 for a path not on diskThe .htaccess door
DecoyAny read of a decoy config file that the implant reads, but nothing legitimate doesCredential collection, with no false positives by design
PhoneChannels originate over AMI or the CLI outside your dial planA call asking for nothing legitimate
PhoneCDRs with premium destinations, off-hours calling and unusual concurrencyThe 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 moveWhy it fails on its own
Delete the webshellCron re-runs k.php within minutes and rewrites all 12 copies, the .htaccess, and the immutable flags.
Patch FreePBXSangoma’s advisory for CVE-2025-57819 states the fix prevents new exploitation but does not clean a compromised system.
Change the admin passwordThe 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.

ValueTypeContext
45[.]95[.]147[.]178IPv4Staging host since April
160[.]119[.]69[.]4IPv4Second staging host and credential collector, new
hxxp://45[.]95[.]147[.]178/hima_data/index.phpURLCredential upload endpoint
hxxp://160[.]119[.]69[.]4/hima_data/index.phpURLCredential upload endpoint
c6e64453ea8bc0856333b6f57567c9efd8c440410787cae135d1c4a248c07c0dSHA256/x takeover script, identical on both hosts
38edbfc4ac89619d2bdb3f4bb2626deaaf422868d6d7af640f59d7959903f838SHA256k.php, 45[.]95[.]147[.]178
1c14a761620691a43b1a554a9eccd38d21c15d59463945250d4f57b10d20fce9SHA256k.php, 160[.]119[.]69[.]4
b54a8d9e98ff36dfe63b2aea97a2003b670f7311595bb4dd072850fed5900a0bSHA256wr.php, 45[.]95[.]147[.]178
aa6dfd7ee90d9aea5af29ae39c14ff70cf8687118abadd7aa4fc98923f01ffd1SHA256wr.php, 160[.]119[.]69[.]4
adeb015fdb37814edc3ed45230f48d2b0e87bb195a5a7e4487c01549b47c744cSHA256JOMANGY webshell on disk, 45[.]95[.]147[.]178 build
d345671e688ee807e1bb9b6fc45e6bb0611bbb1331c33888c8030ec2db47e672SHA256JOMANGY webshell on disk, 160[.]119[.]69[.]4 build
trace_bd42a8a0d23ae16c85dede7d2abaec05, trace_7544725ebaaadac217dfaa7ca8e8fe7eStringJOMANGY watermarks, one per build
8b3a1d970fda0c17a0b785a990fb7b6aMD5Webshell gate hash, JOMANGY and ZenharR
$1$nRz1Cbtk$Hash prefixPassword 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 IDTacticNameEvidence
T1059.004ExecutionCommand and Scripting Interpreter: Unix Shell/x and k.php run as bash scripts fetched by cron
T1105Command and ControlIngress Tool Transferwget of /x, k.php and wr.php from the staging hosts
T1053.003PersistenceScheduled Task/Job: CronMinute-level beacons, a restore line and a watchdog
T1505.003PersistenceServer Software Component: Web ShellJOMANGY and ZenharR, 12 copies across the web root
T1546.004PersistenceEvent Triggered Execution: Unix Shell Configuration ModificationStager added to /root/.bash_profile and /root/.bashrc
T1037.004PersistenceBoot or Logon Initialization Scripts: RC ScriptsStager added to /etc/rc.local
T1136.001PersistenceCreate Account: Local AccountBackdoor accounts, several at UID 0
T1098PersistenceAccount ManipulationShared password hash set on existing accounts; FreePBX admin row inserted
T1027StealthObfuscated Files or Informationbase64, base64 and ROT13 layers around the webshell; base64 cron payloads
T1564.001StealthHide Artifacts: Hidden Files and DirectoriesCrontab spares in dot-paths such as /var/tmp/.X11/.Xauthority
T1070.006StealthIndicator Removal: Timestomptouch -r copies footer.php’s timestamp onto the webshell
T1070.003StealthIndicator Removal: Clear Command HistoryShell and MySQL histories cleared
T1222.002Defense ImpairmentFile and Directory Permissions Modification: Linux and Mac Permissionschattr +i and +a on webshells and crontab spares
T1685.006Defense ImpairmentDisable or Modify Tools: Clear Linux or Mac System LogsWeb server, Asterisk, cron, secure and audit logs truncated, then deleted
T1686Defense ImpairmentDisable or Modify System Firewalliptables rules dropping rival actors’ addresses
T1496ImpactResource HijackingVictim’s PBX and trunks used to place premium-rate calls
T1552.001Credential AccessUnsecured Credentials: Credentials In Filesk.php reads 17 Asterisk, FreePBX, Issabel, Elastix and Postfix configuration files holding secrets
T1119CollectionAutomated CollectionThe 17 files are collected by hardcoded commands on every k.php run
T1041ExfiltrationExfiltration Over C2 ChannelMultipart 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

文章来源: https://cyble.com/blog/hijacked-pbx-to-stolen-trunk-inj3ctor3s-campaign/
如有侵权请联系:admin#unsafe.sh