既存のWebアプリケーションがTomcat 4.1で実行されています。ページにXSSの問題がありますが、ソースを変更できません。ページに表示される前にパラメーターをサニタイズするサーブレットフィルターを作成することにしました。
私はこのようなFilterクラスを書きたいです:
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
は存在しません。
リクエストをチェーンに渡す前に、リクエストパラメータの値を変更するにはどうすればよいですか?
お気づきのとおり、HttpServletRequest
にはsetParameterメソッドがありません。クラスはクライアントからの要求を表しているため、これは意図的なものであり、パラメーターを変更してもそれは表されません。
1つの解決策は、HttpServletRequestWrapper
クラスを使用することです。これにより、ある要求を別の要求でラップできます。それをサブクラス化し、getParameter
メソッドをオーバーライドして、サニタイズされた値を返すことができます。その後、元のリクエストではなく、そのラップされたリクエストをchain.doFilter
に渡すことができます。
少しいですが、それがサーブレットAPIがすべきだと言っていることです。 doFilter
に他のものを渡そうとすると、一部のサーブレットコンテナは、仕様に違反していると文句を言い、処理を拒否します。
より洗練された解決策はより多くの作業です-パラメーターを処理する元のサーブレット/ JSPを変更し、パラメーターではなくリクエストattributeを期待するようにします。フィルターはパラメーターを調べ、それをサニタイズし、サニタイズされた値で属性を設定します(request.setAttribute
を使用)。サブクラス化、なりすましはありませんが、アプリケーションの他の部分を変更する必要があります。
記録のために、ここに私が書いたクラスがあります:
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) {
}
}
入力のサニタイズバージョンを返すgetParameter()メソッドを使用して、HttpServletRequestWrapper
をサブクラス化する単純なクラスを記述します。次に、リクエストオブジェクトの代わりに、HttpServletRequestWrapper
のインスタンスをFilter.doChain()
に直接渡します。
これは私がやったことです
//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();
}
}
}
同じ問題がありました(フィルターで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();
...
利点:
HttpServletRequestWrapper
ボイラープレートよりもエレガントrequest.setAttribute(String,Object)
を実行するときに持っているスコープ、つまり、他のフィルターで変数にアクセスできます)よりも広いです。短所:
Java.util.stream.Stream.parallel
、Java.util.concurrent.Future
、Java.lang.Thread
を使用する場合。いくつかのサイドノート:
サーバーには、HTTP要求を処理するスレッドプールがあります。これはプールなので:
if (value!=null) { THREAD_VARIABLE.set(value);}
などのコードに注意してくださいvalue
がnullの場合、前のHTTP要求からの値:副作用が保証されます)。HttpSession.setAttribute()
を使用することをお勧めします。@RequestScoped
は内部でThreadLocal
を使用しますが、ThreadLocal
を使用する方がより汎用性があります。非JEE/CDIコンテナで使用できます(マルチスレッドJREアプリケーションなど)。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
}
}
正規表現をサニタイズに使用できます。 chain.doFilter(request、response)メソッドを呼び出す前にフィルター内で、このコードを呼び出します。サンプルコードは次のとおりです。
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();
}
}