什么是域前置
域前置介绍
域名前置技术,亦称为域名伪装,是一种用于规避审查的网络技术,其核心功能在于隐藏真实的服务端点。该技术在应用层面上运作,允许用户通过HTTPS协议连接至被屏蔽的服务,同时在表面上看似与另一个完全不同的网站进行通信。
域前置工作原理
域名前置技术的运作原理主要依赖于内容分发网络(CDN)。通过配置A记录或AAAA记录,将网站域名指向服务器的公网IP地址,从而实现用户能够通过易于记忆的域名直接访问服务器上部署的网站,而无需依赖于难以记忆且缺乏直观标识的IP地址。CDN不仅能够加速域名的访问,而且在域名访问请求过程中,并不直接解析至IP地址,这为申请CDN加速服务的虚拟私人服务器(V-PS)提供了隐藏真实IP地址的效果。
CDN工作原理
内容分发网络(CDN)的工作机制如下:当用户尝试访问某个域名时,首先会向本地DNS服务器(LDNS)发起请求。如果LDNS服务器中存在缓存,则直接返回相应的IP地址给用户;若无缓存,则LDNS会向授权DNS服务器发起请求。授权DNS服务器解析域名后返回一个别名域名,随后请求被转发至公有云DNS调度系统,该系统负责分配最佳的节点IP地址。LDNS服务器会缓存此IP地址,用户随后根据该IP地址请求所需资源。通过这种方式,节点IP地址实际上隐藏了真实的IP地址。
什么是云函数?
函数即服务(FaaS: Function as a Service)
函数即服务提供的是计算能力。原有的计算能力,无论是容器也好,虚拟机也好都承载在一定的操作系统之上,函数即服务把计算能力进行了进一步抽象。
后端及服务(BaaS: Backend as a Service)
后端即服务,比如对象存储,数据库应用,缓存服务,我们也可以称之为Serverless,因为这些服务也能够在云上提供开通即服务,开通即使用的能力。在使用这些产品时同样不需要关注它的服务器是什么样的,它的服务器部署在哪里,而是服务开通就可以使用了,后面的运维工作都交给了云,所以不用感知它的最底层服务器。
函数即服务(FaaS: Function as a Service)是指一种计算能力的提供方式。传统的计算能力,无论是通过容器还是虚拟机实现,均建立在特定的操作系统之上。而函数即服务则进一步抽象了这种计算能力。
后端即服务(BaaS: Backend as a Service)涉及的是一系列后端功能,例如对象存储、数据库应用和缓存服务等。这类服务亦可被称为无服务器(Serverless),因为它们能够在云平台上实现即开即用的服务模式。在使用这些服务时,用户无需关心服务器的具体配置、部署位置等底层细节,仅需开通服务即可开始使用。相应的运维工作由云服务提供商负责,因此用户无需直接感知到最底层的服务器架构。
针对云函数、域前置如何反制?
常规手法
批量部署钓鱼马程序
通过客户端观察可见,部署后的IP地址会周期性地自动更迭(此为云函数的固有特性)。若一次性部署大量IP地址,将导致攻击方难以辨识真实来源(即便将所有IP部署于同一虚拟机亦无妨,因为云函数的特性使得每次心跳包均被视为新的请求,从而分配新的IP地址)。
消耗云函数配额
云函数隐藏的命令与控制(C2)中心与内容分发网络(CDN)相似,均存在一个共同的弱点:访问时需计费。因此,可以编写脚本消耗攻击方的云函数配额,从而使得攻击方的所有恶意程序无法上线。
相关工具可于以下网址获取:工具 https://github.com/a1phaboy/MenoyGone
虚假上线策略
通过重新发送心跳包以模拟上线,但攻击方无法执行任何命令(消磨攻击者精力时间)。
截图举报
在收集证据时,应重点记录具有云函数特征的信息,如主机名、X-Api-FuncName、X-Api-AppId等(其中X-Api-AppId尤为重要)。这些信息表明对方正在利用云函数对我公司进行恶意攻击。据此,我们应请求相关机构对攻击方进行临时封禁处理。
样本情报聚合、流量特征识别方案
1.收集阶段
1.1 提取样本信息
首先,基于公司收到的恶意钓鱼邮件或者恶意程序进行初始分析,得到相关特征。
提取内容:
云函数信息:提取样本中使用的云函数名称、提供商、配置等信息。
C2通信地址:提取样本中嵌入的C2服务器地址,包括域名和IP地址。
域名:提取样本中使用的所有域名。
IP地址:提取样本中使用的所有IP地址。
URL:提取样本中访问的所有URL。
工具:
YARA规则:使用YARA规则进行样本特征匹配和提取。
静态分析工具:如IDA Pro、Ghidra,用于逆向工程和静态分析。
动态分析工具:如Cuckoo Sandbox,用于行为分析和提取动态信息。
1.2收集恶意样本
来源:
威胁情报平台:订阅和使用威胁情报平台(如微步在线情报社区、VirusTotal、AlienVault OTX)。
开源情报(OSINT):利用开源情报工具(如Shodan、Censys)和社区(如GitHub、Reddit)。
合作伙伴共享:与其他企业或组织建立威胁情报共享机制。
蜜罐系统:部署蜜罐系统(如Cowrie、Dionaea)来捕获恶意样本。
工具:
VirusTotal:用于提交和分析恶意文件。
Cuckoo Sandbox:用于自动化恶意软件分析。
微步沙箱:用于高级恶意软件行为分析。
2. 分析阶段
2.1 初步分析
静态分析:
特征字符串:提取样本中的可疑字符串,如URL、IP地址、域名等。(这里主要提取流量特征)
API调用:分析样本中调用的API函数,识别潜在的恶意行为。
嵌入域名/IP:提取样本中硬编码的域名和IP地址。
动态分析:
行为监控:在沙箱环境中运行样本,记录其行为,如文件操作、注册表修改、网络通信等。
网络流量:捕获和分析样本的网络流量,识别其通信模式和目标。
2.2 深度分析
云函数映射:
微步情报社区:提交样本到微步沙箱,提取与样本相关的云函数映射IP。(微步对域前置、CDN识别得特别准确)
统计IP:统计样本中所有提取到的IP地址,识别潜在的云服务提供商。
相似度对比:
相似度算法:使用VT内部的相似度对比功能,分析样本与已知恶意样本的相似性。
特征对比:对比样本的特征,如文件哈希、字符串、API调用等。
C2特征对比:
URL模式:分析样本中使用的URL模式,识别C2服务器。
通信协议:分析样本使用的通信协议和加密方式,识别C2通信特征。
行为特征:记录样本的行为特征,如定时任务、数据泄露等。
3. 检测阶段
3.1 CDN和域前置检测
识别域前置:
DNS解析:使用DNS解析工具(如nslookup、dig)解析样本中使用的域名,识别域前置技术。
前置域名记录:记录前置域名和实际通信域名,分析其关联性。
CDN检测:
CDN节点识别:分析样本中使用的CDN服务,记录相关的CDN节点IP和域名。
流量分析:使用流量分析工具(如Wireshark、Zeek)监控样本的网络通信,识别CDN流量特征。
3.2 威胁狩猎
微步沙箱/情报社区API:
基于微步强大的样本库以及情报能力,收敛相关的恶意样本进行特征关联。
IOC匹配:
IOC提取:提取样本中的IOC(Indicator of Compromise),如域名、IP地址、URL、文件哈希等。
日志匹配:将提取的IOC与现有日志进行匹配,识别潜在威胁。
网络流量分析:
流量捕获:使用网络流量捕获工具(如Wireshark、Zeek)捕获网络流量。
异常检测:分析捕获的网络流量,识别异常流量和恶意通信。
日志分析:
日志收集:收集防火墙、IDS/IPS、Web服务器等设备的日志。
日志分析:使用日志分析工具(如ELK Stack)分析日志,识别与样本相关的恶意活动。
4. 响应阶段
4.1 阻断与隔离
阻断恶意域名/IP:
防火墙规则:在防火墙上添加规则,阻断恶意域名和IP地址。
DNS黑名单:在DNS服务器上添加黑名单,阻止解析恶意域名。
隔离受感染系统:
网络隔离:将受感染的系统从网络中隔离,防止进一步传播。
物理隔离:如果必要,将受感染的系统物理断网。
4.2 取证与恢复
数字取证:
证据收集:使用数字取证工具(如FTK Imager、EnCase)收集证据。
证据分析:分析收集到的证据,识别攻击路径和攻击者行为。
系统恢复:
备份恢复:根据备份和恢复计划,恢复受感染系统的正常运行。
补丁管理:应用最新的安全补丁,修复系统漏洞。
4.3 通报与改进
威胁通报:
内部通报:将分析结果通报给相关部门和团队。
外部通报:将威胁情报通报给合作伙伴和威胁情报共享社区。
安全改进:
策略更新:根据分析结果,更新安全策略和措施。
技术改进:引入新的安全技术和工具,提升整体安全水平。
5. 持续监控
5.1 持续监控
实时监控:
MISP系统:使用MISP系统,进行实时监控和告警。(或商业化安全运营平台)
行为分析:使用用户和实体行为分析(UEBA)工具,监控异常行为。
定期扫描:
漏洞扫描:定期进行漏洞扫描,识别和修复系统漏洞。
渗透测试:定期进行渗透测试,评估系统的安全性。
5.2 防御手法策略
规则更新:
检测规则:根据最新威胁情报,更新防火墙、IDS/IPS等设备的检测规则。
YARA规则:更新YARA规则,用于样本特征匹配和提取。
培训与演练:
安全培训:定期进行安全培训,提高员工的安全意识和技能。
应急演练:定期进行应急演练,提高安全团队的响应能力。
实际案例:
对于CDN运营商,一般流量请求的第一跳IP在一段时间内,不会有太大的变化,可以基于微步情报社区API提取IP相关的云函数信息,挖掘云函数关联的恶意样本,对其流量进行匹配,将流量近似的恶意样本进行梳理关联,做到提前预知,同步封禁。
近似流量匹配
Api
相关脚本
Main:
package main
import (
"HunterShell/UtilsRun"
"bufio"
"context"
"encoding/json"
"fmt"
"github.com/adrg/strutil"
"github.com/adrg/strutil/metrics"
"golang.org/x/time/rate"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
"time"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run main.go")
return
}
ipFilePath := os.Args[1]
// 读取IP.txt文件中的IP地址
log.Println("Reading IP addresses from", ipFilePath)
ips, err := readLines(ipFilePath)
if err != nil {
log.Fatalf("Error reading IP.txt file: %v", err)
}
// 将所有域名按行存储到domain.txt文件中
log.Println("Creating domain.txt file")
domainFile, err := os.Create("domain.txt")
if err != nil {
log.Fatalf("Error creating domain.txt file: %v", err)
}
defer domainFile.Close()
var wg sync.WaitGroup
domainChan := make(chan string, len(ips))
// 速率限制器,每秒最多10个请求
limiter := rate.NewLimiter(rate.Every(50*time.Millisecond), 1)
// 用于存储已经处理过的IP地址
processedIPs := make(map[string]bool)
for _, ip := range ips {
if processedIPs[ip] {
continue
}
processedIPs[ip] = true
wg.Add(1)
go func(ip string) {
defer wg.Done()
if err := limiter.Wait(context.Background()); err != nil {
log.Printf("Rate limit wait error: %v", err)
return
}
log.Printf("Querying IP %s for domains", ip)
domains, err := UtilsRun.QueryIPForDomains(ip)
if err != nil {
log.Printf("Error querying IP %s: %v", ip, err)
return
}
for _, domain := range domains {
domainChan <- domain
}
}(ip)
}
go func() {
wg.Wait()
close(domainChan)
}()
for domain := range domainChan {
_, err := domainFile.WriteString(domain + "n")
if err != nil {
log.Fatalf("Error writing to domain.txt file: %v", err)
}
}
// 读取domain.txt文件中的域名
log.Println("Reading domains from domain.txt")
domains, err := readLines("domain.txt")
if err != nil {
log.Fatalf("Error reading domain.txt file: %v", err)
}
// 将所有样本数据按行存储到result.txt文件中
log.Println("Creating result.txt file")
resultFile, err := os.Create("result.txt")
if err != nil {
log.Fatalf("Error creating result.txt file: %v", err)
}
defer resultFile.Close()
sampleChan := make(chan UtilsRun.Sample, len(domains))
// 用于存储已经处理过的域名
processedDomains := make(map[string]bool)
for _, domain := range domains {
if processedDomains[domain] {
continue
}
processedDomains[domain] = true
wg.Add(1)
go func(domain string) {
defer wg.Done()
if err := limiter.Wait(context.Background()); err != nil {
log.Printf("Rate limit wait error: %v", err)
return
}
//log.Printf("Querying domain %s for samples", domain)
samples, err := UtilsRun.QueryDomainForSamples(domain)
if err != nil {
log.Printf("Error querying domain %s: %v", domain, err)
return
}
for _, sample := range samples {
sampleChan <- sample
}
}(domain)
}
go func() {
wg.Wait()
close(sampleChan)
}()
for sample := range sampleChan {
_, err := resultFile.WriteString(fmt.Sprintf(" SHA256: %sn", sample.SHA256))
if err != nil {
log.Fatalf("Error writing to result.txt file: %v", err)
}
// 获取样本详细内容并保存为JSON文件
if err := limiter.Wait(context.Background()); err != nil {
log.Printf("Rate limit wait error: %v", err)
continue
}
log.Printf("Querying sample details for %s", sample.SHA256)
err = UtilsRun.QuerySampleDetails(sample.SHA256)
if err != nil {
log.Printf("Error querying sample details for %s: %v", sample.SHA256, err)
}
}
fmt.Println("Samples have been written to result.txt")
//样本流量对比
// 保存为JSON文件到指定目录
dirPath := "reportjson"
// 检查目录是否存在,如果不存在则创建
log.Println("Checking reportjson directory")
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
err := os.Mkdir(dirPath, 0755)
if err != nil {
log.Fatalf("Error creating directory: %v", err)
}
}
requestMap := make(map[string]string)
groupMap := make(map[string]string)
matchInfoMap := make(map[string]string)
// 遍历目录中的所有文件
log.Println("Walking reportjson directory")
err = filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println("Error accessing path:", path, err)
return err
}
if !info.IsDir() && filepath.Ext(path) == ".json" {
// 读取 JSON 文件
jsonFile, err := os.Open(path)
if err != nil {
fmt.Println("Error opening file:", path, err)
return err
}
defer jsonFile.Close()
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
fmt.Println("Error reading file:", path, err)
return err
}
var jsonData struct {
Data struct {
Network struct {
HTTPSEx []UtilsRun.HTTPSEx `json:"https_ex"`
} `json:"network"`
} `json:"data"`
}
err = json.Unmarshal(byteValue, &jsonData)
if err != nil {
fmt.Println("Error unmarshalling JSON:", path, err)
return err
}
// 提取 request 信息
for _, httpsEx := range jsonData.Data.Network.HTTPSEx {
requestMap[path] = httpsEx.Request
break
}
}
return nil
})
if err != nil {
fmt.Println("Error walking the path:", dirPath, err)
}
// 文本相似性归类
log.Println("Performing text similarity classification")
for file1, request1 := range requestMap {
groupMap[file1] = file1
matchInfoMap[file1] = ""
for file2, request2 := range requestMap {
if file1 != file2 {
similarity := strutil.Similarity(request1, request2, metrics.NewJaccard())
//fmt.Printf(request1, request2)
if similarity > 0.8 { // 设置相似性阈值
groupMap[file2] = groupMap[file1]
matchInfoMap[file2] = fmt.Sprintf("Matched with %s (similarity: %.2f), Key Data: %s", file1, similarity, UtilsRun.GetMatchingKeyData(request1, request2))
}
}
}
}
// 写入分组信息到 spliet.txt
log.Println("Creating spliet.txt file")
groupFile, err := os.Create("spliet.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer groupFile.Close()
groupSet := make(map[string]bool)
for file, group := range groupMap {
if !groupSet[group] {
groupSet[group] = true
groupFile.WriteString(fmt.Sprintf("Group: %sn", group))
}
groupFile.WriteString(fmt.Sprintf(" %sn", file))
if matchInfo := matchInfoMap; matchInfo != "" {
groupFile.WriteString(fmt.Sprintf(" %sn", matchInfo))
}
}
// 输出文件输出成功及文件路径
outputPath := groupFile.Name()
fmt.Printf("文件输出成功,输出路径: %sn", outputPath)
}
// 读取文件内容并按行分割
func readLines(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
UtilsRun:
package UtilsRun
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
// HTTPRequest 结构体表示一个HTTP请求
type HTTPRequest struct {
Method string // 请求方法
Path string // 请求路径
Version string // HTTP协议版本
Headers map[string]string // 请求头
Body string // 请求体
}
const (
maxRetries = 3
retryDelay = 2 * time.Second
)
// 查询IP地址获取域名
func QueryIPForDomains(ip string) ([]string, error) {
url := fmt.Sprintf("https://api.threatbook.cn/v3/ip/adv_query?apikey=xxx&resource=%s&exclude=cur_domains", ip)
var domains []string
var err error
for i := 0; i < maxRetries; i++ {
domains, err = queryIPForDomainsWithRetry(url)
if err == nil {
return domains, nil
}
time.Sleep(retryDelay)
}
return nil, err
}
func queryIPForDomainsWithRetry(url string) ([]string, error) {
client := &http.Client{Timeout: 300 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var response Response
err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
var domains []string
for _, d := range response.Data.HistoryDomains {
domains = append(domains, d...)
}
return domains, nil
}
// 查询域名获取样本数据
func QueryDomainForSamples(domain string) ([]Sample, error) {
url := fmt.Sprintf("https://api.threatbook.cn/v3/domain/query?apikey=xxxx&resource=%s", domain)
var samples []Sample
var err error
for i := 0; i < maxRetries; i++ {
samples, err = queryDomainForSamplesWithRetry(url, domain)
if err == nil {
return samples, nil
}
time.Sleep(retryDelay)
}
return nil, err
}
func queryDomainForSamplesWithRetry(url, domain string) ([]Sample, error) {
client := &http.Client{Timeout: 300 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var response Response2
err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
if data, ok := response.Data[domain]; ok {
return data.Samples, nil
}
return nil, nil
}
// 查询样本详细内容并保存为JSON文件
func QuerySampleDetails(sha256 string) error {
url := fmt.Sprintf("https://api.threatbook.cn/v3/file/report?apikey=xxxxx&resource=%s&query_fields=network", sha256)
var err error
for i := 0; i < maxRetries; i++ {
err = querySampleDetailsWithRetry(url, sha256)
if err == nil {
return nil
}
time.Sleep(retryDelay)
}
return err
}
func querySampleDetailsWithRetry(url, sha256 string) error {
client := &http.Client{Timeout: 300 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// 保存为JSON文件到指定目录
directory := "reportjson"
// 检查目录是否存在,如果不存在则创建
if _, err := os.Stat(directory); os.IsNotExist(err) {
err := os.Mkdir(directory, 0755)
if err != nil {
log.Fatalf("Error creating directory: %v", err)
}
}
filename := fmt.Sprintf("%s/%s.json", directory, sha256)
// 确保目录存在
err = os.MkdirAll(directory, 0755)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, respBody, 0644)
if err != nil {
return err
}
return nil
}
// 获取匹配的关键数据
func GetMatchingKeyData(request1, request2 string) string {
// 这里可以根据实际需求实现具体的匹配关键数据提取逻辑
// 例如,提取请求方法、URL、Host等
method1 := extractMethod(request1)
url1 := extractURL(request1)
host1 := extractHost(request1)
return fmt.Sprintf("Method: %s, URL: %s, Host: %s", method1, url1, host1)
}
// 提取请求方法
func extractMethod(request string) string {
lines := strings.Split(request, "n")
if len(lines) > 0 {
parts := strings.SplitN(lines[0], " ", 3)
if len(parts) > 0 {
return parts[0]
}
}
return ""
}
// 提取URL
func extractURL(request string) string {
lines := strings.Split(request, "n")
if len(lines) > 0 {
parts := strings.SplitN(lines[0], " ", 3)
if len(parts) > 1 {
return parts[1]
}
}
return ""
}
// 提取Host
func extractHost(request string) string {
lines := strings.Split(request, "n")
for _, line := range lines {
if strings.HasPrefix(line, "Host:") {
return strings.TrimSpace(strings.TrimPrefix(line, "Host:"))
}
}
return ""
}
这不请我咖啡啊我丢
原文始发于微信公众号(硅步security):针对云函数、CDN的狩猎追溯方法、思路
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论