Challenge Title: Valenfind Category: Web Difficulty: Medium

The target web app is running on port "5000".

Before exploring the app, I ran an initial `nmap` scan to check for other exposed services.

The service fingerprint on port `5000` shows: `Server: Werkzeug/3.0.1 Python/3.12.3`
That strongly suggests a Flask application, and port `5000` is Flask's default dev port.
I then used Burp Suite proxy and started interacting with the web app.

I began with the signup flow.
After submitting credentials on `/register`, the app redirected me to `/complete_profile`.

After completing the profile, the app redirected me to `/dashboard`.

I tested profile actions like **Like** and **Send Valentine** while watching traffic in Burp.
On `/profile/cupid`, I found this JavaScript comment:
function loadTheme(layoutName) {
// Feature: Dynamic Layout Fetching
// Vulnerability: 'layout' parameter allows LFI
fetch(`/api/fetch_layout?layout=${layoutName}`)
.then(r => r.text())
.then(html => {
const bioText = "I keep the database secure. No peeking.";
const username = "cupid";
// Client-side rendering of the fetched template
let rendered = html.replace('__USERNAME__', username)
.replace('__BIO__', bioText);
document.getElementById('bio-container').innerHTML = rendered;
})
.catch(e => {
console.error(e);
document.getElementById('bio-container').innerText = "Error loading theme.";
});
}
The comment clearly points to an LFI issue.
`loadTheme()` is triggered when the profile theme changes, and it sends the `layout` value to the backend.

I sent this API request to Burp Repeater and tested path traversal payloads.
First test was `/etc/passwd` with:
`../../../../etc/passwd`
It worked.

Next, I enumerated files and successfully read the main app source at:
`/opt/Valenfind/app.py`

The key clue came from `app.py`.
It exposes an admin database export route: `/api/admin/export_db`.
The code also includes a hardcoded admin API key.
By sending that key in the `X-Valentine-Token` header, the database can be downloaded.

I sent the request with the token and got the flag in plain text in the response.