FlawOpen

flawopen.com/Incidents/equifax-struts-ognl-rce-july-2017

● Equifax · CVE-2017-5638 · July 2017 · CVSS 9.8 · अति गंभीर
सुरक्षा अनुसंधान · FlawOpen

Equifax: करीब 15 करोड़ लोगों का डेटा प्रभावित

CVE-2017-5638 ने Equifax के बिना पैच वाले Struts पोर्टल में Content-Type हेडर के ज़रिए OGNL injection संभव बनाया। हमला लगभग 76 दिन चला और करीब 15 करोड़ लोगों का डेटा प्रभावित हुआ।[1], [4]

💡 आसान भाषा में (ELI5)

कल्पना करें कि डाक-प्रणाली अस्वीकृत शिपिंग लेबल को त्रुटि-संदेश में जोड़ देती है। यदि संदेश लेबल को चलने वाले निर्देश-टेम्पलेट की तरह समझे, तो भेजने वाला केवल गलत पते की सूचना देने के बजाय प्रणाली से निर्देश चलवा सकता है। Struts का पैच पहले से उपलब्ध था, लेकिन Equifax की सूचना प्रशासकों की पुरानी सूची पर भेजी गई और नेटवर्क स्कैन पोर्टल की कमज़ोरी का पता नहीं लगा पाया।[1], [4]

इस पेज के मुख्य शब्द

OGNL
Object-Graph Navigation Language, वह एक्सप्रेशन भाषा जिससे Struts Java ऑब्जेक्ट की प्रॉपर्टी पढ़ता और बदलता है। हमलावर के टेक्स्ट को OGNL की तरह चलाना, हमलावर का कोड चलाने जैसा है।[1], [6]
Jakarta मल्टीपार्ट पार्सर
Struts का वह घटक जो फ़ाइल अपलोड संभालता है। ग़लत Content-Type अस्वीकार करते समय वह एक त्रुटि संदेश बनाता था, जिसे बाद में चलाया जाता था।[1], [3], [2]
एक्सप्रेशन इंजेक्शन (CWE-917)
अविश्वसनीय इनपुट ऐसे एक्सप्रेशन के भीतर पहुँच जाता है जिसे कोई इंटरप्रेटर चलाता है, और डेटा निर्देश बन जाता है।[6]
एसेट इन्वेंटरी
किस सिस्टम पर कौन-सा सॉफ़्टवेयर और कौन-सा संस्करण चल रहा है, इसका रिकॉर्ड। इसके बिना पैच की सूचना को उन मशीनों से नहीं जोड़ा जा सकता जिन्हें उसकी ज़रूरत है।[4]

फ्रेमवर्क मैपिंग

घटना का समयक्रम (Timeline)

2017-03-07

Apache सुरक्षा बुलेटिन S2-045 और सुधरे हुए Struts संस्करण 2.3.32 व 2.5.10.1 जारी करता है।[1]

2017-05-13

Equifax ने बाद में GAO को बताया कि 13 मई को एक अलग घुसपैठ शुरू हुई और हमलावरों ने तब व्यक्तिगत डेटा निकालना शुरू किया; इससे पहले अनधिकृत पहुँच हुई थी, लेकिन उस समय डेटा नहीं लिया गया था।[4]

2017-07-29

ट्रैफ़िक जाँचने वाले उपकरण पर एक्सपायर हो चुका सर्टिफ़िकेट नवीनीकृत करने के बाद Equifax को संदिग्ध ट्रैफ़िक दिखता है।[4]

2017-09-07

Equifax सार्वजनिक रूप से सेंधमारी की घोषणा करता है। GAO ने बाद में बताया कि अमेरिका में कम-से-कम 14.55 करोड़ और देश के बाहर लगभग 10 लाख लोगों का डेटा प्रभावित हुआ।[4]

हमले का चरण-दर-चरण प्रवाह

चरण 1

गढ़ा हुआ Content-Type हेडर

हमलावर एक अपलोड अनुरोध भेजता है जिसके Content-Type हेडर में मीडिया टाइप की जगह OGNL एक्सप्रेशन होता है।[1]

चरण 2

त्रुटि संदेश में हेडर आ जाता है

Jakarta पार्सर हेडर को अस्वीकार करता है और एक स्थानीयकृत त्रुटि संदेश बनाता है जिसमें हेडर का मूल टेक्स्ट शामिल होता है।[1], [3], [2]

चरण 3

संदेश चलाया जाता है

Struts उस संदेश के भीतर के एक्सप्रेशन चलाता है, इसलिए हमलावर का OGNL वेब एप्लिकेशन के विशेषाधिकारों के साथ Java कोड की तरह चलता है।[1], [3], [2], [6]

चरण 4

धीमा, चुपचाप डेटा बाहर जाना

हमलावरों ने डेटा भंडार में मिले क्रेडेंशियल से पोर्टल के तीन संबंधित डेटाबेस से आगे 48 अन्य असंबद्ध डेटाबेस तक पहुँच बनाई। GAO के अनुसार उन्होंने लगभग 9,000 क्वेरी चलाईं और हमला करीब 76 दिन चला; एक्सपायर प्रमाणपत्र के कारण एन्क्रिप्टेड ट्रैफ़िक की जाँच नहीं हो सकी।[4]

सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन

यह उदाहरण के लिए Java कोड है, Struts का सोर्स कोड नहीं; 2.3.32 और 2.5.10.1 में असली सुधार ने मल्टीपार्ट त्रुटि बनाने का तरीक़ा बदला था। मज़बूत संस्करण एक तय संदेश कुंजी इस्तेमाल करता है और हेडर को केवल स्थितीय तर्क के रूप में देता है, इसलिए वह टेक्स्ट की तरह फ़ॉर्मेट होता है और कभी एक्सप्रेशन का हिस्सा नहीं बनता। यह मीडिया टाइप जैसा न दिखने वाले किसी भी मान को प्लेसहोल्डर से बदल देता है, ताकि भविष्य में संदेश बदलने पर भी हेडर किसी चलाई जाने वाली स्ट्रिंग में न लौट सके।[3], [2], [6]

कमज़ोर कार्यान्वयन
import com.opensymphony.xwork2.TextProvider;
import javax.servlet.http.HttpServletRequest;

public final class UploadErrors {
    private final TextProvider text;   // resolves messages and evaluates expressions in them

    public UploadErrors(TextProvider text) {
        this.text = text;
    }

    public String describe(HttpServletRequest request) {
        String contentType = request.getHeader("Content-Type");
        // कच्चा header evaluate होने वाले message का हिस्सा बन जाता है।
        return text.getText("Invalid content type: " + contentType);
    }
}
सुरक्षित और सुदृढ़ फ़िक्स
import com.opensymphony.xwork2.TextProvider;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;

public final class UploadErrors {
    private static final Pattern MEDIA_TYPE = Pattern.compile(
        "^[a-z]+/[a-z0-9.+-]+(;\\s*[a-z0-9-]+=[a-z0-9._-]+)*$");
    private final TextProvider text;

    public UploadErrors(TextProvider text) {
        this.text = text;
    }

    public String describe(HttpServletRequest request) {
        String contentType = request.getHeader("Content-Type");
        boolean wellFormed = contentType != null && contentType.length() <= 256
            && MEDIA_TYPE.matcher(contentType.toLowerCase(Locale.ROOT)).matches();
        // एक fixed message key रखें; header केवल positional argument के रूप में दिया जाए,
        // और वह भी तभी जब उसका रूप media type जैसा हो।
        return text.getText("upload.invalid.content.type",
            new String[] { wellFormed ? contentType : "(malformed)" });
    }
}

इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट

स्रोत