Skip to content

Asset Proxy Bypass using non-ASCII character in asset URI

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #2071411 by afewgoats on 2023-07-16, assigned to @nmalcolm:

Report | Attachments | How To Reproduce

Report

Summary

Gitlab EE has an Asset Proxying feature. This feature is active on gitlab.com.

Without this feature, attackers can steal a user’s IP address by referencing images in issues and comments that the victim browses. The image hosting server can also see referer headers identifying the Gitlab instance and the user's cookies for that remote site.

Banzai::Filter::AssetProxyFilter has the following code:

          begin  
            uri = URI.parse(original_src)  
          rescue StandardError  
            next  
          end  
          ...  
          element['src'] = asset_proxy_url(original_src)  

Which states that if URI.parse raises an exception, we do not proxy the URL but use it as-is (next). This is a fail-open check that is easy to satisfy with a valid URL. URI.parse is much stricter than Addressable::URI.parse. URI.parse raises an exception on any URI containing a non-ASCII character anywhere.

> URI.parse("https://example.com/x?$")  
=> #<URI::HTTPS https://example.com/x?$>  
> URI.parse("https://example.com/x?`")  
=> #<URI::HTTPS https://example.com/x?%60>  
> URI.parse("https://example.com/x?¬")  
    /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/uri-0.12.1/lib/uri/rfc3986_parser.rb:20:in `split': URI must be ascii only "https://example.com/x?\u00AC" (URI::InvalidURIError)  
> URI.parse("https://example.com/x#£")  
    /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/uri-0.12.1/lib/uri/rfc3986_parser.rb:20:in `split': URI must be ascii only "https://example.com/x#\u00A3" (URI::InvalidURIError)  
> URI.parse("https://example.com/笨蛋")  
    /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/uri-0.12.1/lib/uri/rfc3986_parser.rb:20:in `split': URI must be ascii only "https://example.com/\u7B28\u86CB" (URI::InvalidURIError)  
> URI.parse("https://㏊ckerone.com/")  
    /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/uri-0.12.1/lib/uri/rfc3986_parser.rb:20:in `split': URI must be ascii only "https://\u33CAckerone.com/" (URI::InvalidURIError)  

When adding an image using the ![]() markdown tags, the URL appears to be URL-encoded before hitting AssetProxyFilter, so there is no bypass.
When adding an image using <img> HTML tags, however, the URL appears to go directly to AssetProxyFilter, allowing the asset proxy to be bypassed.

Click to expand original H1 report, which includes steps to reproduce locally
Steps to reproduce
  1. Turn on Asset Proxying by making the curl request at https://docs.gitlab.com/ee/security/asset_proxy.html). Installing camo is not required here, we just need Gitlab to rewrite images sources to some domain. Alternatively, just use gitlab.com as that has asset proxying.
  2. Write markdown somewhere in Gitlab e.g. an issue comment. The following 3 markdown images load (or fail to load) via the asset proxy. Verify that they do not load directly from giphy.
![markdown image](https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp)  
![markdown image with non-ascii character](https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp?¬)  
<img src="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp" alt="html image" />  
  1. Now try the following Markdown to bypass the asset proxy: an IMG tag where the URL contains a non-ASCII character ¬.
<img src="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp?¬" alt="html image with non-ASCII character" />  

The browser loads the URL directly from the remote server.

The request (view it in dev tools or a proxy):

  • Will reveal the user's IP address
  • Contains a referer header identifying the Gitlab instance
  • Contains cookies for the remote URL, identifying me to the remote server if I have cookies for it. As I have visited giphy.com, it sent didomi_accept_cookie=1; euconsent-v2=...; didomi_token=.... If the image URL was Google, then Google cookies would be sent along with the request to Google (this was tested in Chrome).

Note that gitlab.com CSP allows all remote images img-src * data: blob:;.

Impact

The Asset Proxy is a privacy and security feature to prevent visitors to pages on the Gitlab instance from being tracked by malicious users and malicious remote servers. This bug allows a malicious user to bypass this feature and add non-proxied image tags wherever markdown is accepted on Gitlab and track visitors.

