Merge pull request #215 from microsoft/ps-add-sql-injection-query

PS: Add SQL injection query
This commit is contained in:
Chanel
2025-04-29 10:31:55 -07:00
committed by GitHub
25 changed files with 330 additions and 42 deletions

View File

@@ -577,7 +577,7 @@ module API {
)
or
exists(MemberExprReadAccess read |
read.getMemberName().toLowerCase() = name and
read.getLowerCaseMemberName().toLowerCase() = name and
pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(read.getQualifier()))) and
succ = getForwardStartNode(getNodeFromExpr(read))
)

View File

@@ -24,6 +24,10 @@ class ConstantValue extends TConstantValue {
/** Gets the value of this consant. */
string getValue() { none() }
bindingset[s]
pragma[inline_late]
final predicate stringMatches(string s) { this.asString().toLowerCase() = s.toLowerCase() }
/** Gets the integer value of this constant, if any. */
int asInt() { none() }

View File

@@ -1,7 +1,7 @@
private import AstImport
class Function extends FunctionBase, TFunction {
final override string getName() { any(Synthesis s).functionName(this, result) }
final override string getLowerCaseName() { any(Synthesis s).functionName(this, result) }
final override ScriptBlock getBody() { any(Synthesis s).functionScriptBlock(this, result) }

View File

@@ -2,11 +2,13 @@ private import AstImport
private import semmle.code.powershell.controlflow.BasicBlocks
class FunctionBase extends Ast, TFunctionBase {
final override string toString() { result = this.getName() }
final override string toString() { result = this.getLowerCaseName() }
string getName() { none() }
string getLowerCaseName() { none() }
final predicate hasName(string name) { name = this.getName() }
bindingset[name]
pragma[inline_late]
predicate nameMatches(string name) { this.getLowerCaseName() = name.toLowerCase() }
ScriptBlock getBody() { none() }

View File

@@ -1,12 +1,16 @@
private import AstImport
class Member extends Ast, TMember {
string getName() {
result = getRawAst(this).(Raw::Member).getName()
string getLowerCaseName() {
result = getRawAst(this).(Raw::Member).getName().toLowerCase()
or
any(Synthesis s).memberName(this, result)
}
bindingset[name]
pragma[inline_late]
predicate memberNameMatches(string name) { this.getLowerCaseName() = name.toLowerCase() }
Type getDeclaringType() { result.getAMember() = this }
final Attribute getAttribute(int i) {

View File

@@ -28,19 +28,29 @@ class MemberExpr extends Expr, TMemberExpr {
}
/** Gets the name of the member being looked up, if any. */
string getMemberName() {
string getLowerCaseMemberName() {
result =
getRawAst(this).(Raw::MemberExpr).getMember().(Raw::StringConstExpr).getValue().getValue()
getRawAst(this)
.(Raw::MemberExpr)
.getMember()
.(Raw::StringConstExpr)
.getValue()
.getValue()
.toLowerCase()
}
bindingset[name]
pragma[inline_late]
predicate memberNameMatches(string name) { this.getLowerCaseMemberName() = name.toLowerCase() }
predicate isNullConditional() { getRawAst(this).(Raw::MemberExpr).isNullConditional() }
predicate isStatic() { getRawAst(this).(Raw::MemberExpr).isStatic() }
final override string toString() {
result = this.getMemberName()
result = this.getLowerCaseMemberName()
or
not exists(this.getMemberName()) and
not exists(this.getLowerCaseMemberName()) and
result = "..."
}

View File

@@ -1,7 +1,7 @@
private import AstImport
class Method extends Member, FunctionBase, TMethod {
final override string getName() { result = Member.super.getName() }
final override string getLowerCaseName() { result = Member.super.getLowerCaseName() }
final override ScriptBlock getBody() {
exists(Raw::Ast r | r = getRawAst(this) |
@@ -24,9 +24,7 @@ class Method extends Member, FunctionBase, TMethod {
predicate isConstructor() { getRawAst(this).(Raw::Method).isConstructor() }
ThisParameter getThisParameter() {
result.getFunction() = this
}
ThisParameter getThisParameter() { result.getFunction() = this }
}
/** A constructor definition. */

View File

@@ -1,7 +1,7 @@
private import AstImport
class PropertyMember extends Member, TPropertyMember {
final override string getName() { result = getRawAst(this).(Raw::PropertyMember).getName() }
final override string getLowerCaseName() { result = getRawAst(this).(Raw::PropertyMember).getName().toLowerCase() }
final override string toString() { result = this.getName() }
final override string toString() { result = this.getLowerCaseName() }
}

View File

@@ -1,21 +1,21 @@
private import AstImport
class Type extends Ast, TTypeSynth {
override string toString() { result = this.getName() }
override string toString() { result = this.getLowerCaseName() }
Member getMember(int i) { any(Synthesis s).typeMember(this, i, result) }
string getName() { any(Synthesis s).typeName(this, result) }
string getLowerCaseName() { any(Synthesis s).typeName(this, result) }
Member getAMember() { result = this.getMember(_) }
Method getMethod(string name) { result = this.getAMember() and result.getName() = name }
Method getMethod(string name) { result = this.getAMember() and result.getLowerCaseName() = name }
Method getAMethod() { result = this.getMethod(_) }
Constructor getAConstructor() {
result = this.getAMethod() and
result.getName() = this.getName()
result.getLowerCaseName() = this.getLowerCaseName()
}
TypeConstraint getBaseType(int i) { none() }

View File

@@ -1,9 +1,9 @@
private import AstImport
class TypeDefinitionStmt extends Stmt, TTypeDefinitionStmt {
string getName() { result = getRawAst(this).(Raw::TypeStmt).getName() }
string getLowerCaseName() { result = getRawAst(this).(Raw::TypeStmt).getName().toLowerCase() }
override string toString() { result = this.getName() }
override string toString() { result = this.getLowerCaseName() }
Member getMember(int i) {
exists(ChildIndex index, Raw::Ast r | index = typeStmtMember(i) and r = getRawAst(this) |
@@ -24,7 +24,7 @@ class TypeDefinitionStmt extends Stmt, TTypeDefinitionStmt {
Constructor getAConstructor() {
result = this.getAMethod() and
result.getName() = this.getName()
result.getLowerCaseName() = this.getLowerCaseName()
}
TypeConstraint getBaseType(int i) {
@@ -38,7 +38,7 @@ class TypeDefinitionStmt extends Stmt, TTypeDefinitionStmt {
TypeConstraint getABaseType() { result = this.getBaseType(_) }
TypeDefinitionStmt getASubtype() { result.getABaseType().getName() = this.getName() }
TypeDefinitionStmt getASubtype() { result.getABaseType().getName() = this.getLowerCaseName() }
Type getType() { synthChild(getRawAst(this), typeDefType(), result) }

View File

@@ -639,7 +639,11 @@ module ExprNodes {
ExprCfgNode getMemberExpr() { e.hasCfgChild(e.getMemberExpr(), this, result) }
string getMemberName() { result = e.getMemberName() }
string getLowerCaseMemberName() { result = e.getLowerCaseMemberName() }
bindingset[name]
pragma[inline_late]
predicate memberNameMatches(string name) { this.getLowerCaseMemberName() = name.toLowerCase() }
predicate isStatic() { e.isStatic() }
}
@@ -1504,7 +1508,7 @@ module StmtNodes {
Type getType() { result = s.getType() }
string getName() { result = s.getName() }
string getLowerCaseName() { result = s.getLowerCaseName() }
}
private class FunctionDefinitionChildMapping extends NonExprChildMapping, FunctionDefinitionStmt {

View File

@@ -210,10 +210,10 @@ Node trackInstance(string typename, boolean exact) {
}
private Type getTypeWithName(string s, boolean exact) {
result.getName() = s and
result.getLowerCaseName() = s and
exact = true
or
result.getASubtype+().getName() = s and
result.getASubtype+().getLowerCaseName() = s and
exact = false
}

View File

@@ -353,9 +353,9 @@ private module Cached {
cached
newtype TContent =
TFieldContent(string name) {
name = any(PropertyMember member).getName()
name = any(PropertyMember member).getLowerCaseName()
or
name = any(MemberExpr me).getMemberName()
name = any(MemberExpr me).getLowerCaseMemberName()
} or
// A known map key
TKnownKeyContent(ConstantValue cv) { exists(cv.asString()) } or
@@ -892,7 +892,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
exists(CfgNodes::ExprNodes::MemberExprWriteAccessCfgNode var, Content::FieldContent fc |
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = var.getQualifier() and
node1.asExpr() = var.getAssignStmt().getRightHandSide() and
fc.getName() = var.getMemberName() and
fc.getLowerCaseName() = var.getLowerCaseMemberName() and
c.isSingleton(fc)
)
or
@@ -961,7 +961,7 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
exists(CfgNodes::ExprNodes::MemberExprReadAccessCfgNode var, Content::FieldContent fc |
node2.asExpr() = var and
node1.asExpr() = var.getQualifier() and
fc.getName() = var.getMemberName() and
fc.getLowerCaseName() = var.getLowerCaseMemberName() and
c.isSingleton(fc)
)
or
@@ -1310,7 +1310,7 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
exists(kind) and
exists(FunctionBase f |
f.getBody() = c.asCfgScope() and
creation.asExpr().(CmdName).getName() = f.getName()
creation.asExpr().(CmdName).getName() = f.getLowerCaseName()
)
}

View File

@@ -301,7 +301,7 @@ module Content {
FieldContent() { this = TFieldContent(name) }
/** Gets the name of the field. */
string getName() { result = name }
string getLowerCaseName() { result = name }
override string toString() { result = name }
}
@@ -527,6 +527,9 @@ class CallNode extends ExprNode {
/** Gets the argument with the name `name`, if any. */
Node getNamedArgument(string name) { result.asExpr() = call.getNamedArgument(name) }
/** Holds if an argument with name `name` is provided to this call. */
predicate hasNamedArgument(string name) { call.hasNamedArgument(name) }
/**
* Gets any argument of this call.
*

View File

@@ -0,0 +1,78 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* SQL-injection vulnerabilities, as well as extension points for
* adding your own.
*/
private import semmle.code.powershell.dataflow.DataFlow
import semmle.code.powershell.ApiGraphs
private import semmle.code.powershell.dataflow.flowsources.FlowSources
private import semmle.code.powershell.Cfg
module SqlInjection {
/**
* A data flow source for SQL-injection vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
/** Gets a string that describes the type of this flow source. */
abstract string getSourceType();
}
/**
* A data flow sink for SQL-injection vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
abstract string getSinkType();
}
/**
* A sanitizer for SQL-injection vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A source of user input, considered as a flow source for command injection. */
class FlowSourceAsSource extends Source instanceof SourceNode {
override string getSourceType() { result = "user-provided value" }
}
class InvokeSqlCmdSink extends Sink {
InvokeSqlCmdSink() {
exists(DataFlow::CallNode call | call.matchesName("Invoke-Sqlcmd") |
this = call.getNamedArgument("query")
or
not call.hasNamedArgument("query") and
this = call.getArgument(0)
)
}
override string getSinkType() { result = "call to Invoke-Sqlcmd" }
}
class ConnectionStringWriteSink extends Sink {
string memberName;
ConnectionStringWriteSink() {
exists(CfgNodes::StmtNodes::AssignStmtCfgNode assign |
memberName = "CommandText" and
assign
.getLeftHandSide()
.(CfgNodes::ExprNodes::MemberExprCfgNode)
.memberNameMatches(memberName) and
assign.getRightHandSide() = this.asExpr()
)
}
override string getSinkType() { result = "write to " + memberName }
}
class SqlCmdSink extends Sink {
SqlCmdSink() {
exists(DataFlow::CallOperatorNode call |
call.getCommand().asExpr().getValue().stringMatches("sqlcmd") and
call.getAnArgument() = this
)
}
override string getSinkType() { result = "call to sqlcmd" }
}
}

View File

@@ -0,0 +1,26 @@
/**
* Provides a taint tracking configuration for reasoning about
* SQL-injection vulnerabilities (CWE-078).
*
* Note, for performance reasons: only import this file if
* `SqlInjectionFlow` is needed, otherwise
* `SqlInjectionCustomizations` should be imported instead.
*/
import powershell
import semmle.code.powershell.dataflow.TaintTracking
import SqlInjectionCustomizations::SqlInjection
import semmle.code.powershell.dataflow.DataFlow
private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
}
/**
* Taint-tracking for reasoning about SQL-injection vulnerabilities.
*/
module SqlInjectionFlow = TaintTracking::Global<Config>;

View File

@@ -26,5 +26,5 @@ class ReservedCharacter extends string {
}
from Function f, ReservedCharacter r
where f.getName().matches("%"+ r + "%")
where f.getLowerCaseName().matches("%"+ r + "%")
select f, "Function name contains a reserved character: " + r

View File

@@ -0,0 +1,45 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If a SQL query is built using string concatenation, and the
components of the concatenation include user input, a user
is likely to be able to run malicious database queries.</p>
</overview>
<recommendation>
<p>Usually, it is better to use a prepared statement than to build a
complete query with string concatenation. A prepared statement can
include a parameter, written as either a question mark (<code>?</code>) or with
an explicit name (<code>@parameter</code>), for each part of the SQL query that is
expected to be filled in by a different value each time it is run.
When the query is later executed, a value must be
supplied for each parameter in the query.</p>
<p>It is good practice to use prepared statements for supplying
parameters to a query, whether or not any of the parameters are
directly traceable to user input. Doing so avoids any need to worry
about quoting and escaping.</p>
</recommendation>
<example>
<p>In the following example, the code runs a simple SQL query in two different ways.</p>
<p>The first way involves building a query, <code>query1</code>, by interpolating a
user-supplied text value with some string literals. The value can include special
characters, so this code allows for SQL injection attacks.</p>
<p>The second way builds a query, <code>query2</code>, with a
single string literal that includes a parameter (<code>@username</code>). The parameter
is then given a value by providing a hash table <code>$params</code> when executing the
query. This version is immune to injection attacks, because any special characters are
not given any special treatment.</p>
<sample src="examples/SqlInjection.ps1" />
</example>
<references>
<li>MSDN: <a href="https://msdn.microsoft.com/en-us/library/ff648339.aspx">How To: Protect From SQL Injection in ASP.NET</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,24 @@
/**
* @name SQL query built from user-controlled sources
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
* malicious SQL code by the user.
* @kind path-problem
* @problem.severity error
* @security-severity 8.8
* @precision high
* @id powershell/microsoft/public/sql-injection
* @tags correctness
* security
* external/cwe/cwe-089
*/
import powershell
import semmle.code.powershell.security.SqlInjectionQuery
import SqlInjectionFlow::PathGraph
from SqlInjectionFlow::PathNode source, SqlInjectionFlow::PathNode sink, Source sourceNode
where
SqlInjectionFlow::flowPath(source, sink) and
sourceNode = source.getNode()
select sink.getNode(), source, sink, "This SQL query depends on a $@.", sourceNode,
sourceNode.getSourceType()

View File

@@ -0,0 +1,16 @@
param(
[string]$userinput
)
# BAD: The user input is directly interpolated into the SQL query string
$query1 = "SELECT * FROM users WHERE name = '$userinput'"
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query
# GOOD: Using parameters to prevent SQL injection
$query2 = "SELECT * FROM users WHERE name = @username"
$params = @{
username = $userinput
}
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query -QueryParameters $params

View File

@@ -4,7 +4,7 @@ binaryExpr
cmdExpr
| BinaryExpression.ps1:4:1:4:7 | [Stmt] result | BinaryExpression.ps1:4:1:4:7 | result |
| ExpandableString.ps1:1:1:1:39 | [Stmt] Date: $([DateTime]::Now)\nName: $name | ExpandableString.ps1:1:1:1:39 | Date: $([DateTime]::Now)\nName: $name |
| ExpandableString.ps1:1:23:1:37 | [Stmt] Now | ExpandableString.ps1:1:23:1:37 | Now |
| ExpandableString.ps1:1:23:1:37 | [Stmt] now | ExpandableString.ps1:1:23:1:37 | now |
| MemberExpression.ps1:2:1:2:14 | [Stmt] ... | MemberExpression.ps1:2:1:2:14 | ... |
| SubExpression.ps1:1:1:1:23 | [Stmt] Call to adddays | SubExpression.ps1:1:1:1:23 | Call to adddays |
| SubExpression.ps1:1:3:1:10 | [Stmt] Call to get-date | SubExpression.ps1:1:3:1:10 | Call to get-date |
@@ -15,5 +15,5 @@ invokeMemoryExpression
expandableString
| ExpandableString.ps1:1:1:1:39 | Date: $([DateTime]::Now)\nName: $name | 1 | ExpandableString.ps1:1:21:1:38 | $(...) |
memberExpr
| ExpandableString.ps1:1:23:1:37 | Now | ExpandableString.ps1:1:23:1:32 | DateTime |
| ExpandableString.ps1:1:23:1:37 | now | ExpandableString.ps1:1:23:1:32 | DateTime |
| MemberExpression.ps1:2:1:2:14 | ... | MemberExpression.ps1:2:1:2:10 | DateTime |

View File

@@ -229,11 +229,11 @@
| Expressions/ExpandableString.ps1:1:1:1:39 | {...} | Expressions/ExpandableString.ps1:1:1:1:39 | toplevel function for ExpandableString.ps1 |
| Expressions/ExpandableString.ps1:1:1:1:39 | {...} | Expressions/ExpandableString.ps1:1:1:1:39 | {...} |
| Expressions/ExpandableString.ps1:1:21:1:38 | $(...) | Expressions/ExpandableString.ps1:1:1:1:39 | Date: $([DateTime]::Now)\nName: $name |
| Expressions/ExpandableString.ps1:1:23:1:32 | DateTime | Expressions/ExpandableString.ps1:1:23:1:37 | Now |
| Expressions/ExpandableString.ps1:1:23:1:37 | Now | Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] Now |
| Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] Now | Expressions/ExpandableString.ps1:1:23:1:37 | {...} |
| Expressions/ExpandableString.ps1:1:23:1:32 | DateTime | Expressions/ExpandableString.ps1:1:23:1:37 | now |
| Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] now | Expressions/ExpandableString.ps1:1:23:1:37 | {...} |
| Expressions/ExpandableString.ps1:1:23:1:37 | now | Expressions/ExpandableString.ps1:1:23:1:37 | [Stmt] now |
| Expressions/ExpandableString.ps1:1:23:1:37 | {...} | Expressions/ExpandableString.ps1:1:21:1:38 | $(...) |
| Expressions/ExpandableString.ps1:1:35:1:37 | Now | Expressions/ExpandableString.ps1:1:23:1:37 | Now |
| Expressions/ExpandableString.ps1:1:35:1:37 | Now | Expressions/ExpandableString.ps1:1:23:1:37 | now |
| Expressions/MemberExpression.ps1:1:1:2:14 | [synth] pipeline | Expressions/MemberExpression.ps1:1:1:2:14 | {...} |
| Expressions/MemberExpression.ps1:1:1:2:14 | {...} | Expressions/MemberExpression.ps1:1:1:2:14 | toplevel function for MemberExpression.ps1 |
| Expressions/MemberExpression.ps1:1:1:2:14 | {...} | Expressions/MemberExpression.ps1:1:1:2:14 | {...} |

View File

@@ -0,0 +1,17 @@
edges
| test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | provenance | Src:MaD:11464 |
| test.ps1:1:14:1:45 | Call to read-host | test.ps1:9:72:9:77 | query | provenance | Src:MaD:11464 |
| test.ps1:1:14:1:45 | Call to read-host | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | Src:MaD:11464 |
| test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | Src:MaD:11464 |
nodes
| test.ps1:1:14:1:45 | Call to read-host | semmle.label | Call to read-host |
| test.ps1:5:72:5:77 | query | semmle.label | query |
| test.ps1:9:72:9:77 | query | semmle.label | query |
| test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$userinput' |
| test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$userinput' |
subpaths
#select
| test.ps1:5:72:5:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | user-provided value |
| test.ps1:9:72:9:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:9:72:9:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | user-provided value |
| test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | user-provided value |
| test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | user-provided value |

View File

@@ -0,0 +1 @@
queries/security/cwe-089/SqlInjection.ql

View File

@@ -0,0 +1,56 @@
$userinput = Read-Host "Please enter a value"
# Example using Invoke-Sqlcmd with string interpolation
$query = "SELECT * FROM MyTable WHERE MyColumn = '$userinput'"
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query # BAD
# Example using Invoke-Sqlcmd with string concatenation
$query = "SELECT * FROM MyTable WHERE " + $userinput
Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -Query $query # BAD
#Example using System.Data.SqlClient
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = "Server=MyServer;Database=MyDatabase;"
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = "SELECT * FROM MyTable WHERE MyColumn = '$userinput'" # BAD
$reader = $command.ExecuteReader()
$reader.Close()
$connection.Close()
# Example using System.Data.OleDb
$connection = New-Object System.Data.OleDb.OleDbConnection
$connection.ConnectionString = "Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=MyDatabase;"
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = "SELECT * FROM MyTable WHERE MyColumn = '$userinput'" # BAD
$reader = $command.ExecuteReader()
$reader.Close()
$connection.Close()
# Example using System.Data.SqlClient with parameters
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = "Server=MyServer;Database=MyDatabase;"
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = "SELECT * FROM MyTable WHERE MyColumn = @userinput"
$parameter = $command.Parameters.Add("@userinput", [System.Data.SqlDbType]::NVarChar)
$parameter.Value = $userinput # GOOD
$reader = $command.ExecuteReader()
$reader.Close()
$connection.Close()
# Example using System.Data.OleDb with parameters
$connection = New-Object System.Data.OleDb.OleDbConnection
$connection.ConnectionString = "Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=MyDatabase;"
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = "SELECT * FROM MyTable WHERE MyColumn = ?"
$parameter = $command.Parameters.Add("?", [System.Data.OleDb.OleDbType]::VarChar)
$parameter.Value = $userinput # GOOD
$reader = $command.ExecuteReader()
$reader.Close()
$connection.Close()