JSP / Servlet Web应用程序中的XSS预防


Answers:


113

当(重新)显示用户控制的输入时,可以通过使用JSTL <c:out>标签或fn:escapeXml()EL函数在JSP中防止XSS 。这包括请求参数,标头,cookie,URL,正文等。从请求对象中提取的所有内容。同样,在重新显示期间,也需要转储来自先前请求的用户控制输入,这些输入存储在数据库中。

例如:

<p><c:out value="${bean.userControlledValue}"></p>
<p><input name="foo" value="${fn:escapeXml(param.foo)}"></p>

这将逃脱这可能malform渲染HTML,如人物<>"'&HTML / XML实体,如&lt;&gt;&quot;&apos;&amp;

注意,您不需要在Java(Servlet)代码中对其进行转义,因为它们在那儿是无害的。有些人可能选择在请求处理期间(就像您在Servlet或Filter中所做的那样)而不是响应处理(就像在JSP中那样)对它们进行转义,但是这种方式可能会冒着不必要地对数据进行两次转义的风险(例如,&变为和&amp;amp;而不是&amp;最终,最终用户会看到&amp;正在显示),或者数据库存储的数据变得不可移植(例如,将数据导出到JSON,CSV,XLS,PDF等完全不需要转义HTML的数据时)。您还将失去社交控制,因为您不再知道用户实际填写了什么。作为站点管理员,您真的很想知道哪些用户/ IP正在尝试执行XSS,以便您可以轻松跟踪他们并采取相应的行动。仅在您确实需要在尽可能短的时间内修复开发不良的旧版Web应用程序的残骸时,才应将请求处理期间的转义用作最新手段。不过,您最终应该重写JSP文件,使其成为XSS安全的。

如果你想重新显示用户控制输入为HTML,其中你想只允许HTML标签,如的特定子集<b><i><u>,等等,那么你需要通过一个白名单来净化输入。您可以为此使用HTML解析器(如Jsoup)。但是,更好的方法是引入一种人类友好的标记语言,例如Markdown(也在Stack Overflow上使用)。然后,您可以为此使用Markdown解析器(如CommonMark)。它还具有内置的HTML清理功能。另请参阅Markdown或HTML

服务器端与数据库有关的唯一问题是防止SQL注入。您需要确保不要在SQL或JPQL查询中直接对用户控制的输入进行字符串连接,并且要始终使用参数化查询。用JDBC术语,这意味着您应该使用PreparedStatement而不是Statement。用JPA术语,请使用Query


一种替代方法是从JSP / Servlet迁移到Java EE的MVC框架JSF。它在所有位置都内置了XSS(和CSRF!)预防功能。另请参见JSF中的CSRF,XSS和SQL注入攻击防护


1
仅仅因为您使用的是Hibernate,并不意味着您可以安全地进行SQL注入。例如,请参见blog.harpoontech.com/2008/10/…
MatrixFrog 2011年

@chad:那不是真的。仅在像这样直接在SQL / HQL / JPQL查询中将用户控制的输入字符串连接时,"SELECT ... WHERE SOMEVAL = " + someval而不是像所示的那样使用参数化查询时,才是这种情况。没有人可以防止这种开发人员的错误。
BalusC 2012年

5
我认为您也必须在服务器中进行验证。可以通过更改HTTP参数来绕过所有验证。有时,您保留的数据可能会被企业应用程序中的其他应用程序使用。有时您无权访问其他应用程序的视图,因此您需要先清除输入,然后再保存在数据库中。
Guido Celada 2014年

1
@Guido:您不了解这个问题。
BalusC 2014年

2
@peater:是的,当将不受信任的数据放入JS代码时,您需要使用JS编码而不是HTML编码。而且,将不受信任的数据放入CSS代码时,您需要CSS编码而不是HTML编码。而且,当将不受信任的数据放入URL时,您需要URL编码而不是HTML编码。HTML编码仅应用于将不受信任的数据放入HTML代码中。
BalusC

12

how-to-prevent-xss已被问过几次。您会在StackOverflow中找到很多信息。另外,OWASP网站上有XSS预防备忘单,您应该仔细阅读。

