Java调试
Jar包本地远程调试
新建项目,添加jar包到lib文件夹中,选择Add as Library...
反编译jar包文件。
配置Configuration
打下断点,在当前目录下运行
java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 stegsolve.jar
开启debug调试
Weblogic远程调试
进入/weblogic/CVE-2017-10271,修改docker-compose.yml文件,添加端口
运行容器,进入容器修改 /root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh 文件
docker exec -it 5fd /bin/bash
nano /root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
添加两行:
debugFlag="true"
export debugFlag
重启docker并安装zip,将当前目录下的modules目录和wlserver_10.3目录压缩后用docker cp带出。
# docker 5fd
zip -r modules.zip modules
zip -r wlserver_10.3.zip wlserver_10.3
# 服务器
docker cp 5fd:/root/Oracle/Middleware/modules.zip /tmp/weblogic/
docker cp 5fd:/root/Oracle/Middleware/wlserver_10.3.zip /tmp/weblogic/
解压,将wlserver_10.3/server/lib文件和modules文件添加到Library。
添加调试信息。
下断点,开始debug
Tomcat远程调试
进入/tomcat/CVE-2017-12615,同理开放端口,这里我开放了5005。
进入容器,在/usr/local/tomcat/bin/catalina.sh插入命令
Java_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
将/usr/local/tomcat/lib文件夹复制到物理机,添加到Library,然后根据上面的步骤,就可以调试了。
Maven
pom.xml
定义依赖的项目,并通过Reimport引入依赖的组件。
<dependencies>
<!--xxxxxx-->
<dependency>
<groupId>xxxxx</groupId>
<artifactId>xxxxx</artifactId>
<version>Xxx</version>
</dependency>
</dependencies>
Java EE
Java EE 分层模型
- Domain Object(领域对象)层:本层有一系列普通的、传统的Java对象(POJO)组成,这些对象是该系统的Domain Object,通常包含各自所需实现的业务逻辑方法。
- DAO(Data Access Object,数据访问对象)层:有一系列DAO组件组成,DAO实现了对数据库的创建、查询等操作。
- Service(业务逻辑)层:由一系列的业务逻辑对象组成,这些逻辑对象实现了系统所需要的业务逻辑方法。
- Controller(控制器)层:由一系列控制器组成,用于拦截用户的请求,调用业务逻辑组件的业务逻辑方法去处理用户请求,然后根据处理结果向不同的View组件转发。
- View(表现)层:由一系列的页面和视图组件组成,负责收集用户请求,并显示处理后的结果。
Java MVC
- Controller:接收用户请求,传送用户需求给Model
- Model:接收用户需求,判断逻辑,调用数据库,返回数据给View
- View:渲染视图,返回给用户
Servlet
Servlet3.0之前的版本都是在web.xml中配置,而3.0之后使用注解方式来配置。
基于web.xml
<web-app>
<servlet>
<description>xxx</description>
<display>user</display>
<servlet>user</servlet>
<servlet-class>com.sec.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>user</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
</web-app>
- <servlet>:声明Servlet配置入口
- <description>:声明Servlet描述信息
- <display-name>:定义Web应用的名字
- <servlet-name>:声明Servlet名称以便在后面的映射时使用
- <servlet-class>:指定当前servlet对应的类的路径
- <servlet-mapping>:注册组件访问配置的路径入口
- <servlet-name>:指定上文配置的Servlet的名称
- <url-pattern>:指定配置这个组件的访问路径
基于注解的方式
3.0以上版本,可以通过@WebServlet注解即可修改Servlet的属性。
@WebServlet(description = "this is a description", urlPatterns; {"/test"})
Servlet访问流程
在浏览器中输入/user,根据<servlet-mapping>标签中的文件名为user的url-pattern,通过对应的servlet-name寻找servlet标签中servlet-name相同的servlet,通过servlet-name获取class参数,得到具体的路径,执行响应的class的逻辑。
Servlet方法
创建Servlet文件时,需要创建响应的接口方法。
- init():第一次创建Servlet时被调用,用来初始化对象。
- service():执行任务的主要方法。
- doGet()/doPost():根据HTTP不通请求调用不同的方法。
- destroy():移除对象。
- getServletConfig():初始化参数
filter:Java Web过滤器
配合方式同Servlet类似,3.0前在web.xml中配置,3.0后通过注解方式配置。
基于web.xml
<web-app>
<filter>
<display-name>test</display-name>
<filter-anme>test</filter-name>
<filter-class>com.sec.test.test</filter-name>
</filter>
<filter-mapping>
<filter-name>test</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
</web-app>
配置方式与Serlvet逻辑大致相同
filter接口方法
- init()
- doFilter()
- destroy()
反射机制
反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应的对象进行操作。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
获取类对象
获取类对象的方法
- forName():使用Class类中的方法获取类对象
- .class:直接获取其对应的Class对象,但是要明确到类中的静态成员
- getClass():通过Object类中的getClass()方法获取字节码对象
- getSystemClassLoader().loadClass():与forName()类似,但是知识只是加载类到内存中,不会执行静态代码
获取类方法
获取某个Class对象的方法集合
- getDeclaredMethods:返回声明的所有方法,但不包括继承的方法
- getMethods:返回某个类所有的public方法
- getMethod:返回一个特定方法
- getDeclaredMethod:返回特定方法
获取类成员变量
获取类成员变量
- getDeclaredFields:获得类成员变量数组,不包括父类的申明字段。
- getFields:获取某个类所有的public字段
- getDeclaredField:获得某个特定的类成员变量
- getField:获得某个特定的public类成员变量
ClassLoader类加载机制
ClassLoader类是个抽象类,通过指定的类名称找到或生成对应字节码,返回一个java.lang.Class类的示例。可以继承ClassLoader来实现自定义的类加载器。
- getParent():返回类加载器的父加载器
- loadClass(String name):加载名称为name的类,返回的结果是 java.lang.Class类的实例
- findClass(String name):查找名称为name的类,返回的结果是java.lang.Class类的实例
- findLoadedClass(String name):查找名称为name的已经被加载过的类,返回的结果是java.lang.Class的实例
- defineClass(String name, byte[] b, int off, int len):把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例,该方法被声明为final
- resolveClass(Class<?>c):链接指定的java类
loadClass()方法与Class.forName的区别
loadClass()只会对类进行加载,不会对类进行初始化。Class.forName会默认对类进行初始化,执行静态的代码块。
URLClassLoader
是ClassLoader的一个实现,拥有从远程服务器加载类的能力,可以实现对webshell的远程加载等。
代理
静态代理
静态代理中,代理类和被代理类实现了同样的接口,代理类同时持有被代理类的引用,当我们需要调用被代理类的方法时,可以通过调用代理类的方法实现。
动态代理
基于反射的代理模式,动态代理通过Proxy类创建对象,然后将接口方法“代理”给InvocationHandler接口完成的。
CGLib代理
基于反射的代理模式,只能基于接口设计。性能较高。
Javassist动态编程
绕过编译过程在运行时进行操作的技术。
- ClassPool类:基于HashMap实现的CtClass对象容器,其中键是类名称,值是CtClass对象。
- CtClass类:表示一个类,这些对象可以从ClassPool中获得
- CtMethods类:类中的方法
- CtFields类:类中的字段
可以利用Javassist对目标函数动态注入字节码代码,劫持框架的关键函数,对中间件的安全进行测试,也可以劫持函数进行攻击阻断。
TOP10
注入
SQL注入
JDBC拼接不当造成SQL注入
JDBC有两种方法执行SQL语句,分别为PrepareStatement和Statement。
Statement对象的SQL注入
String sql = "select * from user where id = " + req.getParameter("id");
System.out.println(sql);
try {
java.sql.Statement st = con.createStatement();
ResultSet re = st.executeQuery(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
PrepareStament对象的SQL注入
String sql = "select * from user where id = " + req.getParameter("id");
System.out.println(sql);
try {
java.sql.PreparedStatement psst = con.prepareStament(sql);
ResultSet re = st.executeQuery(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
安全的SQL语句
PrintWriter out = resp.getWriter();
String sql = "select * from user where id = ?";
System.out.println(sql);
try {
java.sql.PreparedStatement psst = con.prepareStament(sql);
psst.setInt(1, Integer.parseInt(res.getParameter("id")));
ResultSet re = psst.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
如果使用字符串拼接执行,可能会造成SQL注入。
正确的PrepareStatement使用?进行占位,可以有效避免SQL注入的产生。
框架使用不当造成SQL注入
Java项目会对JDBC进行更抽象封装的持久化框架,通常在底层实现对SQL注入的防御。但如果研发人员未能恰当使用框架的情况下,仍可能存在SQL注入的风险。
(1) MyBatis
MyBatis的思想是将SQL语句编入配置文件,避免SQL语句在Java程序中大量出现,方便后续对SQL语句的修改和配置。
MyBatis中使用parameterType向SQL语句传参,在SQL引用传参可以使用#{Parameter}和${Parameter}两种方式。#采用预编译的方式构造SQL,$采用拼接的方式构造SQL。
(2)Hibernate框架
Hibernate是Java持久化API规范的一种实现方式。将Java类映射到数据表中,从Java数据类型映射到SQL数据类型。是目前主流的Java数据库持久化框架,采用Hibernate查询语言。(HQL)
语法与SQL类似,但有所不同,在漏洞上具有一定的限制。HQL查询语句是对持久化类的对象进行操作而不是直接对数据库进行操作,因此使用Hibernate引擎进行解析。
正确使用参数绑定的方式可以避免注入的产生。
- 位置参数
String parameter = "admin";
Query<User> query = session.createQuery("from com.admin.bean.Userwhere name = ?1", User.class);
query.setParameter(1, parameter);
2. 命名参数
Query<User> query = session.createQuery("from com.admin.bean.Userwhere name = ?1", User.class);
String parameter = "admin";
Query<User> query = session.createQuery("from com.admin.bean.Userwhere name = :name", User.class);
query.setParameter("name", parameter);
3. 命名参数列表
List<String> names = Arrays.asList("admin", "guest");
Query<User> query = session.createQuery("from com.admin.bean.User where name in (:name)", User.class);
query.setParameter("name", names);
4. 类实例(JavaBean)
user1.setName("admin");
Query<User> query = session.createQuery("from com.admin.bean.User where name = :name", User.class);
query.setProperties(user1);
使用拼接参数的方式构造SQL语句可能会造成SQL注入。
命令注入
Process process = Runtime.getRuntime().exec(cmd);
命令注入的局限性:
- | 前面命令输出结果作为后面命令的输入内容
- || 前面命令执行失败才执行后面的命令
- & 前面命令执行后继续执行后面的命令
- && 前面命令执行成功后才执行后面的命令
在Java中连接符的使用存在一定局限性,会被当做一个完整的字符串而非两条命令。传入的字符串会优先经过StringTokenizer的处理,后续会分割出cmdarray来保存分割后的命令参数,而经过处理后参数会被当做被执行的参数,所以命令分割符不生效。
代码注入
代码注入漏洞的前提条件时将用户输入的数据作为Java代码执行。通过Java的反射,根据传入的不同类名、方法名和参数执行不同的功能。
String ClassName = req.getParameter("ClassName");
String MethodName = req.getParameter("Method");
String[] Args = new String[]{req.getParameter("Args").toString()};
try {
Class clazz = Class.forName(ClassName);
Constructor constructor = clazz.getConstructor(String[].class);
Object obj = constructor.newInstance(new Object[]{Args});
Method method = clazz.getMethod(MethodName);
method.invoke(obj);
} catch (ClassNotFoundException e) {
...
}
表达式注入
表达式语言(Expression Language),又称EL表达式,是一种在JSP中内置的语言,用于作用于用户访问页面的上下文以及不同作用域的对象,取得对象属性值或执行简单的运算或判断操作。
JSP四大作用域
- page:只在一个页面保存数据
- request:只在一个请求中保存数据
- session:在一次会话中保存数据,单用户使用
- application:在整个服务器中保存数据,全用户共享
可以使用作用域范围作为前缀来指定在某个作用域查找,例如${requestScope.name}
表示在request作用域中获取name变量,与${requestScope["name"]}
同理。
EL也可以实例化Java的内置类${Runtime.getRuntime().exec("calc")}
模板注入
FreeMarker引擎进行解析并动态替换模板中的内容进行渲染。
可以通过New函数实例化一个Execute对象并执行exec()方法造成任意代码执行。
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
api函数可以用来访问Java的API,使用方法为value?api.someJavaMethod(),相当于value.someJavaMethod(),因此可以利用API函数通过getClassLoader来获取一个类加载器,进而加载任意类,也可以通过getResource来读取服务器上的资源文件。
失效的身份验证
错误地使用应用程序的身份认证和会话管理功能,使攻击者能够破译密码,密钥或会话令牌,或者利用其他开发漏洞暂时或长久地冒充其他用户的身份,导致攻击者可以执行受害者用户的任何操作。
案例:WebGoat8 JWT Token 猜解实验
敏感信息泄露
敏感信息包括系统敏感信息以及应用敏感信息。
系统敏感信息指业务系统本身的基础环境信息,例如系统信息、中间件版本、代码信息等
应用信息包括个人的身份证、姓名、电话号码、邮箱等。
案例:TurboMail 5.2.0
XML外部实体注入(XXE)
攻击者可以利用XXE实现任意文件读取、内网端口探测、命令执行、拒绝服务攻击等方面的攻击。
读取系统文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<foo>&xxe;</foo>
实体指的是DTD实体,定义引用普通文本或特殊字符的快捷方式的变量。
实体可以分成内部内部声明实体和引用外部实体,引用外部实体可以支持http、file、ftp、jar、netdoc、gopher等协议。
DoS攻击
通过多个引用指数级扩展XML大小。
Blind XXE
盲注XXE,可以操作数据外带,以及引用外部dtd等。
修复
设置属性,禁止使用外部实体
失效的访问控制
失效的访问控制是指未对通过身份验证的用户实施恰当的访问控制,可以利用缺陷访问未授权的功能或数据,例如访问其他用户的账户,查看敏感文件,修改其他用户的数据,更改访问权限等。
横向越权:Cookie、GET、遍历,测试功能点如重置密码、敏感数据访问等
纵向越权:Cookie、JWT,单个功能点、API漏洞做权限验证
安全配置错误
最小权限
Tomcat任意文件写入:/1.jsp/调用DefaultServlet进行处理,调用doPut写入文件,File对象实例化过程中处理掉多余的/。
XSS
- 收集输入、输出点
- 查看输入、输出点的上下文环境
- 判断WEB应用是否对输入、输出防御
不安全的反序列化
Java RMI(Java Remote Method Invocation,Java远程方法调用)是允许运行在一个Java虚拟机的对象调用另一个Java虚拟机对象的方法,通过反序列化的方式传输对象,利用RMI作为反序列化的触发点。
JNDI(Java Naming and Directory Interface, Java命令和目录接口)是一组应用程序接口,目的是方便查找远程或本地对象。通过JNDI获取远程对象过程中,如果访问的服务地址可控,加载并实例化恶意对象,造成JNDI注入。
JEP290:内置过滤器,只允许特定的类进行反序列化。
案例:Apache Commons Collections 反序列化漏洞:transform的input变量可控时调用任意类的任意方法、Templateslmpl
FastJson反序列化漏洞:1. 绕过黑名单 2.寻找绕过AutoType的方式
使用含有已知漏洞的组件
Weblogic cc t3
不足的日志记录和监控
CRLF:HTTP请求走私、HTTP响应拆分 伪造日志
未经记录可审计事件
常见漏洞代码审计
CSRF
一般需要先检查框架对CSRF的防护方案,包括请求是否有token、csrf-token等关键字,是否存在替换token值并重复请求漏洞、重复使用token、对referer校验等漏洞。
对Referer过滤不严导致的CSRF漏洞
String referer = request.getHeader("Referer");
if ((referer!=null) &&(referer.trim().startWith("www.testdomain.com"))) {
......
}
如果是通过startWith过滤,可以利用www.testdomain.com.hacker.com绕过。
token可重用导致CSRF漏洞
token被泄露或盗用,可能导致CSRF漏洞发生。
SSRF
敏感函数
HttpClient.execute()
HttpClient.executeMethod()
HttpURLConnection.connect()
HttpURLConnection.getInputStream()
URL.openStream()
HttpServletRequest()
BasicHttpEntityEnclosingRequest()
DefaultBHttpClientConnection()
BasicHttpRequest()
支持协议
http
https
file
ftp
mailto
jar
netdoc
- CVE-2019-9827
- CVE-2014-4210
URL跳转
常见参数名
url
site
host
redirect
domain
target
link
常见函数
sendRedirect
getHost
redirect
setHeader
forward
文件操作漏洞
文件包含
动态包含
%@include file="test.jsp"%
静态包含
<jsp:include page="<%=file%>"></jsp:include>
通常危害只有文件下载,不包括代码执行
文件上传
File
lastIndexOf
indexOf
FileUpload
getRealPath
getServletPath
getPathInfo
getContentType
equalsIgnoreCase
FileUtils
MultipartFile
MultipartRequestEntity
UploadHandleServlet
FileLoadServlet
FileOutputStream
getInputStream
DiskFileItemFactory
文件下载、读取
FileInputStream
getOutputStream
文件写入
文件解压
Web后门漏洞
直接调用
系统命令执行函数
java.lang.Runtime.exec() # 无回显
java.lang.ProcessBuilder.start()
反射调用
BASE64Decoder()
Class.forName()
getMethod()
JDK特性
利用Lambda表达式编写JSP一句话木马
逻辑漏洞
前端配置不当漏洞
CORS
CSP
拒绝服务攻击漏洞
ReDos
压缩包炸弹
点击劫持漏洞
HTTP参数污染漏洞
WEB服务器 | 参数获取函数 | 获取到的参数 |
PHP/Apache | $_GET | 最后一个 |
JSP/Tomcat | getParameter | 第一个 |
Perl(CGI)/Apache | Param | 第一个 |
ASP/IIS | QueryString | 所有 |
Python/Apache | getvalue | 所有 |