샌드박스(Sandbox)의 개념과 역할

샌드박스(Sandbox)의 개념과 역할

Swift 코드를 사용한 iOS/iPadOS 샌드박스의 비밀 밝히기

💡

개요

안녕하세요, 여러분. 이 아티클에서는 iOS 샌드박스가 무엇인지, 어떻게 작동하는지, 개발자들이 자신의 앱 샌드박스에 어떻게 접근할 수 있는지, 그리고 사용자가 데이터를 저장하고 그 데이터에 접근할 수 있도록 하는 방법에 대해 알아보겠습니다.

컴퓨터 보안에서 샌드박스는 실행 중인 프로그램을 분리하기 위한 보안 메커니즘으로, 일반적으로 시스템 오류 및/또는 소프트웨어 취약점이 확산되는 것을 완화하기 위한 노력입니다.

iOS에서 샌드박스란 무엇인가요?

보안상의 이유로 iOS 앱의 파일 시스템과의 상호작용은 앱의 샌드박스 디렉토리 내부의 디렉토리로 제한됩니다. 서드파티 앱이 설치될 때마다 운영 체제는 자동으로 해당 앱의 샌드박스를 생성합니다. 샌드박스는 앱이 작동하는 컨테이너로 볼 수 있습니다.

샌드박스에는 여러 디렉토리가 포함되어 있습니다.

번들 컨테이너 (Bundle Container)

이것은 앱의 번들입니다. 이 디렉토리에는 앱 바이너리와 앱에서 사용되는 자산과 같은 모든 리소스가 포함되어 있습니다. 이 디렉토리에 쓸 수 없습니다. 변조를 방지하기 위해 번들 디렉토리는 설치 시 암호화를 사용하여 서명됩니다. 이 디렉토리에 쓰면 서명이 변경되어 앱이 실행되지 않습니다. 그러나 Swift API를 사용하여 앱 번들에 저장된 모든 리소스에 대해 읽기 전용 액세스를 얻을 수 있습니다.

데이터 컨테이너 (Data Container)

앱의 데이터를 저장합니다. 예를 들어 사용자에게 텍스트 파일을 저장하거나 이미지를 저장하는 동안 생성된 데이터, 애플리케이션 캐시 등입니다. 하위 디렉토리에는 다음과 같은 것들이 있습니다.

Documents

이 디렉토리를 사용하여 사용자 생성 콘텐츠를 저장하세요. 이 디렉토리의 내용은 iTunes와 같은 파일 공유 애플리케이션을 통해 사용자에게 제공될 수 있으며, iCloud를 사용하여 백업할 수도 있습니다.

Library

이곳은 사용자 데이터 파일이 아닌 모든 파일의 최상위 디렉토리입니다.

tmp

이 디렉토리를 사용하여 앱 실행 간에 지속할 필요가 없는 임시 파일을 작성하세요. 앱은 더 이상 필요하지 않은 파일을 이 디렉토리에서 제거해야 합니다. 이 디렉토리의 내용은 iTunes나 iCloud에 의해 백업되지 않습니다.

샌드박스의 장점

  • 샌드박스는 각 앱을 위한 데이터와 정보의 개인 환경을 보호하고 유지합니다.

  • 샌드박스는 시스템 리소스를 보호하여 잠재적인 해커로부터 발생할 수 있는 피해를 최소화할 수 있습니다.

  • iOS 또는 iPadOS 장치에 앱이 설치되면, 시스템은 이를 위한 고유한 디렉토리를 생성합니다.

개발자로서 앱의 샌드박스 조사하기

  1. Xcode에서 UIKit을 사용하여 빈 iOS 앱을 만드세요.

  2. 스토리보드를 열고 내비게이션 컨트롤러를 추가하세요.

  3. UIImageView 를 추가하세요.

시뮬레이터에서 앱을 테스트하면 앱 샌드박스의 데이터 컨테이너를 쉽게 검사할 수 있습니다.

참고: 이 방법은 시뮬레이터에서 앱을 테스트할 때만 작동하며 실제 장치에서는 작동하지 않습니다.

  • ViewController.swift를 열고 viewDidLoad() 안에 이 코드 줄을 추가하세요.
