从零开始学Fuzzing系列:浏览器挖掘框架Morph诞生记

本文主要是介绍从零开始学Fuzzing系列:浏览器挖掘框架Morph诞生记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


0×00 写在前面

欢迎来到《从零开始学Fuzzing系列》。

软件漏洞领域涉及漏洞分析和漏洞挖掘两个方向,目前安全站点关于各种漏洞分析的文章层出不穷,从2013年几天甚至几周才出来的分析文章,到现在几乎与漏洞预警同时发布的报告,也反映出这几年来漏洞分析技术的发展之迅速。

但不可否认的是,与之相辅相成的漏洞挖掘方向(目前大家普遍用的是Fuzzing,高校及实验室研究较多的是BitBlaze和S2E等二进制分析的高端玩意儿),虽然也出现了很多优秀的开源工具,但通俗易懂的学习资料,总感觉少了许多。

优秀的Peach Fuzzer几乎没有公开的Peach Pits,甚至连Pits的编写语法都需要去研究官网上苦涩的文档;优秀的Grinder,公开的Fuzzer模板只有nduja和fileja等少部分经典,针对最新浏览器的兼容性也或多或少有些问题。当然,瑕不掩瑜,这些工具都值得借鉴和学习。

通过这个专题,写写自己在漏洞Fuzzing方向的学习收获,希望能以一个初学者的力量,向国内的Hacker展示漏洞挖掘领域中Fuzzing方向的进展,彼此收获。

鉴于目前浏览器Fuzz的经久不衰,首先从这方面入手。此文是浏览器Fuzz的第一篇。

即将讲述的Morph框架是本文作者基于Puzzor大神《 如何进行浏览器Fuzz 》思路编写的一款轻量级浏览器Fuzz框架,以个人理解展开了原文中未深入讲述的样本生成、异常捕获、Crash重现等部分的具体实现。

Morph的核心思想是,利用基于预置Random数组的静态样本生成策略,初步解决了Grinder无法重现某些Crash的缺点(使用过Grinder的深有体会),并借鉴Grinder框架思想,将Fuzzer作为插件进行集成,使得Morph能够很好地支持现有的nduja、cross_fuzz等fuzz工具。

0×01 浏览器Fuzz需要考虑的问题

如何设计一个浏览器Fuzz工具呢?最简单的思路就是,随机生成很多html文档,让浏览器一个个去打开,当打开某个样本导致浏览器崩溃时,那么就可以认为挖到了一个Crash。如下图:

这个过程会涉及几个核心问题:

1.如何随机生成html文档
2.如何监控浏览器发生了Crash
3.如何记录导致浏览器Crash的那个样本

这三个问题的解决方式,代表着一款浏览器Fuzz框架的特点。下面详细叙述一下开发Morph过程中对这三个问题的理解与实现。

0×02 样本生成:如何随机生成html文档

样本生成的好坏,直接决定了该工具能否挖掘出有价值的漏洞。

目前纯Fuzz方法中,比较流行的就是根据目标文件格式生成针对性样本,效果也比较理想。以HTML文档为例,诞生过很多随机生成html/DOM元素的Browser Fuzzer。

曾经盛名一时的nduja,其大致思路就是,利用Javascript随机创建DOM元素、随机调用DOM处理函数、随机删除DOM元素等操作来完成每一次Fuzz的。该工具曾经找到了很多UAF类型的漏洞,可以说它是2012年前后浏览器漏洞挖掘的一个里程碑思想。

Nduja的主体是一个HTML文件模板,摘抄部分经典Javascript代码:
// 随机数
function rand( x ){return Math.floor( Math.random() * x );
}
//生成包含随机DOM元素的Range块
function createRange(){range = document.createRange();rstart = rand(document.all.length);range.setStart(document.all[rstart], 0);rend = rand(document.all.length);range.setEnd(document.all[rend], 0);return range;
}
//随机执行Range块处理函数
function alterRange(range){try{rb = rand(document.all.length);range_functions[rand(range_functions.length)](range, document.all[rb]);}catch(exception){ }
}
//支持的所有Range块处理函数
range_functions = [function(range, elem){range.deleteContents();},function(range, elem){range.detach();},function(range, elem){range.extractContents();},......
];
//Fuzz函数
function fuzz(){......range1 = createRange();alterRange(range1);......
}

另外该fuzzer中还包括随机添加DOM事件addElementListener等方式,有兴趣的可以深入下载研究。

