草庐IT

c++ - 默认安全描述符

coder 2024-06-09 原文

我的应用程序有 2 个进程,一个需要提升,另一个不需要,但是它们在同一桌面上的同一用户帐户下运行。

我需要在从文件读取的提升进程中创建一个文件(不在磁盘上,其他类型的文件),但让我的非提升进程对该文件具有写访问权限。

使用 nullptr SECURITY_ATTRIBUTES,非提升进程无法打开文件,CreateFile 失败并显示拒绝访问代码。这是预期的,SetSecurityDescriptorDacl 解决方法类似于 this answer工作正常。

但是,我不喜欢这种解决方法。我不想将对该文件的写入权限授予所有人。我只想授予当前用户访问权限。这有点敏感,提升的读取器进程将运行数小时,我不希望每个人都能够写入该文件。

我如何获取/构建 SECURITY_ATTRIBUTES,这将成为在同一桌面上运行的非提升进程的默认安全性?

最佳答案

如果我们运行提升的进程(由管理员用户) - 它没有提升链接 session 。 (并且非提升的进程具有提升的链接 session )您需要:

  1. 打开进程 token
  2. 通过 TokenLinkedToken 查询此 token 的链接 session token
  3. 通过 TokenDefaultDacl 查询此链接 token 的默认 dacl
  4. 使用此 DACL 初始化安全描述符

为未提升的 session 获取默认 dacl 的代码:

ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? 0 : GetLastError();
}

ULONG GetNotElevatedDefaultDacl(PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    HANDLE hToken;
    ULONG err = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));

    if (!err)
    {
    ULONG cb;
        union {
            TOKEN_LINKED_TOKEN tlt;
            TOKEN_ELEVATION_TYPE tet;
        };

        err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &cb));

        if (!err)
        {
            if (tet == TokenElevationTypeFull)
            {
                err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &cb));
            }
            else
            {
                err = ERROR_ELEVATION_REQUIRED;
            }
        }
        CloseHandle(hToken);

        if (!err)
        {
            union {
                PTOKEN_DEFAULT_DACL p;
                PVOID buf;
            };

            cb = 0x100;

            do 
            {
                if (buf = LocalAlloc(0, cb))
                {
                    if (err = BOOL_TO_ERROR(GetTokenInformation(
                        tlt.LinkedToken, TokenDefaultDacl, buf, cb, &cb)))
                    {
                        LocalFree(buf);
                    }
                    else
                    {
                        *DefaultDacl = p;
                    }
                }
                else
                {
                    err = GetLastError();
                    break;
                }

            } while (err == ERROR_INSUFFICIENT_BUFFER);

            CloseHandle(tlt.LinkedToken);
        }
    }

    return err;
}

并使用它(这适用于在创建时采用 SECURITY_ATTRIBUTES 的任何对象)

PTOKEN_DEFAULT_DACL DefaultDacl;
ULONG err = GetNotElevatedDefaultDacl(&DefaultDacl);

SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };

InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);

if (!err)
{
    SetSecurityDescriptorDacl(&sd, TRUE, DefaultDacl->DefaultDacl, FALSE);
}

HANDLE hObject = CreateMailslot(
        L"\\\\?\\Global\\MailSlot\\12345678", 0, MAILSLOT_WAIT_FOREVER, &sa);

if (!err)
{
    LocalFree(DefaultDacl);
}

if (hObject)
{
    // CheckObjectSD(hObject);
    CloseHandle(hObject);
}

如果使用默认 dacl 从提升的进程创建对象(在本例中为邮槽)- 安全 DACL 将如下所示:

T FL AcessMsK Sid
A 00 001F01FF S-1-5-32-544 'Administrators'
A 00 001F01FF S-1-5-18 'SYSTEM'
A 00 001200A9 S-1-5-5-0-x 'LogonSessionId_0_x'

SYSTEMAdministrators 的所有访问权限以及当前登录 session 的读取+执行访问权限。结果,同一登录 session 中未提升的进程只有读取权限。

如果使用未提升 session 的显式 DACL - 结果:

T FL AcessMsK Sid
A 00 001F01FF S-1-5-21-a-b-c-d 'SomeUser'
A 00 001F01FF S-1-5-18 'SYSTEM'
A 00 001200A9 S-1-5-5-0-x 'LogonSessionId_0_x'

SYSTEMSomeUser 的所有访问权限以及当前登录 session 的读取+执行访问权限。

请注意,因为提升的进程将 SomeUser 作为 TokenUser,他拥有该对象的所有访问权限

对于检查对象安全描述符,我们可以使用例如下一个代码:

void CheckObjectSD(HANDLE hObject)
{
    union {
        PSECURITY_DESCRIPTOR psd;
        PVOID buf;
    };

    ULONG cb = 0, rcb = 0x30;
    volatile static UCHAR guz;
    buf = alloca(guz);
    PVOID stack = alloca(guz);

    ULONG err;
    do 
    {
        if (cb < rcb)
        {
            cb = (ULONG)((ULONG_PTR)stack - (ULONG_PTR)(buf = alloca(rcb - cb)));
        }

        if (!(err = BOOL_TO_ERROR(GetKernelObjectSecurity(hObject, 
            DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, psd, cb, &rcb))))
        {
            PWSTR psz;
            if (ConvertSecurityDescriptorToStringSecurityDescriptorW(psd, SDDL_REVISION, 
                DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, &psz, 0))
            {
                DbgPrint("%S\n", psz);
                LocalFree(psz);
            }
        }

    } while (err == ERROR_INSUFFICIENT_BUFFER);
}

如果我们没有管理员用户帐户,则提升是通过另一个用户帐户进行的。在这种情况下,提升的进程没有更多链接 session (如果我们尝试查询链接 token ,我们会收到错误 - 指定的登录 session 不存在。它可能已经终止。)。接下来可能的解决方案(在一般情况下也是如此):

对于用户进程,默认 DACL 通常为 SystemUserSid 授予 GENERIC_ALLGENERIC_READ | GENERIC_EXECUTE 用于登录 session SID。我们可以查询进程 token ,获取它的默认 DACL,在 DACL 中找到 LogonSession SID 并将其访问掩码更改为 GENERIC_ALL。这可以通过下一个代码来完成:

ULONG GetDaclForLogonSession(HANDLE hToken, PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    ULONG err;

    ULONG cb = 0x100;

    union {
        PTOKEN_DEFAULT_DACL p;
        PVOID buf;
    };

    do 
    {
        if (buf = LocalAlloc(0, cb))
        {
            if (!(err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenDefaultDacl, buf, cb, &cb))))
            {
                err = ERROR_NOT_FOUND;

                if (PACL Dacl = p->DefaultDacl)
                {
                    if (USHORT AceCount = Dacl->AceCount)
                    {
                        union {
                            PVOID pv;
                            PBYTE pb;
                            PACE_HEADER pah;
                            PACCESS_ALLOWED_ACE paaa;
                        };

                        pv = Dacl + 1;

                        static const SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY;
                        do 
                        {
                            switch (pah->AceType)
                            {
                            case ACCESS_ALLOWED_ACE_TYPE:
                                PSID Sid = &paaa->SidStart;
                                if (*GetSidSubAuthorityCount(Sid) == SECURITY_LOGON_IDS_RID_COUNT &&
                                    *GetSidSubAuthority(Sid, 0) == SECURITY_LOGON_IDS_RID &&
                                    !memcmp(GetSidIdentifierAuthority(Sid), &NtAuth, sizeof(NtAuth)))
                                {
                                    paaa->Mask = GENERIC_ALL;
                                    *DefaultDacl = p;
                                    return 0;
                                }

                                break;
                            }
                            pb += pah->AceSize;

                        } while (--AceCount);
                    }
                }
            }

            LocalFree(buf);
        }
        else
        {
            return GetLastError();
        }

    } while (err == ERROR_INSUFFICIENT_BUFFER);

    return err;
}

ULONG GetDaclForLogonSession(PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    HANDLE hToken;
    ULONG err = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));

    if (!err)
    {
        err = GetDaclForLogonSession(hToken, DefaultDacl);

        CloseHandle(hToken);
    }

    return err;
}

作为我们得到 DACL 的结果,我们授予对当前登录 session 的所有访问权限。用法相同 - 只需将调用从 GetNotElevatedDefaultDacl 替换为 GetDaclForLogonSession

关于c++ - 默认安全描述符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52138913/

有关c++ - 默认安全描述符的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  3. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  4. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  5. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  6. ruby - 如何安全地删除文件? - 2

    在Ruby中是否有Gem或安全删除文件的方法?我想避免系统上可能不存在的外部程序。“安全删除”指的是覆盖文件内容。 最佳答案 如果您使用的是*nix,一个很好的方法是使用exec/open3/open4调用shred:`shred-fxuz#{filename}`http://www.gnu.org/s/coreutils/manual/html_node/shred-invocation.html检查这个类似的帖子:Writingafileshredderinpythonorruby?

  7. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  8. ruby-on-rails - 如何在 Rails 中设置路由的默认格式? - 2

    路由有如下代码:resources:orders,only:[:create],defaults:{format:'json'}resources:users,only:[:create,:update],defaults:{format:'json'}resources:delivery_types,only:[:index],defaults:{format:'json'}resources:time_corrections,only:[:index],defaults:{format:'json'}是否可以使用1个字符串为所有资源设置默认格式,每行不带“默认值”散列?谢谢。

  9. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