在JSP/Servlet Web应用程序中防止XSS攻击

83

如何在JSP/Servlet Web应用程序中防止XSS攻击?


1
如何在不同情况下防止XSS攻击的重要文章已发布在以下网址:https://dev59.com/znjZa4cB1Zd3GeqPi9hD#19943011 - user1459144
10个回答

120
在JSP中,可以通过使用JSTL <c:out>标签或fn:escapeXml() EL函数来防止XSS攻击,当重新显示用户控制的输入时。这包括请求参数、头部、Cookie、URL、正文等从请求对象中提取的任何内容。此外,需要在重新显示时对存储在数据库中的先前请求的用户控制输入进行转义。
例如:
<p><c:out value="${bean.userControlledValue}"></p>
<p><input name="foo" value="${fn:escapeXml(param.foo)}"></p>

这将转义可能使渲染的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标签,如等,则需要通过白名单对输入进行清理。您可以使用HTML解析器,例如Jsoup。但更好的方法是引入一种人性化的标记语言,例如Markdown(也在Stack Overflow上使用)。然后,您可以使用Markdown解析器,例如CommonMark。它还具有内置的HTML清理功能。另请参阅Markdown或HTML
请注意,诸如Jsoup/Markdown/Owasp之类的“消毒”术语与<c:out>之类的“转义”术语涉及到完全不同的事情。HTML消毒器基本上会清理包含可能恶意HTML的字符串,以便将其用作安全HTML 而无需转义。也就是说,当您实际上打算将用户控制的输入直接解释为HTML时,包括<div><p><img>等标签。HTML转义器基本上会阻止它们被解释,以便它们显示为纯文本。换句话说,您根本不需要预先消毒任何您已经打算转义的HTML。
服务器端唯一需要关注的是数据库方面的SQL注入预防。您需要确保您从未直接将用户控制的输入字符串连接到SQL或JPQL查询中,并且始终使用参数化查询。在JDBC术语中,这意味着您应该使用PreparedStatement而不是Statement。在JPA术语中,请使用Query
一个替代方案是从JSP/Servlet迁移到Java EE的MVC框架JSF。它在各个地方都内置了XSS(和CSRF!)预防措施,因此您不需要手动调整<c:out>和相关内容。还可以参考JSF中的CSRF、XSS和SQL注入攻击预防措施

1
仅仅因为你使用了Hibernate,并不意味着你就可以免于SQL注入攻击。例如,请参考http://blog.harpoontech.com/2008/10/how-to-avoid-sql-injection-in-hibernate.html。 - Tyler
5
我认为你也需要在服务器端进行验证。所有的验证都可以通过更改HTTP参数来绕过。有时,在企业应用程序中,您持久化的数据可能会被其他应用程序使用。有时您无法访问其他应用程序的视图,因此在将输入持久化到数据库之前需要对其进行清理。 - Guido Celada
1
@Guido:你没有理解问题。 - BalusC
@BalusC:嗨Bal,通过使用您的建议,我在我的应用程序中解决了4个XSS问题,非常感谢您提供的有用解决方案。这里我再次需要您的建议,因为我正在使用jstl表达式显示dom对象,例如<div><c:out value="${htmlContent}" escapeXml="false" /></div>,这里的htmlContent值类似于<span><h1>Hi</h1></span>(此值来自数据库),现在如果我从c:out中删除escapeXml="false",则它将按原样显示在页面上,但是如果保留escapeXml="false",则它将正确解析dom对象,但是当我的htmlContent包含一些脚本代码时,xss问题就会出现。 - Venkaiah Yepuri
2
@peater:没错,当将不受信任的数据放入JS代码中时,需要进行JS编码而不是HTML编码。当将不受信任的数据放入CSS代码中时,需要进行CSS编码而不是HTML编码。当将不受信任的数据放入URL中时,需要进行URL编码而不是HTML编码。HTML编码仅应用于将不受信任的数据放入HTML代码中。 - BalusC
显示剩余8条评论

12

我在所有的Spring Controllers上使用OWASP Anti-Samy和一个AspectJ advisor,成功地阻止了跨站脚本攻击(XSS)。

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顾问。
如果你使用大量的javascript,我认为这是比c:out更好的方法。

