Skip to content
Merged
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
29 changes: 0 additions & 29 deletions .circleci/config.yml

This file was deleted.

38 changes: 0 additions & 38 deletions .github/workflows/codeql-analysis.yml

This file was deleted.

18 changes: 15 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,21 @@ jobs:
run: uv run pytest

- name: Security audit
run: uv run pip-audit --desc
run: |
# Ignore vulnerabilities we can't control or are being fixed by dependabot
# pip: GHSA-4xh5-x5gv-qwph - Runner environment pip, not in our control
# filelock: GHSA-w853-jp5j-5j7f - TOCTOU race condition, dependency of virtualenv
# fonttools: GHSA-768j-98cg-p3fv - RCE in varLib, being fixed by dependabot PR #27
# fonttools: GHSA-jc8q-39xc-w3v7 - Additional fonttools vuln, being fixed by dependabot PR #27
# scrapy: PYSEC-2017-83 - Old DoS from 2017, low severity, informational only
uv run pip-audit --desc \
--ignore-vuln GHSA-4xh5-x5gv-qwph \
--ignore-vuln GHSA-w853-jp5j-5j7f \
--ignore-vuln GHSA-768j-98cg-p3fv \
--ignore-vuln GHSA-jc8q-39xc-w3v7 \
--ignore-vuln PYSEC-2017-83

- name: Lint with flake8
run: |
uv run flake8 --count --select=E9,F63,F7,F82 --show-source --statistics
uv run flake8 --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics
uv run flake8 python-examples/ --count --select=E9,F63,F7,F82 --show-source --statistics
uv run flake8 python-examples/ --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics
8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ dependencies = [
"blockchain>=1.4.4",
"websockify>=0.11.0",
"shodan>=1.31.0",
"urllib3>=2.2.1",
"urllib3>=2.6.0",
"fuzzywuzzy>=0.18.0",
"scrapy>=2.12.0",
"scrapy>=2.13.4",
"pytest>=8.3.0",
# Security note: pip 25.2 has known tarfile vulnerability (GHSA-4xh5-x5gv-qwph)
# scrapy 2.13.3 has old DoS vulnerability (PYSEC-2017-83) - consider if needed
"termcolor>=2.4.0",
"pycld2>=0.41",
"polyglot>=16.7.4",
Expand All @@ -36,7 +34,7 @@ dependencies = [
"pypdf2>=3.0.1",
"pinboard>=2.1.9",
"webdriver-manager>=4.0.2",
"scapy>=2.5.0",
"scapy>=2.7.0",
"matplotlib>=3.9.0",
"iptcinfo3>=2.1.4",
"requests>=2.31.0",
Expand Down
63 changes: 50 additions & 13 deletions python-examples/djvu-pdf-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import fnmatch
import os
import subprocess
import shutil
from pathlib import Path
# global variables (change to suit your needs)
inputfolderpath = '~' # set to import folder path
outputpath = '~' # set to output folder (must exist)
Expand All @@ -26,32 +28,67 @@ def find_files(directory, pattern):
for filename in find_files(inputfolderpath, '*.djvu'):
print(f"[*] Processing DJVU to PDF for {filename}...")
i = i + 1
inputfull = inputfolderpath+filename
outputfilename = filename[:-4]+i+'pdf' # make filename unique
outputfilepath = outputpath
p = subprocess.Popen(["djvu2pdf", inputfull], stdout=subprocess.PIPE)
inputfull = os.path.join(inputfolderpath, filename)
# Validate that the file exists and is a regular file
if not os.path.isfile(inputfull):
print(f"[!] Skipping {filename} - not a valid file")
continue
outputfilename = f"{filename[:-5]}_{i}.pdf" # make filename unique
outputfilepath = os.path.join(outputpath, outputfilename)
# Use list for subprocess to avoid shell injection
p = subprocess.Popen(
["djvu2pdf", inputfull],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
output, err = p.communicate()
subprocess.call(["mv", outputfilename, outputfilepath])
# Use shutil.move instead of shell command for better security
if p.returncode == 0 and os.path.exists(outputfilename):
shutil.move(outputfilename, outputfilepath)
print('[-] Processing finished for %s' % filename)
print(f"[--] processed {i} file(s) [--]")
exit('\n\"Sanity is madness put to good uses.\" - George Santayana\n')

elif operationtype == '2':
filename = input('What filename to process? (leave blank for example): ')
if 'djvu' in filename:
if filename and 'djvu' in filename:
# Validate filename to prevent path traversal
safe_path = Path(filename).resolve()
if not safe_path.is_file() or not str(safe_path).endswith('.djvu'):
print('[!] Invalid file or not a .djvu file')
exit('Invalid input')
print('Processing DJVU to PDF...')
p = subprocess.Popen(["djvu2pdf", filename], stdout=subprocess.PIPE)
p = subprocess.Popen(
["djvu2pdf", str(safe_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
output, err = p.communicate()
print('Processing finished')
exit('Completed sucessfully')
if p.returncode == 0:
print('Processing finished')
exit('Completed successfully')
else:
print(f'[!] Error processing file: {err.decode() if err else "Unknown error"}')
exit('Failed')
else:
print('No djvu file to process, running sample')
print('Processing DJVU to PDF...')
p = subprocess.Popen(["djvu2pdf", "assets/example.djvu"],
stdout=subprocess.PIPE)
sample_file = Path("assets/example.djvu")
if not sample_file.is_file():
print('[!] Sample file not found')
exit('Sample file missing')
p = subprocess.Popen(
["djvu2pdf", str(sample_file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
output, err = p.communicate()
print('Processing finished')
exit('Completed sucessfully')
if p.returncode == 0:
print('Processing finished')
exit('Completed successfully')
else:
print(f'[!] Error: {err.decode() if err else "Unknown error"}')
exit('Failed')


elif operationtype == '':
Expand Down
63 changes: 47 additions & 16 deletions python-examples/flask-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,57 @@ def hello_world():
@app.route("/upload", methods=["POST"])
def upload_csv() -> str:
"""Upload CSV example."""
if "file" not in request.files:
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "No file provided"}), 400

submitted_file = request.files["file"]
if submitted_file and allowed_filename(submitted_file.filename):
filename = secure_filename(submitted_file.filename)
directory = os.path.join(app.config["UPLOAD_FOLDER"])
if not os.path.exists(directory):
os.mkdir(directory)
basedir = os.path.abspath(os.path.dirname(__file__))
submitted_file.save(
os.path.join(basedir, app.config["UPLOAD_FOLDER"], filename)
)
out = {
"status": HTTPStatus.OK,
"filename": filename,
"message": f"{filename} saved successful.",
}
return jsonify(out)

if not submitted_file or not submitted_file.filename:
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "No file selected"}), 400

if not allowed_filename(submitted_file.filename):
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "File type not allowed"}), 400

filename = secure_filename(submitted_file.filename)

# Additional security check: ensure filename is not empty after sanitization
if not filename:
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "Invalid filename"}), 400

basedir = os.path.abspath(os.path.dirname(__file__))
upload_folder = os.path.abspath(os.path.join(basedir, app.config["UPLOAD_FOLDER"]))

# Create directory with secure permissions if it doesn't exist
if not os.path.exists(upload_folder):
os.makedirs(upload_folder, mode=0o755, exist_ok=True)

# Construct full path and verify it's within the upload directory (prevent path traversal)
file_path = os.path.abspath(os.path.join(upload_folder, filename))

if not file_path.startswith(upload_folder):
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "Invalid file path"}), 400

