diff --git a/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/populate.py b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/populate.py new file mode 100644 index 00000000000..026f72bdfe5 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/populate.py @@ -0,0 +1,16 @@ +from pymongo import MongoClient +client = MongoClient() + +db = client.test_database + +import datetime +post = { + "author": "Mike", + "text": "My first blog post!", + "tags": ["mongodb", "python", "pymongo"], + "date": datetime.datetime.now(tz=datetime.timezone.utc), +} + +posts = db.posts +post_id = posts.insert_one(post).inserted_id +post_id diff --git a/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/readme.md b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/readme.md new file mode 100644 index 00000000000..78e7e92e48f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/readme.md @@ -0,0 +1,19 @@ +Tutorials: +- [pymongo](https://pymongo.readthedocs.io/en/stable/tutorial.html) +- [install mongodb](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/#std-label-install-mdb-community-macos) + +I recommend creating a virtual environment with venv and then installing dependencies via +``` +python -m pip --install -r requirements.txt +``` + +Start mongodb: +``` +mongod --config /usr/local/etc/mongod.conf --fork +``` +run flask app: +``` +flask --app server run +``` + +Navigate to the root to see routes. For each route try to get the system to reveal the person in the database. If you know the name, you can just input it, but in some cases you can get to the person without knowing the name! diff --git a/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/requirements.txt b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/requirements.txt new file mode 100644 index 00000000000..a98715ed1e4 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/requirements.txt @@ -0,0 +1,2 @@ +flask +pymongo diff --git a/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/server.py b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/server.py new file mode 100644 index 00000000000..59b51963583 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/server.py @@ -0,0 +1,55 @@ +from flask import Flask, request +from pymongo import MongoClient +import json + +client = MongoClient() +db = client.test_database +posts = db.posts + +app = Flask(__name__) + + +def show_post(post, query): + if post: + return "You found " + post['author'] + "!" + else: + return "You did not find " + query + +@app.route('/plain', methods=['GET']) +def plain(): + author = request.args['author'] + post = posts.find_one({'author': author}) # $ result=OK + return show_post(post, author) + +@app.route('/dict', methods=['GET']) +def as_dict(): + author_string = request.args['author'] + author = json.loads(author_string) + # Use {"$ne": 1} as author + # Found by http://127.0.0.1:5000/dict?author={%22$ne%22:1} + post = posts.find_one({'author': author}) # $ result=BAD + return show_post(post, author) + +@app.route('/dictHardened', methods=['GET']) +def as_dict_hardened(): + author_string = request.args['author'] + author = json.loads(author_string) + post = posts.find_one({'author': {"$eq": author}}) # $ SPURIOUS: result=BAD + return show_post(post, author) + +@app.route('/byWhere', methods=['GET']) +def by_where(): + author = request.args['author'] + # Use `" | "a" === "a` as author + # making the query `this.author === "" | "a" === "a"` + # Found by http://127.0.0.1:5000/byWhere?author=%22%20|%20%22a%22%20===%20%22a + post = posts.find_one({'$where': 'this.author === "'+author+'"'}) # $ MISSING: result=BAD + return show_post(post, author) + +@app.route('/', methods=['GET']) +def show_routes(): + links = [] + for rule in app.url_map.iter_rules(): + if "GET" in rule.methods: + links.append((rule.rule, rule.endpoint)) + return links diff --git a/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/options b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/options new file mode 100644 index 00000000000..48ecb907736 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 -r PoC