11

In an application I'm making I need to run the following command as root (user will be prompted trice if they really want to, and they will be asked to unmount their drives) using NSTask:

/bin/rm -rf /
#Yes, really

The problem is that simply using Substitute User Do (sudo) doesn't work as the user needs to enter the password to the non-available stdin. I'd rather like to show the user the same window as you'd see when you click the lock in Preferences.app, like this (hopefully with a shorter password):

screenshot
(source: quickpwn.com)


Can anyone help me with this? Thanks.

Community
  • 1
  • 1

5 Answers5

9

Check out STPrivilegedTask, an Objective-C wrapper class around AuthorizationExecuteWithPrivileges() with an NSTask-like interface.

svth
  • 1,268
  • 1
  • 14
  • 22
6

That's one of the hardest tasks to do properly on Mac OS X.

The guide documenting how to do this is the Authorization Services Programming Guide. There are multiple possibilities, as usual the most secure is the hardest to implement.

I've started writing a tool that uses a launchd daemon (the most secure way), the code is available on google code. So if you want, you can copy that code.

Georg Schölly
  • 116,521
  • 48
  • 204
  • 258
5

I think I can now answer this, thanks to some Googling and a nice find in this SO question. It's very slightly hacky, but IMHO is a satisfactory solution.

I wrote this generic implementation which should achieve what you want:

- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath
                     withArguments:(NSArray *)arguments
                            output:(NSString **)output
                  errorDescription:(NSString **)errorDescription {

    NSString * allArgs = [arguments componentsJoinedByString:@" "];
    NSString * fullScript = [NSString stringWithFormat:@"%@ %@", scriptPath, allArgs];

    NSDictionary *errorInfo = [NSDictionary new];
    NSString *script =  [NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges", fullScript];

    NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
    NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];

    // Check errorInfo
    if (! eventResult)
    {
        // Describe common errors
        *errorDescription = nil;
        if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
        {
            NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
            if ([errorNumber intValue] == -128)
                *errorDescription = @"The administrator password is required to do this.";
        }

        // Set error message from provided message
        if (*errorDescription == nil)
        {
            if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
                *errorDescription =  (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
        }

        return NO;
    }
    else
    {
        // Set output to the AppleScript's output
        *output = [eventResult stringValue];

        return YES;
    }
}

Usage example:

    NSString * output = nil;
    NSString * processErrorDescription = nil;
    BOOL success = [self runProcessAsAdministrator:@"/usr/bin/id"
                    withArguments:[NSArray arrayWithObjects:@"-un", nil]
                           output:&output
                            errorDescription:&processErrorDescription
                  asAdministrator:YES];


    if (!success) // Process failed to run
    {
         // ...look at errorDescription 
    }
    else
    {
         // ...process output
    }
Community
  • 1
  • 1
Carlos P
  • 3,790
  • 1
  • 30
  • 46
  • 1
    You may want to be a bit careful what you feed into this function, though. If your script's path contains spaces or quote characters (e.g. because the application was placed in a folder with that in its name), or you need to pass it file paths which are quoted, this will give you a syntax error from AppleScript at best. At worst, someone can inject code into your application by e.g. naming a file `rm -rf /" with administrator privileges --` (which will run rm -rf with admin privileges and then comment out the rest of the line with your legitimate code. – uliwitness Apr 17 '16 at 15:52
-1

Okay, so I ran into this while searching how to do this properly... I know it's probably the least secure method of accomplishing the task, but probably the easiest and I haven't seen this answer anywhere. I came up with this for apps that I create to run for my own purposes and as a temporary authorization routine for the type of task that user142019 is describing. I don't think Apple would approve. This is just a snippet and does not include a UI input form or any way to capture stdout, but there are plenty of other resources that can provide those pieces.

Create a blank file called "script.sh" and add it to your project's supporting files.

Add this to header file:

// set this from IBOutlets or encrypted file
@property (strong, nonatomic) NSString * password;
@property (strong, nonatomic) NSString * command;

implementation:

@synthesize password;
@synthesize command;

(IBAction)buttonExecute:(id)sender {
    NSString *scriptPath = [[NSBundle mainBundle]pathForResource:@"script" ofType:@"sh"];
    NSString *scriptText = [[NSString alloc]initWithFormat:@"#! usr/sh/echo\n%@ | sudo -S %@",password,command];
    [scriptText writeToFile:scriptPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    NSTask * task = [[NSTask alloc]init];
    [task setLaunchPath:@"/bin/sh"];
    NSArray * args = [NSArray arrayWithObjects:scriptPath, nil];
    [task setArguments:args];
    [task launch];
    NSString * blank = @" ";
    [blank writeToFile:scriptPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

The last step is just so you don't have a cleartext admin password sitting in your bundle. I recommend making sure that anything beyond the method be obfuscated in some way.

linguri
  • 103
  • 1
  • 1
    This is horrible and will not work in a lot of cases: For one, most users do not have permission to change the current application bundle, so you will get an error trying to write scriptText into the application bundle. Secondly, even if it did work for all users, you could run into race conditions if two users on a machine are using the same application, or the application is launched from a server share. Third, YOU ARE WRITING A USER'S PASSWORD TO DISK UN-ENCRYPTED! At the very least, you should try piping the script text to the shell instead of saving it to disk. – uliwitness Apr 17 '16 at 15:43
-3

In one of my cases this is not correct:

> The problem is that simply using Substitute User Do (sudo) doesn't work as the user needs to enter the password >

I simply edited /etc/sudoers to allow the desired user to start any .sh script without prompting for password. So you would execute a shell script which contains a command line like sudo sed [...] /etc/printers.conf to modify the printers.conf file, and the /etc/sudoers file would contain this line

myLocalUser ALL=(ALL) NOPASSWD: ALL

But of course I am looking for a better solution which correctly prompts the user to type in an admin password to allow the script or NSTask to execute. Thanks for the code which uses an AppleScript call to prompt and execute the task/shell script.

  • 2
    This is a horrible security risk, having a user that can execute any sudo code without entering a password. – uliwitness Apr 17 '16 at 15:46