Une application Web existante est en cours d'exécution sur Tomcat 4.1. Il y a un problème XSS avec une page, mais je ne peux pas modifier le source. J'ai décidé d'écrire un filtre de servlet pour assainir le paramètre avant qu'il ne soit vu par la page.
Je voudrais écrire une classe de filtre comme ceci:
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) {
}
}
Mais ServletRequest.setParameter
n'existe pas.
Comment puis-je modifier la valeur du paramètre de demande avant de transmettre la demande en aval de la chaîne?
Comme vous l'avez noté, HttpServletRequest
n'a pas de méthode setParameter. C'est délibéré, car la classe représente la requête telle qu'elle est venue du client, et modifier le paramètre ne le représenterait pas.
Une solution consiste à utiliser la classe HttpServletRequestWrapper
, qui permet d’envelopper une requête avec une autre. Vous pouvez sous-classer cela et substituer la méthode getParameter
pour renvoyer votre valeur désinfectée. Vous pouvez ensuite transmettre cette demande encapsulée à chain.doFilter
au lieu de la demande d'origine.
C'est un peu moche, mais c'est ce que l'API de servlet vous recommande de faire. Si vous essayez de passer quelque chose d'autre à doFilter
, certains conteneurs de servlets se plaindront de votre violation des spécifications et refuseront de les gérer.
Une solution plus élégante, c'est plus de travail - modifiez le servlet/JSP d'origine qui traite le paramètre, de sorte qu'il attend une requête attribut au lieu d'un paramètre. Le filtre examine le paramètre, l'assainit et définit l'attribut (à l'aide de request.setAttribute
) avec la valeur assainie. Pas de sous-classement, pas de spoofing, mais vous oblige à modifier d'autres parties de votre application.
Pour mémoire, voici le cours auquel j'ai fini par écrire:
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) {
}
}
Ecrivez une classe simple qui sous-analyse HttpServletRequestWrapper
avec une méthode getParameter () qui renvoie la version assainie de l'entrée. Puis passez une instance de votre HttpServletRequestWrapper
à Filter.doChain()
au lieu de l’objet request directement.
C'est ce que j'ai fini par faire
//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();
}
}
}
J'ai eu le même problème (changer un paramètre de la requête HTTP dans le filtre). J'ai fini par utiliser un ThreadLocal<String>
. Dans la Filter
j'ai:
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);
}
}
Dans mon processeur de requête (HttpServlet
, contrôleur JSF ou tout autre processeur de requête HTTP), je récupère la valeur de thread actuelle:
...
String myVariable = MyFilter.THREAD_VARIABLE.get();
...
Avantages:
HttpServletRequestWrapper
passe-partoutrequest.setAttribute(String,Object)
, c'est-à-dire que vous pouvez accéder à la variable dans d'autres filtres.Désavantages:
Java.util.stream.Stream.parallel
, Java.util.concurrent.Future
, Java.lang.Thread
.Quelques notes de côté:
Le serveur dispose d'un pool de threads pour traiter les requêtes HTTP. Puisque c'est la piscine:
if (value!=null) { THREAD_VARIABLE.set(value);}
car vous réutiliserez le valeur de la requête HTTP précédente lorsque value
est null (les effets secondaires sont garantis). HttpSession.setAttribute()
@RequestScoped
utilise en interne une ThreadLocal
, mais l'utilisation de ThreadLocal
est plus polyvalente: vous pouvez l'utiliser dans des conteneurs non JEE/CDI (par exemple, dans des applications JRE multithreads).Essayez request.setAttribute("param",value);
. Cela a bien fonctionné pour moi.
Veuillez trouver cet exemple de code:
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
}
}
Vous pouvez utiliser Expression régulière pour la désinfection. Filtre interne avant d'appeler chain.doFilter (demande, réponse) méthode, appelez ce code . Voici un exemple de code:
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();
}
}