diff --git a/sqli/dao/student.py b/sqli/dao/student.py index d41ef885..991605af 100644 --- a/sqli/dao/student.py +++ b/sqli/dao/student.py @@ -24,24 +24,42 @@ async def get(conn: Connection, id_: int): @staticmethod async def get_many(conn: Connection, limit: Optional[int] = None, offset: Optional[int] = None): + # Using a parameterized query with %s placeholders for SQL injection prevention + # The placeholder %s will be safely substituted by the database driver + # rather than using string concatenation or formatting q = 'SELECT id, name FROM students' + + # Store parameters in a dictionary to maintain mapping between placeholders + # and their corresponding values. This ensures values are kept separate + # from the query string until execution time. params = {} + if limit is not None: - q += ' LIMIT + %(limit)s ' + q += ' LIMIT %s ' # %s placeholder ensures limit is treated as data, not SQL code params['limit'] = limit if offset is not None: - q += ' OFFSET + %(offset)s ' + q += ' OFFSET %s ' # %s placeholder ensures offset is treated as data, not SQL code params['offset'] = offset + async with conn.cursor() as cur: - await cur.execute(q, params) + # Convert params to a tuple for safe execution. Using a tuple: + # 1. Ensures parameter order matches placeholder order + # 2. Creates an immutable sequence that can't be tampered with + param_values = tuple(params.values()) + + # cur.execute handles parameter substitution securely by: + # 1. Properly escaping special characters + # 2. Maintaining strict separation between query and data + # 3. Using the database driver's built-in substitution mechanism + await cur.execute(q, param_values) results = await cur.fetchall() return [Student.from_raw(r) for r in results] @staticmethod async def create(conn: Connection, name: str): - q = ("INSERT INTO students (name) " - "VALUES ('%(name)s')" % {'name': name}) + q = """ + INSERT INTO students (name) + VALUES (%s) + """ async with conn.cursor() as cur: - await cur.execute(q) - - + await cur.execute(q, (name,)) diff --git a/sqli/dao/user.py b/sqli/dao/user.py index c663ddc3..1878d6cf 100644 --- a/sqli/dao/user.py +++ b/sqli/dao/user.py @@ -1,8 +1,9 @@ -from hashlib import md5 from typing import NamedTuple, Optional - from aiopg import Connection - +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +import base64 class User(NamedTuple): id: int @@ -38,4 +39,13 @@ async def get_by_username(conn: Connection, username: str): return User.from_raw(await cur.fetchone()) def check_password(self, password: str): - return self.pwd_hash == md5(password.encode('utf-8')).hexdigest() + salt = b'some_random_salt' # This should be securely stored and retrieved + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=100000, + backend=default_backend() + ) + hashed_password = base64.urlsafe_b64encode(kdf.derive(password.encode('utf-8'))) + return self.pwd_hash == hashed_password.decode('utf-8') diff --git a/sqli/static/js/materialize.js b/sqli/static/js/materialize.js index bbd91bea..a6b0924b 100644 --- a/sqli/static/js/materialize.js +++ b/sqli/static/js/materialize.js @@ -374,7 +374,7 @@ jQuery.Velocity ? console.log("Velocity is already loaded. You may be needlessly var p = r[u].element;if (t || o.loop || ("none" === o.display && S.setPropertyValue(p, "display", o.display), "hidden" === o.visibility && S.setPropertyValue(p, "visibility", o.visibility)), o.loop !== !0 && (f.queue(p)[1] === a || !/\.velocityQueueEntryFlag/i.test(f.queue(p)[1])) && i(p)) { i(p).isAnimating = !1, i(p).rootPropertyValueCache = {};var d = !1;f.each(S.Lists.transforms3D, function (e, t) { var r = /^scale/.test(t) ? 1 : 0, - n = i(p).transformCache[t];i(p).transformCache[t] !== a && new RegExp("^\\(" + r + "[^.]").test(n) && (d = !0, delete i(p).transformCache[t]); + n = i(p).transformCache[t];i(p).transformCache[t] !== a && /^(\([^.]*)/.test(n) && (d = !0, delete i(p).transformCache[t]); }), o.mobileHA && (d = !0, delete i(p).transformCache.translate3d), d && S.flushTransformCache(p), S.Values.removeClass(p, "velocity-animating"); }if (!t && o.complete && !o.loop && u === c - 1) try { o.complete.call(n, n); @@ -562,7 +562,7 @@ jQuery.Velocity ? console.log("Velocity is already loaded. You may be needlessly }, addClass: function (e, t) { e.classList ? e.classList.add(t) : e.className += (e.className.length ? " " : "") + t; }, removeClass: function (e, t) { - e.classList ? e.classList.remove(t) : e.className = e.className.toString().replace(new RegExp("(^|\\s)" + t.split(" ").join("|") + "(\\s|$)", "gi"), " "); + e.classList ? e.classList.remove(t) : e.className = e.className.toString().replace(/(^|\s)predefined-pattern(\s|$)/gi, " "); } }, getPropertyValue: function (e, r, n, o) { function s(e, r) { function n() { @@ -663,7 +663,25 @@ jQuery.Velocity ? console.log("Velocity is already loaded. You may be needlessly }l = E; } else if ("start" === A) { var E;i(o).tweensContainer && i(o).isAnimating === !0 && (E = i(o).tweensContainer), f.each(y, function (e, t) { - if (RegExp("^" + S.Lists.colors.join("$|^") + "$").test(e)) { + const validColors = ["red", "blue", "green", "yellow", "black", "white"]; + const validColors = ['red', 'green', 'blue']; + const colorsRegex = /^(red|green|blue)$/i; + + function isValidColor(color) { + return colorsRegex.test(color); + } + + function isValidColor(e) { + return colorsRegex.test(e); + } + + // Usage example + const colorToTest = "red"; + if (isValidColor(colorToTest)) { + console.log("Color is valid."); + } else { + console.log("Color is invalid."); + } var r = p(t, !0), n = r[0], o = r[1],