SQLi & XSS in 100 Minutes: A Responsible Disclosure on Yogosha
Redacted (via Yogosha VDP)
1 hour 40 minutes
~100 min
Time to Find3
SQLi Found2
XSS Found100%
DisclosedOn This Page
Key outcome
5 vulnerabilities (3 SQLi, 2 Reflected XSS) identified and responsibly disclosed to the program owner within a single testing session. No exploitation beyond proof of concept.
Table of Contents
28
Executive Summary
During a spontaneous bug hunting session on the Yogosha security platform, Salman, security researcher and founder of XHack, identified five web application vulnerabilities across a single target program in under two hours. The findings included three SQL Injection (SQLi) vulnerabilities and two Reflected Cross-Site Scripting (XSS) vulnerabilities, spanning critical and medium severity ratings.
The program was later confirmed to be a Vulnerability Disclosure Program (VDP) offering no monetary compensation. Salman reported every finding regardless. Security, in his view, is a responsibility before it is a reward.
"I did not initially notice the program was a VDP. But once I did, it changed nothing about my decision to report. Protecting systems and users always comes first." Salman, Security Researcher & Founder, XHack
This write-up documents the methodology, the technical findings, the disclosure process, and the remediation guidance provided, serving both as a record of responsible practice and a technical reference for developers and security teams.
Background
Salman does not actively hunt on Yogosha as a primary activity. On the day of this engagement, he had unstructured time and decided to explore a target on the platform with no preparation, no prior reconnaissance, and no tooling beyond a browser and a proxy. What followed was a textbook demonstration of what manual testing with experience and intuition can uncover in a very short window.
The target application, whose identity is withheld in accordance with the platform's responsible disclosure terms, is a web-based platform handling user input across multiple functional areas. The application was accessible to authenticated users, and no special privileges were obtained prior to testing.
Methodology
Salman's approach was entirely manual. No automated scanners were used during this session. The process followed a structured but lightweight flow:
Step 1: Attack Surface Mapping
The first task was building a complete picture of every point where user-controlled input enters the application. Salman walked through the entire application noting every form, URL parameter, search field, filter, and API call visible in browser traffic. This included the news listing pages, category filters, info endpoints, the site search field, and the contact form. Each input point was logged before any testing began.
Step 2: Reconnaissance
With the surface mapped, the focus shifted to understanding how the application behaves. Salman observed response headers for technology hints, watched how the application responded to unexpected input types, and noted which endpoints returned dynamic content versus static pages. Error messages and response timing differences at this stage can reveal backend technology and query structures before a single payload is sent.
Step 3: Testing Each Endpoint
Each identified input point was tested individually with targeted payloads, starting conservative. For numeric parameters, a single quote was the first test. For text fields, a basic XSS vector. The goal at this stage was not exploitation but confirmation of unsafe handling. Endpoints were worked through one at a time, with each test observed carefully before moving to the next.
Step 4: Noting Findings
Each confirmed vulnerability was documented immediately: the request, the response, the parameter, the behaviour observed, and an initial severity assessment. Nothing was left to memory. This discipline meant the final report could be assembled in minutes rather than hours.
Step 5: Safe Further Exploitation
Once a vulnerability was confirmed, Salman performed the minimum exploitation necessary to establish real-world impact. For SQL injection this meant retrieving exactly one row of basic, non-sensitive data to demonstrate the injection was live and data was reachable. No table enumeration. No credential extraction. No further probing once impact was established. The goal was proof, not plunder.
No data was extracted beyond a single non-sensitive row per confirmed SQLi finding. No accounts other than the test account were accessed. Testing ceased as soon as each vulnerability was confirmed and documented.
Finding 1: SQL Injection (Critical)
Overview
The application passed user-supplied input directly into database queries without sanitisation or parameterisation across three endpoints. Salman identified each injection point independently, and each was confirmed exploitable in isolation.
Affected Endpoints
redacted.com/redacted/news?id=
redacted.com/redacted/categories?id=
redacted.com/redacted/info?id=
All three id parameters accepted unsanitised input and passed it directly into SQL queries on the backend.
Proof of Concept
Step 1: Initial anomaly detection
The first test was a single quote appended to a legitimate id value:
GET /redacted/news?id=1' HTTP/1.1
Host: redacted.com
Cookie: session=[redacted]
Error logging was disabled on the server. No SQL error message appeared in the response. What changed was the response behaviour — the content size shifted and the page returned a subtly different structure compared to a clean request. This behavioural difference, not a verbose error, was the first indicator of injection.
Step 2: Union-based confirmation
With a suspected injection point, Salman progressed to union-based testing to confirm data reachability. The goal was to determine the column count and data types, then extract a single controlled value:
GET /redacted/news?id=1 UNION SELECT NULL,NULL,NULL-- HTTP/1.1
Column count was established by incrementing NULL values until the response normalised. Once confirmed, a controlled string was injected to verify output reflection:
GET /redacted/news?id=0 UNION SELECT NULL,'xhack_confirm',NULL-- HTTP/1.1
The injected string appeared in the response body, confirming union-based SQL injection and that at least one column was rendered in the output.
Step 3: Time-based blind confirmation
To confirm the injection was also exploitable in a blind context (where no output is reflected), a time-based payload was used:
GET /redacted/news?id=1 AND SLEEP(5)-- HTTP/1.1
The server delayed its response by approximately 5 seconds, confirming that the injected SQL was being executed by the backend database engine. This technique works regardless of whether any data is returned in the response, confirming the injection persists even on endpoints with no visible output.
Both union-based and time-based injection were confirmed across all three affected endpoints.
Responsible Exploitation
To establish real-world impact without causing harm, Salman retrieved exactly one row of basic, non-sensitive data using a LIMIT 1 constrained query. This was the minimum necessary to confirm that data was genuinely reachable through the injection. No further rows were retrieved. No tables were enumerated. No credentials, personal data, or sensitive records were accessed or extracted at any point.
-- Minimal PoC: retrieve one row only, confirm data access
' UNION SELECT NULL, table_name, NULL FROM information_schema.tables LIMIT 1 --
The query returned a single table name from the schema, confirming database read access. Testing stopped there.


