सर्वलेट फ़िल्टर के साथ अनुरोध पैरामीटर संशोधित करें


114

एक मौजूदा वेब एप्लिकेशन टॉमकैट 4.1 पर चल रहा है। पृष्ठ के साथ एक XSS समस्या है, लेकिन मैं स्रोत को संशोधित नहीं कर सकता। मैंने पृष्ठ द्वारा देखे जाने से पहले पैरामीटर को साफ करने के लिए एक सर्वलेट फ़िल्टर लिखने का निर्णय लिया है।

मैं इस तरह से एक फ़िल्टर वर्ग लिखना चाहूंगा:

import java.io.*;
import javax.servlet.*;

public final class XssFilter implements Filter {

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException
  {
    String badValue = request.getParameter("dangerousParamName");
    String goodValue = sanitize(badValue);
    request.setParameter("dangerousParamName", goodValue);
    chain.doFilter(request, response);
  }

  public void destroy() {
  }

  public void init(FilterConfig filterConfig) {
  }
}

लेकिन ServletRequest.setParameterमौजूद नहीं है।

श्रृंखला के नीचे अनुरोध पारित करने से पहले मैं अनुरोध पैरामीटर के मूल्य को कैसे बदल सकता हूं?


HttpServletRequestWrapper में बहुत सारे API परिभाषित हैं। मैं प्रत्येक API को सार्थक रूप से समझने की कोशिश कर रहा हूँ। Javadoc 'userinRole', 'getPrincipal'xx जैसे API को समझने में मदद नहीं कर रहा है, जहाँ वास्तव में मुझे कुछ मदद मिल सकती है?
sskumar86

जवाबों:


132

जैसा कि आपने नोट किया HttpServletRequestहै एक सेटपैरमीटर विधि नहीं है। यह जानबूझकर किया गया है, क्योंकि वर्ग ग्राहक से आए अनुरोध का प्रतिनिधित्व करता है, और पैरामीटर को संशोधित करने से वह प्रतिनिधित्व नहीं करेगा।

एक समाधान HttpServletRequestWrapperवर्ग का उपयोग करना है , जो आपको एक अनुरोध को दूसरे के साथ लपेटने की अनुमति देता है। आप उसे उप-वर्गित कर सकते हैं, और getParameterअपने स्वीकृत मूल्य को वापस करने की विधि को ओवरराइड कर सकते हैं । आप chain.doFilterमूल अनुरोध के बजाय उस लिपटे अनुरोध को पारित कर सकते हैं।

यह थोड़ा बदसूरत है, लेकिन यह वही है जो सर्वलेट एपीआई कहता है कि आपको करना चाहिए। यदि आप कुछ और पास करने की कोशिश करते हैं doFilter, तो कुछ सर्वलेट कंटेनर शिकायत करेंगे कि आपने कल्पना का उल्लंघन किया है, और इसे संभालने से इनकार कर देंगे।

एक अधिक सुरुचिपूर्ण समाधान अधिक काम है - पैरामीटर को संसाधित करने वाले मूल सर्वलेट / जेएसपी को संशोधित करें, ताकि यह एक पैरामीटर के बजाय एक अनुरोध विशेषता की अपेक्षा करे । फ़िल्टर पैरामीटर की जांच करता है, इसे सैनिटाइज़ करता है, और request.setAttributeस्वच्छता मान के साथ विशेषता (उपयोग ) सेट करता है । कोई उपवर्ग, कोई स्पूफिंग नहीं है, लेकिन आपको अपने आवेदन के अन्य भागों को संशोधित करने की आवश्यकता है।


6
HttpServletRequestWrapper अद्भुत है; मैं कभी नहीं जानता था कि यह अस्तित्व में है। धन्यवाद!
जेरेमी स्टीन

3
विशेषता सेटिंग विकल्प के लिए धन्यवाद! सिर फर्स्ट सर्वलेट्स और जेएसपी में अनुरोध और प्रतिक्रिया आवरणों का उपयोग करके देखा गया नमूना कोड और विश्वास नहीं कर सकता कि इस तरह से चीजों के बारे में जाने के लिए लोगों को कल्पना धक्का देती है।
केविन

मैं कंट्रोलर में अपने मूल्यों के साथ पहुँच गया था और मैंने tha पैरामीटर (ईमेल और पास) सेट कर दिया है ... अब मैं अपने सर्वलेट में मूल्यों को कैसे बदल सकता हूँ <property name="username" value="somemail@gmail.com" /> //Change email on logging in <property name="password" value="*********" />//Change Password on logging in
उमाशंकर

