1 /******************************************************************************* 2 * Copyright (c) 2011, 2020 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * George Suaridze <suag@1c.ru> (1C-Soft LLC) - Bug 560168 14 *******************************************************************************/ 15 package org.eclipse.help.internal.webapp.servlet; 16 17 import java.io.ByteArrayInputStream; 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.io.OutputStream; 21 import java.io.PrintWriter; 22 import java.io.UnsupportedEncodingException; 23 import java.util.ArrayList; 24 import java.util.Enumeration; 25 import java.util.Hashtable; 26 import java.util.List; 27 import java.util.Locale; 28 29 import javax.servlet.ServletConfig; 30 import javax.servlet.ServletException; 31 import javax.servlet.ServletOutputStream; 32 import javax.servlet.http.HttpServlet; 33 import javax.servlet.http.HttpServletRequest; 34 import javax.servlet.http.HttpServletResponse; 35 import javax.servlet.http.HttpServletResponseWrapper; 36 37 import org.eclipse.core.runtime.CoreException; 38 import org.eclipse.core.runtime.IConfigurationElement; 39 import org.eclipse.core.runtime.Platform; 40 import org.eclipse.help.internal.webapp.HelpWebappPlugin; 41 import org.eclipse.help.internal.webapp.WebappResources; 42 import org.eclipse.help.internal.webapp.data.UrlUtil; 43 import org.eclipse.help.internal.webapp.utils.Utils; 44 45 /* 46 * Class is responsible for implementing security protection. All servlets 47 * who use the org.eclipse.help.webapp.validatedServlet extension point will 48 * will be processed for security failures by this class. 49 * 50 * Any URL that starts with <path>/vs<etc> will be redirected here for further 51 * processing. If the validatedServlet extension point has an alias that 52 * matches the URL passed here, it will finish the processing and return 53 * results here for validation. If there are no malicious threats detected, 54 * this class will return the output to the client. 55 * 56 */ 57 public class ValidatorServlet extends HttpServlet { 58 59 private static final long serialVersionUID = -3783758607845176051L; 60 private Hashtable<String, HttpServlet> servletTable = new Hashtable<>(); 61 process(HttpServletRequest req, HttpServletResponse resp)62 protected void process(HttpServletRequest req, HttpServletResponse resp) 63 throws ServletException, IOException { 64 65 String baseURL = req.getRequestURL().toString(); 66 baseURL = baseURL.substring(0, baseURL.indexOf(req.getServletPath())); 67 68 Locale locale = UrlUtil.getLocaleObj(req,resp); 69 70 String service = req.getRequestURL().toString().substring( 71 (baseURL).length()+("/vs".length())); //$NON-NLS-1$ 72 73 try { 74 HttpServletResponseAdv response = new HttpServletResponseAdv(resp); 75 76 HttpServlet servlet = getServlet(service); 77 ServletConfig config = getServletConfig(); 78 servlet.init(config); 79 servlet.service(req, response); 80 81 if (isSecure(req, response)) 82 response.commitOutput(); 83 84 } catch(Exception ex) { 85 86 String errorMsg = WebappResources.getString("cantCreateServlet", //$NON-NLS-1$ 87 locale, service); 88 Platform.getLog(getClass()).error(errorMsg, ex); 89 90 @SuppressWarnings("resource") 91 PrintWriter writer = resp.getWriter(); 92 writer.println(errorMsg); 93 ex.printStackTrace(writer); 94 95 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 96 } 97 } 98 getServlet(String name)99 private HttpServlet getServlet(String name) 100 throws CoreException { 101 102 HttpServlet servlet = servletTable.get(name); 103 104 if (servlet == null) { 105 106 IConfigurationElement[] configs = 107 Platform.getExtensionRegistry().getConfigurationElementsFor(HelpWebappPlugin.PLUGIN_ID+".validatedServlet"); //$NON-NLS-1$ 108 109 for (IConfigurationElement config2 : configs) { 110 111 String alias = config2.getAttribute("alias"); //$NON-NLS-1$ 112 if (alias != null) { 113 114 if (isMatch(alias, name)) { 115 servlet = (HttpServlet)config2.createExecutableExtension("class"); //$NON-NLS-1$ 116 servletTable.put(name, servlet); 117 break; 118 } 119 } 120 } 121 } 122 123 return servlet; 124 } 125 isMatch(String alias, String name)126 private boolean isMatch(String alias, String name) { 127 128 int index = name.indexOf(alias); 129 if (index == 0) { 130 int offset = alias.length(); 131 if (name.length() == offset) 132 return true; 133 char ch = name.charAt(offset); 134 if (ch == '/' || ch == '?') 135 return true; 136 } 137 return false; 138 } 139 140 @Override doGet(HttpServletRequest req, HttpServletResponse resp)141 protected void doGet(HttpServletRequest req, HttpServletResponse resp) 142 throws ServletException, IOException { 143 process(req, resp); 144 } 145 146 @Override doPost(HttpServletRequest req, HttpServletResponse resp)147 protected void doPost(HttpServletRequest req, HttpServletResponse resp) 148 throws ServletException, IOException { 149 process(req, resp); 150 } 151 isSecure(HttpServletRequest req,HttpServletResponseAdv resp)152 public boolean isSecure(HttpServletRequest req,HttpServletResponseAdv resp) 153 throws SecurityException { 154 Enumeration<String> names = req.getParameterNames(); 155 List<String> values = new ArrayList<>(); 156 List<String> scripts = new ArrayList<>(); 157 158 while (names.hasMoreElements()) { 159 160 String name = names.nextElement(); 161 String val = req.getParameter(name); 162 values.add(val); 163 if (replaceAll(val, '+', "").indexOf("<script")>-1) //$NON-NLS-1$ //$NON-NLS-2$ 164 scripts.add(val); 165 } 166 167 if (resp.getWriter() != null) { 168 String data = resp.getString(); 169 for (int s=0; s < scripts.size(); s++) 170 if (data.indexOf(scripts.get(s)) > -1) 171 throw new SecurityException("Potential cross-site scripting detected."); //$NON-NLS-1$ 172 } 173 174 return true; 175 } 176 replaceAll(String str, char remove, String add)177 public String replaceAll(String str, char remove, String add) { 178 179 StringBuilder buffer = new StringBuilder(); 180 for (int s=0; s < str.length(); s++) { 181 182 char ch = str.charAt(s); 183 if (ch == remove) 184 buffer.append(add); 185 else 186 buffer.append(ch); 187 } 188 return buffer.toString(); 189 } 190 191 private class HttpServletResponseAdv extends HttpServletResponseWrapper { 192 193 private HttpServletResponse response; 194 private ServletPrintWriter writer; 195 private ServletOutputStream stream; 196 HttpServletResponseAdv(HttpServletResponse response)197 public HttpServletResponseAdv(HttpServletResponse response) { 198 super(response); 199 this.response = response; 200 } 201 202 @Override getWriter()203 public PrintWriter getWriter() { 204 205 if (writer == null && stream == null) 206 writer = new ServletPrintWriter(); 207 return writer; 208 } 209 210 @Override getOutputStream()211 public ServletOutputStream getOutputStream() throws IOException { 212 213 if (stream == null && writer == null) 214 stream = response.getOutputStream(); 215 return stream; 216 } 217 218 @SuppressWarnings("resource") commitOutput()219 public void commitOutput() throws IOException { 220 221 OutputStream os = response.getOutputStream(); 222 InputStream is = getInputStream(); 223 if (is != null) { 224 Utils.transferContent(is, os); 225 } 226 os.flush(); 227 } 228 getInputStream()229 public InputStream getInputStream() { 230 if (writer != null) { 231 232 try { 233 return new ByteArrayInputStream(writer.toString().getBytes(getCharacterEncoding())); 234 } catch (UnsupportedEncodingException e) { 235 e.printStackTrace(); 236 } 237 } 238 return null; 239 } 240 getString()241 public String getString() { 242 243 if (writer != null) 244 return writer.toString(); 245 246 return null; 247 } 248 } 249 } 250