可以发现nduja采取的策略是,借助于rand随机函数,随机对DOM元素进行处理,以测试某些方式的DOM操作能否存在漏洞。

假设某次随机操作会导致浏览器产生Crash,那如何将这次的样本保存呢?不难理解,只要将本次rand函数生成的所有随机数全部保存下来即可。

著名的Grinder框架就是基于这种思路,采用DLL注入的方式,劫持了Javascript的parseFloat函数,用以记录某次样本需要记录的相关参数。比如上面的alterRange函数:
function alterRange(range){try{rb = rand(document.all.length);rx = rand(range_functions.length);range_functions[rx](range, document.all[rb]);logger.log('range_functions[‘+ rx +’](‘+ range +’, document.all[‘+ rb +’]);;','nduja',1);}catch(exception){}
}
//备注:logger.log最终会调用Javascript的parseFloat函数

可以看出Grinder提供的logging.js可以直接记录某次执行过的所有Javascript语句,这样对样本重现和样本精简是非常有帮助的。当然,也可以选择直接记录rb、rx等随机值,只是后期的样本精简将会比较复杂。

所以,在nduja中添加适当的logger.log函数,即可在Grinder框架中进行Fuzz工作,原作者也是在这个框架下测试应用的。

Grinder的核心思想如下图:

从理论上来说,Grinder采取的方式比较完美,一方面解决了随机数的记录,一方面又能精简样本。但在实际使用Grinder过程中,为何出现无法重现部分漏洞的情况呢?仔细思考Grinder采用的DLL注入方式,可以发现有以下弊端:

1.并不能保证DLL注入能够成功。
必须有symbols文件,它才能正确找到parseFloat函数在浏览器进程的正确位置。而浏览器最新版本一般不提供symbols文件或发布symbols文件比较慢,这无疑限制了Grinder对最新浏览器的测试能力。
2.再者无法保证logger.log记录函数能够把所有的log信息都记录下来。
大部分编写Fuzzer插件的开发者并不能100%精确使用Grinder的logger记录每一条html的javascript语句,即使记录了所有语句,也无法保证Logger能够成功将所有语句写入到log文件而不受某次Fuzz的影响。
3.深层次考虑的话,也有可能是多个html共同导致了某个Crash。
而Grinder的logger日志只记录一个html中的执行的log信息,这显然是无法重现这类漏洞的。

这些都是Grinder的底层架构带来的种种不足。Grinder要实现的最终目的,就是把每次生成的所有random值或该Random值后执行的Javascript语句记录下来。从本质上看,只要记录下所有random值,那么这一次执行的样本也就能确定下来。

既然DLL注入这种“秋后算账”式记录方法总会导致不确定地丢失,那在每个html中提前生成一堆random值,让javascript去取怎么样?请看nduja中最初的random函数:

// 随机数
function rand( x ){return Math.floor( Math.random() * x );
}

我们改写为如下模板方式:

var random_int_array = %RANDOM_INT_ARRAY% ;
var array_index = 0 ;
function rand( x ){if(array_index >= %RANDOM_ARRAY_LENGTH%){array_index = 0 ;}return random_int_array[array_index++] % x ;
}

每次生成样本,采用Python脚本提前将模板中的%RANDOM_INT_ARRAY%替换为随机生成的数组:

var random_int_array = [5,100,45,67,88,9,101,34,25,……] ;
var array_index = 0 ;
function rand( x ){if(array_index >= 500){array_index = 0 ;}return random_int_array[array_index++] % x ;
}

每个文档的末尾采用Window.location.href将多个样本链接起来以便能依次打开它们。整个html文档格式如下图所示:

其基本原理如下图所示:

这样就将原本动态记录样本的方式变为静态生成样本的方式,完美解决了Grinder在样本恢复上的种种不足。

当然,这样做也有一定的弊端,Crash样本没有精简,所有的Crash不同的只是array数组的不同,后续的漏洞分析将是一场噩梦。因此,在后续样本分析之前,还需要提前精简样本。

0×03异常监控:如何监控浏览器发生了Crash

在对浏览器的异常检测上,通常有Pydbg和Windbg等方式,可以说各有千秋吧。但pydbg对python3和x64系统软件支持都不是特别好,另外windbg具有很吸引人的!exploitable插件,虽然某些时候给出的结论并不准确,但不失为一种对漏洞可利用性做出预判的方法,故而在Morph的开发过程中,采用windbg作为异常监控器。