73

रिकॉर्ड के लिए, यहाँ वह वर्ग है जिसे मैंने लिखा था:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public final class XssFilter implements Filter {

    static class FilteredRequest extends HttpServletRequestWrapper {

        /* These are the characters allowed by the Javascript validation */
        static String allowedChars = "+-0123456789#*";

        public FilteredRequest(ServletRequest request) {
            super((HttpServletRequest)request);
        }

        public String sanitize(String input) {
            String result = "";
            for (int i = 0; i < input.length(); i++) {
                if (allowedChars.indexOf(input.charAt(i)) >= 0) {
                    result += input.charAt(i);
                }
            }
            return result;
        }

        public String getParameter(String paramName) {
            String value = super.getParameter(paramName);
            if ("dangerousParamName".equals(paramName)) {
                value = sanitize(value);
            }
            return value;
        }

        public String[] getParameterValues(String paramName) {
            String values[] = super.getParameterValues(paramName);
            if ("dangerousParamName".equals(paramName)) {
                for (int index = 0; index < values.length; index++) {
                    values[index] = sanitize(values[index]);
                }
            }
            return values;
        }
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new FilteredRequest(request), response);
    }

    public void destroy() {
    }

    public void init(FilterConfig filterConfig) {
    }
}

5
आपको getParameterMap विधि के लिए भी खाते की आवश्यकता हो सकती है। शायद फेंक और असमर्थित अपवाद सिर्फ इसलिए कोई घटक विधि का उपयोग नहीं करता है और तर्क को छोड़ दें।
टॉम

1
अच्छा बिंदु, टॉम। इस विशेष मामले में, मैंने जाँच की और पाया कि इसे कॉल नहीं किया जा रहा था, लेकिन मुझे इसे पूर्णता के लिए और अगले व्यक्ति के लिए जोड़ना चाहिए था। धन्यवाद!
जेरेमी स्टीन

13
ऐसा लगता है कि मैं अगले व्यक्ति, जेरेमी हूँ। मुझे यह पोस्ट तब मिली जब एक बाहरी एप्लिकेशन से डेटा को संशोधित करने के विकल्पों को तीसरे पक्ष के सर्वलेट में खोजा जा रहा था। मेरे मामले में, सर्वलेट अनुरोध डेटा प्राप्त करने के लिए HTTPServletRequest.getParameter (), getParameterMap (), या यहां तक ​​कि getAttribute () को कॉल नहीं कर रहा था, इसलिए, परीक्षण और त्रुटि के माध्यम से, मैंने निर्धारित किया कि सर्वलेट HTTPServletRequest.getInputStream () को कॉल कर रहा था और getQueryString ()। बंद सर्वलेट के लिए इस कार्य के लिए मेरी सलाह किसी को भी HTTPServletletRequest में हर एक एक्सेसर को लपेटने के लिए यह समझने के लिए है कि वास्तव में क्या चल रहा है
फ्रेड सोबोटका

3
SrpingMVC के लिए, आपको स्प्रिंग को मूर्ख बनाने के लिए getParameterValues ​​() को ओवरराइड करना होगा। RequestParamMethodArgumentResolver.resovleName () उस पद्धति का उपयोग करता है, इसलिए आपको ओवरराइडिंग के बिना MissingServletRequestParameterException मिल जाएगी। स्प्रिंग-वेब 4.1.7 के साथ स्प्रिंग बूट 1.2.6 पर परीक्षण किया गया।
बैरिकू

10

एक सरल वर्ग लिखें जो HttpServletRequestWrapperएक getParameter () विधि के साथ उपकुंजित होता है जो इनपुट के संचित संस्करण को लौटाता है। फिर अपने का एक उदाहरण से पारित HttpServletRequestWrapperकरने के लिए Filter.doChain()सीधे अनुरोध वस्तु के स्थान पर।


1

मुझे एक ही समस्या थी (फ़िल्टर में HTTP अनुरोध से एक पैरामीटर बदलना)। मैं एक का उपयोग करके समाप्त हो गया ThreadLocal<String>। में Filterमेरे पास है:

class MyFilter extends Filter {
    public static final ThreadLocal<String> THREAD_VARIABLE = new ThreadLocal<>();
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        THREAD_VARIABLE.set("myVariableValue");
        chain.doFilter(request, response);
    }
}

