ทุกวันนี้ มีหลายวิธีที่ผู้โจมตีสามารถพยายามโจมตีแอปพลิเคชันของคุณ และด้วยการโจมตีทางไซเบอร์ที่เพิ่มขึ้นอย่างต่อเนื่อง ความต้องการแอปพลิเคชันบนมือถือที่ปลอดภัย และจากการขยาย การเข้ารหัสที่ปลอดภัย ไม่เคยสูงขึ้น
ดังนั้น หากคุณเป็นนักพัฒนา iOS คุณควรเรียนรู้ที่จะจัดลำดับความสำคัญด้านความปลอดภัยในทุกขั้นตอนของการพัฒนาแอป
Swift ซึ่งเป็นภาษาการเขียนโปรแกรมสมัยใหม่ของ Apple นำเสนอเครื่องมือและเฟรมเวิร์กมากมายที่ทำให้การพัฒนาง่ายขึ้นพร้อมทั้งเพิ่มความปลอดภัย แต่เมื่อใช้อย่างถูกต้องเท่านั้น
บทความนี้จะสำรวจข้อผิดพลาดด้านความปลอดภัยทั่วไป 10 ประการในแอพ iOS ที่ใช้ Swift และเสนอกลยุทธ์ที่ใช้งานได้จริงเพื่อบรรเทาผลกระทบ
ข้อกำหนดเบื้องต้น
ก่อนที่จะดำน้ำ คุณจะต้องมีสิ่งต่อไปนี้:
-
ความรู้ด้านการทำงานของ Swift และการพัฒนา iOS
-
เข้าถึง Xcode
-
ความเข้าใจพื้นฐานเกี่ยวกับวิธีที่แอพ iOS สื่อสารกับเซิร์ฟเวอร์
-
ความคุ้นเคยกับพื้นฐานเทอร์มินัล/บรรทัดคำสั่ง
ตัวอย่างโค้ดใช้งานได้จริงและอธิบายทีละขั้นตอน ทำให้นักพัฒนารุ่นเยาว์เข้าถึงได้ในขณะที่ยังคงมอบคุณค่าให้กับผู้มีประสบการณ์ที่ต้องการเสริมความปลอดภัยให้กับแอปของตน
สิ่งที่เราจะกล่าวถึง:
- กับดักความปลอดภัยที่พบบ่อยที่สุดในแอพพลิเคชั่น Swift iOS คืออะไร
- 1. การจัดเก็บข้อมูลที่ไม่ปลอดภัย
- 2. การสื่อสารผ่านเครือข่ายที่อ่อนแอ
- 3. การตรวจสอบอินพุตที่ไม่เหมาะสม
- 4. ความลับการเข้ารหัส
- 5. การรับรองความถูกต้องและการอนุญาตไม่เพียงพอ
- 6. การบันทึกที่ไม่ปลอดภัยและการจัดการข้อผิดพลาด
- 7. ละเว้นการทำให้โค้ดสับสนและวิศวกรรมย้อนกลับ
- 8. ไลบรารีของบุคคลที่สามที่ไม่ปลอดภัย
- 9. การรับรองความถูกต้องด้วยไบโอเมตริกซ์และหลายปัจจัยไม่เพียงพอ
- 10. ไม่สนใจการทดสอบความปลอดภัยเป็นระยะ
กับดักความปลอดภัยที่พบบ่อยที่สุดในแอพพลิเคชั่น Swift iOS คืออะไร
Swift และ iOS นำเสนอฟีเจอร์ความปลอดภัยที่แข็งแกร่ง แต่ข้อผิดพลาดยังคงเกิดขึ้น ต่อไปนี้เป็นกับดักที่พบบ่อยที่สุดและวิธีแก้ไข:
1. การจัดเก็บข้อมูลที่ไม่ปลอดภัย
ข้อผิดพลาดที่พบบ่อยที่สุดที่นักพัฒนาทำคือการจัดเก็บข้อมูลที่ละเอียดอ่อนไว้ไม่ปลอดภัย รหัสผ่าน โทเค็น หรือแม้แต่ข้อมูลผู้ใช้แต่ละคนอาจถูกทิ้งไว้โดยไม่ตั้งใจใน UserDefaults หรือที่จัดเก็บในเครื่องในรูปแบบที่ไม่ได้เข้ารหัส ป>
แม้ว่า UserDefaults จะสะดวกสำหรับข้อมูลจำนวนเล็กน้อย แต่ก็ไม่ปลอดภัยสำหรับข้อมูลที่ละเอียดอ่อน เนื่องจากผู้โจมตีเข้าถึงได้ง่ายมากหากอุปกรณ์ถูกบุกรุก
วิธีการแก้ไข:
ใช้ Keychain Services API เพื่อจัดเก็บข้อมูลที่ละเอียดอ่อนอย่างปลอดภัย พวงกุญแจเข้ารหัสข้อมูลและผูกเข้ากับอุปกรณ์เพื่อไม่ให้แอปพลิเคชันหรือผู้ใช้ที่ไม่ได้รับอนุญาตรายอื่นเข้าถึงได้
คุณสามารถจัดเก็บข้อมูลประจำตัวได้อย่างปลอดภัยโดยใช้ไลบรารีใน Swift เช่น KeychainAccess หรือฟังก์ชัน SecItemAdd และ SecItemCopyMatching ในตัว
ตัวอย่างเช่น วิธีนี้คุณสามารถจัดเก็บรหัสผ่านผู้ใช้ในพวงกุญแจเพื่อให้แน่ใจว่าข้อมูลที่ละเอียดอ่อนจะถูกเก็บไว้อย่างปลอดภัย:
do {
try keychain.set("userPassword123", key: "userPassword")
} catch {
print("Error saving to Keychain: \(error)")
}
เบื้องหลัง นี่คือสิ่งที่เกิดขึ้นเมื่อคุณโทรหา 08 ภายในคลาส KeychainManager ที่ใช้เฟรมเวิร์กความปลอดภัยดั้งเดิมของ Apple สำหรับการจัดเก็บ:
import Security
class KeychainManager {
func set(_ value: String, key: String) throws {
// 1. Convert string to Data
guard let data = value.data(using: .utf8) else {
throw NSError(domain: "KeychainManager", code: -1)
}
// 2. Build the query dictionary
let query: [String: Any] = [
// Store as password
kSecClass as String: kSecClassGenericPassword,
// Your app's bundle identifier
kSecAttrService as String: "com.yourapp.keychain",
// "userPassword"
kSecAttrAccount as String: key,
// "userPassword123" encrypted
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
// 3. Save to keychain (iOS encrypts it automatically)
let status = SecItemAdd(query as CFDictionary, nil)
// 4. Check if successful
guard status == errSecSuccess else {
throw NSError(domain: "KeychainManager", code: Int(status))
}
}
}
เมื่อฟังก์ชั่นนี้ทำงาน iOS จะแปลงค่าสตริง เช่น "userPassword123" ให้เป็นข้อมูลไบนารีที่เข้ารหัส และจัดเก็บไว้อย่างปลอดภัยในฐานข้อมูลพวงกุญแจของอุปกรณ์ รายการจะถูกบันทึกไว้ภายใต้คีย์ที่ให้มา (เช่น "รหัสผ่านผู้ใช้") และมีเพียงแอปของคุณเท่านั้นที่สามารถเข้าถึงได้
เบื้องหลัง พวงกุญแจใช้ประโยชน์จากคุณสมบัติความปลอดภัยที่แข็งแกร่ง รวมถึงการเข้ารหัสที่สนับสนุนฮาร์ดแวร์โดยใช้คีย์เฉพาะอุปกรณ์ การป้องกันไบโอเมตริกซ์เสริมผ่าน Face ID หรือ Touch ID และการแยกระดับแอพเพื่อให้แน่ใจว่าไม่มีแอพอื่นใดที่สามารถอ่านหรือแก้ไขข้อมูลประจำตัวที่เก็บไว้ของคุณได้
2. การสื่อสารผ่านเครือข่ายที่อ่อนแอ
การส่งข้อมูลที่มีความละเอียดอ่อนผ่านเครือข่ายเป็นอีกพื้นที่หนึ่งที่เสี่ยงต่อช่องโหว่ การใช้การเชื่อมต่อ HTTP ที่ไม่ได้เข้ารหัสจะทำให้แอปของคุณถูกโจมตีจากคนกลาง (MITM) ทำให้ผู้โจมตีสามารถสกัดกั้นและแก้ไขข้อมูลระหว่างทางได้ ป>
ปัญหา :เมื่อข้อมูลเดินทางระหว่างแอปและเซิร์ฟเวอร์ของคุณผ่านการเชื่อมต่อที่ไม่ปลอดภัย ผู้โจมตีบนเครือข่ายเดียวกัน (เช่น Wi-Fi สาธารณะ) จะสามารถ:
-
อ่านข้อมูลที่ละเอียดอ่อน (รหัสผ่าน ข้อมูลส่วนบุคคล รายละเอียดการชำระเงิน)
-
แก้ไขคำขอและการตอบกลับ
-
ปลอมตัวเป็นเซิร์ฟเวอร์ที่ถูกต้องตามกฎหมายของคุณ
วิธีการแก้ไข:
1. ใช้ HTTPS เสมอ
HTTPS เข้ารหัสข้อมูลทั้งหมดระหว่างการส่ง ทำให้ผู้โจมตีไม่สามารถอ่านได้ App Transport Security (ATS) ของ iOS บังคับใช้สิ่งนี้โดยการบล็อกการเชื่อมต่อ HTTP ที่ไม่ปลอดภัยตามค่าเริ่มต้น:
// ❌ INSECURE - HTTP connection (blocked by ATS by default)
let url = URL(string: "http://api.example.com/login")
// ✅ SECURE - HTTPS connection
let url = URL(string: "https://api.example.com/login")
คุณจะต้องหลีกเลี่ยงการเพิ่มข้อยกเว้น ATS ให้กับ Info.plist ของคุณ เว้นแต่จะจำเป็นจริงๆ หาก API ของบุคคลที่สามรองรับเฉพาะ HTTP โปรดติดต่อพวกเขาเพื่ออัปเกรดหรือค้นหาทางเลือกที่ปลอดภัยกว่านี้
<แข็งแกร่ง>2. ใช้การปักหมุดใบรับรอง (การป้องกันขั้นสูง) ป>
แม้ว่าจะใช้ HTTPS แอปของคุณก็อาจยังเสี่ยงต่อการโจมตี MITM ที่ซับซ้อน ผู้โจมตีสามารถติดตั้งใบรับรองที่ฉ้อโกงบนอุปกรณ์ของผู้ใช้ (ผ่านมัลแวร์หรือวิศวกรรมสังคม) เป็นต้น และสกัดกั้นการรับส่งข้อมูล HTTPS ที่ดูเหมือนว่าถูกต้อง อุปกรณ์จะเชื่อถือใบรับรองปลอมของผู้โจมตี ช่วยให้พวกเขาสามารถถอดรหัสและอ่านการสื่อสารที่ "ปลอดภัย" ได้
การปักหมุดใบรับรองช่วยแก้ปัญหานี้ด้วยการทำให้แอปของคุณเชื่อถือเฉพาะใบรับรองของเซิร์ฟเวอร์เฉพาะของคุณเท่านั้น โดยปฏิเสธใบรับรองอื่นๆ ทั้งหมด แม้ว่าจะใช้งานได้ก็ตาม
วิธีการทำงานของการปักหมุดใบรับรอง: ป>
แอปของคุณจัดเก็บใบรับรองที่คาดหวัง (หรือแฮชคีย์สาธารณะ) และตรวจสอบความถูกต้องระหว่างการเชื่อมต่อแต่ละครั้ง:
class SecureNetworkManager: NSObject, URLSessionDelegate {
// Store your server's certificate hash
// Get this by running: openssl x509 -in certificate.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
private let expectedPublicKeyHash = "YOUR_CERTIFICATE_HASH_HERE"
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
// Step 1: Check if this is a server trust challenge (certificate validation)
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust
else {
// Not a certificate challenge; use default handling
completionHandler(.performDefaultHandling, nil)
return
}
// Step 2: Validate that the server's certificate matches our pinned certificate
if isValidServerTrust(serverTrust) {
// Certificate matches - proceed with the connection
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
// Certificate doesn't match - reject the connection to prevent MITM attack
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
// Validates the server's certificate against our pinned hash
private func isValidServerTrust(_ serverTrust: SecTrust) -> Bool {
// Extract the server's certificate
guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
return false
}
// Get the public key from the certificate
let serverPublicKey = SecCertificateCopyKey(serverCertificate)
guard let publicKey = serverPublicKey else {
return false
}
// Convert the public key to data and hash it
var error: Unmanaged<CFError>?
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
return false
}
// Hash the public key using SHA-256
let publicKeyHash = SHA256.hash(data: publicKeyData)
let publicKeyHashString = Data(publicKeyHash).base64EncodedString()
// Compare with our expected hash
return publicKeyHashString == expectedPublicKeyHash
}
}
รหัสนี้ทำอะไรทีละขั้นตอน:
-
12รหัส> – วิธีการนี้จะถูกเรียกเมื่อใดก็ตามที่แอปของคุณทำการเชื่อมต่อ HTTPS iOS ถามว่า:"ฉันควรเชื่อถือใบรับรองของเซิร์ฟเวอร์นี้หรือไม่" -
ตรวจสอบวิธีการตรวจสอบสิทธิ์ – เราตรวจสอบยืนยันว่านี่คือความท้าทายด้านความน่าเชื่อถือของเซิร์ฟเวอร์ (การตรวจสอบใบรับรอง) ไม่ใช่การตรวจสอบสิทธิ์ประเภทอื่น
-
ตรวจสอบใบรับรอง – เราเรียกว่า
26ซึ่ง:-
แยกใบรับรองของเซิร์ฟเวอร์ออกจากการเชื่อมต่อ
-
รับรหัสสาธารณะจากใบรับรองนั้น
-
แฮชกุญแจสาธารณะโดยใช้ SHA-256
-
เปรียบเทียบแฮชกับแฮชที่คาดหวังที่เก็บไว้ของเรา
-
- ตัดสินใจ:
-
หากแฮชตรงกัน แสดงว่าเซิร์ฟเวอร์นั้นถูกต้องตามกฎหมาย ดำเนินการต่อด้วย
31. -
หากแฮชไม่ตรงกัน แสดงว่าอาจถูกโจมตีจาก MITM ยกเลิกด้วย
42.
แล้วจะป้องกันการโจมตี MITM ได้อย่างไร? แม้ว่าผู้โจมตีจะติดตั้งใบรับรองที่หลอกลวงในอุปกรณ์ของผู้ใช้และสกัดกั้นการรับส่งข้อมูล แฮชของใบรับรองจะไม่ตรงกับแฮชที่ปักหมุดไว้ แอปของคุณจะปฏิเสธการเชื่อมต่อ เพื่อป้องกันไม่ให้ผู้โจมตีถอดรหัสการรับส่งข้อมูลของคุณ
<แข็งแกร่ง>3. การป้องกันเพิ่มเติม:แนะนำ VPN บน Wi-Fi สาธารณะ ป>
คุณยังแนะนำให้ผู้ใช้เชื่อมต่อผ่าน VPN เมื่อใช้ Wi-Fi สาธารณะเพื่อเพิ่มระดับความปลอดภัย และให้คำแนะนำเกี่ยวกับวิธีใช้ VPN อย่างมีประสิทธิภาพเพื่อรักษาข้อมูลให้ปลอดภัย
สำหรับนักพัฒนา การรักษาความปลอดภัยให้แอปที่แข็งแกร่งนั้นยังขึ้นอยู่กับการมีระบบที่ได้รับการปรับปรุงมาเป็นอย่างดี การเรียนรู้วิธีเพิ่มความเร็วให้กับ Mac ที่ช้าสามารถช่วยให้มั่นใจว่าการสร้างจะราบรื่นขึ้น การทดสอบเร็วขึ้น และขั้นตอนการพัฒนาโดยรวมที่ปลอดภัยยิ่งขึ้น
3. การตรวจสอบอินพุตที่ไม่เหมาะสม
นักพัฒนาบางรายละเลยการตรวจสอบอินพุตที่ถูกต้อง ซึ่งนำไปสู่ช่องโหว่จำนวนหนึ่ง รวมถึงการแทรก SQL, การเรียกใช้โค้ดจากระยะไกล และความเสียหายของข้อมูล
แม้ว่า Swift จะรองรับการพิมพ์ที่ดี แต่นักพัฒนาบางคนก็ไม่ได้ทำความสะอาดอินพุตของผู้ใช้หรือการตอบสนองของ API การรวมการตรวจสอบอีเมล API แบบเรียลไทม์ช่วยให้แน่ใจว่าผู้ใช้ระบุที่อยู่อีเมลที่ถูกต้องและจัดรูปแบบอย่างถูกต้องก่อนที่จะจัดเก็บหรือประมวลผล ซึ่งช่วยลดความเสี่ยงด้านความปลอดภัยและปัญหาคุณภาพของข้อมูล
วิธีการแก้ไข:
การตรวจสอบอินพุตเป็นด่านแรกในการป้องกันข้อมูลที่เป็นอันตราย ต่อไปนี้เป็นวิธีปกป้องแอปพลิเคชัน iOS ของคุณ:
1. ตรวจสอบการป้อนข้อมูลของผู้ใช้ด้วยรูปแบบ ป>
ตรวจสอบอินพุตของผู้ใช้โดยใช้นิพจน์ทั่วไปหรือรูปแบบที่กำหนดไว้ล่วงหน้าก่อนประมวลผลเสมอ ตัวอย่างเช่น เมื่อยอมรับที่อยู่อีเมล:
func isValidEmail(_ email: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
return emailPredicate.evaluate(with: email)
}
// Only accept properly formatted data
guard isValidEmail(userEmail) else {
// Reject invalid input
return
}
เพื่อให้แน่ใจว่าจะยอมรับเฉพาะข้อมูลที่จัดรูปแบบอย่างถูกต้องเท่านั้น เพื่อป้องกันไม่ให้อินพุตที่มีรูปแบบไม่ถูกต้องหรือเป็นอันตรายเข้าสู่ระบบของคุณ
<แข็งแกร่ง>2. ฆ่าเชื้อการตอบสนองของ API ป>
อย่าเชื่อถือข้อมูลภายนอก ตรวจสอบและฆ่าเชื้อการตอบสนองของ API ก่อนใช้งานเสมอ:
if let userAge = apiResponse["age"] as? Int,
userAge >= 0 && userAge <= 150 {
// Safe to use
user.age = userAge
} else {
// Handle invalid data appropriately
throw ValidationError.invalidAge
}
<แข็งแกร่ง>3. ใช้การสืบค้นแบบกำหนดพารามิเตอร์ (สำคัญที่สุด) ป>
ข้อผิดพลาดที่อันตรายที่สุดคือการสร้างแบบสอบถามฐานข้อมูลผ่านการต่อสตริง พิจารณาโค้ดที่มีช่องโหว่นี้:
// ❌ NEVER DO THIS - Vulnerable to SQL Injection
let username = userInput // Could be: "admin' OR '1'='1"
let query = "SELECT * FROM users WHERE username = '\(username)'"
database.execute(query)
หากผู้ใช้ที่เป็นอันตรายป้อน admin' หรือ '1'='1 เป็นชื่อผู้ใช้ ข้อความค้นหาจะกลายเป็น:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'
การดำเนินการนี้จะส่งคืนผู้ใช้ทั้งหมดในฐานข้อมูลแทนที่จะเป็นเพียงรายการเดียว ซึ่งอาจเปิดเผยข้อมูลที่ละเอียดอ่อน โซลูชันที่ปลอดภัยใช้การสืบค้นแบบกำหนดพารามิเตอร์:
// ✅ SAFE - Using parameterized queries
let query = "SELECT * FROM users WHERE username = ?"
database.execute(query, withArgumentsIn: [username])
ในแนวทางนี้ 51 เป็นตัวยึดตำแหน่งที่ฐานข้อมูลถือเป็นพารามิเตอร์ ไม่ใช่ส่วนหนึ่งของคำสั่ง SQL ค่าชื่อผู้ใช้จะถูกส่งแยกกันใน 64 อาร์เรย์.
ซึ่งหมายความว่าแม้ว่าผู้ใช้จะพยายามแทรกโค้ด SQL เช่น 74 ฐานข้อมูลจะถือว่าสตริงทั้งหมดเป็นชื่อผู้ใช้ตามตัวอักษรเพื่อค้นหา ไม่ใช่โค้ด SQL ที่ปฏิบัติการได้ เอ็นจิ้นฐานข้อมูลจะหลีกอักขระพิเศษใดๆ โดยอัตโนมัติ ซึ่งช่วยลดความเสี่ยงของการแทรก SQL โดยสิ้นเชิง
ด้วยการแยกโครงสร้างการสืบค้นออกจากข้อมูล การสืบค้นแบบกำหนดพารามิเตอร์ทำให้มั่นใจได้ว่าการป้อนข้อมูลของผู้ใช้ไม่สามารถเปลี่ยนแปลงตรรกะที่ต้องการของคำสั่ง SQL ของคุณได้
4. ความลับการเข้ารหัส
คีย์ API ข้อมูลรับรอง หรือโทเค็นส่วนตัวที่ฮาร์ดโค้ดในซอร์สโค้ดถือเป็นข้อผิดพลาดด้านความปลอดภัยที่ร้ายแรงอีกประการหนึ่ง ผู้โจมตีสามารถดึงความลับดังกล่าวออกจากไบนารีที่คอมไพล์โดยใช้เครื่องมือวิศวกรรมย้อนกลับ โดยเฉพาะอย่างยิ่งสำหรับแอปที่เผยแพร่สู่สาธารณะ
เมื่อเปิดเผยแล้ว ข้อมูลประจำตัวเหล่านี้สามารถใช้เพื่อเข้าถึงบริการแบ็กเอนด์ของคุณ ซึ่งอาจนำไปสู่การละเมิดข้อมูลหรือการเรียกเก็บเงินที่ไม่ได้รับอนุญาต
ปัญหา – ความลับแบบฮาร์ดโค้ด:
// NEVER DO THIS
class APIClient {
private let apiKey = "1234567890abcdef"
private let secretToken = "sk_live_51H..."
func makeRequest() {
// These secrets are embedded in your binary
let headers = ["Authorization": "Bearer \(apiKey)"]
}
}
วิธีแก้ไข: ป>
อย่าจัดเก็บข้อมูลประจำตัวที่ละเอียดอ่อนไว้ในโค้ดของคุณโดยตรง นี่คือทางเลือกอื่นที่ปลอดภัย:
โซลูชันที่ 1:ดึงข้อมูลลับจากแบ็กเอนด์ที่รันไทม์ ป>
แนวทางที่ปลอดภัยที่สุดคืออย่าเก็บความลับไว้กับไคลเอนต์เลย ให้ตรวจสอบสิทธิ์ผู้ใช้และให้แบ็กเอนด์ของคุณทำการเรียก API ที่ได้รับอนุญาตแทน:
class APIClient {
private var sessionToken: String?
// User logs in and receives a temporary session token
func authenticateUser(email: String, password: String) async throws {
let response = try await backend.login(email: email, password: password)
// Store only a temporary, user-specific session token
self.sessionToken = response.sessionToken
}
// Backend handles the actual API calls with the real API key
func fetchUserData() async throws -> UserData {
guard let token = sessionToken else {
throw AuthError.notAuthenticated
}
// Your backend receives this request, validates the session token,
// then uses its own API keys to fetch data from third-party services
return try await backend.getUserData(sessionToken: token)
}
}
วิธีการทำงาน: ป>
แอปของคุณไม่มีทางรู้คีย์ API ที่แท้จริง เมื่อผู้ใช้ต้องการข้อมูล แอปของคุณจะส่งคำขอไปยังเซิร์ฟเวอร์แบ็กเอนด์ของคุณเองด้วยโทเค็นเซสชัน แบ็กเอนด์ของคุณจะตรวจสอบโทเค็น จากนั้นใช้คีย์ API ที่เก็บไว้อย่างปลอดภัยของตัวเองเพื่อทำการเรียก API ของบริษัทอื่นจริง ด้วยวิธีนี้ความลับที่แท้จริงจะไม่มีวันออกจากเซิร์ฟเวอร์ของคุณ
โซลูชันที่ 2:ตัวแปรสภาพแวดล้อมหรือไฟล์กำหนดค่า (การพัฒนาเท่านั้น) ป>
สำหรับสภาพแวดล้อมการพัฒนา ให้ใช้ไฟล์ .xcconfig ที่ไม่รวมอยู่ในการควบคุมเวอร์ชัน:
// Secrets.xcconfig (add to .gitignore!)
API_KEY = your_dev_api_key_here
API_SECRET = your_dev_secret_here
// Access in your code through Info.plist
class Config {
static let apiKey: String = {
guard let key = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String else {
fatalError("API_KEY not found in configuration")
}
return key
}()
}
สำคัญ :แนวทางนี้เหมาะสำหรับสภาพแวดล้อมที่ไม่ใช่การผลิตเท่านั้น! อย่าจัดส่งคีย์ API ที่ใช้งานจริงกับแอปของคุณ แม้แต่ในไฟล์กำหนดค่าก็ตาม
5. การรับรองความถูกต้องและการอนุญาตไม่เพียงพอ
การใช้การตรวจสอบสิทธิ์และการตรวจสอบสิทธิ์ฝั่งไคลเอ็นต์ถือเป็นความเสี่ยง ผู้โจมตีอาจทำให้แอปเลี่ยงการตรวจสอบเหล่านี้และเข้าถึงด้วยวิธีที่ไม่ได้รับอนุญาตโดยการบังคับหรือยุ่งเกี่ยวกับแอป/รันไทม์
วิธีการแก้ไข:
-
ทำการรับรองความถูกต้องและการอนุญาตบนฝั่งเซิร์ฟเวอร์แทนฝั่งไคลเอ็นต์
-
ใช้ JWT (JSON Web Tokens) หรือ OAuth 2.0 สำหรับการเข้าสู่ระบบของผู้ใช้ที่ได้รับการตรวจสอบสิทธิ์
-
จำเป็นต้องใช้ตรรกะการหมดอายุและรีเฟรชโทเค็นเพื่อลดโอกาสที่โทเค็นจะถูกขโมย
ตัวอย่าง:การส่ง JWT อย่างปลอดภัย: ป>
let request = URLRequest(url: apiURL)
request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
6. การบันทึกที่ไม่ปลอดภัยและการจัดการข้อผิดพลาด
แนวทางปฏิบัติในการบันทึกที่กว้างขวางและไม่ปลอดภัย รวมถึงข้อยกเว้นที่ไม่ถูกตรวจจับ อาจนำไปสู่การเปิดเผยข้อมูลที่ละเอียดอ่อน รวมถึงชื่อผู้ใช้ รหัสผ่าน และคีย์ API
วิธีการแก้ไข:
-
บันทึกข้อมูลที่ละเอียดอ่อนอย่างระมัดระวัง
-
ใช้การจัดการข้อผิดพลาดที่มีการควบคุมและให้ข้อมูลจำนวนขั้นต่ำในข้อความที่ผู้ใช้นำเสนอ
-
ใช้ไลบรารีการบันทึกที่ปลอดภัยซึ่งปกปิดหรือเข้ารหัสข้อมูลส่วนบุคคล
do {
try someRiskyOperation()
} catch {
// Log error securely
Logger.log("Operation failed: \(error.localizedDescription)")
}
7. ละเว้นการทำให้โค้ดสับสนและวิศวกรรมย้อนกลับ
ไบนารี Swift สามารถวิศวกรรมย้อนกลับได้เพื่อเปิดเผยตรรกะทางธุรกิจ อัลกอริธึม หรือความลับที่ซ่อนอยู่ ผู้โจมตีใช้เครื่องมือเช่น Hopper Disassembler, class-dump หรือ IDA Pro เพื่อถอดรหัสแอปของคุณและวิเคราะห์วิธีการทำงานภายใน ความเสี่ยงนี้มักจะถูกประเมินต่ำเกินไป โดยเฉพาะแอปขนาดเล็ก แต่แอปใดก็ตามก็สามารถตกเป็นเป้าหมายได้
ซึ่งหมายความว่าเมื่อคุณคอมไพล์แอป Swift ไบนารีผลลัพธ์ที่ได้จะประกอบด้วย:
-
ชื่อคลาสและลายเซ็นวิธีการ
-
ตัวอักษรสตริง (URL, ข้อความแสดงข้อผิดพลาด, คีย์)
-
โครงสร้างของตรรกะโค้ดของคุณ
-
การใช้งานอัลกอริทึม
ผู้โจมตีสามารถดึงข้อมูลนี้และใช้ข้อมูลดังกล่าวเพื่อทำความเข้าใจขั้นตอนการตรวจสอบสิทธิ์ของแอปและหลีกเลี่ยงข้อมูลดังกล่าว คัดลอกอัลกอริธึมที่เป็นกรรมสิทธิ์ของคุณ ค้นหาจุดสิ้นสุด API แบบฮาร์ดโค้ดหรือคีย์ที่คุณคิดว่า "ถูกซ่อน" ค้นพบคุณสมบัติระดับพรีเมียมเพื่อปลดล็อกโดยไม่ต้องจ่ายเงิน และอื่นๆ
เหตุใดจึงไม่ดี – ตัวอย่างจริง: ป>
สมมติว่าคุณมีการตรวจสอบคุณสมบัติระดับพรีเมียมในแอปของคุณ:
class FeatureManager {
func isPremiumUser() -> Bool {
// Check if user has premium access
let hasSubscription = UserDefaults.standard.bool(forKey: "premium_unlocked")
return hasSubscription
}
func unlockPremiumFeature() {
guard isPremiumUser() else {
showPaywall()
return
}
// Show premium content
showPremiumContent()
}
}
ผู้โจมตีสามารถย้อนกลับวิศวกรรมแอปของคุณและค้นพบวิธีการ 87 ควบคุมการเข้าถึง และเพียงตรวจสอบ 91 คีย์ชื่อ 102 . จากนั้นพวกเขาจะรู้ว่าพวกเขาสามารถใช้เครื่องมือจัดการรันไทม์เพื่อตั้งค่านี้เป็นจริง โดยข้ามเพย์วอลล์ของคุณไปโดยสิ้นเชิง
วิธีแก้ไข: ป>
1. ใช้การเพิ่มประสิทธิภาพ Swift Compiler ป>
เปิดใช้งานแฟล็กการเพิ่มประสิทธิภาพที่จะตัดสัญลักษณ์การดีบักและทำให้ไบนารีอ่านยากขึ้น:
// In your build settings:
// - Set "Optimization Level" to "-O" (or -Osize) for release builds
// - Enable "Strip Debug Symbols During Copy" = YES
// - Set "Strip Style" to "All Symbols"
การดำเนินการนี้จะลบชื่อฟังก์ชันและทำให้โค้ดที่คอมไพล์อ่านได้น้อยลง แม้ว่าชื่อคลาส/เมธอดจะยังคงมองเห็นได้เพียงบางส่วนก็ตาม
<แข็งแกร่ง>2. ใช้เครื่องมือสร้างความสับสนให้กับสัญลักษณ์ ป>
เครื่องมืออย่าง SwiftShield สามารถเปลี่ยนชื่อคลาส วิธีการ และคุณสมบัติของคุณให้เป็นชื่อที่ไม่มีความหมายได้:
// Before obfuscation (readable to attackers):
class FeatureManager {
func isPremiumUser() -> Bool { ... }
}
// After obfuscation (harder to understand):
class a7f3b2 {
func x9k2m() -> Bool { ... }
}
แม้ว่าวิธีนี้จะไม่ป้องกันวิศวกรรมย้อนกลับ แต่ก็ทำให้ผู้โจมตีเข้าใจสิ่งที่โค้ดทำได้ยากขึ้นอย่างมาก
<แข็งแกร่ง>3. ย้ายตรรกะที่ละเอียดอ่อนไปยังเซิร์ฟเวอร์ (แนวทางปฏิบัติที่ดีที่สุด) ป>
แทนที่จะตรวจสอบสถานะพรีเมียมในเครื่อง ให้ยืนยันฝั่งเซิร์ฟเวอร์:
// ✅ Secure approach - Server validates everything
class FeatureManager {
func unlockPremiumFeature() async {
do {
// Server checks if user truly has premium access
let hasAccess = try await backend.verifyPremiumAccess(userId: currentUserId)
if hasAccess {
showPremiumContent()
} else {
showPaywall()
}
} catch {
// Handle error
showPaywall()
}
}
}
วิธีการทำงาน: ป>
แบ็กเอนด์ของคุณรักษาแหล่งที่มาของความจริงเกี่ยวกับการเข้าถึงระดับพรีเมียม แม้ว่าผู้โจมตีจะวิศวกรรมย้อนกลับแอปของคุณและพยายามเลี่ยงการตรวจสอบ เซิร์ฟเวอร์จะปฏิเสธคำขอที่ไม่ได้รับอนุญาต แอปจะกลายเป็นเพียงเลเยอร์ UI ในขณะที่การตัดสินใจที่สำคัญทั้งหมดเกิดขึ้นฝั่งเซิร์ฟเวอร์ ซึ่งผู้โจมตีไม่สามารถจัดการได้
หลักการสำคัญคือการถือว่าโค้ดของแอปเป็นแบบสาธารณะ อย่าพึ่งพาการตรวจสอบฝั่งไคลเอ็นต์สำหรับการดำเนินการที่มีความสำคัญด้านความปลอดภัย เช่น การชำระเงิน การควบคุมการเข้าถึง หรือการตรวจสอบสิทธิ์ ใช้การสร้างความสับสนเพื่อทำให้วิศวกรรมย้อนกลับยากขึ้น แต่ท้ายที่สุดแล้ว ย้ายตรรกะที่ละเอียดอ่อนไปยังแบ็กเอนด์ที่ปลอดภัยของคุณ
8. ไลบรารีของบุคคลที่สามที่ไม่ปลอดภัย
ห้องสมุดบุคคลที่สามมีความเสี่ยงหากถูกแฮ็กหรือล้าสมัย นักพัฒนาอาจจัดลำดับความสำคัญของฟังก์ชันการทำงานของแอปโดยไม่ได้ตั้งใจเหนือความเสี่ยงด้านความปลอดภัยที่อาจเกิดขึ้นจากการขึ้นต่อกัน และเครื่องมือ ETL สามารถช่วยเพิ่มเติมได้โดยการปรับปรุงการตรวจสอบและการประมวลผลข้อมูลที่เกี่ยวข้องกับการขึ้นต่อกันเพื่อระบุช่องโหว่ได้อย่างมีประสิทธิภาพมากขึ้น
ในระดับที่กว้างขึ้น การใช้แนวปฏิบัติด้านความปลอดภัยของศูนย์ข้อมูลที่แข็งแกร่งช่วยให้มั่นใจได้ว่าแม้ว่าส่วนประกอบของบุคคลที่สามจะมีความเสี่ยง แต่โครงสร้างพื้นฐานพื้นฐานยังคงต้านทานการโจมตีได้
วิธีการแก้ไข:
-
ใช้เฉพาะไลบรารีคุณภาพสูงและได้รับการดูแลอย่างดีเท่านั้น
-
อัปเดตการขึ้นต่อกันและติดตาม CVE (ช่องโหว่และความเสี่ยงทั่วไป)
-
ตรวจสอบรหัสไลบรารีหากจัดการข้อมูลที่ละเอียดอ่อน
9. การตรวจสอบสิทธิ์ไบโอเมตริกซ์และหลายปัจจัยไม่เพียงพอ
แอปพลิเคชันส่วนใหญ่อาศัยรหัสผ่านเพียงอย่างเดียว ซึ่งเสี่ยงต่อการถูกแฮ็ก การเปิดใช้งานข้อมูลไบโอเมตริก เช่น Face ID หรือ Touch ID จะช่วยเพิ่มความปลอดภัยให้กับผู้ใช้
วิธีการแก้ไข:
-
ผูกกรอบ LocalAuthentication สำหรับการตรวจสอบสิทธิ์แบบไบโอเมตริกซ์
-
รวมข้อมูลไบโอเมตริกเข้ากับการตรวจสอบสิทธิ์บนเซิร์ฟเวอร์สำหรับการตรวจสอบสิทธิ์แบบหลายปัจจัย (MFA)
import LocalAuthentication
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Access your account") { success, authError in
DispatchQueue.main.async {
if success {
// Proceed securely
} else {
// Handle failure (authError may contain the reason)
print("Authentication failed: \(authError?.localizedDescription ?? "Unknown error")")
}
}
}
} else {
// Biometrics not available, check error for details
print("Biometrics unavailable: \(error?.localizedDescription ?? "Unknown error")")
}
10. ไม่สนใจการทดสอบความปลอดภัยเป็นระยะ
แอปต่างๆ แม้จะปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดในระหว่างการพัฒนา แต่ก็มักจะมีช่องโหว่ที่ยังไม่ได้สำรวจซึ่งเกิดจากการโต้ตอบที่ซับซ้อน การพึ่งพาของบุคคลที่สาม หรือเวกเตอร์การโจมตีที่เพิ่งค้นพบ การทดสอบความปลอดภัยเป็นประจำถือเป็นสิ่งสำคัญอย่างยิ่งในการค้นหาช่องโหว่เหล่านี้ก่อนที่ผู้โจมตีจะโจมตีช่องโหว่เหล่านั้น
การทดสอบความปลอดภัยควรเกิดขึ้นในหลายขั้นตอน โดยใช้เครื่องมือและแนวทางปฏิบัติที่สามารถเข้าถึงได้:
-
การสแกนความปลอดภัยอัตโนมัติ: ทำงานโดยอัตโนมัติกับทุกบิวด์เพื่อตรวจจับปัญหาทั่วไป
-
การตรวจสอบโค้ดที่ดำเนินการด้วยตนเอง: การตรวจสอบโค้ดของคุณโดยเน้นเรื่องความปลอดภัยเป็นประจำโดยใช้แนวทางที่กำหนดไว้
-
เครื่องมือสแกนช่องโหว่: ใช้เครื่องมือฟรีเช่น MobSF เพื่อวิเคราะห์แอปของคุณเพื่อหาข้อบกพร่องด้านความปลอดภัย
-
การตรวจสอบการพึ่งพา: การตรวจสอบไลบรารีของบุคคลที่สามเพื่อหาช่องโหว่ด้านความปลอดภัยที่ทราบ
วิธีแก้ไข: ป>
1. ใช้การสแกนความปลอดภัยอัตโนมัติใน CI/CD ป>
รวมเครื่องมือสแกนความปลอดภัยเข้ากับไปป์ไลน์การผสานรวมอย่างต่อเนื่องของคุณ เพื่อให้ตรวจสอบการเปลี่ยนแปลงโค้ดทุกครั้งโดยอัตโนมัติ:
# Example: GitHub Actions workflow for automated security scanning
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run MobSF Security Scan
run: |
# Mobile Security Framework - scans for common vulnerabilities
docker run -v $(pwd):/app opensecurity/mobile-security-framework-mobsf
- name: Dependency Vulnerability Check
run: |
# Check CocoaPods/SPM dependencies for known CVEs
brew install dependency-check
dependency-check --scan ./Podfile.lock --format JSON
- name: Secret Detection
run: |
# Detect accidentally committed secrets
brew install truffleHog
truffleHog filesystem . --json
- name: Fail build on critical issues
run: |
if grep -q "CRITICAL" security-report.json; then
echo "Critical security issues found!"
exit 1
fi
ตรวจสอบการสแกนอัตโนมัติสำหรับ: ป>
-
คีย์ API โทเค็นหรือรหัสผ่านแบบฮาร์ดโค้ด
-
การกำหนดค่าเครือข่ายไม่ปลอดภัย (อนุญาต HTTP แทน HTTPS)
-
อัลกอริธึมการเข้ารหัสที่อ่อนแอ
-
ช่องโหว่ที่ทราบในไลบรารีบุคคลที่สาม
-
การตรวจสอบใบรับรอง SSL/TLS ที่ไม่เหมาะสม
-
การจัดเก็บข้อมูลที่ไม่ปลอดภัย (การจัดเก็บข้อมูลที่ละเอียดอ่อนใน UserDefaults)
-
สิทธิ์ของแอปมากเกินไป
ตัวอย่างผลลัพธ์จากการสแกนอัตโนมัติ: ป>
Security Scan Results:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[CRITICAL] Hardcoded API Key Found
File: APIClient.swift:15
Issue: API key "sk_live_abc123..." detected in source code
[HIGH] Insecure HTTP Connection
File: NetworkManager.swift:42
Issue: App allows cleartext HTTP traffic to api.example.com
Fix: Enforce HTTPS or add exception to Info.plist if required
[MEDIUM] Weak Encryption Algorithm
File: DataEncryption.swift:28
Issue: Using MD5 for hashing (cryptographically broken)
Fix: Use SHA-256 or better
[LOW] Outdated Dependency
Library: Alamofire 4.2.0
Issue: Known vulnerability CVE-2021-12345
Fix: Update to version 5.6.0 or later
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Build Failed: 2 critical issues must be fixed before deployment
<แข็งแกร่ง>2. ใช้ MobSF (กรอบงานความปลอดภัยบนมือถือ) สำหรับการวิเคราะห์ช่องโหว่ ป>
MobSF เป็นเครื่องมืออัตโนมัติฟรีที่วิเคราะห์แอป iOS ของคุณเพื่อหาปัญหาด้านความปลอดภัย:
# Install and run MobSF locally
docker pull opensecurity/mobile-security-framework-mobsf
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf
# Upload your .ipa file through the web interface at localhost:8000
# MobSF will analyze and provide a detailed security report
สิ่งที่ MobSF ตรวจสอบ:
-
การวิเคราะห์แบบไบนารีสำหรับความลับแบบฮาร์ดโค้ด
-
รูปแบบการจัดเก็บข้อมูลที่ไม่ปลอดภัย
-
การใช้งานการเข้ารหัสที่อ่อนแอ
-
การเชื่อมต่อเครือข่ายที่ไม่ปลอดภัย
-
แนวทางปฏิบัติที่ดีที่สุดด้านคุณภาพและความปลอดภัยของโค้ด
-
การปฏิบัติตามมาตรฐานความปลอดภัย
<แข็งแกร่ง>3. ดำเนินการตรวจสอบโค้ดเป็นประจำโดยใช้ OWASP MSTG ป>
ใช้คู่มือการทดสอบความปลอดภัยบนมือถือ OWASP เป็นรายการตรวจสอบเพื่อตรวจสอบโค้ดของคุณเอง:
// Example: Following OWASP MSTG recommendations for secure storage
class SecureStorage {
// ❌ Insecure - UserDefaults is not encrypted
func saveTokenInsecurely(_ token: String) {
UserDefaults.standard.set(token, forKey: "authToken")
}
// ✅ Secure - Using Keychain as OWASP recommends
func saveTokenSecurely(_ token: String) throws {
let data = token.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "authToken",
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw StorageError.saveFailed
}
}
}
รายการตรวจสอบ OWASP MSTG สำหรับการตรวจสอบตนเอง: ป>
-
[ ] ข้อมูลที่ละเอียดอ่อนทั้งหมดได้รับการเข้ารหัสเมื่อไม่มีการใช้งานหรือไม่
-
[ ] HTTPS บังคับใช้กับการโทรทุกเครือข่ายหรือไม่
-
[ ] มีการตรวจสอบใบรับรองอย่างถูกต้องหรือไม่
-
[ ] ข้อมูลที่ละเอียดอ่อนถูกแยกออกจากบันทึกหรือไม่
-
[ ] คีย์ API และความลับไม่ได้ฮาร์ดโค้ดใช่หรือไม่
-
[ ] อินพุตของผู้ใช้ได้รับการตรวจสอบและฆ่าเชื้อแล้วหรือไม่
-
[ ] โทเค็นการรับรองความถูกต้องถูกเก็บไว้อย่างปลอดภัยในพวงกุญแจหรือไม่
<แข็งแกร่ง>4. การสแกนการพึ่งพาอัตโนมัติ ป>
ตรวจสอบการพึ่งพาของคุณอย่างต่อเนื่องเพื่อหาช่องโหว่ที่เพิ่งค้นพบ:
# For CocoaPods projects
gem install cocoapods-audit
pod audit
# For Swift Package Manager
# Use GitHub Dependabot (free for public repos) or
brew install swift-outdated
swift-outdated
และตั้งค่าการแจ้งเตือนอัตโนมัติด้วยเครื่องมือเหล่านี้:
-
ขึ้นอยู่กับ GitHub: สร้าง PR โดยอัตโนมัติเมื่อตรวจพบการพึ่งพาที่มีช่องโหว่ (ฟรี)
-
สนิก :ระดับฟรีสำหรับโครงการโอเพ่นซอร์ส
-
การตรวจสอบการพึ่งพา OWASP: เครื่องมือบรรทัดคำสั่งฟรี
บทสรุป
การพัฒนาแอพ iOS ที่ปลอดภัยโดยใช้ Swift ล้วนเป็นเรื่องของการคิดก้าวหน้า คุณควรทำทุกอย่างที่ทำได้เพื่อหลีกเลี่ยงข้อผิดพลาดทั่วไปเหล่านี้ เช่น การจัดเก็บข้อมูลที่ไม่ปลอดภัย การสื่อสารเครือข่ายที่ไม่ดี ความลับแบบฮาร์ดโค้ด หรือการตรวจสอบสิทธิ์ที่ไม่ดี
การใช้พวงกุญแจสำหรับข้อมูลที่เป็นความลับ ซึ่งต้องใช้ HTTPS การตรวจสอบอินพุต และการตรวจสอบสิทธิ์แบบหลายปัจจัย ล้วนเป็นขั้นตอนที่ช่วยลดความเสี่ยง
การทดสอบช่องโหว่ด้านความปลอดภัยเป็นประจำและการจำกัดการใช้ไลบรารีของบุคคลที่สามยังช่วยเพิ่มความปลอดภัยให้กับแอปของคุณได้อีกด้วย
การรักษาความปลอดภัยเป็นความรับผิดชอบอย่างต่อเนื่อง Swift มีเครื่องมือให้ แต่นักพัฒนาจำเป็นต้องใช้เครื่องมือเหล่านั้นอย่างระมัดระวัง การรักษาความปลอดภัยที่ได้รับการจัดการตั้งแต่เริ่มต้นจะปกป้องข้อมูลของผู้ใช้ สร้างความไว้วางใจ และปกป้องชื่อเสียงของแอปพลิเคชัน
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น