Web Security

PortSwigger Academy - Source Code Disclosure via Backup Files

This lab leaks its source code via backup files in a hidden directory. To solve the lab, identify and submit the database password, which is hard-coded in the leaked source code.

L0WK3Y
· 4 min read
Send by email
infophreak 2025

Lab Overview


This lab leaks its source code via backup files in a hidden directory. To solve the lab, identify and submit the database password, which is hard-coded in the leaked source code.

Instructions

To solve the lab:

  1. Identify and submit the database password

Source code disclosure vulnerabilities can have devastating consequences, especially when sensitive information, such as database credentials, are exposed. This write-up explores how such vulnerabilities occur, their implications, and how attackers exploit them.


Discovery Process

To identify the vulnerability, I used tools like Dirbuster and Gobuster to enumerate directories and files. During the scan, I discovered a /backup directory:

img

This directory contains a file named ProductTemplate.java.bak. This file, a backup of the Java source code, was publicly accessible and unprotected. Upon inspecting the file, I found sensitive hardcoded credentials within the source code:

"postgres" - Username
"xlf0wurkoxlq7zmu55auymt1g2gvgvr4" - Password

This username and password can be found by conducting code analysis on the Java backup file (see below) and is used to connect to the database. These hard-coded credentials provide attackers with direct access to the underlying data via the PostgreSQL driver.


Code Analysis

Below is an excerpt from the ProductTemplate.java.bak file:

package data.productcatalog;

import common.db.JdbcConnectionBuilder;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ProductTemplate implements Serializable {
    static final long serialVersionUID = 1L;

    private final String id;
    private transient Product product;

    public ProductTemplate(String id) {
        this.id = id;
    }

    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
        inputStream.defaultReadObject();

        ConnectionBuilder connectionBuilder = ConnectionBuilder.from(
                "org.postgresql.Driver",
                "postgresql",
                "localhost",
                5432,
                "postgres",
                "postgres",
                "xlf0wurkoxlq7zmu55auymt1g2gvgvr4"
        ).withAutoCommit();
        try {
            Connection connect = connectionBuilder.connect(30);
            String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
            Statement statement = connect.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            if (!resultSet.next()) {
                return;
            }
            product = Product.from(resultSet);
        } catch (SQLException e) {
            throw new IOException(e);
        }
    }

    public String getId() {
        return id;
    }

    public Product getProduct() {
        return product;
    }
}

Upon reviewing the code, the connectionBuilder object is passed various parameters which appear to be database credentials.

ConnectionBuilder connectionBuilder = ConnectionBuilder.from(
        "org.postgresql.Driver",   // Database driver (PostgreSQL)
        "postgresql",              // Database type
        "localhost",               // Database host (running locally)
        5432,                      // Port number (default for PostgreSQL)
        "postgres",                // Database name
        "postgres",                // Username
        "xlf0wurkoxlq7zmu55auymt1g2gvgvr4" // Password (hardcoded, security risk)
).withAutoCommit();

The values from this coonnectionBuilder object are then used to establish a connection to the database using the connect object.

try {
    Connection connect = connectionBuilder.connect(30);

Why It’s Vulnerable

1. Exposed Backup Files

The /backup directory is publicly accessible, and directory indexing is enabled. This oversight allows attackers to enumerate and retrieve files within the directory easily. Files with extensions like .bak, .old, .tmp, or .zip often contain sensitive information and are commonly left behind during development.

2. Hardcoded Credentials in Source Code

The backup file contains the following hardcoded database username and password:

"postgres" - Username
"xlf0wurkoxlq7zmu55auymt1g2gvgvr4" - Password

This practice is inherently insecure because:

  • Anyone with access to the file can extract these credentials and connect to the database.

Example using psql command-line client, which is included in PostgreSQL installations:

psql -h localhost -p 5432 -U postgres -d postgres

-h localhost: The hostname or IP address of the database server.
-p 5432: The port number (default is 5432 for PostgreSQL).
-U postgres: The username (postgres in this case).
-d postgres: The database name.
  • Hardcoding secrets in source code increases the likelihood of exposure during a breach.

3. Lack of Access Controls

No authentication or authorization is required to access the /backup directory or the files within it. This makes it easy for attackers to exploit the vulnerability.

4. Directory Indexing Enabled

The /backup directory has directory indexing enabled, as shown by the HTML response. This enables attackers to list and retrieve files directly.


The Impact

Using the hardcoded database credentials, an attacker can:

  • Gain unauthorized access to sensitive data stored in the database.
  • Manipulate or delete records, leading to data integrity issues.
  • Use the compromised credentials to pivot further into the system.