内容简介:【JSP代码审计】某商城几处漏洞审计分析
0x00 前言
前段时间在测试某商城系统时,发现使用了某通用CMS,本着学习的态度,于是下载下来对源码进行分析。
因为自己懒得搭环境,喜欢实战环境,所以直接找官方Demo,因为漏洞提交至今未修复,为了不涉及某商城系统,故对截图进行了打码。
0x01 漏洞分析
远程代码执行
打开源码包,看到这个,struts2架构,远古时期,包都不用解,一看就知道ST2-16。
不搭环境,带上工具,直接官网Demo
任意文件上传
struts2架构,相关页面关联信息都写在了xml里面,找起来方便
我们看看上传页面的前端文件upload.jsp
<script type="text/javascript"> $(function() { var appType = $('#appType').val(); var url = ctx + '/FileUpload!save.do?appType='+appType; var fileDesc = ''; var fileExt = ''; if(appType == 'magazine'){ fileDesc = '支持格式:doc/docx/pdf/rar/zip/txt.'; fileExt = '*.doc;*.docx;*.pdf;*.rar;*.zip;*.txt'; }else{ fileDesc = '支持格式:jpg/gif/jpeg/png/bmp.'; fileExt = '*.jpg;*.gif;*.jpeg;*.png;*.bmp'; } $("#fileupload").uploadify({ 'uploader' : ctx + '/scripts/framework/uploadify/uploadify.swf', 'script' : url, //servlet的路径或者.jsp 'cancelImg' : ctx + '/scripts/framework/uploadify/cancel.png', 'fileDataName' : 'fileupload', //必须 'queueID' : 'fileQueue', 'auto' : false, //选定文件后是否自动上传,默认false 'multi' : false, //是否允许同时上传多文件,默认false 'simUploadLimit' : 1, //一次同步上传的文件数目 'sizeLimit' : 2000000, //设置单个文件大小限制,单位为byte 'queueSizeLimit' : 5, //限制在一次队列中的次数(可选定几个文件)。默认值= 999,而一次可传几个文件有 simUploadLimit属性决定。 'fileDesc' : fileDesc, //如果配置了以下的'fileExt'属性,那么这个属性是必须的 'fileExt' : fileExt, //允许的格式 onComplete: function (event, queueID, fileObj, response, data) { var arrTemp = response.split(','); var idStr = arrTemp[0]; var picPath = arrTemp[1]; if('false'==idStr){ idStr = ''; alert("文件:" + fileObj.name + "上传失败"); }else{ alert("文件:" + fileObj.name + "上传成功"); if('Good'== appType || 'GoodAlbum' == appType || 'magazine' == appType || 'Packaging' == appType || 'Promote' == appType || 'promotionActivity'==appType || 'gift'==appType || 'AdvertisePic'==appType ||'goodType' ==appType ||'GoodBrand'==appType || 'Customer'==appType || 'costomer'==appType || 'Ware' == appType){ parent.upload.close(idStr,picPath); }else if('GoodExtend' == appType || 'Advertise' == appType || 'magazinePic' == appType || 'Information' == appType|| 'GreetingCard' == appType|| 'promotePic' == appType || 'promotionActivityPic' == appType || 'StoreSet' == appType || 'giftPic' == appType ){ window.returnValue = picPath; window.close(); } } }, onError: function(event, queueID, fileObj) { alert("文件:" + fileObj.name + "上传失败"); }, onCancel: function(event, queueID, fileObj){ //alert("取消了" + fileObj.name); } }); }); </script>
文件对appType进行判断,继而进行处理,对于我们来讲,appType在这里没有实质性的作用,只是选择上传的类型目录而已。
来看下处理上传的文件,在文件FileUploadAction.class中
public void save(){ String folderPath = Static.APACHE_CONTEXT_PATH + Static.FILE_PATH; Date now = new Date(); String nowStr = DateUtil.date2Str(now, "yyyyMMdd"); now = DateUtil.str2Date(nowStr, "yyyyMMdd"); folderPath = folderPath + "/" + this.appType + "/" + nowStr; logger.info("relativePath:" + folderPath); String idStr = ""; String imgPath = ""; String fileName = ""; boolean isOk = true; if ((this.fileupload != null) && (this.fileupload.length > 0)) { logger.info("fileupload.length:" + this.fileupload.length); File savedir = new File(folderPath); if (!savedir.exists()) { savedir.mkdirs(); } for (int i = 0; i < this.fileupload.length; i++) { fileName = this.fileuploadFileName[i]; String postfix = fileName.substring(fileName.lastIndexOf(".") + 1); logger.info("uploadFileName[" + i + "]=" + fileName); String id = this.fileUploadService.makeId(); idStr = idStr + (i == 0 ? id : new StringBuilder(",").append(id).toString()); String fileNewName = id + "." + postfix; File savefile = new File(savedir, fileNewName); logger.info("save file:" + fileNewName + " to folder:" + savedir.getPath()); try { FileUtils.copyFile(this.fileupload[i], savefile); FileUpload fileUpload = new FileUpload(); fileUpload.setId(id); fileUpload.setAppType(this.appType); fileUpload.setCreateTime(now); fileUpload.setPostfix(postfix); fileUpload.setOriginalName(fileName); StringBuffer relativePath = new StringBuffer(); relativePath.append(Static.FILE_PATH) .append("/").append(this.appType) .append("/").append(nowStr) .append("/").append(id).append(".").append(postfix); fileUpload.setRelativePath(relativePath.toString()); imgPath = relativePath.toString(); this.fileUploadService.insert(fileUpload); } catch (Exception e) { if (isOk) { isOk = false; } logger.error("error when copyFile,savefile:" + savefile, e); } } } else { logger.warn("fileupload is null or fileupload.length <=0"); isOk = false; } if (!isOk) { responseFlag(isOk); } else if (this.appType.equals("News")) { responseFlag(imgPath); } else if (this.appType.equals("OrderGood")) { responseFlag(idStr + ',' + fileName); } else { responseFlag(idStr + ',' + imgPath); }
首先对appType和目录进行了拼接,也就是上传的路径
folderPath = folderPath + "/" + this.appType + "/" + nowStr;
判断文件长度大小
if ((this.fileupload != null) && (this.fileupload.length > 0))
然后取后缀,到这里为止,文件都没有对上传的内容进行任何判断,后缀也是一样,直接读取拼接,不做判断。
加之在文件中也未发现任何的登录权限验证,所以造成了前端无限制任意文件上传。
String postfix = fileName.substring(fileName.lastIndexOf(".") + 1); String fileNewName = id + "." + postfix;
下面就是存储过程了,最后返回上传结果。附上传成功并getshell截图。
存储型XSS
这个系统好像通篇没有过滤XSS的脚本,不知道有没有过滤文件反正我没有看到.可以在商品收货地址或商品展示处等地方插入XSS。
因为通篇XSS,所以就挑一个来说
在jsp文件edit_SysUser.jsp中,这个是用于修改个人信息的,定位源码SysUserAction.class
下面是两个重要函数
首先edit()从jsp页面获取到登录用户的信息,对信息进行修改,save()函数接收修改的信息,对用户信息进行存储更新,在文件里面,我们没有看到任何的过滤函数存在。
省略无关代码...... public String edit(){ SysUser loginMan = getSessionUserInfo(); if (this.sysUser == null) { this.sysUser = new SysUser(); } String id = this.sysUser.getId(); if (StringUtils.isBlank(id)) { super.initModel(true, this.sysUser, loginMan); } else { this.sysUser = ((SysUser)this.sysUserService.getModel(id)); super.initModel(false, this.sysUser, loginMan); } this.sysRoleList = this.sysRoleService.select(null); if (this.sysRoleList == null) { this.sysRoleList = new ArrayList(); } else { for (int i = 0; i < this.sysRoleList.size(); i++) { if ("admin".equals(((SysRole)this.sysRoleList.get(i)).getCode())) { this.sysRoleList.remove(i); break; } } } return "edit_SysUser";
省略无关代码……
public void save(){ try { String id = this.sysUser.getId(); String roleId = this.sysUser.getRoleId(); if (StringUtils.isNotBlank(roleId)) { SysRole sysRole = (SysRole)this.sysRoleService.getModel(roleId); this.sysUser.setRoleCode(sysRole.getCode()); this.sysUser.setRoleName(sysRole.getName()); } if (StringUtils.isBlank(id)) { this.sysUserService.insert(this.sysUser); } else { this.sysUserService.update(this.sysUser); } responseFlag(true); } catch (Exception e) { responseFlag(false); logger.error("error occur when save model!", e); }
}
测试结果,后台和前台
任意帐号密码修改
漏洞发生在app\front\action\UserManageAction.class文件中
首先在重置密码处会先进行一次帐号验证,也就是邮箱地址验证是否正确,然后会返回注册手机号码(下面会用到),代码就不贴了,这个不是重点,重点是sendEmail()这个函数。
首先会获取提交过来的手机号码和邮箱地址
this.customer = getSessionCustomerInfo(); String toMail = this.customer.getEmail(); String registerName = this.customer.getCode();
接下来,直接设置发送邮件的帐号密码,url构造随机数ID连接。
然后就是理想的发送邮件验证重置密码连接了。
String userName = "XXXXXX@126.com"; String password = "XXXXX"; String registerId = Math.random() * Math.random(); String url = "http://localhsot:8080/frontLogin.do?registerId=" + registerId; MimeMessage msg = new MimeMessage(session); msg.setFrom(from); msg.setSubject("邮箱验证"); msg.setSentDate(new Date()); msg.setContent("<a href='" + url + "'>点击" + url + "</a>", "text/html;charset=utf-8"); msg.setRecipient(Message.RecipientType.TO, to); Transport.send(msg);
直接构造和修改邮箱,即可修改密码。
http://www.xxx.com/sendEmail.do?customer.code=135xxxxxxx6&customer.email=xxxxx@xxx.com
0x02 最后
分析出了这几个漏洞和看了官网后,越发觉得这家公司为什么还能活着?
因为代码通用,影响旗下所有电商系统。
吐槽:一个软件卖到9000+,3年不升级。还有谁?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 某租车系统Java代码审计之后台注入漏洞
- 代码审计Day3 – 实例化任意对象漏洞
- 审计之PHP反序列化漏洞详解(附实例)
- 代码审计Day12 – 误用htmlentities函数引发的漏洞
- .NET高级代码审计(第一课)XmlSerializer反序列漏洞
- 代码审计 | Empire CMS v7.5前台XSS漏洞
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java多线程编程实战指南(设计模式篇)
黄文海 / 电子工业出版社 / 2015-10 / 59.00
随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案。然而,多线程编程相关的设计模式书籍多采用C++作为描述语言,且书中所举的例子多与应用开发人员的实际工作相去甚远。《Java多线程编程实战指南(设计模式篇)》采用Java(JDK1.6)语言和UML 为描述语言,并结合作者多......一起来看看 《Java多线程编程实战指南(设计模式篇)》 这本书的介绍吧!