Eine vorhandene Webanwendung läuft auf Tomcat 4.1. Es gibt ein XSS-Problem mit einer Seite, aber ich kann die Quelle nicht ändern. Ich habe mich entschlossen, einen Servlet-Filter zu schreiben, um den Parameter zu bereinigen, bevor er von der Seite gesehen wird.
Ich möchte so eine Filterklasse schreiben:
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
existiert jedoch nicht.
Wie kann ich den Wert des Anforderungsparameters ändern, bevor die Anforderung in der Kette weitergegeben wird?
Wie bereits erwähnt, hat HttpServletRequest
keine setParameter-Methode. Dies ist absichtlich, da die Klasse die Anforderung so darstellt, wie sie vom Client stammt, und das Ändern des Parameters würde dies nicht darstellen.
Eine Lösung ist die Verwendung der HttpServletRequestWrapper
-Klasse, mit der Sie eine Anforderung mit einer anderen zusammenfassen können. Sie können dies subclassieren und die getParameter
-Methode überschreiben, um Ihren bereinigten Wert zurückzugeben. Sie können diese umschlossene Anforderung dann anstelle der ursprünglichen Anforderung an chain.doFilter
übergeben.
Es ist ein bisschen hässlich, aber laut Servlet-API sollten Sie das tun. Wenn Sie versuchen, etwas anderes an doFilter
zu übergeben, beschweren sich einige Servlet-Container, dass Sie gegen die Spezifikation verstoßen haben, und verweigern die Verarbeitung.
Eine elegantere Lösung ist mehr Arbeit - ändern Sie das ursprüngliche Servlet/die ursprüngliche JSP, die den Parameter verarbeitet, so dass anstelle eines Parameters ein request attribute erwartet wird. Der Filter überprüft den Parameter, bereinigt ihn und setzt das Attribut (mit request.setAttribute
) auf den bereinigten Wert. Keine Unterklassen, kein Spoofing, Sie müssen jedoch andere Teile Ihrer Anwendung ändern.
Hier ist die Klasse, die ich am Ende geschrieben habe:
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) {
}
}
Schreiben Sie eine einfache Klasse, die HttpServletRequestWrapper
mit einer getParameter () - Methode unterzieht, die die bereinigte Version der Eingabe zurückgibt. Übergeben Sie dann eine Instanz Ihrer HttpServletRequestWrapper
an Filter.doChain()
anstelle des Anforderungsobjekts direkt.
Das habe ich am Ende getan
//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();
}
}
}
Ich hatte das gleiche Problem (Ändern eines Parameters von der HTTP-Anforderung im Filter). Am Ende habe ich einen ThreadLocal<String>
verwendet. In der Filter
habe ich:
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);
}
}
In meinem Anforderungsprozessor (HttpServlet
, JSF-Controller oder einem anderen HTTP-Anforderungsprozessor) erhalte ich den aktuellen Thread-Wert zurück:
...
String myVariable = MyFilter.THREAD_VARIABLE.get();
...
Vorteile:
HttpServletRequestWrapper
-Heizplatterequest.setAttribute(String,Object)
haben), d. h. Sie können auf die Variable in anderen Filtrierern zugreifen.Nachteile:
Java.util.stream.Stream.parallel
, Java.util.concurrent.Future
, Java.lang.Thread
.Einige Randnotizen:
Der Server verfügt über einen Thread-Pool, um die HTTP-Anforderungen zu verarbeiten. Da dies Pool ist:
if (value!=null) { THREAD_VARIABLE.set(value);}
, da Sie die wiederverwendet Wert aus der vorherigen HTTP-Anforderung, wenn value
null ist: Nebeneffekte sind garantiert). HttpSession.setAttribute()
zu verwenden.@RequestScoped
verwendet intern eine ThreadLocal
, die Verwendung der ThreadLocal
ist jedoch vielseitiger: Sie können sie in Nicht-JEE/CDI-Containern verwenden (z. B. in Multithread-JRE-Anwendungen)Versuchen Sie es mit request.setAttribute("param",value);
. Es hat gut für mich funktioniert.
Bitte finden Sie dieses Codebeispiel:
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
}
}
Sie können Regular Expression für die Bereinigung verwenden. Rufen Sie vor dem Aufruf von chain.doFilter (request, response) innerhalb des Filters diesen Code auf . Hier ist Beispielcode:
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();
}
}