Java代码审计----<OWASP TOP 10 2017>

Java代码审计----

1. 注入

1.1 SQL注入

  1. jdbc拼接不当

    jdbc有两种执行sql语句的方法:Statement和PrepareStatement。区别在于PrepareStatement会进行预编译,Statement每次都会进行编译

    如下代码,使用拼接方式,将参数”id“的值带入SQL语句中,使用Statement对象,执行SQL语句。这时候,如果攻击者构造”1 or 1 = 2“参数,即可判断出改代码中存在sql注入。

    // 伪代码
    //获取参数,未经验证就拼接到sql语句中
    String sql = "select * from user where id = " + req.getParameter("id");
    // 获取Statement对象
    Statement st = con.createStatement();
    //执行语句
    ResultSet rs = st.executeQuery(sql);
    

    PrepareStatement可以使用”?“进行占位,在预编译阶段填入相应的值构造出完整的SQL语句,从而避免SQL注入到产生。

    但是,如果开发者还是采用拼接的方式构造SQL语句,还是会造成SQL注入的产生。

    // 伪代码
    //获取参数,未经验证就拼接到sql语句中
    String sql = "select * from user where id = " + req.getParameter("id");
    // 获取PrepareStatement对象
    PrepareStatement ps = con.prepareStatement(sql);
    //执行语句
    ResultSet rs = ps.executeQuery();
    

    正确的使用PrepareStatement,应该是使用"?"进行占位,填入对应值的时候,会检查参数类型,如下代码

    // 伪代码
    //获取参数,未经验证就拼接到sql语句中
    String sql = "select * from user where id = ?";
    // 获取PrepareStatement对象
    PrepareStatement ps = con.prepareStatement(sql);
    //填入参数
    ps.set(1,Integer.parseInt(req.getParameter("id")));
    //执行语句
    ResultSet rs = ps.executeQuery();
    
  2. 框架适用不当

开发中一般不会直接使用jdbc,通常会借助持久化框架完成数据库操作,常用的持久化框架有:MyBatis

MyBatis传参一般有两种方式:#{Parament}和${Parament}

#{Parament}采用的是预编译的方式构造SQL语句,避免了SQL注入的产生。${Parament}采用的是拼接的方式构造SQL语句,如果对用户输入过滤不严的话,易造成SQL注入。

<select id="getUsername" resultType="User">
 select id,name,age from user where id=#{id}
 // select id,name,age from user where id=${id}
select>

SQL注入的最主要的成因是:未对用户输入的参数进行严格过滤,采取不当的方式构造SQL语句。应严格检查输入的参数类型、参数格式等是否符合程序要求。

1.2 命令注入

在开发过程中,需要系统本地命令来完成某些特定功能,如果未对参数进行严格过滤,则可能发生命令注入。

java的Runtime可以调用系统命令。

//伪代码
//从参数中获取cmd命令。
String cmd = req.getParameter("cmd");
//执行命令,但未进行过滤
Process process = Runtime.getRuntime().exec(cmd);

系统命令的连接符号:

  • |:前面命令的输出结果作为后面命令的的输入内容
  • ||:前面命令执行失败后时才执行后面的命令
  • &:前面的命令执行完再执行后面的命令
  • &&:前面的命令执行成功才执行后面的命令

在java环境中,连接符的使用存在一些限制,如下代码。参数cmd用户可控,假设用户输入”www.biadu.com&ipconfig“,拼接成的命令为”ping www.biadu.com&ipconfig“,该命令在系统环境下可以执行,但是在java环境下去执行失败。

原因是exec函数会按照空格将命令切割,然后用数组保存分割后的元素,数组的第一个元素为要执行的命令,后面的内容将被当作参数。因此”www.biadu.com&ipconfig“会被整体当作参数,”&“未能生效。

//伪代码
//从参数中获取cmd命令。
String cmd = "ping " + req.getParameter("cmd");
//执行命令,但未进行过滤
Process process = Runtime.getRuntime().exec(cmd);

