mirror of
https://github.com/hohn/codeql-dataflow-sql-injection.git
synced 2025-12-15 17:53:04 +01:00
Insert updates from github.com:hohn/codeql.git
This commit is contained in:
committed by
=Michael Hohn
parent
9d130f1466
commit
dd664fe4ef
194
README.org
194
README.org
@@ -11,9 +11,9 @@
|
||||
./build.sh
|
||||
|
||||
# Prepare db
|
||||
./admin rm-db
|
||||
./admin create-db
|
||||
./admin show-db
|
||||
./admin -r
|
||||
./admin -c
|
||||
./admin -s
|
||||
|
||||
# Add regular user interactively
|
||||
./add-user 2>> users.log
|
||||
@@ -22,34 +22,202 @@
|
||||
|
||||
# Regular user via "external" process
|
||||
echo "User Outside" | ./add-user 2>> users.log
|
||||
./admin show-db
|
||||
|
||||
# Check
|
||||
./admin show-db
|
||||
./admin -s
|
||||
|
||||
# Add Johnny Droptable
|
||||
./add-user 2>> users.log
|
||||
Johnny'); DROP TABLE users; --
|
||||
|
||||
|
||||
# And the problem:
|
||||
./admin show-db
|
||||
./admin -s
|
||||
|
||||
# Check the log
|
||||
|
||||
tail users.log
|
||||
#+END_SRC
|
||||
|
||||
** Identify the problem
|
||||
=./add-user= is reading from =STDIN=, and writing to a database; looking at the code in
|
||||
[[./add-user.c]] leads to
|
||||
: count = read(STDIN_FILENO, buf, BUFSIZE - 1);
|
||||
for the read and
|
||||
: rc = sqlite3_exec(db, query, NULL, 0, &zErrMsg);
|
||||
for the write.
|
||||
|
||||
This problem is thus a dataflow problem; in codeql terminology we have
|
||||
- a /source/ at the =read(STDIN_FILENO, buf, BUFSIZE - 1);=
|
||||
- a /sink/ at the =sqlite3_exec(db, query, NULL, 0, &zErrMsg);=
|
||||
|
||||
We write codeql to identify these two, and then connect them via
|
||||
- a /dataflow configuration/ -- for this problem, the more general /taintflow
|
||||
configuration/.
|
||||
|
||||
** Build codeql database
|
||||
To get started, build the codeql database (adjust paths to your setup):
|
||||
#+BEGIN_SRC sh
|
||||
# Build the db with source commit id.
|
||||
export PATH=$HOME/local/vmsync/codeql224:"$PATH"
|
||||
SRCDIR=$HOME/local/codeql-dataflow-sql-injection/
|
||||
DB=$HOME/local/db/codeql-dataflow-sql-injection-$(cd $SRCDIR && git rev-parse --short HEAD)
|
||||
export PATH=$HOME/local/vmsync/codeql250:"$PATH"
|
||||
SRCDIR=$HOME/local/codeql-training-material.cpp-sqli/cpp/codeql-dataflow-sql-injection
|
||||
DB=$SRCDIR/cpp-sqli-$(cd $SRCDIR && git rev-parse --short HEAD)
|
||||
|
||||
echo $DB
|
||||
test -d "$DB" && rm -fR "$DB"
|
||||
mkdir -p "$DB"
|
||||
|
||||
cd $SRCDIR
|
||||
codeql database create --language=cpp -s $SRCDIR -j 8 -v $DB --command='./build.sh'
|
||||
cd $SRCDIR && codeql database create --language=cpp -s . -j 8 -v $DB --command='./build.sh'
|
||||
#+END_SRC
|
||||
|
||||
Then add this database directory to your VS Code =DATABASES= tab.
|
||||
|
||||
|
||||
** Build codeql database in steps
|
||||
For larger projects, using a single command to build everything is costly when
|
||||
any part of the build fails.
|
||||
|
||||
To build a database in steps, use the following sequence, adjusting paths to
|
||||
your setup:
|
||||
#+BEGIN_SRC sh
|
||||
# Build the db with source commit id.
|
||||
export PATH=$HOME/local/vmsync/codeql250:"$PATH"
|
||||
SRCDIR=$HOME/local/codeql-training-material.cpp-sqli/cpp/codeql-dataflow-sql-injection
|
||||
DB=$SRCDIR/cpp-sqli-$(cd $SRCDIR && git rev-parse --short HEAD)
|
||||
|
||||
# Check paths
|
||||
echo $DB
|
||||
echo $SRCDIR
|
||||
|
||||
# Prepare db directory
|
||||
test -d "$DB" && rm -fR "$DB"
|
||||
mkdir -p "$DB"
|
||||
|
||||
# Run the build
|
||||
cd $SRCDIR
|
||||
codeql database init --language=cpp -s . -v $DB
|
||||
# Repeat trace-command as needed to cover all targets
|
||||
codeql database trace-command -v $DB -- make
|
||||
codeql database finalize -j4 $DB
|
||||
#+END_SRC
|
||||
|
||||
Then add this database directory to your VS Code =DATABASES= tab.
|
||||
|
||||
** Develop the query bottom-up
|
||||
1. Identify the /source/ part of the
|
||||
: read(STDIN_FILENO, buf, BUFSIZE - 1);
|
||||
expression, the =buf= argument.
|
||||
Start from a =from..where..select=, then convert to a predicate.
|
||||
|
||||
2. Identify the /sink/ part of the
|
||||
: sqlite3_exec(db, query, NULL, 0, &zErrMsg);
|
||||
expression, the =query= argument. Again start from =from..where..select=,
|
||||
then convert to a predicate.
|
||||
|
||||
3. Fill in the /taintflow configuration/ boilerplate
|
||||
#+BEGIN_SRC java
|
||||
class CppSqli extends TaintTracking::Configuration {
|
||||
CppSqli() { this = "CppSqli" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) {
|
||||
none()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
Note that an inout-argument in C/C++ (the =buf= pointer is passed to =read=
|
||||
and points to updated data after the return) is accessed as a codeql source
|
||||
via
|
||||
: source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
instead of the usual
|
||||
: source.asExpr()
|
||||
|
||||
The final query (without =isAdditionalTaintStep=) is
|
||||
#+BEGIN_SRC java
|
||||
/**
|
||||
,* @name SQLI Vulnerability
|
||||
,* @description Using untrusted strings in a sql query allows sql injection attacks.
|
||||
,* @kind path-problem
|
||||
,* @id cpp/SQLIVulnerable
|
||||
,* @problem.severity warning
|
||||
,*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class SqliFlowConfig extends TaintTracking::Configuration {
|
||||
SqliFlowConfig() { this = "SqliFlow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
// count = read(STDIN_FILENO, buf, BUFSIZE);
|
||||
exists(FunctionCall read |
|
||||
read.getTarget().getName() = "read" and
|
||||
read.getArgument(1) = source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
// rc = sqlite3_exec(db, query, NULL, 0, &zErrMsg);
|
||||
exists(FunctionCall exec |
|
||||
exec.getTarget().getName() = "sqlite3_exec" and
|
||||
exec.getArgument(1) = sink.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from SqliFlowConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "Possible SQL injection"
|
||||
#+END_SRC
|
||||
|
||||
** Optional: sarif file review of the results
|
||||
Query results are available in several output formats using the cli. The
|
||||
following produces the sarif format, a json-based result description.
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
# The setup information from before
|
||||
export PATH=$HOME/local/vmsync/codeql250:"$PATH"
|
||||
SRCDIR=$HOME/local/codeql-training-material.cpp-sqli/cpp/codeql-dataflow-sql-injection
|
||||
DB=$SRCDIR/cpp-sqli-$(cd $SRCDIR && git rev-parse --short HEAD)
|
||||
|
||||
# Check paths
|
||||
echo $DB
|
||||
echo $SRCDIR
|
||||
|
||||
# To see the help
|
||||
codeql database analyze -h
|
||||
|
||||
# Run a query
|
||||
codeql database analyze \
|
||||
-v \
|
||||
--ram=14000 \
|
||||
-j12 \
|
||||
--rerun \
|
||||
--search-path ~/local/vmsync/ql \
|
||||
--format=sarif-latest \
|
||||
--output cpp-sqli.sarif \
|
||||
-- \
|
||||
$DB \
|
||||
$SRCDIR/SqlInjection.ql
|
||||
|
||||
# Examine the file in an editor
|
||||
edit cpp-sqli.sarif
|
||||
#+END_SRC
|
||||
|
||||
An example of using the sarif data is in the the jq script [[./sarif-summary.jq]].
|
||||
When run against the sarif input via
|
||||
#+BEGIN_SRC sh
|
||||
jq --raw-output --join-output -f sarif-summary.jq < cpp-sqli.sarif > cpp-sqli.txt
|
||||
#+END_SRC
|
||||
it produces output in a form close to that of compiler error messages:
|
||||
#+BEGIN_SRC text
|
||||
query-id: message line
|
||||
Path
|
||||
...
|
||||
Path
|
||||
...
|
||||
#+END_SRC
|
||||
|
||||
|
||||
@@ -42,14 +42,17 @@ void abort_on_exec_error(int rc, sqlite3 *db, char* zErrMsg) {
|
||||
char* get_user_info() {
|
||||
#define BUFSIZE 1024
|
||||
char* buf = (char*) malloc(BUFSIZE * sizeof(char));
|
||||
if(buf==NULL) abort();
|
||||
int count;
|
||||
// Disable buffering to avoid need for fflush
|
||||
// after printf().
|
||||
setbuf( stdout, NULL );
|
||||
printf("*** Welcome to sql injection ***\n");
|
||||
printf("Please enter name: ");
|
||||
count = read(STDIN_FILENO, buf, BUFSIZE);
|
||||
count = read(STDIN_FILENO, buf, BUFSIZE - 1);
|
||||
if (count <= 0) abort();
|
||||
// ensure the buffer is zero-terminated
|
||||
buf[count] = '\0';
|
||||
/* strip trailing whitespace */
|
||||
while (count && isspace(buf[count-1])) {
|
||||
buf[count-1] = 0; --count;
|
||||
@@ -90,6 +93,7 @@ int main(int argc, char* argv[]) {
|
||||
info = get_user_info();
|
||||
id = get_new_id();
|
||||
write_info(id, info);
|
||||
free(info);
|
||||
/*
|
||||
* show_info(id);
|
||||
*/
|
||||
|
||||
43
admin
43
admin
@@ -1,5 +1,24 @@
|
||||
#!/bin/bash
|
||||
rm-db () {
|
||||
|
||||
set -e
|
||||
|
||||
script=$(basename "$0")
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
MAGENTA='\033[0;95m'
|
||||
NC='\033[0m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[0;33m'
|
||||
|
||||
help() {
|
||||
echo -e "Usage: ./${script} [options]" \
|
||||
"\n${YELLOW}Options: ${NC}" \
|
||||
"\n\t -h ${GREEN}Show Help ${NC}" \
|
||||
"\n\t -c ${MAGENTA}Creates a users table ${NC}" \
|
||||
"\n\t -s ${MAGENTA}Shows all records in the users table ${NC}" \
|
||||
"\n\t -r ${RED}Removes users table ${NC}"
|
||||
}
|
||||
remove-db () {
|
||||
rm users.sqlite
|
||||
}
|
||||
|
||||
@@ -18,4 +37,24 @@ show-db () {
|
||||
' | sqlite3 users.sqlite
|
||||
}
|
||||
|
||||
eval $@
|
||||
if [ $# == 0 ]; then
|
||||
help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while getopts "h?csr" option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
h|\?)
|
||||
help
|
||||
exit 0
|
||||
;;
|
||||
c) create-db
|
||||
;;
|
||||
s) show-db
|
||||
;;
|
||||
r) remove-db
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"name": "[codeql-dataflow-sql-injection-d5b28fb source archive]",
|
||||
"uri": "codeql-zip-archive://0-66/Users/hohn/local/db/codeql-dataflow-sql-injection-d5b28fb/src.zip/"
|
||||
"path": "../../../vmsync/ql"
|
||||
},
|
||||
{
|
||||
"name": "[cpp-sqli-89900b3 source archive]",
|
||||
"uri": "codeql-zip-archive://0-110/Users/hohn/local/codeql-training-material.cpp-sqli/cpp/codeql-dataflow-sql-injection/cpp-sqli-89900b3/src.zip"
|
||||
}
|
||||
]
|
||||
],
|
||||
"settings": {
|
||||
"codeQL.runningQueries.autoSave": true
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ If you get stuck, try searching our documentation and blog posts for help and id
|
||||
- [Using the CodeQL extension for VS Code](https://help.semmle.com/codeql/codeql-for-vscode.html)
|
||||
|
||||
## Codeql Recap
|
||||
This is a brief review of codeql taken from the [full
|
||||
This is a brief review of CodeQL taken from the [full
|
||||
introduction](https://git.io/JJqdS). For more details, see the [documentation
|
||||
links](#documentation-links). We will revisit all of this during the tutorial.
|
||||
|
||||
@@ -89,7 +89,7 @@ select /* ... expressions ... */
|
||||
|
||||
The `from` clause specifies some variables that will be used in the query. The
|
||||
`where` clause specifies some conditions on those variables in the form of logical
|
||||
formulas. The `select` clauses speciifes what the results should be, and can refer
|
||||
formulas. The `select` clauses specifies what the results should be, and can refer
|
||||
to variables defined in the `from` clause.
|
||||
|
||||
The `from` clause is defined as a series of variable declarations, where each
|
||||
@@ -206,9 +206,9 @@ This program can be compiled and linked, and a simple sqlite db created via
|
||||
./build.sh
|
||||
|
||||
# Prepare db
|
||||
./admin rm-db
|
||||
./admin create-db
|
||||
./admin show-db
|
||||
./admin -r
|
||||
./admin -c
|
||||
./admin -s
|
||||
```
|
||||
|
||||
Users can be added via `stdin` in several ways; the second is a pretend "server"
|
||||
@@ -226,14 +226,14 @@ echo "User Outside" | ./add-user 2>> users.log
|
||||
Check the db and log:
|
||||
```
|
||||
# Check
|
||||
./admin show-db
|
||||
./admin -s
|
||||
|
||||
tail -4 users.log
|
||||
```
|
||||
|
||||
Looks ok:
|
||||
```
|
||||
0:$ ./admin show-db
|
||||
0:$ ./admin -s
|
||||
87797|First User
|
||||
87808|User Outside
|
||||
|
||||
@@ -252,8 +252,8 @@ Johnny'); DROP TABLE users; --
|
||||
And then we have this:
|
||||
```sh
|
||||
# And the problem:
|
||||
./admin show-db
|
||||
0:$ ./admin show-db
|
||||
./admin -s
|
||||
0:$ ./admin -s
|
||||
Error: near line 2: no such table: users
|
||||
```
|
||||
|
||||
@@ -580,7 +580,7 @@ the process of building and exploring the data flow path.
|
||||
One such feature is adding additional taint steps. This is useful if you use
|
||||
libraries which are not modelled by the default taint tracking. You can implement
|
||||
this by overriding `isAdditionalTaintStep` predicate. This has two parameters, the
|
||||
`from` and the `to` node, and essentially allows you to add extra edges into the
|
||||
`from` and the `to` node, and it essentially allows you to add extra edges into the
|
||||
taint tracking or data flow graph.
|
||||
|
||||
A starting configuration can look like the following, with details to be filled
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
name: cpp_sql_injection
|
||||
name: cpp-sql-injection
|
||||
version: 0.0.0
|
||||
libraryPathDependencies: codeql-cpp
|
||||
|
||||
Reference in New Issue
Block a user