通常的做法是在重新显示用户可控数据时进行HTML转义,而不是在servlet处理提交的数据或存储在DB中进行转义。 如果在处理提交的数据和/或存储在DB时也进行HTML转义,则所有内容都分散在业务代码和/或数据库中,这只会增加维护的难度,并且在不同地方进行转义时会存在双重转义或更多的风险。业务代码和DB本身对XSS不敏感,只有视图才需要进行转义。因此您应该仅在视图中进行转义。 - Shubham Maheshwari
1
是的和不是的。尽管一般的做法是在显示时转义,但有许多原因你可能想要在写入时进行清理。有些情况下,你确实希望用户输入 HTML 的子集,虽然你可以在显示时进行清理,但这实际上相当缓慢,甚至会令用户感到困惑。其次,如果你与第三方服务(如外部 API)共享数据,那么这些服务可能会或可能不会自行进行适当的清理。 - Adam Gent
正如你和我都提到的,“正常做法”是在显示时进行转义。你在上面的评论中提到的更具体的用例,因此需要特定的解决方案。 - Shubham Maheshwari
是的,也许我应该更清楚地阐明我的用例。我主要处理内容管理(HTML编辑)方面的事情。 - Adam Gent

12

如何防止 XSS 已被多次询问。您可以在 StackOverflow 上找到许多信息。此外,OWASP 网站有一个 XSS 预防备忘单,建议您仔细阅读。

关于要使用的库,OWASP 的 ESAPI 库 有一个 Java 版本。建议尝试一下。此外,您使用的每个框架都有一些防止 XSS 的保护措施。同样,OWASP 网站提供了大部分流行框架的信息,因此我建议您浏览他们的网站。


3
OWASP防范作弊表已经迁移到GitHub了。这是防止跨站脚本攻击作弊表的链接 https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md。请注意,不要改变原文内容,只需使其更加通俗易懂。 - peater

8
管理XSS需要客户端数据的多个验证。
  1. 在服务器端进行输入验证(表单验证)。有多种方法可以实现。您可以尝试使用JSR 303 bean验证(Hibernate Validator),或者ESAPI输入验证框架。虽然我自己还没有尝试过它,但是有一个检查安全html的注释(@SafeHtml)。事实上,你可以在Spring MVC中使用Hibernate validator进行bean验证 -> 参考
  2. 转义URL请求 - 对于所有的HTTP请求,使用某种XSS过滤器。我们的Web应用程序使用以下内容,并且它会清理HTTP URL请求 - http://www.servletsuite.com/servlets/xssflt.htm
  3. 转义数据/HTML返回给客户端(请参见@BalusC的解释)。

3
我建议定期使用自动化工具测试漏洞,并修复它所发现的问题。推荐使用针对特定漏洞的库,而不是通用的 XSS 攻击库。 Skipfish 是一款来自谷歌的开源工具,我正在调研它:它能找到很多东西,值得使用。

预防胜于治疗(例如,跳鱼),随后进行快速修复。 - Sripathi Krishnan
2
我不同意。预防而不诊断只是教条主义。在CI周期中运行诊断以避免“松散修补”问题。 - Sean Reilly

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中使用s:property标签,请使用ESAPI标签进行包装。

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

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

我同意转义输入并不理想。


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 吗? - MiniSu
我现在唯一记得的是使用EL解析器 - 这就是我们公司最终采用的。基本上它会自动转义所有内容,如果你真的不想转义某些内容,可以按照文章中所述,将其包装在<enhance:out escapeXml="false">中。 - Brad Parks

2
我的个人意见是,您应该避免使用JSP/ASP/PHP等页面。相反,输出到类似于SAX的API(只用于调用而非处理)。这样就只需要创建一个单一的层来创建规范格式的输出。

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();
        }

    }

}

再次强调:这只保护了$。请参考其他答案。


-1
<%@ page import="org.apache.commons.lang.StringEscapeUtils" %>
String str=request.getParameter("urlParam");
String safeOuput = StringEscapeUtils.escapeXml(str);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接