मेरे अनुरोध प्रोसेसर ( HttpServlet, JSF नियंत्रक या किसी अन्य HTTP अनुरोध प्रोसेसर) में, मुझे वर्तमान थ्रेड मान वापस मिलता है:

...
String myVariable = MyFilter.THREAD_VARIABLE.get();
...

लाभ:

  • HTTP पैरामीटर्स को पार करने से ज्यादा बहुमुखी (आप POJO ऑब्जेक्ट्स को पास कर सकते हैं)
  • थोड़ा तेज़ (चर मान निकालने के लिए URL पार्स करने की आवश्यकता नहीं)
  • thant अधिक सुरुचिपूर्ण HttpServletRequestWrapperबॉयलरप्लेट
  • चर का दायरा सिर्फ HTTP अनुरोध (जब request.setAttribute(String,Object)आप कर रहे हैं , तो गुंजाइश है) की तुलना में व्यापक है , यानी आप चर को अन्य फाइलर में एक्सेस कर सकते हैं।

नुकसान:

  • आप इस विधि का उपयोग केवल तभी कर सकते हैं जब थ्रेड जो प्रक्रिया को फ़िल्टर करता है वह वही है जो HTTP अनुरोध को संसाधित करता है (यह सभी जावा-आधारित सर्वरों में मुझे पता है)। नतीजतन, यह कब काम नहीं करेगा
    • HTTP रीडायरेक्ट करना (क्योंकि ब्राउज़र एक नया HTTP अनुरोध करता है और यह गारंटी देने का कोई तरीका नहीं है कि यह उसी थ्रेड द्वारा संसाधित किया जाएगा)
    • प्रसंस्करण अलग धागे में डेटा का उपयोग करते समय, जैसे java.util.stream.Stream.parallel, java.util.concurrent.Future, java.lang.Thread
  • आपको अनुरोध प्रोसेसर / आवेदन को संशोधित करने में सक्षम होना चाहिए

कुछ साइड नोट्स:

  • HTTP अनुरोधों को संसाधित करने के लिए सर्वर में थ्रेड पूल है। चूंकि यह पूल है:

    1. इस थ्रेड पूल से एक धागा कई HTTP अनुरोधों को संसाधित करेगा, लेकिन एक समय में केवल एक (इसलिए आपको उपयोग के बाद चर को साफ करने या प्रत्येक HTTP अनुरोध के लिए इसे परिभाषित करने की आवश्यकता है = कोड पर ध्यान दें जैसे if (value!=null) { THREAD_VARIABLE.set(value);}कि आप मूल्य का पुन: उपयोग करेंगे पिछले HTTP अनुरोध से जब valueशून्य है: साइड इफेक्ट्स की गारंटी है)।
    2. इस बात की कोई गारंटी नहीं है कि दो अनुरोधों को एक ही थ्रेड द्वारा संसाधित किया जाएगा (यह मामला हो सकता है लेकिन आपके पास कोई गारंटी नहीं है)। यदि आपको उपयोगकर्ता डेटा को एक अनुरोध से दूसरे अनुरोध पर रखने की आवश्यकता है, तो इसका उपयोग करना बेहतर होगाHttpSession.setAttribute()
  • जेईई @RequestScopedआंतरिक रूप से एक का उपयोग करता है ThreadLocal, लेकिन ThreadLocalअधिक बहुमुखी का उपयोग कर रहा है: आप इसे गैर जेईई / सीडीआई कंटेनरों में उपयोग कर सकते हैं (जैसे मल्टीथ्रेडेड जेआर अनुप्रयोगों में)

क्या धागे के दायरे में पैरामीटर सेट करना वास्तव में एक अच्छा विचार है? क्या एकाधिक अनुरोध कभी एक ही धागा देखेंगे? (मुझे नहीं लगता)
ज़ाचरी क्रेग

क्या यह एक अच्छा विचार है = हाँ (लेकिन आपको यह जानना होगा कि आप क्या कर रहे हैं, वैसे भी जेईई @RequestScopedआंतरिक रूप से समान है)। क्या एकाधिक अनुरोध एक ही धागा = नहीं (या कम से कम आपके पास कोई गारंटी नहीं है) देखेंगे। मैंने इन बिंदुओं का सटीक उत्तर दिया है।
जूलियन क्रोनग

1

यह वही है जो मैंने किया

//import ../../Constants;