1.3 代码注入

程序如果可以将用户输入内容当作带代码执行,那就容易造成命令注入。

借用java的反射,可实现这样的功能:根据用户输入类名、方法名、参数执行不同的功能。如下代码:

//伪代码
//获取参数
String classname = req.getParameter("classname");
String methodName = req.getParameter("methodName");
String[] args = new String[](req.getParameter("args").toString);
//获取类对象
Class clazz = Class.forName(classname);
//获取构造方法对象
Constructor constructor = clazz.getConstructor(String[].class);
//使用构造方法创建对象
Object obj = constructor.newInstance(new Object[]{args});
//获取方法对象
Method method = clazz.getMethod(methodName);
//执行obj对象中的方法
method.invoke(obj);

2. 失效的身份认证漏洞

“失效的身份认证”是指错误的使用应用程序的身份认证和会话管理,使攻击者可以破译密码、密钥或会话令牌,或者利用利用其他漏洞暂时或长久的冒充其他用户的身份,导致攻击者可以执行受害用户的任何操作。

在进行身份认证的漏洞挖掘的时候,可以采用“黑白结合”的方式进行审计。如下案例。

JWT全称是JSON Web Token是一个开放标准(RFC 7519),目前最流行的跨域身份验证解决方案。它定义了一种经过加密的格式,放在json对象在请求中传递,用于验证请求是否被允许访问

//伪代码
//JWT_PASSWORD定义,可分析出密钥
Public static final String JWT_PASSWORD = TextCodec.BASE64.encode("viotory");
……
//使用密钥解析accessToken
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
Claims cliams = jwt.getBody();
//若isAdmin为true,则身份认证通过
boolean isAdmin = Boolean.valueof((String) claims.get("admin"))
……

当对关键代码进行审计后,可分析出身份认证信息的加密密钥,结合抓包测试,伪造修改身份信息,可实现越权操作。

3. 敏感信息泄露

敏感信息是业务系统中对保密行要求较高的数据,包括系统敏感信息和应用敏感信息。

**系统敏感信息:**是指业务系统本身的基础环境信息,包括系统信息、中间件版本、代码信息等,这些信息泄露可为攻击者提供更多的攻击途径。

**应用敏感信息:**可进一步分为个人信息和非个人信息。

以Turbomail 5.2.0敏感信息泄露为例,在执行某类中的show方法时,没有进行身份验证,因此构造payload,访问该方法,可获取当前已登录用户的邮箱。

若开发人员未“自定义错误页面”,那就可能将网站的敏感信息暴露到前端。

4. XML外部实体注入(XXE)

当开发人员配置XML解析功能允许外部实体引用时,攻击者可利用这一点实施任意文件读取、内网端口探测、命令执行、拒绝服务攻击等攻击行为。

**实体:**指DTD实体,它是用于定义引用普通文本或特殊字符的快捷方式的变量。

**外部:**实体可分为内部实体和外部实体,内部实体定义格式为:

<!ENTITY 实体名称 “实体值”>

外部实体定义格式为:


<!ENTITY 实体名称 PUBLIC "public_id" “URI”>

JAVA默认提供对http、https、ftp、file、jar、netdoc、mailto、gopher等协议的支持。

**注入:**攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命令。

4.1 读取系统文件

如下代码是OpenRAST的测试用例007-xxe.jsp。

