Skip to content

Commit e4d5b1e

Browse files
committed
PS: Add a query for SQL injection.
1 parent c015c74 commit e4d5b1e

File tree

5 files changed

+189
-0
lines changed

5 files changed

+189
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about
3+
* SQL-injection vulnerabilities, as well as extension points for
4+
* adding your own.
5+
*/
6+
7+
private import semmle.code.powershell.dataflow.DataFlow
8+
import semmle.code.powershell.ApiGraphs
9+
private import semmle.code.powershell.dataflow.flowsources.FlowSources
10+
private import semmle.code.powershell.Cfg
11+
12+
module SqlInjection {
13+
/**
14+
* A data flow source for SQL-injection vulnerabilities.
15+
*/
16+
abstract class Source extends DataFlow::Node {
17+
/** Gets a string that describes the type of this flow source. */
18+
abstract string getSourceType();
19+
}
20+
21+
/**
22+
* A data flow sink for SQL-injection vulnerabilities.
23+
*/
24+
abstract class Sink extends DataFlow::Node {
25+
abstract string getSinkType();
26+
}
27+
28+
/**
29+
* A sanitizer for SQL-injection vulnerabilities.
30+
*/
31+
abstract class Sanitizer extends DataFlow::Node { }
32+
33+
/** A source of user input, considered as a flow source for command injection. */
34+
class FlowSourceAsSource extends Source instanceof SourceNode {
35+
override string getSourceType() { result = "user-provided value" }
36+
}
37+
38+
class InvokeSqlCmdSink extends Sink {
39+
InvokeSqlCmdSink() {
40+
exists(DataFlow::CallNode call | call.matchesName("Invoke-Sqlcmd") |
41+
this = call.getNamedArgument("query")
42+
or
43+
not call.hasNamedArgument("query") and
44+
this = call.getArgument(0)
45+
)
46+
}
47+
48+
override string getSinkType() { result = "call to Invoke-Sqlcmd" }
49+
}
50+
51+
class ConnectionStringWriteSink extends Sink {
52+
string memberName;
53+
54+
ConnectionStringWriteSink() {
55+
exists(CfgNodes::StmtNodes::AssignStmtCfgNode assign |
56+
memberName = "CommandText" and
57+
assign
58+
.getLeftHandSide()
59+
.(CfgNodes::ExprNodes::MemberExprCfgNode)
60+
.memberNameMatches(memberName) and
61+
assign.getRightHandSide() = this.asExpr()
62+
)
63+
}
64+
65+
override string getSinkType() { result = "write to " + memberName }
66+
}
67+
68+
class SqlCmdSink extends Sink {
69+
SqlCmdSink() {
70+
exists(DataFlow::CallOperatorNode call |
71+
call.getCommand().asExpr().getValue().stringMatches("sqlcmd") and
72+
call.getAnArgument() = this
73+
)
74+
}
75+
76+
override string getSinkType() { result = "call to sqlcmd" }
77+
}
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about
3+
* SQL-injection vulnerabilities (CWE-078).
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `SqlInjectionFlow` is needed, otherwise
7+
* `SqlInjectionCustomizations` should be imported instead.
8+
*/
9+
10+
import powershell
11+
import semmle.code.powershell.dataflow.TaintTracking
12+
import SqlInjectionCustomizations::SqlInjection
13+
import semmle.code.powershell.dataflow.DataFlow
14+
15+
private module Config implements DataFlow::ConfigSig {
16+
predicate isSource(DataFlow::Node source) { source instanceof Source }
17+
18+
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
19+
20+
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
21+
}
22+
23+
/**
24+
* Taint-tracking for reasoning about SQL-injection vulnerabilities.
25+
*/
26+
module SqlInjectionFlow = TaintTracking::Global<Config>;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>If a SQL query is built using string concatenation, and the
7+
components of the concatenation include user input, a user
8+
is likely to be able to run malicious database queries.</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>Usually, it is better to use a prepared statement than to build a
13+
complete query with string concatenation. A prepared statement can
14+
include a parameter, written as either a question mark (<code>?</code>) or with
15+
an explicit name (<code>@parameter</code>), for each part of the SQL query that is
16+
expected to be filled in by a different value each time it is run.
17+
When the query is later executed, a value must be
18+
supplied for each parameter in the query.</p>
19+
20+
<p>It is good practice to use prepared statements for supplying
21+
parameters to a query, whether or not any of the parameters are
22+
directly traceable to user input. Doing so avoids any need to worry
23+
about quoting and escaping.</p>
24+
</recommendation>
25+
26+
<example>
27+
<p>In the following example, the code runs a simple SQL query in two different ways.</p>
28+
29+
<p>The first way involves building a query, <code>query1</code>, by interpolating a
30+
user-supplied text value with some string literals. The value can include special
31+
characters, so this code allows for SQL injection attacks.</p>
32+
33+
<p>The second way builds a query, <code>query2</code>, with a
34+
single string literal that includes a parameter (<code>@username</code>). The parameter
35+
is then given a value by providing a hash table <code>$params</code> when executing the
36+
query. This version is immune to injection attacks, because any special characters are
37+
not given any special treatment.</p>
38+
39+
<sample src="examples/SqlInjection.ps1" />
40+
</example>
41+
42+
<references>
43+
<li>MSDN: <a href="https://msdn.microsoft.com/en-us/library/ff648339.aspx">How To: Protect From SQL Injection in ASP.NET</a>.</li>
44+
</references>
45+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name SQL query built from user-controlled sources
3+
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
4+
* malicious SQL code by the user.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 8.8
8+
* @precision high
9+
* @id powershell/microsoft/public/sql-injection
10+
* @tags correctness
11+
* security
12+
* external/cwe/cwe-089
13+
*/
14+
15+
import powershell
16+
import semmle.code.powershell.security.SqlInjectionQuery
17+
import SqlInjectionFlow::PathGraph
18+
19+
from SqlInjectionFlow::PathNode source, SqlInjectionFlow::PathNode sink, Source sourceNode
20+
where
21+
SqlInjectionFlow::flowPath(source, sink) and
22+
sourceNode = source.getNode()
23+
select sink.getNode(), source, sink, "This SQL query depends on a $@.", sourceNode,
24+
sourceNode.getSourceType()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
param(
2+
[string]$userinput
3+
)
4+
5+
# BAD: The user input is directly interpolated into the SQL query string
6+
$query1 = "SELECT * FROM users WHERE name = '$userinput'"
7+
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query
8+
9+
# GOOD: Using parameters to prevent SQL injection
10+
$query2 = "SELECT * FROM users WHERE name = @username"
11+
12+
$params = @{
13+
username = $userinput
14+
}
15+
16+
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query -QueryParameters $params

0 commit comments

Comments
 (0)