public class RequestFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        try {
            CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest((HttpServletRequest) servletRequest);
            filterChain.doFilter(customHttpServletRequest, servletResponse);
        } finally {
            //do something here
        }
    }



    @Override
    public void destroy() {

    }

     public static Map<String, String[]> ADMIN_QUERY_PARAMS = new HashMap<String, String[]>() {
        {
            put("diagnostics", new String[]{"false"});
            put("skipCache", new String[]{"false"});
        }
    };

    /*
        This is a custom wrapper over the `HttpServletRequestWrapper` which 
        overrides the various header getter methods and query param getter methods.
        Changes to the request pojo are
        => A custom header is added whose value is a unique id
        => Admin query params are set to default values in the url
    */
    private class CustomHttpServletRequest extends HttpServletRequestWrapper {
        public CustomHttpServletRequest(HttpServletRequest request) {
            super(request);
            //create custom id (to be returned) when the value for a
            //particular header is asked for
            internalRequestId = RandomStringUtils.random(10, true, true) + "-local";
        }

        public String getHeader(String name) {
            String value = super.getHeader(name);
            if(Strings.isNullOrEmpty(value) && isRequestIdHeaderName(name)) {
                value = internalRequestId;
            }
            return value;
        }

        private boolean isRequestIdHeaderName(String name) {
            return Constants.RID_HEADER.equalsIgnoreCase(name) || Constants.X_REQUEST_ID_HEADER.equalsIgnoreCase(name);
        }

        public Enumeration<String> getHeaders(String name) {
            List<String> values = Collections.list(super.getHeaders(name));
            if(values.size()==0 && isRequestIdHeaderName(name)) {
                values.add(internalRequestId);
            }
            return Collections.enumeration(values);
        }

        public Enumeration<String> getHeaderNames() {
            List<String> names = Collections.list(super.getHeaderNames());
            names.add(Constants.RID_HEADER);
            names.add(Constants.X_REQUEST_ID_HEADER);
            return Collections.enumeration(names);
        }

        public String getParameter(String name) {
            if (ADMIN_QUERY_PARAMS.get(name) != null) {
                return ADMIN_QUERY_PARAMS.get(name)[0];
            }
            return super.getParameter(name);
        }

        public Map<String, String[]> getParameterMap() {
            Map<String, String[]> paramsMap = new HashMap<>(super.getParameterMap());
            for (String paramName : ADMIN_QUERY_PARAMS.keySet()) {
                if (paramsMap.get(paramName) != null) {
                    paramsMap.put(paramName, ADMIN_QUERY_PARAMS.get(paramName));
                }
            }
            return paramsMap;
        }

        public String[] getParameterValues(String name) {
            if (ADMIN_QUERY_PARAMS.get(name) != null) {
                return ADMIN_QUERY_PARAMS.get(name);
            }
            return super.getParameterValues(name);
        }

        public String getQueryString() {
            Map<String, String[]> map = getParameterMap();
            StringBuilder builder = new StringBuilder();
            for (String param: map.keySet()) {
                for (String value: map.get(param)) {
                    builder.append(param).append("=").append(value).append("&");
                }
            }
            builder.deleteCharAt(builder.length() - 1);
            return builder.toString();
        }
    }
}

1

