Изменить параметр запроса с помощью фильтра сервлета
На Tomcat 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
не существует.
Как изменить значение параметра запроса перед передачей запроса по цепочке?
Ответы
Ответ 1
Как вы отметили, HttpServletRequest
не имеет метода setParameter. Это преднамеренно, так как класс представляет запрос, поскольку он пришел от клиента, а изменение параметра не будет представлять это.
Одним из решений является использование класса HttpServletRequestWrapper
, который позволяет обернуть один запрос другим. Вы можете подклассифицировать это и переопределить метод getParameter
, чтобы вернуть ваше дезинфицированное значение. Затем вы можете передать этот завернутый запрос в chain.doFilter
вместо исходного запроса.
Это немного уродливо, но то, что API сервлета говорит, что вы должны делать. Если вы попытаетесь передать что-либо еще в doFilter
, некоторые контейнеры сервлетов будут жаловаться на то, что вы нарушили спецификацию, и откажетесь обработать ее.
Более элегантное решение - это больше работы - измените исходный сервлет /JSP, который обрабатывает параметр, так что он ожидает атрибут запроса вместо параметра. Фильтр проверяет параметр, дезактивирует его и устанавливает атрибут (используя request.setAttribute
) с дезинфицированным значением. Нет подклассов, нет подмены, но вам требуется изменить другие части вашего приложения.
Ответ 2
Для записи, вот класс, который я написал:
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) {
}
}
Ответ 3
Напишите простой класс, который подкартирует HttpServletRequestWrapper
с помощью метода getParameter(), который возвращает санированную версию ввода. Затем передайте экземпляр вашего HttpServletRequestWrapper
в Filter.doChain()
вместо объекта запроса напрямую.
Ответ 4
Попробуйте 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
}
}
Ответ 5
Вы можете использовать Регулярное выражение для санитарии. Внутри фильтра перед вызовом метода 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();
}
}
Ответ 6
У меня была такая же проблема (изменение параметра из 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 для извлечения значения переменной)
- более элегантный, чем шаблон
HttpServletRequestWrapper
- область действия переменной шире, чем просто HTTP-запрос (область, которую вы имеете при выполнении
request.setAttribute(String,Object)
, т.е. вы можете получить доступ к переменной в других фильтрах.
Недостатки:
- Вы можете использовать этот метод только в том случае, если поток, обрабатывающий фильтр, совпадает с потоком, обрабатывающим HTTP-запрос (это относится ко всем известным мне Java-серверам). Следовательно, это не будет работать, когда
- выполнение перенаправления HTTP (поскольку браузер выполняет новый HTTP-запрос, и нет способа гарантировать, что он будет обработан тем же потоком)
- обработка данных в отдельных потоках, например, при использовании
java.util.stream.Stream.parallel
, java.util.concurrent.Future
, java.lang.Thread
.
- Вы должны быть в состоянии изменить обработчик запросов/приложение
Некоторые примечания:
Ответ 7
Это то, что я в итоге делал
//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();
}
}
}