Обкладинка
Цей день почався спокійно, поки не сталося лихо. Ми отримали сповіщення про DDoS-атаки та атаки методом підбору паролів з випадкових IP-адрес ботів. Наша команда швидко мобілізувалася, щоб нейтралізувати атаки. І ось коли ми подумали, що ситуація під контролем, з’явилося ще одне тривожне повідомлення: наша база даних Redis була на 80% заповнена! Це стало шоком, адже зазвичай розмір нашої Redis БД не перевищує 20 МБ.
Фаза розслідування: Містика з Redis
Перед тим, як звернути увагу на проблему з Redis, ми зосередилися на припиненні DDoS-атак та атак методом підбору паролів. Ми реалізували обмеження запитів для певних кінцевих точок за допомогою Cloudflare.
Тепер давайте розглянемо, що ми зберігаємо в Redis. Ми використовуємо його для керування сесіями користувачів у нашому Node.js додатку з Passport.js. Ось приклад того, як виглядає сесія:
{
"cookie": {
"originalMaxAge": number,
"expires": "date",
"secure": true,
"httpOnly": false,
"domain": "domain",
"path": "/",
"sameSite": false
},
"passport": {
"user": // Actual user data here
}
}
Маючи це на увазі, ми почали розслідування. Я запитав усі дані в Redis, щоб перевірити дійсність сесій. На моє здивування, лише 10% сесій містили валідні дані користувачів. Інші були недійсними, без ключа user.
Код, який розкриває секрет: Чому так багато недійсних сесій?
Я заглибився в те, як генеруються ці недійсні сесії, і виявив, що за замовчуванням express-session створює сесію для кожного запиту, який не має приєднаного cookie (детальніше можна дізнатися тут). Під час DDoS-атаки це призводило до того, що для кожного запиту створювалася нова сесія, яка потім зберігалася в Redis.
Щоб виправити це, ми встановили опцію saveUninitialized: false
:
const session = require('express-session');
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: { secure: true }
}));
Виправлення проблеми
Після внесення зміни в код я написав скрипт для видалення недійсних сесій з Redis. Ми сподівалися, що це вирішить проблему, але Redis продовжував заповнюватися з тривожною швидкістю.
Розслідування триває
Попри початкове виправлення, ми помітили, що база даних Redis все ще швидко зростала. Щось було не так.
Я заглибився в код і виявив, що ми використовуємо пакет flash, який використовується для передачі повідомлень між серверами. При подальшому вивченні я з’ясував, що при доступі до повідомлень:
const { msg } = req.flash();
Пакет призначає порожній об'єкт для req.session.flash
, якщо такого не існує:
var msgs = this.session.flash = this.session.flash || {};
Ця зміна req.session
спровокувала express-session на збереження сесії в Redis. Функціональність flash використовувалась на публічних кінцевих точках, що призводило до того, що сесії користувачів залишалися порожніми, і з кожним запитом додавалася нова сесія.
Шлях до вирішення
Щоб вирішити проблему, я змінив код, щоб знищувати сесію після того, як повідомлення буде прочитано, як показано нижче:
const { msg } = req.flash();
req.session.destroy();
І так, подорож детектива завершилася.
Нарешті, якщо стаття була корисною, будь ласка, аплодуйте 👏 та підписуйтеся, дякую!
Перекладено з: Sherlock Holmes: The Case Of Redis Overload During a DDoS Attack