前言:
最近扫描碰到了 CVE-2023-49735漏洞,但是网上一搜发现没有一个对这个漏洞研究的,那我就当个挖井人挖一下这个漏洞,首先我们要了解tiles
org.apache.tiles提供了一种强大而灵活的方式来构建和管理 Java Web 应用程序的视图层。通过视图组合、模板支持、动态内容加载和国际化支持,Tiles 帮助开发者提高开发效率,简化应用程序的维护。
Tiles 可以与国际化(i18n)框架结合使用,根据用户的语言偏好动态加载内容。这使得开发多语言支持的应用程序变得更加简单。
这次的漏洞主要出在国际化操作中,由于官方已经停止维护CVE-2023-49735在最新版3.0.8同样存在,所以只要是调用tiles进行国际化操作的均存在该漏洞:
漏洞详情:
首先来看下 CVE-2023-49735漏洞官方给的解释:
分配时不受支持 在解析 XML 定义文件时,在会话上设置为 DefaultLocaleResolver.LOCALE_KEY 属性的值未得到验证,从而导致在将用户控制的数据传递给此键时可能发生路径遍历并最终导致 SSRF/XXE。将用户控制的数据传递给此键可能相对常见,因为它也被用于在 Tiles 附带的 'tiles-test' 应用程序中设置语言。此问题会影响版本 2 及更高版本的 Apache Tiles。注意:此漏洞仅影响维护者不再支持的产品。
所以可以知道该漏洞存在于DefaultLocaleResolver.LOCALE_KEY属性值没有做校验,进而会导致SSRF或XXE漏洞,下面我们看下具体的漏洞点
漏洞点就位于此处 request.getRequestLocale()方法,当用户发送 HTTP 请求时,浏览器会包含一个 Accept-Language 请求头,该请求头指示用户的语言偏好。request.getRequestLocale() 方法会解析这个请求头,并返回一个适合的 Locale 对象。
这里可以发现对获取到的内容没有任何的过滤就进行了返回,正常情况下发送的是如下数据:
GET /api/resource HTTP/1.1
Host: example.com
Accept-Language: en_US
但是如果这个时候如果我们发送的数据是如下这样,替换为file:///etc/passwd:
GET /api/resource HTTP/1.1
Host: example.com
Accept-Language: file:///etc/passwd
并且在通过 request.getRequestLocale()获取到以后进行后续操作的时候同样没有校验,默认为可信,进行了如下操作,就会触发xxe漏洞
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>
所以当应用程序将 Locale 的值用于动态加载资源文件或进行其他处理。如果 Locale 的值被用于构造 XML 或其他数据格式,且没有合理的过滤或验证,攻击者的输入可能会被传递到后续处理逻辑中。
漏洞总结:
所以单单使用Tiles但是如果没有使用国际化功能,是不会触发该漏洞,该漏洞的本质就是获取Locale时没有校验,且Locale是前端可控的值,如果后续操作中并未对该值进行校验并且碰巧Locale 的值被用于构造 XML 或其他数据格式,则就有可能触发SSRF/XXE漏洞
漏洞修复:
其实漏洞的修复很简单,如果是针对tiles-core进行修复,但是这有个问题,我们要不就针对名称进行过滤,要不就是针对长度进行过滤,但如果项目有一些特殊的语言或者覆盖不全则会有业务bug:
import org.apache.tiles.locale.LocaleResolver;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
public class DefaultLocaleResolver implements LocaleResolver {
public static final String LOCALE_KEY = "org.apache.tiles.LOCALE";
private static final Set<String> ALLOWED_LANGUAGES = new HashSet<>();
static {
// 初始化允许的语言列表
ALLOWED_LANGUAGES.add("en"); // 英语
ALLOWED_LANGUAGES.add("fr"); // 法语
ALLOWED_LANGUAGES.add("de"); // 德语
ALLOWED_LANGUAGES.add("es"); // 西班牙语
ALLOWED_LANGUAGES.add("zh"); // 中文
// 可以根据需要添加更多语言
}
@Override
public Locale resolveLocale(Request request) {
Locale retValue = null;
Map<String, Object> session = request.getContext("session");
// 从会话中获取 Locale
if (session != null) {
retValue = (Locale) session.get(LOCALE_KEY);
}
// 如果会话中没有找到,则尝试从请求中获取
if (retValue == null) {
retValue = request.getRequestLocale();
}
// 校验 Locale 是否有效
if (!isValidLocale(retValue)) {
// 如果无效,返回默认的 Locale(例如,英语-美国)
retValue = Locale.US; // 或者其他合适的默认值
}
return retValue;
}
// 通用的 Locale 校验方法
private boolean isValidLocale(Locale locale) {
// 检查 locale 是否为 null
if (locale == null) {
return false;
}
// 检查语言是否在允许的列表中
return ALLOWED_LANGUAGES.contains(locale.getLanguage());
}
}
所以更建议在项目代码测在使用Locale之前根据项目本身情况进行白名单校验,并且禁用外部实体解析,以防止 XXE 攻击