โค้ดยิม/จาวาบล็อก/สุ่ม/ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด
John Squirrels
ระดับ
San Francisco

ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด

เผยแพร่ในกลุ่ม
เมตริกที่สำคัญที่สุดอย่างหนึ่งในแอปพลิเคชันเซิร์ฟเวอร์คือความปลอดภัย นี่เป็น ข้อกำหนดที่ไม่เกี่ยวกับการทำงานประเภทหนึ่ง ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 1ความปลอดภัยมีส่วนประกอบมากมาย แน่นอนว่าจะต้องใช้มากกว่าหนึ่งบทความจึงจะครอบคลุมหลักการรักษาความปลอดภัยและมาตรการรักษาความปลอดภัยที่รู้จักทั้งหมด ดังนั้นเราจะพูดถึงสิ่งที่สำคัญที่สุด บุคคลที่เชี่ยวชาญในหัวข้อนี้สามารถตั้งค่ากระบวนการที่เกี่ยวข้องทั้งหมด หลีกเลี่ยงการสร้างช่องโหว่ด้านความปลอดภัยใหม่ และจะจำเป็นสำหรับทุกทีม แน่นอน คุณไม่ควรคิดว่าใบสมัครของคุณจะปลอดภัย 100% หากคุณปฏิบัติตามแนวทางปฏิบัติเหล่านี้ เลขที่! แต่จะปลอดภัยกว่าแน่นอน ไปกันเถอะ.

1. ให้ความปลอดภัยในระดับของภาษา Java

ประการแรก ความปลอดภัยใน Java เริ่มต้นที่ระดับความสามารถของภาษา เราจะทำอย่างไรหากไม่มีตัวดัดแปลงการเข้าถึง จะไม่มีอะไรนอกจากอนาธิปไตย ภาษาการเขียนโปรแกรมช่วยให้เราเขียนรหัสที่ปลอดภัยและยังใช้ประโยชน์จากคุณสมบัติความปลอดภัยโดยนัยมากมาย:
  1. การพิมพ์ที่แข็งแกร่ง Java เป็นภาษาที่พิมพ์แบบคงที่ สิ่งนี้ทำให้สามารถตรวจจับข้อผิดพลาดเกี่ยวกับประเภทในขณะรันไทม์ได้
  2. ตัวแก้ไขการเข้าถึง ซึ่งช่วยให้เราปรับแต่งการเข้าถึงคลาส เมธอด และฟิลด์ได้ตามต้องการ
  3. การจัดการหน่วยความจำอัตโนมัติ สำหรับสิ่งนี้ นักพัฒนา Java มีตัวรวบรวมขยะที่ทำให้เราไม่ต้องกำหนดค่าทุกอย่างด้วยตนเอง ใช่ บางครั้งปัญหาก็เกิดขึ้น
  4. การตรวจสอบ Bytecode : Java ถูกคอมไพล์เป็น bytecode ซึ่งตรวจสอบโดยรันไทม์ก่อนที่จะดำเนินการ
นอกจากนี้ยังมี คำแนะนำด้าน ความปลอดภัยของ Oracle แน่นอนว่ามันไม่ได้เขียนด้วยภาษาที่สูงส่งและคุณอาจหลับไปหลายครั้งในขณะที่อ่าน แต่มันก็คุ้มค่า โดยเฉพาะอย่างยิ่ง เอกสารชื่อSecure Coding Guideline for Java SEมีความสำคัญ ให้คำแนะนำเกี่ยวกับวิธีการเขียนรหัสที่ปลอดภัย เอกสารนี้ให้ข้อมูลที่เป็นประโยชน์อย่างมากจำนวนมาก หากคุณมีโอกาสคุณควรอ่านอย่างแน่นอน เพื่อกระตุ้นความสนใจของคุณในเนื้อหานี้ ต่อไปนี้เป็นเคล็ดลับที่น่าสนใจบางประการ:
  1. หลีกเลี่ยงการซีเรียลไลซ์คลาสที่ไวต่อความปลอดภัย การทำให้เป็นอนุกรมจะเปิดเผยอินเทอร์เฟซของคลาสในไฟล์ที่ทำให้เป็นอนุกรม ไม่ต้องพูดถึงข้อมูลที่ทำให้เป็นอนุกรม
  2. พยายามหลีกเลี่ยงคลาสที่ไม่แน่นอนสำหรับข้อมูล สิ่งนี้ให้ประโยชน์ทั้งหมดของคลาสที่ไม่เปลี่ยนรูป (เช่น ความปลอดภัยของเธรด) หากคุณมีออบเจกต์ที่เปลี่ยนแปลงได้ อาจนำไปสู่ลักษณะการทำงานที่ไม่คาดคิดได้
  3. ทำสำเนาของวัตถุที่ไม่แน่นอนที่ส่งคืน ถ้าเมธอดส่งกลับการอ้างอิงไปยังออบเจกต์ภายในที่เปลี่ยนแปลงได้ รหัสไคลเอ็นต์อาจเปลี่ยนสถานะภายในของอ็อบเจ็กต์ได้
  4. และอื่น ๆ ...
โดยทั่วไปแนวทางการเข้ารหัสที่ปลอดภัยสำหรับ Java SE คือการรวบรวมเคล็ดลับและลูกเล่นเกี่ยวกับวิธีการเขียนโค้ด 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 ตัวอย่างเช่น:
  1. เราสามารถส่งสิ่งที่คาดหวังได้ — ชื่อผู้ใช้ จากนั้นฐานข้อมูลจะส่งคืนผู้ใช้ทั้งหมดด้วยชื่อนั้น
  2. เราสามารถส่งสตริงว่าง จากนั้นผู้ใช้ทั้งหมดจะถูกส่งกลับ
  3. แต่เราสามารถส่งผ่านสิ่งต่อไปนี้: "'; DROP TABLE USERS;" และตอนนี้เรามีปัญหา huuuuuuge แบบสอบถามนี้จะลบตารางออกจากฐานข้อมูล พร้อมด้วยข้อมูลทั้งหมด ทั้งหมดของมัน.
คุณนึกภาพออกไหมว่าปัญหาที่จะเกิดขึ้น นอกเหนือจากนั้น คุณสามารถเขียนอะไรก็ได้ที่คุณต้องการ คุณสามารถเปลี่ยนชื่อของผู้ใช้ทั้งหมด คุณสามารถลบที่อยู่ของพวกเขาได้ ขอบเขตของการก่อวินาศกรรมนั้นมีมากมาย เพื่อหลีกเลี่ยงปัญหานี้ คุณต้องป้องกันการแทรกคิวรีสำเร็จรูปและสร้างคิวรีโดยใช้พารามิเตอร์แทน นี่ควรเป็นวิธีเดียวในการสร้างแบบสอบถามฐานข้อมูล นี่คือวิธีที่คุณสามารถกำจัดช่องโหว่นี้ได้ ตัวอย่างเช่น:
// 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ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 2

3. สแกนการอ้างอิงและอัปเดตอยู่เสมอ

นั่นหมายความว่าอย่างไร? หากคุณไม่รู้ว่าการพึ่งพาคืออะไร ฉันจะอธิบาย การพึ่งพาคือไฟล์เก็บถาวร JAR ที่มีรหัสที่เชื่อมต่อกับโครงการโดยใช้ระบบสร้างอัตโนมัติ (Maven, Gradle, Ant) เพื่อนำโซลูชันของผู้อื่นกลับมาใช้ใหม่ ตัวอย่างเช่นProject Lombokซึ่งสร้าง getters, setters ฯลฯ ให้เราในรันไทม์ แอปพลิเคชันขนาดใหญ่สามารถมีการอ้างอิงจำนวนมากและมากมาย บางส่วนเป็นแบบสกรรมกริยา (นั่นคือ แต่ละการขึ้นต่อกันอาจมีการขึ้นต่อกันของตัวเอง เป็นต้น) เป็นผลให้ผู้โจมตีให้ความสำคัญกับการพึ่งพาโอเพนซอร์สมากขึ้น เนื่องจากมีการใช้เป็นประจำและไคลเอนต์จำนวนมากอาจมีปัญหาเนื่องจากสิ่งเหล่านี้ สิ่งสำคัญคือต้องแน่ใจว่าไม่มีช่องโหว่ที่รู้จักในแผนผังการพึ่งพาทั้งหมด (ใช่ ดูเหมือนต้นไม้) มีหลายวิธีในการทำเช่นนี้

ใช้ Snyk สำหรับการตรวจสอบการพึ่งพา

