Security & Hacking

command injection

zeroone-kr 2025. 2. 11. 17:02

 

 

 

command injection은 공격자의 controllable input이 쉘 실행 함수의 인자에 도달할때 발생한다.

이러한 함수의 예로 system, execve, popen가 있는데, 명령어 삽입과 출력 관점에서 차이점에 대해서 정리한다.

 

system()

#include <stdlib.h>

int main() {
    system("ls -l /home/");
    return 0;
}

 

$ strace -f --trace=execve ./system_test
execve("./system_test", ["./system_test"], 0x7ffda5ff59e0 /* 27 vars */) = 0
strace: Process 790 attached
[pid   790] execve("/bin/sh", ["sh", "-c", "ls -l /home/"], 0x7ffda3067f28 /* 27 vars */) = 0
strace: Process 791 attached
[pid   791] execve("/usr/bin/ls", ["ls", "-l", "/home/"], 0x561015968780 /* 27 vars */) = 0
total 4
drwxr-x--- 11 zeroone zeroone 4096 Feb 10 17:55 zeroone
[pid   791] +++ exited with 0 +++
[pid   790] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=791, si_uid=1000, si_status=0, si_utime=0, si_stime=2} ---
[pid   790] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=790, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

 

system함수는 결국엔 sh -c "명령어"를 execve의 인자로 동작한다.

즉, system("명령어")execve("/bin/sh", ["sh", "-c", "명령어"]); 와 동일한것을 확인할 수 있다.

명령어 실행결과는 standard output으로 출력된다.

 

execve()

#include <unistd.h>

int main() {
    char *args[] = {"/bin/ls", "-l", "/home", NULL};
    execve("/bin/ls", args, NULL);
    return 0;
}

 

$ strace -f --trace=execve ./execve_test
execve("./execve_test", ["./execve_test"], 0x7ffff89471a0 /* 26 vars */) = 0
execve("/bin/ls", ["/bin/ls", "-l", "/home"], NULL) = 0
total 4
drwxr-x--- 11 zeroone zeroone 4096 Feb 10 19:13 zeroone
+++ exited with 0 +++

 

execve() 함수 그대로를 실행하는 것을 확인할 수 있다.

명령어 실행 결과는 standard output으로 출력된다.

 

popen()

#include <stdio.h>

int main() {
    FILE *fp;
    char path[1035];

    /* Open the command for reading. */
    fp = popen("/bin/ls -l /home/", "r");
    if (fp == NULL) {
        printf("Failed to run command\n" );
        return 1;
    }

    /* Read the output a line at a time - output it. */
    while (fgets(path, sizeof(path)-1, fp) != NULL) {
        printf("%s", path);
    }

    /* close */
    pclose(fp);
    return 0;
}

 

$ strace -f --trace=execve ./popen_test
execve("./popen_test", ["./popen_test"], 0x7ffc6017b030 /* 26 vars */) = 0
strace: Process 2996 attached
[pid  2996] execve("/bin/sh", ["sh", "-c", "/bin/ls -l /home"], 0x7ffce4a7bd18 /* 26 vars */) = 0
strace: Process 2998 attached
[pid  2998] execve("/bin/ls", ["/bin/ls", "-l", "/home"], 0x5646ea7f5788 /* 26 vars */) = 0
total 4
drwxr-x--- 11 zeroone zeroone 4096 Feb 10 19:20 zeroone
[pid  2998] +++ exited with 0 +++
[pid  2996] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2998, si_uid=1000, si_status=0, si_utime=1, si_stime=1} ---
[pid  2996] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2996, si_uid=1000, si_status=0, si_utime=0, si_stime=2} ---
+++ exited with 0 +++

 

system()과 상당히 유사하게 동작한다.

마찬가지로 execve("/bin/sh", ["sh", "-c", "명령어"]); 와 동일하다. 

다만, 명령어 실행 결과는 pipe로 출력된다.

 

결론

명령어 삽입과 출력 관점에서 정리한다.

command injection이 일어나려면 우선 "sh -c 명령어"를 실행하는 형식이 되어야한다. 역은 아직 잘 모르겠다.

system() 과 popen()은 "sh -c 명령어" 로 실행되기 때문에 메타 쉘 문자를 사용하여 command injection이 execve()에 비해 비교적 쉽다. execve()는 sh -c 명령어로 실행되면 취약할 수 있다.

#include <unistd.h>

int main(){
	...
    char *args[] = {"sh", "-c", "ls -l /home; cat /etc/passwd", NULL};
    execve("/bin/sh", args, NULL);
	...
}

 

한편, system(), execve()는 기본적으로 표준 출력으로 결과를 출력하고, popen()은 pipe로 출력한다.

'Security & Hacking' 카테고리의 다른 글

3 types of embedded systems  (0) 2025.03.03
Android Security - exported 편  (0) 2025.02.16
CodeQL  (0) 2025.02.11
00_angr_find  (0) 2025.02.10
angr 핵심 개념  (0) 2025.02.10