家好,我是 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 等框架中都用到了依賴注入的設計模式。
在 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 中,provide 和 inject 其實也使用了依賴注入的設計模式。
export default {
name: 'Parent',
provide() {
return {
user: this.user
};
},
data() {
return {
user: {
name: 'John',
age: 30
}
};
}
};
// 子組件
export default {
name: 'Child',
inject: ['user'],
computed: {
userName() {
return this.user.name;
}
}
};
在 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 實際上就是一個專門用來實現依賴注入的工具庫,它主要就由 injectable 、inject 等幾個裝飾器組成的,這么神奇的功能究竟是咋實現的呢,下面我們手動來實現一下。
首先我們來明確一個需求場景,假設我們要使用 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,我們先分別來回顧一下它們的基礎知識。
裝飾器模式是一種經典的設計模式,其目的是在不修改被裝飾者(如某個函數、某個類等)源碼的前提下,為被裝飾者增加 / 移除某些功能。一些現代編程語言在語法層面提供了對裝飾器模式的支持,并且各語言中的現代框架都大量應用了裝飾器。主要用處分為兩大類:
我們目前用的比較多的裝飾器就是 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 函數中,一般會接收三個參數:
下面是一個示例,可以修改類屬性為只讀:
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly name = 'person'
}
const person = new Person();
person.name = 'tom';
Reflect 是 JavaScript 中的一個內置對象,它提供了一組用于操作對象的方法。它與其他內置對象類似,但是它的目的是為了提供一組用于操作對象的通用方法。
Reflect Metadata 是 ES7 的一個提案,它主要用來在聲明的時候添加和讀取元數據。
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 裝飾器:
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 裝飾器:
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使用不當導致。
mysql5.0以上版本中存在一個重要的系統數據庫information_schema,通過此數據庫可訪問mysql中存在的數據庫名、表名、字段名等元數據。information_schema中有三個表成為了sql注入構造的關鍵。
SQL注入常用SQL函數
// 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語句參數采用單引號閉合。
對于字符類型注入,通常先嘗試單引號,判斷單引號是否被拼接到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執行導致報錯無法顯示信息
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語句單行注釋后需有空格,+會被解碼為空格。
主要用于定位后端sql字段在前端顯示的位置,采用聯合查詢的方式確定。注意聯合查詢前后字段需一致,這也就是我們為什么做第二步的原因。
通過下圖可知,后端查詢并回顯的字段位置為2,3位。
聯合查詢后的字段可以隨意,本次采用的是數字1到4直觀方便。
group_concat()函數用于將查詢結果拼接為字符串。
sqlmap兼容python2和python3,可以自動化檢測各類注入和幾乎所有數據庫類型。
-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
//采用#不會導致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);
//錯誤寫法
<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>
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>
slqmap手冊:https://octobug.gitbooks.io/sqlmap-wiki-zhcn/content/Users-manual/Introduction.html
sql注入詳解:http://sqlwiki.radare.cn/#/
*請認真填寫需求信息,我們會在24小時內與您取得聯系。