记一次对JavaWeb-SMBMS的审计

0x00 前言

在跟着网上培训视频学习完无框架的JavaWeb的时候,都会让做一个内容管理系统,这里我是跟着B站狂神的视频走的,最后是做一个SMBMS项目,故针对该源码代码审计一波,也是考验下自己学的JavaWeb扎实不扎实。

0x01 源码下载与搭建

源码地址:https://www.lanzoux.com/iGLJTdusuyf

项目笔记:https://blog.csdn.net/bell_love/article/details/106157413

源码搭建方法(以IDEA为例,非详细步骤):

1、新建一个Maven项目如下图,后续创建操作按个人情况配置。

2、把下载的源码直接替换了src目录和pom.xml文件即可(可以直接在文件夹中直接替换)。

3、再配置下相应的Tomcat环境(细节请百度IDEA配置tomcat环境)。

4、修改连接MySQL的配置文件,即本地MySQL的账号和密码。

5、启动MySQL服务并导入源码SQL文件。

6、启动maven项目,看一下这个网站首页。

0x02 审计思路

根据以前搞PHP代码审计时的思路差不多,可以分为三个方法,分别是:

  • 根据敏感函数审计
  • 根据业务功能审计
  • 全量源码通读审计

其中对于通读全量源码这个方式,肯定是不推荐的,耗时耗力,但是好处是可以挖到一些别人挖不到的漏洞点。而根据业务功能审计,这个是老P的专爱了,他PHP开发出身,记得还挖到过DedeCMS的前台任意文件删除。而敏感函数审计这个我是最喜欢了,法师的Seay源代码审计系统扫完之后,再验证验证,基本上水几个CVE就出来了。

针对该源码,我就两个方向同步走了,敏感函数放Fortify里直接跑了,业务逻辑我一个功能点一个功能点的过。

0x03 Fortify扫描结果

常规扫描步骤就不演示了,直接看下漏洞扫描的结果吧。

高危漏洞:

1、Access Control: Database(数据越权)

2、j2ee bad practices:non-serializable object stored in session(存储在会话中的非可序列化对象)

3、Password Management:Password in Configuration File(密码在配置文件中)

4、Privacy Violation(隐私泄露)

5、Unreleased Resource(未释放资源)-Streams(流)

中危漏洞:

1、Cross-Site Scripting:Persistent(持久性XSS漏洞)

2、Cross-Site Scripting:Reflected(反射性XSS漏洞)

3、Privacy Violation(隐私泄露)

0x04 反射型跨站脚本攻击

通过Fortify的扫描结果,我们先抽一个反射的XSS漏洞看看。

1、这是描述的在 billlist.jsp 文件中,第14行代码存在一个输出点,可能存在反射型XSS漏洞。

2、我们根据这个 form 表单可以知道是提交给 /jsp/ 目录下的 bill.do 文件,我们通过web.xml找一下对应的servlet处理对象类。

3、可以看出我们提交的参数是在 servlet.bill.BillServlet 中进行处理,跟进去看一下。

4、可以看出函数还是比较多的,根据步骤1中该 form 表单是 get 形式提交,我们只需要看 doGet 函数即可。

1
2
3
4
5
6
7
文件路径:servlet.bill.BillServlet

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

doPost(request, response);
}

5、毕竟是结课实操项目,肯定是写的简陋的,我们继续看 doPost 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
文件路径:servlet.bill.BillServlet

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

/*String totalPrice = request.getParameter("totalPrice");
//23.234 45
BigDecimal totalPriceBigDecimal =
//设置规则,小数点保留两位,多出部分,ROUND_DOWN 舍弃
//ROUND_HALF_UP 四舍五入(5入) ROUND_UP 进位
//ROUND_HALF_DOWN 四舍五入(5不入)
new BigDecimal(totalPrice).setScale(2,BigDecimal.ROUND_DOWN);*/

String method = request.getParameter("method");
if(method != null && method.equals("query")){
this.query(request,response);
}else if(method != null && method.equals("add")){
this.add(request,response);
}else if(method != null && method.equals("view")){
this.getBillById(request,response,"billview.jsp");
}else if(method != null && method.equals("modify")){
this.getBillById(request,response,"billmodify.jsp");
}else if(method != null && method.equals("modifysave")){
this.modify(request,response);
}else if(method != null && method.equals("delbill")){
this.delBill(request,response);
}else if(method != null && method.equals("getproviderlist")){
this.getProviderlist(request,response);
}

}

6、根据步骤1中的 form表单,我们知道提交的 method 对应的 value 值为 query,所以继续跟进下 this.query 函数内容。

1
<input name="method" value="query" class="input-text" type="hidden">

注:this.query 为 BillServlet.query 的意思。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
文件路径:servlet.bill.BillServlet

private void query(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

List<Provider> providerList = new ArrayList<Provider>();
ProviderService providerService = new ProviderServiceImpl();
providerList = providerService.getProviderList("","");
request.setAttribute("providerList", providerList);

String queryProductName = request.getParameter("queryProductName");
String queryProviderId = request.getParameter("queryProviderId");
String queryIsPayment = request.getParameter("queryIsPayment");
if(StringUtils.isNullOrEmpty(queryProductName)){
queryProductName = "";
}

List<Bill> billList = new ArrayList<Bill>();
BillService billService = new BillServiceImpl();
Bill bill = new Bill();
if(StringUtils.isNullOrEmpty(queryIsPayment)){
bill.setIsPayment(0);
}else{
bill.setIsPayment(Integer.parseInt(queryIsPayment));
}

if(StringUtils.isNullOrEmpty(queryProviderId)){
bill.setProviderId(0);
}else{
bill.setProviderId(Integer.parseInt(queryProviderId));
}
bill.setProductName(queryProductName);
billList = billService.getBillList(bill);
request.setAttribute("billList", billList);
request.setAttribute("queryProductName", queryProductName);
request.setAttribute("queryProviderId", queryProviderId);
request.setAttribute("queryIsPayment", queryIsPayment);
request.getRequestDispatcher("billlist.jsp").forward(request, response);

}

7、其它的代码先不细看,只跟进下涉及到接收的 queryProductName 参数部分。

8、根据代码可以看到,首先把接受到的参数传递给字符串 queryProductName,其次判断下这个参数传递的内容是否为空,若为空则赋值一个空字符串值。

9、中间除了把 queryProductName 传递到 bill 对象中外,就直接响应给客户端了,中间没有对参数进行任何过滤,也就是说 billlist.jsp 页面中,value 的值是没有任何过滤的,是存在反射xss漏洞的。

10、这里我们根据页面HTML代码闭合下标签并生成一个payload的,看一下效果。

1
2
payload:
11"><img src=x onerror=alert(1)><"11

0x05 持久型跨站脚本攻击

这里我们抽1个储存型的XSS告警看一下。

1、根据这个页面名称我们知道这是一个供应商管理的页面。

2、根据这个页面我们可以看出可以直接在首页显示的只有6个字段,而这些字段中根据名字只有供应商和联系人这两个字段可能是纯String的,我们找一些具体的逻辑处理部分,查看是否有过滤等函数。

3、我们跟进下 servlet.provider.ProviderServlet 里的内容,由于url地址为 provider.do?method=query ,所以 我们只需跟进下ProviderServlet中的query函数看一看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
文件路径:servlet.provider.ProviderServlet

private void query(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String queryProName = request.getParameter("queryProName");
String queryProCode = request.getParameter("queryProCode");
if(StringUtils.isNullOrEmpty(queryProName)){
queryProName = "";
}
if(StringUtils.isNullOrEmpty(queryProCode)){
queryProCode = "";
}
List<Provider> providerList = new ArrayList<Provider>();
ProviderService providerService = new ProviderServiceImpl();
providerList = providerService.getProviderList(queryProName,queryProCode);
request.setAttribute("providerList", providerList);
request.setAttribute("queryProName", queryProName);
request.setAttribute("queryProCode", queryProCode);
request.getRequestDispatcher("providerlist.jsp").forward(request, response);
}

4、ProviderServiceImpl类我也跟进下,没有发现什么过滤的情况,那在输出上没问题,就继续看一下输入流的部分吧。

5、来具体跟一下输入流的代码,看看是否存在过滤的代码等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
文件路径:servlet.provider.ProviderServlet

private void add(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String proCode = request.getParameter("proCode");
String proName = request.getParameter("proName");
String proContact = request.getParameter("proContact");
String proPhone = request.getParameter("proPhone");
String proAddress = request.getParameter("proAddress");
String proFax = request.getParameter("proFax");
String proDesc = request.getParameter("proDesc");

Provider provider = new Provider();
provider.setProCode(proCode);
provider.setProName(proName);
provider.setProContact(proContact);
provider.setProPhone(proPhone);
provider.setProFax(proFax);
provider.setProAddress(proAddress);
provider.setProDesc(proDesc);
provider.setCreatedBy(((User)request.getSession().getAttribute(Constants.USER_SESSION)).getId());
provider.setCreationDate(new Date());
boolean flag = false;
ProviderService providerService = new ProviderServiceImpl();
flag = providerService.add(provider);
if(flag){
response.sendRedirect(request.getContextPath()+"/jsp/provider.do?method=query");
}else{
request.getRequestDispatcher("provideradd.jsp").forward(request, response);
}
}

6、通过对 Provider() 、ProviderServiceImpl() 的跟进,没有发现有过滤函数,那继续下一步。

7、在代码层没有过滤的情况,还需要考虑的就是数据库层面是否有字符数的限制,这个是需要了解的,我们知道是这个存放供应商的是在 smbms_provider 表中,那我们具体的看一下。

服务器: 127.0.0.1 »数据库: smbms »表: smbms_provider

8、而常见的payload都是超过20个字符的,所以我们只能在 proDesc 和 proAddress 这两个字段中进行插入XSS代码。

9、由于proAddress字段并没有在页面中显示,所以只在 proDesc 字段中进行了插入。

10、当我们进入这个供应商详情界面,即触发了XSS攻击代码。

0x06 小结

由于后台功能其实也就个查询和添加功能,能联想到的漏洞也就是SQL注入了,但是整体SQL层都是使用了预编译形式,故也就是不挖了。

该CMS是基于三层架构的学员毕业作品,等后续对spring进而学习后,再尝试对框架类的源码进行一波审计。