אחד המדדים החשובים ביותר ביישומי שרת הוא אבטחה. זהו סוג של דרישה לא פונקציונלית
.
אבטחה כוללת מרכיבים רבים. כמובן, יידרש יותר ממאמר אחד כדי לכסות באופן מלא את כל עקרונות האבטחה ואמצעי האבטחה הידועים, אז נתעכב על החשוב ביותר. אדם הבקיא בנושא זה יכול להגדיר את כל התהליכים הרלוונטיים, להימנע מיצירת חורי אבטחה חדשים, ויהיה צורך בכל צוות. כמובן, אתה לא צריך לחשוב שהאפליקציה שלך תהיה מאובטחת ב-100% אם תפעל לפי הנהלים האלה. לא! אבל זה בהחלט יהיה בטוח יותר איתם. בוא נלך.
כמו כן, כפי שניתן לראות מהתמונה למעלה, אם פגיעות תוקנה בגרסה חדשה יותר, אז Snyk יציע את התיקון ויצור בקשת משיכה. אתה יכול להשתמש בו בחינם עבור פרויקטים בקוד פתוח. פרויקטים נסרקים במרווחי זמן קבועים, למשל פעם בשבוע, פעם בחודש. נרשמתי והוספתי את כל המאגרים הציבוריים שלי לסריקת Snyk (אין בזה שום דבר מסוכן, מכיוון שהם כבר ציבוריים לכולם). לאחר מכן Snyk הראה את תוצאת הסריקה:
ואחרי זמן מה, Snyk-bot הכין מספר בקשות משיכה בפרויקטים שבהם יש צורך לעדכן תלות:
וגם:
זהו כלי נהדר לאיתור נקודות תורפה ולניטור עדכונים עבור גרסאות חדשות.