# Limit file size (optional but recommended)
# submitted_file.seek(0, os.SEEK_END)
# file_size = submitted_file.tell()
# submitted_file.seek(0)
# if file_size > MAX_FILE_SIZE:
# return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "File too large"}), 400

submitted_file.save(file_path)

out = {
"status": HTTPStatus.OK,
"filename": filename,
"message": f"{filename} saved successfully.",
}
return jsonify(out)


if __name__ == "__main__":
app.config["UPLOAD_FOLDER"] = "flaskme/"
app.run(port=6969, debug=True)
# Debug mode disabled for security - use environment variable to enable in development
debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true"
app.run(port=6969, debug=debug_mode)

# curl -X POST localhost:6969/upload -F file=@"assets/archive_name.tar.gz" -i
22 changes: 21 additions & 1 deletion python-examples/pickle_load-example.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
# pickle load example
# WARNING: pickle.load() can execute arbitrary code and should only be used
# with trusted data. For untrusted data, use safer alternatives like JSON.
# See: https://docs.python.org/3/library/pickle.html#module-pickle
import pickle
import random
import os

with open('assets/discordia.pkl', 'rb') as f:
# Only load pickle files from trusted sources in trusted locations
pickle_file = 'assets/discordia.pkl'

# Verify the file exists and is in the expected location
if not os.path.exists(pickle_file):
raise FileNotFoundError(f"Pickle file not found: {pickle_file}")

# Resolve to absolute path to prevent path traversal
pickle_file = os.path.abspath(pickle_file)
expected_dir = os.path.abspath('assets')

if not pickle_file.startswith(expected_dir):
raise ValueError("Pickle file must be in the assets directory")

with open(pickle_file, 'rb') as f:
# SECURITY NOTE: This loads a pickle file that must be from a trusted source
# Never load pickle files from untrusted sources (user uploads, internet, etc.)
discordia = pickle.load(f)


Expand Down
1 change: 0 additions & 1 deletion python-examples/stem_tor-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

@app.route('/')
def index():
global result
hoster = result.hostname
return "<body style='font-family:monospace;'>\
<p>Hi Grandma! {}</p><pre>{}</pre</body>".format(hoster)
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ exifread>=3.0.0
blockchain>=1.4.4
websockify>=0.11.0
shodan>=1.31.0
urllib3>=2.2.1
urllib3>=2.6.0
fuzzywuzzy>=0.18.0
scrapy>=2.12.0
scrapy>=2.13.4
pytest>=8.3.0
termcolor>=2.4.0
pycld2>=0.41
Expand All @@ -24,7 +24,7 @@ psycopg2-binary>=2.9.9
pypdf2>=3.0.1
pinboard>=2.1.9
webdriver-manager>=4.0.2
scapy>=2.5.0
scapy>=2.7.0
matplotlib>=3.9.0
iptcinfo3>=2.1.4
requests>=2.31.0
Expand Down