در برنامه های سرور یکی از مهمترین شاخص ها امنیت است. این یکی از انواع نیازهای غیر عملکردی است . امنیت مولفه های زیادی دارد. البته، برای پوشش کامل تمام آن اصول و اقدامات محافظتی شناخته شده، باید بیش از یک مقاله بنویسید، بنابراین بیایید روی مهمترین آنها تمرکز کنیم. فردی که در این مبحث به خوبی مسلط باشد، میتواند تمامی فرآیندها را راهاندازی کند و اطمینان حاصل کند که حفرههای امنیتی جدیدی ایجاد نمیکنند، در هر تیمی مورد نیاز خواهد بود. البته، نباید فکر کنید که اگر این روش ها را دنبال کنید، برنامه کاملاً ایمن خواهد بود. نه! اما قطعا با آنها امن تر خواهد بود. برو
1. تامین امنیت در سطح زبان جاوا
اول از همه، امنیت در جاوا درست از سطح ویژگی زبان شروع می شود. اگر اصلاح کننده های دسترسی وجود نداشت، این کاری است که ما انجام می دادیم؟... هرج و مرج، نه کمتر. یک زبان برنامه نویسی به ما کمک می کند کد ایمن بنویسیم و همچنین از بسیاری از ویژگی های امنیتی ضمنی استفاده کنیم:- تایپ قوی جاوا یک زبان تایپ ایستا است که توانایی تشخیص خطاهای نوع را در زمان اجرا فراهم می کند.
- اصلاح کننده های دسترسی به لطف آنها، می توانیم دسترسی به کلاس ها، متدها و فیلدهای کلاس را به روشی که نیاز داریم پیکربندی کنیم.
- مدیریت خودکار حافظه برای این منظور، ما (جاوائیست ها ؛)) Garbage Collector را داریم که شما را از پیکربندی دستی رها می کند. بله، گاهی اوقات مشکلاتی پیش می آید.
- بررسی بایت کد: جاوا به بایت کد کامپایل می شود که قبل از اجرای آن توسط زمان اجرا بررسی می شود .
- از سریال سازی کلاس های حساس به امنیت خودداری کنید. در این حالت، می توانید رابط کلاس را از فایل سریالی دریافت کنید، البته به داده هایی که در حال سریال سازی هستند اشاره نمی کنیم.
- سعی کنید از کلاس های داده قابل تغییر اجتناب کنید. این همه مزایای کلاس های تغییرناپذیر (به عنوان مثال ایمنی نخ) را به همراه دارد. اگر یک شیء قابل تغییر وجود داشته باشد، ممکن است منجر به رفتار غیرمنتظره شود.
- از اشیاء قابل تغییر برگشتی کپی تهیه کنید. اگر یک متد یک مرجع به یک شیء داخلی قابل تغییر برگرداند، کد مشتری می تواند وضعیت داخلی شی را تغییر دهد.
- و غیره…
2. آسیب پذیری تزریق SQL را از بین ببرید
آسیب پذیری منحصر به فرد منحصر به فرد بودن آن در این است که هم یکی از معروف ترین و هم یکی از رایج ترین آسیب پذیری هاست. اگر به موضوع امنیت علاقه ای ندارید، پس از آن اطلاعی نخواهید داشت. تزریق SQL چیست؟ این یک حمله به پایگاه داده با تزریق کد SQL اضافی در جایی است که انتظار نمی رود. فرض کنید روشی داریم که برای پرس و جو از پایگاه داده مقداری پارامتر نیاز دارد. مثلا نام کاربری. کد دارای آسیب پذیری چیزی شبیه به این خواهد بود:// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
// Создается связь с базой данных
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Пишем sql request в базу данных с нашим firstName
String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
// выполняем request
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(query);
// при помощи mapToUsers переводит ResultSet в коллекцию юзеров.
return mapToUsers(result);
}
private List<User> mapToUsers(ResultSet resultSet) {
//переводит в коллекцию юзеров
}
در این مثال کوئری sql از قبل در یک خط جداگانه آماده شده است. به نظر می رسد که مشکل چیست، درست است؟ شاید مشکل این باشد که استفاده از آن بهتر است String.format
؟ نه؟ بعدش چی شد؟ بیایید خودمان را جای یک آزمایشگر بگذاریم و به این فکر کنیم که چه چیزی می تواند در ارزش منتقل شود firstName
. مثلا:
- شما می توانید آنچه مورد انتظار است - نام کاربری - را منتقل کنید. سپس پایگاه داده همه کاربران را با آن نام برمی گرداند.
- می توانید یک رشته خالی ارسال کنید: سپس همه کاربران برگردانده می شوند.
- یا می توانید موارد زیر را پاس کنید: "''; کاربران جدول را رها کنید؛”. و در اینجا مشکلات بزرگتری وجود خواهد داشت. این کوئری جدول را از پایگاه داده حذف می کند. با تمام داده ها هر کس.
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
// Создается связь с базой данных
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Создаем параметризированный request.
String query = "SELECT * FROM USERS WHERE firstName = ?";
// Создаем подготовленный стейтмент с параметризованным requestом
PreparedStatement statement = connection.prepareStatement(query);
// Передаем meaning параметра
statement.setString(1, firstName);
// выполняем request
ResultSet result = statement.executeQuery(query);
// при помощи mapToUsers переводим ResultSet в коллекцию юзеров.
return mapToUsers(result);
}
private List<User> mapToUsers(ResultSet resultSet) {
//переводим в коллекцию юзеров
}
به این ترتیب از این آسیب پذیری جلوگیری می شود. برای کسانی که می خواهند عمیق تر از این مقاله به موضوع بپردازند، در اینجا یک مثال عالی وجود دارد . چگونه متوجه می شوید که این بخش را درک کرده اید؟ اگر شوخی زیر مشخص شد، پس این یک نشانه مطمئن است که ماهیت آسیب پذیری روشن است :D
3. وابستگی ها را اسکن کرده و به روز نگه دارید
چه مفهومی داره؟ برای آنهایی که نمیدانند وابستگی چیست، توضیح میدهم: این یک آرشیو jar با کد است که با استفاده از سیستمهای ساخت خودکار (Maven، Gradle، Ant) به پروژه متصل میشود تا از راهحل شخص دیگری استفاده مجدد کند. به عنوان مثال، Project Lombok که در زمان اجرا برای ما دریافت کننده، ستتر و غیره تولید می کند. و اگر در مورد برنامه های کاربردی بزرگ صحبت کنیم، آنها از وابستگی های مختلفی استفاده می کنند. برخی گذرا هستند (یعنی هر وابستگی می تواند وابستگی های خاص خود را داشته باشد و غیره). بنابراین، مهاجمان به طور فزاینده ای به وابستگی های منبع باز توجه می کنند، زیرا آنها به طور منظم استفاده می شوند و می توانند برای بسیاری از مشتریان مشکل ایجاد کنند. مهم است که مطمئن شوید هیچ آسیبپذیری شناختهشدهای در کل درخت وابستگی (که دقیقاً شبیه آن است) وجود ندارد. و چندین راه برای این کار وجود دارد.از Snyk برای نظارت استفاده کنید
ابزار Snyk تمام وابستگی های پروژه را بررسی می کند و آسیب پذیری های شناخته شده را علامت گذاری می کند. در آنجا می توانید برای مثال پروژه های خود را از طریق GitHub ثبت و وارد کنید. همچنین، همانطور که در تصویر بالا می بینید، اگر نسخه جدیدتر راه حلی برای این آسیب پذیری داشته باشد، Snyk این کار را انجام داده و یک Pull-Request ایجاد می کند. می توان از آن به صورت رایگان برای پروژه های منبع باز استفاده کرد. پروژه ها در برخی از فرکانس ها اسکن می شوند: یک بار در هفته، یک بار در ماه. من ثبت نام کردم و تمام مخازن عمومی خود را به اسکن Snyk اضافه کردم (هیچ چیز خطرناکی در این مورد وجود ندارد: آنها از قبل برای همه باز هستند). سپس، Snyk نتیجه اسکن را نشان داد: و پس از مدتی، Snyk-bot چندین درخواست Pull-Request را در پروژههایی که وابستگیها باید بهروزرسانی شوند، آماده کرد: و در اینجا یکی دیگر: بنابراین این یک ابزار عالی برای جستجوی آسیبپذیریها و نظارت بر بهروزرسانی است. نسخه های جدیداز GitHub Security Lab استفاده کنید
کسانی که در GitHub کار می کنند نیز می توانند از ابزارهای داخلی خود استفاده کنند. شما میتوانید در مورد این رویکرد در ترجمه من از وبلاگ آنها اطلاعیه آزمایشگاه امنیتی GitHub بیشتر بخوانید . این ابزار، البته، ساده تر از Snyk است، اما قطعاً نباید از آن غافل شوید. به علاوه، تعداد آسیبپذیریهای شناخته شده تنها افزایش مییابد، بنابراین هم Snyk و هم GitHub Security Lab گسترش و بهبود خواهند یافت.Sonatype DepShield را فعال کنید
اگر از GitHub برای ذخیره مخازن خود استفاده می کنید، می توانید یکی از برنامه های کاربردی را از MarketPlace - Sonatype DepShield به پروژه های خود اضافه کنید. با کمک آن می توانید پروژه ها را برای وابستگی ها نیز اسکن کنید. علاوه بر این، اگر چیزی پیدا کند، یک مشکل GitHub با توضیحات مربوطه ایجاد می شود، همانطور که در زیر نشان داده شده است:4. داده های حساس را با دقت مدیریت کنید
در گفتار انگلیسی عبارت «دادههای حساس» رایجتر است. افشای اطلاعات شخصی، شماره کارت اعتباری و سایر اطلاعات شخصی مشتری ممکن است صدمات جبران ناپذیری را به همراه داشته باشد. اول از همه، باید به طراحی اپلیکیشن نگاهی دقیق بیندازید و مشخص کنید که آیا واقعاً به داده ای نیاز است یا خیر. شاید نیازی به برخی از آنها نباشد اما برای آینده ای که نیامده و بعید است اضافه شده اند. علاوه بر این، در طول گزارش پروژه، چنین داده هایی ممکن است نشت کند. یک راه ساده برای جلوگیری از ورود دادههای حساس به گزارشهای شما، پاک کردن روشهایtoString()
موجودیتهای دامنه (مانند کاربر، دانشآموز، معلم و غیره) است. این کار از چاپ تصادفی فیلدهای حساس جلوگیری می کند. اگر از Lombok برای تولید یک متد استفاده می کنید toString()
، می توانید از یک حاشیه نویسی @ToString.Exclude
برای جلوگیری از استفاده از فیلد در خروجی از طریق متد استفاده کنید toString()
. همچنین هنگام به اشتراک گذاری داده ها با دنیای خارج بسیار مراقب باشید. به عنوان مثال، یک نقطه پایانی http وجود دارد که نام همه کاربران را نشان می دهد. نیازی به نشان دادن شناسه منحصر به فرد داخلی کاربر نیست. چرا؟ زیرا با استفاده از آن، مهاجم میتواند اطلاعات محرمانهتری درباره هر کاربر به دست آورد. برای مثال، اگر از جکسون برای سریالسازی و سریالزدایی POJOها در JSON استفاده میکنید ، میتوانید از @JsonIgnore
و حاشیهنویسی @JsonIgnoreProperties
برای جلوگیری از سریالسازی و غیر سریالی شدن فیلدهای خاص استفاده کنید. به طور کلی برای مکان های مختلف باید از کلاس های مختلف POJO استفاده کنید. چه مفهومی داره؟
- برای کار با پایگاه داده، فقط از POJO - Entity استفاده کنید.
- برای کار با منطق تجاری، Entity را به Model منتقل کنید.
- برای کار با دنیای خارج و ارسال درخواست های http، از نهادهای سوم - DTO استفاده کنید.
از الگوریتم های رمزگذاری و هش قوی استفاده کنید
اطلاعات محرمانه مشتری باید به طور ایمن ذخیره شود. برای این کار باید از رمزگذاری استفاده کنید. بسته به کار، باید تصمیم بگیرید که از چه نوع رمزگذاری استفاده کنید. علاوه بر این، رمزگذاری قویتر زمان بیشتری را میطلبد، بنابراین باز هم باید در نظر بگیرید که نیاز به آن چقدر زمان صرف شده برای آن را توجیه میکند. البته می توانید الگوریتم را خودتان بنویسید. اما این غیر ضروری است. می توانید از راهکارهای موجود در این زمینه بهره ببرید. به عنوان مثال، Google Tink :<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
<groupId>com.google.crypto.tink</groupId>
<artifactId>tink</artifactId>
<version>1.3.0</version>
</dependency>
بیایید نحوه استفاده از آن را با استفاده از مثال نحوه رمزگذاری یک راه و روش دیگر ببینیم:
private static void encryptDecryptExample() {
AeadConfig.register();
KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
String plaintext = "Цой жив!";
String aad = "Юрий Клинских";
Aead aead = handle.getPrimitive(Aead.class);
byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
System.out.println(encryptedString);
byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
System.out.println(new String(decrypted));
}
رمزگذاری رمز عبور
برای این کار، استفاده از رمزگذاری نامتقارن امن ترین کار است. چرا؟ زیرا برنامه واقعاً نیازی به رمزگشایی رمزهای عبور ندارد. این رویکرد کلی است. در واقع، زمانی که کاربر رمز عبور را وارد می کند، سیستم آن را رمزگذاری می کند و آن را با آنچه در خزانه رمز عبور است مقایسه می کند. رمزگذاری با استفاده از همان ابزار انجام می شود، بنابراین می توانید انتظار داشته باشید که آنها مطابقت داشته باشند (البته اگر رمز عبور صحیح را وارد کنید). BCrypt و SCrypt برای این منظور مناسب هستند. هر دو توابع یک طرفه (هش رمزنگاری) با الگوریتم های محاسباتی پیچیده هستند که زمان زیادی را صرف می کنند. این دقیقاً همان چیزی است که شما به آن نیاز دارید، زیرا رمزگشایی مستقیم آن برای همیشه طول خواهد کشید. به عنوان مثال، Spring Security طیف وسیعی از الگوریتم ها را پشتیبانی می کند.SCryptPasswordEncoder
همچنین می توانید استفاده کنید BCryptPasswordEncoder
. آنچه اکنون یک الگوریتم رمزگذاری قوی است ممکن است سال آینده ضعیف باشد. در نتیجه نتیجه می گیریم که بررسی الگوریتم های مورد استفاده و به روز رسانی کتابخانه ها با الگوریتم ضروری است.
GO TO FULL VERSION