print(NSHomeDirectory())

이제 앱을 실행하고 출력 콘솔을 조사하면, 앱이 시뮬레이터에 설치될 때 생성되는 샌드박스의 데이터 컨테이너 경로가 출력되는 것을 볼 수 있습니다.

시뮬레이터이므로 Mac OS에서 앱의 샌드박스를 검사할 수 있습니다.

  • 출력 콘솔에서 경로를 복사하여 터미널에 붙여넣으세요.

보시다시피 우리는 우리의 앱 샌드박스의 데이터 컨테이너 내부에 있습니다. 그리고 그것은 이전에 언급한 하위 디렉토리를 가지고 있습니다.

파인더를 열고 Command+Shift+G를 눌러 경로를 붙여넣고 이동을 누르면 열 수 있습니다.

Swift를 사용하여 샌드박스의 Documents 디렉토리에 데이터 저장하기

  • ViewController.swift를 열고 다음 코드를 추가하세요.
override func viewDidLoad() {
    super.viewDidLoad()

    title = "SandBoxDemo"

    print(NSHomeDirectory())

    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(pickImage))
    navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(addNote))
}

여기에서 카메라 아이콘과 노트 아이콘이 있는 두 개의 버튼을 내비게이션 바에 추가합니다. 버튼을 탭하면 우리가 만들 pickImageaddNote 메서드를 호출합니다.

  • UIImageView에 대한 IBOutlet과 빈 문자열 배열을 생성합니다.
var notes = [String]()
@IBOutlet weak var imageView: UIImageView!
  • 이제 saveImage()라는 함수를 만들어서 이 함수를 사용하여 이미지를 앱의 샌드박스의 문서 디렉토리에 저장합니다.
func saveImage(image: UIImage) {
    // Documents 디렉토리의 경로
    var path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

    // 우리 이미지에 대한 랜덤한 UUID 파일 이름
    let filename = "\(UUID().uuidString).jpg"

    // 파일 이름을 포함한 파일의 전체 경로
    path.appendPathComponent(filename)
    // 디버그 프린트
    print(path.absoluteString)


    /* 여기서는 UIImage를 JPEG 데이터로 변환하여
     샌드박스의 Documents 디렉토리 안에 저장합니다. */
    if let jpegImageData = image.jpegData(compressionQuality: 1.0) {
        do {
            try jpegImageData.write(to: path)
        }
        catch {
            print(error.localizedDescription)
        }
    }
}

보시다시피 FileManager 싱글톤 인스턴스의 urls() 메서드는 Path URL의 배열을 반환하지만, 우리 샌드박스에는 Documents 디렉토리가 하나만 있다는 것을 알고 있으므로 배열의 첫 번째 요소를 가져옵니다.

  • 카메라 버튼을 내비게이션 바에서 사용자가 탭할 때 호출되는 pickImage()라는 함수를 만듭니다. 이 메서드에서는 사용자가 자신의 전화 갤러리에서 이미지를 선택하고 UIImage에 설정할 수 있도록 허용합니다.
@objc func pickImage() {
    let picker = UIImagePickerController()
    picker.delegate = self
    picker.allowsEditing = true // 유저가 이미지를 Crop할 수 있도록 허용

    present(picker, animated: true, completion: nil)
}
  • 이제 UIImagePickerController에서 delegateself로 설정했는지 확인한 후, 선택한 이미지를 UIImage로 설정할 수 있습니다.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

    // .editedImage 키를 이용해 info[] 디렉토리에서 사용자가 선택한 이미지를 가져옵니다.
    if let image = info[.editedImage] as? UIImage {
        // UIImage 뷰에서 이미지를 설정
        imageView.image = image
        // 또한 이미지를 Sandbox의 Documents 디렉토리에 저장
        saveImage(image: image)
    }

    // 사용자가 이미지를 선택하면 Picker를 해제
    dismiss(animated: true, completion: nil)
}
  • 이제 사용자가 텍스트 데이터를 텍스트 파일에 작성하고 이를 Sandbox에 .txt 파일로 저장할 수 있도록 할 것입니다.

  • saveTextFile()라는 메서드를 만들고 그 안에 다음 코드를 추가하세요.

