Skip to content

Commit 64975e5

Browse files
authored
Merge pull request github#5842 from japroc/cpp-pqxx-sqli-sink
C++: SqlPqxxTainted query searches for sql injections via pqxx connector to postgres
2 parents 6e9d744 + efa657d commit 64975e5

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-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 <code>esc</code> and <code>quote</code> pqxx library functions.</p>
16+
17+
</recommendation>
18+
<example>
19+
<sample src="SqlPqxxTainted.cpp" />
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: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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-via-pqxx
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.dataflow.TaintTracking
17+
import DataFlow::PathGraph
18+
19+
predicate pqxxTransationClassNames(string className, string namespace) {
20+
namespace = "pqxx" and
21+
className in [
22+
"dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction",
23+
"subtransaction", "transaction", "basic_transaction", "transaction_base", "work"
24+
]
25+
}
26+
27+
predicate pqxxConnectionClassNames(string className, string namespace) {
28+
namespace = "pqxx" and
29+
className in ["connection_base", "basic_connection", "connection"]
30+
}
31+
32+
predicate pqxxTransactionSqlArgument(string function, int arg) {
33+
function = "exec" and arg = 0
34+
or
35+
function = "exec0" and arg = 0
36+
or
37+
function = "exec1" and arg = 0
38+
or
39+
function = "exec_n" and arg = 1
40+
or
41+
function = "exec_params" and arg = 0
42+
or
43+
function = "exec_params0" and arg = 0
44+
or
45+
function = "exec_params1" and arg = 0
46+
or
47+
function = "exec_params_n" and arg = 1
48+
or
49+
function = "query_value" and arg = 0
50+
or
51+
function = "stream" and arg = 0
52+
}
53+
54+
predicate pqxxConnectionSqlArgument(string function, int arg) { function = "prepare" and arg = 1 }
55+
56+
Expr getPqxxSqlArgument() {
57+
exists(FunctionCall fc, Expr e, int argIndex, UserType t |
58+
// examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
59+
e = fc.getQualifier() and
60+
// to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
61+
// and return pointer to a connection/transation object
62+
e.getType().refersTo(t) and
63+
// transaction exec and connection prepare variations
64+
(
65+
pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) and
66+
pqxxTransactionSqlArgument(fc.getTarget().getName(), argIndex)
67+
or
68+
pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) and
69+
pqxxConnectionSqlArgument(fc.getTarget().getName(), argIndex)
70+
) and
71+
result = fc.getArgument(argIndex)
72+
)
73+
}
74+
75+
predicate pqxxEscapeArgument(string function, int arg) {
76+
arg = 0 and
77+
function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"]
78+
}
79+
80+
predicate isEscapedPqxxArgument(Expr argExpr) {
81+
exists(FunctionCall fc, Expr e, int argIndex, UserType t |
82+
// examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
83+
e = fc.getQualifier() and
84+
// to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
85+
// and return pointer to a connection/transation object
86+
e.getType().refersTo(t) and
87+
// transaction and connection escape functions
88+
(
89+
pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) or
90+
pqxxConnectionClassNames(t.getName(), t.getNamespace().getName())
91+
) and
92+
pqxxEscapeArgument(fc.getTarget().getName(), argIndex) and
93+
// is escaped arg == argExpr
94+
argExpr = fc.getArgument(argIndex)
95+
)
96+
}
97+
98+
class Configuration extends TaintTracking::Configuration {
99+
Configuration() { this = "SqlPqxxTainted" }
100+
101+
override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) }
102+
103+
override predicate isSink(DataFlow::Node sink) { sink.asExpr() = getPqxxSqlArgument() }
104+
105+
override predicate isSanitizer(DataFlow::Node node) { isEscapedPqxxArgument(node.asExpr()) }
106+
}
107+
108+
from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config, string taintCause
109+
where
110+
config.hasFlowPath(source, sink) and
111+
isUserInput(source.getNode().asExpr(), taintCause)
112+
select sink, source, sink, "This argument to a SQL query function is derived from $@", source,
113+
"user input (" + taintCause + ")"

0 commit comments

Comments
 (0)