iOS Universal Links

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°

Introduction

Universal linksλŠ” μ‚¬μš©μžκ°€ Safari λ¦¬λ””λ ‰μ…˜ 없이 μ•±μ—μ„œ 직접 μ½˜ν…μΈ λ₯Ό μ—΄ 수 μžˆλ„λ‘ ν•˜λŠ” λ§€λ„λŸ¬μš΄ λ¦¬λ””λ ‰μ…˜ κ²½ν—˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ§ν¬λŠ” κ³ μœ ν•˜κ³  μ•ˆμ „ν•˜μ—¬ λ‹€λ₯Έ 앱이 μ£Όμž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ΄λŠ” μ›Ήμ‚¬μ΄νŠΈμ˜ 루트 디렉토리에 apple-app-site-association JSON νŒŒμΌμ„ ν˜ΈμŠ€νŒ…ν•¨μœΌλ‘œμ¨ 보μž₯되며, μ›Ήμ‚¬μ΄νŠΈμ™€ μ•± κ°„μ˜ 검증 κ°€λŠ₯ν•œ 링크λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. 앱이 μ„€μΉ˜λ˜μ§€ μ•Šμ€ 경우, Safariκ°€ μ‚¬μš©μžμ—κ²Œ μ›ΉνŽ˜μ΄μ§€λ‘œ μ•ˆλ‚΄ν•˜μ—¬ μ•±μ˜ 쑴재λ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.

침투 ν…ŒμŠ€ν„°μ—κ²Œ apple-app-site-association νŒŒμΌμ€ λ―Όκ°ν•œ 경둜λ₯Ό λ“œλŸ¬λ‚Ό 수 있기 λ•Œλ¬Έμ— 특히 관심이 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ—λŠ” μΆœμ‹œλ˜μ§€ μ•Šμ€ κΈ°λŠ₯κ³Ό κ΄€λ ¨λœ κ²½λ‘œκ°€ 포함될 수 μžˆμŠ΅λ‹ˆλ‹€.

Associated Domains Entitlement λΆ„μ„ν•˜κΈ°

κ°œλ°œμžλŠ” Xcode의 Capabilities νƒ­μ—μ„œ Associated Domainsλ₯Ό κ΅¬μ„±ν•˜κ±°λ‚˜ .entitlements νŒŒμΌμ„ κ²€μ‚¬ν•˜μ—¬ Universal Linksλ₯Ό ν™œμ„±ν™”ν•©λ‹ˆλ‹€. 각 도메인은 applinks:둜 접두사가 λΆ™μŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Telegram의 ꡬ성은 λ‹€μŒκ³Ό 같이 λ‚˜νƒ€λ‚  수 μžˆμŠ΅λ‹ˆλ‹€:

<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:telegram.me</string>
<string>applinks:t.me</string>
</array>

더 포괄적인 톡찰λ ₯을 μ›ν•˜μ‹œλ©΄ λ³΄κ΄€λœ Apple Developer Documentation을 μ°Έμ‘°ν•˜μ„Έμš”.

컴파일된 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‚¬μš©ν•˜λŠ” 경우, 이 κ°€μ΄λ“œμ— μ„€λͺ…λœ λŒ€λ‘œ κΆŒν•œμ„ μΆ”μΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Apple App Site Association 파일 κ°€μ Έμ˜€κΈ°

apple-app-site-association νŒŒμΌμ€ κΆŒν•œμ— μ§€μ •λœ 도메인을 μ‚¬μš©ν•˜μ—¬ μ„œλ²„μ—μ„œ 가져와야 ν•©λ‹ˆλ‹€. 파일이 https://<domain>/apple-app-site-association (λ˜λŠ” /.well-known/apple-app-site-association)μ—μ„œ HTTPSλ₯Ό 톡해 직접 μ ‘κ·Ό κ°€λŠ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€. Apple App Site Association (AASA) Validator와 같은 도ꡬ가 이 과정에 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€.

macOS/Linux μ…Έμ—μ„œμ˜ λΉ λ₯Έ μ—΄κ±°

