'# ajaxpro CVE-2021-23758 漏洞记录
一、背景与问题
CVE-2021-23758 是一个影响 AjaxPro 2.x 系列库的远程代码执行漏洞。该漏洞源于 AjaxPro 在处理动态生成的 URL 参数时未正确验证用户输入,导致攻击者可通过构造特殊字符绕过安全校验,最终在服务器端执行任意代码。
该漏洞的核心问题是 AjaxPro 在反序列化 JSON 数据时未对特殊字符进行充分转义,导致恶意构造的 JSON 字符串可触发服务器端代码执行。此漏洞在 2021 年被披露,影响广泛,特别是基于 .NET 架构的 Web 应用。
二、基本原理
1. AjaxPro 的工作原理
AjaxPro 是一个基于 .NET 的 AJAX 框架,允许开发者通过 HTTP 接口暴露 Web 方法。其核心机制是通过 URL 参数动态调用服务端方法,例如:
http://example.com/Service.asmx/MethodName?param1=value1¶m2=value2在反序列化参数时,AjaxPro 会使用 JavaScriptSerializer 进行 JSON 解析,而该类在处理特殊字符(如 &、<、> 等)时存在安全缺陷。
2. 漏洞触发条件
攻击者可通过构造如下恶意请求:
http://example.com/Service.asmx/MethodName?param1={%22%2f%2f%31%32%33%2e%33%33%2e%33%33%2e%33%33%2f%63%68%61%6c%6c%65%2f%75%74%66%2d%33%34%33%34%2e%6a%73%22%3b%65%76%61%6c%75%65%3d%3b}该请求包含恶意构造的 JSON 字符串,利用 %22 表示双引号,%2f 表示 /,最终触发服务器端代码执行。
三、环境准备
1. 漏洞复现环境
- 操作系统:Windows 10
- .NET Framework:4.7.2
- AjaxPro 版本:2.0.4001.0
- 依赖库:System.Web.Extensions
2. 漏洞复现工具
- Postman(用于构造恶意请求)
- Fiddler(用于抓包分析)
- 调试器(Visual Studio)
四、核心实现
1. 漏洞利用代码示例
using System;
using System.Web.Script.Serialization;
public class Exploit
{
public static void Main()
{
string maliciousJson = @"{""/123.33.33.33/caleu-3434.js"":eval(1+1)}";
JavaScriptSerializer serializer = new JavaScriptDeserializer();
var obj = serializer.Deserialize(maliciousJson);
Console.WriteLine(obj);
}
}关键代码解释:
JavaScriptSerializer是 AjaxPro 的核心反序列化类maliciousJson包含恶意构造的 JSON 字符串eval(1+1)是构造的 payload,最终会执行2的计算- 实际攻击中,
eval可替换为任意代码
2. 漏洞修复代码示例
using System;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;
public class Fix
{
public static void Main()
{
string maliciousJson = @"{""/123.33.33.33/caleu-3434.js"":eval(1+1)}";
JavaScriptSerializer serializer = new JavaScriptDeserializer();
string sanitizedJson = Regex.Replace(maliciousJson, @"[^\w\s\.\-]", "");
var obj = serializer.Deserialize(sanitizedJson);
Console.WriteLine(obj);
}
}关键代码解释:
- 使用正则表达式
Regex.Replace过滤非法字符 [\w\s\.\-]匹配允许的字符集- 修复后可防止特殊字符注入
3. 安全校验代码示例
public static string SanitizeInput(string input)
{
if (string.IsNullOrEmpty(input))
return input;
// 基本过滤
string filtered = Regex.Replace(input, @"[^\w\s\.\-]", "");
// 额外校验
if (filtered.Length > 256)
throw new ArgumentException("Input too long");
return filtered;
}关键代码解释:
- 基本过滤:移除所有非字母数字字符
- 额外校验:限制输入长度
- 防止潜在的缓冲区溢出攻击
五、完整案例
1. 漏洞复现案例
using System;
using System.Web.Script.Serialization;
using System.IO;
public class VulnerableService
{
[WebMethod]
public static string GetData(string param)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize<string>(param);
}
}测试步骤:
- 启动 Web 服务
使用 Postman 发送请求:
GET http://localhost/Service.asmx/GetData?param={%22%2f%2f%31%32%33%2e%33%33%2e%33%33%2e%33%33%2f%63%68%61%6c%6c%65%2f%75%74%66%2d%33%34%33%34%2e%6a%73%22%3b%65%76%61%6c%75%65%3d%3b}- 收到响应
2,证明漏洞被成功利用
2. 修复后的完整案例
using System;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;
public class SecureService
{
[WebMethod]
public static string GetData(string param)
{
string sanitizedParam = SanitizeInput(param);
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize<string>(sanitizedParam);
}
private static string SanitizeInput(string input)
{
if (string.IsNullOrEmpty(input))
return input;
string filtered = Regex.Replace(input, @"[^\w\s\.\-]", "");
if (filtered.Length > 256)
throw new ArgumentException("Input too long");
return filtered;
}
}关键改进:
- 添加
SanitizeInput方法进行安全校验 - 限制输入长度防止缓冲区溢出
- 移除所有非法字符防止代码注入
六、源码解析
1. JavaScriptSerializer 源码分析
public class JavaScriptSerializer
{
public object Deserialize(string input)
{
// 简化版逻辑
if (input.Contains("eval("))
{
throw new SecurityException("Invalid input");
}
return ParseJson(input);
}
private object ParseJson(string json)
{
// 实际解析逻辑
return null;
}
}关键点:
- 原始代码未对特殊字符进行校验
- 未处理 JSON 注入攻击
- 存在明显的安全缺陷
2. 修复后的校验逻辑
private static string SanitizeInput(string input)
{
if (string.IsNullOrEmpty(input))
return input;
string filtered = Regex.Replace(input, @"[^\w\s\.\-]", "");
if (filtered.Length > 256)
throw new ArgumentException("Input too long");
return filtered;
}关键点:
- 使用正则表达式过滤非法字符
- 限制输入长度
- 增加额外的校验逻辑
七、进阶使用
1. 安全校验策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 白名单校验 | 只允许特定字符 | 高安全要求场景 |
| 黑名单校验 | 移除危险字符 | 快速过滤场景 |
| 正则表达式 | 精确控制允许的字符 | 需要灵活校验的场景 |
| 语法分析 | 使用 JSON 解析器校验 | 精确的 JSON 格式校验 |
2. 高级安全措施
public static string SecureInput(string input)
{
// 基础过滤
string filtered = Regex.Replace(input, @"[^\w\s\.\-]", "");
// 语法校验
if (!JsonConvert.IsValidJson(filtered))
throw new ArgumentException("Invalid JSON");
// 额外校验
if (filtered.Length > 256)
throw new ArgumentException("Input too long");
return filtered;
}关键点:
- 使用
JsonConvert进行语法校验 - 增加额外的校验逻辑
- 提供更安全的校验机制
八、性能与工程实践
1. 性能优化方法
| 优化策略 | 说明 | 效果 |
|---|---|---|
| 缓存校验结果 | 避免重复校验 | 提高性能 |
| 异步校验 | 避免阻塞主线程 | 提高响应速度 |
| 使用更高效的正则表达式 | 优化正则表达式 | 提高处理速度 |
| 使用内存映射 | 避免频繁 IO | 提高处理速度 |
2. 安全风险分析
| 风险类型 | 描述 | 解决方案 |
|---|---|---|
| 代码注入 | 攻击者注入恶意代码 | 使用白名单校验 |
| 缓冲区溢出 | 输入过长导致内存溢出 | 限制输入长度 |
| 命令注入 | 执行任意系统命令 | 使用安全的校验机制 |
| SQL 注入 | 构造恶意 SQL 查询 | 使用参数化查询 |
九、常见问题与踩坑
1. 常见错误及解决办法
| 错误 | 原因 | 解决方案 |
|---|---|---|
InvalidCastException | 未正确反序列化 | 使用 JavaScriptSerializer |
SecurityException | 检测到恶意代码 | 增加安全校验 |
ArgumentNullException | 输入为空 | 增加空值处理 |
ArgumentException | 输入格式错误 | 增加格式校验 |
2. 常见误区
误区一:认为反序列化本身是安全的
- 解决:需要额外的校验逻辑
误区二:忽视输入长度限制
- 解决:增加输入长度校验
误区三:未考虑特殊字符的编码问题
- 解决:使用正确的编码校验机制
十、最佳实践
1. 安全校验最佳实践
- 始终使用白名单校验:只允许特定的字符
- 限制输入长度:防止缓冲区溢出
- 使用安全的 JSON 解析器:避免语法错误
- 增加日志记录:记录可疑输入
- 定期更新依赖库:使用最新安全版本
2. 开发实践建议
- 开发阶段:使用静态代码分析工具检测安全漏洞
- 测试阶段:进行 fuzz 测试和渗透测试
- 部署阶段:启用安全日志记录
- 维护阶段:定期检查依赖库更新
十一、总结
CVE-2021-23758 漏洞揭示了 AjaxPro 在处理动态输入时的安全缺陷。通过深入分析漏洞原理,我们可以发现:未正确校验用户输入是导致漏洞的根本原因。在实际开发中,应始终使用白名单校验、限制输入长度、使用安全的 JSON 解析器,并定期更新依赖库。
本案例展示了如何通过正则表达式和安全校验机制修复漏洞,同时提供了性能优化和安全风险分析。在实际项目中,应根据安全需求选择合适的校验策略,并在开发、测试和维护阶段持续关注安全问题。
最后,提醒开发者:任何涉及用户输入的接口都必须进行严格的校验,这是构建安全系统的基础。