Insufficient Type Check on GraphQL leading to Maintainer delete repository
HackerOne report #858671 by ledz1996
on 2020-04-24:
Summary
As you have know, Maintainer cannot delete/archive repository. But via GraphQL, they can do as there exists an insufficient check on GraphQL API
app/graphql/mutations/snippets/destroy.rb
def resolve(id:)
snippet = authorized_find!(id: id)
response = ::Snippets::DestroyService.new(current_user, snippet).execute
The function authorized_find
lead to object_from_id!
app/graphql/mutations/snippets/base.rb
def find_object(id:)
GitlabSchema.object_from_id(id)
end
def authorized_resource?(snippet)
Ability.allowed?(context[:current_user], ability_for(snippet), snippet)
end
def ability_for(snippet)
"#{ability_name}_#{snippet.to_ability_name}".to_sym
end
Here there is no check for whether the Object returned from find_object
is a Snippet. I could specify any object which the user have permission of
"#{ability_name}_#{snippet.to_ability_name}".to_sym
to.
For example: A DiffNote that is created by a Maintainer in the Project as the function.
If I have such a ID:
mutation test{
destroySnippet(input: {id: "gid://gitlab/DiffNote/116"}){
errors
}
}
It refer to an DiffNote with id 116
Perfectly, a Maintainer have an admin_note
and admin_snippet
on a DiffNote (!!!)
In the mutation Destroy the call to ::Snippets::DestroyService.new(current_user, snippet)
but the Object of snippet
is actually a DiffNote
app/services/snippets/destroy_service.rb
def attempt_destroy!
result = Repositories::DestroyService.new(snippet.repository).execute
raise DestroyError if result[:status] == :error
snippet.destroy!
end
and in
app/models/diff_note.rb
def repository
noteable.respond_to?(:repository) ? noteable.repository : project.repository
end
It return the project.repository
which in turn the Project that the DestroyService
gonna delete
Steps to reproduce
- Create 2 User: User A, User B
- User A create a project set User B as Maintainer.
unauthen1.PNG - User B create 2 branch with the same file but different content
unauthen2.PNG
unauthen3.PNG - Create a merge request for those 2 Branch
unauthen4.PNG - Create a diff note for the file by clicking at the comment on a line of the file then Submit it
unauthen5.PNG - To know the ID of diff note, delete this one, the ID will show up in burp then you will know the ID of the next one
unauthen6.PNG - Use the
/-/graphiql-explorer
to execute the following the query
mutation test{
destroySnippet(input: {id: "gid://gitlab/DiffNote/118"}){
errors
}
}
unauthen7.PNG
8. Enjoy no repository
unauthen8.PNG
9. If you click on Create empty repository
It will actually make the Project 404 but It still show up on the User's project feed
unauthen10.PNG
unauthen9.PNG
Impact
Unauthorized deleting of repository/project by maintainers
Output of checks
This bug happens on GitLab.com
Impact
Unauthorized deleting of repository/project by maintainers
Attachments
Warning: Attachments received through HackerOne, please exercise caution!