# फ़ील्ड रिपोर्ट — CVE-2026-111497

तीन प्रोडक्शन Linux होस्ट्स पर टूलसेट चलाने के परिणाम, और वे दोष जो इस अभ्यास ने उजागर किए।
होस्ट्स को अनाम रखा गया है: कोई IP पता, hostname या ग्राहक का नाम प्रकाशित नहीं है।

## 1. सार

| होस्ट | OS | भूमिका | संस्थापित (भेद्य) | फ़िक्स | परिणाम |
|---|---|---|---|---|---|
| A | Ubuntu 26.04 LTS | hosting panel, 10 साइटें | `libbrotli1 1.2.0-3build1` | `1.2.0-3ubuntu0.1` (`resolute-security`) | निवारण हुआ, सत्यापित |
| B | Debian 12 (bookworm) | एप्लिकेशन सर्वर, डेस्कटॉप सत्र मौजूद | `libbrotli1 1.0.9-2+b6` | `1.0.9-2+deb12u1` (`bookworm-security`) | निवारण हुआ, सत्यापित |
| C | Ubuntu 24.04 LTS | प्रॉक्सी और ऐप सर्वर, VPN क्लाइंट मौजूद | `libbrotli1 1.1.0-2build2` | `1.1.0-2ubuntu0.1` (`noble-security`) | `VERIFY: PASS`, पूरा चक्र |

तीनों हाइब्रिड होस्ट हैं: एक सर्वर वर्कलोड के साथ ग्राफ़िकल सत्र भी। तीनों के लिए `-security` में
आधिकारिक backport मौजूद था; प्रत्येक संस्थापन से पहले authenticity gate (candidate changelog में
CVE का नाम) पास हुआ।

## 2. ऑडिट ने वास्तव में क्या पाया

### 2.1 डिकोड पथ libcurl था, वेब सर्वर नहीं

होस्ट A पर वेब परत केवल-संपीडन निकली: nginx प्रतिक्रिया संपीडन के लिए `ngx_brotli` का उपयोग
करता था, Apache में `mod_brotli` उपलब्ध पर अक्षम था, और कोई भी घटक अनुरोध निकाय डिकोड नहीं करता
था। इसलिए `Content-Encoding: br` के माध्यम से मुख्य RCE वेक्टर पहुँच से बाहर था।

पहुँचने योग्य पथ PHP से आउटबाउंड HTTP था:

- `wp-includes/Requests/src/Transport/Curl.php` में `CURLOPT_ENCODING` खाली स्ट्रिंग पर सेट है,
  जिससे libcurl वह हर encoding स्वीकार करता है जिसके साथ वह बना — `br` सहित;
- `wp-includes/SimplePie/src/File.php` फ़ीड फ़ेचिंग के लिए वही करता है;
- प्लगइन्स के भीतर भेजी जाने वाली Guzzle factories vendor APIs के लिए वही करती हैं।

सूची में `CURLOPT_ENCODING` या `CURLOPT_ACCEPT_ENCODING` वाली **164 PHP फ़ाइलें** गिनी गईं।
libcurl ने अपने फ़ीचर्स में `brotli/1.2.0` बताया, इसलिए हर `wp_remote_*`, RSS फ़ेच और vendor call
भेद्य डिकोडर के माध्यम से `br` डीकंप्रेस करता था।

### 2.2 ऐसे candidates जो सही दिखते हैं और हैं नहीं

security pocket ताज़ा होने से पहले `libbrotli1` का एकमात्र candidate संस्थापित `1.2.0-3build1`
था — वही upstream रिलीज़, फिर से बनाई गई। `apt-get update` के बाद candidate `1.2.0-3ubuntu0.1`
आया। उस क्षण distribution changelog endpoint ने 404 लौटाया, क्योंकि पैकेज कुछ घंटे पहले ही
प्रकाशित हुआ था, इसलिए अकेला संस्करण-प्रत्यय प्रमाण नहीं था।

प्रमाण पैकेज से ही आया: security `.deb` डाउनलोड करके, उसे extract करके
`usr/share/doc/libbrotli1/changelog.Debian.gz` पढ़ने पर वह प्रविष्टि दिखी जिसमें CVE-2026-111497
का नाम और डिकोडर परिवर्तन का विवरण था। वही जाँच `remediate.sh` का authenticity gate बनी — संस्करण
स्ट्रिंग अपने आप से कभी निर्णय नहीं करती।

### 2.3 ऐसे उपभोक्ता जिन्हें पुनः चालू नहीं करना है

