200600
This commit is contained in:
27
package-lock.json
generated
27
package-lock.json
generated
@@ -6392,16 +6392,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
@@ -8736,6 +8737,22 @@
|
||||
"prettier": "2.8.7"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-language-server/node_modules/prettier": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-language-server/node_modules/request-light": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
|
||||
|
||||
127
src/content/blog/13-mikrotik-log-to-telegram.md
Normal file
127
src/content/blog/13-mikrotik-log-to-telegram.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: 'MikroTik: отправка логов в Telegram без внешних серверов'
|
||||
summary: 'Пошаговая инструкция по настройке автоматической отправки критических логов MikroTik в Telegram через встроенные скрипты RouterOS. Фильтрация событий, предотвращение дублей, советы по безопасности и расширению.'
|
||||
date: '2025-11-18'
|
||||
draft: false
|
||||
tags:
|
||||
- mikrotik
|
||||
- telegram
|
||||
- routeros
|
||||
- мониторинг
|
||||
- безопасность
|
||||
- автоматизация
|
||||
---
|
||||
# 🛠️ Инструкция: Настройка отправки логов MikroTik в Telegram
|
||||
|
||||
Привет! сегодня научу тебя настраивать мгновенные оповещения о критических событиях с MikroTik прямо в Telegram. Всё делается через родные скрипты RouterOS — никаких внешних серверов!
|
||||
|
||||
## 🔍 Что мы сделаем
|
||||
- Настроим фильтрацию логов по важным событиям
|
||||
- Отправляем уведомления в Telegram через бота
|
||||
- Гарантируем уникальность сообщений (без спама)
|
||||
|
||||
## ⚙️ Предварительные требования
|
||||
1. Устройство MikroTik с RouterOS v6+
|
||||
2. Доступ к интернету (разрешен `api.telegram.org`)
|
||||
3. Telegram-бот (создай через [@BotFather](https://t.me/BotFather))
|
||||
4. ID чата (узнай через [@userinfobot](https://t.me/userinfobot))
|
||||
|
||||
## 🚀 Пошаговая настройка
|
||||
|
||||
### Шаг 1: Создаем скрипт
|
||||
Зайди в Winbox → `System` → `Scripts` → Добавь новый скрипт:
|
||||
```routeros
|
||||
# Название: telegram-log-sender
|
||||
:local chat_id "ВАШ_CHAT_ID" # Замени на реальный ID
|
||||
:local bot_token "ВАШ_ТОКЕН" # Токен бота от @BotFather
|
||||
:local last_log_time [/log get [find] time]
|
||||
:local prefixes ("dhcp", "interface", "error") # Твои ключевые префиксы
|
||||
|
||||
:local logs ""
|
||||
:local last_unique_log ""
|
||||
|
||||
:foreach prefix in=$prefixes do={
|
||||
:local prefix_logs [/log find where time>($last_log_time + 1h) and message~($prefix)]
|
||||
|
||||
:foreach log in=$prefix_logs do={
|
||||
:local log_entry [/log get $log]
|
||||
:local log_message ($log_entry->"message")
|
||||
|
||||
# Фильтр дубликатов
|
||||
:if ($log_message != $last_unique_log) do={
|
||||
:set logs ($logs . "⚠️ " . $log_message . "\n\n")
|
||||
:set last_unique_log $log_message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Отправка в Telegram
|
||||
:if ($logs != "") do={
|
||||
:local url ("https://api.telegram.org/bot" . $bot_token . "/sendMessage")
|
||||
:local data ("chat_id=" . $chat_id . "&text=" . $logs)
|
||||
/tool fetch url=$url mode=http http-method=post http-data=$data
|
||||
}
|
||||
|
||||
# Обновляем метку времени
|
||||
:set last_log_time [/log get [find] time]
|
||||
```
|
||||
|
||||
### Шаг 2: Настраиваем планировщик
|
||||
В Winbox → `System` → `Scheduler` → Добавь новое задание:
|
||||
```routeros
|
||||
Name: telegram-logs
|
||||
Interval: 5m
|
||||
On Event: /system script run telegram-log-sender
|
||||
```
|
||||
|
||||
### Шаг 3: Проверяем работу
|
||||
1. Сымитируй событие в логах:
|
||||
```bash
|
||||
/log info message="dhcp: Test lease added"
|
||||
```
|
||||
2. Жди до 5 минут
|
||||
3. Проверь Telegram-чат:
|
||||
|
||||

|
||||
|
||||
## 🚨 Возможные проблемы и решения
|
||||
|
||||
| Симптом | Проверка | Исправление |
|
||||
|---------|----------|-------------|
|
||||
| Сообщения не приходят | `:put [:resolve "api.telegram.org"]` | Разреши DNS и доступ |
|
||||
| "failure" в логах | `/tool fetch url="https://api.telegram.org"` | Проверь токен и chat_id |
|
||||
| Дубли сообщений | Проверь `last_unique_log` логику | Добавь больше префиксов в фильтр |
|
||||
| Обрезанные сообщения | `:put [:len $logs]` | Разбей сообщение на части по 4000 символов |
|
||||
|
||||
## 💡 Продвинутые настройки
|
||||
|
||||
### Добавляем форматирование
|
||||
Измени блок отправки для Markdown:
|
||||
```routeros
|
||||
:local data ("chat_id=" . $chat_id . "&parse_mode=Markdown&text=*Router Alert!*%0A" . $logs)
|
||||
```
|
||||
|
||||
### Фильтр по критичности
|
||||
Добавь приоритеты в скрипт:
|
||||
```routeros
|
||||
:local levels ("error", "warning")
|
||||
...
|
||||
and message~($prefix) and topics~($level)
|
||||
```
|
||||
|
||||
### Защита токена (только ROS v7+)
|
||||
```routeros
|
||||
:local bot_token [/certificate get telegram_token value-name=key]
|
||||
# Где telegram_token - имя зашифрованного сертификата
|
||||
```
|
||||
|
||||
## 🌟 Советы
|
||||
1. **Тестовый режим**: сначала добавь префикс `test` и проверь работу
|
||||
2. **Ограничение частоты**: при >50 событиях/мин добавь задержку
|
||||
```routeros
|
||||
:delay 3s
|
||||
```
|
||||
3. **Визуализация**: используй emoji в сообщениях (⚠️🔥🚨)
|
||||
4. **Резервный канал**: продублируй на почту через `/tool e-mail`
|
||||
|
||||
Обновил скрипт или нашел ошибку? Дай знать в обратной связи!
|
||||
308
src/content/blog/14-auto-command.md
Normal file
308
src/content/blog/14-auto-command.md
Normal file
@@ -0,0 +1,308 @@
|
||||
---
|
||||
title: 'Автоматизация сбора системной информации и логов с серверов: Ansible + Graylog'
|
||||
summary: 'Практическое руководство по созданию кроссплатформенной системы автоматического сбора метрик и логов с серверов Windows и Linux с помощью Ansible и отправкой данных в Graylog. Описаны архитектура, примеры скриптов, интеграция и расширяемость.'
|
||||
date: '2025-11-18'
|
||||
draft: false
|
||||
tags:
|
||||
- ansible
|
||||
- graylog
|
||||
- мониторинг
|
||||
- windows
|
||||
- linux
|
||||
- автоматизация
|
||||
- devops
|
||||
---
|
||||
## Универсальная система сбора системной информации с интеграцией в Graylog
|
||||
|
||||
### 🔍 Введение
|
||||
В современной инфраструктуре критически важен **централизованный сбор системной информации** с разнородных серверов. Ручной сбор данных через SSH/WinRM неэффективен, а разрозненные логи затрудняют анализ. Решение — **автоматизированная кроссплатформенная система**, которая:
|
||||
- Собирает ключевые метрики на Windows/Linux
|
||||
- Стандартизирует формат логов
|
||||
- Отправляет данные в Graylog для централизованного анализа
|
||||
- Работает через Ansible без ручного вмешательства
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ Технические требования
|
||||
| Компонент | Windows | Linux |
|
||||
|-----------------|-----------------------------|---------------------------|
|
||||
| **Управление** | Ansible 2.9+ | Ansible 2.9+ |
|
||||
| **Доступ** | WinRM, Python 3.x | SSH, Bash |
|
||||
| **Целевые хосты**| WinRM включён, порт 5985 | Стандартные утилиты (ip, df) |
|
||||
| **Сервер логирования** | Graylog с настроенным GELF-input | |
|
||||
|
||||
---
|
||||
|
||||
### 🧩 Архитектура решения
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Ansible Control Node] --> B(Windows Hosts)
|
||||
A --> C(Linux Hosts)
|
||||
B --> D[run_commands.py]
|
||||
C --> E[collect_info.sh]
|
||||
D --> F[Graylog]
|
||||
E --> F
|
||||
F[Graylog] --> G[Дашборды]
|
||||
F --> H[Алерты]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Быстрый старт
|
||||
**Шаг 1. Клонирование репозитория**
|
||||
```bash
|
||||
git clone https://git.fipi.pro/Plata_Upravleniya_RF/commander.git
|
||||
cd commander
|
||||
```
|
||||
|
||||
**Шаг 2. Настройка инвентаря**
|
||||
`inventory/hosts.ini`:
|
||||
```ini
|
||||
[windows]
|
||||
win-server1 ansible_host=192.168.1.10
|
||||
|
||||
[linux]
|
||||
linux-server1 ansible_host=192.168.1.20
|
||||
|
||||
[windows:vars]
|
||||
ansible_user=Admin
|
||||
ansible_password=SecurePass123
|
||||
ansible_connection=winrm
|
||||
|
||||
[linux:vars]
|
||||
ansible_user=root
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
**Шаг 3. Запуск системы**
|
||||
```bash
|
||||
ansible-playbook -i inventory/hosts.ini playbook.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔧 Детали реализации
|
||||
#### Для Windows
|
||||
**Основной скрипт:** `roles/graylog_collector/files/run_commands.py`
|
||||
```python
|
||||
import subprocess, yaml
|
||||
|
||||
def execute_command(cmd):
|
||||
"""Выполнение команды с перехватом вывода"""
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
return {
|
||||
'stdout': result.stdout,
|
||||
'stderr': result.stderr,
|
||||
'returncode': result.returncode
|
||||
}
|
||||
```
|
||||
|
||||
**Конфигурация команд:** `commands.yaml`
|
||||
```yaml
|
||||
commands:
|
||||
- name: Network Configuration
|
||||
command: ipconfig /all
|
||||
- name: System Information
|
||||
command: systeminfo
|
||||
```
|
||||
|
||||
#### Для Linux
|
||||
**Bash-скрипт:** `collect_info.sh`
|
||||
```bash
|
||||
#!/bin/bash
|
||||
commands=(
|
||||
"ip -br a"
|
||||
"df -h --output=source,fstype,pcent"
|
||||
"free -m"
|
||||
"ss -tuln"
|
||||
)
|
||||
```
|
||||
|
||||
#### Интеграция с Graylog
|
||||
**Общий модуль:** `graylog_sender.py`
|
||||
```python
|
||||
def send_gelf(log_entry):
|
||||
"""Отправка лога в формате GELF"""
|
||||
message = {
|
||||
"version": "1.1",
|
||||
"host": socket.gethostname(),
|
||||
"short_message": "System Snapshot",
|
||||
"_environment": "production",
|
||||
"_log": log_entry
|
||||
}
|
||||
sock.sendto(json.dumps(message).encode(), (GRAYLOG_HOST, GRAYLOG_PORT))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ Ansible-интеграция
|
||||
**Структура проекта:**
|
||||
```
|
||||
commander/
|
||||
├── playbook.yml
|
||||
└── roles/
|
||||
└── graylog_collector/
|
||||
├── tasks/
|
||||
│ ├── windows.yml
|
||||
│ └── linux.yml
|
||||
├── files/
|
||||
│ ├── run_commands.py
|
||||
│ ├── collect_info.sh
|
||||
│ └── graylog_sender.py
|
||||
└── defaults/
|
||||
└── main.yml
|
||||
```
|
||||
|
||||
**Плейбук:** `playbook.yml`
|
||||
```yaml
|
||||
- name: Deploy Graylog Collector
|
||||
hosts: all
|
||||
roles:
|
||||
- graylog_collector
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Пример использования в Graylog
|
||||
**Поиск по конкретному серверу:**
|
||||
```sql
|
||||
source:"win-server1" AND "Domain Admins"
|
||||
```
|
||||
|
||||
**Анализ ошибок:**
|
||||
```sql
|
||||
_full_message:"ERROR" AND _environment:"production"
|
||||
```
|
||||
|
||||
**Дашборд в Grafana:**
|
||||
```sql
|
||||
SELECT
|
||||
host,
|
||||
COUNT_IF(_log LIKE '%CRITICAL%') AS criticals
|
||||
FROM graylog_messages
|
||||
WHERE timestamp > NOW() - INTERVAL 1 DAY
|
||||
GROUP BY host
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🛠️ Кастомизация
|
||||
**Добавление новых команд:**
|
||||
1. Для Windows:
|
||||
```yaml
|
||||
# commands.yaml
|
||||
- name: Check DNS Resolution
|
||||
command: nslookup example.com
|
||||
```
|
||||
|
||||
2. Для Linux:
|
||||
```bash
|
||||
# В collect_info.sh
|
||||
commands+=("lsof -i :80")
|
||||
```
|
||||
|
||||
**Настройка Graylog:**
|
||||
```yaml
|
||||
# defaults/main.yml
|
||||
graylog_host: log-collector.company.com
|
||||
graylog_port: 12201
|
||||
windows_log_dir: C:\Monitoring\Logs
|
||||
linux_log_dir: /var/log/monitoring
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ Решение проблем
|
||||
**Частые ошибки Windows:**
|
||||
```powershell
|
||||
# Разрешить WinRM:
|
||||
Enable-PSRemoting -Force
|
||||
New-NetFirewallRule -Name "WinRM-HTTP" -DisplayName "WinRM HTTP" -Protocol TCP -LocalPort 5985 -Action Allow
|
||||
```
|
||||
|
||||
**Ошибки подключения к Linux:**
|
||||
```bash
|
||||
sudo systemctl restart sshd
|
||||
sudo ssh-keygen -A
|
||||
```
|
||||
|
||||
**Проверка Graylog:**
|
||||
```bash
|
||||
# Проверка открытости порта
|
||||
nc -vz graylog.company.com 12201
|
||||
|
||||
# Тестовая отправка
|
||||
echo '{"version":"1.1","host":"test","short_message":"Test"}' | nc -u -w1 graylog.company.com 12201
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📈 Расширенные сценарии
|
||||
**Планирование регулярного сбора:**
|
||||
```yaml
|
||||
- name: Schedule Daily Collection
|
||||
win_scheduled_task:
|
||||
name: "Daily System Snapshot"
|
||||
actions:
|
||||
- path: "python"
|
||||
arguments: "C:\\Collector\\run_commands.py"
|
||||
triggers:
|
||||
- type: daily
|
||||
start_time: "02:30"
|
||||
```
|
||||
|
||||
**Безопасная обработка данных:**
|
||||
```python
|
||||
# Фильтрация чувствительных данных
|
||||
if "password" in command.lower():
|
||||
log_entry = "REDACTED: Sensitive command skipped"
|
||||
```
|
||||
|
||||
**Сжатие логов:**
|
||||
```python
|
||||
import zlib
|
||||
compressed = zlib.compress(log_entry.encode())
|
||||
sock.sendto(compressed, (GRAYLOG_HOST, GRAYLOG_PORT))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📚 Заключение
|
||||
Представленная система решает ключевую задачу DevOps - **автоматизированный сбор критической информации** с инфраструктуры. Благодаря интеграции с Ansible и Graylog, она обеспечивает:
|
||||
|
||||
- Единую точку сбора логов
|
||||
- Возможность оперативного анализа инцидентов
|
||||
- Автоматическое обнаружение аномалий
|
||||
- Исторический аудит изменений
|
||||
|
||||
**Полный код доступен на Git:**
|
||||
https://git.fipi.pro/Plata_Upravleniya_RF/commander.git
|
||||
|
||||
Для более простого исполнения есть `/old_script/` это раннее написанные версии скриптов с максимально простой структурой:
|
||||
## Старая версия
|
||||
|
||||
```
|
||||
old_scripts/
|
||||
├── run_commands.py # основной скрипт win
|
||||
├── script.sh # основной скрипт unix
|
||||
├── commands.yaml # конфигурация команд
|
||||
└── logs/ # автоматически создаётся, сюда пишутся логи
|
||||
```
|
||||
|
||||
Файл `commands.yaml` Можно добавлять любые команды. Главное — указать поле `name` и `command`.
|
||||
|
||||
### 🔍 Пример запуска
|
||||
|
||||
#### ** 1. Python версия скрипта для Windows-хостов**
|
||||
|
||||
```bash
|
||||
python run_commands.py
|
||||
```
|
||||
В консоли будет отображаться ход выполнения, а весь лог сохраняется в `logs/commands_*.log`.
|
||||
|
||||
---
|
||||
#### **2. Bash-версия скрипта для Linux-хостов**
|
||||
|
||||
> ❗ Не забудь `chmod +x script.sh`
|
||||
> Можно запускать из Ansible с `shell` или `script`.
|
||||
---
|
||||
298
src/content/blog/15-wifi-scan-snippit.md
Normal file
298
src/content/blog/15-wifi-scan-snippit.md
Normal file
@@ -0,0 +1,298 @@
|
||||
---
|
||||
title: 'Wi-Fi сканер на Python: консоль и WebSocket-интерфейс'
|
||||
summary: 'Примеры кода для сканирования Wi-Fi сетей с помощью Python и scapy: консольный сниффер, WebSocket-вывод в браузер и TUI-интерфейс. Практические советы по запуску и расширению.'
|
||||
date: '2025-11-18'
|
||||
draft: false
|
||||
tags:
|
||||
- wifi
|
||||
- python
|
||||
- scapy
|
||||
- безопасность
|
||||
- сниффер
|
||||
- мониторинг
|
||||
---
|
||||
Код для Wi-Fi сниффера на Python, использующего `scapy` и интерфейс в режиме мониторинга.
|
||||
### ✅ Скрипт `scanwf.py`
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
from scapy.all import *
|
||||
from scapy.layers.dot11 import Dot11, Dot11Elt
|
||||
|
||||
# Завершение по Ctrl+C
|
||||
def signal_handler(sig, frame):
|
||||
print("\n=================")
|
||||
print("Завершение работы по Ctrl+C")
|
||||
print("=================\n")
|
||||
sys.exit(0)
|
||||
|
||||
# Инструкция
|
||||
def usage():
|
||||
print("\nИспользование:")
|
||||
print("\tsudo python3 scanwf.py -i <интерфейс> [-p]\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Обработка пакетов
|
||||
def sniffpackets(packet):
|
||||
if packet.haslayer(Dot11):
|
||||
mac_src = packet.addr2
|
||||
mac_dst = packet.addr1
|
||||
bssid = packet.addr3
|
||||
ssid = ""
|
||||
|
||||
if packet.haslayer(Dot11Elt):
|
||||
ssid = packet[Dot11Elt].info.decode(errors="ignore")
|
||||
|
||||
if packet.type == 0 and packet.subtype == 8:
|
||||
print(f"[+] SSID: {ssid:30} | BSSID: {bssid} | SRC: {mac_src} | DST: {mac_dst}")
|
||||
|
||||
# Перевод интерфейса в режим мониторинга
|
||||
def setup_monitor(iface):
|
||||
print(f"[+] Перевод {iface} в режим мониторинга...")
|
||||
subprocess.call(["ifconfig", iface, "down"])
|
||||
subprocess.call(["iwconfig", iface, "mode", "monitor"])
|
||||
subprocess.call(["ifconfig", iface, "up"])
|
||||
return iface
|
||||
|
||||
# Проверка root
|
||||
def check_root():
|
||||
if os.geteuid() != 0:
|
||||
print("[!] Запуск возможен только от root")
|
||||
sys.exit(1)
|
||||
|
||||
# Показ интерфейсов
|
||||
def show_interfaces():
|
||||
print("[*] Доступные интерфейсы:\n")
|
||||
subprocess.call(["ifconfig"])
|
||||
|
||||
# Парсинг аргументов
|
||||
def parse_args():
|
||||
if "-p" in sys.argv:
|
||||
show_interfaces()
|
||||
sys.exit(0)
|
||||
if "-i" not in sys.argv:
|
||||
usage()
|
||||
idx = sys.argv.index("-i")
|
||||
try:
|
||||
iface = sys.argv[idx + 1]
|
||||
except IndexError:
|
||||
usage()
|
||||
return iface
|
||||
|
||||
# Главная
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
check_root()
|
||||
iface = parse_args()
|
||||
setup_monitor(iface)
|
||||
|
||||
print(f"[+] Начинаем прослушку интерфейса {iface}...\nНажмите Ctrl+C для выхода.\n")
|
||||
sniff(iface=iface, prn=sniffpackets, store=0)
|
||||
```
|
||||
|
||||
---
|
||||
### 🔧 Зависимости
|
||||
|
||||
Убедись, что установлен `scapy`:
|
||||
```bash
|
||||
pip install scapy
|
||||
```
|
||||
---
|
||||
|
||||
### 🔐 Пример запуска
|
||||
|
||||
```bash
|
||||
sudo python3 scanwf.py -i wlan0
|
||||
```
|
||||
Показать интерфейсы:
|
||||
```bash
|
||||
python3 scanwf.py -p
|
||||
```
|
||||
---
|
||||
|
||||
## ✅ Вариант 1: WebSocket-интерфейс для отображения в браузере
|
||||
|
||||
Позволяет в реальном времени выводить данные в браузер через WebSocket.
|
||||
|
||||
### 📦 Зависимости:
|
||||
|
||||
```bash
|
||||
pip install scapy flask flask-socketio eventlet
|
||||
```
|
||||
|
||||
### 📁 Структура проекта:
|
||||
|
||||
```
|
||||
wifi_sniffer_ws/
|
||||
├── app.py # сервер Flask + WebSocket
|
||||
├── sniffer.py # логика сниффера
|
||||
└── templates/
|
||||
└── index.html # браузерный интерфейс
|
||||
```
|
||||
|
||||
### 🔧 `sniffer.py` — сниффер как поток
|
||||
|
||||
```python
|
||||
from scapy.all import *
|
||||
from scapy.layers.dot11 import Dot11, Dot11Elt, RadioTap
|
||||
from datetime import datetime
|
||||
|
||||
def sniff_wifi(interface, callback):
|
||||
def handle_packet(packet):
|
||||
if packet.haslayer(Dot11) and packet.type == 0 and packet.subtype == 8:
|
||||
ssid = packet[Dot11Elt].info.decode(errors="ignore") if packet.haslayer(Dot11Elt) else "<unknown>"
|
||||
bssid = packet.addr3
|
||||
signal = packet.dBm_AntSignal if hasattr(packet, 'dBm_AntSignal') else "?"
|
||||
ch = "?"
|
||||
el = packet.getlayer(Dot11Elt)
|
||||
while el:
|
||||
if el.ID == 3:
|
||||
ch = ord(el.info)
|
||||
break
|
||||
el = el.payload.getlayer(Dot11Elt)
|
||||
callback({
|
||||
"time": datetime.now().strftime("%H:%M:%S"),
|
||||
"ssid": ssid,
|
||||
"bssid": bssid,
|
||||
"channel": ch,
|
||||
"signal": signal
|
||||
})
|
||||
sniff(iface=interface, prn=handle_packet, store=0)
|
||||
```
|
||||
|
||||
### 🚀 `app.py` — Flask + WebSocket-сервер
|
||||
|
||||
```python
|
||||
from flask import Flask, render_template
|
||||
from flask_socketio import SocketIO
|
||||
from threading import Thread
|
||||
from sniffer import sniff_wifi
|
||||
|
||||
INTERFACE = "wlan0mon" # заменить на нужный
|
||||
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
def emit_packet(packet):
|
||||
socketio.emit('packet', packet)
|
||||
|
||||
def start_sniffer():
|
||||
sniff_wifi(INTERFACE, emit_packet)
|
||||
|
||||
if __name__ == '__main__':
|
||||
thread = Thread(target=start_sniffer)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
socketio.run(app, host='0.0.0.0', port=5000)
|
||||
```
|
||||
|
||||
### 🌐 `templates/index.html`
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wi-Fi Sniffer</title>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>📡 Обнаруженные сети:</h2>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr><th>Time</th><th>SSID</th><th>BSSID</th><th>Channel</th><th>Signal</th></tr>
|
||||
</thead>
|
||||
<tbody id="wifiTable"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const socket = io();
|
||||
socket.on('packet', data => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `<td>${data.time}</td><td>${data.ssid}</td><td>${data.bssid}</td><td>${data.channel}</td><td>${data.signal}</td>`;
|
||||
document.getElementById('wifiTable').appendChild(row);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Вариант 2: Консольный TUI (Text User Interface) на `Textual`
|
||||
|
||||
Подходит для терминала, удобно для SSH или серверов без GUI.
|
||||
|
||||
### 📦 Установка
|
||||
|
||||
```bash
|
||||
pip install scapy textual rich
|
||||
```
|
||||
|
||||
### 🐍 `sniffer_tui.py` — пример TUI-интерфейса
|
||||
|
||||
```python
|
||||
from scapy.all import sniff, Dot11, Dot11Elt
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Header, Footer, DataTable
|
||||
from datetime import datetime
|
||||
|
||||
class WifiTUI(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
self.table = DataTable()
|
||||
self.table.add_columns("Time", "SSID", "BSSID", "Channel", "Signal")
|
||||
yield self.table
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.seen = set()
|
||||
sniff(iface="wlan0mon", prn=self.handle_packet, store=0)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
if packet.haslayer(Dot11) and packet.subtype == 8:
|
||||
ssid = packet[Dot11Elt].info.decode(errors="ignore") if packet.haslayer(Dot11Elt) else "<unknown>"
|
||||
bssid = packet.addr3
|
||||
signal = packet.dBm_AntSignal if hasattr(packet, 'dBm_AntSignal') else "?"
|
||||
channel = "?"
|
||||
el = packet.getlayer(Dot11Elt)
|
||||
while el:
|
||||
if el.ID == 3:
|
||||
channel = ord(el.info)
|
||||
break
|
||||
el = el.payload.getlayer(Dot11Elt)
|
||||
key = (ssid, bssid)
|
||||
if key not in self.seen:
|
||||
self.seen.add(key)
|
||||
self.table.add_row(
|
||||
datetime.now().strftime("%H:%M:%S"),
|
||||
ssid,
|
||||
bssid,
|
||||
str(channel),
|
||||
str(signal)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
WifiTUI().run()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Расширения
|
||||
|
||||
|Возможность|Поддержка|
|
||||
|---|---|
|
||||
|📡 WebSocket в браузере|✅|
|
||||
|🖥 TUI в терминале|✅ `Textual`|
|
||||
|🔒 Graylog интеграция|🔜 (через GELF или syslog)|
|
||||
|📦 Ansible-интеграция|🔜 (в виде роли)|
|
||||
|
||||
---
|
||||
234
src/content/blog/16-scan-user-folder.md
Normal file
234
src/content/blog/16-scan-user-folder.md
Normal file
@@ -0,0 +1,234 @@
|
||||
---
|
||||
title: 'Сканирование пользовательских папок в корпоративной сети: автоматизация аудита файлов'
|
||||
summary: 'Практическое руководство по созданию Python-скрипта для поиска файлов с определёнными расширениями на рабочих станциях сотрудников через административные шары Windows. Описание архитектуры, безопасности и запуска.'
|
||||
date: '2025-11-18'
|
||||
draft: false
|
||||
tags:
|
||||
- аудит
|
||||
- безопасность
|
||||
- python
|
||||
- windows
|
||||
- сканирование
|
||||
- администрирование
|
||||
---
|
||||
# Документация для сканера расширений файлов
|
||||
|
||||
- Внутри корпаративной сети пользователи частенько хранят что то не нужно, давйте выясним чем сотруднок занимаеться на рабочем месте и каким файлам пользуеться, а после проанализируем что там есть у него на ПК.
|
||||
## Обзор
|
||||
|
||||
Скрипт представляет собой инструмент для сканирования сетевых компьютеров с целью поиска файлов с определенными расширениями в пользовательских директориях.
|
||||
## Основные возможности
|
||||
|
||||
* **Сетевое сканирование**: Подключение к удаленным компьютерам через административные шары (C\$)
|
||||
* **Поиск файлов**: Обнаружение файлов с заданными расширениями (.exe, .url, .lnk)
|
||||
* **Фильтрация**: Исключение системных пользователей и нежелательных файлов
|
||||
* **Логирование**: Детальное ведение журнала операций
|
||||
* **Экспорт результатов**: Сохранение данных в CSV формате
|
||||
## Конфигурация
|
||||
### Основные параметры
|
||||
|
||||
```python
|
||||
EXTENSIONS = (".exe", ".url", ".lnk") # Искомые расширения файлов
|
||||
EXCLUDE_USERS = ("All Users", "Default", "Default User", "Public", "desktop.ini", "Administrator")
|
||||
SEARCH_DIRS = ("Desktop", "Downloads", "Documents") # Сканируемые директории
|
||||
EXCLUDE_FILENAME_KEYWORDS = ("putty", "telegram", "vmware", "anydesk") # Исключаемые ключевые слова
|
||||
```
|
||||
### Файлы конфигурации
|
||||
|
||||
* `hosts.txt` - список хостов для сканирования (по одному на строку)
|
||||
* `found_files.csv` - результаты сканирования
|
||||
* `failed_hosts.csv` - журнал неудачных подключений
|
||||
* `scanner.log` - детальный лог операций
|
||||
## Архитектура
|
||||
### Основные функции
|
||||
#### `read_hosts(file_path: str) -> List[str]`
|
||||
Читает список хостов из файла, игнорируя комментарии (строки, начинающиеся с `#`)
|
||||
#### `is_excluded_file(file: str) -> bool`
|
||||
Проверяет, должен ли файл быть исключен из результатов на основе:
|
||||
* Имени файла (`desktop.ini`, `Thumbs.db`)
|
||||
* Ключевых слов в имени файла
|
||||
#### `wait_for_access(path: str, timeout: int = 3) -> bool`
|
||||
Проверяет доступность сетевого пути с таймаутом
|
||||
#### `scan_host(host: str) -> List[List[str]]`
|
||||
Основная функция сканирования:
|
||||
1. Подключается к `\\{host}\c$\Users`
|
||||
2. Перебирает пользовательские директории
|
||||
3. Сканирует указанные поддиректории
|
||||
4. Фильтрует файлы по расширениям и исключениям
|
||||
5. Возвращает список найденных файлов
|
||||
#### `write_results(rows: List[List[str]], header: List[str], file_path: str)`
|
||||
Записывает результаты в CSV файл с разделителем `;`
|
||||
#### `log_failed_host(host: str, reason: str)`
|
||||
Записывает информацию о неудачных подключениях
|
||||
## Структура данных
|
||||
### Формат результатов CSV
|
||||
|
||||
| Поле | Описание |
|
||||
| --------- | ------------------------- |
|
||||
| PC Name | Имя компьютера |
|
||||
| User | Имя пользователя |
|
||||
| Extension | Расширение файла |
|
||||
| File Path | Полный путь к файлу |
|
||||
| Scan Date | Дата и время сканирования |
|
||||
### Формат журнала ошибок
|
||||
| Поле | Описание |
|
||||
| --------- | -------------- |
|
||||
| Host | Имя хоста |
|
||||
| Reason | Причина ошибки |
|
||||
| Timestamp | Время ошибки |
|
||||
## Безопасность и требования
|
||||
### Системные требования
|
||||
|
||||
* **ОС**: Windows (для доступа к административным шарам)
|
||||
* **Права**: Административные права на целевых машинах
|
||||
* **Сеть**: Доступ к портам SMB (445/tcp)
|
||||
* **Python**: 3.6+ с модулями `os`, `csv`, `pathlib`, `logging`, `datetime`, `typing`
|
||||
### Рекомендации по безопасности
|
||||
|
||||
1. **Ограничение доступа**: Запускать только с учетных записей с минимально необходимыми правами
|
||||
2. **Сетевая сегментация**: Использовать в изолированных сетевых сегментах
|
||||
3. **Аудит**: Регулярно проверять логи на предмет подозрительной активности
|
||||
4. **Шифрование логов**: При необходимости добавьте шифрование логов или безопасную транспортировку логов на удалённый сервер
|
||||
## Развёртывание и запуск
|
||||
|
||||
1. Установите Python 3.8+:
|
||||
- на MACOS:
|
||||
```bash
|
||||
brew install python # или иным способом в зависимости от ОС
|
||||
```
|
||||
|
||||
- на Windows:
|
||||
```bash
|
||||
choco install python # или иным способом в зависимости от ОС
|
||||
```
|
||||
|
||||
- на Linux:
|
||||
```bash
|
||||
sudo apt-get install python3 # или иным способом в зависимости от ОС
|
||||
```
|
||||
|
||||
2. Подготовьте файл `hosts.txt`:
|
||||
```text
|
||||
PC-NAME-1
|
||||
PC-NAME-2
|
||||
```
|
||||
|
||||
3. Запустите скрипт:
|
||||
```bash
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
```python
|
||||
import os
|
||||
import csv
|
||||
import pathlib
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
# Конфигурация
|
||||
EXTENSIONS = (".exe", ".url", ".lnk")
|
||||
EXCLUDE_USERS = ("All Users", "Default", "Default User", "Public", "desktop.ini", "Administrator")
|
||||
SEARCH_DIRS = ("Desktop", "Downloads", "Documents")
|
||||
EXCLUDE_FILENAME_KEYWORDS = ("putty", "telegram", "vmware", "anydesk") # слова в имени файла, а не расширения
|
||||
EXCLUDE_FILENAMES = ("desktop.ini", "Thumbs.db")
|
||||
OUTPUT_FILE = "found_files.csv"
|
||||
FAILED_FILE = "failed_hosts.csv"
|
||||
HOSTS_FILE = "hosts.txt"
|
||||
LOG_FILE = "scanner.log"
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
filename=LOG_FILE,
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
def read_hosts(file_path: str) -> List[str]:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
return [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
|
||||
|
||||
def is_excluded_file(file: str) -> bool:
|
||||
file_lower = file.lower()
|
||||
return (
|
||||
file in EXCLUDE_FILENAMES
|
||||
or any(keyword in file_lower for keyword in EXCLUDE_FILENAME_KEYWORDS)
|
||||
)
|
||||
|
||||
def wait_for_access(path: str, timeout: int = 3) -> bool:
|
||||
try:
|
||||
return os.path.exists(path)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def scan_host(host: str) -> List[List[str]]:
|
||||
results = []
|
||||
base_users_path = f"\\\\{host}\\c$\\Users"
|
||||
try:
|
||||
if not wait_for_access(base_users_path):
|
||||
raise Exception("Недоступен путь к Users")
|
||||
logging.info(f"Подключение к: {host}")
|
||||
users = os.listdir(base_users_path)
|
||||
for user in users:
|
||||
if user in EXCLUDE_USERS:
|
||||
continue
|
||||
user_base = os.path.join(base_users_path, user)
|
||||
for sub_dir in SEARCH_DIRS:
|
||||
target_dir = os.path.join(user_base, sub_dir)
|
||||
if not os.path.exists(target_dir):
|
||||
continue
|
||||
for root, _, files in os.walk(target_dir):
|
||||
for file in files:
|
||||
if is_excluded_file(file):
|
||||
continue
|
||||
if not file.lower().endswith(EXTENSIONS):
|
||||
continue
|
||||
full_path = os.path.join(root, file)
|
||||
ext = pathlib.Path(file).suffix
|
||||
logging.info(f"[{host}] Обнаружен файл: {full_path}")
|
||||
results.append([
|
||||
host, user, ext, full_path, datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
])
|
||||
except Exception as e:
|
||||
log_failed_host(host, str(e))
|
||||
logging.error(f"Ошибка при сканировании {host}: {e}")
|
||||
return results
|
||||
|
||||
def write_results(rows: List[List[str]], header: List[str], file_path: str):
|
||||
file_exists = os.path.exists(file_path)
|
||||
with open(file_path, mode="a", newline='', encoding="utf-8") as f:
|
||||
writer = csv.writer(f, delimiter=";")
|
||||
if not file_exists:
|
||||
writer.writerow(header)
|
||||
writer.writerows(rows)
|
||||
logging.info(f"Записано {len(rows)} строк в {file_path}")
|
||||
|
||||
def log_failed_host(host: str, reason: str):
|
||||
with open(FAILED_FILE, mode="a", newline='', encoding="utf-8") as f:
|
||||
writer = csv.writer(f, delimiter=";")
|
||||
writer.writerow([host, reason, datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
|
||||
|
||||
def main():
|
||||
logging.info("=== Запуск сканирования ===")
|
||||
hosts = read_hosts(HOSTS_FILE)
|
||||
header = ["PC Name", "User", "Extension", "File Path", "Scan Date"]
|
||||
for host in hosts:
|
||||
print(f"Scanning: {host}")
|
||||
results = scan_host(host)
|
||||
if results:
|
||||
write_results(results, header, OUTPUT_FILE)
|
||||
else:
|
||||
logging.warning(f"Нет результатов или ошибка доступа: {host}")
|
||||
logging.info("=== Завершение сканирования ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## Полезные ресурсы
|
||||
|
||||
1. [Официальная документация Python](https://docs.python.org/3/)
|
||||
2. [Работа с путями в Python](https://realpython.com/python-pathlib/)
|
||||
3. [Примеры работы с CSV](https://pythonexamples.org/python-csv/)
|
||||
4. [Сетевые пути Windows](https://learn.microsoft.com/ru-ru/windows/win32/fileio/naming-a-file)
|
||||
Reference in New Issue
Block a user