# ent.xml에 κΆŒν•œμ„ μΆ”μΆœν–ˆλ‹€κ³  κ°€μ •
doms=$(plutil -extract com.apple.developer.associated-domains xml1 -o - ent.xml | \
       grep -oE 'applinks:[^<]+' | cut -d':' -f2)
for d in $doms; do
  echo "[+] $d에 λŒ€ν•œ AASA κ°€μ Έμ˜€λŠ” 쀑";
  curl -sk "https://$d/.well-known/apple-app-site-association" | jq '.'
done

μ•±μ—μ„œμ˜ μœ λ‹ˆλ²„μ„€ 링크 처리

앱은 μœ λ‹ˆλ²„μ„€ 링크λ₯Ό μ˜¬λ°”λ₯΄κ²Œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ νŠΉμ • λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ°Ύμ•„μ•Ό ν•  μ£Όμš” λ©”μ„œλ“œλŠ” application:continueUserActivity:restorationHandler:μž…λ‹ˆλ‹€. μ²˜λ¦¬λ˜λŠ” URL의 μŠ€ν‚΄μ΄ HTTP λ˜λŠ” HTTPSμ—¬μ•Ό ν•˜λ©°, λ‹€λ₯Έ μŠ€ν‚΄μ€ μ§€μ›λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

데이터 ν•Έλ“€λŸ¬ λ©”μ„œλ“œ 검증

μœ λ‹ˆλ²„μ„€ 링크가 앱을 μ—΄λ©΄, NSUserActivity 객체가 URLκ³Ό ν•¨κ»˜ 앱에 μ „λ‹¬λ©λ‹ˆλ‹€. 이 URL을 μ²˜λ¦¬ν•˜κΈ° 전에 λ³΄μ•ˆ μœ„ν—˜μ„ λ°©μ§€ν•˜κΈ° μœ„ν•΄ 이λ₯Ό κ²€μ¦ν•˜κ³  μ •λ¦¬ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€. λ‹€μŒμ€ 이 과정을 λ³΄μ—¬μ£ΌλŠ” Swift μ˜ˆμ œμž…λ‹ˆλ‹€:

func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Check for web browsing activity and valid URL
if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL {
application.open(url, options: [:], completionHandler: nil)
}

return true
}

URLsλŠ” 특히 λ§€κ°œλ³€μˆ˜λ₯Ό ν¬ν•¨ν•˜λŠ” 경우 잠재적인 μŠ€ν‘Έν•‘μ΄λ‚˜ 잘λͺ»λœ λ°μ΄ν„°λ‘œλΆ€ν„° λ³΄ν˜Έν•˜κΈ° μœ„ν•΄ μ‹ μ€‘ν•˜κ²Œ ꡬ문 λΆ„μ„λ˜κ³  κ²€μ¦λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. NSURLComponents APIλŠ” 이λ₯Ό μœ„ν•΄ μœ μš©ν•˜λ©°, μ•„λž˜μ— μ„€λͺ…λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:

func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let path = components.path,
let params = components.queryItems else {
return false
}

if let albumName = params.first(where: { $0.name == "albumname" })?.value,
let photoIndex = params.first(where: { $0.name == "index" })?.value {
// Process the URL with album name and photo index

return true

} else {
// Handle invalid or missing parameters

return false
}
}

λΆ€μ§€λŸ°ν•œ ꡬ성 및 검증을 톡해 κ°œλ°œμžλŠ” μœ λ‹ˆλ²„μ„€ 링크가 μ‚¬μš©μž κ²½ν—˜μ„ ν–₯μƒμ‹œν‚€λ©΄μ„œ λ³΄μ•ˆ 및 개인 정보 보호 기쀀을 μœ μ§€ν•˜λ„λ‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

일반적인 취약점 및 νŽœν…ŒμŠ€νŒ… 점검