在要使用的库上,OWASP的ESAPI库具有Java风格。您应该尝试一下。除此之外,您使用的每个框架都具有针对XSS的保护。再次,OWASP网站提供了有关大多数流行框架的信息,因此,我建议您浏览他们的网站。


OWASP速查表已移至GitHub。这里是链接的XSS预防小抄github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/...
peater

12

我对OWASP Anti-Samy以及在所有阻止XSS进入的所有Spring Controller上的AspectJ顾问感到很幸运。

public class UserInputSanitizer {

    private static Policy policy;
    private static AntiSamy antiSamy;

    private static AntiSamy getAntiSamy() throws PolicyException  {
        if (antiSamy == null) {
            policy = getPolicy("evocatus-default");
            antiSamy = new AntiSamy();
        }
        return antiSamy;

    }

    public static String sanitize(String input) {
        CleanResults cr;
        try {
            cr = getAntiSamy().scan(input, policy);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return cr.getCleanHTML();
    }

    private static Policy getPolicy(String name) throws PolicyException {
        Policy policy = 
            Policy.getInstance(Policy.class.getResourceAsStream("/META-INF/antisamy/" + name + ".xml"));
        return policy;
    }

}

您可以从此stackoverflow帖子中获取AspectJ顾问程序

我认为这是一种比c:out更好的方法,特别是如果您使用了大量的javascript。


通常的做法是在重新显示过程中而不是在servlet中处理提交的数据或在DB中存储过程中,对所有用户控制的数据进行HTML转义。如果您在处理提交的数据和/或将其存储在数据库中时对其进行HTML转义,那么它们都将散布在业务代码和/或数据库中。这仅是维护方面的麻烦,并且在不同的地方执行两次转义符时甚至会冒更大的风险。反过来,业务代码和数据库对XSS不敏感。只有视图。然后,您应该仅在可见的地方对其进行转义。
Shubham Maheshwari,2015年

1
是的,没有。尽管通常的做法是逃避显示,但仍有许多原因可能需要在写入时进行清理。在某些情况下,您确实希望用户输入HTML的子集,尽管您可以清除显示的内容,但这实际上相当缓慢,甚至会使用户感到困惑。其次,如果您与第三方服务(例如外部API)共享数据,则这些服务可能会或可能不会自行进行适当的清理。
亚当·根特

正如您和我都提到的,“常规做法”是逃避现实。您在上面的评论中提到的是更特定的用例,因此可以肯定地需要特定的解决方案。
Shubham Maheshwari 2015年

是的,我也许应该使用例更加清楚。我主要从事内容管理(HTML编辑)方面的工作。
亚当·根特

8

管理XSS需要多次验证,这些验证来自客户端。

  1. 在服务器端输入验证(表单验证)。有多种解决方法。您可以尝试JSR 303 bean验证(休眠验证器)或ESAPI输入验证框架。尽管我自己还没有尝试过,但是有一个注释可以检查安全的html (@SafeHtml)。实际上,您可以将Hibernate验证器与Spring MVC一起用于bean验证->参考
  2. 转义URL请求-对于所有HTTP请求,请使用某种XSS过滤器。我已将以下内容用于我们的Web应用程序,它负责清理HTTP URL请求-http: //www.servletsuite.com/servlets/xssflt.htm
  3. 将数据/ html转义返回给客户端(请参见上面的@BalusC解释)。

3

我建议定期使用自动化工具测试漏洞,并修复所发现的问题。一般而言,建议一个库来帮助解决特定漏洞要容易得多,而不是针对所有XSS攻击。

Skipfish是Google一直在研究的开源工具:它发现了很多东西,似乎值得使用。


预防胜于诊断(例如skip鱼),然后进行后续快速修复。
Sripathi Krishnan

2
我不同意。没有诊断的预防只是教条。作为CI周期的一部分运行诊断,以避免出现“快速修复”问题。
肖恩·赖利

3

没有针对XSS的简单易用的解决方案。OWASP ESAPI API对转义提供了一些非常有用的支持,并且它们具有标记库。

我的方法是基本上按照以下方式扩展stuts 2标签。

