Skip to content
Advertisement

Is there a command execution vulnerability in this C program?

So I am working on a challenge problem to find a vulnerability in a C program binary that allows a command to be executed by the program (using the effective UID in Linux).

I am really struggling to find how to do this with this particular program.

The disassembly of the function in question (main function):

 **************************************************************
 *                                                            *
 *  FUNCTION                                                  *
 **************************************************************
 int __cdecl main(int argc, char * * argv)
     int               EAX:4          <RETURN>
     int               Stack[0x4]:4   argc
     char * *          Stack[0x8]:4   argv                            XREF[2]:     000109b0(R), 
                                                                                   000109dd(R)  
     undefined4        Stack[-0x8]:4  local_8                         XREF[1]:     00010bcb(R)  
     int               Stack[-0xc]:4  in                              XREF[5]:     000109f0(W), 
                                                                                   000109f3(R), 
                                                                                   00010ad4(R), 
                                                                                   00010b27(R), 
                                                                                   00010b59(R)  
     int               Stack[-0x10]:4 fd                              XREF[6]:     00010a1f(W), 
                                                                                   00010a22(R), 
                                                                                   00010aa5(R), 
                                                                                   00010ab2(R), 
                                                                                   00010ac9(R), 
                                                                                   00010b4e(R)  
     pid_t             Stack[-0x14]:4 pid                             XREF[4]:     00010a6b(W), 
                                                                                   00010a6e(R), 
                                                                                   00010a8b(R), 
                                                                                   00010b6a(R)  
     int[2]            Stack[-0x1c]:8 pipefd                          XREF[3,3]:   00010a3f(*), 
                                                                                   00010a95(R), 
                                                                                   00010b42(R), 
                                                                                   00010abd(R), 
                                                                                   00010b0f(R), 
                                                                                   00010b36(R)  
     char              Stack[-0x1d]:1 c                               XREF[2]:     00010b14(*), 
                                                                                   00010b23(*)  
     int               Stack[-0x24]:4 status                          XREF[2]:     00010b66(*), 
                                                                                   00010b75(R)  
                     main                                    XREF[5]:     Entry Point(*), 
                                                                          _start:00010866(*), 00010d30, 
                                                                                  00010da0(*), 00011f34(*)  
0001097d 55              PUSH       EBP
0001097e 89 e5           MOV        EBP,ESP
00010980 53              PUSH       EBX
00010981 83 ec 1c        SUB        ESP,0x1c
00010984 e8 87 16        CALL       <EXTERNAL>::geteuid                              __uid_t geteuid(void)
         00 00
00010989 89 c3           MOV        EBX,EAX
0001098b e8 80 16        CALL       <EXTERNAL>::geteuid                              __uid_t geteuid(void)
         00 00