Snykตรวจสอบการพึ่งพาโครงการทั้งหมดและตั้งค่าสถานะช่องโหว่ที่รู้จัก คุณสามารถลงทะเบียนบน Snyk และนำเข้าโครงการของคุณผ่าน GitHub ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 3นอกจากนี้ ดังที่คุณเห็นจากรูปภาพด้านบน หากมีการแก้ไขช่องโหว่ในเวอร์ชันที่ใหม่กว่า Snyk จะเสนอการแก้ไขและสร้างคำขอดึงข้อมูล คุณสามารถใช้งานได้ฟรีสำหรับโครงการโอเพ่นซอร์ส โครงการจะถูกสแกนตามช่วงเวลาปกติ เช่น สัปดาห์ละครั้ง เดือนละครั้ง ฉันลงทะเบียนและเพิ่มที่เก็บข้อมูลสาธารณะทั้งหมดของฉันในการสแกน Snyk (ไม่มีอันตรายใด ๆ เกี่ยวกับเรื่องนี้ เนื่องจากเป็นสาธารณะสำหรับทุกคนอยู่แล้ว) จากนั้น Snyk แสดงผลการสแกน: ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 4และหลังจากนั้นไม่นาน Snyk-bot ก็เตรียมคำขอดึงหลายรายการในโครงการที่ต้องอัปเดตการพึ่งพา: ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 5และยัง:ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 6นี่เป็นเครื่องมือที่ยอดเยี่ยมในการค้นหาช่องโหว่และตรวจสอบการอัปเดตสำหรับเวอร์ชันใหม่

ใช้ GitHub Security Lab

ใครก็ตามที่ทำงานบน GitHub สามารถใช้ประโยชน์จากเครื่องมือในตัวของมันได้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับแนวทางนี้ได้ในบล็อกโพสต์ของพวกเขาที่ชื่อว่าประกาศGitHub Security Lab แน่นอนว่าเครื่องมือนี้ง่ายกว่า Snyk แต่คุณไม่ควรละเลยมันอย่างแน่นอน ยิ่งไปกว่านั้น จำนวนช่องโหว่ที่รู้จักจะเพิ่มขึ้นเท่านั้น ดังนั้นทั้ง Snyk และ GitHub Security Lab จะขยายและปรับปรุงต่อไป

เปิดใช้งาน Sonatype DepShield

หากคุณใช้ GitHub เพื่อจัดเก็บที่เก็บข้อมูลของคุณ คุณสามารถเพิ่ม Sonatype DepShield ซึ่งเป็นหนึ่งในแอปพลิเคชันใน MarketPlace ในโครงการของคุณได้ นอกจากนี้ยังสามารถใช้เพื่อสแกนโครงการเพื่อหาการพึ่งพา นอกจากนี้ หากพบบางสิ่ง ปัญหา GitHub จะถูกสร้างขึ้นพร้อมคำอธิบายที่เหมาะสมตามที่แสดงด้านล่าง:ความปลอดภัยใน Java: แนวทางปฏิบัติที่ดีที่สุด - 7

4. จัดการกับข้อมูลที่เป็นความลับด้วยความระมัดระวัง