<%@ page contentType="text/html; charset=UTF-8" %>
<%
    String url = new String(request.getRequestURL());
    String baseUrl = url.substring(0, url.lastIndexOf("/"));
    String linux_querystring = "/xxeDom?data=%3C%3F%78%6D%6C%20%76%65%72%73%69%6F%6E%3D%22%31%2E%30%22%20%65%6E%63%6F%64%69%6E%67%3D%22%49%53%4F%2D%38%38%35%39%2D%31%22%3F%3E%3C%21%44%4F%43%54%59%50%45%20%66%6F%6F%20%5B%20%20%20%3C%21%45%4C%45%4D%45%4E%54%20%66%6F%6F%20%41%4E%59%20%3E%20%20%3C%21%45%4E%54%49%54%59%20%78%78%65%20%53%59%53%54%45%4D%20%22%66%69%6C%65%3A%2F%2F%2F%65%74%63%2F%70%61%73%73%77%64%22%20%3E%5D%3E%3C%66%6F%6F%3E%26%78%78%65%3B%3C%2F%66%6F%6F%3E";
    String windows_querystring = "/xxeDom?data=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22ISO-8859-1%22%3F%3E%3C%21DOCTYPE%20foo%20%5B%20%20%20%3C%21ELEMENT%20foo%20ANY%20%3E%20%20%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fc%3A%2Fwindows%2Fwin.ini%22%20%3E%5D%3E%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E";
%>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>007 XXE 漏洞测试title>
head>

<body>
<h1>007 - 通过XXE读取系统文件h1>

<p>不正常调用 - Linux (读取 /etc/passwd)p>
<p>curl '<a href="<%=baseUrl+linux_querystring%>" target="_blank"><%=baseUrl + linux_querystring%>
a>'p>

<p>不正常调用 - Windows (读取 c:/windows/win.ini)p>
<p>curl '<a href="<%=baseUrl+windows_querystring%>" target="_blank"><%=baseUrl + windows_querystring%>
a>'p>
<p>(有漏洞会看到文件内容)p>
body>
html>

分析代码,将字符串linux_querystring进行解码,可得以下字符串:

DOCTYPE foo [   <!ELEMENT foo ANY >  ]><foo>&xxe;foo>

将字符串windows_querystring进行解码,可得以下字符串:

DOCTYPE foo [   <!ELEMENT foo ANY >  ]><foo>&xxe;foo>

4.2 DoS攻击

利用DTD可以产生XML炸弹,迅速占用大量内存,如下为例:
当XML解析器尝试解析该文件时,由于DTD的定义指数级展开,这个1K不到的文件会占用到3G的内存。


DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"``>
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;lolz>

还有一种,虽然扩展率没那么大,也很有效。200K的能够扩展到2.5G。


DOCTYPE kaboom [
<!ENTITY a "aaaaaaaaaaaaaaaaaa...">
]>
<kaboom>&a;&a;&a;&a;&a;&a;&a;&a;&a;...kaboom>

4.3 微信支付的XXE漏洞

原文章http://www.manongjc.com/detail/51-ifstdocjxygkomf.html

“微信支付的xxe漏洞爆发点就在xmltoMap方法中。该方法是为了将xml格式的字符串strXML转化为map。由于strXML可由攻击者控制,且程序未作任何防护措施(如禁止引用外部实体;过滤关键字符串等),导致恶意攻击者可利用外部实体注入读取服务器上的文件。当攻击者获取支付加密所用的安全密匙后,完全可以实现0元支付商品。”

5. 失效的访问控制

访问控制一旦失效通常会导致未认证信息泄露、内部数据篡改、数据删除和越权操作等后果。

如果正常用户可对自己的信息进行增删查改,但是开发者未对这些操作进行访问控制,使平级攻击用户可以控制这些操作,这就造成了越权。在代码审计的过程中,就判断代码中是否对请求进行了访问控制。

6. 安全配置错误

安全配置错误可能发生在一个应用的任何层面,包括网络服务、平台、Web服务器、应用服务器、数据库、框架、代码、容器、存储等。

6.1 Tomcat任意文件写入(cve-2017-12615)

直接使用 PUT 发起请求就可以上传任意文件,比如向 /1.jsp/ 发起请求,请求体中,写文件内容,就可以在服务器中创建该文件。

Tomcat 在处理时有两个默认的 Servlet,分别为 DefaultServlet 和 JspServlet。从配置文件里可以看到对于后缀为 .jsp 和 .jspx 的请求由 JspServlet 处理,而其他的请求则由 DefaultServlet 处理。所以当请求 /1.jsp 时将会由 JspServlet 处理,无法触发漏洞;而请求 /1.jsp/ 将绕过这个限制,交由 DefaultServlet 处理,这时就可以触发漏洞了。

