本文首发于阿里云先知社区记一次Java Servlet实战审计 - 先知社区,转载请注明出处。

感谢观看!

0x00    背景

某次渗透中,遇到个jsp的站,弱密码进到后台以后,却发现功能很少,并不好搞,简单排查后并无突破,然而老大给的要求是尽快搞到shell。。。

一番梳理后,我的想法是:要么通过漏洞拿下后台,要么搞到源码直接白盒开冲。白盒的话很香,对审计能力提升也大,所以可以按下面几步来试试:

  • **目录扫描。**使用dirbuster的字典directory-list-2.3-medium.txt指定后缀jsp来扫,工具就随便用个顺手的,dirseach

  • **网盘泄露。**这套系统并不开源,估计厂家发版的时候放了不少在网盘,简单一搜就有结果了。在网盘搜索引擎上,搜到了厂家安装包,但似乎是PE文件,还得安装,且不清楚是否有混淆/加密,暂搁置。

内心OS:这年头——可不敢乱点。

  • **Github、gitlab泄露。**尝试了多个关键词,均无果

  • **Fofa找同类型的站。**这个无需多说,不管是发版的时候没有把备份文件删除,还是运维的疏忽大意,都是很容易被发现的。同时,在fofa上,用favicon.ico,或是用title来搜,准确率都相当惊人

那工具方面,首先是选用了broken5师傅的https://github.com/broken5/WebAliveScan,1024个线程猛冲之后,却并无发现。。。

考虑是不是字典不够牛,接着使用dirsearch自带的字典(约17,000条),

# 只要字典大没有拿不下
python3 dirsearch3.py -e "jsp" -l ip_port.txt -t 50 --plain-text-report=ip_port_DirScan.txt  -q

跑目标列表花了一个上午,终于有了收获——web.rar,香!

天黑了,打开IDEA!天亮了,关闭IDEA。

我发现jsp的代码虽然不难懂,可基础知识不牢,代码审起来简直让人打脑壳,于是有了下面这第一章。

0x01    Servlet简介

正常情况下的目录结构,长下面这样

exampleApp
└─images
└─WEB-INF                             
   ├─classes     # 包含了所有的 Servlet 类和其他类文件重要
       └─com	
   				└─example
		
   └─lib					# 项目依赖包的储存位置.jar文件
└─web.xml					# Servlet的配置文件重要

定义路由

路由的定义,可以在这两个地方进行:Servlet注解web.xml, 配置的时候二选一即可。

Ⅰ    Servlet中的注解

@WebServlet("/Hello")
public class HelloServlet extends HttpServlet{
  //处理 GET 方法请求的方法
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		//实现的代码
  }

  //处理POST方法请求的方法
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		//实现的代码
  }
}

Servlet3.0之前,需要在web.xml中配置,才能够使用Servlet,这一块由于路由跟逻辑并不在一块儿实现,比较陌生,因此我们重点看下它。

Ⅱ    配置文件web.xml

Java项目中的web.xml,可以配置web的路由,里面的属性很多,咱们主要关心两条:

  1. ****对应类的名字
  2. **,**路由。为servlet提供一个缺省的URL:http://host/webAppPre fix/servlet/ServletName
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.example.HelloServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/Hello</url-pattern>
  </servlet-mapping>
</web-app>

Ⅲ    jsp:useBean标签

同时,在jsp代码的开头,看到大量使用<jsp:useBean...的代码,见图片中的第2~4行。

这东西,叫**<jsp:useBean>****标签,**它的定义,整理出来是这样的:

<jsp:useBean>标签可以在JSP中声明一个JavaBean,然后使用。

  • 声明后,JavaBean对象就成了脚本变量,可以通过脚本元素或其他自定义标签来访问。
  • <jsp:useBean>标签的语法格式如下:
    • id值,可随意定义,只要跟上下文不重复;在习惯上,跟class的最后一级相同(HttpSession);
    • scope值,可以是pagerequestsessionapplication,分别对应不同的作用范围,注意不要将【需要经常变动的 bean】 的 scope 设为 applicationsession
    • class值,指定对应的java类;一般是WEB-INF/classes/为起点的一个相对路径(用作路径分隔符)
<jsp:useBean id="HttpSession" scope="session" class="example.HttpSession"/>

JavaBean,个人认为就是Java的一种对象,遵循一些规范,有一些特征。

