【问题标题】:Virtual paths from the client to real paths on the server从客户端到服务器上的真实路径的虚拟路径
【发布时间】:2020-12-05 23:04:39
【问题描述】:

客户端应该只看到服务器上的目录及其内容 (FS_ROOT)。

并且服务器应该将从客户端接收到的路径转换为存在的真实路径并执行客户端请求的文件操作:

我创建了这两个函数来处理这个问题,我想问一下它们是否足够安全。我的意思是客户端应该没有办法欺骗服务器在FS_ROOT之外做一些事情@

  function fromVirtualPath(virtPath){
    if(virtPath === '/' || virtPath === '.')
      return FS_ROOT;

    virtPath = virtPath.trim();

    if(virtPath[0] === '/')
      virtPath = virtPath.substr(1);

    const absPath = path.resolve(FS_ROOT, virtPath);

    if(absPath.indexOf(FS_ROOT) !== 0)
      throw new Error('Outside root dir - no permissions!');

    return absPath;
  }

  function toVirtualPath(absPath){
    return '/' + path.relative(FS_ROOT, absPath);
  }

真实路径示例:/www/site.com/public_html/yo

客户应该看到:/yo

【问题讨论】:

    标签: javascript node.js security path


    【解决方案1】:

    关于fromVirtualPath我直接把virtPath = virtPath.trim();这行移到函数的第一行就好了。

    如果传递给toVirtualPath 的值总是fromVirtualPath 的返回值,是的,它足够安全;否则我们可以检查该值是否是一个好的 absPath。

    function fromVirtualPath(virtPath) {
      virtPath = virtPath.trim();
    
      if (virtPath === '/' || virtPath === '.')
        return FS_ROOT;
    
      if (virtPath[0] === '/')
        virtPath = virtPath.substr(1);
    
      const absPath = path.resolve(FS_ROOT, virtPath);
    
      if (absPath.indexOf(FS_ROOT) !== 0)
        throw new Error('Outside root dir - no permissions!');
    
      return absPath;
    }
    
    function toVirtualPath(absPath) {
      if (absPath.indexOf(FS_ROOT) !== 0)
        throw new Error('Bad absolute path!');
    
      return '/' + path.relative(FS_ROOT, absPath);
    }
    

    【讨论】:

      【解决方案2】:

      在您使用上述文章中 NODE.JS 提供的技术之前,您的代码有点不安全。尝试实现以下代码,

      function fromVirtualPath(virtPath) {
        virtPath = virtPath.trim();
      
        if (virtPath === '/' || virtPath === '.')
          return FS_ROOT;
      
        if (virtPath.indexOf('\0') !== -1)
          throw new Error('That was evil.');
      
        const absPath = path.join(FS_ROOT, virtPath);
      
        if (absPath.indexOf(FS_ROOT) !== 0)
          throw new Error('Outside root dir - no permissions!');
      
        return absPath;
      }
      
      function toVirtualPath(absPath) {    
        return '/' + path.relative(FS_ROOT, absPath);
      }
      

      以下来自 NODE.JS 的文章对你很有帮助。

      "How can I secure my code?"

      毒空字节

      毒化空字节是一种诱使您的代码看到另一个代码的方法 文件名比实际打开的文件名。

      if (filename.indexOf('\0') !== -1) {
        return respond('That was evil.');
      }
      

      防止目录遍历

      此示例假定您已经检查了 userSuppliedFilename 变量,如“Poison Null”中所述 字节”部分。

      var rootDirectory = '/var/www/'; // this is your FS_ROOT
      

      确保在允许的文件夹名称末尾有一个斜杠

      • 您不希望人们能够访问/var/www-secret/,是吗?

        var path = require('path'); var filename = path.join(rootDirectory, userSuppliedFilename);

      现在文件名包含一个绝对路径并且不包含.. 序列不再 - path.join 负责。然而,它可能 现在是/etc/passwd 这样的东西,所以你必须检查它是否 以rootDirectory开头:

      if (filename.indexOf(rootDirectory) !== 0) {
        return respond('trying to sneak out of the web root?');
      }
      

      现在filename 变量应该包含文件名或 允许目录内的目录(除非它没有 存在)。

      【讨论】:

        【解决方案3】:

        安全是一个复杂的问题。而且你永远无法确定。

        尽管我在@RahulVerma 答案中找不到任何流量,但我会加我的 2 美分...

        @RahulVerma 发布的link 是官方的,但本身不是文档。并且在文档中没有关于 Poison Null Bytes 的内容......不是很奇怪。

        这让您思考:也许,只是也许,当编写 fs 和/或 path 模块时,作者没有在安全考虑方面付出足够的努力,或者只是错过了这一点。是的,也许你有一些很好的理由而不是fs/path 来处理\0。但是,如果每个人都默认受到\0 的保护,那不是更好吗?并且仅在某些情况下,您可以明确设置一个选项以允许 \0 在路径中。

        所以...我想说的是:即使对于我们中最好的人来说,安全也很困难,并且没有适当的同行评审(目前,这个问题的浏览量不到 100不要把我当作“适当的同行评审”),或者更好的是,在生产中取得成功的历史,你不应该对这些答案(包括我的)感到满意,说 “没关系,如果你添加这个或那个”

        您为什么不使用一些已经在战斗中测试过的代码,而不是尝试自己编写安全代码?

        例如,serve-staticExpress 中使用。
        (可能它不能满足您的需求 - 毕竟它是 静态的,但您会得到想法)

        即使您不希望项目中出现其他依赖项,您至少也可以学习并复制经过验证的实现。 (但是,是的,它似乎与@RahulVerma 的答案没有什么不同)

        就是这么说的。我想指出:

        1. 如果您要复制实现,您可能会在这样做时出错。

        2. 即使您的代码是安全的,也要考虑您管理代码的安全程度。明天会安全吗?

        3. 即使是经过良好测试的库和引擎也可能并且经常存在错误,并成为 0day 漏洞利用

          的牺牲品

          哦!刚刚找到:https://github.com/autovance/ftp-srv/issues/167 这是关于你的另一个问题中建议的图书馆。

        因此,如果您决定(或者如果您确信)现在您的代码肯定是安全的,请不要就此止步!无论如何都要为其添加额外的安全层:

        在操作系统级别限制服务器对 /www/site.com/public_html/ 之外的文件夹的访问。

        【讨论】:

          【解决方案4】:

          可以应用以下原则来保护客户端对相对于 Web 根目录的路径的访问:

          1. 将公共 Web 根文件夹之外的访问限制为您的 服务。 理由:从零信任开始。
          2. 将用户提供的路径拆分为多个部分。这将删除前导“/”和所有“/”分隔符,只留下路径的一部分。更好的是,对路径部分使用白名单以使用正则表达式限制路径部分中可接受的字符。 理由:清理用户输入
          3. 假设第一部分按预期从 Web 根目录开始,按顺序验证每个部分是否存在。禁止在部件名称中使用 ..(父目录)(以防止遍历 Web 根文件夹之外)。 理由:清理用户输入并验证用户输入
          4. 避免在 Web 根文件夹下使用符号链接(以防止 遍历 web 根文件夹外)。 理由:减少攻击面
          5. 遇到第一个无效部分时出现错误并提前失败。 理由:减少攻击面

          要优化系统调用,您可以一次性完成对 .. 的检查和部分白名单。如果路径中有任何 .. 或有问题的部分,则返回错误。否则,拆分这些部分并通过将它们与您的 Web 根目录连接来重建绝对路径字符串,并进行一次存在性检查,而不是沿路径进行多个文件夹存在性检查。

          【讨论】:

            【解决方案5】:

            与其尝试自己验证每条路径,不如让操作系统为您完成!这是可以使用 chroot 的应用程序的一个很好的示例。

            Here 是一个创建 chroot 的 npm 库示例。

            > var chroot = require("chroot")
            > var fs = require("fs")
            > chroot('/virtual/root/here', 'nobody')
            > fs.readdir(".", function(err, files) { console.log(files); }) // Lists virtual root
            > fs.readdir("..", function(err, files) { console.log(files); }) // Also lists virtual root
            > fs.readdir("/", function(err, files) { console.log(files); }) // ALSO lists virtual root
            

            如果您以 root 身份运行此脚本,它会立即将用户更改为“nobody”并将您沙箱化到您的虚拟 root。这可以防止脚本访问它之外的任何内容,并且程序也无法 chroot,因为它不再以 root 身份运行。

            现在您已进入虚拟根目录,使用“/”将为您提供虚拟根目录的目录列表 - 本质上,您可以直接在 fs.readdir() 中使用虚拟路径!

            需要访问新根目录之外的某些特定文件?使用微服务!您可以在后台运行 node.js 实例作为文件访问器,并在主服务器和文件访问器之间进行通信。拥有两个 nodejs 实例不仅可以让您的后台任务自行沙箱,还可以让您利用多线程。

            【讨论】:

              【解决方案6】:

              你的是一个基本的java代码。在实时场景中,这些基本的java代码不应该部署在服务器端,我们不能指望 安全。

              为了向这个 java 代码添加安全检查,许多 API 是 Spring 框架的一部分,但是由于我们正在编写 java 代码,所以我们可以 仅使用 java NIO 包,API 名称 WatchService 和 WatchEvent

              class DirectoryWatchTest {
              public static void main(String[] args) {
                  try {
                      WatchService watchService = FileSystems.getDefault().newWatchService();
                      Path path = Paths.get("C:/");
              
                      /**
                       * The register() method of the Path class takes a WatchService object and an event type for which the 
                       * application needs to get notified. 
                       * 
                       * The supported event types are: 
                       * ENTRY_CREATE: indicates if a directory or file is created. 
                       * ENTRY_DELETE: indicates if a directory or file is deleted. 
                       * ENTRY_MODIFY: indicates if a directory or file is modified. 
                       * OVERFLOW: indicates if the event might have been lost or discarded. This event is always implicitly 
                       * registered so we don't need to explicitly specify it in the register() method. */
                      path.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
                      while (true) {
                          WatchKey key;
                          try {
                              key = watchService.take();
                          } catch (InterruptedException ex) {
                              return;
                          }
                          
                      /** 
                       * The whole work flow:
                       * A Watchable object is registered with a watch service by invoking its register method, 
                       * returning a WatchKey to represent the registration. 
                       * 
                       * When an event for an object is detected, the key is signalled, and if not currently signalled, 
                       * it is queued to the watch service so that it can be retrieved by consumers that invoke the poll or 
                       * take methods to retrieve keys and process events. 
                       * 
                       * pollEvents List<WatchEvent<?>> pollEvents() method retrieves and removes all pending events for 
                       * this watch key, returning a List of the events that were retrieved. Note that this method does not 
                       * wait if there are no events pending. */
                      
                      for (WatchEvent<?> event : key.pollEvents()) {
                          WatchEvent.Kind<?> kind = event.kind();
                          @SuppressWarnings("unchecked")
                          WatchEvent<Path> ev = (WatchEvent<Path>) event;
                          Path fileName = ev.context();
                          System.out.println(kind.name() + ": " + fileName);
                          
                          if (kind == ENTRY_MODIFY && fileName.toString().equals("DirectoryWatchTest.java")) {
                                  System.out.println("My source file has changed!!!");
                                  System.out.println("My source file has changed!!! - Modified");
                              }
                          }
                      /**Once the events have been processed the consumer invokes the key's reset method to reset the 
                       * key which allows the key to be signalled and re-queued with further events.*/
                      boolean valid = key.reset();
                          if (!valid) {
                              break;
                          }
                      }
                  } catch (IOException ex) {
                      System.err.println(ex);
                  }
              }
              

              }

              这种基本的安全检查可以放在java代码中。除非我们没有得到,否则用户将能够观看该网址 持有协议并通过@PutMapping 隐藏它或在此实现基于安全的API,但为此我们需要基于框架的API

              enter code here
              

              【讨论】:

                猜你喜欢
                • 2013-07-20
                • 2014-11-28
                • 2014-12-18
                • 1970-01-01
                • 1970-01-01
                • 2019-03-31
                • 2021-02-15
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多