Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions sqli/dao/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,))
18 changes: 14 additions & 4 deletions sqli/dao/user.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -38,4 +39,13 @@
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,

Check failure

Code scanning / SonarCloud

Password hashing functions should use an unpredictable salt High

Make this salt unpredictable. See more on SonarQube Cloud
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')
24 changes: 21 additions & 3 deletions sqli/static/js/materialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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],
Expand Down