mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-24 20:13:37 +00:00
267 lines
14 KiB
Markdown
267 lines
14 KiB
Markdown
# macOS XPC 授权
|
||
|
||
<details>
|
||
|
||
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks 云 ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
||
|
||
* 你在一家**网络安全公司**工作吗?你想在 HackTricks 中看到你的**公司广告**吗?或者你想获得**PEASS 的最新版本或下载 HackTricks 的 PDF 版本**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
|
||
* 发现我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品[**The PEASS Family**](https://opensea.io/collection/the-peass-family)
|
||
* 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com)
|
||
* **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**telegram 群组**](https://t.me/peass) 或 **关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。**
|
||
* **通过向**[**hacktricks 仓库**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud 仓库**](https://github.com/carlospolop/hacktricks-cloud) **提交 PR 来分享你的黑客技巧。**
|
||
|
||
</details>
|
||
|
||
## XPC 授权
|
||
|
||
苹果还提出了另一种验证连接进程是否具有**调用公开的 XPC 方法的权限**的方法。
|
||
|
||
当应用程序需要**以特权用户身份执行操作**时,通常不会将应用程序作为特权用户运行,而是作为根用户安装一个 HelperTool 作为 XPC 服务,应用程序可以从中调用该服务来执行这些操作。但是,调用服务的应用程序应具有足够的授权。
|
||
|
||
### ShuoldAcceptNewConnection 总是 YES
|
||
|
||
在 [EvenBetterAuthorizationSample](https://github.com/brenwell/EvenBetterAuthorizationSample) 中可以找到一个示例。在 `App/AppDelegate.m` 中,它尝试**连接**到**HelperTool**。而在 `HelperTool/HelperTool.m` 中,函数**`shouldAcceptNewConnection`**不会检查之前提到的任何要求。它将始终返回 YES:
|
||
```objectivec
|
||
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
|
||
// Called by our XPC listener when a new connection comes in. We configure the connection
|
||
// with our protocol and ourselves as the main object.
|
||
{
|
||
assert(listener == self.listener);
|
||
#pragma unused(listener)
|
||
assert(newConnection != nil);
|
||
|
||
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)];
|
||
newConnection.exportedObject = self;
|
||
[newConnection resume];
|
||
|
||
return YES;
|
||
}
|
||
```
|
||
有关如何正确配置此检查的更多信息,请参阅:
|
||
|
||
{% content-ref url="macos-xpc-connecting-process-check.md" %}
|
||
[macos-xpc-connecting-process-check.md](macos-xpc-connecting-process-check.md)
|
||
{% endcontent-ref %}
|
||
|
||
### 应用程序权限
|
||
|
||
然而,当调用 HelperTool 的方法时,会进行一些授权操作。
|
||
|
||
`App/AppDelegate.m` 中的函数 `applicationDidFinishLaunching` 在应用程序启动后会创建一个空的授权引用。这应该总是有效的。\
|
||
然后,它将尝试通过调用 `setupAuthorizationRights` 向该授权引用添加一些权限:
|
||
```objectivec
|
||
- (void)applicationDidFinishLaunching:(NSNotification *)note
|
||
{
|
||
[...]
|
||
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
|
||
if (err == errAuthorizationSuccess) {
|
||
err = AuthorizationMakeExternalForm(self->_authRef, &extForm);
|
||
}
|
||
if (err == errAuthorizationSuccess) {
|
||
self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
|
||
}
|
||
assert(err == errAuthorizationSuccess);
|
||
|
||
// If we successfully connected to Authorization Services, add definitions for our default
|
||
// rights (unless they're already in the database).
|
||
|
||
if (self->_authRef) {
|
||
[Common setupAuthorizationRights:self->_authRef];
|
||
}
|
||
|
||
[self.window makeKeyAndOrderFront:self];
|
||
}
|
||
```
|
||
`Common/Common.m`中的`setupAuthorizationRights`函数将应用程序的权限存储在`/var/db/auth.db`授权数据库中。请注意,它只会添加尚未存在于数据库中的权限:
|
||
```objectivec
|
||
+ (void)setupAuthorizationRights:(AuthorizationRef)authRef
|
||
// See comment in header.
|
||
{
|
||
assert(authRef != NULL);
|
||
[Common enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) {
|
||
OSStatus blockErr;
|
||
|
||
// First get the right. If we get back errAuthorizationDenied that means there's
|
||
// no current definition, so we add our default one.
|
||
|
||
blockErr = AuthorizationRightGet([authRightName UTF8String], NULL);
|
||
if (blockErr == errAuthorizationDenied) {
|
||
blockErr = AuthorizationRightSet(
|
||
authRef, // authRef
|
||
[authRightName UTF8String], // rightName
|
||
(__bridge CFTypeRef) authRightDefault, // rightDefinition
|
||
(__bridge CFStringRef) authRightDesc, // descriptionKey
|
||
NULL, // bundle (NULL implies main bundle)
|
||
CFSTR("Common") // localeTableName
|
||
);
|
||
assert(blockErr == errAuthorizationSuccess);
|
||
} else {
|
||
// A right already exists (err == noErr) or any other error occurs, we
|
||
// assume that it has been set up in advance by the system administrator or
|
||
// this is the second time we've run. Either way, there's nothing more for
|
||
// us to do.
|
||
}
|
||
}];
|
||
}
|
||
```
|
||
函数`enumerateRightsUsingBlock`是用于获取应用程序权限的函数,这些权限在`commandInfo`中定义:
|
||
```objectivec
|
||
static NSString * kCommandKeyAuthRightName = @"authRightName";
|
||
static NSString * kCommandKeyAuthRightDefault = @"authRightDefault";
|
||
static NSString * kCommandKeyAuthRightDesc = @"authRightDescription";
|
||
|
||
+ (NSDictionary *)commandInfo
|
||
{
|
||
static dispatch_once_t sOnceToken;
|
||
static NSDictionary * sCommandInfo;
|
||
|
||
dispatch_once(&sOnceToken, ^{
|
||
sCommandInfo = @{
|
||
NSStringFromSelector(@selector(readLicenseKeyAuthorization:withReply:)) : @{
|
||
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.readLicenseKey",
|
||
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
|
||
kCommandKeyAuthRightDesc : NSLocalizedString(
|
||
@"EBAS is trying to read its license key.",
|
||
@"prompt shown when user is required to authorize to read the license key"
|
||
)
|
||
},
|
||
NSStringFromSelector(@selector(writeLicenseKey:authorization:withReply:)) : @{
|
||
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.writeLicenseKey",
|
||
kCommandKeyAuthRightDefault : @kAuthorizationRuleAuthenticateAsAdmin,
|
||
kCommandKeyAuthRightDesc : NSLocalizedString(
|
||
@"EBAS is trying to write its license key.",
|
||
@"prompt shown when user is required to authorize to write the license key"
|
||
)
|
||
},
|
||
NSStringFromSelector(@selector(bindToLowNumberPortAuthorization:withReply:)) : @{
|
||
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.startWebService",
|
||
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
|
||
kCommandKeyAuthRightDesc : NSLocalizedString(
|
||
@"EBAS is trying to start its web service.",
|
||
@"prompt shown when user is required to authorize to start the web service"
|
||
)
|
||
}
|
||
};
|
||
});
|
||
return sCommandInfo;
|
||
}
|
||
|
||
+ (NSString *)authorizationRightForCommand:(SEL)command
|
||
// See comment in header.
|
||
{
|
||
return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName];
|
||
}
|
||
|
||
+ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block
|
||
// Calls the supplied block with information about each known authorization right..
|
||
{
|
||
[self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||
#pragma unused(key)
|
||
#pragma unused(stop)
|
||
NSDictionary * commandDict;
|
||
NSString * authRightName;
|
||
id authRightDefault;
|
||
NSString * authRightDesc;
|
||
|
||
// If any of the following asserts fire it's likely that you've got a bug
|
||
// in sCommandInfo.
|
||
|
||
commandDict = (NSDictionary *) obj;
|
||
assert([commandDict isKindOfClass:[NSDictionary class]]);
|
||
|
||
authRightName = [commandDict objectForKey:kCommandKeyAuthRightName];
|
||
assert([authRightName isKindOfClass:[NSString class]]);
|
||
|
||
authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault];
|
||
assert(authRightDefault != nil);
|
||
|
||
authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc];
|
||
assert([authRightDesc isKindOfClass:[NSString class]]);
|
||
|
||
block(authRightName, authRightDefault, authRightDesc);
|
||
}];
|
||
}
|
||
```
|
||
这意味着在此过程结束时,`commandInfo`中声明的权限将存储在`/var/db/auth.db`中。请注意,您可以在其中找到**每个需要身份验证的方法**的**权限名称**和**`kCommandKeyAuthRightDefault`**。后者**指示谁可以获得此权限**。
|
||
|
||
有不同的范围来指示谁可以访问权限。其中一些在[AuthorizationDB.h](https://github.com/aosm/Security/blob/master/Security/libsecurity\_authorization/lib/AuthorizationDB.h)中定义(您可以在[此处找到所有内容](https://www.dssw.co.uk/reference/authorization-rights/)),但总结如下:
|
||
|
||
<table><thead><tr><th width="284.3333333333333">名称</th><th width="165">值</th><th>描述</th></tr></thead><tbody><tr><td>kAuthorizationRuleClassAllow</td><td>allow</td><td>任何人</td></tr><tr><td>kAuthorizationRuleClassDeny</td><td>deny</td><td>无人</td></tr><tr><td>kAuthorizationRuleIsAdmin</td><td>is-admin</td><td>当前用户需要是管理员(在管理员组内)</td></tr><tr><td>kAuthorizationRuleAuthenticateAsSessionUser</td><td>authenticate-session-owner</td><td>要求用户进行身份验证。</td></tr><tr><td>kAuthorizationRuleAuthenticateAsAdmin</td><td>authenticate-admin</td><td>要求用户进行身份验证。他需要是管理员(在管理员组内)</td></tr><tr><td>kAuthorizationRightRule</td><td>rule</td><td>指定规则</td></tr><tr><td>kAuthorizationComment</td><td>comment</td><td>在权限上指定一些额外的注释</td></tr></tbody></table>
|
||
|
||
### 权限验证
|
||
|
||
在`HelperTool/HelperTool.m`中,函数**`readLicenseKeyAuthorization`**检查调用者是否被授权**执行此方法**,调用函数**`checkAuthorization`**。此函数将检查调用进程发送的**authData**是否具有**正确的格式**,然后将检查**获取权限所需的内容**以调用特定方法。如果一切顺利,**返回的`error`将为`nil`**:
|
||
```objectivec
|
||
- (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command
|
||
{
|
||
[...]
|
||
|
||
// First check that authData looks reasonable.
|
||
|
||
error = nil;
|
||
if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) {
|
||
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil];
|
||
}
|
||
|
||
// Create an authorization ref from that the external form data contained within.
|
||
|
||
if (error == nil) {
|
||
err = AuthorizationCreateFromExternalForm([authData bytes], &authRef);
|
||
|
||
// Authorize the right associated with the command.
|
||
|
||
if (err == errAuthorizationSuccess) {
|
||
AuthorizationItem oneRight = { NULL, 0, NULL, 0 };
|
||
AuthorizationRights rights = { 1, &oneRight };
|
||
|
||
oneRight.name = [[Common authorizationRightForCommand:command] UTF8String];
|
||
assert(oneRight.name != NULL);
|
||
|
||
err = AuthorizationCopyRights(
|
||
authRef,
|
||
&rights,
|
||
NULL,
|
||
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
|
||
NULL
|
||
);
|
||
}
|
||
if (err != errAuthorizationSuccess) {
|
||
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
|
||
}
|
||
}
|
||
|
||
if (authRef != NULL) {
|
||
junk = AuthorizationFree(authRef, 0);
|
||
assert(junk == errAuthorizationSuccess);
|
||
}
|
||
|
||
return error;
|
||
}
|
||
```
|
||
请注意,要检查调用该方法的权限,函数`authorizationRightForCommand`将仅检查先前的注释对象`commandInfo`。然后,它将调用`AuthorizationCopyRights`来检查是否有权调用该函数(请注意,标志允许与用户交互)。
|
||
|
||
在这种情况下,要调用函数`readLicenseKeyAuthorization`,`kCommandKeyAuthRightDefault`被定义为`@kAuthorizationRuleClassAllow`。因此,**任何人都可以调用它**。
|
||
|
||
### 数据库信息
|
||
|
||
提到这些信息存储在`/var/db/auth.db`中。您可以使用以下命令列出所有存储的规则:
|
||
```sql
|
||
sudo sqlite3 /var/db/auth.db
|
||
SELECT name FROM rules;
|
||
SELECT name FROM rules WHERE name LIKE '%safari%';
|
||
```
|
||
然后,您可以使用以下命令查看谁可以访问权限:
|
||
```bash
|
||
security authorizationdb read com.apple.safaridriver.allow
|
||
```
|
||
<details>
|
||
|
||
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks 云 ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 推特 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
||
|
||
* 你在一家 **网络安全公司** 工作吗?想要在 HackTricks 中 **宣传你的公司** 吗?或者你想要获得 **PEASS 的最新版本或下载 HackTricks 的 PDF** 吗?请查看 [**订阅计划**](https://github.com/sponsors/carlospolop)!
|
||
* 发现我们的独家 [**NFTs**](https://opensea.io/collection/the-peass-family) 集合 [**The PEASS Family**](https://opensea.io/collection/the-peass-family)
|
||
* 获得 [**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com)
|
||
* **加入** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或者 [**Telegram 群组**](https://t.me/peass),或者在 **Twitter** 上 **关注** 我 [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。**
|
||
* **通过向** [**hacktricks 仓库**](https://github.com/carlospolop/hacktricks) **和** [**hacktricks-cloud 仓库**](https://github.com/carlospolop/hacktricks-cloud) **提交 PR 来分享你的黑客技巧。**
|
||
|
||
</details>
|