近期,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_template
和group_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 @project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute # ... end # ... def project_params_attributes [ # ... :template_name, # ... ]
而在EE中,EE:Projects::CreateService
和Projects::CreateService
相关联。前置的EE代码包含验证参数use_custom_template
和group_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
值,那么就永远不会执行验证逻辑。这也就意味着在实例变量params
中use_custom_template
和group_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
该方法需要两个参数:search
和subgroup_id
。第一个是用户传递的template_name
,第二个是group_with_project_templates_id
。templates
变量根据以下方法定义获取其值:
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
使用一个初始**,其中包含经过身份验证的用户可以访问的项目。但是,它并不会验证用户的访问级别。这意味着任何项目都是公开的,例如敏感的Repository
,Issue
,Snippets
等,都会被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问题。当导出作业创建后,将自动将其导入到用户能完全访问的项目中。
将以上三个漏洞结合起来,攻击者就能够获得某个项目中的任何敏感信息。这种攻击仅适用于属于一个组且repositories
,issues
,pipelines
,merge requests
访问受限的公共项目。一个明显的例子即是[https://gitlab.com/gitlab-com/finance](https://gitlab.com/gitlab-com/finance)
,它虽然是一个公共项目,但项目很多部分都没有公开。
PoC
复现步骤如下:
1.以普通用户身份登录并创建一个组,假设组ID是1
2.在这个组中,创建一个公共项目test_project
3.在Settings > General
下更新Visibility, project features, permissions
,让多个项目部分只对项目成员开放
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_template
为true
,template_name
改为攻击目标,group_with_project_templates_id
改为受害者创建的组ID。接着发送请求,你将很快看到项目被导入。
根据项目大小,这一过程可能需要几分钟。最后你就可以看到目标项目的全貌。
Gitlab在确认漏洞后,发放了12000美金的奖励。
本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场:https://nosec.org/home/detail/3227.html 来源:https://hackerone.com/reports/689314
来源:freebuf.com 2019-11-28 17:45:16 by: 白帽汇
请登录后发表评论
注册