  1. 修改s:property标签,以便它可以使用额外的属性来说明需要哪种转义(escapeHtmlAttribute =“ true”等)。这涉及创建新的Property和PropertyTag类。Property类使用OWASP ESAPI api进行转义。
  2. 更改freemarker模板以使用新版本的s:property并设置转义。

如果您不想在步骤1中修改类,另一种方法是将ESAPI标签导入到freemarker模板中,然后根据需要进行转义。然后,如果需要在JSP中使用as:property标记,请使用和ESAPI标记进行包装。

我在这里写了更详细的解释。

http://www.nutshellsoftware.org/software/securing-struts-2-using-esapi-part-1-securing-outputs/

我同意转义输入不是理想的。


2

我个人的观点是,您应该避免使用JSP / ASP / PHP / etc页面。而是输出到类似于SAX的API(仅设计用于调用而不是处理)。这样,只有一个层必须创建格式正确的输出。


2

如果要自动转义所有JSP变量而不必显式包装每个变量,则可以使用EL解析器(如此处所述,包括完整的源代码和示例(JSP 2.0或更高版本),并在此处进行详细讨论:

例如,通过使用上述的EL解析器,您的JSP代码将保持不变,但是解析器会自动转义每个变量

...
<c:forEach items="${orders}" var="item">
  <p>${item.name}</p>
  <p>${item.price}</p>
  <p>${item.description}</p>
</c:forEach>
...

如果您想在Spring默认情况下强制转义,您也可以考虑这样做,但是它不会转义EL表达式,只是标记输出,我认为:

http://forum.springsource.org/showthread.php?61418-Spring-cross-site-scripting&p=205646#post205646

注意:可以在这里找到使用XSL转换预处理JSP文件的另一种EL转义方法:

http://therning.org/niklas/2007/09/preprocessing-jsp-files-to-automatically-escape-el-expressions/


嗨,布拉德,以上用例是我想要的。请您帮忙解释一下在上述情况下(如何防止发生)如何防止xss
RockingDev

我现在真正记得的唯一一个是使用EL解析器-这就是我们最终在公司中使用的解析器。基本上,它会自动转义所有内容,如果您真的不希望某个内容转义,则可以<enhance:out escapeXml="false">按照本文中的详细说明将其包装。
布拉德·帕克斯

0

如果要确保您的$操作员不会遭受XSS黑客攻击,则可以ServletContextListener在此处实施并进行一些检查。

完整的解决方案,位于:http : //pukkaone.github.io/2011/01/03/jsp-cross-site-scripting-elresolver.html

@WebListener
public class EscapeXmlELResolverListener implements ServletContextListener {
    private static final Logger LOG = LoggerFactory.getLogger(EscapeXmlELResolverListener.class);


    @Override
    public void contextInitialized(ServletContextEvent event) {
        LOG.info("EscapeXmlELResolverListener initialized ...");        
        JspFactory.getDefaultFactory()
                .getJspApplicationContext(event.getServletContext())
                .addELResolver(new EscapeXmlELResolver());

    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOG.info("EscapeXmlELResolverListener destroyed");
    }


    /**
     * {@link ELResolver} which escapes XML in String values.
     */
    public class EscapeXmlELResolver extends ELResolver {

        private ThreadLocal<Boolean> excludeMe = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return Boolean.FALSE;
            }
        };

        @Override
        public Object getValue(ELContext context, Object base, Object property) {

            try {
                    if (excludeMe.get()) {
                        return null;
                    }

                    // This resolver is in the original resolver chain. To prevent
                    // infinite recursion, set a flag to prevent this resolver from
                    // invoking the original resolver chain again when its turn in the
                    // chain comes around.
                    excludeMe.set(Boolean.TRUE);
                    Object value = context.getELResolver().getValue(
                            context, base, property);

                    if (value instanceof String) {
                        value = StringEscapeUtils.escapeHtml4((String) value);
                    }
                    return value;
            } finally {
                excludeMe.remove();
            }
        }

        @Override
        public Class<?> getCommonPropertyType(ELContext context, Object base) {
            return null;
        }

        @Override
        public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base){
            return null;
        }

        @Override
        public Class<?> getType(ELContext context, Object base, Object property) {
            return null;
        }

        @Override
        public boolean isReadOnly(ELContext context, Object base, Object property) {
            return true;
        }

        @Override
        public void setValue(ELContext context, Object base, Object property, Object value){
            throw new UnsupportedOperationException();
        }

    }

}

再说一次:这只会保护$。另请参阅其他答案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.