func saveTextFile() {
    // 샌드박스의 Documents 디렉토리 경로를 가져옵니다.
    var path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

    // 텍스트 파일의 이름
    let filename = "notes.txt"

    // 파일 이름을 포함한 전체 경로
    path.appendPathComponent(filename)

    // 디버그 프린트
    print(path.absoluteString)

    do {
        /* 여기서는 문자열 배열의 요소를 \n 문자로 구분하여
         각 노트를 텍스트 파일 안의 새 줄에 저장할 수 있도록 합니다. */
        try self.notes.joined(separator: "\n").write(to: path, atomically: false, encoding: .utf8)
    }

    catch {
        print(error)
    }
}
  • addNote() 메서드를 생성하여 사용자가 내비게이션 바에서 addnote 버튼을 탭할 때 호출되도록 합니다.
@objc func addNote() {
    /* 여기에 텍스트 필드가 있는 팝업을 표시하고
     입력한 텍스트를 노트 문자열 배열에 추가한 후
     saveTextFile() 메서드를 호출하여
     Documents 디렉토리에 저장합니다. */
    let ac = UIAlertController(title: "Enter the note", message: nil, preferredStyle: .alert)

    ac.addTextField(configurationHandler: nil)

    ac.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in

        if let enteredText = ac.textFields?.first?.text {
            self.notes.append(enteredText)
            self.SaveTextFile()
        }
    }))

    ac.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

    present(ac, animated: true, completion: nil)
}
  • 이제 시뮬레이터에서 앱을 실행하세요.

  • 이제 터미널이나 GUI에서 샌드박스의 Documents 디렉토리를 열면 거기에서 우리의 이미지와 텍스트 파일을 볼 수 있습니다.

그러나 개발자로서 우리는 시뮬레이터에서만 앱의 샌드박스에 저장된 데이터에 접근할 수 있지만 실제 사용자는 시뮬레이터를 사용하지 않고 물리적 장치를 사용하므로 위의 방법으로는 접근할 수 없습니다. 이제 우리는 사용자가 iOS/iPadOS 파일 관리자 앱을 사용하여 앱의 샌드박스 문서 디렉토리에 저장된 데이터를 볼 수 있도록 허용하는 작은 변경을 합니다!

  • info.plist를 열고 다음 키를 추가한 후 부울 값을 '예'로 설정합니다.

응용 프로그램은 iTunes 파일 공유를 지원합니다 — 이 속성은 사용자가 iPhone/iPad를 Mac에 연결할 때 샌드박스의 Documents 디렉토리에서 notes.tx 파일과 이미지 파일에 접근할 수 있도록 합니다.

문서 직접 열기 지원 — 이 속성은 사용자가 Mac과 연결할 필요 없이 iPad/iPhone 기본 파일 관리 앱을 사용하여 notes.txt 및 이미지 파일에 직접 접근할 수 있도록 합니다.

이제 이 앱을 시뮬레이터나 실제 장치에서 실행하면 기본 파일 관리자 앱을 열어 샌드박스에 저장된 파일에 직접 접근할 수 있는 파일 앱을 볼 수 있습니다.

Github Repository

역자 코멘트

CS 스터디에서 iOS의 샌드박스에 대해 공부하다가 좋은 글을 발견해 번역해 보았습니다.

참고로, iOS의 앱 샌드박스는 2007년 첫 출시부터 지원했다고 알고 있는데 iOS의 보안 수준이 높다는건 익히 들어 알고 있었지만 어떤 방식 덕분인지는 잘 몰랐기에 이번에 찾아보고, 이 글을 번역해보면서 많이 배웠습니다.

안드로이드에서의 보안 정책도 궁금해서 찾아봤는데, 안드로이드 또한 킷캣 이후로 UID 기반의 커널 수준 샌드박싱으로 보안 수준을 높였다고 하네요! 자세하게 찾아보지는 못했습니다.

혹시 번역에 오류가 있거나, 관련된 문의는 으로 연락주세요!