none
C++ WINAPI: LogonUser проблемы имперсонализации. RRS feed

  • Вопрос

  • Всем доброго времени суток.

    В процессе разработки софтяры наткнулся на проблему, которую пытаюсь решить вот уже несколько дней. Увы но подходящего решения пока так и не нашел, посему решил обратиться за помощью к сему благородному сообществу.

    Преамбула:

    Разрабатываю приложение для отложенного удаления файлов и папок(в дальнейшем объект). Юзер через мордофейс создает "задание на удаление", которое хранится в БД. Задание содержит: список объектов, даты всяко разные, и пользовательские креденшелы. Второй частью приложения является сервис, который вычитывает из БД эти задания и исполняет возложенную на него функцию удаления. При этом для того, чтобы пользователь не "понаудалял всяко-разно ненужно-безобразного" сервис имперсонифицируется под пользователя который и создал задание посредством ВИНАПИшной функций LogonUser().

    Вопрос:

    Все вроде бы работает и удаляется у доменных пользователей и у локальных, однако в случае если у пользователя нет пароля(что характерно для домашних компьютеров по большей части). Функция возвращает ошибку. Порыскав по просторам бескрайнего интернета нашел похожие темы и в них, как правило решением данного вопроса становилось изменение локальных политик безопасности паролей, что, как мне кажется, не комильфо. В то же время софтина не может диктовать пользователю, дескать: "давай ставь себе пароль на учетную запись, и тогда все будет в мармеладе".

    У меня была идея заполнять АКЛ на этапе добавления задания и проверять по-элементно, имеет ли пользователь право удалить этот объект или нет. В случае наличия таких прав объект добавлялся бы в задание, а сервис по наступлении момента Х удалял бы его без всякой задней мысли с правами Локальной Системной Учетки, однако дабы ни у кого не возникало желания написать какой-нибудь эксплоит и менять элементы объектов в заданиях, я был вынужден отказаться от этой идеи.

    Смиренно жду вашей помощи, о премудрое сообщество гуру.

    С уважением.

    17 января 2013 г. 7:13

Ответы

Все ответы

  • Ну как я понял у WinAPI LogonUser параметр lpszPassword необязательный то есть, если у пользователя нет пароля вы просто вызываете версию функции без указания данного параметра.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 7:36
    Отвечающий
  • Не смотря на то что в справке параметр lpszPassword опциональный при подстановке значения NULL в аргумент функции LogonUser() вернет ошибку ERROR_ACCOUNT_RESTRICTION (1327).
    17 января 2013 г. 9:23
  • Опционный значит, что при вызове функции его вообще можно не указывать, то есть просто пропустить (в случаях когда это нужно). В VB это выглядело бы так LogonUser (lpszUsername, lpszDomain,,dwLogonType, dwLogonProvider, phToken)

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 9:29
    Отвечающий
  • Компилятор не даст так задать. Должен выдать что-то а-ля Expected an expression.

    В данном случае речь идет об Visual C++

    17 января 2013 г. 9:53
  • В данном случае речь идет об Visual C++

    Я понимаю, что C++, но функция то WinAPI и она едина для всех языков и платформ и optional именно и означает, что можно его не задавать. Как это сделать на С++ я не знаю, но знаю что решение в этом. Пробуйте вы или ждите других рекомендаций.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 9:55
    Отвечающий
  • На самом деле сам топик и был создан мной, однако посредством помощи от товарища, поскольку при попытке создания новой темы(вопроса) мне выдает "неожиданную ошибку"

    Попробовал "пропустить" аргумент - Компилятор материт, как я и предполагал.

    пробовал поставить NULL в качестве lpszPassword - результатом становится вышеописанная ошибка 1327.


    17 января 2013 г. 10:04
  • А можно кусок кода где происходит вызов данной API? Лучше с комментариями на русском где что делается, ибо я С++ плохо понимаю.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 10:42
    Отвечающий
  • Вызова два(и они в принципе идентичные):

    1. При заполнении задания требуется ввести пароль для подтверждения задания(+проверка правильности креденшелов).

    2. В самом сервисе перед непосредственно для имперсонализации перед удалением.

    bool UserInformation::ValidateCredential(const std::wstring& domainName, const std::wstring& userName, const std::wstring& password)
    // Check the valid of the domain/user/password
    {
      bool result = false;
      HANDLE logonToken = 0; //токен, который будет присвоен в случае успешного вызова LogonUser()
      wchar_t* pwd = NULL;
      if (!password.empty())
      {
        pwd = const_cast<wchar_t*>(password.c_str());
      }
    
      int retResult = 0; //Для проверки ошибки
      if (LogonUser(userName.c_str(), domainName.c_str(), pwd, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &logonToken))
      {
        //Проверку прошли.
        CloseHandle(logonToken);
        result = true;
      }
      else
      {
        retResult = GetLastError();
      }
    
      return result;
    }

    17 января 2013 г. 10:57
  • Чуть чуть не то что я ожидал. В С++ что бы обратится к API есть какой то способ ее предварительно объявить? Или вы сразу пишите LogonUser и он понимает, что это APIфункция?

    Если первый случай, то я хотел видеть место объявления функции. Скорее всего возможно объявить ее так, что бы пропустить lpszPassword


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 11:01
    Отвечающий
  • в плюсах вначале подключаются заголовочные файлы директивой #include. После подключения нужного заголовочного файла можно использовать функции в нем описанные напрямую.
    17 января 2013 г. 11:13
  • А ну да. А возможно #include править?

    Я это все к тому, что игнорирование само собой не нравится компилятору, так как при объявлении функции параметр есть. его нужно убирать в объявлении, а обращение к API ошибки не вызовет, так как параметр optional


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    17 января 2013 г. 11:19
    Отвечающий
  • 1. Править системные заголовочные файлы а. не даст компилятор, б. не правильный подход ИМХО.

    2. Есть у меня такое ощущение что этот аргумент optional только для системных учетных записей типа LocalSystem, NetworkSystem etc... поскольку они не имеют пароля.

    Причем я более чем на 100% уверен что должны быть приложения которые спокойно работают с учетками без паролей и не обламываются. Поиск подобных решений на просторах сети интернет не привел меня к положительному результату на текущий момент, к сожалению.


    17 января 2013 г. 11:41
  • Народ, вы где?!

    Неужто нет более плюсовых гуру?

    18 января 2013 г. 6:00
  • Если параметр опциональный, то нужно задавать NULL. Собственно даже пример тут есть. Другой вопрос почему функция возвращает 0. Но если это происходит надо смотреть GetLastError(). На счет запрета пустых паролей могу сказать, что например встроенный в винду Remote Desktop не позволяет подключение пользователем для которого не задан пароль (т.е. пустой). Может быть это как-то связано с API-функцией, а может и просто дополнительная защита в Remote Desktop.
    18 января 2013 г. 7:04
  • Привет

    Воспользоваться этой функцией для юзеров без пароля скорей всего никак не получится. NULL-пароль разрешен там скорей всего только для запуска от локальных системных служб. Другая функция, которая бы могла решить проблему - CreateProcessWithLogonW, к сожалению тоже требует чтобы был пароль у пользователя. Такова система безопасности.

    Из глупых решений - можно попробовать вместо NULL передать какую-нибудь рандомную строку.


    Для связи [mail]

    18 января 2013 г. 7:25
  • Привет

    Воспользоваться этой функцией для юзеров без пароля скорей всего никак не получится. NULL-пароль разрешен там скорей всего только для запуска от локальных системных служб. Другая функция, которая бы могла решить проблему -CreateProcessWithLogonW, к сожалению тоже требует чтобы был пароль у пользователя. Такова система безопасности.

    Из глупых решений - можно попробовать вместо NULL передать какую-нибудь рандомную строку.


    Для связи [mail]

    Все дело в том, что нужно имперсонировать поток, а не создавать дочерний процесс в определенном контексте безопасности. Как я уже выше писал, пустой пароль возможен только для системных служб.

    Касательно глыпых решений: Передавать левую строку можно только будет возвращена ошибка "неправильный логин или пароль".

    Всё дело в том, что имперсонификация требуется по большей части для того, чтобы какой-нибудь "чайник" не грохнул себе системные файлы и дирректории, и потом не писал дескать программа отстой потому, что убила мне систему.

    Если параметр опциональный, то нужно задавать NULL. Собственно даже пример тут есть. Другой вопрос почему функция возвращает 0. Но если это происходит надо смотреть GetLastError(). На счет запрета пустых паролей могу сказать, что например встроенный в винду Remote Desktop не позволяет подключение пользователем для которого не задан пароль (т.е. пустой). Может быть это как-то связано с API-функцией, а может и просто дополнительная защита в Remote Desktop.

    Удаленный логин без пароля запрещен локальной политикой безопасности - это как бы нормально. К сожалению на МСДН не написано для кого собственно это опциональный параметр. Выше писал, что возвращается если задавать NULL
    18 января 2013 г. 9:15
  • Действительно и в приложении VB.Net та же песня. У пользователя с паролем функция выполняется, а у пользователя без пароля при любом раскладе возвращает 0

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    18 января 2013 г. 9:43
    Отвечающий

  •   чтобы какой-нибудь "чайник" не грохнул себе системные файлы и дирректории, и потом не писал дескать программа отстой потому, что убила мне систему.
    Если система будет убита, ему не с чего будет писать :) А если серьёзно, то такое поведение настраивается политиками безопасности для пользователей на уровне ОС. Не понятно зачем Вы пытаетесь перенести это в свое приложение.
    18 января 2013 г. 9:49
  • Соглашусь с tulosba в том, что вам нужно пересматривать подход в решении задачи.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    18 января 2013 г. 9:51
    Отвечающий
  • А какой эксплойт вы боитесь, если программа, запущенная от определенного юзера сначала проверить существование файла, убедится в наличие прав, а после уже запишет в БД. То что доступ к аккаунту Васи может получить любой пользователь, т.к. Вася не установил пароль, и выбрать любой файл для удаления - это не страшно?


    Для связи [mail]

    18 января 2013 г. 10:14
  • Приветствую. Я программист, который вместе с Alexey Kurylev разрабатывает ту же программу.

    Уточню существующее решение и проблемы, которые хотим решить.

    Сейчас есть служба, запущенная по локальной системой. Эта служба, если накидать заданий на удаление, грохнет все файлы и не поморщится. причём не важно: принадлежат они Васе Пупкину без пароля или Супер-Админу с паролем. Чтобы Вася Пупкин или какой-либо другой пользователь/эксплоит не смог удалить файлы, для которых у него нет доступа - делается имперсонация потока. т.е. даже если пользователь зайдёт под Васей Пупкиным (без пароля) и накидает заданий на удаление, то прав не хватит - и удаления не произойдёт. Однако такое решение работает только для пользователей с паролями.

    Если это решение переводить на поддержку всех пользователей, тогда получится что любой пользователь сможет грохнуть любой файл - удалять будет служба под правами локальной системы.

    Поэтому ищутся обходные пути. В частности, Алексей спрашивал реализуемость следующей проверки: служба получает имя файла на удаление и имя пользователя, который запросил удаление. Далее на основе ACL, DACL, чёрта с бантиком и др. эта служба проверяет: а есть ли у этого пользователя права удалить этот файл? И если права есть, то служба его удаляет (уже от своего имени).

    Соответственно, повторю вопрос: нужно проверить по имени файла и имени пользователя - есть у него права на удаление файла?

    18 января 2013 г. 12:19
  • А вот такой способ имперсонализации на момент создания задания юзером вы пробовали делать? Должно вроде бы помочь вам решить проблему.

    Для связи [mail]

    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 12:10
    18 января 2013 г. 12:46

  • Соответственно, повторю вопрос: нужно проверить по имени файла и имени пользователя - есть у него права на удаление файла?

    Не поможет ссылка?

    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 12:10
    18 января 2013 г. 12:49
  • А вот такой способ имперсонализации на момент создания задания юзером вы пробовали делать? Должно вроде бы помочь вам решить проблему.

    Для связи [mail]

    Энумерация текущих сессий это хорошо, а как определить от какой пришло задание, если одновременно на машине запущено две сессии? Касательно НТФСных прав, тут мой коллега поспешил. Основной посыл был пытаться удалять службой в контексте безопасности конкретного пользователя. Без залезания в НТФСные права. Если у нужного пользователя нет прав на удаление файла , ну и тем лучше. Не удалится файлик и все.
    18 января 2013 г. 14:16
  • Полностью вникнув, соглашусь, что удалять от имени пользователя, указанного в очереди самый идеальный вариант, но как говорят бывалые API без пароля не работает.

    Только я вот не очень понимаю. У меня была ситуация, когда был администратор и обычный пользователь. И там и там не было пароля. Когда я выполнял программу от имени администратора появлялось окно со вводом пароля, я жал "Ок" ничего не вводя и получал выполнение программы от имени администратора. Как система поступала в этом случае?


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    18 января 2013 г. 15:31
    Отвечающий