`libbrotlidec` का मैपिंग केवल सर्वरों तक सीमित नहीं है। हाइब्रिड होस्ट्स पर वही लाइब्रेरी डेस्कटॉप
स्टैक द्वारा freetype और fontconfig के माध्यम से मैप होती है: GNOME Shell, Xorg, NetworkManager,
`fwupd`, `packagekitd`, `gsd-*` helpers और VPN क्लाइंट। उन्हें पुनः चालू करने से सत्र और टनल टूट
जाते, जबकि सर्वर आक्रमण-सतह में कोई योगदान नहीं होता।

इसलिए उन्हें `deferred` के रूप में वर्गीकृत किया जाता है: रिपोर्ट उनके नाम लेती है, कोई
पुनः-प्रारंभ नहीं किया जाता, और पैच की गई लाइब्रेरी अगले लॉगिन या रीबूट पर लोड होती है।

### 2.4 पैनल daemons का पुनः-प्रारंभ अलग तंत्र है

होस्ट A पर ISPmanager के दो `core` daemons हर `systemctl` पुनः-प्रारंभ के बाद भी पुराना inode
मैप किए रहे। वे पैनल के अपने launcher द्वारा `systemd-run` transient scopes से शुरू होते हैं,
इसलिए वे वेब सर्वर की संतान नहीं हैं और उनका अपना unit नहीं है। उन्हें PID से kill करने पर पैनल
की स्थिति भ्रष्ट होने का जोखिम है; समर्थित पथ पैनल का नियंत्रण टूल है:

```bash
mgrctl -m core --restart srvparam
mgrctl -m ispmgr --restart srvparam
```

दोनों daemons पैच की गई लाइब्रेरी पर पुनः जन्मे, और stale-inode स्कैन ने फिर शून्य लौटाया।

### 2.5 ऐसी पूर्व-विद्यमान विफलता जिसे परिवर्तन का श्रेय नहीं देना चाहिए

निवारण के तुरंत बाद होस्ट A पर `systemctl --failed` ने `logrotate.service` दिखाया। timestamp से
पता चला कि वह अपने दैनिक timer से किसी असंबंधित साइट कॉन्फ़िगरेशन पर विफल हुआ था (दोहरी log
प्रविष्टियाँ और एक permission error)। उसे दर्ज किया गया और छोड़ दिया गया: असंबंधित मरम्मत को
सुरक्षा परिवर्तन में मिलाने से परिवर्तन असत्यापनीय हो जाता है।

## 3. फ़ील्ड ने जो दोष उजागर किए, और उनके फ़िक्स

नौ दोष एजेंट को चलाकर मिले, उसे पढ़कर नहीं। प्रत्येक पंक्ति अनुभाग 4 के क्रम से दोबारा पैदा की
जा सकती है।

| # | दोष | फ़ील्ड में लक्षण | फ़िक्स |
|---:|---|---|---|
| 1 | जल्दी बाहर निकलने वाली pipelines के साथ `set -o pipefail` | ऑडिट सफल होने पर भी exit 141 (SIGPIPE) | `pipefail` हटाया गया; शुद्धता स्पष्ट जाँचों पर टिकी है |
| 2 | लटकते `-o` के साथ `set -euo` | shell ने अपनी विकल्प-सूची stdout पर उड़ेल दी | `set -eu` |
| 3 | state फ़ाइल को shell की तरह source करना | syntax error: OS pretty-name में कोष्ठक हैं | मान कुंजी से `state_get` द्वारा पढ़ें |
| 4 | `state.after` द्वारा `installed` का पुनर्परिभाषण | before/after तुलना में झूठी `FAIL` | अलग `OLD_` और `AFTER_` चर |
| 5 | cgroup लूप में बिना newline के `printf '%s'` | unit नाम जुड़कर एक बेकार unit बने, इसलिए restart चुपचाप कुछ नहीं करता रहा | `printf '%s\n'` |
| 6 | glob से inode लेना | symlink का inode दर्ज हुआ, इसलिए stale जाँच झूठी सफल हुई | `find -type f -printf '%i'` |
| 7 | `\|\| true` के बिना PID लूप | पैकेज संस्थापित हो जाने के बाद भी `set -e` ने 1 लौटाया | लूप `\|\| true` से सुरक्षित |
| 8 | nested slices और template instances के प्रति अंधा cgroup regex | `revoap-worker@1` पुरानी लाइब्रेरी पर ही रहा | `awk` से सबसे भीतरी `.service` खंड लें |
| 9 | डेस्कटॉप और इन्फ्रास्ट्रक्चर उपभोक्ताओं को भेद्य चिह्नित करना | रिपोर्ट का शोर जो वास्तविक निष्कर्ष छिपा रहा था | `DESKTOP_INFRA_PAT` और `deferred` स्थिति |

