程序员科普小课堂:应用安全防护ESAPI

作者:Uncle_Tom

原文链接:应用安全防护ESAPI-云社区-华为云

1. ESAPI 简介

OWASP Enterprise Security API (ESAPI)是一个免费、开源的web应用程序安全控制库,使程序员更容易编写风险较低的应用程序。ESAPI库旨在使程序员更容易对现有应用程序进行安全性改造。ESAPI库也是新开发的坚实基础。

考虑到特定语言的差异,所有OWASP ESAPI版本都有相同的基本设计:

  • 有一组安全控制接口。例如,定义了传递给安全控件类型的参数类型。
  • 每个安全控制都有一个参考实现。例如:基于字符串的输入验证。例如,Java 的 org.owasp.ESAPI.reference.FileBasedAuthenticator 的 ESAPI,而其他参考实现则是成熟的企业级参考实现,例如,org.oasp.ESAPI.reference.DefaultEncoder 或 org.owasp.ESAPI.reference.DefaultValidator。
  • 每个安全控件都有自己的实现(可选)。这些类中可能包含应用程序逻辑,这些逻辑可能由您的组织开发或为您的组织而开发。例如:企业身份验证。
  • 为使本项目尽可能易于传播并使更多人能够自由自用,本项目的源代码使用了 BSD 许可证。本项目的文档使用了知识共享署名许可证。你可以随意使用、修改ESAPI,甚至将它包含在商业产品中。

2. ESAPI 框架

  • OWASP ESAPI 已经实现下面安全控件

    • 身份认证
    • 访问控制
    • 输入验证
    • 输出编码/转义
    • 密码
    • 错误处理和日志
    • 通信安全
    • HTTP 安全
    • 安全配置
  • ESAPI 框架

  • ESAPI 覆盖的OWASP Top 10

3. ESAPI 的使用

3.1. ESAPI 在 pom.xml 中的配置

目前最新的版本是: 2.5.3.1, 可以直接在Maven 库中找到。

xml 复制代码
<!-- https://mvnrepository.com/artifact/org.owasp.esapi/esapi -->
<dependency>
  <groupId>org.owasp.esapi</groupId>
  <artifactId>esapi</artifactId>
  <version>2.5.3.1</version>
  <exclusions>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

3.2. 注入类问题的防护

网络安全问题很大一部分是由于注入类问题造成的。这类问题主要是由于对外部输入使用的过程中转码不当造成的,例如:SQL 注入、命令注入、跨站脚本等等。

Encode(编码器接口)包含了许多解码输入和编码输出的方法,这样处理过的字符对于各种解释器都是安全的。

3.2.1. 防护:XSS 跨站脚本攻击

  • HTML编码器(encodeForHTML)
ini 复制代码
@Test
void testEncodeForHTML() {
    String input = "<a href='sdfs'></a> < script > alert('xss'); </ script >";
    String encodedString = ESAPI.encoder().encodeForHTML(input);

    LOG.info("EncodeForHTML: {}", encodedString);
}
  • 输出:
xml 复制代码
EncodeForHTML: &lt;a href&#x3d;&#x27;xss&#x27;&gt;&lt;&#x2f;a&gt; &lt; script &gt; alert&#x28;&#x27;xss&#x27;&#x29;&#x3b; &lt;&#x2f; script &gt;
  • URL编码器(encodeForURL)
typescript 复制代码
@Test
void testEncodeForURL() {
    String input = "/?callback=<script>alert('xss')</script>";
    String encodedString;
    try {
        encodedString = ESAPI.encoder().encodeForURL(input);
        LOG.info("EncodeForURL: {}", encodedString);
    } catch (EncodingException e) {
        fail("Should not get exception:" + e.getMessage());
    }
}
  • 输出:
perl 复制代码
EncodeForURL: %2F%3Fcallback%3D%3Cscript%3Ealert%28%27xss%27%29%3C%2Fscript%3E

3.2.2. 防护:SQL 注入

typescript 复制代码
@Test
void testEncodeForSQL() {
    String userId = "tom' or '1=1'";
    String sql = "select * from user where user='"
            + ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD), userId) + "'";

    LOG.info("sql = {}", sql);
}
  • 输出:
sql 复制代码
sql = select * from user where user='tom' or '1=1''

3.2.3. 防护:命令注入

typescript 复制代码
@Test
void testEncodeForOS() {
    String input = "dir & dir /s";
    String cmd = ESAPI.encoder().encodeForOS(new WindowsCodec(), input);

    LOG.info("cmd = {}", cmd);
}
  • 输出:
bash 复制代码
cmd = dir^ ^&^ dir^ ^/s

3.3. 输入校验问题的防护

网络安全最大的威胁是外部输入,所以对外部输入的校验对应用安全起着最大的防护作用。

3.3.1. ESAPI的输入校验

ESAPI有一个输入校验配置: validation.properties 给出了常用的校验。

  • validation.properties
