Skip to content

Commit d7a694b

Browse files
authored
Add code with more vulns (#3)
* Add call for sql sensitive data exposure * Add HTTP header misconfiguration * Tweak name of identifiers holding session info * Add code for XSS vuln * Add code for Open Redirect * Reformat code with black * Fix pylint errors
1 parent aa94f2c commit d7a694b

File tree

10 files changed

+176
-81
lines changed

10 files changed

+176
-81
lines changed

flask_webgoat/__init__.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
from flask import Flask, g
66

7-
DB_FILENAME = 'database.db'
7+
DB_FILENAME = "database.db"
88

99

10-
def query_db(query, args = (), one=False, commit=False):
10+
def query_db(query, args=(), one=False, commit=False):
1111
with sqlite3.connect(DB_FILENAME) as conn:
12+
conn.set_trace_callback(print)
1213
cur = conn.cursor().execute(query, args)
1314
if commit:
1415
conn.commit()
@@ -17,7 +18,7 @@ def query_db(query, args = (), one=False, commit=False):
1718

1819
def create_app():
1920
app = Flask(__name__)
20-
app.secret_key = 'aeZ1iwoh2ree2mo0Eereireong4baitixaixu5Ee'
21+
app.secret_key = "aeZ1iwoh2ree2mo0Eereireong4baitixaixu5Ee"
2122

2223
db_path = Path(DB_FILENAME)
2324
if db_path.exists():
@@ -34,15 +35,16 @@ def create_app():
3435
conn.commit()
3536
conn.close()
3637

37-
3838
with app.app_context():
39-
from . import status
39+
from . import actions
4040
from . import auth
41+
from . import status
42+
from . import ui
4143
from . import users
42-
from . import actions
43-
app.register_blueprint(status.bp)
44+
45+
app.register_blueprint(actions.bp)
4446
app.register_blueprint(auth.bp)
47+
app.register_blueprint(status.bp)
48+
app.register_blueprint(ui.bp)
4549
app.register_blueprint(users.bp)
46-
app.register_blueprint(actions.bp)
4750
return app
48-

flask_webgoat/actions.py

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,49 @@
11
from pathlib import Path
22
import subprocess
3-
import uuid
4-
from flask import request
53

6-
from flask import (
7-
Blueprint, jsonify, request, jsonify, session
8-
)
9-
from werkzeug.security import check_password_hash
10-
from flask import current_app as app
4+
from flask import Blueprint, request, jsonify, session
115

12-
bp = Blueprint('actions', __name__)
6+
bp = Blueprint("actions", __name__)
137

148

15-
@bp.route('/message', methods=['POST'])
9+
@bp.route("/message", methods=["POST"])
1610
def log_entry():
17-
auth = session.get('user_info', None)
18-
if auth is None:
19-
return jsonify({'error': 'no auth found in session'})
20-
access_level = auth[2]
11+
user_info = session.get("user_info", None)
12+
if user_info is None:
13+
return jsonify({"error": "no user_info found in session"})
14+
access_level = user_info[2]
2115
if access_level > 2:
22-
return jsonify({'error': 'access level < 2 is required for this action'})
23-
filename_param = request.form.get('filename')
16+
return jsonify({"error": "access level < 2 is required for this action"})
17+
filename_param = request.form.get("filename")
2418
if filename_param is None:
25-
return jsonify({'error': 'filename parameter is required'})
26-
text_param = request.form.get('text')
19+
return jsonify({"error": "filename parameter is required"})
20+
text_param = request.form.get("text")
2721
if text_param is None:
28-
return jsonify({'error': 'text parameter is required'})
22+
return jsonify({"error": "text parameter is required"})
2923

30-
user_id = auth[0]
24+
user_id = user_info[0]
3125
user_dir = "data/" + str(user_id)
3226
user_dir_path = Path(user_dir)
3327
if not user_dir_path.exists():
3428
user_dir_path.mkdir()
3529

3630
filename = filename_param + ".txt"
3731
path = Path(user_dir + "/" + filename)
38-
with path.open("w", encoding ="utf-8") as f:
39-
f.write(text_param)
40-
return jsonify({'success': True})
32+
with path.open("w", encoding="utf-8") as open_file:
33+
open_file.write(text_param)
34+
return jsonify({"success": True})
4135

4236

43-
@bp.route('/grep_processes')
37+
@bp.route("/grep_processes")
4438
def grep_processes():
45-
name = request.args.get('name')
46-
res = subprocess.run(["ps aux | grep " + name + " | awk '{print $11}'"], shell=True, capture_output=True)
39+
name = request.args.get("name")
40+
res = subprocess.run(
41+
["ps aux | grep " + name + " | awk '{print $11}'"],
42+
shell=True,
43+
capture_output=True,
44+
)
4745
if res.stdout is None:
48-
return jsonify({'error': 'no stdout returned'})
46+
return jsonify({"error": "no stdout returned"})
4947
out = res.stdout.decode("utf-8")
50-
names = out.split('\n')
51-
return jsonify({'success': True, 'names': names})
52-
48+
names = out.split("\n")
49+
return jsonify({"success": True, "names": names})

flask_webgoat/auth.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
1-
from flask import (
2-
Blueprint, jsonify, request, jsonify, session
3-
)
4-
from werkzeug.security import check_password_hash
5-
from flask import current_app as app
1+
from flask import Blueprint, request, jsonify, session, redirect
62
from . import query_db
73

8-
bp = Blueprint('auth', __name__)
4+
bp = Blueprint("auth", __name__)
95

106

11-
@bp.route('/login', methods=['POST'])
7+
@bp.route("/login", methods=["POST"])
128
def login():
13-
username = request.form.get('username')
14-
password = request.form.get('password')
9+
username = request.form.get("username")
10+
password = request.form.get("password")
1511
if username is None or password is None:
16-
return jsonify({'error': 'username and password parameter have to be provided'}), 400
12+
return (
13+
jsonify({"error": "username and password parameter have to be provided"}),
14+
400,
15+
)
1716

18-
query = "SELECT id, username, access_level FROM user WHERE username = '%s' AND password = '%s'" % (username, password)
17+
query = (
18+
"SELECT id, username, access_level FROM user WHERE username = '%s' AND password = '%s'"
19+
% (username, password)
20+
)
1921
result = query_db(query, [], True)
2022
if result is None:
21-
return jsonify({'bad_login': True}), 400
22-
session['user_info'] = (result[0], result[1], result[2])
23-
return jsonify({'success': True})
23+
return jsonify({"bad_login": True}), 400
24+
session["user_info"] = (result[0], result[1], result[2])
25+
return jsonify({"success": True})
2426

27+
28+
@bp.route("/login_and_redirect")
29+
def login_and_redirect():
30+
username = request.args.get("username")
31+
password = request.args.get("password")
32+
url = request.args.get("url")
33+
if username is None or password is None or url is None:
34+
return (
35+
jsonify(
36+
{"error": "username, password, and url parameters have to be provided"}
37+
),
38+
400,
39+
)
40+
41+
query = "SELECT id, username, access_level FROM user WHERE username = ? AND password = ?"
42+
result = query_db(query, (username, password), True)
43+
if result is None:
44+
return redirect(url)
45+
session["user_info"] = (result[0], result[1], result[2])
46+
return jsonify({"success": True})

flask_webgoat/status.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
from flask import (
2-
Blueprint, jsonify
3-
)
1+
from flask import Blueprint, jsonify
42

5-
bp = Blueprint('status', __name__)
3+
bp = Blueprint("status", __name__)
64

7-
@bp.route('/status')
5+
6+
@bp.route("/status")
87
def status():
9-
return jsonify({'success': True})
8+
return jsonify({"success": True})
109

1110

12-
@bp.route('/ping')
11+
@bp.route("/ping")
1312
def ping():
14-
return jsonify({'success': True})
13+
return jsonify({"success": True})

flask_webgoat/templates/base.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<title>flask_webgoat</title>
3+
<nav>
4+
<h1>Flask Webgoat</h1>
5+
</nav>
6+
<section class="content">
7+
{% block content %}{% endblock %}
8+
</section>
9+

flask_webgoat/templates/error.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h1>{% block title %}Register{% endblock %}</h1>
5+
{% endblock %}
6+
7+
{% block content %}
8+
<div class="error">
9+
{{ message }}
10+
</div>
11+
{% endblock %}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h1>{% block title %}Register{% endblock %}</h1>
5+
{% endblock %}
6+
7+
{% block content %}
8+
<div class="results">
9+
Found {{ num_results }} results for query {{ query }}.
10+
{% for result in results %}
11+
<div class="result">{{ result }}</div>
12+
{% endfor %}
13+
</div>
14+
{% endblock %}

flask_webgoat/ui.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import sqlite3
2+
3+
from flask import Blueprint, request, render_template
4+
from . import query_db
5+
6+
bp = Blueprint("ui", __name__)
7+
8+
9+
@bp.route("/search")
10+
def search():
11+
query_param = request.args.get("query")
12+
if query_param is None:
13+
message = "please provide the query parameter"
14+
return render_template("error.html", message=message)
15+
16+
try:
17+
query = "SELECT username, access_level FROM user WHERE username LIKE ?;"
18+
results = query_db(query, (query_param,))
19+
return render_template(
20+
"search.html", results=results, num_results=len(results), query=query_param
21+
)
22+
except sqlite3.Error as err:
23+
message = "Error while executing query " + query_param + ": " + err
24+
return render_template("error.html", message=message)

flask_webgoat/users.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
1-
from flask import (
2-
Blueprint, jsonify, request, jsonify, session
3-
)
4-
from werkzeug.security import check_password_hash
5-
from flask import current_app as app
1+
import sqlite3
2+
3+
from flask import Blueprint, jsonify, session, request
4+
65
from . import query_db
76

8-
bp = Blueprint('users', __name__)
7+
bp = Blueprint("users", __name__)
98

109

11-
@bp.route('/create_user', methods=['POST'])
10+
@bp.route("/create_user", methods=["POST"])
1211
def create_user():
13-
auth = session.get('user_info', None)
14-
if auth is None:
15-
return jsonify({'error': 'no auth found in session'})
12+
user_info = session.get("user_info", None)
13+
if user_info is None:
14+
return jsonify({"error": "no user_info found in session"})
1615

17-
access_level = auth[2]
16+
access_level = user_info[2]
1817
if access_level != 0:
19-
return jsonify({'error': 'access level of 0 is required for this action'})
20-
username = request.form.get('username')
21-
password = request.form.get('password')
22-
access_level = request.form.get('access_level')
18+
return jsonify({"error": "access level of 0 is required for this action"})
19+
username = request.form.get("username")
20+
password = request.form.get("password")
21+
access_level = request.form.get("access_level")
2322
if username is None or password is None or access_level is None:
24-
return jsonify({'error': 'username, password and access_level parameters have to be provided'}), 400
23+
return (
24+
jsonify(
25+
{
26+
"error": "username, password and access_level parameters have to be provided"
27+
}
28+
),
29+
400,
30+
)
2531
if len(password) < 3:
26-
return jsonify({'error': 'the password needs to be at least 3 characters long'}), 402
32+
return (
33+
jsonify({"error": "the password needs to be at least 3 characters long"}),
34+
402,
35+
)
2736

28-
query = "INSERT INTO user (username, password, access_level) VALUES ('%s', '%s', %d)" % (username, password, int(access_level))
37+
query = (
38+
"INSERT INTO user (username, password, access_level) VALUES ('%s', '%s', %d)"
39+
% (username, password, int(access_level))
40+
)
2941

3042
try:
3143
query_db(query, [], False, True)
32-
return jsonify({'success': True})
44+
return jsonify({"success": True})
3345
except sqlite3.Error as err:
34-
return jsonify({'error': 'could not create user:' + err})
35-
46+
return jsonify({"error": "could not create user:" + err})

run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@
22

33
app = create_app()
44

5+
@app.after_request
6+
def add_csp_headers(response):
7+
response.headers['Access-Control-Allow-Origin'] = '*'
8+
response.headers['Content-Security-Policy'] = "script-src 'self' 'unsafe-inline'"
9+
return response
10+
511
if __name__ == '__main__':
612
app.run()

0 commit comments

Comments
 (0)