Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文最初發布于 Bits and Pieces 博客,經原作者授權由 InfoQ 中文站翻譯并分享。
微前端是一個可以追溯到多年前的新趨勢。隨著新方法的出現以及各種挑戰被克服,它們正在慢慢地進入主流。但遺憾的是,許多非常明顯的認識誤區,讓許多人很難理解微前端到底是什么。
簡而言之,微前端就是將微服務的一些好處引入前端。除此之外,我們不應該忘記,微服務也不是什么“銀彈”。
提示:要在微前端或任何其他項目之間共享 React/Angular/Vue 組件,可以使用像 Bit 這樣的工具。它允許你從任何代碼庫中“harvest”組件,并將它們共享到 bit.dev 的一個集合中。它讓團隊可以在任何存儲庫中使用你的組件。使用它可以優化協作、加速開發和保持 UI 一致性。
示例:在 bit.dev 中查找共享 React 組件
我想列一下在過去幾個月中,我最常聽到的關于微前端的誤解。先從從一個明顯的例子開始。
目前,許多微前端解決方案都是 JavaScript 框架。這怎么可能錯呢?JavaScript 不再是可選的。每個人都想要高度交互的體驗,而 JS 在提供這些體驗中發揮著至關重要的作用。
除了加載速度快、可訪問 Web 應用的優點外,還有其他因素應該考慮。因此,許多 JavaScript 框架都提供了 isomorphic 渲染能力。最終,這讓它們不僅能夠在客戶端進行拼接(stitch),還能在服務器上準備好一切。如果有性能要求(如第一次有意義渲染的初始時間),這個選項聽起來很不錯。
請記住,isomorphic 渲染有其自身的挑戰。
然而,即使一個 JavaScript 解決方案沒有提供 isomorphic 呈現,這也沒什么問題。如果不想在構建微前端時使用 JavaScript,我們當然可以這樣做。有許多模式,其中很多根本不需要 JavaScript。
考慮一種“比較舊的”模式:使用<frameset>。我聽見你笑了?好吧,有一些現如今人們試圖做的分割,它以前就支持了(下文有更詳細的討論)。一個頁面(可能由另一個服務渲染)負責菜單,而另一個頁面負責標題。
復制代碼
<frameset cols="25%,*,25%"> <frame src="menu.html"> <frame src="content.html"> <frame src="sidebar.html"></frameset>
如今,我們使用更靈活(且仍然受到活躍支持)的<iframe>元素。它們提供了一些很好的特性——最重要的是使得不同的微前端相互隔離,但仍然可以通過postMessage進行通信。
在 JavaScript 認識誤區之后,這是下一個層次。當然,在客戶端有多種技術可以實現微前端,實際上,我們甚至不需要<iframe>或任何類似的技術。
微前端可以像服務器端“includes”一樣簡單。有了諸如 Edge Side Includes 之類的高級技術,這將變得更加強大。如果我們排除了在微前端功能中實現的微前端場景,那么即使是簡單的鏈接也可以很好的工作。最終,微前端解決方案也能像小而獨立的服務器端渲染器一樣簡單。每個渲染器可能只有一個頁面那么小。
下圖展示了在反向代理中發生的更高級的拼接:
通過反向代理實現服務器端拼接
當然,可能 JavaScript 有許多優點,但它仍然高度依賴于你試圖通過微前端解決的問題。根據你的需要,服務器端解決方案可能仍然是最好的(或者至少是更好的)選擇。
在幾乎每一個關于微前端的教程中,不同的部分不僅由不同的團隊開發,而且使用了不同的技術。這是假的。
適當的微前端方法可能使用不同的技術,但是,這不應該是目標。我們做微服務也不只是為了在后端拼湊技術。如果我們使用多種技術,那只是因為我們獲得了一個特定的好處。
我們的目標應該始終是某種統一性。最好的方法是考慮一個新項目:我們會怎么做?如果答案是“使用單一框架”,那么我們就走上正軌了。
長遠來看,有很多原因可以解釋為什么應用程序中會出現多個框架。可能是遺留的,可能為了方便,也可能是一個概念驗證。無論是什么原因:能夠處理這種場景還是不錯的,但它絕不應該是開始就希望達到的狀態。
不管你的微前端框架多高效——使用多個框架總是要付出不可忽視的代價。不僅初始渲染會花費更長的時間,而且內存消耗也會朝著錯誤的方向發展。不能使用方便模型(例如,針對某個框架的模式庫)。需要更多的重復。最終,程序的 Bug 數量、不一致行為和可感知的響應性都會受到影響。
一般來說,這沒有多大意義。我還沒見過微服務后端的數據處理在一個服務中而 API 在另一個服務中。通常,服務由多個層組成。雖然某些技術內容(如日志記錄)肯定會引入到公共服務中,但有時也會使用諸如 Sidecar 之類的技術。此外,還需要通用服務編程技術。
對于微前端,情況也是如此。為什么一個微前端只能做菜單?每個微前端都有相應的菜單嗎?拆分應該根據業務需求來做,而不是技術決策。如果你讀過一些關于領域驅動設計的書,你就會知道它是關于定義這些領域的——而這個定義與任何技術要求無關。
考慮以下劃分:
按布局分解成微前端
這些都是技術組件,和微前端無關。在一個真實的微前端應用程序中,屏幕可能看起來是這樣的。
按領域分解成微前端
的確,這里的拼接要復雜得多,但這是一個可靠的微前端應用程序應該為你提供的!
不。你應該共享那些值得共享的東西。你絕對不應該共享所有東西(見下一條)。但要做到始終如一,你至少需要共享一套原則。至于是通過共享庫、共享 URL,或者只是在構建或設計應用程序時使用的文檔,那就不重要了。
對于微服務,“無共享”架構如下圖所示:
微服務的“無共享”架構
在瀏覽器中,這將導致使用<iframe>,因為目前沒有其他方法可以防止資源泄漏。使用影子 DOM,則 CSS 可能會被隔離,但腳本層面仍然能訪問所有內容。
即使想遵循無共享的架構,我們也會遇到麻煩。
當然,共享的程度越深(例如,使用一個通過應用 shell 追加到 DOM 的共享庫),就越會出問題。另一方面,共享程度越淺(例如,只是一個指定基本設計元素的文檔),就會出現更多的不一致性。
絕對不是。如果這樣想,那么單體更有意義。就性能而言,這可能已經是一個問題了。什么可以延遲加載?我們能去掉一些東西嗎?但真正的問題是依賴管理。什么都不能更新,因為它可能會破壞某個東西。
共享部件的好處是一致性保證。
現在,如果我們共享所有的東西,我們就是用復雜性來換一致性。這種一致性是不可維護的,因為復雜性會在每個角落引入 Bug。
問題的根源在于“依賴地獄”。下圖很好地說明了這一點。
簡而言之,如果一切都相互依賴,那么我們就會有依賴問題。僅僅更新一個方框就會影響整個系統。一致嗎?確實。簡單的?絕不。
為什么只是 Web?誠然,到目前為止,我們接觸到的主要是 Web,但其概念和想法可以應用于任何類型的應用程序(移動應用、客戶端應用……甚至是 CLI 工具)。在我看來,微前端只是“插件架構”的一個花哨叫法。不過,插件接口如何設計,以及運行使用插件的應用程序需要具備什么條件,這就是另外一回事了。
下圖顯示了一個非常通用的插件架構,來自 Omar Elgabry :
通用插件架構
該架構并沒有在哪里運行的概念。它既可以在手機上運行,也能在 Windows 上運行,甚至還能在服務器上運行。
為什么?如果解決方案超級復雜,那么我肯定會找一個簡單的。有些問題需要復雜的解決方案,但好的解決方案通常是簡單的。
根據場景的不同,它甚至可能不需要一個分布式團隊。擁有分布式團隊是采用微前端的首要原因之一,但這不是唯一原因。另一個很好的理由是特性的粒度。
如果從業務的角度來看微前端,那么你就會發現,擁有啟用和關閉特定特性的能力是很有意義的。針對不同的市場,使用不同的微前端。回到一個簡單的權限模式,這是有意義的。不需要編寫代碼來根據特定條件打開或關閉某些東西。所有這些都留給公共層,可以根據(可能是動態的)條件激活或停用。
這樣,不能(或不應該)使用的代碼也不會被交付。雖然這不應該是一個保護層,但它肯定是一個便捷(和性能)層。用戶不會感到困惑,因為他們看到的是他們能做的。他們看不到沒有交付的功能,所以沒有字節浪費在不可用的代碼上。
對于任何類型的實現(或供討論的底層架構),開發經驗都可能遭到削弱。應對這種情況的唯一方法是開發人員優先。實現中的第一原則應該是:使調試和開發成為可能。采用標準的工具。
有些微前端框架根本不接受這一點。有些需要在線連接、專用環境、多重服務……這不應該是標準,也絕不是常態。
解耦的模塊化后端可能為解耦前端打下了一個很好的基礎,但通常情況下,情況并非如此。后端單體,前端模塊化,也是完全可行的,例如,為簡化個性化可能就要結合授權、權限和市場。
實際上,同樣,微服務后端并不能證明適合將類似的模式應用于前端。許多微服務后端都是由單用途的應用程序操作的,它們的功能沒有增加,只是外觀發生了改變。
我已經讀到過好幾次,要創建一個微前端解決方案,就需要利用單存儲庫,最好使用像 Lerna 這樣的工具。我不認可這一點。當然,單存儲庫有一些優點,但也有明顯的缺點。
雖然有一些微前端框架需要聯合 CI/CD 構建,但大多數都不需要。聯合 CI/CD 構建通常會導致單存儲庫,因為其設置要簡單得多。但對我來說,這是單體重新打包。如果你在單存儲庫上進行聯合構建,那么你就失去了讓微前端富有吸引力的兩個非常重要的優點:
不管怎樣,如果你看到微前端解決方案需要單存儲庫:那樣做就行。一個精心設計的單體系統可能會更好,它不會有分布式系統的所有問題。
微前端技術并不適合所有人。我不認為微前端是未來的發展趨勢,但我也相信它們在未來會發揮重要作用。
關注我并轉發此篇文章,私信我“領取資料”,即可免費獲得InfoQ價值4999元迷你書,點擊文末「了解更多」,即可移步InfoQ官網,獲取最新資訊~
一文章中整合了spring cloud 用Sentinel來實現降級熔斷,并用Nacos來作為配置存儲。需要看的朋友可以查看我的主頁,或者直接點擊:面對大流量如何優雅熔斷降級Sentinel實踐 。在上文我們實現了Sentine控制臺將配置推送到應用服務,但是還沒有實現Sentine控制臺將配置實時推拉到Nacos,本文將通過修改官方的源碼來實現此步驟。
上篇文章中的推拉數據如下:
Sentinel控制臺并沒有與遠程配置中心Nacos相連接。本文將實現連接到配置中心。
可以下載最新的版本的:
https://github.com/alibaba/Sentinel
,也可以下載穩定版本的地方下載源碼:
https://github.com/alibaba/Sentinel/releases
具體如下:
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- 使用Sentinel Dashboard動態推拉數據同步到Nacos 注掉test-->
<!-- <scope>test</scope>-->
</dependency>
找到resources/app/scripts/directives/sidebar/sidebar.html中的這段代碼
把原來:
<li ui-sref-active="active">
<a ui-sref="dashboard.flowV1({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規則
</a>
</li>
修改為:
<li ui-sref-active="active">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規則
</a>
</li>
如下:
原來:
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.flowV1({app: app})">
回到單機頁面
</a>
修改為:
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.flow({app: app})">
回到單機頁面
</a>
修改效果如下:
如下圖:
包的結構圖如下:
1、添加FlowRuleNacosProvider
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eric Zhao,yaokj
* @since 1.4.0
*/
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<FlowRuleEntity>> converter;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String dataidPostfix=NacosConfigUtil.FLOW_DATA_ID_POSTFIX;
if(StringUtil.isNotBlank(nacosConfigProperties.getDataidPostfix())){
dataidPostfix=nacosConfigProperties.getDataidPostfix();
}
String rules=configService.getConfig(appName + dataidPostfix, nacosConfigProperties.getGroupId(), 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
2、添加FlowRuleNacosPublisher
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Eric Zhao,yaokj
* @since 1.4.0
*/
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<FlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules==null) {
return;
}
String dataidPostfix=NacosConfigUtil.FLOW_DATA_ID_POSTFIX;
if(StringUtil.isNotBlank(nacosConfigProperties.getDataidPostfix())){
dataidPostfix=nacosConfigProperties.getDataidPostfix();
}
configService.publishConfig(app + dataidPostfix, nacosConfigProperties.getGroupId(), converter.convert(rules));
}
}
3、添加NacosConfig
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Properties;
/**
* @author Eric Zhao,yaokj
* @since 1.4.0
*/
@Configuration
public class NacosConfig {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
Properties properties=new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
properties.put(PropertyKeyConst.NAMESPACE, nacosConfigProperties.getNamespace());
return ConfigFactory.createConfigService(properties);
}
}
4、添加NacosConfigProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* yaokj
*/
@Component
@ConfigurationProperties(prefix="nacos.server")
public class NacosConfigProperties {
private String ip;
private String port;
private String namespace;
private String groupId;
private String dataidPostfix;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip=ip;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port=port;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace=namespace;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId=groupId;
}
public String getDataidPostfix() {
return dataidPostfix;
}
public void setDataidPostfix(String dataidPostfix) {
this.dataidPostfix=dataidPostfix;
}
public String getServerAddr() {
return this.getIp()+":"+this.getPort();
}
@Override
public String toString() {
return "NacosConfigProperties{" +
"ip='" + ip + '\'' +
", port='" + port + '\'' +
", namespace='" + namespace + '\'' +
", groupId='" + groupId + '\'' +
", dataidPostfix='" + dataidPostfix + '\'' +
'}';
}
}
5、添加NacosConfigUtil
/**
* @author yaokj
* @since 1.4.0
*/
public final class NacosConfigUtil {
public static final String GROUP_ID="SENTINEL_GROUP";
public static final String FLOW_DATA_ID_POSTFIX="-flow-rules";
public static final String PARAM_FLOW_DATA_ID_POSTFIX="-param-rules";
public static final String CLUSTER_MAP_DATA_ID_POSTFIX="-cluster-map";
/**
* cc for `cluster-client`
*/
public static final String CLIENT_CONFIG_DATA_ID_POSTFIX="-cc-config";
/**
* cs for `cluster-server`
*/
public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX="-cs-transport-config";
public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX="-cs-flow-config";
public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX="-cs-namespace-set";
private NacosConfigUtil() {}
}
6、在application.properties添加Nacos配置
#nacos
nacos.server.ip=nacos-web.test.xx.net
nacos.server.port=80
nacos.server.namespace=sentinel-config
nacos.server.group-id=SENTINEL_GROUP
nacos.server.dataid-postfix=-flow-rules
如下:
將FlowControllerV2類的:
@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
修改為:
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
通過上面的步驟已經修改完成源碼了。
1、在客戶端服務應用zizai-wxwork-api的配置為如下
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
datasource:
ds:
nacos:
server-addr: nacos-web.test.xx.net:80
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
namespace: sentinel-config
data-type: json
rule-type: flow
nacos:
discovery:
server-addr: nacos-web.test.xx.net:80
namespace: c8d3b947-7c0b-4cc6-9958-xx
2、Nacos的配置為如下
3、在Sentinel控制臺上配置便可以同步過去
4、打開Nacos查看,可看到同步成功
實現了限流。
喜歡的朋友評論、點贊、轉發、收藏本文。有疑問的在評論區留言。謝謝!
可能會認為釣魚網站很難檢測和跟蹤,但實際上,許多釣魚網站都包含唯一標識它們的HTML片段。
你可能會認為釣魚網站很難檢測和跟蹤,但實際上,許多釣魚網站都包含唯一標識它們的HTML片段。本文就以英國皇家郵政(Royal Mail)釣魚網站為例來進行說明,它們都包含字符串css_4WjozGK8ccMNs2W9MfwvMVZNPzpmiyysOUq4_0NulQo。
這些長而隨機的字符串是追蹤釣魚網站的絕佳指標,幾乎可以肯定,任何含有css_4WjozGK8ccMNs2W9MfwvMVZNPzpmiyysOUq4_0NulQo的網頁都是皇家郵政釣魚工具的實例。
但是,像這樣的獨特字符串最終如何成為檢測網絡釣魚工具標識的呢?
不幸的是,我們并不是RFC 3514的模仿者,在RFC 3514中,如果所有的IP數據包是惡意的,那么它們都包含一個標志信號。不,這些識別字符串完全是由釣魚工具開發者無意中包含的。
→【網絡安全更多技術學習資料包】←
釣魚網站試圖盡可能接近他們真正的目標網站,然而,大多數釣魚者并不具備復制公司網站的技能。相反,他們采用了快捷方式,只是假冒了原始網站的HTML并對其進行了一些小的調整。
假冒目標網站并將其變成釣魚工具的過程大致如下:
1.使用諸如HTTrack之類的工具復制目標網站,甚至只需在網絡瀏覽器中點擊文件→保存即可。
2.調整HTML以添加一個請求受害者個人信息的表單。
3.將其與PHP后端粘合在一起,以保存收集到的數據。
然后,可以將該工具包輕松部署到便宜的托管服務提供商上,并準備收集受害者的詳細信息。
4.通過復制整個網頁,釣魚者幾乎不需要什么技巧或精力即可獲得一個超級逼真的釣魚頁面。但是,這種假冒模式意味著他們的釣魚頁面充滿了他們實際上并不需要的東西。
特別是,原始網站中的任何特殊字符串都有可能意外地出現在最終的釣魚工具中。這對我們來說很好,因為尋找特殊字符串是一種非常容易和可靠的方法來檢測釣魚網站。
所謂的特殊字符串就是一個足夠長或復雜的字符串,該字符串在整個互聯網上都是獨一無二的,這可能是因為它是隨機字符(如64a9e3b8)或只是因為它足夠長。
那么,問題來了:為什么在最初的網站中會有這些字符串?事實證明,在現代開發實踐中,網站到處都是這些足夠長或復雜的字符串。
現代網站很少是100%靜態的內容,當前的開發實踐和網絡安全特性意味著,有多種方法可以使冗長的隨機字符串最終出現在網站中。以下是我所見過的各種來源的概述:
1.文件名中的哈希
現代網站通常使用諸如Webpack或Parcel之類的“捆綁包”進行處理,這些捆綁包將所有JavaScript和CSS組合成一組文件。例如,網站的sidebar.css和footer.css可能合并為一個styles.css文件。
為了確保瀏覽器獲得這些文件的正確版本,捆綁程序通常在文件名中包含一個哈希。昨天你的網頁可能使用的是styles.64a9e3b8.css,但是在更新你的樣式表之后,它現在使用的是styles.a4b3a5ee.css。這個文件名的改變迫使瀏覽器獲取新的文件,而不是依賴于它的緩存。
但這些足夠長或復雜的文件名正是最近皇家郵政(Royal Mail)的釣魚工具被發現的原因。
當釣魚者假冒真正的皇家郵政網站時,HTML看起來是這樣的:
不幸的是,不管他們用什么技術來假冒網站,文件名都沒有改變。因此,通過urlscan.io查找大量使用CSS文件的釣魚網站是很容易的:
2. 版本控制參考
網絡釣魚者針對的任何網站很可能都是由一個團隊開發的,他們很可能會使用git等版本控制系統(VCS)進行協作。
一個合理的常見的選擇是在網站的每一個構建中嵌入一個來自VCS的參考,這有助于完成諸如將漏洞報告與當時正在運行的代碼版本相關聯之類的任務。
例如,Monzo網站使用一個小的JavaScript代碼片段嵌入了git commit哈希:
VCS參考資料對于安防人員來說非常有用,因為它們很容易在版本控制系統中找到。如果你發現一個釣魚網站無意中包含了VCS參考,你就可以直接查找該網站的編寫時間(也就是該網站被假冒的時間)。
3.SaaS的API密鑰
網站經常使用各種第三方服務,如對講機或reCAPTCHA。為了使用這些服務,網站通常需要包含相關的JavaScript庫以及一個API密鑰。
例如,Tide使用reCAPTCHA,并將這段代碼作為其集成的一部分:
因為reCAPTCHA “sitekey” 對每個網站來說都是唯一的,因此任何包含字符串6Lclb0UaAAAAAJJVHqW2L8FXFAgpIlLZF3SPAo3w且不在tide.co上的頁面都很可能是假冒的網站。
雖然SaaS API密鑰是非常獨特的,并且具有很好的指示作用,但它們變化非常少,因此無法區分從同一網站假冒出來的不同釣魚工具。一個網站可能會使用相同的API密鑰達數年之久,因此在那時創建的所有工具包都將包含相同的密鑰。出于同樣的原因,API密鑰對于識別何時創建網絡釣魚工具包也沒有任何幫助。
4. 跨站請求偽造(CSRF)令牌
事實證明,許多網絡安全最佳實踐也使網絡釣魚成為重要的指標。其中最常見的可能是“跨網站請求偽造”(CSRF)令牌。
簡單地說,CSRF是一個漏洞,惡意網站可以借此誘騙用戶在目標網站上執行經過身份驗證的操作。例如,此HTML創建了一個按鈕,點擊該按鈕可將POST請求發送到https://example.com/api/delete-my-account":
如果example.com不能防御CSRF,它將處理此請求并刪除毫無戒心的用戶帳戶。
防御CSRF的最常見方法是使用所謂的CSRF令牌,這是一個嵌入在每個網頁中的隨機值,服務器希望將其與敏感請求一起發送回去。例如,example.com的“刪除我的賬戶”按鈕應該是這樣的:
服務器將拒絕任何不包含預期隨機值的請求。
CSRF令牌非常適合檢測釣魚網站,因為從設計上看,它們是獨一無二的。
5. 內容安全策略隨機數
內容安全策略(CSP)是一種較新的安全手段,可幫助防御跨網站腳本(XSS)攻擊。它允許開發人員指定策略,比如只允許特定域的< script >標記,或更有趣的是,對于我們的用例,僅允許包含指定“nonce”的< script >標記。
要使用基于隨機數的CSP,網站需要包含以下政策:
并且使用具有匹配隨機值的腳本標簽:
這有助于防止XSS攻擊,因為惡意注入的JavaScript不會具有匹配的現時值,因此瀏覽器將拒絕運行它。
就像CSRF令牌一樣,CSP隨機數也構成了完美的網絡釣魚工具包檢測器:它們的設計不可篡改,因此通常會為每個請求隨機生成長且復雜的字符串。
6. 的資源完整性哈希
現代瀏覽器中可用的另一個安全功能是子資源完整性(SRI),通過允許你指定期望內容的哈希值,可以保護你免受惡意修改的JavaScript / CSS的侵害。當瀏覽器加載受SRI保護的JavaScript / CSS文件時,它將對內容進行哈希處理并將其與HTML中的預期哈希進行比較。如果不匹配,則會引發漏洞。
例如,以下是研究人員的博客如介紹的如何將子資源完整性用于其CSS:
這個SRI哈希值是根據研究者網站上所有CSS計算得出的,結果,盡管研究者使用的是公共博客模板,但極不可能有另一個網站具有相同的哈希值,他們必須使用完全相同的模板版本,并且必須包含所有相同的插件。
對于自定義網站比研究者更多的公司,實際上可以確保沒有其他網站擁有完全相同的CSS。
下次當你分析網絡釣魚網站時,請注意其中一些有用的長且復雜的字符串。
文件名中的哈??赡苁悄阌龅降淖畛R姷氖吕@些也是最有用的,因為你可以在urlscan.io上搜索文件名以查找同一工具包的其他實例。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。