यहाँ आपकी सभी टिप्पणियों के आधार पर मेरा प्रस्ताव है कि मेरे लिए काम किया है:

 private final class CustomHttpServletRequest extends HttpServletRequestWrapper {

    private final Map<String, String[]> queryParameterMap;
    private final Charset requestEncoding;

    public CustomHttpServletRequest(HttpServletRequest request) {
        super(request);
        queryParameterMap = getCommonQueryParamFromLegacy(request.getParameterMap());

        String encoding = request.getCharacterEncoding();
        requestEncoding = (encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8);
    }

    private final Map<String, String[]> getCommonQueryParamFromLegacy(Map<String, String[]> paramMap) {
        Objects.requireNonNull(paramMap);

        Map<String, String[]> commonQueryParamMap = new LinkedHashMap<>(paramMap);

        commonQueryParamMap.put(CommonQueryParams.PATIENT_ID, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_ID)[0] });
        commonQueryParamMap.put(CommonQueryParams.PATIENT_BIRTHDATE, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_BIRTHDATE)[0] });
        commonQueryParamMap.put(CommonQueryParams.KEYWORDS, new String[] { paramMap.get(LEGACY_PARAM_STUDYTYPE)[0] });

        String lowerDateTime = null;
        String upperDateTime = null;

        try {
            String studyDateTime = new SimpleDateFormat("yyyy-MM-dd").format(new SimpleDateFormat("dd-MM-yyyy").parse(paramMap.get(LEGACY_PARAM_STUDY_DATE_TIME)[0]));

            lowerDateTime = studyDateTime + "T23:59:59";
            upperDateTime = studyDateTime + "T00:00:00";

        } catch (ParseException e) {
            LOGGER.error("Can't parse StudyDate from query parameters : {}", e.getLocalizedMessage());
        }

        commonQueryParamMap.put(CommonQueryParams.LOWER_DATETIME, new String[] { lowerDateTime });
        commonQueryParamMap.put(CommonQueryParams.UPPER_DATETIME, new String[] { upperDateTime });

        legacyQueryParams.forEach(commonQueryParamMap::remove);
        return Collections.unmodifiableMap(commonQueryParamMap);

    }

    @Override
    public String getParameter(String name) {
        String[] params = queryParameterMap.get(name);
        return params != null ? params[0] : null;
    }

    @Override
    public String[] getParameterValues(String name) {
        return queryParameterMap.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
            return queryParameterMap; // unmodifiable to uphold the interface contract.
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(queryParameterMap.keySet());
        }

        @Override
        public String getQueryString() {
            // @see : https://stackoverflow.com/a/35831692/9869013
            // return queryParameterMap.entrySet().stream().flatMap(entry -> Stream.of(entry.getValue()).map(value -> entry.getKey() + "=" + value)).collect(Collectors.joining("&")); // without encoding !!
            return queryParameterMap.entrySet().stream().flatMap(entry -> encodeMultiParameter(entry.getKey(), entry.getValue(), requestEncoding)).collect(Collectors.joining("&"));
        }

        private Stream<String> encodeMultiParameter(String key, String[] values, Charset encoding) {
            return Stream.of(values).map(value -> encodeSingleParameter(key, value, encoding));
        }

        private String encodeSingleParameter(String key, String value, Charset encoding) {
            return urlEncode(key, encoding) + "=" + urlEncode(value, encoding);
        }

        private String urlEncode(String value, Charset encoding) {
            try {
                return URLEncoder.encode(value, encoding.name());
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException("Cannot url encode " + value, e);
            }
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            throw new UnsupportedOperationException("getInputStream() is not implemented in this " + CustomHttpServletRequest.class.getSimpleName() + " wrapper");
        }

    }

नोट: queryString () को प्रत्येक कुंजी के लिए सभी मानों को संसाधित करने की आवश्यकता होती है और यदि आवश्यक हो तो अपने स्वयं के मानों को जोड़ते समय encodeUrl () को मत भूलना

एक सीमा के रूप में, यदि आप request.getParameterMap () या किसी भी विधि से कॉल करेंगे जो request.getReader () को कॉल करेगी और पढ़ना शुरू करेगी, तो आप request.setCharacterEncoding (...) को किसी भी अन्य कॉल को रोकेंगे।


0

आप नियमितीकरण के लिए नियमित अभिव्यक्ति का उपयोग कर सकते हैं । कॉल करने से पहले फिल्टर के अंदर chain.doFilter (अनुरोध, प्रतिक्रिया) विधि, इस कोड को कहते हैं। यहाँ नमूना कोड है:

for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) {
String name = (String)en.nextElement();
String values[] = request.getParameterValues(name);
int n = values.length;
    for(int i=0; i < n; i++) {
     values[i] = values[i].replaceAll("[^\\dA-Za-z ]","").replaceAll("\\s+","+").trim();   
    }
}

1
आप इस तरह से मूल अनुरोध पैरामीटर को संशोधित नहीं करते हैं, लेकिन एक प्रतिलिपि पर।
मेहदी

-1

कोशिश करो request.setAttribute("param",value);। इसने मेरे लिए अच्छा काम किया।

कृपया इस कोड का नमूना देखें:

private void sanitizePrice(ServletRequest request){
        if(request.getParameterValues ("price") !=  null){
            String price[] = request.getParameterValues ("price");

            for(int i=0;i<price.length;i++){
                price[i] = price[i].replaceAll("[^\\dA-Za-z0-9- ]", "").trim();
                System.out.println(price[i]);
            }
            request.setAttribute("price", price);
            //request.getParameter("numOfBooks").re
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.