[C][execev][waitpid] Separate parameters and executable to prevent sh command injection.


這幾天遇到一個狀況,
一隻用來收檔案的cgi被種了指令,指令是埋在Post payload的某欄位混進來的,
欄位值被系統當作檔名操作的時候,就順便root執行這行指令了。

嘗試了一些迴避方法,
根據這裡的建議,寫出了一個wrapper

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
//#define UNIT_TEST
#ifdef UNIT_TEST
#ifdef LOG_ERROR
#undef LOG_ERROR
#define LOG_ERROR(...) printf("[ERROR]");printf(__VA_ARGS__);printf("\n")
#endif
#ifdef LOG_DEBUG
#undef LOG_DEBUG
#define LOG_DEBUG(...) printf("[DEBUG]");printf(__VA_ARGS__);printf("\n")
#endif
#endif
int system_safe(char const **cmd, const char *output_file)
{
pid_t pid;
int status;
pid_t ret;
char *env[]={0};
int fd_null = NULL;
int fd_output = NULL;
if (cmd == NULL || *cmd == NULL || strlen(cmd[0]) == 0){
LOG_ERROR("Invalid executable.");
return -1;
}
LOG_DEBUG("cmd[0]: %s", cmd[0]);
pid = fork();
if (pid == 0){
//dump stderr
fd_null = open("/dev/null", O_WRONLY);
if (fd_null != NULL){
if(dup2(fd_null, 2) == -1){//stderr
LOG_ERROR("dup2 failed with errno %s", strerror(errno));
}
}
//if output file path specified, redirect stdout to file.
if (output_file != NULL){
fd_output = open(output_file, O_WRONLY | O_CREAT);
if (fd_output != NULL){
if(dup2(fd_output, 1) == -1){//stdout
LOG_ERROR("dup2 failed with errno %s", strerror(errno));
}
}
else{
if(dup2(fd_null, 1) == -1){//stdout
LOG_ERROR("dup2 failed with errno %s", strerror(errno));
}
}
}
else{
if (fd_null != NULL){
if(dup2(fd_null, 1) == -1){//stdout
LOG_ERROR("dup2 failed with errno %s", strerror(errno));
}
}
}
if (fd_null != NULL){
close(fd_null);//close fd
}
if (fd_output != NULL){
close(fd_output);
}
if (execve(cmd[0], &cmd[0], env) == -1) {
LOG_ERROR("execve %s failed with : %s", cmd[0], strerror(errno));
return -1;
}
}
else if(pid == -1){
LOG_ERROR("fork failed");
return -1;
}
else{
LOG_DEBUG("pid: %d", pid);
//wait for only specified child process
//set option 0 to let it block.
ret = waitpid(pid, &status, 0);
//waitpid returns given pid when end successfully.
//otherwise check errno
if(ret == -1) {
if (errno != EINTR) {
//child process was not terminated by parent's received signal
LOG_ERROR("child ended with errno %s", strerror(errno));
return -1;
}
}
else{
if(WIFEXITED(status)){
//should always go this way
LOG_DEBUG("execve status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
else{
//child process ended normally but status was wrong
LOG_ERROR("child ended with !WIFEXITED(status)");
return -1;
}
}
}
}
view raw system_safe.c hosted with ❤ by GitHub
/**
* Executing system command safe from command injection.
* @param cmd: system command. ex: char *cmd[]={"/usr/bin/test", "-e", "readme", NULL};
* Executable must be absolute path, NULL is a must at end of array.
* @param output_file: file path to redirect stdout to, put NULL to ignore stdout.
* @return : If command executed sucessfully, return command's exit code, otherwise return -1.
*/
int system_safe(char const **cmd, const char *output_file);
view raw system_safe.h hosted with ❤ by GitHub

裡面小撞牆的地方是,Executable要給absolute path,不然errno會一直報「找不到」
然後SIGNAL還沒有處理.....
繼續奮鬥 zzzZZZZ

參考資料

waitpid用法詳解。
http://linux.die.net/man/2/waitpid
http://www.gnu.org/software/libc/manual/html_node/Process-Completion.html


留言