เราอาจใช้วลี "ข้อมูลที่ละเอียดอ่อน" อีกทางหนึ่ง การรั่วไหลของข้อมูลส่วนบุคคลของลูกค้า หมายเลขบัตรเครดิต และข้อมูลที่ละเอียดอ่อนอื่นๆ อาจทำให้เกิดอันตรายที่แก้ไขไม่ได้ ก่อนอื่น พิจารณาการออกแบบแอปพลิเคชันของคุณอย่างใกล้ชิดและพิจารณาว่าคุณต้องการข้อมูลนี้หรือข้อมูลนั้นจริงๆ บางทีคุณอาจไม่ต้องการข้อมูลบางอย่างที่คุณมี — ข้อมูลที่เพิ่มเข้ามาสำหรับอนาคตที่ยังมาไม่ถึงและไม่น่าจะมา นอกจากนี้ คุณมักทำให้ข้อมูลดังกล่าวรั่วไหลโดยไม่ได้ตั้งใจผ่านการบันทึก วิธีง่ายๆ ในการป้องกันไม่ให้ข้อมูลที่ละเอียดอ่อนเข้าสู่บันทึกของคุณคือการขัดเมธอดtoString()ของเอนทิตีโดเมน (เช่น ผู้ใช้ นักเรียน ครู ฯลฯ) วิธีนี้จะป้องกันไม่ให้คุณส่งข้อมูลในช่องที่เป็นความลับโดยไม่ตั้งใจ หากคุณใช้ Lombok เพื่อสร้างtoString()วิธีการ คุณสามารถใช้ คำอธิบาย ประกอบ @ToString.Excludeเพื่อป้องกันไม่ให้ฟิลด์ถูกใช้ในผลลัพธ์ของเมธอดtoString () นอกจากนี้ควรระมัดระวังในการส่งข้อมูลไปยังโลกภายนอก สมมติว่าเรามีปลายทาง HTTP ที่แสดงชื่อของผู้ใช้ทั้งหมด ไม่จำเป็นต้องแสดง ID ภายในเฉพาะของผู้ใช้ ทำไม เนื่องจากผู้โจมตีสามารถใช้เพื่อรับข้อมูลอื่น ๆ ที่ละเอียดอ่อนเกี่ยวกับผู้ใช้ ตัวอย่างเช่น หากคุณใช้ Jackson เพื่อซีเรียลไลซ์/ดีซีเรียลไลซ์POJOเป็น/จากJSONคุณสามารถใช้@JsonIgnoreและ@JsonIgnorePropertiesคำอธิบายประกอบเพื่อป้องกันการซีเรียลไลเซชัน/ดีซีเรียลไลเซชันของฟิลด์เฉพาะ โดยทั่วไป คุณต้องใช้คลาส POJO ที่แตกต่างกันในที่ต่างๆ นั่นหมายความว่าอย่างไร?
  1. เมื่อทำงานกับฐานข้อมูล ให้ใช้ POJO (เอนทิตี) ประเภทหนึ่ง
  2. เมื่อทำงานกับตรรกะทางธุรกิจ ให้แปลงเอนทิตีเป็นแบบจำลอง
  3. เมื่อทำงานกับโลกภายนอกและส่งคำขอ 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 = "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 เหมาะสมที่นี่ ทั้งคู่เป็นฟังก์ชันแบบทางเดียว (แฮชการเข้ารหัส) ที่มีอัลกอริธึมที่ซับซ้อนในการคำนวณซึ่งใช้เวลานาน นี่คือสิ่งที่เราต้องการ เนื่องจากการคำนวณโดยตรงจะใช้เวลาตลอดไป (ก็นาน นาน) Spring Security รองรับอัลกอริทึมที่หลากหลาย เราสามารถใช้SCryptPasswordEncoderและBCryptPasswordEncoder. สิ่งที่ถือว่าเป็นอัลกอริธึมการเข้ารหัสที่แข็งแกร่งในปัจจุบันอาจถือว่าอ่อนแอในปีหน้า ด้วยเหตุนี้ เราจึงสรุปได้ว่าเราควรตรวจสอบอัลกอริทึมที่เราใช้เป็นประจำ และอัปเดตไลบรารีที่มีอัลกอริทึมการเข้ารหัสตามความจำเป็น

แทนที่จะเป็นข้อสรุป

วันนี้เราพูดถึงเรื่องความปลอดภัย และโดยธรรมชาติแล้ว มีหลายสิ่งหลายอย่างถูกทิ้งไว้เบื้องหลัง ฉันเพิ่งเปิดประตูสู่โลกใหม่ให้คุณ โลกที่มีชีวิตเป็นของตัวเอง ความปลอดภัยก็เหมือนกับการเมือง ถ้าคุณไม่ยุ่งกับการเมือง การเมืองก็จะยุ่งกับคุณเอง ฉันแนะนำให้คุณติดตามฉันในบัญชีGitHub ฉันโพสต์ผลงานสร้างสรรค์ของฉันเกี่ยวกับเทคโนโลยีต่างๆ ที่ฉันกำลังศึกษาและนำไปใช้ในที่ทำงานที่นั่น

ลิงค์ที่มีประโยชน์

  1. Guru99: บทช่วยสอนการฉีด SQL
  2. Oracle: Java Security Resource Center
  3. Oracle: แนวทางการเข้ารหัสที่ปลอดภัยสำหรับ Java SE
  4. Baeldung: พื้นฐานของความปลอดภัย Java
  5. ปานกลาง: 10 เคล็ดลับในการเพิ่มประสิทธิภาพความปลอดภัย Java ของคุณ
  6. Snyk: 10 แนวทางปฏิบัติที่ดีที่สุดในการรักษาความปลอดภัย Java
  7. GitHub: ประกาศ GitHub Security Lab: ร่วมกันรักษาความปลอดภัยรหัสของโลก
ความคิดเห็น
  • เป็นที่นิยม
  • ใหม่
  • เก่า
คุณต้องลงชื่อเข้าใช้เพื่อแสดงความคิดเห็น
หน้านี้ยังไม่มีความคิดเห็นใด ๆ