Servlet filtresi ile istek parametresini değiştirin


114

Tomcat 4.1'de mevcut bir web uygulaması çalışıyor. Sayfayla ilgili bir XSS sorunu var, ancak kaynağı değiştiremiyorum. Parametreyi sayfada görünmeden önce temizlemek için bir servlet filtresi yazmaya karar verdim.

Bunun gibi bir Filtre sınıfı yazmak istiyorum:

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) {
  }
}

Ama ServletRequest.setParameteryok.

İsteği zincirden aşağı iletmeden önce istek parametresinin değerini nasıl değiştirebilirim?


HttpServletRequestWrapper'ın tanımlanmış birçok API'si var.Her API'yi anlamlı bir şekilde anlamaya çalışıyorum.Javadoc, 'userinRole', 'getPrincipal'etx gibi API'leri anlamaya yardımcı olmuyor., Tam olarak nereden yardım alabilirim?
sskumar86

Yanıtlar:


132

Sizin de belirttiğiniz HttpServletRequestgibi setParameter yöntemine sahip değil. Bu kasıtlıdır, çünkü sınıf, isteği istemciden geldiği şekliyle temsil eder ve parametrenin değiştirilmesi bunu temsil etmez.

Çözümlerden biri HttpServletRequestWrappersınıfı kullanmaktır , bu da bir isteği diğerine bağlamanıza izin verir. Bunu alt sınıflandırabilir ve getParameterarındırılmış değerinizi döndürmek için yöntemi geçersiz kılabilirsiniz . Daha sonra, bu sarmalanmış isteği chain.doFilterorijinal istek yerine iletebilirsiniz .

Bu biraz çirkin, ancak sunucu uygulamacığı API'nin yapmanız gerektiğini söylediği şey bu. Başka bir şey iletmeye çalışırsanız doFilter, bazı servlet kapsayıcıları, şartnameyi ihlal ettiğinizden şikayet edecek ve bunu işlemeyi reddedecektir.

Daha zarif bir çözüm daha fazla çalışmadır - parametreyi işleyen orijinal sunucu uygulamasını / JSP'yi , bir parametre yerine bir istek özniteliği bekleyecek şekilde değiştirin. Filtre parametreyi inceler, sterilize eder ve özniteliği (kullanarak request.setAttribute) arındırılmış değerle ayarlar . Alt sınıflandırma yok, sahtekarlık yok, ancak uygulamanızın diğer bölümlerini değiştirmenizi gerektiriyor.


6
HttpServletRequestWrapper harika; Var olduğunu hiç bilmiyordum. Teşekkürler!
Jeremy Stein

3
Özellik ayarı alternatifi için teşekkürler! Head First Servlets ve JSP'lerde istek ve yanıt sarmalayıcılarını kullanarak örnek kod gördüm ve spesifikasyonun insanları işleri bu şekilde yapmaya zorladığına inanamadım.
Kevin

Denetleyicideki değerlerime ulaştım ve bu parametreyi (e-posta ve geçiş) ayarladım ... şimdi servlet'imdeki değerleri nasıl değiştirebilirim <property name="username" value="somemail@gmail.com" /> //Change email on logging in <property name="password" value="*********" />//Change Password on logging in
UmaShankar

73

Kayıt için, burada yazdığım ders:

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 yöntemini de hesaba katmanız gerekebilir. Belki de, hiçbir bileşenin yöntemi kullanmaması ve sterilize etme mantığını atlamaması için istisna atabilir ve desteklenmeyen istisna oluşturabilir.
Tom

1
İyi nokta Tom. Bu özel durumda, kontrol ettim ve çağrılmadığını anladım, ancak bunu eksiksizlik ve bir sonraki kişinin iyiliği için eklemeliydim. Teşekkürler!
Jeremy Stein

13
Görünüşe göre sıradaki kişi benim, Jeremy. Bu gönderiyi, harici bir uygulamadan üçüncü taraf bir sunucu uygulamasına geçirilen verileri değiştirmek için seçenekler ararken buldum. Benim durumumda, sunucu uygulaması istek verilerini almak için HTTPServletRequest.getParameter (), getParameterMap () veya getAttribute () 'i çağırmıyordu, bu yüzden deneme yanılma yoluyla sunucu uygulamasının HTTPServletRequest.getInputStream ()' i çağırdığını belirledim ve getQueryString (). Kapalı sunucu uygulamaları için bu görevi deneyen herkese tavsiyem, gerçekte neler olup bittiğini anlamak için HTTPServletRequest'teki her bir erişimciyi paketlemektir
Fred Sobotka

3
SrpingMVC için, Spring'i kandırmak için getParameterValues ​​() değerini geçersiz kılmanız gerekir. RequestParamMethodArgumentResolver.resovleName () bu yöntemi kullanır, böylece MissingServletRequestParameterException'ı geçersiz kılmadan alırsınız. Yaylı ağ 4.1.7 ile Spring Boot 1.2.6 üzerinde test edilmiştir.
barryku

