更多关于AI安全、大模型安全、智能体安全的相关资料和文章,可在公众号《小枣信安》中查看。
小枣信安:专注AI安全,包括但不限于大模型安全、智能体安全、AI赋能网络安全等。
介绍
在大模型TOP10中,原先是有DDOS攻击的,后来进行了改版,去掉了这个问题,但DDOS攻击依然是经常被使用的一种方式,本文主要分为网络层、应用层、模型层来记录下相关内容。
其中网络层、应用层基本就是传统DDOS的相关知识,因为模型服务或API本质也都是互联网资产。这里我们先来理解下对应的网络层、应用层、模型层是如何进行DDOS的。
假设我们要去某个餐馆吃饭,网络层DDOS相当于让路上堵车,应用层DDOS相当于让餐馆拥挤,模型层DDOS相当于让厨师做不了饭,换到实际场景中,网络层针对的就是请求目标服务器的流量,相当于带宽,应用层针对的就是服务器上的应用服务、中间件的业务处理能力,比如连接数、CPU、内存等,模型层针对的就是大模型本身,相当于GPU,去消耗算力和Token。
反射和放大
先来看下网络层,主要借助一些网络协议展开,这里先看两个概念,即反射和放大。假设有一台目标机以及一个DNS服务器和一个攻击者,攻击者伪造了一个查询域名的请求,在这个请求包中将源IP改为了目标机的IP,当DNS服务器收到请求后,会进行响应,将结果返回给数据包中的源IP,即DNS的响应本来应该给攻击机,但此时却给到了目标机,这种类似于反射的机制,叫做反射攻击。
反射攻击有几个特点,第一个,它是基于UDP的,不需要验证来源,所以攻击者可以伪造源IP,攻击者自己的IP是隐藏的,难以发现。第二个,如果使用了互联网上多个DNS服务器,那么目标机收到的就是来自于全球各地的响应,会收到大量的数据包。导致宽带暴增。
放大攻击是基于反射攻击来进行,假设还是通过DNS协议去进行反射攻击,我们请求DNS协议时,例如发送一个ANY指令(请求域名所有类型的记录,返回数据量会比较大,通常是请求包的几十倍),发送的数据包大小会很小,但ANY查出来的内容会很多,此时返回给目标机的内容就会变多,相当于数据包进行了放大,这种就属于放大攻击。
放大攻击也有几个特点,第一个,它也是基于UDP的,不需要做来源验证,可以很好的隐藏攻击者和伪造源IP。第二个,对响应的数据包大小会明显大于查询数据包。
所以这种情况也可以叫做反射放大攻击,而我们后续讨论的网络层DDOS,基本就是网络层的相关协议去利用反射放大来进行的。
NTP反射放大
NTP(Network Time Protocol)是网络时间协议,用来在网络中同步各个计算机上的时间,默认端口是123,比如我们的一些设备,为了保证时间的准确性,会去请求NTP服务器获取一个准确的时间。
而NTP有一个monlist指令,当客户端请求包中带上这个指令时,NTP服务端会把最近请求自己的600个IP进行返回,6个为1组,那也就意味着会响应100个数据包,所以直观上的现象就是发一个包,然后收100个包。
而NTP走的是UDP,不需要进行客户端验证,所以客户端发送请求时,可以伪造一个源地址(被攻击的地址),那么NTP服务器收到请求后,就会把这100个包返回给源地址。所以这个情况就符合我们一开始说到的反射放大攻击。
monlist这个指令,在NTP的4.2.7p26及以上版本会被默认禁用,低于该版本的NTP会默认开启monlist。利用思路就是全网搜索出支持monlist的NTP服务,然后进行利用即可。
比如说可以利用一些空间搜索引擎搜索开放了123端口的服务,然后通过nmap的monlist脚本去批量检测是否支持monlist,或msf的脚本也可以,命令示例如下。
nmap示例:
nmap -sU -p 123 --script ntp-monlist -iL ip.txt -oN monlist_results.txt
msf示例:
auxiliary/scanner/ntp/ntp_monlist
筛选出来后,也可以手动进行验证,手动的命令如下:
ntpdc -n -c monlist <IP>
或者在shodan尝试直接搜索ntpd的版本号也可以,确定了列表,也确定了目标地址后,可以通过脚本去构造请求包,然后遍历NTP服务器地址,去攻击目标,比如使用python的scapy包去模拟请求,具体的脚本内容可以让AI写一个。
需要注意的是,脚本执行要放到有公网IP的服务器上,且这个服务器厂商没有做ISP验证,即支持IP欺骗,这个服务器厂商不多,因为现在规定只能发源IP的包,不能伪造,所以在本地或者常见云上,即使脚本中伪造了IP,但到了网关检查不通过会直接丢弃,或者自动修改为真实IP。
DNS反射放大
DNS用来将域名解析为IP,默认运行在53端口,先回顾下它的大概流程,我们本地访问一个URL,浏览器会先看自己缓存是否有记录,再看系统的DNS缓存,没有的话再看hosts中是否有记录,再没有则继续看本地DNS服务器(本机配置的DNS地址)中是否有记录,如果还没有,本地DNS会向根服务器进行请求查询,根服务器不存储域名IP,它只存储顶级域的服务器地址,比如.com对应一个服务器,.cn对应一个服务器,每个域名后缀都有对应的服务器,如果我们查找的是example.com,根域返回的就是.com服务器的地址,它类似于一张映射表,本地DNS拿到这个地址后,去请求这个地址,这个地址对应的服务器叫做顶级域服务器,它也不存储具体的IP,它也类似于一张表,它存储的是权威服务器的地址,它会把权威服务器地址进行返回,权威服务器地址由用户自己管理,比如一些企业自己搭建的dns服务器,个人的话一些DNS云厂商较多,此时本地服务器再去权威服务器进行查询,权威服务器会返回域名对应的IP。
整体可参考下图理解:
DNS查询分递归查询和迭代查询,递归查询指的是我们去一个服务器查询域名IP,这个服务器没有记录,然后它会继续向其它服务器去查询,最后把结果给我们,这种我们只需要查最开始这一次的,后续查询我们不用管的情况,就属于递归。
而迭代是我们向一个服务器查域名IP,这个服务器没记录,然后它会告诉我们说你可以去另一台查,然后我们又去另一台查,这种每次查询都需要我们自己去查的情况,就属于迭代查询。
根据刚才DNS查询流程来看,我们本地发起一个域名查询,给到本地服务器时,这个就属于递归查询,因为本地服务器会去根服务器、顶级域服务器以及权威服务器上去查。而以本地服务器视角来看,它的查询就是迭代查询,因为根服务器返回的不是域名具体IP,而是告诉你去顶级域查,顶级域也不返回域名IP,而是告诉你去权威服务器查。
所以上面服务器中,那个本地DNS就是一个支持递归查询的服务器,这种服务器通常是运营商的DNS以及一些云厂商的DNS或自建的DNS等,而在进行DNS反射方法攻击时,通常就需要找这些支持递归的DNS。
具体使用和NTP类似,可以先通过搜索引擎去找开放了53端口的服务器,然后使用一些工具把支持递归的筛选出来,比如Nmap的脚本示例:
nmap -sU -p53 --script=dns-recursion -iL dns_ips.txt -oN recursive_results.txt
或者使用DNSChecker等相关工具查找,得到DNS列表后,同理可以用Scapy写一个批量发送的脚本,DNS反射放大的原理在于,伪造目标机的IP,然后去请求DNS查询,比如any类型(即指定域名下的所有DNS记录,比如A记录、MX记录、TXT记录等等),让响应大于请求,再加上迭代查询,从而实现反射放大,同样的,也需要一个支持IP伪造的公网服务器。
Memcached反射放大
Memcached是一个key-value的缓存服务,和redis比较像,它默认运行在11211端口,支持TCP和UDP,Memcached可以通过set命令去设置一些键值信息,可以通过get命令去获取相应键的信息,可以通过stats命令获取服务器的相关信息,大部分命令的使用和redis很像。
因为Memcached支持UDP,当其服务存在未授权时,攻击者可以利用进行反射方法攻击,在攻击时,可以事先通过set存一个大容量的key-value到服务器,或通过stats命令获取服务器的相关信息,因为返回信息较多,且UDP可伪造目标IP,故可进行反射放大。
Memcached的服务器查找和其它协议类似,可以通过空间搜索引擎去搜索关键字或端口号,或者使用Nmap,Masscan等工具进行全网IP扫描。当有了服务器列表后,可以通过Nmap来进行未授权检测,示例命令如下:
nmap -iL ip_list.txt -p 11211 --script memcached-info -oN results.txt
如果服务器未授权,会带有Authentication: no的字样,或者通过telnet手动进行连接尝试,
telnet <IP地址> 11211
确认未授权的Memcached服务器列表后,可以找一些利用脚本,或者使用模型写一个利用脚本进行批量尝试。
CLDAP反射放大
LDAP是一个轻量级目录访问协议,相当于一个目录数据库服务,使用TCP进行连接,因为有三次握手,所以无法伪造IP造成响应包,而CLDAP可理解为是使用UDP的LDAP,无需验证,它们默认端口都是389。
使用CLDAP的原理在于,去发送一个RootDSE请求,该请求会让服务器返回目录服务的详细配置、支持功能、命名上下文等,导致这个回包通常能达到几千字节。具备反射放大的条件。
关于CLDAP的验证,可以用Python的scapy库写一个验证脚本,目前nmap的相关ldap扫描脚本,主要针对的是TCP协议,结果可能会有偏差,所以最好自己写一个。
SSDP反射放大
SSDP是一个简单服务发现协议,基于UDP,简单理解就是用来让别人发现自己的,比如家里的局域网,现在要接入一个新设备,接入冰箱、洗衣机之类的,它们接入后,会利用SSDP协议在局域网内进行播报,说我是谁,我在哪,我能做什么,让别人知道它。
常见的场景,例如手机联到局域网后,能搜到电视就是因为电视靠SSDP广播的自己,电脑能发现局域网打印机,就是打印机靠SSDP广播的自己。而很多家用路由器都是对公网开放SSDP的,默认端口是1900。
SSDP有一个m-search请求,该请求会返回其下所有设备的详细信息,比如设备描述文件地址、设备信息、缓存等,所以SSDP也是满足反射放大的一个协议。
可以通过nmap的相关脚本去验证IP是否可用:
nmap -sU -p 1900 --script ssdp-discover -iL ips.txt --open
SNMP反射放大
SNMP叫做简单网络管理协议,主要是给相关设备做监控用的,比如网管需要监控网络中的相关设备情况,例如交换机、路由器、防火墙、服务器、摄像头等等,这些设备会通过SNMP将相关信息给到网管监控机器上,例如内存、CPU、温度、流量、硬件等信息。
SNMP是UDP协议,在161端口,在SNMPv2c版本,其存在弱口令,默认就是public,它有一个GetBulkRequest指令,作用是返回一大堆监控数据,从而造成反射放大。注意v3版本开启了加密验证,所以不能用于反射放大。
可以使用nmap进行验证:
# 确认SNMP是否可用
nmap -sU -p161 --script snmp-info 目标IP
# 猜测常见的弱口令,看是否为默认的public
nmap -sU -p161 --script snmp-brute 目标IP
或者使用自带的snmpwalk也行:
snmpwalk -v2c -c public 目标IP
WS-Discovery反射放大
WS-Discovery也是一个设备发现协议,基于UDP的3702端口,它是近期比较受欢迎的协议,主要用来IOT相关设备的发现,比如一个摄像头,不需要配置它的APP,手机点击搜索设备就能找到它,利用的就是WS-Discovery协议。可以发现它和之前说的SSDP比较像,好像都是用来发现设备,实际上它们也有一些区别,比如WS-Discovery是用于工业设备发现的,像摄像头、打印机等,协议传输格式基于xml,而SSDP是用于家用设备发现的,像冰箱、洗衣机等,协议传输格式是纯文本。
它的反射放大原理在于有一个probe请求,对应的是probematch响应,这个响应会包含完整设备信息、服务地址、xml字段等大量信息。
可以通过nmap的脚本来进行验证是否可用:
nmap -sU -p 3702 --script wsdd-discover 目标IP
关于可用于反射放大的协议还有很多,这里不再一一列举,但它们原理都是一样的,即:UDP+源IP伪造+大量内容的响应包。
HTTP Flood
接下来看下关于应用层的相关攻击,HTTP Flood的方式很简单,就是多线程并发去访问服务器,针对的是应用/中间件的处理能力,因为完整的请求过来后,服务器要分配线程、解析请求、处理任务等,当然,如果请求过多,也可能占满带宽,但占满带宽属于网络层,它不是HTTP Flood的主要目的,对于HTTP Flood攻击有很多工具,比如wrk等。
Slowloris
Slowloris可以称为慢速攻击,即连接上服务器后不断开,占用连接数,它的速率相对来说会比Flood慢。需要注意的是Flood和Slowloris都是发生在三次握手后,握手完毕后,Flood是发出去后就赶紧发下一个,而Slowloris是先发部分头内容,让目标认为自己还没有发完,从而造成等待,相关工具也有很多,比如slowhttptest等。
模型层
最后再来看下模型层的DDOS攻击,大模型的DDOS现在更多指的是恶意消耗对方Token数量,关于模型的Token消耗,确切说属于现阶段TOP10的第十个,叫做无限制消费,这个我们后面会详细的单独记录一篇,本篇既然主题是DDOS,那下面来演示一个无限制应用被高频访问的示例。因为大模型的推理成本很高,所以有可能造成宕机,也有可能不宕机但大幅消耗Token。
示例代码如下,模拟了一个接口,该接收会把接收的数据进行处理后发给大模型,但没有做频率限制,将以下代码保存为js文件,使用node运行:
npm install express
node xxx.js
代码如下:
const express = require('express');
const app = express();
app.use(express.json());
let activeTasks = 0; // 当前处理中的并发数
let totalTokensConsumed = 0; // 累计消耗的虚拟 Token
// 简单的 Token 估算函数(模拟真实 LLM,通常 1 个汉字约 1.5~2 个 Token)
function estimateTokens(text) {
return Math.ceil(text.length * 1.5);
}
app.post('/api/processData', async (req, res) => {
const startMemory = process.memoryUsage().heapUsed / 1024 / 1024;
activeTasks++;
const inputData = req.body.data || "";
const tokens = estimateTokens(inputData);
totalTokensConsumed += tokens;
const requestId = Math.random().toString(36).substring(7);
// 打印实时监控数据
console.log(`--- [请求进入: ${requestId}] ---`);
console.log(`并发任务: ${activeTasks}`);
console.log(`本次 Token: ${tokens} | 累计总 Token: ${totalTokensConsumed}`);
// 内存指标说明:
// rss: 进程占用的物理内存总大小
// heapUsed: V8 引擎实际使用的堆内存
const mem = process.memoryUsage();
console.log(`当前内存 (RSS): ${(mem.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(`当前堆内存 (Heap): ${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`);
console.log(`-------------------------------\n`);
try {
// 模拟 2 秒的重度推理延迟
await new Promise(resolve => setTimeout(resolve, 2000));
activeTasks--;
res.status(200).send(`[${requestId}] 处理完成`);
} catch (error) {
activeTasks--;
res.status(500).send('系统崩溃');
}
});
app.listen(3000, () => {
console.log('监控服务器已就绪: http://localhost:3000');
});
运行后可以写一个批量访问的脚本或者用一些批量测试工具,这里用postman,效果如下:
单接口没问题后,来批量测试下,将接口保存到Collections集合中,然后点击Run,之后选择Performance性能测试,其中virtual users的数量就是要模拟的真实用户访问数量,如下图:
可见用户数量上来后,会有失败的请求:
服务器是没有响应的:
针对该情况,可采取如下防护建议:
1、接口接收的内容长度做限制,因为后面要给到模型处理,所以也相当于是做了输入侧的Token限制。
2、模型输出最大Token数做限制,这个一般调用大厂接口时,对方都支持设置。
3、做访问速率限制,例如一个IP一分钟只能只能访问XX次,否则提示访问频繁。
3、服务可以异步处理,如果同步处理的话,相当于处理完一个再处理下一个,容易造成堵塞。
以下脚本为添加防护措施后的代码,添加了访问频率限制、接收内容长度限制和异步处理,其中速率限制使用了express-rate-limit插件:
npm install express-rate-limit
代码如下:
const express = require('express');
const rateLimit = require('express-rate-limit'); // 引入速率限制中间件
const app = express();
app.use(express.json());
// --- 修复方式: 频率限制 ---
const limiter = rateLimit({
// 窗口期:1 分钟(1 * 60 * 1000 毫秒)
windowMs: 1 * 60 * 1000,
// 在这 1 分钟内,每个 IP 最多只能发 5 个请求
max: 5,
message: { error: "请求过于频繁,请稍后再试" },
standardHeaders: true,
legacyHeaders: false,
});
// --- 辅助函数:模拟非阻塞的重度计算 (修复方式 3) ---
function processDataAsync(data) {
return new Promise((resolve) => {
// 使用 setImmediate 将重计算逻辑切分,避免阻塞事件循环
setImmediate(() => {
console.log(`正在异步处理 ${data.length} 长度的数据...`);
// 模拟一些计算逻辑
resolve();
});
});
}
// 应用修复后的端点
app.post('/api/processData', limiter, async (req, res) => {
let inputData = req.body.data;
// --- 修复方式: 输入验证与大小限制 ---
if (!inputData || typeof inputData !== 'string') {
return res.status(400).send('无效的输入:必须提供字符串数据');
}
if (inputData.length > 500) { // 限制最大长度为 500 字符
return res.status(413).send('输入内容过长,请精简后再试');
}
try {
// --- 修复方式: 异步处理 ---
await processDataAsync(inputData);
// 模拟调用 LLM
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("处理成功");
res.status(200).send('数据处理完成');
} catch (error) {
res.status(500).send('服务器内部错误');
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`安全服务器运行在 http://localhost:${PORT}`);
});
此时一分钟之内请求超过5次就会被限制:
长度内容超过500后也会限制:
总结
关于大模型安全TOP10中的无限制消费问题,后续会单独进行记录,以上就是大模型安全的DDOS相关内容,感谢阅读。