PUT请求将交由Servlet中的doPut方法处理,找到 DefaultServlet 里重写的 doPut 方法

//伪代码
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //如果readOnly为false,将不会执行文件的操作
    if (this.readOnly) {
        resp.sendError(403);
    } else {
        //获取相对路径
        String path = this.getRelativePath(req);
        
        WebResource resource = this.resources.getResource(path);
        DefaultServlet.Range range = this.parseContentRange(req, resp);
        Object resourceInputStream = null;
		try {
            if (range != null) {
                File contentFile = this.executePartialPut(req, range, path);
                resourceInputStream = new FileInputStream(contentFile);
            } else {
                resourceInputStream = req.getInputStream();
            }

            if (this.resources.write(path, (InputStream)resourceInputStream, true)) {
                if (resource.exists()) {
                    resp.setStatus(204);
                } else {
                    resp.setStatus(201);
                }
            } else {
                resp.sendError(409);
            }
        } finally {
            if (resourceInputStream != null) {
                try {
                    ((InputStream)resourceInputStream).close();
                } catch (IOException var13) {
                }
            }
        }
    }
}

readOnly值来自web.xml中,因此,该漏洞的修复方式是在xml的DefaultServlet 的配置部分,添加如下配置:

<init-param>
    <param-name>readonly
    <param-value>falseparam-value>
init-param>

7. 跨站脚本

XSS漏洞是指攻击者在网页中嵌人客户端脚本(通常是JavaScript编写的恶意代码),进而执行其植入代码的漏洞。

7.1 反射型XSS

反射型XSS漏洞通过外部输人,然后直接在浏览器端触发。我们需要寻找带有参数的输出方法,然后根据输出方法对输出内容回溯输入参数

如下JSP代码:

//伪代码
<%
//从请求中获得"name”参数
String name = request.getParameter("name");
//从请求中获得“学号”参数
String studentId = request.getParameter ("sid") ;
//将参数输出到前端
out. println("name = " +name) ;
out . println ("studentId = "+studentId) ;
%>

构建恶意payload:sid=31&&name=jj%3Cscript%3Ealert(1)%3C/script%3E,即可验证该漏洞。

这份 JSP 代码会将变量 name 与 studentId 输出到前端,而这两个变量是从HttpServletRequest 请求对象中取得的。由于这份代码并未对输入和输出数据进行过滤、扰乱以及编码方面的工作,因为无法对 XSS 漏洞进行防御。

7.2 存储型XSS

针对存储型 XSS ,攻击者需要将payload保存在数据库或者文件中,当 Web 程序读取payload并输出在页面时执行恶意代码。

在审计存储型XSS时,需要寻找输入点和输出点,输入点和输出点很可能不在一个业务系统中,需要黑白盒结合进行挖掘。

获取输入点时可参考如下步骤:

  1. 先通过黑盒测试,寻找存在存储型XSS的漏洞点。
  2. 然后结合抓包工具,确认接口。
  3. 通过接口,确认源码位置,对源码进行审计。

在获取输入点后,可根据参数名等查找输出点。

7.3 DOM型 XSS

客户端的脚本程序可以通过 DOM 动态地操作和修改页面内容。DOM 型 XSS漏洞不需要与服务器交互,它只发生在客户端处理数据阶段。DOM XSS漏洞的成因是不可控的危险数据,未经过滤被传入存在缺陷的 JavaScript 代码处理。

<script> 
 //返回url中#号的位置。
 var pos = document.URL.indexOf("#")+1; 
 //将#号后面的字符串切断。
 var name = document.URL.substring(pos, document.URL.length); 
//向文档写 HTML 表达式 或 JS 代码。
 document.write(name); 
//eval可以将接受的字符串转换成js表达式并且立即执行该表达式。
 eval("var a = " + name); 
</script>

构造payload:url+#1;alert(/safedog/) ,即可验证漏洞。

