Skip to content

Commit 955d97f

Browse files
committed
C++: Init SqlPqxxTainted.ql
1 parent 69cd9df commit 955d97f

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <iostream>
2+
#include <stdexcept>
3+
#include <pqxx/pqxx>
4+
5+
int main(int argc, char ** argv) {
6+
7+
if (argc != 2) {
8+
throw std::runtime_error("Give me a string!");
9+
}
10+
11+
pqxx::connection c;
12+
pqxx::work w(c);
13+
14+
// BAD
15+
char *userName = argv[1];
16+
char query1[1000] = {0};
17+
sprintf(query1, "SELECT UID FROM USERS where name = \"%s\"", userName);
18+
pqxx::row r = w.exec1(query1);
19+
w.commit();
20+
std::cout << r[0].as<int>() << std::endl;
21+
22+
// GOOD
23+
pqxx::result r2 = w.exec("SELECT " + w.quote(argv[1]));
24+
w.commit();
25+
std::cout << r2[0][0].c_str() << std::endl;
26+
27+
return 0;
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The code passes user input as part of a SQL query without escaping special elements.
7+
It generates a SQL query to Postgres using <code>sprintf</code>,
8+
with the user-supplied data directly passed as an argument
9+
to <code>sprintf</code>. This leaves the code vulnerable to attack by SQL Injection.</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>Use a library routine to escape characters in the user-supplied
15+
string before converting it to SQL. Use esc and quote pqxx library functions.</p>
16+
17+
</recommendation>
18+
<example>
19+
<sample src="SqlPqxxTainted.c" />
20+
21+
</example>
22+
<references>
23+
24+
<li>MSDN Library: <a href="https://docs.microsoft.com/en-us/sql/relational-databases/security/sql-injection">SQL Injection</a>.</li>
25+
26+
27+
<!-- LocalWords: SQL CWE
28+
-->
29+
30+
</references>
31+
</qhelp>
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/**
2+
* @name Uncontrolled data in SQL query to Postgres
3+
* @description Including user-supplied data in a SQL query to Postgres
4+
* without neutralizing special elements can make code
5+
* vulnerable to SQL Injection.
6+
* @kind path-problem
7+
* @problem.severity error
8+
* @precision high
9+
* @id cpp/sql-injection
10+
* @tags security
11+
* external/cwe/cwe-089
12+
*/
13+
14+
import cpp
15+
import semmle.code.cpp.security.Security
16+
// import semmle.code.cpp.security.FunctionWithWrappers
17+
// import semmle.code.cpp.security.TaintTracking
18+
// import TaintedWithPath
19+
import semmle.code.cpp.dataflow.TaintTracking
20+
import DataFlow::PathGraph
21+
22+
predicate pqxxTransationClassNames(string class_name, string namespace) {
23+
class_name = "dbtransaction" and namespace = "pqxx"
24+
or
25+
class_name = "nontransaction" and namespace = "pqxx"
26+
or
27+
class_name = "basic_robusttransaction" and namespace = "pqxx"
28+
or
29+
class_name = "robusttransaction" and namespace = "pqxx"
30+
or
31+
class_name = "subtransaction" and namespace = "pqxx"
32+
or
33+
class_name = "transaction" and namespace = "pqxx"
34+
or
35+
class_name = "basic_transaction" and namespace = "pqxx"
36+
or
37+
class_name = "transaction_base" and namespace = "pqxx"
38+
or
39+
class_name = "work" and namespace = "pqxx"
40+
}
41+
42+
predicate pqxxConnectionClassNames(string class_name, string namespace) {
43+
class_name = "connection_base" and namespace = "pqxx"
44+
or
45+
class_name = "basic_connection" and namespace = "pqxx"
46+
or
47+
class_name = "connection" and namespace = "pqxx"
48+
}
49+
50+
predicate pqxxTransactionSqlArgument(string function, int arg) {
51+
function = "exec" and arg = 0
52+
or
53+
function = "exec0" and arg = 0
54+
or
55+
function = "exec1" and arg = 0
56+
or
57+
function = "exec_n" and arg = 1
58+
or
59+
function = "exec_params" and arg = 0
60+
or
61+
function = "exec_params0" and arg = 0
62+
or
63+
function = "exec_params1" and arg = 0
64+
or
65+
function = "exec_params_n" and arg = 1
66+
or
67+
function = "query_value" and arg = 0
68+
or
69+
function = "stream" and arg = 0
70+
}
71+
72+
predicate pqxxConnectionSqlArgument(string function, int arg) {
73+
function = "prepare" and arg = 1
74+
}
75+
76+
Expr getPqxxSqlArgument() {
77+
exists(FunctionCall fc, Expr e, int argIndex, Type t |
78+
// examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
79+
e = fc.getQualifier() and
80+
// to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
81+
// and return pointer to a connection/transation object
82+
e.getType().refersTo(t) and
83+
// transation exec and connection prepare variations
84+
(
85+
pqxxTransationClassNames(t.getName(), _) and pqxxTransactionSqlArgument(fc.getTarget().getName(), argIndex)
86+
or
87+
pqxxConnectionClassNames(t.getName(), _) and pqxxConnectionSqlArgument(fc.getTarget().getName(), argIndex)
88+
)
89+
and
90+
result = fc.getArgument(argIndex)
91+
)
92+
}
93+
94+
predicate pqxxEscapeArgument(string function, int arg) {
95+
arg = 0 and
96+
(
97+
function = "esc"
98+
or
99+
function = "esc_raw"
100+
or
101+
function = "quote"
102+
or
103+
function = "quote_raw"
104+
or
105+
function = "quote_name"
106+
or
107+
function = "quote_table"
108+
or
109+
function = "esc_like"
110+
)
111+
}
112+
113+
predicate isEscapedPqxxArgument(Expr argExpr) {
114+
exists(FunctionCall fc, Expr e, int argIndex, Type t |
115+
// examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
116+
e = fc.getQualifier() and
117+
// to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
118+
// and return pointer to a connection/transation object
119+
e.getType().refersTo(t) and
120+
// transation and connection escape functions
121+
(pqxxTransationClassNames(t.getName(), _) or pqxxConnectionClassNames(t.getName(), _))
122+
and
123+
pqxxEscapeArgument(fc.getTarget().getName(), argIndex)
124+
and
125+
// eval is escaped
126+
argExpr = fc.getArgument(argIndex)
127+
)
128+
}
129+
130+
// class Configuration extends TaintTrackingConfiguration {
131+
// override predicate isSink(Element tainted) {
132+
// tainted = getPqxxSqlArgument()
133+
// }
134+
// override predicate isBarrier(Expr e) {
135+
// super.isBarrier(e) or e.getUnspecifiedType() instanceof IntegralType
136+
// }
137+
// }
138+
// from
139+
// Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode, string taintCause
140+
// where
141+
// taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
142+
// isUserInput(taintSource, taintCause)
143+
// select taintedArg, sourceNode, sinkNode,
144+
// "This argument to a SQL query function is derived from $@", taintSource, "user input (" + taintCause + ")"
145+
146+
147+
class Configuration extends TaintTracking::Configuration {
148+
Configuration() { this = "SqlPqxxTainted" }
149+
150+
override predicate isSource(DataFlow::Node source) {
151+
isUserInput(source.asExpr(), _)
152+
}
153+
154+
override predicate isSink(DataFlow::Node sink) {
155+
sink.asExpr() = getPqxxSqlArgument()
156+
}
157+
158+
override predicate isSanitizer(DataFlow::Node node) {
159+
isEscapedPqxxArgument(node.asExpr())
160+
}
161+
}
162+
163+
from
164+
DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config, string taintCause
165+
where
166+
config.hasFlowPath(source, sink) and
167+
isUserInput(source.getNode().asExpr(), taintCause)
168+
select
169+
sink, source, sink,
170+
"This argument to a SQL query function is derived from $@", source, "user input (" + taintCause + ")"

0 commit comments

Comments
 (0)