14.5.10

Security-driven design #2 - SQL Injection

ผมเขียนตอนแรก ไว้ที่ เป็นเรื่องเกี่ยวกับการโจมตีจุดอ่อน XSS
บทความในตอนนี้เป็นตอนต่อเนื่องที่พูดเกี่ยวกับจุดอ่อนที่เรามักพบกันได้บ่อย นั่นคือ SQL Injection โดยเป็นการโจมตีโดยการฉีค SQL query ผ่านช่องทางต่างๆ ที่ให้เราส่ง input จาก client ไปหาตัวระบบได้ เมื่อทำสำเร็จแล้วผู้โจมตีจะสามารถเข้าถึงข้อมูลเพื่อทำ CRUD ที่อยู่ในฐานข้อมูลได้ โดยไม่ต้องผ่านการตรวจสอบสิทธิของ application นั้นๆ
ผลกระทบของ SQL Injection ขึ้นอยู่กับปัจจัยเหล่านี้
  • จุดที่ฉีคเข้ามา query
  • สิทธิของ user ที่ execute query นั้น
  • database platform และ version
  • database configuration
ยกตัวอย่าง SQL Injection ที่เกิดขึ้นเมื่อนักพัฒนาสร้าง dynamic query โดยนำมารวมกับ input ที่ผู้ใข้ส่งเข้ามาโดยไม่ผ่านวิธีการที่เหมาะสม
String query = “Select * from Users Where username = ‘“ + request.getParameter(”username”) + “‘ and password = ‘“ + request.getParameter(”password”) + “‘“;
  try {
    Statement statement = connection.createStatement( … );
    ResultSet results = statement.executeQuery( query );
  }
แล้วถ้าใช้ url ลักษณะนี้
http://www.yoursite.com/login?username=tofu’%20or%20‘1’%20=%20’1&password=1’%20or%20’1’%20=%20’1
เท่ากับเราจะได้ query ที่ค่าจะออกมาเป็น true และ query ผลลัพธ์ออกมาได้
Select * from Users Where username = ‘tofu’ or ‘1’=’1’ and password=’1’ or ‘1’=’1’ 
นอกจากนี้ยังมี query อีกสารพัดแบบที่ถูกฉีคเข้ามาทดสอบความอ่อนแอของ application เราหาได้ใน google

แนวทางป้องกันหลัก
  • Parameterized query
    • ใช้ Prepared Statement / HQL / ...
    • ใช้ Stored Procedures
  • Character escaping
  • User-input Handling ได้กล่าวไปในบท ก่อนหน้านี้ และดูเพิ่มเติมได้จาก นี้
  • ซ่อน error message ที่มาจาก database server เนื่องจากการทำ SQL injection จะเกิด error message ที่ return ออกมาจาก
  • database server ได้ซึ่ง message ดังกล่าวมันช่วยให้ผู้ไม่ประสงค์ดีนำไปวิเคราะห์ และนำกลับมาโจมตีเราใหม่
  • ใช้ user ที่มีสิทธิ์เท่าที่จำเป็นต่อการ execute query นั้นเท่านั้น ไม่ควรใช้ sa, dba หรือ admin
ตัวอย่างการใช้ Prepared Statement เพื่อส่งผ่าน parameter
String query = “Select * from Users Where username = ? and password = ?”;
  try {
    PrepareStatement pstmt = conn.prepareStatement(query);
    pstmt.setString(1, username);
    pstmt.setString(2, password);
    ResultSet results = pstmt.executeQuery(query);
  }
ใน query language ของ framework ต่างๆ ก็มี parameter statment ให้ใช้ เช่น Hibernate Query Language (HQL)
Query query = session.createQuery(”from Users where username =:username and password =:password”;
  query.setParameter(”username”, username);
  query.setParameter(”password”, password);
ตัวอย่างการใช้ Stored Procedure เพื่อส่งผ่าน parameterized query
String username = request.getParameter(”username”);
  String password = request.getParameter(”password”);
  try {
   CallableStatement cs = conn.prepareCall(”{call getUsers(?, ?)}”);
   cs.setString(1, username);
   cs.setString(2, password);
   ResultSet results = cs.executeQuery();
  } 
เทคนิค Escape อีกทางเลือกหนึ่งกรณีที่เราไม่ใช้ Parameterized query และส่งผลต่อ ประสิทธิภาพ ถ้าใช้งานอย่างไม่เหมาะสม เพื่อสร้าง dynamic query ลองเลือกเทคนิค Escape (เช่นเดียวกับ Java ที่มี character escaping) แต่อย่างไรก็ตามเทคนิคนี้ค่อนข้างเปราะบางกว่าเมื่อเทียบกับ parameterized query อ่านรายละเอียด character escaping ของ DBMS แต่ละเจ้าได้ดังนี้ศึกษาตัวอย่าง escape ได้จากโค้ด OracleCodec class ของ owasp-esapi
//encode ' to '' 
  public String encodeCharacter( char[] immune, Character c ) { 
    if ( c.charValue() == '\'' ) 
      return "\'\'";
    return ""+c; 
  } 
Note: การเลือกใช้ PreparedStatement หรือ Statement และการเพิ่มประสิทธิภาพการทำงาน เพิ่มเติม: Review Code for SQL Injection Test for SQL Injection Reference: SQL injection

No comments:

Post a Comment