10

Girişin HttpServletRequestWrappersterilize edilmiş sürümünü döndüren getParameter () yöntemiyle alt aramalar yapan basit bir sınıf yazın . Sonra bir örneğini geçmesi HttpServletRequestWrapperiçin Filter.doChain()yerine doğrudan istek nesnesinin.


1

Aynı sorunu yaşadım (Filtredeki HTTP isteğinden bir parametreyi değiştirerek). Kullanarak bitirdim ThreadLocal<String>. In Filterben vardır:

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);
    }
}

İstek işlemcimde ( HttpServletJSF denetleyicisi veya başka herhangi bir HTTP istek işlemcisi) mevcut iş parçacığı değerini geri alıyorum:

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

Avantajları:

  • HTTP parametrelerini iletmekten daha çok yönlü (POJO nesnelerini iletebilirsiniz)
  • biraz daha hızlı (değişken değerini çıkarmak için URL'yi ayrıştırmaya gerek yoktur)
  • thant daha şık HttpServletRequestWrapperboilerplate
  • değişken kapsamı, HTTP isteğinden daha geniştir (bunu yaparken sahip olduğunuz kapsam request.setAttribute(String,Object), yani değişkene diğer filtrelerden erişebilirsiniz.

Dezavantajları:

  • Bu yöntemi yalnızca filtreyi işleyen iş parçacığı HTTP isteğini işleyen iş parçacığı ile aynı olduğunda kullanabilirsiniz (bu, bildiğim tüm Java tabanlı sunucularda geçerlidir). Sonuç olarak, bu ne zaman işe yaramayacak
    • HTTP yeniden yönlendirmesi yapmak (çünkü tarayıcı yeni bir HTTP isteği yapar ve aynı iş parçacığı tarafından işleneceğini garanti etmenin bir yolu yoktur)
    • Ayrı konuda verilerin işlenmesi kullanıldığında, örneğin java.util.stream.Stream.parallel, java.util.concurrent.Future, java.lang.Thread.
  • Talep işlemcisini / uygulamasını değiştirebilmelisiniz

Bazı yan notlar:

  • Sunucu, HTTP isteklerini işlemek için bir İş Parçacığı havuzuna sahiptir. Bu havuz olduğu için:

    1. Bu iş parçacığı havuzundaki bir iş parçacığı birçok HTTP isteğini işleyecektir, ancak her seferinde yalnızca bir tane (bu nedenle, değişkeninizi kullanımdan sonra temizlemeniz veya her HTTP isteği için tanımlamanız gerekir = if (value!=null) { THREAD_VARIABLE.set(value);}değeri yeniden kullanacağınız için olduğu gibi koda dikkat edin valuenull olduğunda önceki HTTP isteğinden : yan etkiler garantilidir).
    2. İki isteğin aynı iş parçacığı tarafından işleneceğine dair bir garanti yoktur (böyle olabilir, ancak garantiniz yoktur). Kullanıcı verilerini bir istekten diğerine saklamanız gerekiyorsa, kullanmak daha iyi olurHttpSession.setAttribute()
  • JEE @RequestScopeddahili olarak a kullanır ThreadLocal, ancak ThreadLocalkullanımı daha çok yönlüdür: JEE / CDI olmayan kaplarda kullanabilirsiniz (örneğin, çok iş parçacıklı JRE uygulamalarında)

İş parçacığının kapsamında bir parametre ayarlamak gerçekten iyi bir fikir mi? Birden fazla istek aynı diziyi görecek mi? (
Zachary Craig

Bu iyi bir fikir mi = evet (ama ne yaptığınızı bilmeniz gerekiyor, zaten JEE @RequestScopeddahili olarak aynı şeyi yapıyor). Birden fazla istek aynı iş parçacığını görecek mi = hayır (veya en azından garantiniz yok). Bu noktaları kesinleştirmek için cevabı düzenledim.
Julien Kronegg

1

Sonunda yaptığım şey bu

//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

Buradaki tüm sözlerinize dayanarak, benim için işe yarayan teklifim:

 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");
        }

    }

not: queryString (), her KEY için TÜM değerleri işlemeyi gerektirir ve gerekirse kendi param değerlerinizi eklerken encodeUrl () 'yi unutmayın

Sınırlama olarak, request.getParameterMap () veya request.getReader () 'i çağıracak herhangi bir yöntemi çağırır ve okumaya başlarsanız, request.setCharacterEncoding (...)


0

Temizlik için Normal İfade kullanabilirsiniz . Chain.doFilter (istek, yanıt) yöntemini çağırmadan önce filtre içinde bu kodu çağırın. İşte Örnek Kod:

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
Orijinal istek parametrelerini bu şekilde değil, bir kopya üzerinde değiştirirsiniz.
Mehdi

-1

Deneyin request.setAttribute("param",value);. Benim için iyi çalıştı.

Lütfen bu kod örneğini bulun:

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
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.