在Windows下某个进程崩溃时,通常会弹出WerFault.exe异常提示,可以根据进程列表中有无WerFault.exe作为监控浏览器是否发生Crash的依据。

更好的方法是,将Windbg的命令行版cdb.exe设置为默认即时调试器:

>cdb.exe -iaec "-y -logo c:/log.txt -c \"!load msec.dll;!exploitable -v;\""

参数解释:

-iae
将CDB安装为即时调试器,该参数不能和其他参数一起使用。该命令并不实际启动CDB。
-log{o|a} LogFile
将日志记录到日志文件中。如果指定文件已存在,使用-logo 时会被覆盖,使用-loga 时会将新内容添加到后面。
-c "command"
指定启动时运行的初始调试器命令。该命令必须用引号括起来,多条命令可以使用分号来分隔。

当系统某个进程出现异常时,会调用cdb.exe并执行!load msec.dll;!exploitable指令,判断该异常是否是可利用的,最终log信息会存放在log.txt文件中。

下面是Python脚本判断cdb.exe进程的核心代码:

def Watch():config.MONITOR_RUNNING = True# 1.循环检测是否出现崩溃进程monitor_crash_proc()if config.MONITOR_RUNNING is True:# 2.检测到异常后保存当前样本save_crash_and_log ()
def monitor_crash_proc( ):while True:if LAST_COMPLETE_VECTOR >= (VECTORS_NUM-1):MONITOR_RUNNING = Falseif MONITOR_RUNNING is False or psutil.exist(‘cdb.exe’):return

利用Windbg的!exploitable插件得到的log信息大致如下:

Description: Read Access Violation near NULL
Short Description: ReadAVNearNull
Exploitability Classification: PROBABLY_NOT_EXPLOITABLE
Recommended Bug Title: Read Access Violation near NULL starting at MSHTML!CDoc::AFunC+0x058a (Hash=0x5789fdff.0x12345d4e)
This is a user mode read access violation near null, and is probably not exploitable.

可以提取出Short Description、Exploitability Classification、Hash等关键词作为Crash文件名。主要脚本如下:

def GetCrashHash(logPath):content = file.ReadFromFile(logPath)try:crash_exploitable = re.search("Explo..: \w+", content).group().replace("Exploitabi..: ", "", 1)crash_type = re.search("Short Description: \w+", content).group().replace("Short Description: ", "", 1)crash_hash = re.search("Hash=0x\w+\.0x\w+", content).group().replace("Hash=", "", 1)return ("%s_%s_%s" % (crash_exploitable, crash_type, crash_hash))except:return False

分析得到的文件名如下:

PROBABLY_NOT_EXPLOITABLE_ ReadAVNearNull_0x5789fdff.0x12345d4e.html

其中Windbg中采用的!Exploitable插件最新版是v1.6.0。

0×04 样本保存:如何记录导致浏览器崩溃的那个样本

根据前面Random数组的方式,很容易生成很多静态html样本,在每个html结尾添加window.location.href指向下一个html文档。

假设生成了N个html文档,则打开第一个文档,浏览器即可依次执行序号为1,2,3,…N的html文档:

但这个过程中如果浏览器出现崩溃,却无法判断是这N个html中的哪一个导致的崩溃。这种情况该如何解决呢?

既然问题的关键在于,如何确定发生崩溃时正在执行的样本序号,那有没有一种方法可以让html文档在被打开时,直接告诉监控器自身的序号呢?

分析到这里,容易想到,可以让每个html文档执行fuzz函数之前,提前调用一个函数告诉监控器自身的序号,如果浏览器出现崩溃,监控器读取当前获得的序号即可。

在Javascript中可以向服务端发送消息并返回的手段包括XMLHttpRequest和WebSocket。鉴于HTML5是WEB前端的潮流,这里采用WebSocket方式。

在每个HTML文档Javascript中的Fuzz函数之前,添加一条WebSocket.Send语句,由网页自身发起Socket通信,告诉监控器的WebSocket Server端关于自身的序号。当该网页导致浏览器崩溃时,监控器很容易就能知道是哪个网页引起的了。

每个html网页模板添加WebSocket.Send后的格式类似于:

在编程实现上,为了保证漏洞重现时,减少对WebSocket的依赖,对html模板的顺序调整如下:

所有网页被浏览器打开后依次执行的原理如下图:

具体在html模板页面中JavaScript增加的代码如下:
function morph_fuzz(){……
}
function morph_notify_href(){var socket  ;socket = new WebSocket('ws://127.0.0.1:8080/');socket.onopen = function(event) {socket.send('1');}socket.onmessage = function(event) {window.location.href = 2.html';}
}
function morph_main(){morph_fuzz();morph_notify_href();
}  
......
<body οnlοad="morph_main()"></body>

Server端负责记录最近被加载的html文档的序号:

def websocketHandle(websocket, path):while True:if not websocket.open:returnmsg = yield from websocket.recv()if msg is None:continue# 记录最近被加载的VECTOR样本序号LAST_COMPLETE_VECTOR = int(msg)yield from websocket.send('RUN')

当监控器监控到浏览器进程出现崩溃时,可以断定LAST_COMPLETE_VECTOR+1即为导致崩溃的样本序号,将其对应的html文档保存即可:

def save_crash_and_log ( ):# 得到当前Crash序号crash_num = config.LAST_COMPLETE_VECOTR + 1……# 保存当前样本和当前logfile.SaveFileFromSrcToDst(vectorCrashPath, dstCrashPath)file.SaveFileFromSrcToDst(debuggerLogPath, dstLogPath)……config.LAST_COMPLETE_VECOTR += 1

在保存该Crash样本后,LAST_COMPLETE_VECTOR自动+1,表示该样本已经被处理完成。

0×05 浏览器Fuzz需要注意的几个问题

在设计浏览器Fuzz框架过程中,会遇到很多看似很小但很重要的问题,下面列举和大家一起分享交流。

1.浏览器打开样本是采用file:///协议还是http://协议?

浏览器常用的是http和https协议,另外还支持file、data、gopher等协议。

在实际Fuzz中发现,部分网页使用http协议打开没事,但通过file协议打开就会导致Crash,猜测可能是由于file协议和http协议的权限不同造成的,也有可能是浏览器对file协议的解析不当(漏洞原因还未分析)。

目前在编程时,采用file协议打开样本,后续版本准备增加http等其它协议。

2.Firefox浏览器如何关闭Safemode安全模式?

默认情况下,采用第三方进程强制结束Firefox进程几次后,会弹出是否进入Firefox安全模式的提示,影响了正常Fuzz过程。需要在Firefox地址栏输入about:config,查找toolkit.startup.max_resumed_crashes,将其设置为-1即可关闭安全模式。

0×06 关于Morph框架的使用说明

上述就是Morph框架的核心思想,目前该工具仍在开发中,Github项目地址:

https://github.com/walkerfuz/morph

里面公开了nduja fuzzer插件,如果有兴趣的,可以根据Simple.html和nduja.html增加自定义插件。

关于Morph框架的安装和使用:

1. 安装Windbgx86 or Windbgx64。

下载MSECExtensions插件放在Windbg的winext文件夹,并打开Windbg测试load msec.dll是否成功,若出现Can't Load Library的错误,则需要安装Visual C++ Redistributable for Visual Studio 2008/2012。

注意:MSECExtensions1.6.0需要VC++2012运行时环境支持。

2. 将Windbg程序文件夹下的cdb.exe设置为默认JIT即时调试器:

>cdb.exe -iaec "-logo c:/log.txt -c \"!load msec.dll;!exploitable -v;\""

注意:c:/log.txt与config.py中的Debugger中的log参数必须一致

3. 如果Fuzz目标是Firefox,则需要关闭安全模式:

在firefox进入about:config找到toolkit.startup.max_resumed_crashes(默认是3),将其设置为-1即可。另外需要在Firefox选项–>隐私–>选择'不记录历史' 关闭Firefox的历史记录功能。

4.采用Windbg目录下自带的gflags.exe开启目标进程的页堆调试功能,比IE浏览器:

>gflags.exe /i iexplore.exe +hpa

5.下载Morph并配置Config.py中的默认参数。下面是几个比较常用的参数:

# configs which Can be modified
MOR_FUZZERS_FOLDER = "fuzzer"
MOR_CRASHES_FOLDER = "crash"
MOR_VECTORS_FOLDER = "vector"
MOR_FUZZER_SUFFIX = ".html"
MOR_DBGLOG_SUFFIX = ".log"
MOR_PRE_VECTORS_NUM = 50
MOR_RANDOM_ARRAY_LENGTH = 10000
MOR_MAX_RANDOM_NUMBER = 1000
MOR_WEBSOCKET_SERVER = "127.0.0.1:8080"
MOR_BROWSERS = {"IE": {'proc': 'iexplore.exe','args': "",'fault': "WerFault.exe",'path': "C:/Program Files/Internet Explorer/iexplore.exe",},"FF": {'proc': 'firefox.exe','args': "",'fault': "WerFault.exe",'path': "C:/Program Files (x86)/Mozilla Firefox/firefox.exe",},"CM": {'proc': 'chrome.exe','args': "--no-sandbox",'fault': "WerFault.exe",'path': "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",},
}
MOR_DEBUGGER = {"Windows": {'proc': "cdb.exe",'args': "",'path': "C:/Program Files (x86)/Debugging Tools for Windows (x86)/cdb.exe",'log': "C:/log.txt",}
}

请确保上述参数与实际环境对应起来。

6.运行:

morph.py --browser=IE --fuzzer=nduja.html

另附运行截图:

0×07 Fuzzer插件的开发方法

Morph采用插件式Fuzzer集成方法,目前提供了一个Simple.html模板和改进的nduja.html模板,请从Morph/Fuzzer目录中查看源代码,其中Simple.html如下:

<!DOCTYPE html>
<html>
<head>
<title>Morph Simple Fuzzer</title>
<script type='text/javascript'>
var random_int_array = %MOR_RANDOM_INT_ARRAY% ;
var array_index = 0 ;
// Pick a random number between 0 and X
function morph_rand( x ){if(array_index >= %MOR_RANDOM_ARRAY_LENGTH%){array_index = 0 ;}return random_int_array[array_index++] % x ;   
}
function rand_item( arr ){return arr[morph_rand(arr.length)];
}
elements = [
"a","abbr","input","ins","isindex","b","base","basefont","bdi","bdo","big","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","datalist","dd","del","details","dir","div","dfn","dialog","dl","dt","h1","h2","h3","h4","h5","h6","head","header","hr","html",];
MAX_ELEMENT_NUM = 200;
function morph_fuzz(){elementTree = [];  for(k=0;k<morph_rand(MAX_ELEMENT_NUM);k++){r = rand_item(elements);elementTree[k] = document.createElement(r);elementTree[k].id = "Element" + k;rb = morph_rand(document.all.length);document.all[rb].appendChild(elementTree[k]);  }
}
function morph_notify_href(){var socket  ;socket = new WebSocket('ws://%MOR_WEBSOCKET_SERVER%/');socket.onopen = function(event) {socket.send('%MOR_CURRENT_HREF%');}socket.onmessage = function(event) {window.location.href = '%MOR_NEXT_HREF%';           }
}
function morph_main(){morph_fuzz();morph_notify_href();
}  
</script>
</head>
<body onload="morph_main()">
</body>
</html>

在每次生成静态样本时,利用Python脚本,将%MOR_RANDOM_INT_ARRAY%、%MOR_RANDOM_ARRAY_LENGTH%替换为random数组,将%MOR_WEBSOCKET_SERVER%替换为config.py中设置的WebSocket服务器,将%MOR_CURRENT_HREF%和%MOR_NEXT_HREF%替换为前后连续的两个序号。

只要按照上面的插件编写逻辑,即可轻松实现Fuzzer插件的开发,有兴趣的童鞋可以将网上公开的fileja、cross_fuzz改写为Morph的插件,仍旧会发现很多漏洞滴。

0×08 总结

虽然目前纯粹做Fuzz已经不适应最新技术潮流了,但开发Morph时也学到了很多东西,同时对Fuzz的核心有了更深入的理解。

Morph在Fuzz效率(生成所有样本文件太耗时)和针对Linux、Andriod等兼容上还需要完善,自定义插件只编写了nduja,后面也会陆续发布比较经典的其它几种插件,让我们一起期待morph v0.3吧!

这篇关于从零开始学Fuzzing系列:浏览器挖掘框架Morph诞生记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/275266

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

Spring Framework系统框架

序号表示的是学习顺序 IoC(控制反转)/DI(依赖注入): ioc:思想上是控制反转,spring提供了一个容器,称为IOC容器,用它来充当IOC思想中的外部。 我的理解就是spring把这些对象集中管理,放在容器中,这个容器就叫Ioc这些对象统称为Bean 用对象的时候不用new,直接外部提供(bean) 当外部的对象有关系的时候,IOC给它俩绑好(DI) DI和IO