इनमें से दो को विस्तार से लिखना ज़रूरी है, क्योंकि दोनों ने *झूठा हरा* संकेत दिया:

- दोष 5 का अर्थ था कि restart अनुभाग चला, कुछ भी चेतावनी-जनक नहीं छापा, और हर सेवा भेद्य
  लाइब्रेरी पर ही छोड़ दी। unit सूची सही बनी थी और फिर एक ही स्ट्रिंग में बदल गई, इसलिए
  `systemctl restart` को एक गैर-विद्यमान unit नाम के साथ बुलाया गया और उसकी विफलता घातक नहीं
  मानी गई।
- दोष 6 का अर्थ था कि "कोई प्रक्रिया पुराना inode मैप नहीं करती" जाँच symlink के inode से तुलना
  कर रही थी, जिसे रचना से ही कोई प्रक्रिया मैप नहीं करती। जाँच ने उस होस्ट पर सफलता बताई जिसे
  पुनः चालू ही नहीं किया गया था।

दोनों में से कोई भी exit code से दिखाई नहीं देता था। दोनों रन के बाद होस्ट पर `/proc/*/maps` से
दावे की क्रॉस-जाँच करने पर मिले।

## 4. जिस क्रम में निष्पादन हुआ

```text
host A  detect -> install -> restart nginx, php-fpm pools, pdns, zabbix-agent, ihttpd
        -> mgrctl --restart core/ispmgr -> stale scan 0 -> 10/10 sites HTTP 200 -> decode round-trip
host B  detect -> remediate (defects 1..5 surfaced) -> restart apache2, php-fpm x2, zabbix x2,
        fastpanel apps, revoap-worker instances -> stale scan: server consumers 0
host C  detect -> remediate (defects 6..9 surfaced) -> verify -> VERIFY: PASS
```

## 5. सत्यापन का प्रमाण

| होस्ट | बाद का पैकेज | पुराने सर्वर मैपिंग | विफल units | कार्यात्मक जाँच |
|---|---|---|---|---|
| A | `1.2.0-3ubuntu0.1` | 0 (पैनल पैच किए गए inode पर पुनः जन्मा) | खाली | nginx ने `content-encoding: br` लौटाया; `curl --compressed` ने वैध HTML दिया; 10 में से 10 साइटें HTTP 200 |
| B | `1.0.9-2+deb12u1` | सर्वर उपभोक्ताओं के लिए 0; डेस्कटॉप स्थगित | खाली | स्थानीय endpoint के माध्यम से डिकोड round-trip |
| C | `1.1.0-2ubuntu0.1` | सर्वर उपभोक्ताओं के लिए 0; डेस्कटॉप और VPN स्थगित | खाली | पूरा चक्र `VERIFY: PASS` पर समाप्त |

होस्ट B और होस्ट C पर `needrestart` पैकेज hook ने संस्थापन के दौरान कुछ सर्वर सेवाएँ स्वयं पुनः
चालू कर दीं; रिपोर्ट उन पुनः-प्रारंभों को hook का बताती है, और स्पष्ट restart चरण के पास उन
units के लिए फिर कुछ शेष नहीं रहा।

## 6. वे सबक जिन्होंने टूल को आकार दिया

1. **संस्करण स्ट्रिंग प्रमाण नहीं है।** gate पैकेजित changelog पढ़ता है, क्योंकि rebuild और
   वास्तविक फ़िक्स संस्करण-फ़ील्ड में तब तक एक जैसे दिखते हैं जब तक revision की जाँच न हो।
2. **पैच की गई फ़ाइल पैच की गई प्रक्रिया नहीं है।** प्रभाव के हर दावे की जाँच
   `/proc/*/maps` में होती है, `dpkg` से कभी अनुमान नहीं लगाया जाता।
3. **चुपचाप कुछ न करना त्रुटि से बुरा है।** दो दोषों ने कुछ न करते हुए "सफलता" उत्पन्न की; अब
   टूल छापता है कि उसने क्या पुनः चालू किया और बाद में inode फिर जाँचता है।
4. **हाइब्रिड होस्ट्स को denylist चाहिए, ढक्कन-पुनः-प्रारंभ नहीं।** सर्वर सतह और डेस्कटॉप सतह
   स्पष्ट रूप से अलग होनी चाहिए, अन्यथा निवारण सत्र ही गिरा देता है।
5. **पैनल सॉफ़्टवेयर का अपना जीवनचक्र है।** transient scopes के रूप में शुरू किए गए daemons
   पैनल के नियंत्रण टूल से पुनः चालू होते हैं, कभी signal से नहीं।