因此,在审计时,只需先在jsp文件中找敏感函数,根据当前文件中的标签定义,找到定义函数的.class,完成漏洞的确认就好了。用IDEA可以很方便地进行查看。

Ⅳ    取HTTP参数

JSP中取request参数的写法,虽然非常好理解,但对小白来说,也是有一些需要注意的点。

<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
    ...
<% 
String id;  
id = request.getParameter("id");

// 处理中文
String name =new String(request.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");
...

上面这段代码中,使用request.getParameter来接受client发送的HTTP参数。但对于id参数,无论你以GET、还是POST方法来提交的,都能被server接收。

也就是说:request.getParameter是兼容POST/GET参数滴!有点像PHP中的$_REQUEST方法。

同时,对于中文数据,需要对其转码,才能正常显示。

当然,JSP中还有其它取参数的写法,由于并未在本次渗透中出现,也就不再赘述。感兴趣的师傅可以自行了解。

Ⅴ    文件的包含

此外,在项目中还出现了文件包含的写法

<%@ include file="check.jsp"%>

经过一番学习,这个跟PHP中的文件包含有点像

那看看check.jsp的内容吧,

很明显,下面这段代码一出现,就意味着本页面的功能属于后台功能了。

<%@ include file="check.jsp"%>

不过呢,我还有一点没搞懂,又没看到有exit函数,为啥include以后的代码,走到out.print以后就不再执行了呢?估计跟servlet的生命周期有关。有了解的师傅麻烦在评论区抬一手。

OK,基础知识补充得差不多了,下面开冲,尝试完成前台RCE的挖掘。

0x02    后台SQLi

轻松找到一处注入,无任何过滤。

不过,考虑到可能需要SQLMAP作自动化利用——删除型的注入点,还是算了。

那么,又重新找到一处拼接表名的注入点。

但问题又来了,这个测试环境的数据库文件,我之前作目录扫描的时候是搞了一份的,找半天咋找不到task_开头的数据表呢???

没办法,只能FUZZ了,采用SecLists里面的raft-large-words.txt来进行FUZZ。哈哈,果然没用!

最后,审计呗,找到一处完美注入点,既不伤害数据库,又不需要花里胡哨地FUZZ。SQLMAP跑的结果如下:

注意到是DBA权限,且通过大小写判断目标是Windows环境。

对我来说,一是想到可以用UNC地址进行带外注入(可以,但在联合注入面前,没必要);

二是写webshell。一般而言,windows通过注入写webshell比linux更难一些,因为路径相对更难猜。不过,由于我手上有系统的部分源码,很快翻找到了web路径,是C:\example,尝试使用--os-shell写shell,并不成功;一开始还猜测是当前环境更换了盘符,结果26个字母捋了一遍,都不成功。。。

0x03    路径泄露 => 后台getshell

天无绝人之路啊。我回想起目标环境的Tomcat似乎很拉跨,并未对报错进行屏蔽,经常露源码出来,嘿嘿!

通过让后台备份功能产生报错,成功获得路径d:\exam\bak\

接着通过SQLMAP --os-shell参数,也是不费工夫就拿到了SQLMAP的shell,允许上传任意文件了。

0x04    前台getshell

话又说回来,目前这个目标是拿下来了,可毕竟是弱密码,难免被诟病“不讲武德”。

于是,全局搜索没有包含check.jsp的代码,发现一处前台SQL注入。。。

好的,前台getshell的方式有了,不过要知道目标环境的web路径才行,但考虑到该产品差不多采用的都是销售OEM服务器的形式,基本不会出太大岔子。

感谢观看,本文主要是简单的代码审计,有很多思路不到位的地方,请各位师傅不吝指点!

Refs

0x05    复盘

TODO思维导图。

1、为什么不去搞401认证?

主要还是TCL,又嫌Burp跑401认证有些小麻烦,主要还是成功率太低。那么优化的Action,总结为两条

Action


2、为什么一开始没有深入审计?

一开始进去是通过弱密码呀,说不定是千万人搞过的站了,找漏洞:扫描器不敢开,手工测成本又高,必须另辟蹊径,先搞源码,有道理吧。但后续发现,找源码的工作量也不小,所以优化的Action可以从下面着手

Action

  • 开发一键查源码系统,整合github API、百度网盘搜索引起、Fofa/Zoomeye/Shodan 上的gitlab、Gogs、HTTPServer等内容

(刚写好README)🤭