- Логирование PHP mail(): В файле
php.iniможно включить параметрыmail.add_x_header = On(добавляет заголовок с путем к скрипту, отправившему письмо) иmail.log = /var/log/php_mail.log. Это позволит точно увидеть, какой именно файл на каком сайте инициировал отправку. - Анализ почтовой очереди: Используйте команды
mailq(для Postfix) илиexim -bp(для Exim), чтобы увидеть скопившиеся письма. Огромная очередь часто сигнализирует о взломе или спам-рассылке. - Просмотр логов MTA: Почтовые логи (обычно
/var/log/mail.logили/var/log/maillog) содержат информацию о каждом отправленном письме, включая отправителя и статус доставки.
- Postmaster Mail.ru: Показывает статистику по доставке на ящики Mail.ru, включая количество жалоб на спам и ошибки аутентификации.
- Google Postmaster Tools: Аналогичный инструмент для отслеживания репутации домена при отправке на Gmail.
- Сервисы проверки на спам: Инструменты вроде Mail-Tester или GlockApps помогают протестировать отдельное письмо перед отправкой, чтобы понять, не выглядит ли оно как спам для фильтров.
- Email-трекинг: Использование сервисов вроде Unisender или Sendsay позволяет автоматизировать рассылки и получать подробную аналитику по кликам и жалобам в одном окне.
- Аутентификация домена: Настройте SPF, DKIM и DMARC для всех своих сайтов. Это не только улучшает доставляемость, но и предотвращает отправку писем от вашего имени сторонними спамерами.
- Защита форм на сайтах: Установите капчу (например, reCAPTCHA) на все формы обратной связи, чтобы роботы не могли использовать их для рассылки спама через ваши серверы.
mail(), PHP может автоматически добавлять в заголовки писем путь к файлу.- Включение заголовка: В файле
php.iniустановите параметрmail.add_x_header = On. В отправленные письма будет добавлен заголовокX-PHP-Originating-Script, содержащий UID пользователя и имя скрипта. - Ведение отдельного лога: Укажите путь к файлу лога в
php.ini:mail.log = /var/log/phpmail.log.- Что вы увидите: Каждую попытку отправки с указанием полного пути к PHP-файлу и строки кода.
mail.log его не зафиксирует. Здесь помогут следующие методы:- Идентификация по пользователю (UID): Если каждый сайт на сервере работает от своего системного пользователя (через PHP-FPM или FastCGI), в логах почтового сервера (
/var/log/mail.log) будет видно, какой именно системный пользователь инициировал соединение с SMTP. - Скрипт-обертка для Sendmail: Можно временно подменить стандартный бинарный файл
sendmailна скрипт-заглушку (wrapper), который будет записывать в файл переменные окружения (например,PWDилиHTTP_HOST), а затем передавать данные реальному почтовому агенту. Это «поймает» даже те скрипты, которые пытаются вызватьsendmailнапрямую. - Глобальное логирование сетевой активности: Для глубокого анализа можно использовать инструменты типа
tcpdumpна порт 25/465/587, чтобы увидеть, какие локальные процессы открывают соединения с почтовыми портами, но это технически сложный метод.
mail(), PHP сам может записать путь к скрипту.- Найдите ваш
php.ini(обычно в папке с версией PHP внутри IIS). - Установите:ini
mail.add_x_header = On mail.log = "C:\inetpub\logs\php_mail.log"Используйте код с осторожностью. - Результат: В файле
php_mail.logпоявится запись:[Дата] Mail-хост:порт Файл: C:\inetpub\wwwroot\site1\send.php Line: 42. - Важно: Дайте пользователю
IIS AppPool\<ИмяПула>права на запись в этот лог-файл.
- Откройте папку
MDaemon\Logs\. - Ищите файлы
MDaemon-SMTP-In.log. - Найдите строку
Inbound SMTP connection from. - Смотрите на команду
AUTH LOGINилиMAIL FROM. Там будет указан Email-аккаунт, который используется для рассылки. - Как это поможет: Если у каждого сайта своя почтовая замена (например,
info@site1.ru), вы сразу поймете, какой сайт взломан. Если аккаунт один на всех — переходим к пункту 3.
- В логе MDaemon зафиксируйте точное время подозрительной отправки (до секунды).
- Откройте логи IIS (обычно
C:\inetpub\logs\LogFiles\W3SVC...). - Ищите POST-запросы в это же время.
- Пример: Если в 12:00:05 MDaemon получил письмо, а в логе IIS в 12:00:05 зафиксирован запрос к
/assets/lib/mailer.php, значит, этот скрипт и есть источник.
- Пример: Если в 12:00:05 MDaemon получил письмо, а в логе IIS в 12:00:05 зафиксирован запрос к
- Настройте фильтр:
Process Nameisphp-cgi.exe(илиw3wp.exe). - Добавьте фильтр:
OperationisTCP Connect. - Смотрите, какой процесс обращается к портам 25, 465 или 587.
- В колонке Path или через свойства события можно увидеть, какой именно PHP-скрипт был открыт этим процессом в момент соединения.
w3wp.exe (какого пользователя) создает нагрузку на сеть.php-cgi.exe к почтовым портам (25, 465, 587):- Process Name is
php-cgi.exe— Include (Добавить) - Operation is
TCP Connect— Include (Добавить) - Path contains
:25(или:465,:587) — Include (Добавить)
TCP Connect, сделайте следующее:- Нажмите правой кнопкой на пойманную строку.
- Выберите Properties (Свойства).
- Перейдите на вкладку Process.
- Смотрите поле Command Line.
- В IIS при использовании PHP-CGI в командной строке часто передается путь к конфигурационному файлу или переменные окружения, которые указывают на конкретный пул приложений или директорию сайта.
- Перейдите на вкладку Stack.
- Если вы видите там вызовы библиотек вроде
wp-includes(для WordPress) или конкретные пути к файлам в папкеC:\inetpub\wwwroot\..., это и есть ваш источник.
- Если вы видите там вызовы библиотек вроде
php-cgi.exe часто запускается «обобщенно». В этом случае:- В Process Monitor во время активности спама ищите операции ReadFile или CreateFile от того же самого
Process ID (PID), который делаетTCP Connect. - PHP должен сначала прочитать файл скрипта с диска, прежде чем его выполнить и отправить почту.
- Отфильтруйте по этому PID и посмотрите последние открытые файлы
.phpпрямо перед сетевым соединением.
w3wp.exe (который порождает php-cgi.exe), и в колонке User Name вы сразу увидите название пула (например, IIS AppPool\Site1), что мгновенно укажет на виновный сайт.- Нажмите
Win + R, введитеresmonи нажмите Enter. - Перейдите на вкладку Network (Сеть).
- Разверните секцию Network Connections (Сетевые подключения).
- Отсортируйте список по колонке Remote Port (Удаленный порт).
- Ищите порты 25, 465, 587.
- В колонке Image (Образ) вы увидите
php-cgi.exeилиw3wp.exe. - Главное: Посмотрите на колонку PID (Идентификатор процесса). Запомните этот номер.
Откройте PowerShell от имени администратора и введите:
Get-WmiObject Win32_Process -Filter "ProcessId = ТВОЙ_PID" | Select-Object CommandLine
- Что это даст: В строке
CommandLineбудет виден путь кphp.iniэтого сайта или переменные среды, указывающие на конкретную папку вinetpub.
w3wp.exe, используйте команду для IIS:C:\Windows\System32\inetsrv\appcmd.exe list wp
- Что это даст: Вы увидите список всех запущенных рабочих процессов и их соответствие именам сайтов (AppPool).
while($true) {
Get-NetTCPConnection -RemotePort 25,465,587 -ErrorAction SilentlyContinue | Select-Object LocalAddress, RemoteAddress, OwningProcess, State | Add-Content C:\inetpub\logs\mail_detect.log
Start-Sleep -Seconds 1
}
- Зайдите в IIS Manager.
- Для каждого сайта создайте отдельный Application Pool (Пул приложений).
- В настройках пула (Identity) можно даже указать отдельного пользователя.
- Результат: Теперь в Диспетчере задач или Мониторе ресурсов в колонке «User Name» будет написано не общее
IUSR, а имя вашего сайта. Вы сразу увидите:php-cgi.exeзапущен от имениSite1_Pool.
В
php.ini (для каждого пула или общего) включите расширенное логирование ошибок. Если скрипт тормозит при отправке (а SMTP — это медленно), он попадет в Slow Log или Error Log.- Но лучший способ — X-PHP-Originating-Script (о котором я писал выше). Даже если письмо уходит через SMTP-аккаунт в коде, PHP все равно фиксирует запуск скрипта.
Вместо моего прошлого скрипта, выполните это в PowerShell (от админа):
Get-NetTCPConnection -State Established | Where-Object { $_.RemotePort -in @(25, 465, 587) } | Select-Object OwningProcess, RemoteAddress, @{Name="ProcessName";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}}
OwningProcess: PID (например, 4520).RemoteAddress: Куда шлет (IP вашего MDaemon или внешнего сервера).ProcessName:php-cgi.
Start-Sleep -Milliseconds 100).php-cgi.exe без привязки к сайту в Диспетчере задач, сделайте вот что:- Добавьте колонку «Имя пользователя» в Диспетчере задач (вкладка Подробности).
- Если там везде
NetworkServiceилиIUSR, значит пулы настроены неверно. - Зайдите в IIS -> Application Pools -> Advanced Settings сайта.
- Найдите Identity и убедитесь, что там стоит
ApplicationPoolIdentity. - Теперь в процессах вы увидите
IIS AppPool\ИмяСайта.
netstat -ano | findstr :25
Get-Process -Id НОМЕР_PID | Select-Object *
Path будет путь к конкретному php-cgi.exe. Если у вас для каждого сайта своя версия PHP в своей папке — это победа. Если папка общая — ищите в свойствах процесса "Environment Variables" (Переменные окружения), там всегда есть путь к корню сайта (DOCUMENT_ROOT).DOCUMENT_ROOT для конкретного PID процесса, пока он висит в памяти? Это даст прямой путь к папке со спам-скриптом.netstat показывает важную деталь: PID 4 и PID 3392.- PID 3392 — это сам MDaemon, который слушает порт 25 (LISTENING).
- PID 4 — это System (ядро Windows), через которое IIS (HTTP.sys) обрабатывает веб-трафик.
monitor_spam.ps1 и запустите его от администратора:$logFile = "C:\inetpub\logs\mail_monitor.log"
echo "Мониторинг запущен... Нажмите Ctrl+C для остановки." | Out-File $logFile
while($true) {
# Ищем активные исходящие соединения на почтовые порты
$connections = Get-NetTCPConnection -RemotePort 25, 465, 587 -State Established, SynSent -ErrorAction SilentlyContinue
foreach ($conn in $connections) {
$pid = $conn.OwningProcess
# Получаем данные о процессе и его владельце (AppPool)
$procInfo = Get-WmiObject Win32_Process -Filter "ProcessId = $pid"
$user = $procInfo.GetOwner().User
$cmdLine = $procInfo.CommandLine
$time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$time] PID: $pid | Site/User: $user | Remote: $($conn.RemoteAddress) | CMD: $cmdLine"
# Выводим в консоль и пишем в файл
Write-Host $logEntry -ForegroundColor Yellow
$logEntry | Out-File -FilePath $logFile -Append
}
# Пауза минимальна, чтобы не пропустить быстрые сессии
Start-Sleep -Milliseconds 100
}
В скрипте используется фильтр
-RemotePort. Мы игнорируем входящие запросы к вашему сайту и ловим только те моменты, когда ваш сервер пытается достучаться до почтовика. Поле RemoteAddress покажет, куда именно летит спам.- Site/User: Если у вас настроены Application Pools (Identity = AppPoolIdentity), здесь будет написано
IIS AppPool\ИмяСайта. Это 100% идентификация сайта. - CMD: Здесь часто виден путь к
php.iniэтого сайта. Если используется PHP-CGI, в командной строке часто передаются аргументы, указывающие на корень сайта.
Да, этот скрипт — самодостаточное решение. Он заменяет собой Process Monitor для данной задачи.
Site/User: NetworkService, значит ваши пулы приложений настроены на общую системную учетную запись. Чтобы это исправить одним махом для всех сайтов, выполните в PowerShell:Import-Module WebAdministration
Get-ChildItem "IIS:\AppPools\" | ForEach-Object {
$_.processModel.identityType = "ApplicationPoolIdentity"
$_.SetInfo()
Write-Host "Пул $($_.Name) переведен на выделенную учетную запись."
}
NetworkService.Get-WmiObject — очень медленная команда. Пока она опрашивает систему, процесс спам-бота (который живет доли секунды) уже может закрыться, и скрипт выдаст ошибку «Process not found» или пустой CommandLine.Get-WmiObject дергает только в крайнем случае. Также добавлена проверка на существование папки логов.$logFile = "C:\inetpub\logs\mail_monitor.log"
# Проверяем наличие папки для лога
$logDir = Split-Path $logFile
if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force }
Write-Host "Мониторинг запущен... Лог: $logFile" -ForegroundColor Green
while($true) {
# Берем ВСЕ активные соединения на почтовые порты (SYN_SENT ловит попытку подключения)
$conns = Get-NetTCPConnection -RemotePort 25, 465, 587 -ErrorAction SilentlyContinue |
Where-Object { $_.State -in @('Established', 'SynSent') }
foreach ($c in $conns) {
$pid = $c.OwningProcess
if ($pid -eq 0 -or $pid -eq 4) { continue } # Пропускаем System и Idle
try {
# Быстро получаем объект процесса из памяти
$proc = Get-Process -Id $pid -ErrorAction Stop
$procName = $proc.ProcessName
# Более тяжелые данные (WMI) берем только если процесс еще жив
$wmi = Get-WmiObject Win32_Process -Filter "ProcessId = $pid" -ErrorAction SilentlyContinue
$user = $wmi.GetOwner().User
$cmd = $wmi.CommandLine
$time = Get-Date -Format "HH:mm:ss.fff" # Время до миллисекунд
$logEntry = "[$time] PID: $pid | User: $user | Proc: $procName | Remote: $($c.RemoteAddress) | CMD: $cmd"
Write-Host $logEntry -ForegroundColor Yellow
$logEntry | Out-File -FilePath $logFile -Append -Encoding UTF8
} catch {
# Если процесс исчез слишком быстро, пишем хотя бы факт соединения
$time = Get-Date -Format "HH:mm:ss.fff"
$msg = "[$time] Скрипт завершился слишком быстро. PID $pid пытался отправить почту на $($c.RemoteAddress)"
Write-Host $msg -ForegroundColor Red
$msg | Out-File $logFile -Append
}
}
# 50мс - золотая середина, чтобы не грузить CPU, но поймать бота
Start-Sleep -Milliseconds 50
}
netstat (из прошлого сообщения) сбивает с толку:89.179.240.91:80 и удаленными портами 25xxx. Это НЕ отправка почты. Это:89.179.240.91:80— ваш веб-сервер.34.239.85.139:25374— это входящий посетитель, у которого порт отправителя (динамический) случайно попал в диапазон 25000+.
netstat так:TCP 192.168.1.10:54321 1.2.3.4:25 ESTABLISHED(Где порт на удаленной стороне строго 25, 465 или 587). Мой обновленный скрипт фильтрует именно такие случаи.
CMD полный путь к запущенному интерпретатору с его конфигом.Out-File в старых версиях PowerShell использует UTF-16 или ANSI, а текстовые редакторы (вроде Notepad) или консоль могут ожидать UTF-8.UTF8 при первой записи в файл.$logFile = "C:\inetpub\logs\mail_monitor.log"
$logDir = Split-Path $logFile
if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force }
# Пишем заголовок с принудительной кодировкой UTF8
"--- Мониторинг запущен $(Get-Date) ---" | Out-File -FilePath $logFile -Encoding UTF8
Write-Host "Мониторинг запущен... Лог: $logFile" -ForegroundColor Green
- ANSI vs UTF8: PowerShell 5.1 (стандартный для Windows Server) пишет в
Out-Fileв кодировке ANSI (кириллица Windows-1251), если не указано иное. - Смешивание: Если файл уже был создан в одной кодировке, а вы дописываете (
-Append) в другой, получается каша.
- Удалите старый файл
C:\inetpub\logs\mail_monitor.log. - Примените исправление
-Encoding UTF8ко всем командамOut-Fileв скрипте. - Запустите заново.
Out-File, а ваш просмотрщик (или сам скрипт при дозаписи) ожидает UTF-8. Из-за этого русские буквы превращаются в «кракозябры».-Encoding UTF8 во всех командах записи.$logFile = "C:\inetpub\logs\mail_monitor.log"
# Исправляем заголовок и кодировку
"--- Мониторинг запущен $(Get-Date) ---" | Out-File -FilePath $logFile -Encoding UTF8 -Force
Write-Host "Мониторинг запущен... Лог: $logFile" -ForegroundColor Green
while($true) {
# Ловим именно ИСХОДЯЩИЕ на почтовые порты
$conns = Get-NetTCPConnection -RemotePort 25, 465, 587 -ErrorAction SilentlyContinue |
Where-Object { $_.State -in @('Established', 'SynSent') }
foreach ($c in $conns) {
$pid = $c.OwningProcess
if ($pid -lt 10) { continue } # Пропуск системных процессов
try {
$proc = Get-Process -Id $pid -ErrorAction Stop
$wmi = Get-WmiObject Win32_Process -Filter "ProcessId = $pid" -ErrorAction SilentlyContinue
$user = $wmi.GetOwner().User
$cmd = $wmi.CommandLine
$time = Get-Date -Format "HH:mm:ss.fff"
$logEntry = "[$time] PID: $pid | SiteUser: $user | RemoteIP: $($c.RemoteAddress) | CMD: $cmd"
Write-Host $logEntry -ForegroundColor Yellow
$logEntry | Out-File -FilePath $logFile -Append -Encoding UTF8
} catch {
$time = Get-Date -Format "HH:mm:ss.fff"
$msg = "[$time] Быстрая сессия: PID $pid стучался на $($c.RemoteAddress)"
Write-Host $msg -ForegroundColor Red
$msg | Out-File -FilePath $logFile -Append -Encoding UTF8
}
}
Start-Sleep -Milliseconds 50
}
- Запустите скрипт.
- Откройте на сервере любой браузер и попробуйте зайти на сайт
smtp.mail.ru:25(или любой почтовый адрес через телнет). - Скрипт должен мгновенно выдать желтую строку в консоли.
CMD (командная строка) пусто:.php файлу, который исполнялся в момент сетевой активности.$PID в PowerShell является зарезервированной (она всегда хранит ID текущей сессии PowerShell) и её нельзя перезаписывать.$TargetPID, и уберем кириллицу из вывода в лог, чтобы точно избежать проблем с кодировкой.$logFile = "C:\mail_monitor_v3.log"
# Пишем заголовок латиницей, чтобы не было проблем с кодировкой
"--- Monitoring Started $(Get-Date) ---" | Out-File -FilePath $logFile -Encoding UTF8 -Force
Write-Host "Monitoring RUNNING... Log: $logFile" -ForegroundColor Green
while($true) {
# Ищем исходящие на почтовые порты (игнорируем локальный трафик на 127.0.0.1, если нужно)
$conns = Get-NetTCPConnection -RemotePort 25, 465, 587 -ErrorAction SilentlyContinue |
Where-Object { $_.State -in @('Established', 'SynSent') }
foreach ($c in $conns) {
$TargetPID = $c.OwningProcess
if ($TargetPID -lt 10) { continue } # Пропуск System
try {
$proc = Get-Process -Id $TargetPID -ErrorAction Stop
$procName = $proc.ProcessName
$wmi = Get-WmiObject Win32_Process -Filter "ProcessId = $TargetPID" -ErrorAction SilentlyContinue
$user = $wmi.GetOwner().User
$cmd = $wmi.CommandLine
$time = Get-Date -Format "HH:mm:ss.fff"
$logEntry = "[$time] PID: $TargetPID | User: $user | Proc: $procName | Remote: $($c.RemoteAddress) | CMD: $cmd"
Write-Host $logEntry -ForegroundColor Yellow
$logEntry | Out-File -FilePath $logFile -Append -Encoding UTF8
} catch {
$time = Get-Date -Format "HH:mm:ss.fff"
$msg = "[$time] Quick Session: PID $TargetPID to $($c.RemoteAddress)"
Write-Host $msg -ForegroundColor Red
$msg | Out-File -FilePath $logFile -Append -Encoding UTF8
}
}
Start-Sleep -Milliseconds 50
}
- User:
IIS AppPool\ИмяВашегоСайта(если настроены пулы). - Proc:
php-cgiилиw3wp. - CMD: Полный путь к скрипту или его конфигурации.
CMD будет просто путь к php-cgi.exe, дайте знать — я покажу, как вытянуть имя конкретного файла скрипта через логи IIS.CMD (командная строка) будет отличаться:mail()mail() обычно настроена на пересылку письма локальному SMTP-серверу (например, вашему MDaemon).- Что вы увидите в логе: Процесс
php-cgi.exe(илиw3wp.exe) обращается к127.0.0.1:25. - Как понять какой скрипт: В поле
CMDбудет путь к конфигу PHP этого сайта. Если у всех сайтов один конфиг, то единственным способом узнать точный.phpфайл будет включениеmail.add_x_header = Onвphp.ini(как мы обсуждали в начале). Тогда путь к файлу запишется в само письмо в заголовки.
- Что вы увидите в логе: Прямое соединение процесса PHP с удаленным IP (например, IP Mail.ru или Gmail) по портам 465 или 587.
- Данные: В поле
Userвы увидите имя пула приложений (Application Pool). Это самый быстрый способ понять, какой именно сайт на сервере генерирует трафик.
mail() на Windows:mail() не открывает сетевое соединение сам, а передает данные системной службе или пишет в папку Pickup почтового сервера. В этом случае сетевой активности от php-cgi.exe не будет, и мой скрипт её не поймает.Отправьте тестовое письмо через
mail() с любого сайта.- Если в консоли скрипта появилась желтая строка — отлично, метод работает.
- Если тишина, значит PHP на вашем сервере работает через «Pickup» папку.
mail()? Если нет, я дам способ мониторить создание файлов в папках сайтов.mail(), значит PHP в Windows настроен на работу через папку сброса (Pickup directory) или системную службу, а не через прямое сетевое соединение. В этом случае сетевой активности нет, и Get-NetTCPConnection её не видит.C:\inetpub\wwwroot и мгновенно сообщать, какой .php файл был изменен или создан непосредственно перед отправкой.$path = "C:\inetpub\wwwroot" # Путь к вашим сайтам
$logFile = "C:\mail_file_monitor.log"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $path
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
# Фильтруем активность PHP файлов
$action = {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$time = Get-Date -Format "HH:mm:ss.fff"
if ($path -match "\.php$") {
$logEntry = "[$time] ALERT: PHP Activity in $path ($changeType)"
Write-Host $logEntry -ForegroundColor Cyan
$logEntry | Out-File $logFile -Append -Encoding UTF8
}
}
Register-ObjectEvent $watcher "Changed" -Action $action
Register-ObjectEvent $watcher "Created" -Action $action
Write-Host "Monitoring FILE activity in $path... Press Ctrl+C to stop." -ForegroundColor Green
while($true) { Start-Sleep 1 }
- Скрипты-самописцы: Взломщики часто загружают в дебри сайта файлы с именами вроде
db_cache.phpилиmail_test.php. Скрипт покажет их создание или изменение. - Сессии: При отправке
mail()PHP часто создает временные файлы сессий или кэша. - Логи MDaemon: Если письмо ушло, MDaemon записал его в свой лог. Сопоставьте время до секунды из лога MDaemon с временем из моего файлового монитора — так вы вычислите конкретный файл скрипта.
php.ini:mail.add_x_header = On
mail.log = "C:\inetpub\logs\php_mail.log"
iisreset). Если после этого файл php_mail.log останется пустым при отправке через сайт, значит ваш PHP использует стороннюю библиотеку (PHPMailer) и шлет почту через SMTP, но делает это так быстро, что сетевой монитор не успевает её «схватить».C: может перегрузить процессор, а Get-NetTCPConnection не успевает поймать короткие сессии.php.ini, который используют ваши сайты, и пропишите там:[mail function]
; Добавляет заголовок с путем к скрипту в каждое письмо
mail.add_x_header = On
; Записывает КАЖДУЮ отправку через mail() в файл
mail.log = "C:\inetpub\logs\php_mail_global.log"
C:\inetpub\logs\php_mail_global.log вручную и дайте группе Users или Everyone права на Запись, иначе PHP не сможет туда ничего добавить. После изменений выполните iisreset.mail() не вызывается, и лог выше будет пуст. В этом случае на 1000 сайтов поможет только мониторинг пулов приложений (AppPool).Import-Module WebAdministration
Write-Host "Monitoring 1000+ sites via IIS Worker Processes..." -ForegroundColor Green
while($true) {
# Получаем все активные рабочие процессы IIS
$wps = Get-WebApplicationProxy | Get-Process -ErrorAction SilentlyContinue
# Или более быстрый способ через appcmd:
$activeWp = & "$env:windir\system32\inetsrv\appcmd.exe" list wp
foreach ($line in $activeWp) {
# Строка выглядит так: WP "1234" (applicationpool:SiteName)
if ($line -match 'WP "(\d+)" \(applicationpool:(.+)\)') {
$pid = $Matches[1]
$siteName = $Matches[2]
# Проверяем, лезет ли этот конкретный PID в сеть на почтовые порты
$conn = Get-NetTCPConnection -OwningProcess $pid -RemotePort 25,465,587 -ErrorAction SilentlyContinue
if ($conn) {
$time = Get-Date -Format "HH:mm:ss"
Write-Host "[$time] ALERT! Site: $siteName (PID: $pid) is sending MAIL!" -ForegroundColor Red
"[$time] Site: $siteName | PID: $pid | Remote: $($conn.RemoteAddress)" | Out-File "C:\inetpub\logs\detected_spammers.log" -Append
}
}
}
Start-Sleep -Milliseconds 200
}
- Идите в IIS Manager -> Logging этого сайта.
- Посмотрите логи за последние 5 минут.
- Ищите POST-запросы к
.phpфайлам. - Обычные пользователи редко шлют POST-запросы пачками. Тот файл, к которому идет 100+ обращений в минуту — это и есть спам-скрипт.
- Производительность: Мы не следим за файловой системой, а только за списком процессов IIS (их обычно не 1000, а гораздо меньше, так как не все сайты активны одновременно).
- Точность: Вы сразу получаете имя сайта, даже если у вас один
php-cgi.exeна всех.
mail.log из пункта 1). [1][2]- PowerShell запущен не от имени Администратора (нужно нажать правой кнопкой на значок PS -> «Запуск от имени администратора»).
- Командлет
Get-WebApplicationProxyпредназначен для других целей, я заменю его на стандартныйappcmd, который есть на любом IIS и работает быстрее.
appcmd.exe. Она встроена в IIS и точно покажет, какой Application Pool (сайт) сейчас занимает сетевой порт.$logFile = "C:\detected_spammers.log"
$appcmd = "$env:windir\system32\inetsrv\appcmd.exe"
Write-Host "Monitoring IIS Worker Processes for Mail Activity..." -ForegroundColor Green
"--- Start Monitoring $(Get-Date) ---" | Out-File $logFile -Encoding UTF8
while($true) {
# 1. Получаем список всех активных процессов сайтов через appcmd
$activeWp = & $appcmd list wp
foreach ($line in $activeWp) {
# Извлекаем PID и название сайта (AppPool)
if ($line -match 'WP "(\d+)" \(applicationpool:(.+)\)') {
$currentPID = $Matches[1]
$siteName = $Matches[2]
# 2. Проверяем, есть ли у этого конкретного PID соединения на почтовые порты
$conns = Get-NetTCPConnection -OwningProcess $currentPID -RemotePort 25, 465, 587 -ErrorAction SilentlyContinue |
Where-Object { $_.State -in @('Established', 'SynSent') }
if ($conns) {
foreach ($c in $conns) {
$time = Get-Date -Format "HH:mm:ss.fff"
$msg = "[$time] SPAM ALERT! Site: $siteName | PID: $currentPID | Dest: $($c.RemoteAddress)"
Write-Host $msg -ForegroundColor Red
$msg | Out-File $logFile -Append -Encoding UTF8
}
}
}
}
# Оптимальная пауза для 1000 сайтов
Start-Sleep -Milliseconds 300
}
- Изоляция: Даже если у вас 1000 сайтов, одновременно «живых» процессов (
w3wp.exe) обычно не более 50-100. Скрипт проверяет только их. - Точность: Поле
applicationpoolв выводеappcmd— это и есть название вашего сайта в IIS. - mail() vs SMTP: Если PHP шлет через SMTP (PHPMailer), скрипт поймает это по портам. Если через
mail(), ищите в логе записи к127.0.0.1:25.
Зайдите в настройки MDaemon и посмотрите путь к Queues -> Pickup. Если там постоянно появляются и исчезают файлы, значит вам нужно включить
mail.log в php.ini (как в предыдущем совете), так как это единственный способ узнать, какой именно скрипт создал этот файл.Get-NetTCPConnection ничего не видит при отправке письма, это 100% подтверждение, что ваш PHP на Windows настроен на работу через локальную папку (Pickup), а не через сетевой протокол.mail(). Это делается через глобальный конфиг php.ini.php.ini (или все, если версий несколько) и примените эти настройки:[mail function]
; Добавляет в заголовки письма путь к файлу (увидите в MDaemon)
mail.add_x_header = On
; Прямой лог всех отправок через mail()
mail.log = "C:\inetpub\logs\php_mail_audit.log"
- Создайте пустой файл
C:\inetpub\logs\php_mail_audit.log. - Нажмите Правой кнопкой -> Свойства -> Безопасность.
- Добавьте группу Everyone или Users и дайте права на Запись (Write).
- Выполните в командной строке
iisreset.
$phpLog = "C:\inetpub\logs\php_mail_audit.log"
Write-Host "Ожидание отправки писем через PHP mail()..." -ForegroundColor Green
Get-Content $phpLog -Wait -Tail 0 | ForEach-Object {
if ($_ -match "mail\(\) on \[(.+)\]") {
$path = $Matches[1]
$time = Get-Date -Format "HH:mm:ss"
Write-Host "[$time] ALERT! Письмо отправлено скриптом: $path" -ForegroundColor Red
} else {
Write-Host $_ -ForegroundColor Gray
}
}
- В этом случае они соединяются по портам 465 или 587.
- Решение: В моем предыдущем скрипте (монитор портов) замените
25на465,587и попробуйте отправить письмо через SMTP-библиотеку.
C:\inetpub\logs\php_mail_audit.log после того, как вы отправили письмо через mail()?$phpLog = "C:\php_mail_audit.log"
# Создаем пустой файл
New-Item -Path $phpLog -ItemType File -Force
# Даем права всем пользователям на запись (чтобы любой сайт мог туда писать)
icacls $phpLog /grant "Everyone:(W)"
php.ini путь указан точно так же, как в скрипте:mail.log = "C:\php_mail_audit.log"
php.ini обязательно перезапустите IIS (команда iisreset в консоли), иначе изменения не вступят в силу.while, так как команда Get-Content -Wait сама умеет бесконечно ждать новых строк.$phpLog = "C:\php_mail_audit.log"
if (!(Test-Path $phpLog)) {
Write-Host "Файл лога не найден. Создаю его..." -ForegroundColor Yellow
New-Item -Path $phpLog -ItemType File -Force
icacls $phpLog /grant "Everyone:(W)"
}
Write-Host "Monitoring PHP mail() activity... (Press Ctrl+C to stop)" -ForegroundColor Green
# Читаем файл в реальном времени
Get-Content $phpLog -Wait -Tail 0 | ForEach-Object {
# Пример строки в логе: [14-Mar-2026 15:00:00] mail() on [C:\inetpub\wwwroot\site\script.php]:42
if ($_ -match "mail\(\) on \[(.+)\]") {
$path = $Matches[1]
$time = Get-Date -Format "HH:mm:ss"
Write-Host "[$time] SPAM DETECTED: $path" -ForegroundColor Red
} else {
# Вывод любых других строк из лога (например, ошибки)
Write-Host $_ -ForegroundColor Gray
}
}
mail(), PHP сам запишет полный путь к скрипту в этот файл, а мой скрипт мгновенно выведет это на экран красным цветом.C:\php_mail_audit.log после отправки письма?Turn on your Visual Search History?
Google uses its visual recognition technologies to process the images you use to search, like when you search with Google Lens. If you turn on your Visual Search History, Google will save these images from eligible Google services to your Web & App Activity when you’re signed in to your Google Account. You can learn more about this setting and which Google services save images to it at g.co/Search/VisualSearchHistory.
How visual search history is used
Your Visual Search History may be used to improve your experience on Google services, like letting you revisit your past visual searches. It may be used to develop and improve Google’s visual recognition and search technologies, as well as the Google services that use them.
When visual search history is off
If you turn this setting off, any previous Visual Search History may still be kept and used to improve Google’s visual recognition and search technologies, unless you delete it from your Web & App Activity.
Visual Search History doesn’t affect images saved by other settings, like Gemini Apps Activity.
How to manage your Visual Search History
You can view, delete, or manage your Visual Search History at activity.google.com. To download your Visual Search History, visit takeout.google.com. Images will be deleted in accordance with your Web & App Activity auto-delete settings, although some types of images may be deleted sooner.
Google uses and saves data in accordance with Google Privacy Policy.