00010990 53              PUSH       EBX
00010991 50              PUSH       EAX
00010992 e8 9d 16        CALL       <EXTERNAL>::setreuid                             int setreuid(__uid_t __ruid, __u
         00 00
00010997 83 c4 08        ADD        ESP,0x8
0001099a e8 75 16        CALL       <EXTERNAL>::getegid                              __gid_t getegid(void)
         00 00
0001099f 89 c3           MOV        EBX,EAX
000109a1 e8 6e 16        CALL       <EXTERNAL>::getegid                              __gid_t getegid(void)
         00 00
000109a6 53              PUSH       EBX
000109a7 50              PUSH       EAX
000109a8 e8 9b 16        CALL       <EXTERNAL>::setregid                             int setregid(__gid_t __rgid, __g
         00 00
000109ad 83 c4 08        ADD        ESP,0x8
000109b0 8b 45 0c        MOV        EAX,dword ptr [EBP + argv]
000109b3 83 c0 04        ADD        EAX,0x4
000109b6 8b 00           MOV        EAX,dword ptr [EAX]
000109b8 85 c0           TEST       EAX,EAX
000109ba 75 21           JNZ        LAB_000109dd
000109bc a1 98 1f        MOV        EAX,[stderr]
         01 00
000109c1 50              PUSH       EAX
000109c2 6a 22           PUSH       0x22
000109c4 6a 01           PUSH       0x1
000109c6 68 50 0c        PUSH       s_Please_specify_the_file_to_verif_00010c50      = "Please specify the file to ve
         01 00
000109cb e8 50 16        CALL       <EXTERNAL>::fwrite                               size_t fwrite(void * __ptr, size
         00 00
000109d0 83 c4 10        ADD        ESP,0x10
000109d3 b8 01 00        MOV        EAX,0x1
         00 00
000109d8 e9 ee 01        JMP        LAB_00010bcb
         00 00
                     LAB_000109dd                                    XREF[1]:     000109ba(j)  
000109dd 8b 45 0c        MOV        EAX,dword ptr [EBP + argv]
000109e0 83 c0 04        ADD        EAX,0x4
000109e3 8b 00           MOV        EAX,dword ptr [EAX]
000109e5 6a 00           PUSH       0x0
000109e7 50              PUSH       EAX
000109e8 e8 43 16        CALL       <EXTERNAL>::open                                 int open(char * __file, int __of
         00 00
000109ed 83 c4 08        ADD        ESP,0x8
000109f0 89 45 f8        MOV        dword ptr [EBP + in],EAX
000109f3 83 7d f8 00     CMP        dword ptr [EBP + in],0x0
000109f7 79 17           JNS        LAB_00010a10
000109f9 68 73 0c        PUSH       DAT_00010c73                                     = 6Fh    o
         01 00
000109fe e8 19 16        CALL       <EXTERNAL>::perror                               void perror(char * __s)
         00 00
00010a03 83 c4 04        ADD        ESP,0x4
00010a06 b8 02 00        MOV        EAX,0x2
         00 00
00010a0b e9 bb 01        JMP        LAB_00010bcb
         00 00
                     LAB_00010a10                                    XREF[1]:     000109f7(j)  
00010a10 6a 02           PUSH       0x2
00010a12 68 78 0c        PUSH       s_/dev/null_00010c78                             = "/dev/null"
         01 00
00010a17 e8 14 16        CALL       <EXTERNAL>::open                                 int open(char * __file, int __of
         00 00
00010a1c 83 c4 08        ADD        ESP,0x8
00010a1f 89 45 f4        MOV        dword ptr [EBP + fd],EAX
00010a22 83 7d f4 00     CMP        dword ptr [EBP + fd],0x0
00010a26 79 17           JNS        LAB_00010a3f
00010a28 68 73 0c        PUSH       DAT_00010c73                                     = 6Fh    o
         01 00
00010a2d e8 ea 15        CALL       <EXTERNAL>::perror                               void perror(char * __s)
         00 00
00010a32 83 c4 04        ADD        ESP,0x4
00010a35 b8 05 00        MOV        EAX,0x5
         00 00
00010a3a e9 8c 01        JMP        LAB_00010bcb
         00 00
                     LAB_00010a3f                                    XREF[1]:     00010a26(j)  
00010a3f 8d 45 e8        LEA        EAX=>pipefd,[EBP + -0x18]
00010a42 50              PUSH       EAX
00010a43 e8 f8 15        CALL       <EXTERNAL>::pipe                                 int pipe(int * __pipedes)
         00 00
00010a48 83 c4 04        ADD        ESP,0x4
00010a4b 85 c0           TEST       EAX,EAX
00010a4d 79 17           JNS        LAB_00010a66
00010a4f 68 82 0c        PUSH       DAT_00010c82                                     = 70h    p
         01 00
00010a54 e8 c3 15        CALL       <EXTERNAL>::perror                               void perror(char * __s)
         00 00
00010a59 83 c4 04        ADD        ESP,0x4
00010a5c b8 03 00        MOV        EAX,0x3
         00 00
00010a61 e9 65 01        JMP        LAB_00010bcb
         00 00
                     LAB_00010a66                                    XREF[1]:     00010a4d(j)  
00010a66 e8 d9 15        CALL       <EXTERNAL>::fork                                 __pid_t fork(void)
         00 00
00010a6b 89 45 f0        MOV        dword ptr [EBP + pid],EAX
00010a6e 83 7d f0 00     CMP        dword ptr [EBP + pid],0x0
00010a72 79 17           JNS        LAB_00010a8b
00010a74 68 87 0c        PUSH       DAT_00010c87                                     = 66h    f
         01 00
00010a79 e8 9e 15        CALL       <EXTERNAL>::perror                               void perror(char * __s)
         00 00
00010a7e 83 c4 04        ADD        ESP,0x4
00010a81 b8 04 00        MOV        EAX,0x4
         00 00
00010a86 e9 40 01        JMP        LAB_00010bcb
         00 00
                     LAB_00010a8b                                    XREF[1]:     00010a72(j)  
00010a8b 83 7d f0 00     CMP        dword ptr [EBP + pid],0x0
00010a8f 0f 85 8c        JNZ        LAB_00010b21
         00 00 00
00010a95 8b 45 e8        MOV        EAX,dword ptr [EBP + pipefd[0]]
00010a98 6a 00           PUSH       0x0
00010a9a 50              PUSH       EAX
00010a9b e8 60 15        CALL       <EXTERNAL>::dup2                                 int dup2(int __fd, int __fd2)
         00 00
00010aa0 83 c4 08        ADD        ESP,0x8
00010aa3 6a 01           PUSH       0x1
00010aa5 ff 75 f4        PUSH       dword ptr [EBP + fd]
00010aa8 e8 53 15        CALL       <EXTERNAL>::dup2                                 int dup2(int __fd, int __fd2)
         00 00
00010aad 83 c4 08        ADD        ESP,0x8
00010ab0 6a 02           PUSH       0x2
00010ab2 ff 75 f4        PUSH       dword ptr [EBP + fd]
00010ab5 e8 46 15        CALL       <EXTERNAL>::dup2                                 int dup2(int __fd, int __fd2)
         00 00
00010aba 83 c4 08        ADD        ESP,0x8
00010abd 8b 45 ec        MOV        EAX,dword ptr [EBP + pipefd[1]]
00010ac0 50              PUSH       EAX
00010ac1 e8 8a 15        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010ac6 83 c4 04        ADD        ESP,0x4
00010ac9 ff 75 f4        PUSH       dword ptr [EBP + fd]
00010acc e8 7f 15        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010ad1 83 c4 04        ADD        ESP,0x4
00010ad4 ff 75 f8        PUSH       dword ptr [EBP + in]
00010ad7 e8 74 15        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010adc 83 c4 04        ADD        ESP,0x4
00010adf 6a 00           PUSH       0x0
00010ae1 68 8c 0c        PUSH       s_-asxml_00010c8c                                = "-asxml"
         01 00
00010ae6 68 93 0c        PUSH       DAT_00010c93                                     = 74h    t
         01 00
00010aeb 68 93 0c        PUSH       DAT_00010c93                                     = 74h    t
         01 00
00010af0 e8 17 15        CALL       <EXTERNAL>::execlp                               int execlp(char * __file, char *
         00 00
00010af5 83 c4 10        ADD        ESP,0x10
00010af8 68 98 0c        PUSH       s_execlp_00010c98                                = "execlp"
         01 00
00010afd e8 1a 15        CALL       <EXTERNAL>::perror                               void perror(char * __s)
         00 00
00010b02 83 c4 04        ADD        ESP,0x4
00010b05 b8 05 00        MOV        EAX,0x5
         00 00
00010b0a e9 bc 00        JMP        LAB_00010bcb
         00 00
                     LAB_00010b0f                                    XREF[1]:     00010b34(j)  
00010b0f 8b 45 ec        MOV        EAX,dword ptr [EBP + pipefd[1]]
00010b12 6a 01           PUSH       0x1
00010b14 8d 55 e7        LEA        EDX=>c,[EBP + -0x19]
00010b17 52              PUSH       EDX
00010b18 50              PUSH       EAX
00010b19 e8 1e 15        CALL       <EXTERNAL>::write                                ssize_t write(int __fd, void * _
         00 00
00010b1e 83 c4 0c        ADD        ESP,0xc
                     LAB_00010b21                                    XREF[1]:     00010a8f(j)  
00010b21 6a 01           PUSH       0x1
00010b23 8d 45 e7        LEA        EAX=>c,[EBP + -0x19]
00010b26 50              PUSH       EAX
00010b27 ff 75 f8        PUSH       dword ptr [EBP + in]
00010b2a e8 d5 14        CALL       <EXTERNAL>::read                                 ssize_t read(int __fd, void * __
         00 00
00010b2f 83 c4 0c        ADD        ESP,0xc
00010b32 85 c0           TEST       EAX,EAX
00010b34 75 d9           JNZ        LAB_00010b0f
00010b36 8b 45 ec        MOV        EAX,dword ptr [EBP + pipefd[1]]
00010b39 50              PUSH       EAX
00010b3a e8 11 15        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010b3f 83 c4 04        ADD        ESP,0x4
00010b42 8b 45 e8        MOV        EAX,dword ptr [EBP + pipefd[0]]
00010b45 50              PUSH       EAX
00010b46 e8 05 15        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010b4b 83 c4 04        ADD        ESP,0x4
00010b4e ff 75 f4        PUSH       dword ptr [EBP + fd]
00010b51 e8 fa 14        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010b56 83 c4 04        ADD        ESP,0x4
00010b59 ff 75 f8        PUSH       dword ptr [EBP + in]
00010b5c e8 ef 14        CALL       <EXTERNAL>::close                                int close(int __fd)
         00 00
00010b61 83 c4 04        ADD        ESP,0x4
00010b64 6a 00           PUSH       0x0
00010b66 8d 45 e0        LEA        EAX=>status,[EBP + -0x20]
00010b69 50              PUSH       EAX
00010b6a ff 75 f0        PUSH       dword ptr [EBP + pid]
00010b6d e8 b2 14        CALL       <EXTERNAL>::waitpid                              __pid_t waitpid(__pid_t __pid, i
         00 00
00010b72 83 c4 0c        ADD        ESP,0xc
00010b75 8b 45 e0        MOV        EAX,dword ptr [EBP + status]
00010b78 c1 f8 08        SAR        EAX,0x8
00010b7b 0f b6 c0        MOVZX      EAX,AL
00010b7e 83 f8 01        CMP        EAX,0x1
00010b81 74 18           JZ         LAB_00010b9b
00010b83 83 f8 02        CMP        EAX,0x2
00010b86 74 22           JZ         LAB_00010baa
00010b88 85 c0           TEST       EAX,EAX
00010b8a 75 2d           JNZ        LAB_00010bb9
00010b8c 68 9f 0c        PUSH       DAT_00010c9f                                     = 4Fh    O
         01 00
00010b91 e8 92 14        CALL       <EXTERNAL>::puts                                 int puts(char * __s)
         00 00
00010b96 83 c4 04        ADD        ESP,0x4
00010b99 eb 2b           JMP        LAB_00010bc6
                     LAB_00010b9b                                    XREF[1]:     00010b81(j)  
00010b9b 68 a4 0c        PUSH       s_Your_file_is_not_completely_comp_00010ca4      = "Your file is not completely c
         01 00
00010ba0 e8 83 14        CALL       <EXTERNAL>::puts                                 int puts(char * __s)
         00 00
00010ba5 83 c4 04        ADD        ESP,0x4
00010ba8 eb 1c           JMP        LAB_00010bc6
                     LAB_00010baa                                    XREF[1]:     00010b86(j)  
00010baa 68 ca 0c        PUSH       s_Your_file_contains_errors_00010cca             = "Your file contains errors"
         01 00
00010baf e8 74 14        CALL       <EXTERNAL>::puts                                 int puts(char * __s)
         00 00
00010bb4 83 c4 04        ADD        ESP,0x4
00010bb7 eb 0d           JMP        LAB_00010bc6
                     LAB_00010bb9                                    XREF[1]:     00010b8a(j)  
00010bb9 68 e4 0c        PUSH       s_I_can't_tell_if_your_file_is_XHT_00010ce4      = "I can't tell if your file is 
         01 00
00010bbe e8 65 14        CALL       <EXTERNAL>::puts                                 int puts(char * __s)
         00 00
00010bc3 83 c4 04        ADD        ESP,0x4
                     LAB_00010bc6                                    XREF[3]:     00010b99(j), 00010ba8(j), 
                                                                                  00010bb7(j)  
00010bc6 b8 00 00        MOV        EAX,0x0
         00 00
                     LAB_00010bcb                                    XREF[6]:     000109d8(j), 00010a0b(j), 
                                                                                  00010a3a(j), 00010a61(j), 
                                                                                  00010a86(j), 00010b0a(j)  
00010bcb 8b 5d fc        MOV        EBX,dword ptr [EBP + local_8]
00010bce c9              LEAVE
00010bcf c3              RET

According to Ghidra, this decompiles to:

int main(int argc,char **argv)

{
  __uid_t __euid;
  __uid_t __ruid;
  __gid_t __egid;
  __gid_t __rgid;
  int iVar1;
  int __fd;
  int iVar2;
  __pid_t __pid;
  ssize_t sVar3;
  uint uVar4;
  int status;
  char c;
  int pipefd [2];
  pid_t pid;
  int fd;
  int in;
  
  __euid = geteuid();
  __ruid = geteuid();
  setreuid(__ruid,__euid);
  __egid = getegid();
  __rgid = getegid();
  setregid(__rgid,__egid);
  if (argv[1] == (char *)0x0) {
    fwrite("Please specify the file to verifyn",1,0x22,stderr);
    iVar1 = 1;
  }
  else {
    iVar1 = open(argv[1],0);
    if (iVar1 < 0) {
      perror("open");
      iVar1 = 2;
    }
    else {
      __fd = open("/dev/null",2);
      if (__fd < 0) {
        perror("open");
        iVar1 = 5;
      }
      else {
        iVar2 = pipe(pipefd);
        if (iVar2 < 0) {
          perror("pipe");
          iVar1 = 3;
        }
        else {
          __pid = fork();
          if (__pid < 0) {
            perror("fork");
            iVar1 = 4;
          }
          else if (__pid == 0) {
            dup2(pipefd[0],0);
            dup2(__fd,1);
            dup2(__fd,2);
            close(pipefd[1]);
            close(__fd);
            close(iVar1);
            execlp("tidy","tidy","-asxml",0);
            perror("execlp");
            iVar1 = 5;
          }
          else {
            while( true ) {
              sVar3 = read(iVar1,&c,1);
              if (sVar3 == 0) break;
              write(pipefd[1],&c,1);
            }
            close(pipefd[1]);
            close(pipefd[0]);
            close(__fd);
            close(iVar1);
            waitpid(__pid,&status,0);
            uVar4 = status >> 8 & 0xff;
            if (uVar4 == 1) {
              puts("Your file is not completely compliant");
            }
            else if (uVar4 == 2) {
              puts("Your file contains errors");
            }
            else if (uVar4 == 0) {
              puts("OK!");
            }
            else {
              puts("I can't tell if your file is XHTML-compliant");
            }
            iVar1 = 0;
          }
        }
      }
    }
  }
  return iVar1;
}

It appears it is (to summarize) opening the file passed as the first argument using open in read only mode. If successful, it is forking and using the child process to execute tidy to validate the file is valid XHTML.

Nothing about it stands out to me as an obvious vulnerability that I can use here. I’ve looked into vulnerabilities for the tidy command, but wasn’t really able to find anything useful for this.

Any help would be much appreciated!

Advertisement

Answer

In regular C code, execlp("tidy","tidy","-asxml",0); is incorrect as execlp() expects a null pointer argument to mark the end of the argument list.

0 is a null pointer when used in a pointer context, which this is not. Yet on architectures where pointers have the same size and passing convention as int, such as 32-bit linux, passing 0 or passing NULL generate the same code, so sloppiness does not get punished.

In 64-bit mode, it would be incorrect to do so but you might get lucky with the x86_64 ABI and a 64-bit 0 value will be passed in this case.

In your own code, avoid such pitfalls and use NULL or (char *)0 as the last argument for execlp(). But on this listing, Ghidra produces code that generates the same assembly code, and in 32-bit mode, passing 0 or (char *)0 produce the same code, so no problem here.

In your context, execlp("tidy","tidy","-asxml",0); shows another problem: it will look for an executable program with the name tidy in the current PATH and run this program as tidy with a command line argument -asxml. Since it changed the effective uid and gid, this is a problem if the program is setuid root because you can create a program named tidy in a directory appearing in the PATH variable before the system directories and this program will be run with the modified rights.

Another potential problem is the program does not check for failure of the system calls setreuid() and setregid(). Although these calls are unlikely to fail for the arguments passed, as documented in the manual pages, it is a grave security error to omit checking for a failure return from setreuid(). In case of failure, the real and effective uid (or gid) is not changed and the process may fork and exec with root privileges.

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement