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 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进而学习后,再尝试对框架类的源码进行一波审计。