← All audits

Audit #39 Suspicious

2 affected plugins · 8k+ combined active installs · baseline → head 2.0.2 · by beacon-scan-skill · closed 1mo ago

Show full summary

This audit re-examines the JoomSky vendor portfolio after audit #23 found setup.joomsky.com was the C2 endpoint for an eval(curl_exec(JCONSTINST)) remote-PHP-execution primitive shipped in js-support-ticket through 2017. Audit #23 concluded "current 3.0.8 is clean" based on the eval primitive's removal. Three new findings change that picture:

1. js-support-ticket v3.0.8: eval primitive removed, but constants base64-obfuscated and preserved as dead code

includes/includer.php:244-250 shipped 2026-04-28 (4 days before audit #23 published) defines:

define('JCONSTINST', base64_decode("aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby9pbmRleC5waHA="));
//                                  → https://setup.joomsky.com/jsticketwp/pro/index.php
define('JCONSTV',    base64_decode("aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby92ZXJpZmllci5waHA="));
//                                  → https://setup.joomsky.com/jsticketwp/pro/verifier.php

Both constants are defined but have no callers anywhere in the trunk. There is no eval() or curl_exec() connected to them. The constants are dead code.

The base64 encoding has no functional purpose for dead constants. The only effect is to evade plain-string IOC scanners — a grep "setup.joomsky.com" over the trunk would miss them. This is the audit-evasion shape: the vendor knew (or anticipated) the IOC catalog watching for those strings and obfuscated them while preserving the values intact for potential reactivation.

2. js-jobs v2.0.2: active install primitive against the same C2

The vendor did NOT apply the same removal to the sibling plugin. includes/includer.php:101-102 ships the constant in plain text:

define('JCONSTV', 'https://setup.joomsky.com/jsjobswp/pro/index.php');

And modules/proinstaller/controller.php:54-93 ships an active wp_remote_post → JSON → install_plugin primitive:

$post_data = array(
    'transactionkey' => JSJOBSrequest::getVar('transactionkey'),
    // ... license parameters from the user's GET/POST ...
);
$url = JCONSTV;
$response = wp_remote_post($url, array('body' => $post_data, 'timeout' => 7, 'sslverify' => false));
$result = json_decode($response['body']);

if (isset($result[0]) && $result[0] == 1) {
    $plugin_url = $result[1];   // ← server controls this
    $sql_url    = $result[2];   // ← and this
    $installed  = $this->install_plugin($plugin_url, $sql_url);
    if (!is_wp_error($installed) && $installed) {
        $link = admin_url("admin.php?page=jsjobs");
        wp_redirect($link);
        exit;
    }
}

Then install_plugin() at controller.php:96+ does download_url($plugin_zip)WP_Filesystem extract into WP_PLUGIN_DIR/js-jobs/. The endpoint's response controls the zip URL; the plugin then unzips and installs that code with full author privileges. sslverify => false makes the channel MITM-able.

This is the same architecture as the audit #23 malicious primitive, only the eval(curl_exec()) in-line execution is replaced with download → unzip → install. Functionally equivalent attack surface (vendor or any MITM can ship arbitrary PHP that runs on the next admin page load).

3. js-jobs getDemo() — dead $return_string; where an eval used to live

modules/postinstallation/model.php:1644-1672 fetches a literal PHP file from setup.joomsky.com:

$url = 'https://setup.joomsky.com/jobmanagertheme/demoimporter/demos/' . $foldername . '/democode.php';
$response = wp_remote_post($url, array('body' => $post_data, 'timeout' => 7, 'sslverify' => false));
if (!is_wp_error($response) && $response['response']['code'] == 200 && isset($response['body'])) {
    $call_result = $response['body'];
}
// ...
if ($call_result) {
   $return_string = $call_result;
   $pro = 0;
   $return_string;                                                    // ← dead expression statement
   update_option('job_manager_demno_id', $demoid);
}

$return_string; on line 1668 is a no-op expression statement that does nothing in PHP. The pattern matches a removed eval($return_string); line — the cleanup deleted the eval but left the surrounding scaffolding (remote fetch, response capture, body assignment) intact. The endpoint URL filename democode.php confirms the response was intended to be PHP code to execute, not data to render.

4. sslverify=false used 9 times across js-jobs

Every wp_remote_post call to setup.joomsky.com (proinstaller, postinstallation, license verification, translation API) uses 'sslverify' => false. There is no benign reason a plugin would disable TLS verification on its own vendor's HTTPS endpoint. The effect is the channel is MITM-able by any actor on the network path between the customer site and setup.joomsky.com — including any operator who later acquires control of any intermediate hop.

⚠️
Pattern detected — pending vendor response or further evidence.

Not yet confirmed malicious. Site owners should treat with caution; plugin author should review the cleanup steps.

If you run any of these 2 plugins on your site

See the Affected plugins table below for the full slug list. To check whether any are installed across your fleet:

wp plugin list --field=name | grep -E '^(js\-support\-ticket|js\-jobs)$'

For each match, verify your install against the wp.org canonical and remove if compromised:

wp plugin verify-checksums <slug>
wp plugin deactivate <slug>
wp plugin delete <slug>

Patched builds for the major affected slugs are hosted at plugins.captaincore.io — see the cleanup instructions for site operators below for the full per-plugin URL list.

If you're the plugin author

Cleanup steps to clear this label have not yet been documented for this audit. Contact the investigator listed above.

The label clears automatically on the next wp beacon scan-deltas once the cleanup conditions above are met.

Affected plugins (2)

All plugins covered by this incident report. Combined exposure: 8k+ active installs across 2 slugs.

Plugin Active installs Trunk version wp.org status
JS Help Desk – AI-Powered Support & Ticketing System 8k+ 3.1.1 Open
JS Job Manager 100 2.0.2 Open

IOCs extracted (4)

Kind Value Confidence
code_pattern aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbQ high
code_pattern JCONSTV medium
url https://setup.joomsky.com/jobmanagertheme/demoimporter/ medium
url https://setup.joomsky.com/jsjobswp/pro/index.php high

Audit #39 — JoomSky portfolio (rabilal): js-jobs + js-support-ticket

  • Plugins covered:
  • js-jobs (100 active installs, head v2.0.2, last update 2025-11-22) — primary focus
  • js-support-ticket (8,000 active installs, head v3.0.8, last update 2026-04-28) — sibling, cross-referenced from audit #23
  • Author: rabilal (Ahmad Bilal / JoomSky), sole committer across all 3 wp.org plugins
  • Event: #2534 code_pattern · critical · 2026-05-08 12:42:47 (setup.joomsky.com IOC match in js-jobs)
  • Cross-reference: audit #23 (js-support-ticket, verdict: malicious, 2026-05-02) — established the eval-curl primitive pattern in historic v1.0.3 → v2.0.1 versions

Summary

This audit re-examines the JoomSky vendor portfolio after audit #23 found setup.joomsky.com was the C2 endpoint for an eval(curl_exec(JCONSTINST)) remote-PHP-execution primitive shipped in js-support-ticket through 2017. Audit #23 concluded "current 3.0.8 is clean" based on the eval primitive's removal. Three new findings change that picture:

1. js-support-ticket v3.0.8: eval primitive removed, but constants base64-obfuscated and preserved as dead code

includes/includer.php:244-250 shipped 2026-04-28 (4 days before audit #23 published) defines:

define('JCONSTINST', base64_decode("aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby9pbmRleC5waHA="));
//                                  → https://setup.joomsky.com/jsticketwp/pro/index.php
define('JCONSTV',    base64_decode("aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby92ZXJpZmllci5waHA="));
//                                  → https://setup.joomsky.com/jsticketwp/pro/verifier.php

Both constants are defined but have no callers anywhere in the trunk. There is no eval() or curl_exec() connected to them. The constants are dead code.

The base64 encoding has no functional purpose for dead constants. The only effect is to evade plain-string IOC scanners — a grep "setup.joomsky.com" over the trunk would miss them. This is the audit-evasion shape: the vendor knew (or anticipated) the IOC catalog watching for those strings and obfuscated them while preserving the values intact for potential reactivation.

2. js-jobs v2.0.2: active install primitive against the same C2

The vendor did NOT apply the same removal to the sibling plugin. includes/includer.php:101-102 ships the constant in plain text:

define('JCONSTV', 'https://setup.joomsky.com/jsjobswp/pro/index.php');

And modules/proinstaller/controller.php:54-93 ships an active wp_remote_post → JSON → install_plugin primitive:

$post_data = array(
    'transactionkey' => JSJOBSrequest::getVar('transactionkey'),
    // ... license parameters from the user's GET/POST ...
);
$url = JCONSTV;
$response = wp_remote_post($url, array('body' => $post_data, 'timeout' => 7, 'sslverify' => false));
$result = json_decode($response['body']);

if (isset($result[0]) && $result[0] == 1) {
    $plugin_url = $result[1];   // ← server controls this
    $sql_url    = $result[2];   // ← and this
    $installed  = $this->install_plugin($plugin_url, $sql_url);
    if (!is_wp_error($installed) && $installed) {
        $link = admin_url("admin.php?page=jsjobs");
        wp_redirect($link);
        exit;
    }
}

Then install_plugin() at controller.php:96+ does download_url($plugin_zip)WP_Filesystem extract into WP_PLUGIN_DIR/js-jobs/. The endpoint's response controls the zip URL; the plugin then unzips and installs that code with full author privileges. sslverify => false makes the channel MITM-able.

This is the same architecture as the audit #23 malicious primitive, only the eval(curl_exec()) in-line execution is replaced with download → unzip → install. Functionally equivalent attack surface (vendor or any MITM can ship arbitrary PHP that runs on the next admin page load).

3. js-jobs getDemo() — dead $return_string; where an eval used to live

modules/postinstallation/model.php:1644-1672 fetches a literal PHP file from setup.joomsky.com:

$url = 'https://setup.joomsky.com/jobmanagertheme/demoimporter/demos/' . $foldername . '/democode.php';
$response = wp_remote_post($url, array('body' => $post_data, 'timeout' => 7, 'sslverify' => false));
if (!is_wp_error($response) && $response['response']['code'] == 200 && isset($response['body'])) {
    $call_result = $response['body'];
}
// ...
if ($call_result) {
   $return_string = $call_result;
   $pro = 0;
   $return_string;                                                    // ← dead expression statement
   update_option('job_manager_demno_id', $demoid);
}

$return_string; on line 1668 is a no-op expression statement that does nothing in PHP. The pattern matches a removed eval($return_string); line — the cleanup deleted the eval but left the surrounding scaffolding (remote fetch, response capture, body assignment) intact. The endpoint URL filename democode.php confirms the response was intended to be PHP code to execute, not data to render.

4. sslverify=false used 9 times across js-jobs

Every wp_remote_post call to setup.joomsky.com (proinstaller, postinstallation, license verification, translation API) uses 'sslverify' => false. There is no benign reason a plugin would disable TLS verification on its own vendor's HTTPS endpoint. The effect is the channel is MITM-able by any actor on the network path between the customer site and setup.joomsky.com — including any operator who later acquires control of any intermediate hop.

Vendor pattern across portfolio

PluginInstallsTreatment in current head
js-support-ticket v3.0.88,000eval-curl removed, JCONST constants base64-obfuscated as dead code
js-jobs v2.0.2100JCONSTV in plain text, active wp_remote_post → install_plugin primitive, dead $return_string; evidence of removed eval
learn-manager v1.1.810clean (no JCONST*, no proinstaller, abandoned 2021)

The vendor's response to audit #23 (or to the same forces that prompted audit #23) was a partial cleanup: the flagship product js-support-ticket got the eval primitive removed, the C2 constants base64-obfuscated, and the resulting dead code preserved. The lower-traffic sibling js-jobs was left at the prior state with the install primitive still active. The cleanup pattern is consistent with someone who wants the plugin to pass IOC string-match scans while retaining the code paths for reactivation.

Verdict

suspicious

js-jobs ships an active vendor-controlled remote-install primitive with TLS verification disabled. The endpoint is the same setup.joomsky.com server that was the C2 for the historic eval-curl primitive in js-support-ticket. The architecture is functionally equivalent to remote-PHP-execution by a different mechanism (zip-install vs. eval-curl). Even ignoring the historical evidence, sslverify=false on a vendor self-update primitive is a real attack surface.

The base64 obfuscation in js-support-ticket v3.0.8 changes my read of audit #23's "current 3.0.8 is clean" conclusion. The constants are dead but the vendor's choice to obfuscate rather than delete them is the audit-evasion shape, not the cleanup shape.

This is short of "malicious" because:

  • No active eval/curl_exec in either plugin's current trunk
  • The js-jobs install primitive is at least nominally a vendor-pro-installer feature (not hidden)
  • The js-support-ticket constants are dead code with no triggering path

But it's well into "suspicious" because:

  • Same vendor with a documented historical malicious primitive
  • Active install primitive in js-jobs targets the exact same C2 host
  • sslverify=false is gratuitous insecurity on the vendor's own endpoint
  • Base64 obfuscation of dead constants is audit-evasion behavior

Recommendation

1. wp.org should re-evaluate js-jobs. The active install primitive matches the architecture pattern of audit #23's malicious finding, only with a softer execution mechanism. At minimum, sslverify=false should be required to be removed. 2. js-support-ticket v3.0.8 should not be considered "fully cleaned." The base64 obfuscation of dead constants pointed at the same C2 host is the wrong response to a malicious finding. Recommend wp.org request full removal of the JCONST* defines. 3. New IOC catalog entry: base64-encoded form of setup.joomsky.com URLs. Multiple vendors who get caught will follow this exact obfuscation playbook; catching the encoded form prevents repeat detection-evasion.

Suspicious pattern hits (reproduced from auto-scaffold for reference)

setup.joomsky.com — 5 hits in js-jobs

remote_enqueue — 3 hits (Google Charts + reCAPTCHA, BENIGN public-CDN class)

getJSModel($module)->$task() — 1 hit (includes/ajax.php:18) — dynamic-method dispatch over user-controlled $task. Separate concern but worth noting.

base64_decode — 3 hits (1 string utility + 2 data: URI handling, BENIGN)

str_rot13 — 1 hit (includes/jsjobslib.php:135) — string utility, BENIGN.

IOCs to extract

Full diff

_Multi-plugin audit; no single-plugin baseline diff applies. js-support-ticket v3.0.8 was reviewed against audit #23's historical reference points._

Cross-reference

  • Audit #23 — js-support-ticket (verdict: malicious, 2026-05-02) established the eval-curl primitive against setup.joomsky.com in historical versions
  • IOC catalog entries 109-112 (audit #23-derived) — these audit findings extend the catalog: see "IOCs to extract" above