Impact
SQL injection at this level carries a Critical severity rating. Depending on the database configuration and privileges, a malicious actor could:
- Extract the full contents of every database table including user credentials, personal data, session tokens, and business records
- Bypass authentication entirely using crafted login payloads
- Modify or delete data, disrupting application integrity
- Execute OS-level commands if the database user holds
FILEorSUPERprivileges
The OWASP Top 10 classifies this as A03:2021: Injection, consistently one of the most critical and commonly exploited vulnerability classes in web applications.
Finding 2: Reflected XSS via News Search (Medium)
Overview
The news search field reflected user input directly into the HTML response without output encoding. Any value entered into the search box was echoed back into the page source verbatim, including HTML and script tags.
Affected Endpoint
redacted.com/redacted/news?search=
Proof of Concept
GET /redacted/news?search=<svg/onload=alert(origin)> HTTP/1.1
Host: redacted.com
The server returned:
<div class="search-results">
Results for: <svg/onload=alert(origin)>
</div>
The SVG tag executed in the browser, triggering the alert with the page origin, confirming unsanitised reflection and live script execution under the application's domain. This is a classic reflected XSS vector: a victim who follows a crafted link with the payload in the search parameter will have the script execute in their browser under the application's origin.
Impact
- Session hijacking: the session cookie is exposed to an attacker-controlled server via a crafted URL shared with the victim
- Phishing under trusted domain: malicious content delivered from the application's own domain, bypassing browser trust warnings
- Credential harvesting: injecting a fake login overlay into the legitimate site
Finding 3: Reflected XSS via Contact Form and CSRF (Medium / High in combination)
Overview
The contact form reflected user-supplied input back into the HTTP response without sanitisation. On its own this constitutes a reflected XSS. What elevated the severity was the absence of any anti-CSRF protection on the form. The two weaknesses together create a more dangerous attack chain than either vulnerability alone.
Affected Vector
The contact form submission endpoint. No CSRF token was present on the form, and the server performed no origin validation on incoming POST requests.
Proof of Concept: Reflected XSS
Submitting a script payload in a form field:
POST /redacted/contact HTTP/1.1
Host: redacted.com
Content-Type: application/x-www-form-urlencoded
name=<svg/onload=alert(origin)>&email=test@test.com&message=hello
The server returned the name field value unencoded in the response. The SVG payload executed in the browser, confirming live XSS under the application's origin.
Proof of Concept: CSRF-Assisted Delivery
Because the form carried no anti-CSRF token, an attacker does not need to persuade the victim to visit a URL with a visible payload in the query string. Instead, a malicious page hosted externally can auto-submit the contact form on the victim's behalf the moment they land on it:
<html>
<body onload="document.forms[0].submit()">
<form action="https://redacted.com/redacted/contact" method="POST">
<input name="name" value="<svg/onload=alert(origin)>" />
<input name="email" value="attacker@attacker.com" />
<input name="message" value="hello" />
</form>
</body>
</html>
When a logged-in user visits this page, their browser silently submits the form. The application processes it, reflects the injected name field back in the response, and the SVG payload executes in the victim's browser under the application's own origin. No interaction required beyond visiting the attacker's page.

