-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Hardening: Avoid shell-based popen usage in InspectFile operator #3489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3/master
Are you sure you want to change the base?
Changes from all commits
80bcbbf
ffc4b67
0c1209d
35ad0d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -13,18 +13,31 @@ | |||
| * | ||||
| */ | ||||
|
|
||||
| /* | ||||
| * ModSecurity, http://www.modsecurity.org/ | ||||
| * Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. | ||||
| * | ||||
| * Licensed under the Apache License, Version 2.0 | ||||
| */ | ||||
|
|
||||
| #include "src/operators/inspect_file.h" | ||||
|
|
||||
| #include <stdio.h> | ||||
|
|
||||
| #include <string> | ||||
| #include <iostream> | ||||
| #include <sstream> | ||||
| #include <array> | ||||
| #include <vector> | ||||
|
|
||||
| #include "src/operators/operator.h" | ||||
| #include "src/utils/system.h" | ||||
|
|
||||
| #ifdef WIN32 | ||||
| #include "src/compat/msvc.h" | ||||
| #else | ||||
| #include <unistd.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/wait.h> | ||||
| #endif | ||||
|
|
||||
| namespace modsecurity { | ||||
|
|
@@ -52,40 +65,103 @@ bool InspectFile::init(const std::string ¶m2, std::string *error) { | |||
| return true; | ||||
| } | ||||
|
|
||||
|
|
||||
| bool InspectFile::evaluate(Transaction *transaction, const std::string &str) { | ||||
| if (m_isScript) { | ||||
| return m_lua.run(transaction, str); | ||||
| } else { | ||||
| FILE *in; | ||||
| char buff[512]; | ||||
| std::stringstream s; | ||||
| std::string res; | ||||
| std::string openstr; | ||||
|
|
||||
| openstr.append(m_param); | ||||
| openstr.append(" "); | ||||
| openstr.append(str); | ||||
| if (!(in = popen(openstr.c_str(), "r"))) { | ||||
| return false; | ||||
| } | ||||
|
|
||||
| while (fgets(buff, sizeof(buff), in) != NULL) { | ||||
| s << buff; | ||||
| } | ||||
|
|
||||
| pclose(in); | ||||
|
|
||||
| res.append(s.str()); | ||||
| if (res.size() > 1 && res[0] != '1') { | ||||
| return true; /* match */ | ||||
| } | ||||
|
|
||||
| /* no match */ | ||||
| } | ||||
|
|
||||
| #ifndef WIN32 | ||||
| /* | ||||
| * SECURITY HARDENING: | ||||
| * Replace shell-based popen() execution with fork()+execvp() | ||||
| * to avoid shell interpretation while preserving behavior. | ||||
| */ | ||||
|
|
||||
| std::array<int, 2> pipefd{}; | ||||
| if (pipe(pipefd.data()) == -1) { | ||||
| return false; | ||||
| } | ||||
|
|
||||
| pid_t pid = fork(); | ||||
| if (pid == -1) { | ||||
| close(pipefd[0]); | ||||
| close(pipefd[1]); | ||||
| return false; | ||||
| } | ||||
|
|
||||
| if (pid == 0) { | ||||
| // Child process | ||||
| close(pipefd[0]); | ||||
| dup2(pipefd[1], STDOUT_FILENO); | ||||
| close(pipefd[1]); | ||||
|
|
||||
| // Create mutable copies (avoid const_cast) | ||||
| std::string param_copy = m_param; | ||||
| std::string str_copy = str; | ||||
|
|
||||
| std::vector<char*> argv; | ||||
| argv.push_back(param_copy.data()); | ||||
| argv.push_back(str_copy.data()); | ||||
| argv.push_back(nullptr); | ||||
|
|
||||
| execvp(argv[0], argv.data()); | ||||
|
Comment on lines
+98
to
+107
|
||||
|
|
||||
| _exit(1); // exec failed | ||||
| } | ||||
|
|
||||
| // Parent process | ||||
| close(pipefd[1]); | ||||
|
|
||||
| std::stringstream s; | ||||
| std::array<char, 512> buff{}; | ||||
| ssize_t count; | ||||
|
|
||||
| while ((count = read(pipefd[0], buff.data(), buff.size())) > 0) { | ||||
| s.write(buff.data(), count); | ||||
| } | ||||
|
|
||||
| close(pipefd[0]); | ||||
| waitpid(pid, nullptr, 0); | ||||
|
|
||||
|
Comment on lines
+119
to
+125
|
||||
| if (const std::string res = s.str(); | ||||
| res.size() > 1 && res[0] != '1') { | ||||
| return true; | ||||
| } | ||||
|
|
||||
| return false; | ||||
|
|
||||
|
Comment on lines
+126
to
+132
|
||||
| #else | ||||
| /* | ||||
| * Windows fallback: preserve existing behavior | ||||
| */ | ||||
| FILE *in; | ||||
| std::array<char, 512> buff{}; | ||||
| std::stringstream s; | ||||
| std::string res; | ||||
|
||||
| std::string res; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Copilot review mentions a variable with the same name: res - it's shadowed here.
And that's the problem of cppcheck now (see):
2026-03-05T19:58:57.7181420Z warning: src/operators/inspect_file.cc,157,style,shadowVariable,Local variable 'res' shadows outer variable
2026-03-05T19:58:57.7307690Z warning: src/operators/inspect_file.cc,140,style,unusedVariable,Unused variable: res
(use CTRL-F and warning to find these).
Copilot
AI
Mar 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This structured if statement is also mis-indented in the Windows fallback path. Please reformat it to match the surrounding style (condition alignment, brace placement, and indentation of the return).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also an indentation request change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file already contains the full ModSecurity/Apache 2.0 license header at the top. This additional shorter license block is redundant and can create confusion/inconsistency across the codebase; please remove the duplicated header and keep the existing canonical one only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, this is true, it's a bit weird to see two licenses block :).
Please remove this one.