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.