整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一文讀懂 JavaScript依賴注入

          家好,我是 Echa。

          依賴注入 DI (Dependency Injection) 是編程領域中一個非常常見的設計模式,它指的是將應用程序所需的依賴關系(如服務或其他組件)通過構造函數參數或屬性自動注入的過程。這樣做的好處是可以減少組件之間的耦合,更容易測試和維護。

          我們先舉個簡單的例子,我們有兩個簡單的 A 類和 B 類,在 B 類中依賴了 A 類,我們在 B 類中對它進行實例化,并調用它的方法:

          class A {
            constructor(name) {
              this.name = name;
            }
            log() {
              console.log("name: ", this.name);
            }
          }
          
          class B {
            a = new A("Echa");
          
            start() {
              this.a.log();
            }
          }
          
          const b = new B();
          b.start();
          

          但是這種寫法是非常不靈活的, A 類作為一個依賴項,它的初始化的邏輯被硬編碼到了 B 類中,如果我們想添加或修改其他的依賴項,必須要不斷修改 B 類。

          借助依賴注入的設計思想,我們可以將代碼改寫成下面這樣:

          class A {
            constructor(name) {
              this.name = name;
            }
            log() {
              console.log("name: ", this.name);
            }
          }
          
          class B {
            constructor(a) {
              this.a = a;
            }
          
            start() {
              this.a.log();
            }
          }
          
          const a = new A();
          const b = new B(a);
          b.start();
          

          代碼只做了很小的改動,最核心的變化就是我們將 A 類和 B 的實現完全分離開來了,他們無需再關心依賴的實例化,因為我們將依賴的注入提到的最外側。

          這也就是為什么我們常常將依賴注入和控制反轉 IoC (Inversion of Control) 放在一起講,控制反轉即將創建對象的控制權進行轉移,以前創建對象的主動權和創建時機是由自己把控的,而現在這種權力轉移到第三方。

          可能在這樣簡單的代碼中我們還看不出來什么好處,但是在大型的代碼庫中,這種設計可以顯著幫助我們減少樣板代碼,創建和連接依賴項的工作由一段程序統一處理,我們無需擔心創建特定類所需的類的實例。

          JavaScript 的各大框架中,依賴注入的設計模式也發揮著非常重要的作用,在 Angular、Vue.js、Next.js 等框架中都用到了依賴注入的設計模式。

          JavaScript 框架中的依賴注入

          Angular

          Angular 中大量應用了依賴注入的設計思想。Angular 使用依賴注入來管理應用的各個部分之間的依賴關系,以及如何將這些依賴關系注入到應用中,例如你可以使用依賴注入來注入服務、組件、指令、管道等。

          比如我們現在有個日志打點的工具類,我們可以使用 Injectable 將其指定為可注入對象。

          // logger.service.ts
          import { Injectable } from '@angular/core';
          
          @Injectable({providedIn: 'root'})
          export class Logger {
            writeCount(count: number) {
              console.warn(count);
            }
          }
          

          然后在組件中使用時,無需進行實例化,直接在 constructor 的參數中就可以取出自動注入好的對象:

          // hello-world-di.component.ts
          import { Component } from '@angular/core';
          import { Logger } from '../logger.service';
          
          @Component({
            selector: 'hello-world-di',
            templateUrl: './hello-world-di.component.html'
          })
          export class HelloWorldDependencyInjectionComponent  {
            count = 0;
          
            constructor(private logger: Logger) { }
          
            onLogMe() {
              this.logger.writeCount(this.count);
              this.count++;
            }
          }
          

          Vue.js

          Vue.js 中,provideinject 其實也使用了依賴注入的設計模式。

          • provide 屬性可以用來在父組件中提供一個值,這個值可以在父組件的所有子組件中注入。
          export default {
            name: 'Parent',
            provide() {
              return {
                user: this.user
              };
            },
            data() {
              return {
                user: {
                  name: 'John',
                  age: 30
                }
              };
            }
          };
          
          
          • inject 屬性可以用來在子組件中注入父組件提供的值。
          // 子組件
          export default {
            name: 'Child',
            inject: ['user'],
            computed: {
              userName() {
                return this.user.name;
              }
            }
          };
          

          React.js

          React.js 中,并沒有直接使用依賴注入的地方,不過我們依然可以借助一些第三方庫來實現, 比如我們可以通過 InversifyJS 提供的 injectable decorator 標記 class 是可被注入的。

          import { injectable } from "inversify";
          
          export interface IProvider<T> {
            provide(): T;
          }
          
          @injectable()
          export class NameProvider implements IProvider<string> {
            provide() {
              return "World";
            }
          }
          

          在組件中,我們可以直接調用注入的 provide 方法,而組件內部不用關心它的實現。

          import * as React from "react";
          import { IProvider } from "./providers";
          
          export class Hello extends React.Component {
            private readonly nameProvider: IProvider<string>;
          
            render() {
              return <h1>Hello {this.nameProvider.provide()}!</h1>;
            }
          }
          

          手動實現依賴注入

          前面我們提到的 InversifyJS 實際上就是一個專門用來實現依賴注入的工具庫,它主要就由 injectableinject 等幾個裝飾器組成的,這么神奇的功能究竟是咋實現的呢,下面我們手動來實現一下。

          首先我們來明確一個需求場景,假設我們要使用 Koa 框架開發一個簡單的 Node.js 服務。

          Koa 中,Controller 用來處理用戶請求和響應,它負責接收用戶的請求,然后調用相應的服務或業務邏輯進行處理,最后將處理結果返回給用戶。Service 用來封裝業務邏輯和數據處理,它負責實現應用程序的核心功能。

          Service 通常會被多個 Controller 所調用,它們之間是松散耦合的關系,我們希望用兩裝飾器來實現 Service 的自動依賴注入:

          export default class UserController extends Controller {
            @Inject
            user: UserService;
          
            @UseService
            async list(ctx: ThriftContext): Promise<void> {
              const user = await this.user.findAll({ id: 1000 });
              console.log(1, user);
            }
          }
          

          在實現過程中我們可能會用到兩個非常重要的 API,Metadata Reflection API 以及 Decorator API,我們先分別來回顧一下它們的基礎知識。

          Decorator API

          裝飾器模式是一種經典的設計模式,其目的是在不修改被裝飾者(如某個函數、某個類等)源碼的前提下,為被裝飾者增加 / 移除某些功能。一些現代編程語言在語法層面提供了對裝飾器模式的支持,并且各語言中的現代框架都大量應用了裝飾器。主要用處分為兩大類:

          • 收集用戶定義的類/函數的信息(例如,用于生成路由表,用于實現依賴注入,等等)
          • 對用戶定義的類/函數進行增強,增加額外功能

          我們目前用的比較多的裝飾器就是 TypeScript 的實驗性裝飾器,以及 ECMAScript中還處于 legacy 階段的 Decorator API,下面是它的用法:

          裝飾類的時候,裝飾器方法一般會接收一個目標類作為參數,下面是一個示例,給類增加靜態屬性、原型方法:

          const addField = target => {
            target.age = 17;
            target.prototype.speak = function () {
              console.log('xxx');
            };
          };
          
          @addField
          class People {
            
          }
          
          console.log(People.age);
          const a = new People();
          a.speak();
          

          類屬性裝飾器可以用在類的屬性、方法、get/set 函數中,一般會接收三個參數:

          • target:被修飾的類
          • name:類成員的名字
          • descriptor:屬性描述符,對象會將這個參數傳給 Object.defineProperty

          下面是一個示例,可以修改類屬性為只讀:

          function readonly(target, name, descriptor) {
            descriptor.writable = false;
            return descriptor;
          }
          
          class Person {
              @readonly name = 'person'
          }
          
          const person = new Person();
          person.name = 'tom'; 
          

          Metadata Reflection API

          ReflectJavaScript 中的一個內置對象,它提供了一組用于操作對象的方法。它與其他內置對象類似,但是它的目的是為了提供一組用于操作對象的通用方法。

          Reflect MetadataES7 的一個提案,它主要用來在聲明的時候添加和讀取元數據。

          Reflect.getMetadata('design:type', target, key) 可以用來獲取類 target 中屬性 key 的類型信息:

          function Inject() {
            return function (target: any, key: string, descriptor: PropertyDescriptor) {
              const type = Reflect.getMetadata('design:type', target, key);
              console.log(type); // [class Service]
              return descriptor;
            };
          }
          
          export default class WebsiteController extends Controller {
            @Inject()
            service: Service
          
            // ... 
          }
          

          Reflect.getMetadata('design:paramtypes', target, key) 可以用來獲取類 target 中屬性 key 的函數參數類型;

          Reflect.getMetadata('design:returntype', target, key) 可以用來獲取類 target 中屬性 key 的函數返回值類型。

          除能獲取固定的類型信息之外,也可以自定義 MetaData,并在合適的時機獲取它的值,示例如下:

          function classDecorator(): ClassDecorator {
            return target => {
              // 在類上定義元數據,key 為 `classMetaData`,value 為 `a`
              Reflect.defineMetadata('classMetaData', 'a', target);
            };
          }
          
          @classDecorator()
          class SomeClass {
            
          }
          
          Reflect.getMetadata('classMetaData', SomeClass); // 'a'
          

          好了,有了這些知識,我們就可以手動來實現一個依賴注入裝飾器了。

          實現依賴注入

          再明確一下我們的需求:在不同服務的 Controller 中共用 Service,使用 Service 時可以自動獲取已注入的 Service 實例,同時 Service 里可以獲取到請求的 Context 信息。

          首先我們來實現,Inject 裝飾器:

          • Controller 中注冊需要用到哪些 Service
          • 通過 design:type 獲取 Service 的類型信息
          • 通過自定義 metadata 存儲 Controller 中用到哪些 Service
          function Inject(target: any, key: string) {
            console.log(`注冊 Controller: ${target} Service: ${key}`);
            // 獲取當前 Service 的類型
            const serviceClass = Reflect.getMetadata('design:type', target, key);
            // 獲取當前 Controller 已經注冊過的 Service List
            const serviceList = Reflect.getMetadata(META_KEY_CONTROLLER_SERVICE, target) || [];
            // 將當前 Service 進行追加
            Reflect.defineMetadata(
              META_KEY_CONTROLLER_SERVICE,
              [...serviceList, { serviceClass, serviceName: key }],
              target
            );
          }
          

          然后是 UseService 裝飾器:

          • 在請求過來時取出 metadata 中存儲的 ControllerService 對應信息
          • Service 實例化,并將 Context 傳入 Service
          function UseService(target: any, name: string, descriptor: PropertyDescriptor) {
            const value = descriptor.value;
            descriptor.value = async function (...args: any) {
              // 獲取當前請求的 Context
              const [ctx] = args;
              // 取出當前 Controller 已綁定的 Service
              const serviceList = Reflect.getMetadata(META_KEY_CONTROLLER_SERVICE, target) || [];
              console.log(serviceList);
          
              for (let i = 0; i < serviceList.length; i++) {
                const { serviceClass, serviceName } = serviceList[i];
                // 實例化 Service 并綁定 Context
                const service = new serviceClass(ctx);
                Reflect.set(service, 'ctx', ctx);
                // 給當前 Controller 掛載 Service 實例
                Reflect.set(target, serviceName, service);
              }
              return await Promise.resolve(value.apply(this, args));
            };
            return descriptor;
          }
          

          好了,接下來就可以愉快的使用了~

          export default class UserController extends Controller {
            @Inject
            user: UserService;
          
            @UseService
            async list(ctx: ThriftContext): Promise<void> {
              const user = await this.user.findAll();
              console.log(1, user);
            }
          }
          

          最后

          如果這篇文章幫助到了你,歡迎點贊和關注。

          019年10月,貴陽某高校網站遭遇黑客攻擊,登錄頁面被非法篡改為違法有害信息。警方根據《中華人民共和國網絡安全法》第21條、 第59條之規定,依法對該高校及高校負責人分別處以10萬元和5萬元的行政罰款。


          網站被攻擊了,網站負責人還要被處罰,這到底是怎么回事?

          實際上,《網絡安全法》規定,網站運營者應負擔網站網絡安全保護義務。

          近年來,網絡安全形勢日趨嚴峻,威脅持續上升。為檢驗企事業單位關鍵信息基礎設施安全防護能力,提升網絡安全應急處置隊伍應對能力,自2016年起,我國每年都會定期開展“HW行動”,以全國范圍內的真實網絡目標為對象進行實戰攻防活動。

          今年是建黨100周年,也是COP15(聯合國《生物多樣性公約》第十五次締約方大會)的召開之年。新一輪的“HW行動”近期正在開展,我省安全主管部門加大了對各類網站的檢測力度,嚴查可能出現的網絡安全風險問題。

          近一個月,藍隊云已經收到了多家被通報企業的緊急求助,這些網站或多或少都被檢測出了不同類型的漏洞風險,并要求在規定時間內完成整改。

          大部分被通報的網站會面臨兩種類型的漏洞。

          1、軟件代碼漏洞

          簡單來說就是自行開發或使用的第三方公司提供的軟件存在漏洞,這一類的漏洞一般是軟件開發者代碼編寫不規范造成的隱患,或者是編程語言的局限性導致的漏洞,軟件代碼漏洞可能會被入侵者利用。

          2、服務器漏洞

          在硬件、軟件、協議的具體實現或系統安全策略上存在的缺陷,從而可以使攻擊者能夠在未授權的情況下訪問或破壞系統。一般服務器供應商只提供計算環境和網絡帶寬服務,服務器的密碼和系統安全設置都掌握在用戶自己手中,服務器安全依賴于使用主體對服務器的安全部署。

          藍隊云根據近幾年在“HW行動”中被通報的網站漏洞情況,將常見漏洞做了以下整理:

          1、跨站腳本XSS

          黑客通過“HTML注入”篡改了網頁,插入了惡意腳本,從而在用戶在瀏覽網頁時,實現控制用戶瀏覽器行為的一種攻擊方式。

          2、SQL注入

          SQL注入是比較常見的網絡攻擊方式之一,它不是利用操作系統的BUG來實現攻擊,而是針對程序員編寫時的疏忽,通過SQL語句,實現無賬號登錄,甚至篡改數據庫。

          3、WEB服務器漏洞

          服務器解析漏洞依然廣泛存在,常見Web服務器的解析漏洞有Apache/Nginx/IIS等。

          4、數據庫漏洞

          常見的數據庫漏洞主要有Oracle/MySQL兩大類。內外部黑客會想法利用管理、網絡、主機或數據庫的自身漏洞嘗試入侵到數據庫中,以達到自身的目的。

          5、弱口令

          弱口令指的是僅包含簡單數字和字母的口令,例如“123”、“abc”等,因為這樣的口令很容易被別人破解,通過系統弱口令,可被黑客直接獲得系統控制權限。

          6、信息泄漏漏洞

          最常見的信息泄漏包含電子郵件地址泄漏、數據庫信息泄漏、內網IP地址泄漏、網站路徑泄漏風險,這類漏洞信息泄露可能是不慎泄露的,也有可能是攻擊者通過惡意的交互從網站獲得數據。


          建議有預算的企事業單位、社會機構盡早進行安全策略的部署,防患于未然。藍隊云為云上用戶提供基礎的安全策略配置,可有效提升服務器的安全級別,同時附送5G基礎流量型DDOS攻擊防御;當然也可以請安全專家來進行整體加固,防范系統層和應用層的安全風險。

          如果遇到自己處理不了的網站漏洞,也可以隨時隨地找隊長免費救援,針對在護網中遇到“困難”的企業,我們可免費提供專家級風險評估服務,幫助您點對點處理問題。

          背景

          京東SRC(Security Response Center)收錄大量外部白帽子提交的sql注入漏洞,漏洞發生的原因多為sql語句拼接和Mybatis使用不當導致。

          2 手工檢測

          2.1 前置知識

          mysql5.0以上版本中存在一個重要的系統數據庫information_schema,通過此數據庫可訪問mysql中存在的數據庫名、表名、字段名等元數據。information_schema中有三個表成為了sql注入構造的關鍵。

          1)infromation_schema.columns:

          • table_schema 數據庫名
          • table_name 表名
          • column_name 列名

          2)information_schema.tables

          • table_schema 數據庫名
          • table_name 表名

          3)information_schema.schemata

          • schema_name 數據庫名

          SQL注入常用SQL函數

          • length(str) :返回字符串str的長度
          • substr(str, pos, len) :將str從pos位置開始截取len長度的字符進行返回。注意這里的pos位置是從1開始的,不是數組的0開始
          • mid(str,pos,len) :跟上面的一樣,截取字符串
          • ascii(str) :返回字符串str的最左面字符的ASCII代碼值
          • ord(str) :將字符或布爾類型轉成ascll碼
          • if(a,b,c) :a為條件,a為true,返回b,否則返回c,如if(1>2,1,0),返回0

          2.2 注入類型

          2.2.1 參數類型分類

          • 整型注入
            例如?id=1,其中id為注入點,類型為int類型。
          • 字符型注入
            例如?id=”1”,其中id為注入點,類型為字符型,要考慮閉合后端sql語句中的引號。

          2.2.2 注入方式分類

          • 盲注
          • 布爾盲注:只能從應用返回中推斷語句執行后的布爾值。
          • 時間盲注:應用沒有明確的回顯,只能使用特定的時間函數來判斷,例如sleep,benchmark等。
          • 報錯注入:應用會顯示全部或者部分的報錯信息
          • 堆疊注入:有的應用可以加入 ; 后一次執行多條語句
          • 其他

          2.3 手動檢測步驟(字符型注入為例)

           // sqli vuln code
                      Statement statement = con.createStatement();
                      String sql = "select * from users where username = '" + username + "'";
                      logger.info(sql);
                      ResultSet rs = statement.executeQuery(sql);
          // fix code 如果要使用原始jdbc,請采用預編譯執行
                      String sql = "select * from users where username = ?";
                      PreparedStatement st = con.prepareStatement(sql);

          使用未預編譯原始jdbc作為demo,注意此demo中sql語句參數采用單引號閉合。

          2.3.1 確定注入點

          對于字符類型注入,通常先嘗試單引號,判斷單引號是否被拼接到SQL語句中。推薦使用瀏覽器擴展harkbar作為手工測試工具。https://chrome.google.com/webstore/detail/hackbar/ginpbkfigcoaokgflihfhhmglmbchinc

          正常頁面應該顯示如下:

          admin后加單引號導致無信息回顯,原因是后端sql執行報錯,說明引號被拼接至SQL語句中

          select * from users where username = 'admin'  #正常sql
          select * from users where username = 'admin'' #admin'被帶入sql執行導致報錯無法顯示信息

          2.3.2 判斷字段數

          mysql中使用order by 進行排序,不僅可以是字段名也可以是字段序號。所以可以用來判斷表中字段數,order by 超過字段個數的數字就會報錯。

          判斷字段數

          當order by 超過4時會報錯,所以此表共四個字段。

          后端所執行的sql語句

          select * from users where username = 'admin' order by 1-- '

          此處我們將原本username的值admin替換為admin’ order by 1 —+,其中admin后的單引號用于閉合原本sql語句中的前引號,—+用于注釋sql語句中的后引號。—后的+號主要作用是提供一個空格,sql語句單行注釋后需有空格,+會被解碼為空格。

          2.3.3 確定回顯位置

          主要用于定位后端sql字段在前端顯示的位置,采用聯合查詢的方式確定。注意聯合查詢前后字段需一致,這也就是我們為什么做第二步的原因。

          通過下圖可知,后端查詢并回顯的字段位置為2,3位。

          聯合查詢后的字段可以隨意,本次采用的是數字1到4直觀方便。

          2.3.4 利用information_schema庫實現注入

          group_concat()函數用于將查詢結果拼接為字符串。

          • 查看存在數據庫

          • 查看當前數據庫中的表

          • 查看指定表中字段

          • 利用以上獲取信息讀取users表中username和password

          3 自動化檢測

          3.1 sqlmap 使用

          sqlmap兼容python2和python3,可以自動化檢測各類注入和幾乎所有數據庫類型。

          3.1.1 常用命令

          -u  可能存在注入的url鏈接
          -r讀取http數據包
          --data 指定post數據
          --cookie 指定cookie
          --headers 指定http頭 如采用token認證的情況下
          --threads 指定線程數
          --dbms 指定后端的數據庫
          --os 指定后端的操作系統類型
          --current-user 當前用戶
          --users 所有用戶
          --is-dba 是否是dba
          --sql-shell 交互式的sqlshell
          -p指定可能存在注入點的參數
          --dbs 窮舉系統存在的數據庫
          -D指定數據庫
          --tables 窮舉存在的表
          -T指定表
          --column 窮舉字段
          -C指定字段
          --dump dump數據

          直接檢測
          其中—cookie用于指定cookie,—batch 自動化執行,—dbms指定數據庫類型

          檢測結果

          讀取系統中存在數據庫
          —dbs讀取當前用戶下的數據庫

          讀取指定庫下的表
          -D java_sec_code —tables

          dump users表數據
          -D java_sec_code -T users —dump

          4 進階

          4.1 Mybatis注入

          1)$錯誤使用導致注入

          //采用#不會導致sql注入,mybatis會使用預編譯執行
              @Select("select * from users where username = #{username}")
              User findByUserName(@Param("username") String username);
          //采用$作為入參可導致sql注入
              @Select("select * from users where username = '${username}'")
              List<User> findByUserNameVuln01(@Param("username") String username);

          2)模糊查詢拼接

          //錯誤寫法
            <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
                  select * from users where username like '%${_parameter}%'
              </select>
          
           //正確寫法
           <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
                  select * from users where username like concat(‘%’,#{_parameter}, ‘%’)  
              </select>

          3)order by 注入

          order by 后若使用#{}會導致報錯,因為#{}默認添加引號會導致找不到字段從而報錯。

             //錯誤寫法 
          <select id="findByUserNameVuln03" parameterType="String" resultMap="User">
                  select * from users
                  <if test="order != null">
                      order by ${order} asc
                  </if>
              </select>
          //正確寫法 id指字段id 此表字段共四個 所以id為1-4
              <select id="OrderByUsername" resultMap="User">
                  select * from users order by id asc limit 1
              </select>

          以上測試均在本地進行,請勿未授權進行滲透測試

          5 文章及資料推薦

          slqmap手冊:https://octobug.gitbooks.io/sqlmap-wiki-zhcn/content/Users-manual/Introduction.html
          sql注入詳解:http://sqlwiki.radare.cn/#/


          作者:羅宇(物流安全小分隊)


          主站蜘蛛池模板: 日本大香伊一区二区三区| 国内自拍视频一区二区三区| 久久久久人妻一区精品果冻| 丰满爆乳一区二区三区| 美女视频一区三区网站在线观看| 人妻无码一区二区三区四区| 精品一区二区三区3d动漫| 日本精品高清一区二区2021| 亚洲国产成人一区二区三区| 国产AV一区二区三区传媒| 久久精品一区二区三区中文字幕| 麻豆视传媒一区二区三区| 岛国精品一区免费视频在线观看 | 国产伦精品一区二区三区视频猫咪| 天堂va在线高清一区 | 日本一道高清一区二区三区| 中文字幕日韩精品一区二区三区| 在线观看中文字幕一区| 日本一区二区三区不卡视频中文字幕| 亚洲日韩一区二区一无码| 亚洲欧洲日韩国产一区二区三区 | 老熟妇仑乱视频一区二区| 亚洲丰满熟女一区二区v| 午夜DV内射一区区| 午夜DV内射一区区| 天码av无码一区二区三区四区| 国产精品亚洲一区二区无码| 午夜精品一区二区三区在线视 | 日本强伦姧人妻一区二区| 精品视频午夜一区二区| 色一情一乱一伦一区二区三区日本 | 精品欧洲av无码一区二区14| 亚洲av成人一区二区三区观看在线| 国产美女一区二区三区| 精品无码国产一区二区三区51安| 精品国产天堂综合一区在线| jizz免费一区二区三区| 波多野结衣久久一区二区| 精品国产免费观看一区| 区三区激情福利综合中文字幕在线一区| 国产一区二区高清在线播放|