9 Commits
v0.1 ... v1.0

Author SHA1 Message Date
7fce3f57e9 Remove secrets from requirements.txt
On macOS, a dependency of secrets fails to install using pip. After
testing, it looks like the secrets module is not required.
2020-08-31 23:46:28 -05:00
a587040809 Bump cryptography from 2.8 to 3.1 (#2) 2020-09-01 04:41:50 +00:00
8a95dbb0fa Remove trailing whitespace from lines 2020-08-31 23:09:39 -05:00
a5a22b7c88 Remove UPLOADKEYS_CHMOD option due to keygen.py
Since keygen.py is run as root, uploadkeys is owned by root. This causes
issues when imgupload.py tries to chmod the uploadkeys file since it
doesn't have permissions to chmod it.
Solution: remove UPLOADKEYS_CHMOD option
2020-08-31 21:14:09 -05:00
7ccaafc6c6 Bugfixes in keygen.py
- Handle if uploadkeys becomes corrupted
- Disambiguate variable names
- Handle case where the uploadkeys file doesn't already exist
2020-08-31 20:32:00 -05:00
797bebb1a1 Fix ENCKEY_PATH check in configtest.py 2020-08-31 20:29:07 -05:00
08f9e13da0 Merge pull request #1 from BBaoVanC/dev
added encryption to uploadkeys and added a key generator
2020-08-31 17:13:24 -07:00
3d1304b3b0 added encryption to uploadkeys and added a key generator 2020-08-31 17:12:39 -07:00
4b624f3fed Ignore settings.py and add settings.py.default 2020-08-31 18:48:22 -05:00
7 changed files with 149 additions and 21 deletions

2
.gitignore vendored
View File

@ -132,3 +132,5 @@ dmypy.json
uploadkeys uploadkeys
savelog.log savelog.log
uwsgi.log uwsgi.log
settings.py
secret.key

View File

@ -1,4 +1,9 @@
# imgupload # imgupload
Python Flask uWSGI application to receive and save images over POST requests. ![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/BBaoVanC/imgupload/master?color=purple) ![GitHub repo size](https://img.shields.io/github/repo-size/bbaovanc/imgupload?color=purple) ![GitHub All Releases](https://img.shields.io/github/downloads/bbaovanc/imgupload/total?color=purple) ![GitHub issues](https://img.shields.io/github/issues/bbaovanc/imgupload?color=purple) ![GitHub closed issues](https://img.shields.io/github/issues-closed/bbaovanc/imgupload?color=purple) ![GitHub](https://img.shields.io/github/license/bbaovanc/imgupload?color=purple)
This project is still in development. Use at your own risk! ### What is imgupload?
imgupload is a Flask + uWSGI application to serve as an all-purpose image/file uploader over POST requests.
### Usage
Make sure you install the dependencies first. To do this, run `sudo python3 -m pip install -r requirements.txt`.
To deploy imgupload, run `flask run`.

View File

@ -9,8 +9,8 @@ defaults = {
"ROOTURL": "https://img.bbaovanc.com/", "ROOTURL": "https://img.bbaovanc.com/",
"SAVELOG": "savelog.log", "SAVELOG": "savelog.log",
"SAVELOG_CHMOD": "0o644", "SAVELOG_CHMOD": "0o644",
"UPLOADKEYS_CHMOD": "0o400",
"SAVELOG_KEYPREFIX": 4, "SAVELOG_KEYPREFIX": 4,
"ENCKEY_PATH": "secret.key"
} }
deftypes = { deftypes = {
@ -19,8 +19,8 @@ deftypes = {
"ROOTURL": str, "ROOTURL": str,
"SAVELOG": str, "SAVELOG": str,
"SAVELOG_CHMOD": int, "SAVELOG_CHMOD": int,
"UPLOADKEYS_CHMOD": int,
"SAVELOG_KEYPREFIX": int, "SAVELOG_KEYPREFIX": int,
"ENCKEY_PATH": str,
} }
@ -94,6 +94,16 @@ if "ROOTURL" in checksettings:
print("[" + u"\u2713" + "] ROOTURL is good!") print("[" + u"\u2713" + "] ROOTURL is good!")
# Check if ENCKEY_PATH exists
enckey_exists = True
if "ENCKEY_PATH" in checksettings:
if not os.path.isfile(settings.ENCKEY_PATH):
enckey_exists = False
print("[!] The path set in ENCKEY_PATH ('{0}') doesn't exist!".format(settings.ENCKEY_PATH))
else:
print("[" + u"\u2713" + "] ENCKEY_PATH exists!")
# Ask the user if SAVELOG is the intended filename # Ask the user if SAVELOG is the intended filename
if "SAVELOG" in checksettings: if "SAVELOG" in checksettings:
print("[*] SAVELOG was interpreted to be {0}".format(settings.SAVELOG)) print("[*] SAVELOG was interpreted to be {0}".format(settings.SAVELOG))
@ -126,6 +136,10 @@ if not uploadfolder_exists:
summarygood = False summarygood = False
print("UPLOAD_FOLDER ({0}) does not exist!".format(settings.UPLOAD_FOLDER)) print("UPLOAD_FOLDER ({0}) does not exist!".format(settings.UPLOAD_FOLDER))
if not enckey_exists:
summarygood = False
print("ENCKEY_PATH ({0}) does not exist!".format(settings.ENCKEY_PATH))
if not rooturl_good: if not rooturl_good:
summarygood = False summarygood = False
print("ROOTURL may cause issues!") print("ROOTURL may cause issues!")

View File

@ -1,4 +1,5 @@
from flask import Flask, request, jsonify, abort, Response from flask import Flask, request, jsonify, abort, Response
from cryptography.fernet import Fernet
from flask_api import status from flask_api import status
from pathlib import Path from pathlib import Path
import string import string
@ -40,30 +41,31 @@ def log_savelog(key, ip, savedname):
slogf.write("[{0}] {1} - {2}\n".format(datetime.datetime.now(), ip, savedname)) slogf.write("[{0}] {1} - {2}\n".format(datetime.datetime.now(), ip, savedname))
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD) os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
@app.route("/upload", methods = ["POST"]) @app.route("/upload", methods = ["POST"])
def upload(): def upload():
if request.method == "POST": # sanity check: make sure it's a POST request if request.method == "POST": # sanity check: make sure it's a POST request
print("Request method was POST!") print("Request method was POST!")
os.chmod("uploadkeys", settings.UPLOADKEYS_CHMOD) with open(settings.ENCKEY_PATH,"rb") as enckey: # load encryption key
print("Changed permissions of `uploadkeys`") key = enckey.read()
with open("uploadkeys", "r") as keyfile: # load valid keys f = Fernet(key)
validkeys = keyfile.readlines()
validkeys = [x.strip("\n") for x in validkeys] with open("uploadkeys", "rb") as keyfile:
encrypted_data = keyfile.read()
decrypted_data = str(f.decrypt(encrypted_data).decode('utf-8'))
decrypted_data = decrypted_data.splitlines()
validkeys = [x.strip("\n") for x in decrypted_data]
while "" in validkeys: while "" in validkeys:
validkeys.remove("") validkeys.remove("")
print("removed blank key") print("Removed blank key(s)")
print("Valid keys: {0}".format(validkeys))
print("Loaded validkeys") print("Loaded validkeys")
if "uploadKey" in request.form: # if an uploadKey was provided if "uploadKey" in request.form: # if an uploadKey was provided
if request.form["uploadKey"] in validkeys: # check if uploadKey is valid if request.form["uploadKey"] in validkeys: # check if uploadKey is valid
print("Key is valid!") print("Key is valid!")
if "imageUpload" in request.files: # check if image to upload was provided if "imageUpload" in request.files: # check if image to upload was provided
f = request.files["imageUpload"] # f is the image to upload f = request.files["imageUpload"] # f is the image to upload
print("Found uploaded image")
else: else:
print("No image upload was found!") print("No image upload was found!")
return jsonify({'status': 'error', 'error': 'NO_IMAGE_UPLOADED'}), status.HTTP_400_BAD_REQUEST return jsonify({'status': 'error', 'error': 'NO_IMAGE_UPLOADED'}), status.HTTP_400_BAD_REQUEST
@ -73,23 +75,23 @@ def upload():
return jsonify({'status': 'error', 'error': 'FILENAME_BLANK'}), status.HTTP_400_BAD_REQUEST return jsonify({'status': 'error', 'error': 'FILENAME_BLANK'}), status.HTTP_400_BAD_REQUEST
fext = Path(f.filename).suffix # get the uploaded extension fext = Path(f.filename).suffix # get the uploaded extension
print("Uploaded file extensions is {0}".format(fext))
if allowed_extension(fext): # if the extension is allowed if allowed_extension(fext): # if the extension is allowed
print("Generating file with extension {0}".format(fext))
fname = generate_name(fext) # generate file name fname = generate_name(fext) # generate file name
print("Generated name: {0}".format(fname)) print("Generated name: {0}".format(fname))
if f: # if the uploaded image exists if f: # if the uploaded image exists
print("Uploaded image exists, obviously.") print("Uploaded image exists")
f.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image f.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image
print("Saved to {0}".format(fname)) print("Saved to {0}".format(fname))
url = settings.ROOTURL + fname # construct the url to the image url = settings.ROOTURL + fname # construct the url to the image
if settings.SAVELOG != "/dev/null": if settings.SAVELOG != "/dev/null":
print("Saving to savelog")
log_savelog(request.form["uploadKey"], request.remote_addr, fname) log_savelog(request.form["uploadKey"], request.remote_addr, fname)
print("Logged message to savelog")
print("Returning json response") print("Returning json response")
return jsonify({'status': 'success', 'url': url, 'name': fname, 'uploadedName': f.filename}), status.HTTP_201_CREATED return jsonify({'status': 'success', 'url': url, 'name': fname, 'uploadedName': f.filename}), status.HTTP_201_CREATED
else: # this shouldn't happen else: # this shouldn't happen
print("Um... uploaded image is nonexistent..? Please report this error!") print("Um... uploaded image... is nonexistent? Please report this error!")
return jsonify({'status': 'error', 'error': 'UPLOADED_IMAGE_FAILED_SANITY_CHECK_1'}), status.HTTP_400_BAD_REQUEST return jsonify({'status': 'error', 'error': 'UPLOADED_IMAGE_FAILED_SANITY_CHECK_1'}), status.HTTP_400_BAD_REQUEST
else: # if the extension was invalid else: # if the extension was invalid

102
keygen.py Normal file
View File

@ -0,0 +1,102 @@
from cryptography.fernet import Fernet
from cryptography.fernet import InvalidToken
from pathlib import Path
import settings
import string
import secrets
import sys
import os
# Check if the script was run as root
if os.geteuid() != 0:
exit("Root privileges are necessary to run this script.\nPlease try again as root or using `sudo`.")
# Check if encryption key already exists
enckey = Path(settings.ENCKEY_PATH)
if enckey.is_file():
print("Encryption key found.")
else:
print("Encryption key not found.")
print("Generating key...")
key = Fernet.generate_key()
with open(settings.ENCKEY_PATH, "wb") as key_file:
key_file.write(key)
print("Encryption key generated and stored in secret.key.")
# Load encryption key
def load_key():
with open(settings.ENCKEY_PATH, "rb") as kf:
kdata = kf.read()
return kdata
# Encrypting and storing of key
def encrypt_key(message):
key = load_key()
keyf = Fernet(key)
with open('uploadkeys', 'a+') as uploadkeys:
print(str(token), file=uploadkeys)
with open("uploadkeys", "rb") as keyfile:
keyfile_data = keyfile.read()
encrypted_data = keyf.encrypt(keyfile_data)
with open("uploadkeys", "wb") as keyfile:
keyfile.write(encrypted_data)
def ask_yn(msg):
resps = {"y": True, "n": False}
ask = True
while ask:
proceedraw = input(msg)
if proceedraw.lower() in resps.keys():
proceed = resps[proceedraw]
ask = False
else:
print("Invalid response.")
return proceed
N = 64 # Size of token
# Generate key
token = ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(N))
# Decrypt the existing keyfile
key = load_key()
keyf = Fernet(key)
genkey = True
uploadkeysp = Path("uploadkeys")
if not uploadkeysp.is_file():
uploadkeysp.touch()
else:
with open("uploadkeys", "rb") as ukf:
# read the encrypted data
encrypted_data = ukf.read()
try:
decrypted_data = keyf.decrypt(encrypted_data) # decrypt data
with open("uploadkeys", "wb") as ukf:
ukf.write(decrypted_data) # write the original file
except InvalidToken:
print("The encrypted key data is invalid and cannot be read.")
print("It may be necessary to clear the file entirely, which will invalidate all tokens.")
proceed = ask_yn("Do you wish to proceed to clearing the uploadkeys file? [y/n] ")
if proceed:
os.remove("uploadkeys")
print("Removed uploadkeys file.")
proceed2 = ask_yn("Would you like to continue and generate a new token? [y/n] ")
if not proceed2:
genkey = False
if genkey:
print("Your new token is: " + str(token)) # Print token
encrypt_key(str(token)) # Encrypt the key and save

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask_API==2.0
cryptography==3.1
Flask==1.1.2

View File

@ -1,7 +1,7 @@
UPLOAD_FOLDER = "/var/www/img" UPLOAD_FOLDER = "/path/to/images"
ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"] ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"]
ROOTURL = "https://img.bbaovanc.com/" ROOTURL = "https://example.com/"
SAVELOG = "savelog.log" SAVELOG = "savelog.log"
SAVELOG_CHMOD = 0o644 SAVELOG_CHMOD = 0o644
UPLOADKEYS_CHMOD = 0o600
SAVELOG_KEYPREFIX = 4 SAVELOG_KEYPREFIX = 4
ENCKEY_PATH = "secret.key"