مقدمه
بعد از اتفاقی که دیروز برای TAP30 اتفاق افتاد، تصمیم گرفتم برم سراغ دیتابیسای مونگوی خودم و اونارو بررسی کرده و کردنشیال هاشون رو ست کنم. (البته نگران نباشین، دیتاهای موجود روی دیتابیس من اصلا چیز قابل داری نبوده و همهش دیتای تست واسه کارای تستیه 😬)
اگه نمیدونین چه اتفاقی رخ داده، خب منم حوصلهشو ندارم براتون توضیح بدم، فقط بگم که ماجرا از لو رفتن نزدیک به ۶ میلیون دیتاسِت از یکی از دیتابیسهایی در ایران بوده که پورتش باز بوده و موتورهای جستجوگر دیتابیس اون رو ایندکس کرده بودن که آقای باب دیاچنکو در این توییت ماجرای پیدا کردن اونارو توضیح داده:
More than 6.7M Iranian IDs exposed: names, SSN, phone numbers. Reported to Iranian CERT, but if you have a better contact please let me know. pic.twitter.com/seZA4O7Aeu
— Bob Diachenko (@MayhemDayOne) April 18, 2019
اطلاعات کامل راجع به این قضیه رو از این رشته توییت نیما اکبر پور هم میتونید دنبال کنید:
اطلاعات بیش از ۶.۷ میلیون شناسه ایرانی شامل نام، شماره ملی و شماره تلفن منتشر شده. https://t.co/pIm7R4zNrO
— Nima Akbarpour نیما (@nima) April 18, 2019
از خود ماجرا که بگذریم بهتره بریم سراغ بحث خودمون. اولین اشتباهی که که سهوی هم بوده (انشالله)، قطعا این بوده که پورت مربوط به دیتابیس مورد نظر رو همینجوری باز گذاشته بودن! 😑
اولین گام اینه که پورت دیتابیستون رو قبل از هرچیزی ببندین لطفا. (مگر اینکه زمان خاصی بخواین روی کلاینت GUI دسکتاپی چیزی بخواین بهش وصل بشین، فقط تو همون بازهی زمانی بزارین باز بمونه). پس:
گام اول: بستن پورتهای دیتابیس
برای اینکر فقط کافیه فایروال سرور لینوکسیتون رو فعال کرده و با دستور زیر پورت مورد نظر رو ببندین:
sudo ufw enable
sudo ufw deny 27017 #or any other port
اگه خواستین پورت رو برای دقایقی باز کنین، فقط کافیه اجازه دسترسی بدین بهش با همین نیم خط دستور:
sudo ufw allow 27017
گام دوم: ایجاد سطح دسترسی با احراز هویت به دیتابیس
کار دومی که باید بکنین اینه که برای دیتابیستون در سطوح مختلف یوزر
با نقشهای مرطبت و لازمه ایجاد کنید. این کارو در چند مرحله انجام باهم انجامش میدم. قبل از شروع بگم که این آموزش رو بر اساس راه اندازی یک دیتابیس Mongo روی Docker قراره انجام بدیم:
مرحله اول: راه اندازی کانترینر Mongo بر مبنای auth
اگر کانتینر مونگو رو با استفاده از تگ --auth
راه اندازه کنید، این امکان رو میده به مونگو که خودشو آماده کنه برای سکیور شدن. پس، از دستور زیر استفاده میکنیم:
docker run -d --name CONTAINER_NAME -v VOLUME:/data/db -p PORT:27017 mongo --auth
و بعد، با دستوری پایین وارد شل mongo میشم که دستورات بعدی رو وارد کنیم:
docker exec -it CONTAINER_NAME mongo admin
مرحله دوم: ایجاد کاربر admin
در این مرحله باید دیتابیسمون یه یوزر ادمین تعریف کنیم که دسترسی ادمینی به کل دیتابیس داره. اول از همه دیتابیس ادمین رو use میکنیم:
use admin
که بهمون جواب میده:
و بعد بهش میگیم که یه یوزر admin با نقش userAdminAnyDatabase
روی دیتابیس admin
برامون ایجاد کن:
db.createUser(
{
user: "admin",
pwd: "PASS",
roles:[
{
role: "userAdminAnyDatabase",
db: "admin"
}
]
}
);
و حتما جواب میده که:
که اوکی من یوزر ادمین رو براتون ساختم. بعدش باید بریم تست کنیم که ما میتونیم با این یوزر ادمینی که تعریف کردیم به سیستم وارد شیم. برای این کار کافیه از دستور db.auth
استفاده کنیم:
db.auth("admin","PASS")
و اگه مشخصات رو درست وارد کرده باشیم، اونم در جواب باید بگه 1
که یعنی بفرمایین تو سرور من 😈:
بعد از اینکه اینکارارو انجام دادیم. با دستور exit
میایم بیرون از شل، و یه بار کانتیرنر رو ری استارت میکنیم تا با یوزر ادمین خودشو سینک کنه و بیاد بالا. دوباره بعد دوباره با همون دستوری که در مرحله اول گفتم وارد شل مونگو میشیم تا کارای بعدی رو انجام بدیم.
مرحله سه: ایجاد کاربر با دسترسی محدود روی یک دیتابیس خاص
حالا وقتشه که یه کاربر با دسترسی Read.write
برای اون دیتابیس مورد نظرمون ایجاد کنیم.
ابتدا ادمین رو use کنیم:
use admin
بعد با ادمین خودمون رو احراز هویت کنیم برای مونگو:
db.auth("admin","PASS")
حالا باید دیتابیس مورد نظرمون رو use کنم (مهم نیست که دیتابیس قبلا بوده یا قراره بعدا ساخته شه، فقط مطئن شین اسم دیتابیس مورد نظر رو درست وارد می کنین):
use my_database
دقیقا همینجا باید یه کاربر بسازیم برای این دیتابیس که به این دیتایس دسترسی داره و نقش اون هم در همون حد Read/Write
هست. برای اینکار مثل تعریف یوزرِ ادمین عمل میکنیم:
db.createUser(
{
user: "USE_NAME",
pwd: "USER_PASS",
roles:[
{
role: "readWrite", #pay attention to role
db: "DATABASE_NAME"
}
]
}
);
و جواب شل هم به ما این خواهد بود عزیزان:
و بعدش هم یه بار تست میکنیم تا مطسمئن شیم که یوزرمون به درستی مشخصاتش ست شده:
db.auth("my_user", "my_user_pass")
و نتیجه موفقت آمیز احراز هویتمون:
خب حالا یه یوزر ساختیم که به دیتابیس my_database
تنها و تنها دسترسی Read/Write
داره، اون هم با کردنشیالی که براش ست کردیم.
مرحله چهار: وصل شدن به دیتابیس mongo با استفاده از کاربر تعریف شده
از حالا به بعد حتی اگه پورت هم باز باشه، برای وصل شدن به دیتابیس باید از کردنشیال مورد نظر استفاده کرد، وگرنه حق دسترسی به دیتابیس رو ندارید.
برای تست این موضوع من از لایبرری mongoose استفاده کردم.
خب توی پروژه مورد نظر بعد از install کردن لایبرری توسط npm
یا yarn
اون رو به پروژه ایمپورت کنید:
// Using Node.js `require()`
const mongoose = require('mongoose');
// Using ES6 imports
import mongoose from 'mongoose';
و بعدش باید برای وصل شدن به دیتابیس با استفاده از الگوی زیر از متد mongo.connect استفاده کنید:
'mongodb://$USERNAME:$PASSWORD@localhost:$PORT/$DB_NAME?authMechanism=$HASH_MECHANISM'
یا:
('mongodb://localhost:$PORT/$DB_NAME?authMechanism=$HASH_MECHANISM', {
username: "$USERNAME",
password: "$PASSWORD"
})
که من خودم حالت اول (که بهش میگن string
مود) رو ترجیح میدم.
حتما حتما حتما، هواستون باشه که مکانیسم هش کردن مشخصات auth رو باید ست کنین. در حالت پیشفرض از مکانیسم SCRAM-SHA-1
استفاده میشه مگه اینکه توی تنظیمات مونگوتون تغییراتی ایجاد کرده باشین. اگه اینکار رو نکنید قطعا با خطای معروف زیر برخورد میکنید:😱
MongoError: Authentication failed.
در نهایت کدی که مارو وصل میکنه به دیتابیس مونگومون اینه:
mongoose.Promise = global.Promise;
async function run()
{
await mongoose.connect('mongodb://my_user:my_user_pass@localhost:27017/my_db?authMechanism=SCRAM-SHA-1',
{
useNewUrlParser: true,
keepAlive: true,
connectTimeoutMS: 15000, // Give up initial connection after 10 seconds
socketTimeoutMS: 15000, // Close sockets after 45 seconds of inactivity
});
const Test = mongoose.model('Test', new mongoose.Schema({ name: String }));
const doc = await Test.create({ name: 'Val' });
console.log('Mongo connection Result: '+doc);
}
run().catch(error => console.error(error.stack));
و فقط کافیه کد اجرا بشه و ما با دیدن پیام زیر متوجه میشیم که اتصال امن به دیتابیسمون با موفقیت انجام شده (البته بدون یوزر پسورد هم میتونید تست کنید تا مطمئن بشید که بدون کردنشیال امکان اتصال بهش وجود نداره):
و تِمااااام🧐
راه سادهتر
ممکنه بپرسین راه سادهتری هم هست؟
باید بگم که راههای مختلفی رو تست کردم، ولی بهترین و مطمئنترین راه (که فارغ از ورژن مونگو داکر و مونگوز باشه) همین راهی بود که بهتون گفتم. منتهی خود مونگو یه راه دیگه برای تعریف کاربر در هنگام اجرا گفته که متاسفانه من هروقت اجرا کردم نتونستم به دیتابیس وصل شم و مدام خطای uthenticaiton failed
میگرفتم. خب، راهی که مونگو پیشنهاد میده اینه:
$ docker run -d --network some-network --name some-mongo \
-e MONGO_INITDB_ROOT_USERNAME=mongoadmin \
-e MONGO_INITDB_ROOT_PASSWORD=secret \
mongo
$ docker run -it --rm --network some-network mongo \
mongo --host some-mongo \
-u mongoadmin \
-p secret \
--authenticationDatabase admin \
some-db
> db.getName();
some-db
ممنون میشم تست کنید و اگه به جوابی رسیدید تو کامنتا نتیجهتون رو اعلام کنید.
منابع برای مطالعه بیشتر
اگه دوس دارین در مورد امنیت مونگو و حفظ اطلاعاتتون بیشتر بدونید تا میتونید لینکهای زیر رو مطالعه کنید:
- پابلیشر mongoaudit کلی مقاله خوب و مفید درباره امنیت مونگو داره
- امنیت در دیتابیس مونگو هست: لینک ۱، لینک ۲، لینک ۳، چک لیست امنیت مونگو که باید رعایت بشه
- سطوح کاربری و اصولا کار کردن با کاربر: لینک ا، لینک ۲
- امنیت در مونگوی داکر: لینک ۱
- فایروالها در لینکوس: لینک۱، لینک ۲
جمع بندی
این پست جمع بندی خاصی نداره، جز اینکه هواستون باشه پورتهاتون رو بی دلیل باز نگذارید 🤦♂️ و دیتابیس با اطلاعات مهم رو روی تنظیمات پیشفرض بالا نیارین! حتما موارد امنیتی رو رعایت کرده و کردنشیالها رو اعمال کنید.
لطفا اگه راه بهتر یا روشهای دیگهای بلدین، در کامنتها با بقیه به اشتراک بگذارین 🙏
اگه سوال یا موردی بود، مثل همیشه در توییتر یا در تلگرام میتونیم باهم در ارتباط باشیم. 😉🍷