/* * Copyright 2009-2012 Michael Dalton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jtaint; import java.util.Arrays; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Set; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import javax.servlet.Servlet; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.MockHttpServlet; import javax.servlet.http.MockHttpServletRequest; import javax.servlet.http.HttpUtils; public class ServletTest { private final int maxlen; private final SafeRandom sr; private final TestUtil tu; private List cookies, requests, servlets; public ServletTest(int maxlen, SafeRandom sr) { this.maxlen = maxlen; this.sr = sr; this.tu = new TestUtil(maxlen, sr, TestUtil.UNTAINTED); cookies = new ArrayList(); requests = new ArrayList(); servlets = new ArrayList(); } private Cookie randCookie() { Cookie c; if (sr.nextBoolean() && cookies.size() > 0) { c = (Cookie) cookies.get(sr.nextInt(cookies.size())); if (sr.nextBoolean()) c.randomize(); return c; } c = new Cookie(tu); cookies.add(c); return c; } private MockHttpServlet randServlet() { MockHttpServlet s; if (sr.nextBoolean() && servlets.size() > 0) { s = (MockHttpServlet) servlets.get(sr.nextInt(servlets.size())); return s; } s = new MockHttpServlet(tu); servlets.add(s); return s; } private MockHttpServletRequest randRequest() { MockHttpServletRequest req; if (sr.nextBoolean() && requests.size() > 0) { req = (MockHttpServletRequest)requests.get(sr.nextInt(requests.size())); if (sr.nextBoolean()) req.randomize(); return req; } req = new MockHttpServletRequest(tu); requests.add(req); return req; } private void testServlet() { Throwable excpt = null; ServletRequest req = randRequest(); ServletResponse res = new HttpServletResponse(); MockHttpServlet s = randServlet(); if (s.getServletInfo().@internal@isTainted()) throw new RuntimeException("Unexpected taint"); try { s.service(req, res); } catch (Throwable th) { excpt = th; } if ((s.pendingRuntimeException() && excpt == null) || (s.pendingJTaintException() && excpt != null)) throw new RuntimeException("Unexpected Servlet exception behavior"); Thread t = Thread.currentThread(); if (t.@internal@getRequestParams() != null || t.@internal@getRemoteHost() != null || t.@internal@getRemoteAddr() != null) throw new RuntimeException("Non-null Thread servlet fields"); } private void testServletRequest() { ServletRequest req = randRequest(); String s = null, taintS = null, tmp; switch(sr.nextInt(8)) { case 0: taintS = req.getCharacterEncoding(); s = req.getCharacterEncodingSafe(); break; case 1: taintS = req.getContentType(); s = req.getContentTypeSafe(); break; case 2: tmp = tu.randString(); taintS = req.getParameter(tmp); s = req.getParameterSafe(tmp); break; case 3: Map taintM = req.getParameterMap(); Map m = req.getParameterMapSafe(); if (!tu.isValidTaintedMap(m, taintM)) throw new RuntimeException("Parameter map taint invalid"); return; case 4: Enumeration taintE = req.getParameterNames(); Enumeration e = req.getParameterNamesSafe(); if (!tu.isValidTaintedEnumeration(e, taintE)) throw new RuntimeException("Enumeration taint invalid"); return; case 5: tmp = tu.randString(); String[] taintSa = req.getParameterValues(tmp); String[] sa = req.getParameterValuesSafe(tmp); if (!tu.isValidTaintedStringArray(sa, taintSa)) throw new RuntimeException("Invalid string array"); return; case 6: taintS = req.getRemoteHost(); s = req.getRemoteHostSafe(); break; case 7: taintS = req.getRemoteAddr(); s = req.getRemoteAddrSafe(); break; default: throw new RuntimeException("switch"); } if (!tu.isValidTaintedString(s, taintS)) throw new RuntimeException("Corrupt Servlet method"); } private void testCookie() { Cookie c = randCookie(); String s, taintS; switch (sr.nextInt(5)) { case 0: taintS = c.getComment(); s = c.getCommentSafe(); break; case 1: taintS = c.getDomain(); s = c.getDomainSafe(); break; case 2: taintS = c.getName(); s = c.getNameSafe(); break; case 3: taintS = c.getPath(); s = c.getPathSafe(); break; case 4: taintS = c.getValue(); s = c.getValueSafe(); break; default: throw new RuntimeException("switch"); } if (!tu.isValidTaintedString(s, taintS)) throw new RuntimeException("Corrupt Cookie method"); } private void testHttpServlet() { Throwable excpt = null; HttpServletRequest req = randRequest(); MockHttpServlet s = randServlet(); if (s.getServletInfo().@internal@isTainted()) throw new RuntimeException("Unexpected taint"); try { s.service(req, new HttpServletResponse()); } catch (Throwable th) { excpt = th; } if ((s.pendingRuntimeException() && excpt == null) || (s.pendingJTaintException() && excpt != null)) throw new RuntimeException("Unexpected Servlet exception behavior"); Thread t = Thread.currentThread(); if (t.@internal@getRequestParams() != null || t.@internal@getRemoteHost() != null || t.@internal@getRemoteAddr() != null) throw new RuntimeException("Non-null Thread servlet fields"); } private void testHttpServletRequest() { HttpServletRequest req = randRequest(); String s = null, taintS = null, h; Enumeration e, taintE; switch(sr.nextInt(12)) { case 0: taintS = req.getContextPath(); s = req.getContextPathSafe(); break; case 1: h = tu.randString(); taintS = req.getHeader(h); s = req.getHeaderSafe(h); break; case 2: taintE = req.getHeaderNames(); e = req.getHeaderNamesSafe(); if (!tu.isValidTaintedEnumeration(e, taintE)) throw new RuntimeException("Corrupted enumeration"); return; case 3: h = tu.randString(); taintE = req.getHeaders(h); e = req.getHeadersSafe(h); if (!tu.isValidTaintedEnumeration(e, taintE)) throw new RuntimeException("Corrupted enumeration"); return; case 4: taintS = req.getPathInfo(); s = req.getPathInfoSafe(); break; case 5: taintS = req.getPathTranslated(); s = "/correct/" + req.getPathInfoSafe(); int prefixLen = "/correct/".length(); if (!taintS.equals(s)) throw new RuntimeException("Path translated corruption"); if (s.@internal@isTainted()) throw new RuntimeException("Unexpected taint"); if (taintS.substring(0, prefixLen).@internal@isTainted()) throw new RuntimeException("Tainted prefix"); for (int i = prefixLen; i < taintS.length(); i++) if (!taintS.@internal@taint().get(i)) throw new RuntimeException("Untainted suffix"); return; case 6: taintS = req.getQueryString(); s = req.getQueryStringSafe(); break; case 7: taintS = req.getRemoteUser(); s = req.getRemoteUserSafe(); break; case 8: taintS = req.getRequestedSessionId(); s = req.getRequestedSessionIdSafe(); break; case 9: taintS = req.getRequestURI(); s = req.getRequestURISafe(); break; case 10: taintS = req.getRequestURL().toString(); s = req.getRequestURLSafe().toString(); break; case 11: taintS = req.getServletPath(); s = req.getServletPathSafe(); break; default: throw new RuntimeException("switch"); } if (!tu.isValidTaintedString(s, taintS)) throw new RuntimeException("Invalid string"); } private void testHttpUtils() { int i = sr.nextInt(3); HttpServletRequest req = randRequest(); HttpUtils.init(tu); if (i == 0) { StringBuffer taintSb = HttpUtils.getRequestURL(req); StringBuffer sb = HttpUtils.getRequestURLSafe(req); if (!tu.isValidTaintedString(sb.toString(), taintSb.toString())) throw new RuntimeException("Invalid stringbuffer taint"); return; } Hashtable taintH, h; if (i == 1) { String s = tu.randString(); taintH = HttpUtils.parseQueryString(s); h = HttpUtils.parseQueryStringSafe(s); } else if (i == 2) { ServletInputStream is = new ServletInputStream(); taintH = HttpUtils.parsePostData(0, is); h = HttpUtils.parsePostDataSafe(0, is); } else { throw new RuntimeException("unexpected if"); } if (!tu.isValidTaintedHashtable(h, taintH)) throw new RuntimeException("Corrupt hash table"); } private void test() { switch(sr.nextInt(6)) { case 0: testServlet(); break; case 1: testServletRequest(); break; case 2: testCookie(); break; case 3: testHttpServlet(); break; case 4: testHttpServletRequest(); break; case 5: testHttpUtils(); break; default: throw new RuntimeException("switch"); } } public static void main(String[] args) { long seed = System.currentTimeMillis(); int maxlen = 128; int nrtest = 16384; ServletTest st; String logfile = "ServletTest.log"; PrintStream ps = null; for (int i = 0; i < args.length; i++) { if (args[i].equals("-s")) seed = Long.decode(args[++i]).longValue(); else if (args[i].equals("-l")) maxlen = Integer.decode(args[++i]).intValue(); else if (args[i].equals("-n")) nrtest = Integer.decode(args[++i]).intValue(); else if (args[i].equals("-f")) logfile = args[++i]; else { System.out.println("Usage: java ServletTest " + "[-s randomSeed] " + "[-l maximumLengthofString] " + "[-n NumberofTests]" + "[-f logFileName]"); System.exit(-1); } } try { ps = new PrintStream(new FileOutputStream(logfile)); } catch (FileNotFoundException e) { System.out.println("Error opening logfile [" + logfile + "]: " + e); System.exit(-1); } ps.print("-s "); ps.print(seed); ps.print(" -l "); ps.print(maxlen); ps.print(" -n "); ps.print(nrtest); ps.print(" -f "); ps.print(logfile + "\n"); ps.flush(); ps.close(); st = new ServletTest(maxlen, new SafeRandom(seed)); for (int i = 0; i < nrtest; i++) st.test(); if (Log.hasError() || Log.hasWarning() || Log.hasVuln()) { System.out.println("Error or Warning encountered -- check logs"); System.exit(-1); } } }