C++ : NULL application name with an unquoted path in call to CreateProcess

Calling a function of the CreatePorcess* family of functions, which may result in a security vulnerability if the path contains spaces.
This commit is contained in:
Raul Garcia
2018-10-12 15:57:01 -07:00
parent 54493eb990
commit 85283d63ce
9 changed files with 714 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
STARTUPINFOW si;
PROCESS_INFORMATION pi;
// ...
CreateProcessW( // BUG
NULL, // lpApplicationName
(LPWSTR)L"C:\\Program Files\\MyApp", // lpCommandLine
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
// ...

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>This query indicates that there is a call to a function of the <code>CreatePorcess*</code> family of functions, which may result in a security vulnerability if the path contains spaces.</p>
</overview>
<recommendation>
<p>Do not use <code>NULL</code> for the <code>lpApplicationName</code> argument to the <code>CreateProcess*</code> function.</p>
<p>If you pass <code>NULL</code> for <code>lpApplicationName</code>, use quotation marks around the executable path in <code>lpCommandLine</code>.</p>
</recommendation>
<example>
<p>In the following example, <code>CreateProcessW</code> is called with a NULL value for <code>lpApplicationName</code>,
and the value for <code>lpCommandLine</code> that represent the application path is not quoted and has spaces int.</p>
<p>If an attacker has access to the file system, it is possible to elevate privileges by creating a file such as "C:\Program.exe" that will be executed instead of the intended application.</p>
<sample src="UnsafeCreateProcessCall.cpp" />
<p>To fix this issue, specify a valid string for <code>lpApplicationName</code>, or quote the path for <code>lpCommandLine</code>. For example:</p>
<p><code>(LPWSTR)L"\"C:\\Program Files\\MyApp\"", // lpCommandLine</code></p>
</example>
<references>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa">CreateProcessA function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessw">CreateProcessW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessasusera">CreateProcessAsUserA function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw">CreateProcessAsUserW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createprocesswithlogonw">CreateProcessWithLogonW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createprocesswithtokenw">CreateProcessWithTokenW function (Microsoft documentation).</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,135 @@
/**
* @name NULL application name with an unquoted path in call to CreateProcess
* @description Calling a function of the CreatePorcess* family of functions, which may result in a security vulnerability if the path contains spaces.
* @id cpp/unsafe-create-process-call
* @kind problem
* @problem.severity error
* @precision medium
* @tags security
* external/cwe/cwe-428
* external/microsoft/C6277
*/
import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.dataflow.DataFlow2
/**
* A function call to CreateProcess (either wide-char or single byte string versions)
*/
class CreateProcessFunctionCall extends FunctionCall {
CreateProcessFunctionCall() {
(
this.getTarget().hasGlobalName("CreateProcessA") or
this.getTarget().hasGlobalName("CreateProcessW") or
this.getTarget().hasGlobalName("CreateProcessWithTokenW") or
this.getTarget().hasGlobalName("CreateProcessWithLogonW") or
this.getTarget().hasGlobalName("CreateProcessAsUserA") or
this.getTarget().hasGlobalName("CreateProcessAsUserW")
)
}
int getApplicationNameArgumentId() {
if(
this.getTarget().hasGlobalName("CreateProcessA") or
this.getTarget().hasGlobalName("CreateProcessW")
) then ( result = 0 )
else if (
this.getTarget().hasGlobalName("CreateProcessWithTokenW")
) then ( result = 2 )
else if (
this.getTarget().hasGlobalName("CreateProcessWithLogonW")
) then ( result = 4 )
else if(
this.getTarget().hasGlobalName("CreateProcessAsUserA") or
this.getTarget().hasGlobalName("CreateProcessAsUserW")
) then ( result = 1 )
else (result = -1 )
}
int getCommandLineArgumentId() {
if(
this.getTarget().hasGlobalName("CreateProcessA") or
this.getTarget().hasGlobalName("CreateProcessW")
) then ( result = 1 )
else if (
this.getTarget().hasGlobalName("CreateProcessWithTokenW")
) then ( result = 3 )
else if (
this.getTarget().hasGlobalName("CreateProcessWithLogonW")
) then ( result = 5 )
else if(
this.getTarget().hasGlobalName("CreateProcessAsUserA") or
this.getTarget().hasGlobalName("CreateProcessAsUserW")
) then ( result = 2 )
else (result = -1 )
}
}
/**
* Dataflow that detects a call to CreateProcess with a NULL value for lpApplicationName argument
*/
class NullAppNameCreateProcessFunctionConfiguration extends DataFlow::Configuration {
NullAppNameCreateProcessFunctionConfiguration() {
this = "NullAppNameCreateProcessFunctionConfiguration"
}
override predicate isSource(DataFlow::Node source) {
nullValue(source.asExpr())
}
override predicate isSink(DataFlow::Node sink) {
exists(
CreateProcessFunctionCall call, Expr val |
val = sink.asExpr() |
val = call.getArgument(call.getApplicationNameArgumentId())
)
}
}
/**
* Dataflow that detects a call to CreateProcess with an unquoted commandLine argument
*/
class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Configuration {
QuotedCommandInCreateProcessFunctionConfiguration() {
this = "QuotedCommandInCreateProcessFunctionConfiguration"
}
override predicate isSource(DataFlow2::Node source) {
exists( string s |
s = source.asExpr().getValue().toString()
and
not isQuotedApplicationNameOnCmd(s)
)
}
override predicate isSink(DataFlow2::Node sink) {
exists(
CreateProcessFunctionCall call, Expr val |
val = sink.asExpr() |
val = call.getArgument(call.getCommandLineArgumentId())
)
}
}
bindingset[s]
predicate isQuotedApplicationNameOnCmd(string s){
s.regexpMatch("\"([^\"])*\"(\\s|.)*")
}
from CreateProcessFunctionCall call, string msg1, string msg2
where
exists( Expr source, Expr appName,
NullAppNameCreateProcessFunctionConfiguration nullAppConfig |
appName = call.getArgument(call.getApplicationNameArgumentId())
and nullAppConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(appName))
and msg1 = call.toString() + " with lpApplicationName == NULL (" + appName + ")"
)
and
exists( Expr source, Expr cmd,
QuotedCommandInCreateProcessFunctionConfiguration quotedConfig |
cmd = call.getArgument(call.getCommandLineArgumentId())
and quotedConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(cmd))
and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") may result in a security vulnerability if the path contains spaces."
)
select call, msg1 + " " + msg2