#μ•½μ ν…ŒμŠ€νŠΈ λ°©λ²•μ•…μš© / 영ν–₯
1AASA 파일의 κ³Όλ„ν•œ paths / components (예: "/": "*" λ˜λŠ” "/a/*"와 같은 μ™€μΌλ“œμΉ΄λ“œ).β€’ λ‹€μš΄λ‘œλ“œν•œ AASAλ₯Ό κ²€μ‚¬ν•˜κ³  *, ν›„ν–‰ μŠ¬λž˜μ‹œ λ˜λŠ” {"?": …} κ·œμΉ™μ„ μ°ΎμŠ΅λ‹ˆλ‹€.
β€’ μ—¬μ „νžˆ κ·œμΉ™κ³Ό μΌμΉ˜ν•˜λŠ” μ•Œλ €μ§€μ§€ μ•Šμ€ λ¦¬μ†ŒμŠ€λ₯Ό μš”μ²­ν•΄ λ³΄μ„Έμš” (https://domain.com/a/evil?_p_dp=1).
μœ λ‹ˆλ²„μ„€ 링크 ν•˜μ΄μž¬ν‚Ή: λ™μΌν•œ 도메인을 λ“±λ‘ν•œ μ•…μ„± iOS 앱이 μ΄λŸ¬ν•œ 링크λ₯Ό μ£Όμž₯ν•˜κ³  ν”Όμ‹± UIλ₯Ό ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ‹€μ œ μ‚¬λ‘€λ‘œλŠ” 2025λ…„ 5μ›” Temu.com 버그 λ°”μš΄ν‹° λ³΄κ³ μ„œμ—μ„œ κ³΅κ²©μžκ°€ /a/* 경둜λ₯Ό μžμ‹ μ˜ μ•±μœΌλ‘œ λ¦¬λ””λ ‰μ…˜ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
2λ”₯ 링크 κ²½λ‘œμ— λŒ€ν•œ μ„œλ²„ μΈ‘ 검증 λˆ„λ½.ν—ˆμš©λœ 경둜λ₯Ό μ‹λ³„ν•œ ν›„, μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λ¦¬μ†ŒμŠ€μ— λŒ€ν•΄ curl/Burp μš”μ²­μ„ λ°œν–‰ν•˜κ³  HTTP μƒνƒœ μ½”λ“œλ₯Ό κ΄€μ°°ν•©λ‹ˆλ‹€. 404 μ΄μ™Έμ˜ λͺ¨λ“  것(예: 200/302)은 μ˜μ‹¬μŠ€λŸ½μŠ΅λ‹ˆλ‹€.κ³΅κ²©μžλŠ” ν—ˆμš©λœ 경둜 뒀에 μž„μ˜μ˜ μ½˜ν…μΈ λ₯Ό ν˜ΈμŠ€νŒ…ν•˜κ³  이λ₯Ό 합법적인 도메인을 톡해 μ œκ³΅ν•˜μ—¬ ν”Όμ‹± λ˜λŠ” μ„Έμ…˜ 토큰 λ„λ‚œμ˜ 성곡λ₯ μ„ 높일 수 μžˆμŠ΅λ‹ˆλ‹€.
3μŠ€ν‚΄/호슀트 ν™”μ΄νŠΈλ¦¬μŠ€νŠΈ 없이 μ•± μΈ‘ URL 처리 (CVE-2024-10474 – Mozilla Focus < 132).직접 openURL:/open(_:options:) 호좜 λ˜λŠ” μž„μ˜μ˜ URL을 μ „λ‹¬ν•˜λŠ” JavaScript λΈŒλ¦¬μ§€λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.λ‚΄λΆ€ νŽ˜μ΄μ§€λŠ” λΈŒλΌμš°μ €μ˜ URL λ°” μ•ˆμ „ 검사λ₯Ό μš°νšŒν•˜λŠ” myapp:// λ˜λŠ” https:// URL을 λ°€λ°˜μž…ν•  수 μžˆμ–΄ μŠ€ν‘Έν•‘ λ˜λŠ” μ˜λ„ν•˜μ§€ μ•Šμ€ κΆŒν•œ μžˆλŠ” μž‘μ—…μœΌλ‘œ μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.
4κΆŒν•œμ—μ„œ μ™€μΌλ“œμΉ΄λ“œ μ„œλΈŒλ„λ©”μΈ μ‚¬μš© (*.example.com).κΆŒν•œμ—μ„œ *.λ₯Ό grepν•©λ‹ˆλ‹€.μ„œλΈŒλ„λ©”μΈμ΄ 인수되면(예: μ‚¬μš©λ˜μ§€ μ•ŠλŠ” S3 버킷을 톡해) κ³΅κ²©μžλŠ” μžλ™μœΌλ‘œ μœ λ‹ˆλ²„μ„€ 링크 바인딩을 νšλ“ν•©λ‹ˆλ‹€.

λΉ λ₯Έ 체크리슀트

  • κΆŒν•œμ„ μΆ”μΆœν•˜κ³  λͺ¨λ“  applinks: ν•­λͺ©μ„ λ‚˜μ—΄ν•©λ‹ˆλ‹€.
  • 각 ν•­λͺ©μ— λŒ€ν•΄ AASAλ₯Ό λ‹€μš΄λ‘œλ“œν•˜κ³  μ™€μΌλ“œμΉ΄λ“œλ₯Ό κ°μ‚¬ν•©λ‹ˆλ‹€.
  • μ›Ή μ„œλ²„κ°€ μ •μ˜λ˜μ§€ μ•Šμ€ κ²½λ‘œμ— λŒ€ν•΄ 404λ₯Ό λ°˜ν™˜ν•˜λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.
  • λ°”μ΄λ„ˆλ¦¬μ—μ„œ 였직 μ‹ λ’°ν•  수 μžˆλŠ” 호슀트/μŠ€ν‚΄λ§Œ μ²˜λ¦¬λ˜λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.
  • 앱이 μƒˆλ‘œμš΄ components ꡬ문(iOS 11+)을 μ‚¬μš©ν•˜λŠ” 경우 쿼리 λ§€κ°œλ³€μˆ˜ κ·œμΉ™({"?":{…}})을 νΌμ¦ˆν•©λ‹ˆλ‹€.

도ꡬ

  • GetUniversal.link: μ•±μ˜ μœ λ‹ˆλ²„μ„€ 링크 및 AASA 파일의 ν…ŒμŠ€νŠΈ 및 관리λ₯Ό λ‹¨μˆœν™”ν•˜λŠ” 데 도움을 μ€λ‹ˆλ‹€. 도메인을 μž…λ ₯ν•˜μ—¬ AASA 파일 무결성을 ν™•μΈν•˜κ±°λ‚˜ μ‚¬μš©μž μ •μ˜ λŒ€μ‹œλ³΄λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 링크 λ™μž‘μ„ μ‰½κ²Œ ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 λ„κ΅¬λŠ” Apple이 λ‹€μŒμ— AASA νŒŒμΌμ„ 인덱싱할 μ‹œκΈ°λ₯Ό κ²°μ •ν•˜λŠ” 데도 도움이 λ©λ‹ˆλ‹€.
  • Knil: 도메이에 μ˜ν•΄ μ„ μ–Έλœ λͺ¨λ“  μœ λ‹ˆλ²„μ„€ 링크λ₯Ό 직접 μž₯μΉ˜μ—μ„œ κ°€μ Έμ˜€κ³ , ꡬ문 λΆ„μ„ν•˜λ©° νƒ­ ν…ŒμŠ€νŠΈλ₯Ό ν•  수 μžˆλŠ” μ˜€ν”ˆ μ†ŒμŠ€ iOS μœ ν‹Έλ¦¬ν‹°μž…λ‹ˆλ‹€.
  • universal-link-validator: μ—„κ²©ν•œ AASA 적합성 검사λ₯Ό μˆ˜ν–‰ν•˜κ³  μœ„ν—˜ν•œ μ™€μΌλ“œμΉ΄λ“œλ₯Ό κ°•μ‘°ν•˜λŠ” CLI / μ›Ή κ²€μ¦κΈ°μž…λ‹ˆλ‹€.

참고자료

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°