1. לספק אבטחה ברמת שפת Java
קודם כל, אבטחה בג'אווה מתחילה ממש ברמת היכולות של השפה. מה היינו עושים אם לא היו משנים גישה? לא תהיה דבר מלבד אנרכיה. שפת התכנות עוזרת לנו לכתוב קוד מאובטח וגם עושה שימוש בתכונות אבטחה מרומזות רבות:- הקלדה חזקה. Java היא שפה בהקלדה סטטית. זה מאפשר לתפוס שגיאות הקשורות לסוג בזמן ריצה.
- משנה גישה. אלה מאפשרים לנו להתאים אישית גישה למחלקות, שיטות ושדות לפי הצורך.
- ניהול זיכרון אוטומטי. בשביל זה, למפתחי Java יש אספן אשפה שמשחרר אותנו מהצורך להגדיר הכל באופן ידני. כן, לפעמים מתעוררות בעיות.
- אימות קוד בייט : Java מורכבת לקוד בייט, שנבדק על ידי זמן הריצה לפני ביצועו.
- הימנע מסידרה של שיעורים רגישים לאבטחה. סריאליזציה חושפת את ממשק המחלקה בקובץ המסודר, שלא לדבר על הנתונים שמועברים בסידרה.
- נסה להימנע משיעורים שניתנים לשינוי עבור נתונים. זה מספק את כל היתרונות של מחלקות בלתי ניתנות לשינוי (למשל בטיחות חוטים). אם יש לך אובייקט שניתן לשינוי, זה יכול להוביל להתנהגות בלתי צפויה.
- צור עותקים של אובייקטים הניתנים לשינוי שהוחזרו. אם שיטה מחזירה הפניה לאובייקט פנימי שניתן לשינוי, אז קוד הלקוח יכול לשנות את המצב הפנימי של האובייקט.
- וכולי…
2. הסר את פגיעויות הזרקת SQL
זהו סוג מיוחד של פגיעות. זה מיוחד מכיוון שהוא גם אחת הפגיעות המפורסמות וגם אחת הפגיעות הנפוצות ביותר. אם מעולם לא התעניינתם באבטחת מחשבים, אז לא תדעו על כך. מהי הזרקת SQL? זוהי מתקפת מסד נתונים הכוללת הזרקת קוד SQL נוסף במקום בו הוא אינו צפוי. נניח שיש לנו שיטה שמקבלת איזשהו פרמטר לשאילתה במסד הנתונים. לדוגמה, שם משתמש. קוד פגיע ייראה בערך כך:// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Compose a SQL database query with our firstName
String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
// Execute the query
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
בדוגמה זו, שאילתת SQL מוכנה מראש בשורה נפרדת. אז מה הבעיה, נכון? אולי הבעיה היא שעדיף להשתמש ב-String.format ? לא? נו, מה אז? הבה נכניס את עצמנו לנעליו של בוחן ונחשוב מה ניתן להעביר כערך של firstName . לדוגמה:
- אנחנו יכולים להעביר את מה שמצופה - שם משתמש. אז מסד הנתונים יחזיר את כל המשתמשים עם השם הזה.
- אנחנו יכולים להעביר מחרוזת ריקה. אז כל המשתמשים יוחזרו.
- אבל אנחנו יכולים גם להעביר את הדברים הבאים: "'; DROP TABLE USERS;". והנה יש לנו עכשיו בעיות יואוו. שאילתה זו תמחק טבלה ממסד הנתונים. יחד עם כל הנתונים. כל זה.
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Create a parameterized query.
String query = "SELECT * FROM USERS WHERE firstName = ?";
// Create a prepared statement with the parameterized query
PreparedStatement statement = connection.prepareStatement(query);
// Pass the parameter's value
statement.setString(1, firstName);
// Execute the query
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
כך נמנעת הפגיעות. למי שרוצה לצלול עמוק יותר לתוך מאמר זה, הנה דוגמה מצוינת
. איך אתה יודע מתי אתה מבין את הפגיעות הזו? אם הבנתם את הבדיחה בקומיקס למטה, כנראה שיש לכם הבנה ברורה במה מדובר בפגיעות זו :D
3. סרוק תלות ושמור אותן מעודכנות
מה זה אומר? אם אתה לא יודע מהי תלות, אני אסביר. תלות היא ארכיון JAR עם קוד שמחובר לפרויקט באמצעות מערכות בנייה אוטומטיות (Maven, Gradle, Ant) על מנת לעשות שימוש חוזר בפתרון של מישהו אחר. לדוגמא, Project Lombok , שמייצר עבורנו getters, setters וכו' בזמן הריצה. ליישומים גדולים יכולים להיות המון המון תלות. חלקם טרנזיטיביים (כלומר, לכל תלות עשויות להיות תלות משלה, וכן הלאה). כתוצאה מכך, התוקפים שמים לב יותר ויותר לתלות בקוד פתוח, מכיוון שהם נמצאים בשימוש קבוע ולקוחות רבים עלולים להיתקל בבעיות בגללן. חשוב לוודא שאין נקודות תורפה ידועות בכל עץ התלות (כן, זה נראה כמו עץ). ישנן מספר דרכים לעשות זאת.השתמש ב-Snyk לניטור תלות
Snyk בודק את כל התלות בפרויקט ומסמן פגיעויות ידועות. אתה יכול להירשם ב-Snyk ולייבא את הפרויקטים שלך דרך GitHub.



