#include <stdlib.h>
int system(const char *command);
system()
은 command를 입력으로 받아 fork()
를 통해 자식을 생성하고, 다음과 같은 execl
함수로 shell command를 실행시키는 함수다.
execl("/bin/sh", "sh", "-c", command, (char *) NULL);
command가 실행되는 동안 SIGCHLD가 blocked 되고, SIGINT와 SIGQUIT가 무시된다.
command가 NULL인 경우, system()은 shell이 사용가능한지 상태를 반환한다.
system()
은 fork
, execl
, waitpid
호출의 세부사항을 처리해주며, 필요한 신호들을 조정해줌으로써, 단순성과 편리성을 제공한다.
system
을 호출하는 경우, 프로그램이 중단되지 않을 수 있다는 문제점이 있다. while(something){
int ret = system("foo");
//If Child Process Terminated by a Signal
if(WIFSIGNALED(ret) && \
//The Signal is SIGINT or SIGQUIT
TERMSIG(ret) == SIGINT || WTERMSIG(ret) == SIGQUIT)
break;
}
예시 코드 : loop 내에서 system을 호출하고 signal 발생으로 loop를 빠져나가는 경우
pthread_atfork
는 fork()
를 호출할 때, 실행할 fork handler를 선언하는 함수로, 이때 지정된 fork handler는 system()
실행 중에 호출되는지의 여부는 지정되지 않았으며, glibc 구현에서는 handler는 호출되지 않는다. #include <pthread.h>
int pthread_atfork(void (*prepare)(void) ,
void (*parent)(void) , void (*child)(void));
system
함수 사용 시, 일부 환경 변수에 대한 비정상적인 값이 시스템 무결성을 파괴하는 데 사용될 수 있기 때문에 setuid
, setgid
기능이 있는 프로그램과 같이 권한이 있는 프로그램에서는 사용을 금한다.환경 변수를 조작하는 예시는 아래와 같다.
system()
을 호출하는 경우 안전한 호출이 파괴될 수 있다.system()
시작 시 권한을 삭제하기 때문에, system()
은 bash 2인 시스템에서 권한이 있는 프로그램을 실행할 경우 제대로 동작하지 않는다.popen()
, system()
은 명령 쉘을 호출함으로써 구현되며,
execlp()
와 execvp()
도 filename에 ‘/’가 붙지 않은 경우에 쉘의 동작을 복제한다.
이때 쉘 메타 문자에 영향을 받게 되는데, 이로 인해 예기치 않은 저수준 루틴을 호출할 수 있어, 많은 가이드라인에서는 popen()
, system()
, execlp()
, execvp()
사용을 전부 피하고, 프로세스를 생성하려고 할 때는 C에서 직접적으로 execve()
을 사용하도록 제안하고 있다 [2].
또한, 리눅스와 유닉스를 위한 Secure Programming 가이드 라인 [3]에서는
system()
은 쉘을 사용하여 명령을 계속 주입할 수 있어, Command Injection 등 비정상적인 사용 시도가 발생할 가능성이 있기 때문에,
최소한 execve()
를 사용할 수 있는 경우에 system()
을 사용하지 말라고 언급하고 있다.
권한이 있는 프로그램에서 system()
을 사용할 때 예기치 않은 쉘 명령, 명령 옵션이 실행되지 않도록, command 실행의 일부로 사용되는 사용자 입력은 신중히 처리해야 한다.
system()
의 대안 : execve()
execve()는 쉘을 실행하는 프로세스를 생성하고, command를 실행하는 system()과는 달리, 현재 프로세스에서 실행 중인 프로그램을 새 프로그램으로 대체하는 함수다.
기존 프로세스가 새로운 프로그램을 실행할 수 있도록 정렬한다.
#include <unistd.h>
int execve(const char *pathname, char *const argv[],
char *const envp[]);
execve()
는 system()
과 달리 전체 쉘 인터프리터를 사용하지 않으므로 Command Injection 공격에 취약하지 않다. 또한 입력이 args
배열에 통합되어 execve ()
에 인수로 전달되므로 의도하지 않은 시스템 실행을 방지할 수 있다.
#include <stdlib.h>
int main(void)
{
char *command = "/bin/ls -a";
system(command);
return 0;
}
system()
을 이용한 ls -a
커맨드 실행
#include <unistd.h>
int main(void)
{
char *command[] = {"ls", "-a", 0};
char *envp[] = {0};
execve("/bin/ls", command, envp);
return 0;
}
execve()
을 이용한 ls -a
커맨드 실행
system()
의 대안 : exec 함수 계열exec 함수 계열은 현재 프로세스 이미지를 새로운 프로세스 이미지로 대체한다.
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
하지만 exec
함수 계열 중 execlp()
, execvp()
, execvpe()
함수는 지정된 파일 이름에 ‘/’ 가 포함되어 있지 않으면 실행 파일을 검색할 때 쉘의 동작을 복제하기 때문에 PATH 환경 변수 값이 신뢰할 수 있을 경우에만 파일 이름에 ‘/’ 문자 없이 사용해야 한다.
The Unix Secure Programming FAQ [2] 에 의하면 쉘을 호출할 위험이 있는 execlp()
, execvp()
의 사용을 피하고 가급적 execve()
를 직접적으로 사용하라고 제안하고 있다.