home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / winbase / security / winnt / rcmd / server.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-10-05  |  16.2 KB  |  580 lines

  1. /*++
  2.  
  3. Copyright 1996 - 1997 Microsoft Corporation
  4.  
  5. Module Name:
  6.  
  7.     server.c
  8.  
  9. Abstract:
  10.  
  11.     This sample illustrates the new DuplicateTokenEx() API for Windows NT 4.0.
  12.     This portion of the sample illustrates how to implement a remote cmd
  13.     server which launches cmd.exe in the security context of the client that
  14.     connected to the server.
  15.  
  16.     When a client connects to the named-pipe, the server process impersonates
  17.     the client and then saves a copy of the impersonation token.  This token
  18.     is then duplicated via DuplicateTokenEx() to a primary level access token.
  19.     The primary level token is then supplied to CreateProcessAsUser(), which
  20.     launches a process in the security context of the client.  All threads in
  21.     the new process inherit the security context of the client.  All processes
  22.     created by the new process inherit the security context of the client.  The
  23.     input and output of the new process will be redirected to the client over
  24.     the named-pipes, which provides a means of interactively executing commands
  25.     remotely in a secure manner.
  26.  
  27.     In order to allow processes to run in a different security context than
  28.     the server process, it is necessary to adjust the security on the
  29.     windowstation and desktop objects associated with the server process.
  30.     This is required to allow for proper console initialization.
  31.  
  32.     For simplicity, this sample applies a Null Dacl to the windowstation and
  33.     desktop objects, which is generally not appropriate in a production
  34.     environment.  The best approach to selectively secure the windowstation and
  35.     desktop objects is to extract the Logon Sid from the access token returned
  36.     by DuplicateTokenEx().  The Logon Sid can be extracted with
  37.     GetTokenInformation() and then added to an access allowed ace which is
  38.     supplied in a Dacl which contains the existing access allowed aces in
  39.     addition to the new access allowed ace.
  40.  
  41.     Some additional considerations with this sample follow:
  42.  
  43.     * The account which the server process runs in needs the following two
  44.       privileges granted, to allow for the call to CreateProcessAsUser() to
  45.       succeed:
  46.  
  47.       SeAssignPrimaryTokenPrivilege (Replace a process level token)
  48.       SeIncreateQuotaPrivilege (Increase quotas)
  49.  
  50.     * The security on the initial server side named-pipe allows for Everyone
  51.       to connect.  This may present security problems in the event that the
  52.       server's file system has not be secured appropriately.  In a production
  53.       environment, it may be appropriate to apply a more restrictive Dacl on
  54.       the initial named-pipe.
  55.  
  56.     * This sample only allows one client to be connected at a time.
  57.  
  58.     * Any child processes started by the launched process will not be terminated
  59.       when the initial child process exits.
  60.  
  61.  
  62. Author:
  63.  
  64.     Scott Field (sfield)    02-Apr-96
  65.  
  66. --*/
  67.  
  68. #include <windows.h>
  69. #include <stdio.h>
  70.  
  71. BOOL
  72. BuildNamedPipeAcl(
  73.     PACL pAcl,
  74.     PDWORD cbAclSize
  75.     );
  76.  
  77. BOOL
  78. SetupNamedPipes(
  79.     PSECURITY_ATTRIBUTES saInbound,
  80.     PSECURITY_ATTRIBUTES saOutbound,
  81.     PHANDLE hFileIn,
  82.     PHANDLE hFileOut
  83.     );
  84.  
  85. BOOL
  86. SetWinstaDesktopSecurity(
  87.     void
  88.     );
  89.  
  90. void
  91. DisplayLastError(
  92.     LPSTR szAPI // pointer to Ansi function name
  93.     );
  94.  
  95. //
  96. // this defines the commandline that the server will execute in the security
  97. // context of the client.  The /Q switch tells cmd.exe not to echo the
  98. // entered commands back to the client.
  99. //
  100. #define COMMANDLINE     TEXT("cmd.exe /Q")
  101.  
  102. #define INBOUND_PIPE    TEXT("\\\\.\\pipe\\rcmd_in")
  103. #define OUTBOUND_PIPE   TEXT("\\\\.\\pipe\\rcmd_out")
  104.  
  105. #define RTN_OK 0
  106. #define RTN_ERROR 13
  107.  
  108. int
  109. __cdecl
  110. main(
  111.     void
  112.     )
  113. {
  114.     HANDLE hPipeInbound;
  115.     HANDLE hPipeOutbound;
  116.  
  117.     SECURITY_ATTRIBUTES saInbound;
  118.     SECURITY_DESCRIPTOR sd;
  119.  
  120.     BYTE AclBuf[ 64 ];
  121.     DWORD cbAclSize = 64;
  122.     PACL pAcl = (PACL)AclBuf;
  123.  
  124.     SECURITY_ATTRIBUTES saOutbound;
  125.  
  126.     HANDLE hImpersonationToken;
  127.     HANDLE hPrimaryToken;
  128.  
  129.     STARTUPINFO si;
  130.     PROCESS_INFORMATION pi;
  131.  
  132.     //
  133.     // suppress errors regarding startup directory, etc
  134.     //
  135.     SetErrorMode(SEM_FAILCRITICALERRORS);
  136.  
  137.     //
  138.     // build security attributes for named-pipes
  139.     //
  140.     // the inbound pipe will grant Everyone the ability to connect,
  141.     // and the server process full control over the pipe.  This way,
  142.     // only the server process has the ability to update security
  143.     // on the resource; clients can only connect.
  144.     //
  145.     // the outbound pipe will be built after the server impersonates
  146.     // the client, and will inherit the default security of the client
  147.     // as a result.  This allows only the same user that connected to
  148.     // the inbound pipe to connect to the outbound pipe
  149.     //
  150.  
  151.     if(!BuildNamedPipeAcl(pAcl, &cbAclSize)) {
  152.         DisplayLastError("BuildNamedPipeAcl");
  153.         return RTN_ERROR;
  154.     }
  155.  
  156.     if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
  157.         DisplayLastError("InitializeSecurityDescriptor");
  158.         return RTN_ERROR;
  159.     }
  160.  
  161.     if(!SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE)) {
  162.         DisplayLastError("SetSecurityDescriptorDacl");
  163.         return RTN_ERROR;
  164.     }
  165.  
  166.     saInbound.nLength = sizeof(SECURITY_ATTRIBUTES);
  167.     saInbound.lpSecurityDescriptor = &sd;
  168.     saInbound.bInheritHandle = TRUE;
  169.  
  170.     saOutbound.nLength = sizeof(SECURITY_ATTRIBUTES);
  171.     saOutbound.lpSecurityDescriptor = NULL; // default Dacl of caller
  172.     saOutbound.bInheritHandle = TRUE;
  173.  
  174.     //
  175.     // modify security on windowstation and desktop to allow for correct
  176.     // process and console initialization by arbitrary clients
  177.     //
  178.     if(!SetWinstaDesktopSecurity()) {
  179.         DisplayLastError("SetWinstaDesktopSecurity");
  180.         return RTN_ERROR;
  181.     }
  182.  
  183.     while(1) {
  184.  
  185.     hPipeInbound = INVALID_HANDLE_VALUE;
  186.     hPipeOutbound = INVALID_HANDLE_VALUE;
  187.     hImpersonationToken = INVALID_HANDLE_VALUE;
  188.     hPrimaryToken = INVALID_HANDLE_VALUE;
  189.     pi.hProcess = INVALID_HANDLE_VALUE;
  190.     pi.hThread = INVALID_HANDLE_VALUE;
  191.  
  192.     if(!SetupNamedPipes(
  193.         &saInbound,
  194.         &saOutbound,
  195.         &hPipeInbound,
  196.         &hPipeOutbound
  197.         )) {
  198.  
  199.         //
  200.         // just bail out on failure for now
  201.         //
  202.         DisplayLastError("SetupNamedPipes");
  203.         break;
  204.     }
  205.  
  206.     //
  207.     // obtain the impersonation token from the current thread
  208.     //
  209.     if(!OpenThreadToken(
  210.         GetCurrentThread(),
  211.         TOKEN_DUPLICATE,
  212.         TRUE,
  213.         &hImpersonationToken
  214.         )) {
  215.         DisplayLastError("OpenThreadToken");
  216.         goto cleanup;
  217.     }
  218.  
  219.     //
  220.     // duplicate the impersonation token to primary
  221.     // since we are impersonating the client, the token will get the
  222.     // default Dacl of the client
  223.     //
  224.     if(!DuplicateTokenEx(
  225.         hImpersonationToken,
  226.         TOKEN_IMPERSONATE | TOKEN_READ |
  227.         TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
  228.         NULL,
  229.         SecurityImpersonation,
  230.         TokenPrimary,
  231.         &hPrimaryToken
  232.         )) {
  233.         DisplayLastError("DuplicateTokenEx");
  234.         goto cleanup;
  235.     }
  236.  
  237.     RevertToSelf();
  238.     CloseHandle(hImpersonationToken);
  239.     hImpersonationToken = INVALID_HANDLE_VALUE;
  240.  
  241.     //
  242.     // setup STARTUPINFO structure
  243.     //
  244.     si.cb = sizeof(STARTUPINFO);
  245.     si.lpReserved = NULL;
  246.     si.lpDesktop = NULL;
  247.     si.lpTitle = NULL;
  248.     si.cbReserved2 = 0;
  249.     si.lpReserved2 = NULL;
  250.     si.dwFlags = STARTF_USESTDHANDLES;
  251.     si.hStdOutput = hPipeOutbound;
  252.     si.hStdError = hPipeOutbound;
  253.     si.hStdInput = hPipeInbound;
  254.  
  255.     //
  256.     // create a process running as the user
  257.     //
  258.  
  259.     if(!CreateProcessAsUser(
  260.         hPrimaryToken,
  261.         NULL,
  262.         COMMANDLINE, // commandline to execute
  263.         NULL,   // process sa
  264.         NULL,   // thread sa
  265.         TRUE,   // inherit handles?
  266.         0,      // process creation flags (inherit existing console)
  267.         NULL,   // environment
  268.         NULL,   // current directory
  269.         &si,    // startupinfo
  270.         &pi     // processinfo
  271.         )) {
  272.         DisplayLastError("CreateProcessAsUser");
  273.         goto cleanup;
  274.     }
  275.  
  276. cleanup:
  277.  
  278.     if(hImpersonationToken != INVALID_HANDLE_VALUE) {
  279.         RevertToSelf();
  280.         CloseHandle(hImpersonationToken);
  281.     }
  282.  
  283.     if(hPrimaryToken != INVALID_HANDLE_VALUE) {
  284.         CloseHandle(hPrimaryToken);
  285.     }
  286.  
  287.     if(pi.hThread != INVALID_HANDLE_VALUE) {
  288.         CloseHandle(pi.hThread);
  289.     }
  290.  
  291.     if(pi.hProcess != INVALID_HANDLE_VALUE) {
  292.         WaitForSingleObject(pi.hProcess, INFINITE);
  293.         CloseHandle(pi.hProcess);
  294.  
  295.         //
  296.         // TODO kill any child by enumerating process tokens in the system
  297.         // by looking at the tokenID
  298.         //
  299.     }
  300.  
  301.     if(hPipeInbound != INVALID_HANDLE_VALUE) {
  302.         DisconnectNamedPipe(hPipeInbound);
  303.         CloseHandle(hPipeInbound);
  304.     }
  305.  
  306.     if(hPipeOutbound != INVALID_HANDLE_VALUE) {
  307.         DisconnectNamedPipe(hPipeOutbound);
  308.         CloseHandle(hPipeOutbound);
  309.     }
  310.  
  311.     } // while
  312.  
  313.     return RTN_OK;
  314. }
  315.  
  316. /***
  317.  If the function succeeds, the return value is TRUE.  The parameters
  318.  hFileIn and hFileOut will point to the Inbound and Outbound named-pipe
  319.  handles.  The current thread will be impersonating the client.
  320.  
  321.  If the function fails, the return value is FALSE.  The current thread will
  322.  not be impersonating the client.
  323. ***/
  324. BOOL
  325. SetupNamedPipes(
  326.     PSECURITY_ATTRIBUTES saInbound, // security attributes for Inbound pipe
  327.     PSECURITY_ATTRIBUTES saOutbound,// security attributes for Outbound pipe
  328.     PHANDLE hFileIn,                // resultant Inbound pipe handle on success
  329.     PHANDLE hFileOut                // resultant Outbound pipe handle on success
  330.     )
  331. {
  332.     HANDLE hPipeInbound = INVALID_HANDLE_VALUE;
  333.     HANDLE hPipeOutbound = INVALID_HANDLE_VALUE;
  334.  
  335.     BOOL bSuccess = FALSE; // assume this function fails
  336.  
  337.     //
  338.     // create Inbound named-pipe
  339.     //
  340.     hPipeInbound = CreateNamedPipe(
  341.         INBOUND_PIPE,
  342.         PIPE_ACCESS_INBOUND,
  343.         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
  344.         1, // number of instances
  345.         4096,
  346.         4096,
  347.         NMPWAIT_USE_DEFAULT_WAIT,
  348.         saInbound
  349.         );
  350.  
  351.     if(hPipeInbound == INVALID_HANDLE_VALUE) {
  352.         DisplayLastError("CreateNamedPipe");
  353.         return FALSE;
  354.     }
  355.  
  356.     printf("Waiting for connection... ");
  357.  
  358.     //
  359.     // wait for somebody to connect to the Inbound named pipe
  360.     //
  361.     ConnectNamedPipe(hPipeInbound, NULL);
  362.  
  363.     //
  364.     // impersonate the client
  365.     //
  366.     if(!ImpersonateNamedPipeClient(hPipeInbound)) {
  367.         DisplayLastError("ImpersonateNamedPipeClient");
  368.         goto cleanup;
  369.     }
  370.  
  371.     //
  372.     // create Outbound named-pipe
  373.     // the security on this named-pipe will be inherited from the default Dacl
  374.     // of the client that just connected, because this pipe instance is
  375.     // created in that users security context.  This prevents other users
  376.     // from connecting to the Outbound pipe.
  377.     //
  378.     hPipeOutbound = CreateNamedPipe(
  379.         OUTBOUND_PIPE,
  380.         PIPE_ACCESS_OUTBOUND,
  381.         PIPE_TYPE_MESSAGE | PIPE_WAIT,
  382.         1, // number of instances
  383.         4096,
  384.         4096,
  385.         NMPWAIT_USE_DEFAULT_WAIT,
  386.         saOutbound
  387.         );
  388.  
  389.     if(hPipeOutbound == INVALID_HANDLE_VALUE) {
  390.         DisplayLastError("CreateNamedPipe");
  391.         goto cleanup;
  392.     }
  393.  
  394.     //
  395.     // wait for the client to connect to the Outbound named pipe
  396.     //
  397.     ConnectNamedPipe(hPipeOutbound, NULL);
  398.  
  399.     printf("Client connected!\n");
  400.  
  401.     *hFileIn = hPipeInbound;
  402.     *hFileOut = hPipeOutbound;
  403.  
  404.     bSuccess = TRUE;
  405.  
  406. cleanup:
  407.  
  408.     if(!bSuccess) {
  409.         RevertToSelf();
  410.  
  411.         if(hPipeInbound != INVALID_HANDLE_VALUE)
  412.             CloseHandle(hPipeInbound);
  413.         if(hPipeOutbound != INVALID_HANDLE_VALUE)
  414.             CloseHandle(hPipeOutbound);
  415.     }
  416.  
  417.     return bSuccess;
  418. }
  419.  
  420. /**
  421. This function builds a Dacl which grants the creator of the objects
  422. FILE_ALL_ACCESS and Everyone FILE_GENERIC_READ and FILE_GENERIC_WRITE
  423. access to the object.
  424.  
  425. This Dacl allows for higher security than a NULL Dacl, which is common for
  426. named-pipes, as this only grants the creator/owner write access to the
  427. security descriptor, and grants Everyone the ability to "use" the named-pipe.
  428. This scenario prevents a malevolent user from disrupting service by preventing
  429. arbitrary access manipulation.
  430. **/
  431. BOOL
  432. BuildNamedPipeAcl(
  433.     PACL pAcl,
  434.     PDWORD cbAclSize
  435.     )
  436. {
  437.     DWORD dwAclSize;
  438.  
  439.     SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
  440.     SID_IDENTIFIER_AUTHORITY siaCreator = SECURITY_CREATOR_SID_AUTHORITY;
  441.  
  442.     BYTE BufEveryoneSid[32];
  443.     BYTE BufOwnerSid[32];
  444.  
  445.     PSID pEveryoneSid = (PSID)BufEveryoneSid;
  446.     PSID pOwnerSid = (PSID)BufOwnerSid;
  447.  
  448.     //
  449.     // compute size of acl
  450.     //
  451.     dwAclSize = sizeof(ACL) +
  452.         2 * ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
  453.         GetSidLengthRequired( 1 ) + // well-known Everyone Sid
  454.         GetSidLengthRequired( 1 ) ; // well-known Creator Owner Sid
  455.  
  456.     if(*cbAclSize < dwAclSize) {
  457.         *cbAclSize = dwAclSize;
  458.         return FALSE;
  459.     }
  460.  
  461.     *cbAclSize = dwAclSize;
  462.  
  463.     //
  464.     // intialize well known sids
  465.     //
  466.  
  467.     if(!InitializeSid(pEveryoneSid, &siaWorld, 1)) return FALSE;
  468.     *GetSidSubAuthority(pEveryoneSid, 0) = SECURITY_WORLD_RID;
  469.  
  470.     if(!InitializeSid(pOwnerSid, &siaCreator, 1)) return FALSE;
  471.     *GetSidSubAuthority(pOwnerSid, 0) = SECURITY_CREATOR_OWNER_RID;
  472.  
  473.     if(!InitializeAcl(pAcl, dwAclSize, ACL_REVISION))
  474.         return FALSE;
  475.  
  476.     //
  477.     //
  478.     if(!AddAccessAllowedAce(
  479.         pAcl,
  480.         ACL_REVISION,
  481.         FILE_GENERIC_READ | FILE_GENERIC_WRITE,
  482.         pEveryoneSid
  483.         ))
  484.         return FALSE;
  485.  
  486.     //
  487.     //
  488.     return AddAccessAllowedAce(
  489.         pAcl,
  490.         ACL_REVISION,
  491.         FILE_ALL_ACCESS,
  492.         pOwnerSid
  493.         );
  494. }
  495.  
  496. /**
  497. This function adjusts the security on the current windowstation and desktop,
  498. to allow arbitrary client to have access to the windowstation and desktop.
  499. This is necessary to allow for correct process and console initialization.
  500.  
  501. Note that in a secure environment, it would be appropriate to create
  502. a specific desktop and launch the process spawned by the client on that
  503. desktop, rather than opening up the current desktop to the client.
  504.  
  505. This function simply applies a NULL Dacl to the current windowstation and
  506. desktop in order to reduce the size of this sample.  Applying NULL Dacls
  507. to objects is generally not a wise idea.
  508.  
  509. TODO revist this function later to better address security on the windowstation
  510. and desktop.
  511. **/
  512. BOOL
  513. SetWinstaDesktopSecurity(
  514.     void
  515.     )
  516. {
  517.     HWINSTA hWinsta;
  518.     HDESK hDesk;
  519.  
  520.     SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;
  521.     SECURITY_DESCRIPTOR sd;
  522.  
  523.     hWinsta = GetProcessWindowStation();
  524.     if(hWinsta == NULL) return FALSE;
  525.  
  526.     hDesk = GetThreadDesktop(GetCurrentThreadId());
  527.     if(hDesk == NULL) return FALSE;
  528.  
  529.     InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
  530.     SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE);
  531.  
  532.     if(!SetUserObjectSecurity(hWinsta, &si, &sd)) return FALSE;
  533.  
  534.     return SetUserObjectSecurity(hDesk, &si, &sd);
  535. }
  536.  
  537. void
  538. DisplayLastError(
  539.     LPSTR szAPI // pointer to Ansi function name
  540.     )
  541. {
  542.     LPSTR MessageBuffer;
  543.     DWORD dwBufferLength;
  544.  
  545.     //
  546.     // TODO get this fprintf out of here!
  547.     //
  548.     fprintf(stderr,"%s error!\n", szAPI);
  549.  
  550.     if(dwBufferLength=FormatMessageA(
  551.             FORMAT_MESSAGE_ALLOCATE_BUFFER |
  552.             FORMAT_MESSAGE_FROM_SYSTEM,
  553.             NULL,
  554.             GetLastError(),
  555.             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  556.             (LPSTR) &MessageBuffer,
  557.             0,
  558.             NULL
  559.             ))
  560.     {
  561.         DWORD dwBytesWritten; // unused
  562.  
  563.         //
  564.         // Output message string on stderr
  565.         //
  566.         WriteFile(
  567.                 GetStdHandle(STD_ERROR_HANDLE),
  568.                 MessageBuffer,
  569.                 dwBufferLength,
  570.                 &dwBytesWritten,
  571.                 NULL
  572.                 );
  573.  
  574.         //
  575.         // free the buffer allocated by the system
  576.         //
  577.         LocalFree(MessageBuffer);
  578.     }
  579. }
  580.