Why CSRF Makes This Worse
A plain reflected XSS via a URL parameter requires the attacker to get the victim to click a suspicious-looking link. Phishing campaigns, social engineering, or shortened URLs are the delivery mechanism, and security-aware users may notice unusual query strings.
A CSRF-assisted XSS removes that friction entirely. The payload is delivered through a legitimate-looking POST request auto-fired by a third-party page. The victim sees no unusual URL. The application itself processes the request. The attack succeeds silently.
The combination of missing output encoding and missing CSRF protection turned a medium-severity finding into a practical, low-friction account takeover vector.
Impact
- Silent session hijacking without requiring the victim to interact with a crafted URL
- Account takeover via cookie exfiltration for any user who visits the attacker's page while authenticated
- Escalation path: once a session is hijacked, the SQLi vulnerabilities become accessible from the attacker's perspective under a legitimate user session
The Responsible Disclosure Decision
When Salman was preparing to submit the reports, he confirmed the program's status: VDP with no monetary rewards.
He reported everything anyway.
This is not a trivial point. Bug bounty culture sometimes conflates vulnerability reporting with financial transaction. Salman's position, and XHack's, is that responsible disclosure is a professional and ethical obligation, not a service rendered in exchange for payment.
The vulnerabilities existed. Real users were potentially exposed. The right action was clear.
All five findings were submitted to Yogosha with:
- Full proof-of-concept reproduction steps
- Request and response captures
- Severity assessments with justification
- Concrete remediation guidance
At no point during the engagement was more than one row of basic data retrieved through any SQL injection. No personal data, credentials, or sensitive records were accessed. Testing stopped the moment each vulnerability was confirmed. The scope was respected throughout.
Remediation Guidance
SQL Injection
1. Use Parameterised Queries (Prepared Statements)
User input must never be concatenated into a SQL string. The query structure and the data must be separated at the driver level.
# Vulnerable: never do this
query = "SELECT * FROM news WHERE id = '" + user_input + "'"
# Secure: parameterised query
cursor.execute("SELECT * FROM news WHERE id = %s", (user_input,))
// Vulnerable
db.query(`SELECT * FROM news WHERE id = ${req.params.id}`);
// Secure
db.query('SELECT * FROM news WHERE id = ?', [req.params.id]);
// Vulnerable
$result = mysqli_query($conn, "SELECT * FROM categories WHERE id = " . $_GET['id']);
// Secure: PDO prepared statement
$stmt = $pdo->prepare("SELECT * FROM categories WHERE id = ?");
$stmt->execute([$_GET['id']]);
2. Apply Least Privilege to Database Accounts
The application database user should hold only SELECT, INSERT, and UPDATE on specific tables. It must never hold DROP, FILE, SUPER, or administrative grants.
3. Suppress Verbose Error Messages in Production
Database errors must never reach the end user. Log server-side, return a generic error page.
; php.ini production settings
display_errors = Off
log_errors = On
error_log = /var/log/app_errors.log
4. Deploy a Web Application Firewall (WAF)
A WAF is a defence-in-depth layer, not a substitute for parameterised queries. It catches known SQLi patterns and opportunistic automated scanning.
XSS
1. Encode All Output
Every user-supplied value rendered into HTML must be output-encoded before being written to the page.
// Vulnerable: raw HTML injection
element.innerHTML = userInput;
// Secure: text node only
element.textContent = userInput;
# Flask / Jinja2 auto-escapes by default
# Safe:
return render_template('page.html', message=user_input)
# Bypasses escaping entirely, never use with user input:
return render_template_string("{{ message|safe }}", message=user_input)
2. Implement a Content Security Policy (CSP)
CSP instructs the browser on which scripts it may execute. A correct CSP eliminates the impact of reflected XSS even when a reflection vulnerability is present.
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
3. Set HttpOnly and Secure Flags on Session Cookies
This prevents JavaScript from reading session cookies, removing the primary value of any session-hijacking XSS payload.
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
CSRF
1. Add CSRF Tokens to All State-Changing Forms
Every form that performs a state-changing action must include a unique, unpredictable, server-validated token tied to the user's session.
<form method="POST" action="/contact">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<!-- other fields -->
</form>
# Server-side validation (Flask-WTF example)
from flask_wtf import CSRFProtect
csrf = CSRFProtect(app)
2. Validate the Origin Header
Reject requests whose Origin or Referer header does not match the application's own domain. This is a secondary defence and should not replace CSRF tokens.
3. Use SameSite Cookie Attribute
SameSite=Strict prevents the browser from sending session cookies on cross-origin form submissions, breaking the CSRF delivery mechanism entirely.
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
Key Takeaways
For developers:
- SQL injection in 2025 is entirely preventable. Parameterised queries have been best practice for over two decades. Every ORM, every database driver, every modern framework supports them. There is no excuse for string concatenation in SQL.
- XSS is equally preventable with proper output encoding. Modern frameworks handle this by default. The vulnerabilities arise when developers deliberately bypass the protections using
innerHTML,|safe, ordangerouslySetInnerHTMLwithout need. - CSRF and XSS are a dangerous combination. Either alone is serious. Together, they remove the social engineering friction required for an attacker to deliver a payload. Protect both independently.
- Verbose error messages in production are a free reconnaissance gift to attackers. Turn them off.
For security teams:
- Manual testing by an experienced researcher found five vulnerabilities in under two hours with no automation. Automated scanners alone are not sufficient coverage.
- Defence in depth matters. Parameterised queries combined with a WAF, CSP,
HttpOnlycookies, and CSRF tokens is a layered posture. Any single control can be bypassed; all together they significantly raise the cost of exploitation. - Regular security assessments, not just at launch but on an ongoing basis, are the operational standard for applications handling user data.
For the security community:
- Responsible disclosure is not conditional on reward. Vulnerabilities reported to a VDP protect real users. The decision to report is always the right one.
- Responsible exploitation means retrieving the minimum data necessary to prove impact and stopping there. One row. No enumeration. No extraction. Proof, not plunder.
- Security is a commitment, not a transaction.
About the Researcher
Salman is the founder of XHack, a certified cybersecurity firm providing VAPT, Red Teaming, SOC Services, AI-powered security tooling, and Threat Intelligence. He holds OSCP+ and OSCP certifications, is an active Synack Red Team member, and has conducted security assessments across web, API, and cloud environments.
This case study is published in the spirit of responsible disclosure and community education. The target application and program identity are withheld in accordance with Yogosha's platform disclosure terms.
If your application has never been tested by a human researcher, it has not been tested.
Engagement details
Redacted (via Yogosha VDP)
Bug Bounty / VDP
Manual Web Application Penetration Testing
1 hour 40 minutes
2026
Start your engagement
Get Your Application Tested
XHack delivers the same rigorous methodology behind every case study. Let us pressure-test your defences.
Get Your Application Tested