DOM型XSS,常见输入点:

  • document.URL:返回网页完整的URL
  • document.location:用于获取或设置当前文档的 URL 地址
  • document.refer:返回载入当前网页的网页URL。
  • document.from:返回对文档中所有 Form 对象引用。

常见输出点:

  • eval:可以将接受的字符串转换成js表达式并且立即执行该表达式。
  • document.write:向文档写 HTML 表达式 或 JavaScript 代码。
  • document.InnerHTML:设置或返回调用元素开始结束标签之间的HTML元素。
  • document.OuterHTML:用于获取当前文档的 HTML 源代码。

8. 不安全的反序列化

8.1 反序列化基础

Java代码审计----<OWASP TOP 10 2017>_第1张图片

  1. Serializable是一个标记接口,能够序列化的类必须实现Serializable接口。

    public class Throwable implements Serializable { 
     /** 使用 JDK 1.0.2 中的 serialVersionUID 实现互操作性 */ 
     private static final long serialVersionUID = -3042686055658047285L; 
     private transient Object backtrace; 
    private String detailMessage; 
    \\省略
    }
    

    Throwable实现了Serializable接口,其中:

    • serialVersionUID作为版本号信息,若在不同系统中该属性值不相等,则无法进行反序列化。

    • Transient 关键字用于标记该属性不希望进行序列化。

  2. Externalizable 是Serializable的子类,包含writeExternal()和readExternal()方法,分别在序列化和反序列化的时候自动调用。开发者可以在这两个方法中添加一些操作,以便在反序列化和序列化的过程中完成一些特殊的功能。

  3. 在 Java 原生的API 中,序列化的过程由 ObjectOutputStream 类的 writeObject()方法实现,反序列化过程由 ObjectInputStream 类的 readObject()方法实现。

  4. 将字节流还原成对象的过程都可以称作反序列化,例如,JSON 串或 XML 串还原成对象的过程也是反序列化的过程。同理,将对象转化成 JSON 串或 XML 串的过程也是序列化的过程。

在反序列化的过程中,一个字节流将按照二进制结构被反序列化成一个对象。当开发者重写readObject 方法或 readExternal 方法时,其中如果隐藏有一些危险的操作且未对正在进行序列化的字节流进行充分的检测时,则会成为反序列化漏洞的触发点。

8.2 Apache Commons Collections 反序列化漏洞

  1. 核心原理:在 org/apache/commons/collections/functors/InvokerTransformer#transform 中存在一段利用反射技术执行任意 Java 代码的代码,如下所示,当 input 变量可控时,可以通过反射执行任意类的任意方法。
//伪代码
public Object transform(Object input) { 
     if (input == null) { 
     	return null; 
     } else { 
         try { 
         Class cls = input.getClass(); 
         Method method = cls.getMethod(this.iMethodName, this.iParamTypes); 
         return method.invoke(input, this.iArgs); 
         } 
     \\省略
     } 
}

这后面的具体内容属实看不懂了,真奔溃,等写完后面的两项再回过头搞搞反序列化吧,/(ㄒoㄒ)/~~

9. 使用含有已知漏洞的组件

根据中间件版本去CNVD、CNNVD、CVE、NVD查找已知漏洞。

10.不足的日志记录和监控

不足的日志记录和监控,以及事件响应缺失或无效的集成,这类漏洞使攻击者能够进一步攻击系统、保持持续性或转向更多系统,以及篡改、提取或销毁数据。大多数缺陷研究显示,缺陷被检测出的时间会超过 200 天,且通常通过外部检测方检测,而不是通过内部流程或监控检测。

最基础的日志记录可涵盖以下信息。

  1. 事件发生的时间。
  2. 用户 ID。
  3. 访问发起端地址或标识(如关联终端、端口、网络地址或通信设备)。
  4. 事件类型。
  5. 被访问的资源名称。
  6. 事件的结果。

你可能感兴趣的:(代码审计,java,安全性测试,web安全)