Arc의 클라우드 기능 소개
- Arc를 사용하려면 계정이 필요함
- Firebase를 인증에 사용함
- 'Easels'라는 화이트보드 같은 기능이 있음
- 공유 버튼을 클릭해도 mitmproxy에 요청이 나타나지 않음
Objective-C 기반 Firebase 앱 해킹
- Firestore를 사용하여 백엔드를 작성하지 않고 데이터베이스 보안 규칙만 작성함
- Firestore는 Swift SDK에서 시스템 프록시 설정을 따르지 않음
- Frida 스크립트를 작성하여 관련 호출을 덤프함
var documentWithPath = ObjC.classes.FIRCollectionReference["- documentWithPath:"];
var queryWhereFieldIsEqualTo = ObjC.classes.FIRQuery["- queryWhereField:isEqualTo:"];
var collectionWithPath = ObjC.classes.FIRFirestore["- collectionWithPath:"];
function getFullPath(obj) {
if (obj.path && typeof obj.path === "function") {
return obj.path().toString();
}
return obj.toString();
}
var queryStack = [];
function logQuery(query) {
var queryString = `firebase.${query.type}("${query.path}")`;
query.whereClauses.forEach((clause) => {
queryString += `.where("${clause.fieldName}", "==", "${clause.value}")`;
});
console.log(queryString);
}
Interceptor.attach(documentWithPath.implementation, {
onEnter: function (args) {
var parent = ObjC.Object(args[0]);
var docPath = ObjC.Object(args[2]).toString();
var fullPath = getFullPath(parent) + "/" + docPath;
var query = { type: "doc", path: fullPath, whereClauses: [] };
queryStack.push(query);
logQuery(query);
},
});
Interceptor.attach(collectionWithPath.implementation, {
onEnter: function (args) {
var collectionPath = ObjC.Object(args[2]).toString();
var query = { type: "collection", path: collectionPath, whereClauses: [] };
queryStack.push(query);
},
});
Interceptor.attach(queryWhereFieldIsEqualTo.implementation, {
onEnter: function (args) {
var fieldName = ObjC.Object(args[2]).toString();
var value = ObjC.Object(args[3]).toString();
if (queryStack.length > 0) {
var currentQuery = queryStack[queryStack.length - 1];
currentQuery.whereClauses.push({ fieldName: fieldName, value: value });
}
},
onLeave: function (retval) {},
});
var executionMethods = [
"- getDocuments",
"- addSnapshotListener:",
"- getDocument",
"- addDocumentSnapshotListener:",
"- getDocumentsWithCompletion:",
"- getDocumentWithCompletion:",
];
executionMethods.forEach(function (methodName) {
if (ObjC.classes.FIRQuery[methodName]) {
Interceptor.attach(ObjC.classes.FIRQuery[methodName].implementation, {
onEnter: function (args) {
if (queryStack.length > 0) {
var query = queryStack.pop();
logQuery(query);
}
},
});
}
});
function formatFirestoreData(data) {
if (data.isKindOfClass_(ObjC.classes.NSDictionary)) {
let result = {};
data.enumerateKeysAndObjectsUsingBlock_(
ObjC.implement(function (key, value) {
result[key.toString()] = value.toString();
})
);
return JSON.stringify(result);
}
return data.toString();
}
var documentMethods = [
{ name: "- updateData:completion:", type: "update" },
{ name: "- updateData:", type: "update" },
{ name: "- setData:completion:", type: "set" },
{ name: "- setData:", type: "set" },
];
documentMethods.forEach(function (method) {
if (ObjC.classes.FIRDocumentReference[method.name]) {
Interceptor.attach(
ObjC.classes.FIRDocumentReference[method.name].implementation,
{
onEnter: function (args) {
var docRef = ObjC.Object(args[0]);
var data = ObjC.Object(args[2]);
var fullPath = getFullPath(docRef);
var formattedData = formatFirestoreData(data);
console.log(
`firebase.doc("${fullPath}").${method.type}(${formattedData})`
);
},
}
);
} else {
console.log("Warning: " + method.name + " not found");
}
});
- Arc가 Firestore에 사용자 기본 설정, 사용자 객체, 추천 및 부스트를 저장함
Arc 부스트란 무엇인가
- Arc 부스트는 사용자가 웹사이트를 커스터마이징할 수 있는 방법임
- 요소 차단, 글꼴 변경, 색상 변경, 사용자 정의 CSS 및 JS 사용 가능
- 부스트를 생성하고 다른 사용자 ID로 업데이트할 수 있음
다른 사용자의 ID 얻기
- 사용자 추천: 추천 테이블에서 사용자 ID를 얻을 수 있음
- 공개 부스트: 부스트 스냅샷에 생성자의 사용자 ID가 포함됨
- 사용자 이젤: 이젤을 공유하여 사용자 ID를 얻을 수 있음
최종 공격 체인
- 피해자의 사용자 ID를 얻음
- 악성 부스트를 생성하고 자신의 계정에 저장함
- 부스트의
creatorID
필드를 타겟의 ID로 업데이트함
- 피해자가 타겟 웹사이트를 방문하면 감염됨
특권 페이지에서의 RCE
개인정보 보호 문제
- 방문하는 사이트에 대한 데이터가 서버로 전송됨
- Arc의 개인정보 보호 정책에 위배됨
GN⁺의 정리
- Arc의 클라우드 기능과 보안 취약점을 분석한 기사임
- Firestore를 활용한 백엔드 보안 문제를 다룸
- Arc 부스트를 통한 사용자 커스터마이징과 보안 취약점 설명
- 다른 사용자의 ID를 얻어 악성 부스트를 실행하는 방법을 제시함
- 개인정보 보호 문제와 특권 상승 가능성에 대한 우려를 제기함