检查您的REST参数!

我正在进行与正在进行的“项目学生”系列相关的研究,并意识到我犯了最常见的错误,也最容易纠正的错误之一。 我并没有利用我对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 {

    @Test
    public void testValidUuid() {
        assertTrue(StudentUtil.isPossibleUuid("63c7d688-705c-4374-937c-6628952b41e1"));
    }

    @Test
    public 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)
     */
    @Override
    public void init(FilterConfig cfg) throws ServletException {

        // learn valid nouns
        final 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)
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
            ServletException {

        HttpServletRequest hreq = (HttpServletRequest) req;
        HttpServletResponse hresp = (HttpServletResponse) resp;

        // verify the noun + uuid
        if (!checkPathInfo(hreq, hresp)) {
            return;
        }

        // do additional tests, e.g., inspect payload

        chain.doFilter(req, resp);
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public 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来检测包含的应用服务器。)

参考: 检查您的REST参数! 来自Invariant Properties博客的JCG合作伙伴 Bear Giles。

翻译自: https://www.javacodegeeks.com/2014/01/check-your-rest-parameters.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值