What is the current bug behavior?

Entering <img src="https://url-containing-non-ascii/£" /> in a Markdown field results in Asset Proxy being bypassed.

<a class="no-attachment-icon" href="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp?%C2%AC" target="_blank" rel="nofollow noreferrer noopener"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="x" decoding="async" class="lazy" data-src="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp?¬"></a>  

(lazy-loaded but directly from source)

What is the expected correct behavior?
<a class="no-attachment-icon" href="https://proxy-gitlab.example.com/c5adcab7ef96e3081000c51459e8c1483863d751/68747470733a2f2f692e67697068792e636f6d2f6d656469612f336f4b49506e4169614d437773386e4f73452f67697068792e77656270" target="_blank" rel="nofollow noreferrer noopener" data-canonical-src="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="x" data-canonical-src="https://i.giphy.com/media/3oKIPnAiaMCws8nOsE/giphy.webp" decoding="async" class="lazy" data-src="https://proxy-gitlab.example.com/c5adcab7ef96e3081000c51459e8c1483863d751/68747470733a2f2f692e67697068792e636f6d2f6d656469612f336f4b49506e4169614d437773386e4f73452f67697068792e77656270"></a>  

(via proxy-gitlab.example.com)

Relevant logs and/or screenshots

Comments showing proxied images (failed to load as no camo set up) and one image loaded bypassing the proxy.

image.png

Network tab showing three (failed) image requests to proxy and one to the remote server.

image.png

HTML of proxied (above) and unproxied (below) comments.

image.png

Output of checks

This bug happens on GitLab.com

Results of GitLab environment info
root@gitlab:/# gitlab-rake gitlab:env:info

System information  
System:  
Proxy:          no  
Current User:   git  
Using RVM:      no  
Ruby Version:   3.0.6p216  
Gem Version:    3.4.13  
Bundler Version:2.4.14  
Rake Version:   13.0.6  
Redis Version:  6.2.11  
Sidekiq Version:6.5.7  
Go Version:     unknown

GitLab information  
Version:        16.1.2-ee  
Revision:       0642e8c5c91  
Directory:      /opt/gitlab/embedded/service/gitlab-rails  
DB Adapter:     PostgreSQL  
DB Version:     13.11  
URL:            https://gitlab.example.com  
HTTP Clone URL: https://gitlab.example.com/some-group/some-project.git  
SSH Clone URL:  git@gitlab.example.com:some-group/some-project.git  
Elasticsearch:  no  
Geo:            no  
Using LDAP:     no  
Using Omniauth: yes  
Omniauth Providers: 

GitLab Shell  
Version:        14.23.0  
Repository storages:  
- default:      unix:/var/opt/gitlab/gitaly/gitaly.socket  
GitLab Shell path:              /opt/gitlab/embedded/service/gitlab-shell  

Impact

The Asset Proxy is a privacy and security feature to prevent visitors to pages on the Gitlab instance from being tracked by malicious users and malicious remote servers. This bug allows a malicious user to bypass this feature and add non-proxied image tags wherever markdown is accepted on Gitlab and track visitors.
Impact depends on how much importance people place on privacy and the asset proxy.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce on GitLab.com

Please add [reproducibility information] to this section:

  1. Create an Issue on GitLab.com
  2. Enter the following markdown:
# Markdown

![](https://i.imgur.com/5ADf1jq.png)

# HTML

<img src="https://i.imgur.com/5ADf1jq.png" alt="html image" />

# HTML plus ascii

<img src="https://i.imgur.com/5ADf1jq.png¬" alt="html image" />
  1. Observe that the first two images are requested via user-content.gitlab-static.net (Good)
  2. Observe that the third image, with the ¬, is requested direct to i.imgur.com which is bypassing the asset proxy.

Screenshot_2023-07-20_at_4.34.43_PM

Edited by Nick Malcolm