השתמש במעבדת האבטחה של GitHub
כל מי שעובד על GitHub יכול לנצל את הכלים המובנים שלו. אתה יכול לקרוא עוד על גישה זו בפוסט הבלוג שלהם שכותרתו הכריזה על GitHub Security Lab . הכלי הזה, כמובן, פשוט יותר מסניק, אבל בהחלט לא כדאי להזניח אותו. יתרה מכך, מספר הפגיעויות הידועות רק יגדל, כך שגם Snyk וגם GitHub Security Lab ימשיכו להתרחב ולהשתפר.הפעל את Sonatype DepShield
אם אתה משתמש ב-GitHub כדי לאחסן את המאגרים שלך, אתה יכול להוסיף את Sonatype DepShield, אחד מהיישומים ב-MarketPlace, לפרויקטים שלך. זה יכול לשמש גם לסריקת פרויקטים לאיתור תלות. יתר על כן, אם הוא ימצא משהו, תיווצר בעיית GitHub עם תיאור מתאים כפי שמוצג להלן:
4. טפל בנתונים חסויים בזהירות
לחילופין אנו עשויים להשתמש בביטוי "נתונים רגישים". הדלפת מידע אישי של לקוח, מספרי כרטיסי אשראי ומידע רגיש אחר עלולה לגרום לנזק בלתי הפיך. קודם כל, תסתכל מקרוב על עיצוב האפליקציה שלך וקבע אם אתה באמת צריך נתונים כאלה או אחרים. אולי אתה בעצם לא צריך חלק מהנתונים שיש לך - נתונים שנוספו לעתיד שלא הגיע וספק אם יגיע. בנוסף, רבים מכם מדליפים בטעות נתונים כאלה דרך רישום. דרך קלה למנוע כניסת נתונים רגישים ליומנים שלך היא לשפשף את שיטות toString() של ישויות תחום (כגון משתמש, תלמיד, מורה וכו'). זה ימנע ממך להוציא בטעות שדות חסויים. אם אתה משתמש ב-Lombok כדי ליצור את השיטה toString() תוכל להשתמש בביאור @ToString.Exclude כדי למנוע שימוש בשדה בפלט של השיטה toString() . כמו כן, היזהר מאוד בעת שליחת נתונים לעולם החיצון. נניח שיש לנו נקודת קצה HTTP שמציגה את שמות כל המשתמשים. אין צורך להציג מזהה פנימי ייחודי של משתמש. למה? מכיוון שתוקף יכול להשתמש בו כדי להשיג מידע אחר ורגיש יותר על המשתמש. לדוגמה, אם אתה משתמש בג'קסון כדי להפוך את POJO ל-/מ- JSON בהמשכה/לבטל את הרצף, אז אתה יכול להשתמש בהערות @JsonIgnore ו- @JsonIgnoreProperties כדי למנוע הסדרה/דה-סידריאליזציה של שדות ספציפיים. באופן כללי, אתה צריך להשתמש בשיעורי POJO שונים במקומות שונים. מה זה אומר?- בעבודה עם מסד נתונים, השתמש בסוג אחד של POJO (ישות).
- כאשר עובדים עם לוגיקה עסקית, המרת ישות למודל.
- בעת עבודה עם העולם החיצון ושליחת בקשות HTTP, השתמש בישויות שונות (DTOs).
השתמש באלגוריתמי הצפנה וגיבוב חזקים
הנתונים הסודיים של הלקוחות חייבים להיות מאוחסנים בצורה מאובטחת. לשם כך, עלינו להשתמש בהצפנה. בהתאם למשימה, עליך להחליט באיזה סוג של הצפנה להשתמש. בנוסף, הצפנה חזקה יותר לוקחת יותר זמן, אז שוב אתה צריך לשקול עד כמה הצורך בה מצדיק את הזמן המושקע בה. כמובן, אתה יכול לכתוב אלגוריתם הצפנה בעצמך. אבל זה מיותר. ניתן להשתמש בפתרונות הקיימים בתחום זה. לדוגמה, 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 = "Elvis lives!";
String aad = "Buddy Holly";
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 מתאימות כאן. שתיהן פונקציות חד-כיווניות (hashs קריפטוגרפיים) עם אלגוריתמים מורכבים מבחינה חישובית שלוקחים זמן רב. זה בדיוק מה שאנחנו צריכים, מכיוון שהחישובים הישירים יימשכו לנצח (טוב, הרבה מאוד זמן). Spring Security תומך במגוון שלם של אלגוריתמים. אנו יכולים להשתמש ב- SCryptPasswordEncoder ו- BCryptPasswordEncoder . מה שנחשב כיום לאלגוריתם הצפנה חזק עשוי להיחשב חלש בשנה הבאה. כתוצאה מכך, אנו מסיקים שעלינו לבדוק באופן קבוע את האלגוריתמים בהם אנו משתמשים ולעדכן, לפי הצורך, את הספריות המכילות את אלגוריתמי ההצפנה.במקום מסקנה
היום דיברנו על אבטחה ומטבע הדברים, הרבה דברים נשארו מאחורי הקלעים. זה עתה פתחתי עבורך את הדלת לעולם חדש, עולם שיש לו חיים משלו. ביטחון הוא בדיוק כמו פוליטיקה: אם אתה לא עסוק בפוליטיקה, הפוליטיקה תעסוק בך. באופן מסורתי אני מציע לך לעקוב אחריי בחשבון GitHub . שם אני מפרסם את היצירות שלי הכוללות טכנולוגיות שונות שאני לומד ומיישם בעבודה.קישורים שימושיים
- Guru99: הדרכה להזרקת SQL
- אורקל: Java Security Resource Center
- אורקל: הנחיות קידוד מאובטח עבור Java SE
- Baeldung: היסודות של אבטחת Java
- בינוני: 10 טיפים לחיזוק אבטחת Java שלך
- Snyk: 10 שיטות עבודה מומלצות לאבטחת Java
- GitHub: הכרזה על GitHub Security Lab: אבטחת הקוד של העולם, ביחד
GO TO FULL VERSION