Merge branch 'Zeecka:main' into main
This commit is contained in:
+8
-5
@@ -26,17 +26,20 @@ RUN wget https://github.com/syvaidya/openstego/releases/download/openstego-0.8.6
|
|||||||
dpkg -i /tmp/openstego.deb && \
|
dpkg -i /tmp/openstego.deb && \
|
||||||
rm /tmp/openstego.deb
|
rm /tmp/openstego.deb
|
||||||
|
|
||||||
# Clean up apt cache and remove useless packages
|
# Copy application
|
||||||
RUN apt-get remove -y wget && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY aperisolve/ /aperisolve/
|
COPY aperisolve/ /aperisolve/
|
||||||
|
|
||||||
RUN mkdir -p /data
|
RUN mkdir -p /data
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
RUN pip install --no-cache-dir -r /aperisolve/requirements.txt
|
RUN pip install --no-cache-dir -r /aperisolve/requirements.txt
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Auto clean after build
|
||||||
|
RUN apt-get remove -y wget && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "--access-logfile", "-", "--error-logfile", "-", "--log-level", "info", "--capture-output", "aperisolve.wsgi:application"]
|
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "--access-logfile", "-", "--error-logfile", "-", "--log-level", "info", "--capture-output", "aperisolve.wsgi:application"]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .utils import MAX_PENDING_TIME, update_data
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ def analyze_binwalk(input_img: Path, output_dir: Path) -> None:
|
|||||||
stderr = ""
|
stderr = ""
|
||||||
# Run binwalk
|
# Run binwalk
|
||||||
data = subprocess.run(
|
data = subprocess.run(
|
||||||
["binwalk", "-e", "../" + str(image_name), "--run-as=root"],
|
["binwalk", "--matryoshka", "-e", "../" + str(image_name), "--run-as=root"],
|
||||||
cwd=output_dir,
|
cwd=output_dir,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -47,16 +48,17 @@ def analyze_binwalk(input_img: Path, output_dir: Path) -> None:
|
|||||||
# Only report error if stderr exists AND extraction failed.
|
# Only report error if stderr exists AND extraction failed.
|
||||||
# If zip_exist is True, binwalk worked (despite warnings), so we proceed.
|
# If zip_exist is True, binwalk worked (despite warnings), so we proceed.
|
||||||
if len(stderr) > 0 and not zip_exist:
|
if len(stderr) > 0 and not zip_exist:
|
||||||
err = {
|
err: dict[str, dict[str, Any]] = {
|
||||||
"binwalk": {
|
"binwalk": {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
|
"output": data.stdout.split("\n") if data else [],
|
||||||
"error": stderr,
|
"error": stderr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_data(output_dir, err)
|
update_data(output_dir, err)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
output_data = {
|
output_data: dict[str, dict[str, Any]] = {
|
||||||
"binwalk": {
|
"binwalk": {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"output": data.stdout.split("\n") if data else [],
|
"output": data.stdout.split("\n") if data else [],
|
||||||
|
|||||||
+37
-43
@@ -6,6 +6,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from shutil import rmtree
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from flask import Flask, Response, abort, jsonify, render_template, request, send_file
|
from flask import Flask, Response, abort, jsonify, render_template, request, send_file
|
||||||
@@ -112,34 +113,33 @@ def upload_image() -> tuple[Response, int]:
|
|||||||
submission_hash = hashlib.md5(submission_data).hexdigest()
|
submission_hash = hashlib.md5(submission_data).hexdigest()
|
||||||
submission_path = RESULT_FOLDER / img_hash / submission_hash
|
submission_path = RESULT_FOLDER / img_hash / submission_hash
|
||||||
|
|
||||||
# Check if hash is present on the DB
|
# If submission already exists, return submission hash
|
||||||
submission: Submission = Submission.query.filter_by(
|
if submission_path.exists():
|
||||||
hash=submission_hash
|
submission: Submission = Submission.query.filter_by(
|
||||||
).first()
|
hash=submission_hash
|
||||||
|
).first()
|
||||||
|
|
||||||
# Only return if BOTH the file exists AND the DB entry exists
|
if submission is not None: # File exist but not in DB (should not happen),
|
||||||
if submission_path.exists() and submission is not None:
|
rmtree(submission_path) # Removing file and continue processing as new
|
||||||
return jsonify({"submission_hash": submission.hash}), 200
|
db.session.delete(submission) # pylint: disable=no-member
|
||||||
|
db.session.commit() # pylint: disable=no-member
|
||||||
|
else:
|
||||||
|
return jsonify({"submission_hash": submission.hash}), 200
|
||||||
|
|
||||||
# Self-Healing Logic
|
# If image is new, create a new Image entry
|
||||||
new_img_path = RESULT_FOLDER / img_hash / image_name
|
new_img_path = RESULT_FOLDER / img_hash / image_name
|
||||||
|
sub_img = Image.query.filter_by(hash=img_hash).first() # type: ignore
|
||||||
|
|
||||||
# Try to find image in DB by hash
|
if new_img_path.parent.exists() and sub_img is None:
|
||||||
sub_img = Image.query.filter_by(
|
# Inconsistent state: image file exists but no DB entry
|
||||||
hash=img_hash
|
rmtree(new_img_path.parent) # Remove the existing files and continue
|
||||||
).first()
|
|
||||||
|
|
||||||
# If DB entry is missing (even if file exists on disk), create it!
|
if not new_img_path.parent.exists():
|
||||||
if sub_img is None:
|
new_img_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
if not new_img_path.parent.exists() or not new_img_path.exists():
|
with open(new_img_path, "wb") as f: # Write file to disk
|
||||||
new_img_path.parent.mkdir(parents=True, exist_ok=True)
|
f.write(image_data)
|
||||||
with open(new_img_path, "wb") as f:
|
|
||||||
f.write(image_data)
|
|
||||||
|
|
||||||
|
new_img = Image(
|
||||||
# Recreate the missing Database Entry
|
|
||||||
sub_img = Image(
|
|
||||||
file=str(new_img_path),
|
file=str(new_img_path),
|
||||||
hash=img_hash,
|
hash=img_hash,
|
||||||
size=len(image_data),
|
size=len(image_data),
|
||||||
@@ -147,39 +147,33 @@ def upload_image() -> tuple[Response, int]:
|
|||||||
first_submission_date=datetime.now(timezone.utc),
|
first_submission_date=datetime.now(timezone.utc),
|
||||||
last_submission_date=datetime.now(timezone.utc),
|
last_submission_date=datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
db.session.add(sub_img)
|
db.session.add(new_img) # pylint: disable=no-member
|
||||||
db.session.commit()
|
db.session.commit() # pylint: disable=no-member
|
||||||
|
|
||||||
# Increment upload count for the image
|
sub_img = Image.query.filter_by(hash=img_hash).first() # type: ignore
|
||||||
sub_img.upload_count += 1
|
sub_img.upload_count += 1
|
||||||
db.session.commit()
|
db.session.commit() # pylint: disable=no-member
|
||||||
|
|
||||||
# Create new Submission entry
|
# Create new Submission entry
|
||||||
submission_path.mkdir(parents=True, exist_ok=True)
|
submission_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
submission = Submission(
|
||||||
|
filename=image.filename,
|
||||||
|
password=password,
|
||||||
|
deep_analysis=deep_analysis,
|
||||||
|
hash=submission_hash,
|
||||||
|
status="pending",
|
||||||
|
date=time.time(),
|
||||||
|
image_hash=sub_img.hash, # type: ignore
|
||||||
|
)
|
||||||
|
db.session.add(submission) # pylint: disable=no-member
|
||||||
|
|
||||||
# Only create a new submission if it does not already exist
|
db.session.commit() # pylint: disable=no-member
|
||||||
# New submission for new image
|
|
||||||
if not submission:
|
|
||||||
submission = Submission(
|
|
||||||
filename=image.filename,
|
|
||||||
password=password,
|
|
||||||
deep_analysis=deep_analysis,
|
|
||||||
hash=submission_hash,
|
|
||||||
status="pending",
|
|
||||||
date=time.time(),
|
|
||||||
image_hash=sub_img.hash,
|
|
||||||
)
|
|
||||||
db.session.add(submission)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Start the analysis jobs
|
# Start the analysis jobs
|
||||||
queue.enqueue("aperisolve.workers.analyze_image", submission.hash, job_timeout=300)
|
queue.enqueue("aperisolve.workers.analyze_image", submission.hash, job_timeout=300)
|
||||||
|
|
||||||
# Return submission identifier
|
|
||||||
return jsonify({"submission_hash": submission.hash}), 200
|
return jsonify({"submission_hash": submission.hash}), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/status/<hash_val>", methods=["GET"])
|
@app.route("/status/<hash_val>", methods=["GET"])
|
||||||
def get_status(hash_val: str) -> Response:
|
def get_status(hash_val: str) -> Response:
|
||||||
"""Get the processing status of a submission."""
|
"""Get the processing status of a submission."""
|
||||||
|
|||||||
@@ -252,6 +252,38 @@ function parseResult(result) {
|
|||||||
|
|
||||||
analyzer.innerHTML += `<h2>${capitalize(tool.replace("_"," "))}</h2>`;
|
analyzer.innerHTML += `<h2>${capitalize(tool.replace("_"," "))}</h2>`;
|
||||||
|
|
||||||
|
// Parse text output
|
||||||
|
if (typeof result[tool]["output"] === "string") {
|
||||||
|
analyzer.innerHTML += `<pre>${escapeHtml(
|
||||||
|
result[tool]["output"]
|
||||||
|
)}</pre>`;
|
||||||
|
} else if (Array.isArray(result[tool]["output"])) {
|
||||||
|
if (result[tool]["output"].length > 0) {
|
||||||
|
var texarea_content = `<div class="textarea-container">`;
|
||||||
|
texarea_content += `<textarea class="form-control w-100 mb-2" rows="8" readonly>`;
|
||||||
|
for (const line of result[tool]["output"]) {
|
||||||
|
texarea_content += escapeHtml(`${line}\n`);
|
||||||
|
}
|
||||||
|
texarea_content += `</textarea>`;
|
||||||
|
texarea_content += `<i class="fas fa-copy copy-icon"></i>`;
|
||||||
|
texarea_content += `</div>`;
|
||||||
|
analyzer.innerHTML += texarea_content;
|
||||||
|
}
|
||||||
|
} else if (typeof result[tool]["output"] === "object") {
|
||||||
|
var table_content = `<div class="table-container">`;
|
||||||
|
table_content += `<table>`;
|
||||||
|
for (const key in result[tool]["output"]) {
|
||||||
|
table_content += `<tr><td>${escapeHtml(key)}</td>`;
|
||||||
|
table_content += `<td>${escapeHtml(
|
||||||
|
result[tool]["output"][key]
|
||||||
|
)}</td></tr>`;
|
||||||
|
}
|
||||||
|
table_content += `</table>`;
|
||||||
|
table_content += `</table>`;
|
||||||
|
analyzer.innerHTML += table_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse images, downloads, ...
|
||||||
if (result[tool]["status"] === "ok") {
|
if (result[tool]["status"] === "ok") {
|
||||||
if ("images" in result[tool]) {
|
if ("images" in result[tool]) {
|
||||||
// Parse image output
|
// Parse image output
|
||||||
@@ -286,41 +318,6 @@ function parseResult(result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse text output
|
|
||||||
|
|
||||||
|
|
||||||
if (typeof result[tool]["output"] === "string") {
|
|
||||||
analyzer.innerHTML += `<pre>${escapeHtml(
|
|
||||||
result[tool]["output"]
|
|
||||||
)}</pre>`;
|
|
||||||
} else if (Array.isArray(result[tool]["output"])) {
|
|
||||||
if (result[tool]["output"].length > 0) {
|
|
||||||
var texarea_content = `<div class="textarea-container">`;
|
|
||||||
texarea_content += `<textarea class="form-control w-100 mb-2" rows="8" readonly>`;
|
|
||||||
for (const line of result[tool]["output"]) {
|
|
||||||
texarea_content += escapeHtml(`${line}\n`);
|
|
||||||
}
|
|
||||||
texarea_content += `</textarea>`;
|
|
||||||
texarea_content += `<i class="fas fa-copy copy-icon"></i>`;
|
|
||||||
texarea_content += `</div>`;
|
|
||||||
analyzer.innerHTML += texarea_content;
|
|
||||||
}
|
|
||||||
} else if (typeof result[tool]["output"] === "object") {
|
|
||||||
var table_content = `<div class="table-container">`;
|
|
||||||
table_content += `<table>`;
|
|
||||||
for (const key in result[tool]["output"]) {
|
|
||||||
table_content += `<tr><td>${escapeHtml(key)}</td>`;
|
|
||||||
table_content += `<td>${escapeHtml(
|
|
||||||
result[tool]["output"][key]
|
|
||||||
)}</td></tr>`;
|
|
||||||
}
|
|
||||||
table_content += `</table>`;
|
|
||||||
table_content += `</table>`;
|
|
||||||
analyzer.innerHTML += table_content;
|
|
||||||
} else {
|
|
||||||
// analyzer.innerHTML += `<pre>${result[tool]["output"]}</pre>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("download" in result[tool]) {
|
if ("download" in result[tool]) {
|
||||||
// Parse download link
|
// Parse download link
|
||||||
analyzer.innerHTML += `<br/><a href="${escapeHtml(
|
analyzer.innerHTML += `<br/><a href="${escapeHtml(
|
||||||
|
|||||||
Reference in New Issue
Block a user