我正在进行与正在进行的“项目学生”系列相关的研究,并意识到我犯了最常见的错误,也最容易纠正的错误之一。 我并没有利用我对Web应用程序了解的所有知识来向外扩展我的安全范围。
我正在专门考虑UUID参数。 我知道每个有效的外部可见ID都是UUID。 我知道UUID的形式。 那么,为什么不进一步检查我的“ uuid”参数是否是潜在的有效UUID?
的确,数据库层不会识别出错误的“ uuid”值-但这可能不是攻击者的意图。 也许这是SQL注入攻击的一部分。 也许这是XSS攻击的一部分。 也许这是对我的日志的攻击的一部分(例如,通过包含一个很长的值可能会导致缓冲区溢出)。 也许这是我从未听说过的东西的一部分。 没关系–通过尽快消除已知无效的数据,我将永远变得更强大。
效用方法
确定一个值是否可能是UUID的实用方法使用简单的正则表达式模式。
public final class StudentUtil {private static final Pattern UUID_PATTERN = Pattern.compile("^\\p{XDigit}{8}+-\\p{XDigit}{4}+-\\p{XDigit}{4}-\\p{XDigit}{4}+-\\p{XDigit}{12}$");/*** Private constructor to prevent instantiation.*/private StudentUtil() {}public static boolean isPossibleUuid(String value) {return value != null && UUID_PATTERN.matcher(value).matches();}
}
如果我们要积极进取,我们可以仔细选择我们的UUID,以便它们具有我们可以检查的其他属性。 例如,对应的BigInteger始终可以保留3 mod 17的余数。攻击不太可能知道这一点,并且当有人探测我们的系统时,我们会发出警告。 甚至更复杂的方法将为每个类的UUID使用不同的属性,例如,“课程” UUID可能是3 mod 17,而“学生” UUID是5 mod 17。
单元测试
进行测试很容易,但是最少的设置是检查非十六进制数字,
值太多或太少,一个空字符串和一个空值。
public class StudentUtilTest {@Testpublic void testValidUuid() {assertTrue(StudentUtil.isPossibleUuid("63c7d688-705c-4374-937c-6628952b41e1"));}@Testpublic void testInvalidUuid() {assertTrue(!StudentUtil.isPossibleUuid("63c7d68x-705c-4374-937c-6628952b41e1"));assertTrue(!StudentUtil.isPossibleUuid("63c7d68-8705c-4374-937c-6628952b41e1"));assertTrue(!StudentUtil.isPossibleUuid("63c7d688-705c4-374-937c-6628952b41e1"));assertTrue(!StudentUtil.isPossibleUuid("63c7d688-705c-43749-37c-6628952b41e1"));assertTrue(!StudentUtil.isPossibleUuid("63c7d688-705c-4374-937c6-628952b41e1"));assertTrue(!StudentUtil.isPossibleUuid("63c7d688-705c-4374-937c-6628952b41e1a"));assertTrue(!StudentUtil.isPossibleUuid("63c7d688-705c-4374-937c-6628952b41e"));assertTrue(!StudentUtil.isPossibleUuid(""));assertTrue(!StudentUtil.isPossibleUuid(null));}
}
REST服务器
REST服务器应检查所有需要一种方法的UUID值。 在我们确认它是格式正确的UUID之后,可以安全地记录请求参数,但是仍然需要注意在请求中记录未过滤的值。
@Path("/{courseId}")@GET@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })public Response getCourse(@PathParam("courseId") String id) {Response response = null;if (!StudentUtil.isPossibleUuid(id)) {response = Response.status(Status.BAD_REQUEST).build();LOG.info("attempt to use malformed UUID");} else {LOG.debug("CourseResource: getCourse(" + id + ")");try {Course course = finder.findCourseByUuid(id);response = Response.ok(scrubCourse(course)).build();} catch (ObjectNotFoundException e) {response = Response.status(Status.NOT_FOUND).build();LOG.debug("course not found: " + id);} catch (Exception e) {if (!(e instanceof UnitTestException)) {LOG.info("unhandled exception", e);}response = Response.status(Status.INTERNAL_SERVER_ERROR).build();}}return response;}
一个明显的改进是将该检查(和异常处理包)移到所有服务方法的AOP包装器中。 这将简化代码,并在保证始终执行检查方面大有帮助。 (由于Web服务服务器层当前不具有Spring依赖关系,因此我目前不在Project Student中使用它。)
您可以提出一个强有力的opsec参数,即REST方法应返回NOT_FOUND响应而不是BAD_REQUEST响应,以减少信息泄漏。
网络应用
细节有所不同,但即使webapp只是REST服务的浅层前端,我们也应该对它们进行相同的处理。 只要有UUID,无论其来源是什么,都应在使用前对其进行检查。
筛选器
有一种流派认为,安全性应与应用程序分开处理–最好的安全性是在部署时(通过过滤器和AOP)结合在一起的,而不是嵌入到应用程序中。 没有人建议应用程序开发人员应该忽略安全性考虑因素,就像我上面所讨论的那样,检查很混乱,分散了开发人员的注意力,并且不可靠,因为开发人员很容易忽略。 他们建议改用AOP或过滤器。
编写与上述代码具有相同功能的过滤器很简单:
public class RestParameterFilter implements Filter {private static final Logger LOG = Logger.getLogger(RestParameterFilter.class);private static final Set<String> validNouns = new HashSet<>();/*** @see javax.servlet.Filter#init(javax.servlet.FilterConfig)*/@Overridepublic void init(FilterConfig cfg) throws ServletException {// learn valid nounsfinal String nouns = cfg.getInitParameter("valid-nouns");if (nouns != null) {for (String noun : nouns.split(",")) {validNouns.add(noun.trim());}}}/*** @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,* javax.servlet.ServletResponse, javax.servlet.FilterChain)*/@Overridepublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,ServletException {HttpServletRequest hreq = (HttpServletRequest) req;HttpServletResponse hresp = (HttpServletResponse) resp;// verify the noun + uuidif (!checkPathInfo(hreq, hresp)) {return;}// do additional tests, e.g., inspect payloadchain.doFilter(req, resp);}/*** @see javax.servlet.Filter#destroy()*/@Overridepublic void destroy() {}/*** Check the pathInfo. We know that all paths should have the form* /{noun}/{uuid}/...* * @param req* @return*/public boolean checkPathInfo(HttpServletRequest req, HttpServletResponse resp) {// this pattern only handles noun and UUID, no additional parameters.Pattern pattern = Pattern.compile("^/([\\p{Alpha}]+)(/?([\\p{XDigit}-]+)?)?");Matcher matcher = pattern.matcher(req.getPathInfo());matcher.find();// verify this is a valid noun.if ((matcher.groupCount() >= 1) && !validNouns.contains(matcher.group(1))) {// LOG.info("unrecognized noun");LOG.info("unrecognized noun: '" + matcher.group(1) + "'");resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);return false;}// verify this is a valid verb.if ((matcher.groupCount() >= 4) && !StudentUtil.isPossibleUuid(matcher.group(4))) {LOG.info("invalid UUID");resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);return false;}return true;}
}
没有理由我们也不能检查有效载荷。 例如,我们可以验证日期,电话号码和信用卡号的格式是否正确; 或名称仅包含字母(包括非拉丁字符,如ñ),空格和撇号。 (想想“ Anne-MariePeñaO'Brien”。)重要的是要记住,这些检查不是针对“有效”数据的,而是要消除“无效”数据。
我们必须将过滤器添加到我们的web.xml文件中。
web.xml
<filter><filter-name>REST parameter filter</filter-name><filter-class>com.invariantproperties.sandbox.student.webservice.security.RestParameterFilter</filter-class><init-param><param-name>valid-nouns</param-name><param-value>classroom,course,instructor,section,student,term,testRun</param-value></init-param>
</filter><filter-mapping><filter-name>REST parameter filter</filter-name><servlet-name>REST dispatcher</servlet-name>
</filter-mapping>
ModSecurity
为电话号码和姓名之类的简单元素编写过滤器很容易,但是纯文本字段是另一回事。 这些字段需要最大的灵活性,同时我们希望将XSS和其他攻击的风险降至最低。
这些方面的一个很好的资源是ModSecurity。 这最初是一个Apache模块,但已被Trustwave Spider Labs采用。 它位于Web服务器上,而不是Webapp上,并检查通过它的数据。 最近的端口(2013年夏季)允许使用Servlet过滤器而不是外部反向代理来进行设置。 (它使用JNI来检测包含的应用服务器。)
- 有关更多信息,请参见ModSecurity for Java 。
翻译自: https://www.javacodegeeks.com/2014/01/check-your-rest-parameters.html