ini 复制代码
Validator.SafeString=^[.\p{Alnum}\p{Space}]{0,1024}$
Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\.[a-zA-Z]{2,4}$
Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
Validator.URL=^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\:\'\/\\\+=&;%\$#_]*)?$
Validator.CreditCard=^(\d{4}[- ]?){3}\d{4}$
Validator.SSN=^(?!000)([0-6]\d{2}|7([0-6]\d|7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$
  • 校验接口
typescript 复制代码
/**
  * 输入校验
  * 
  * @param context 校验内容
  * @param input 校验输入  
  * @param type 校验类型,对应到 validation.properties 中的类型
  * @param maxLength 输入字符最大长度校验
  * @param allowNull 输入字符Null值校验,false - 不允许;true - 允许
  * @return 校验失败返回 false,校验成功返回 true
  */
boolean ESAPI.validator().getValidInput(String context, String input, String type, int maxLength, boolean allowNull);
typescript 复制代码
@Test
void testValidatorEmail() {
    String input = "xxxx.com";
    if (!ESAPI.validator().isValidInput("", input, "Email", 11, false)) {
        LOG.error("Email validate fail!");
    } else {
        LOG.info("Email is validate.");
    }
}
  • 输出:

    Email validate fail!

4. ESAPI 使用和升级过程中常遇到的问题

4.1. ExceptionInInitializerError 造成的 org.owasp.esapi.reference.DefaultEncoder CTOR threw exception

less 复制代码
org.owasp.esapi.errors.ConfigurationException: java.lang.reflect.InvocationTargetException Encoder class (org.owasp.esapi.reference.DefaultEncoder) CTOR threw exception.
 at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:129)
 at org.owasp.esapi.ESAPI.encoder(ESAPI.java:101)
 at com.test.esapi.EsapiTest.testUpdateJulietInfo_good(EsapiTest.java:19)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.reflect.InvocationTargetException

 at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:86)
 ... 71 more
Caused by: java.lang.ExceptionInInitializerError
 at java.base/java.lang.Class.forName0(Native Method)
 at java.base/java.lang.Class.forName(Class.java:375)
 at org.owasp.esapi.util.ObjFactory.loadClassByStringName(ObjFactory.java:158)
 at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:81)
 at org.owasp.esapi.ESAPI.logFactory(ESAPI.java:139)
 at org.owasp.esapi.ESAPI.getLogger(ESAPI.java:155)
 at org.owasp.esapi.reference.DefaultEncoder.(DefaultEncoder.java:85)
 at org.owasp.esapi.reference.DefaultEncoder.(DefaultEncoder.java:109)
 at org.owasp.esapi.reference.DefaultEncoder.getInstance(DefaultEncoder.java:68)
 ... 76 more
Caused by: org.owasp.esapi.errors.ConfigurationException: Unable to locate resource: esapi-java-logging.properties
 at org.owasp.esapi.logging.java.JavaLogFactory.readLoggerConfiguration(JavaLogFactory.java:128)
 at org.owasp.esapi.logging.java.JavaLogFactory.(JavaLogFactory.java:96)
 ... 85 more

这个是 ESAPI 升级到 2.5.0.0 之后的版本时,最常碰到的问题。

2.5.0.0 是 ESAPI 一个重要的变动版本,我们从版本变更信息中可以看到:

  • 此版本 ESAPI 全面放弃了被 Log4J 不断的漏洞困扰的 Log4J 的支持,转而使用 SLF4J。如果您的 ESAPI.Logger 属性设置为使用 Log4J,如果不更改它,将引发模糊的 Exceptions 或 Errors,通常为 ExceptionInInitializerError。
  • 对 AntiSamy 升级到了 1.7.0,并支持了 AntiSamy 自定义的 AntiSamy-sapi.xml 文件。
  • 从上一个版本开始,此版本仅支持 Java 8 或更高版本。

从这里可以看到 ExceptionInInitializerError 应该是变更日志组件造成的,熟悉的朋友会立刻想到 ESAPI 的配置文件 ESAPI.properties 里面给出所有组件的配置。

从图中 69 到 72 行可以看到,69 行的:

ini 复制代码
ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory

还是指向 org.owasp.esapi.logging.java.JavaLogFactory, 后面的注释也提醒我们需要修改成 71 行的:

ini 复制代码
ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory

修改后问题解决。

4.2. java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest

css 复制代码
java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest
 at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
 at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3402)
 at java.base/java.lang.Class.getMethodsRecursive(Class.java:3543)
 at java.base/java.lang.Class.getMethod0(Class.java:3529)
 at java.base/java.lang.Class.getMethod(Class.java:2225)
 at org.owasp.esapi.util.ObjFactory.loadMethodByStringName(ObjFactory.java:196)
 at org.owasp.esapi.util.ObjFactory.findSingletonCreateMethod(ObjFactory.java:173)
 at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:84)
 at org.owasp.esapi.ESAPI.validator(ESAPI.java:192)
 at com.huawei.hwe.esapi.EsapiTest.testEsapi_encodeForURL(EsapiTest.java:23)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest
 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
 at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
 at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
 ... 79 more

这个问题明显是缺少了依赖库。我们可以查看 Maven 库关于 ESAPI 的定义和依赖信息。
mvnrepository.com/artifact/or...

  • 编译依赖
  • 运行依赖

从运行依赖可以看到需要:javax.servlet >> javax.servlet-api

点开后面的版本号:3.1.0,就可以得到 javax.servlet-api 3.1.0 的 mvn 依赖配置,将这个配置加到工程的 pom.xml 文件中就可以了。

在 pom.xml 中加入 javax.servlet-api 配置后,问题解决。

xml 复制代码
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
相关推荐
bobz9652 分钟前
ovs patch port 对比 veth pair
后端
Asthenia041212 分钟前
Java受检异常与非受检异常分析
后端
uhakadotcom26 分钟前
快速开始使用 n8n
后端·面试·github
JavaGuide32 分钟前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz96543 分钟前
qemu 网络使用基础
后端
Asthenia04121 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04121 小时前
Spring 启动流程:比喻表达
后端
Asthenia04122 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua2 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫
致心2 小时前
记一次debian安装mariadb(带有迁移数据)
后端