鉴权模式
其实鉴权模式和开发时使用的模式相关(明面看也和路由相关),这里先记录常见的鉴权模式[0],之后遇到再补充:
模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
每功能检查 | 在每个具体功能/路由里先检查是否有权限 | 简单 | 很容易漏掉 |
基于功能检查 | 在每个功能/路由上设定所需权限(如使用装饰器),它分为正向和反向(类似于黑白名单)来检查 | 直观易管理 | 若使用正向容易漏掉权限标记 |
集中检查 | 在一个集中的位置先检查权限(如通过过滤器/拦截器/中间件 在请求处理前先根据ACL做判断),通过检查再放通 | 不容易出错 | 大项目难维护 |
... |
参考
[0] Code Patterns for API Authorization: Designing for Security -- Tanner Prynn@nccgroup
AA协议与安全
记下在设备中常出现的和认证与授权相关的协议...
Digest
这可以说是Basic升级版吧,它不再传输密码了,而是通过双方协商Nonce等信息来传递密码产生的共识数据,暂时没发现过相关漏洞,之后遇到再补充...
JWT
JWT细节就不多说了,如果遇到使用了JWT的一定得先看看它,它很容易出问题:
- 密钥硬编码(黑盒时可以直接用网上的字典试试[1]),遇到了不知道多少次
- 支持RS256转HS256,None等,或者在实现上根本不校验签名 [0]
- 可指定密钥[0]
参考
[0] JWT attacks -- portswigger
[1] jwt-secrets -- d0znpp (2020)
SAML
基本概念
SAML(Security Assertion Markup Language)是一种基于XML的认证与授权协议,听着就比较古老但因此特别普遍所以必会!相比于其他协议它可以说是灰常复杂啦,相关手册数百页,不过只需要关注常见用法遇到不懂得再查手册就行!接下来将根据如下图介绍它的一些基本概念:

首先是Profile,它表示一套方案,SAML可用于单点登录(Single Sign-On)/联合身份(Federated identity)或其他安全场景,这里也只需要关注前两者,一种典型的工作流程(Profile)如下:

SP即服务提供者(Service Provider)是我们要请求的资源(如通常挖掘的目标),而idP即身份提供者(identity Provider)是进行认证和授权服务,看过程很好懂可对?
请注意这只是一种场景的过程,说到其他流程就得提一个术语Binding,它在这里指将SAML和具体的协议映射起来,SAML并没有新的传输协议,只是指定了怎么使用现有协议,有多种binding这里介绍常见的几个:
1.HTTP Redirect Binding
:使用HTTP协议传输,通过Location头重定向各个请求,由于使用GET能传输的数据量有限制
2.HTTP POST Binding
:使用HTTP协议传输,通过隐藏表单返回数据,再有JS脚本触发请求,嗯,POST方式,这两种的过程都和上图一致,由UserAgent和SP/idP交互,后两者不会直接通信
3.HTTP Artifact Binding
:依然是HTTP协议,但UA只会收到SP返回的Artifact再转发给idP,idP识别后直接和SP通信
4.HTTP SOAP Binding
:基于SOAP over HTTP协议来传递数据,过程和1/2差不多
还有绑定其他方式,有需要可以见binding文档,接下来的术语是Assertion,就是SAML里的A,直译为断言,在这里表示SAML权威在一个认证行为后产生的关于主体的一些数据,如属性信息,认证状态,授权状态等。
接下来是metadata元信息,它表示各种配置数据,如SP要配置idP的地址与公钥才能使用这个idP进行鉴权。最后是Protocol规定了具体的请求与响应格式等规范,见后文。
⚠️:再遇到不理解的术语可查查Glossary参考
协议分析
SAML定义了多种协议,这里暂只关注Authentication Request Protocol
,它就是用来认证和授权的,这里只需要看懂两个包即可:
AuthnRequest
它是SP生成的交给idP的包,用于告知idP俺要干嘛,需要哪些东西等,接下来从samltool上找几个例子分析,先看个妹签名的:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" Version="2.0" ProviderName="SP test" IssueInstant="2014-07-16T23:52:45Z" Destination="http://idp.example.com/SSOService.php" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">
<saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true"/>
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
解释下这里面一些可能看不懂的字段含义:
1.samlp:AuthnRequest
: 根嘛看属性,ID
是实体都要有的,IssueInstant
是生成时间,Destination
是交给哪个idP处理,AssertionConsumerServiceURL
指向生成的请求交给谁
2.saml:Issuer
: 请求生成者的实体标志符
3.samlp:NameIDPolicy
: 要返回的名称/格式
4.saml:AuthnContextClassRef
: 标识一个认证上下文类,告知idP使用哪种方式进行认证,PasswordProtectedTransport
当然是说用账号密码咯!
没签名的看完了,但通常请求也是有签名的,它的签名使用了xmldsig,重点关注这里,还是上例子:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfx41d8ef22-e612-8c50-9960-1b16f15741b3" Version="2.0" ProviderName="SP test" IssueInstant="2014-07-16T23:52:45Z" Destination="http://idp.example.com/SSOService.php" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">
<saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#pfx41d8ef22-e612-8c50-9960-1b16f15741b3">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>yJN6cXUwQxTmMEsPesBP2NkqYFI=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>g5eM...</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIICajCC...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true"/>
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
可以看到相比于未签名的版本,它是在原有的根结点下新增了个节点ds:Signature
,这其实是xmldsig的enveloped签名方式,这部分得先从ds:Reference
节点开始看:
1.ds:Reference
:一个该节点指明一个要签名的节点,这里只有一个节点samlp:AuthnRequest
需要签名因此只有一个Reference
节点,它的URI
指向要签名的节点,不写也是指根节点。
2.ds:Transforms
: 是转换方式,即这个节点在签名前需要先进行某种转换,这里有两个转换,第一个xmldsig#enveloped-signature
表示当前文档是enveloped
方式只取原文档的信息,xml-exc-c14n#
表示用该算法进行规范化,XML规范化用于消除无意义(字面上不同但含义上一致,如外部空格数不一致,属性顺序不一致等)差异,因此在做比较(包括签名等)时需要先规范化。
3.ds:DigestMethod
: 即对该节点用的摘要算法
4.ds:DigestValue
: 最终生成的摘要值
所有Reference处理完后,会生成整个文档的签名信息,即ds:Signature
节点:
1.ds:SignedInfo
: 它里面除了Reference的信息,还有签名这部分使用的规范化算法xml-exc-c14n
,签名算法rsa-sha1
2.ds:KeyInfo
: 为签名用的证书(公钥)
3.ds:SignatureValue
: 为对SignedInfo
部分最终生成的签名
这里就要注意了,尽管这里面携带了证书/公钥但只是为了方便对方确定主体,接收方还需要对该证书进行验证或者使用自己保存的证书对该签名进行验证!
Response
认证请求的响应内容就会多很多,依然先看未签名与加密的,如下例:
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
根里面的属性同上,Issuser
和Status
表示签发者和响应状态秒懂,主要还是看Assertion
里的元素:
1.saml:Subject
: 指示主体
2.saml:Conditions
: 该响应的使用限制,如NotBefore
和NotBefore
为有效时间,AudienceRestriction
指定只能由谁使用
3.AuthnStatement
: 描述idP在什么时间通过什么方式进行认证,AuthnContext
里面有熟悉的AuthnContextClassRef
用来告知SP这个响应是用什么方式认证的
4.AttributeStatement
: 包含主体的属性信息
响应里的断言可以被签名或加密,签名过程和请求那是一样的,而加密是把Assertion
改成了EncryptedAssertion
放密文,详见文档。
⚠️:遇到其他不懂的字段可见断言与协议参考
环境搭建
对它测试通常是不需要搭相应的服务器的,但有时也会遇到,这里记录下用Citrix ADC作为idP和SP的方式(因为我是个挖掘机手上刚好有它很合理吧),注意这需要证书授权了该功能,过程可参考文档。
idP
关键步骤有四步:
1.创建认证策略:它用于idP对用户进行身份认证,路径为Security->AAA-Application Traffic->Policies->Authentication->Advanced Policies->Policy
,添加一个策略并制定认证类型,为了简单此处选择Local类型,若之前没有本地用户可在Security->AAA-Application Traffic->Users
里创建一个。
2.创建虚拟认证服务器:它的功能是处理认证请求,对用户进行身份认证,路径为Security->AAA-Application Traffic->Virtual Servers
,在这里添加一个虚拟服务器,主要就指定一个IP(和物理网卡同网段),再指定其认证策略,选择上一步创建的进行绑定即可。
3.创建idP Profile:它就是上面提到的Profile概念,路径选择从策略里开始,即先Security->AAA-Application Traffic->Policies->Authentication->Advanced Policies->SAML IDP
里创建策略,再里面会需要选择Action,在这里创建Profile,其中SAML SP可通过metadata url导入,也可以自己指定,其他内容含义和上面介绍一致按实际填即可。
4.创建idP Policy:它的作用就是将Profile,Log方案等整合起来
命令行如下:
# 注:如下过程需要用到很多证书,可提前创建,仅实验可共用同一证书
# 创建profile,这里使用metadata url去获取sp的元数据,也可以用参数指定元数据
add authentication samlIdPProfile saml-idp-profile -samlIdPCertName saml-idp-cert -rejectUnsignedRequests ON -signatureAlg RSA-SHA256 -digestMethod SHA256 -samlIssuerName idpissuer -serviceProviderID spissuer -metadataUrl "https://192.168.202.15/metadata/samlsp/saml-sp-action"
# 添加idp策略,action为刚创建的profile
add authentication samlIdPPolicy saml-idp-policy -rule true -action saml-idp-profile
# 新建身份验证服务器
add authentication vserver saml-auth-vserver SSL 192.168.202.17 443
# 创建服务器后需要先绑定证书它才能启动
bind ssl vserver saml-auth-vserver -certkeyName saml-auth-vserver-cert
# 将策略绑定到新建的服务器
bind authentication vserver saml-auth-vserver -policy saml-idp-policy -priority 100
SP
SP和idP差不多,直接上命令行:
# 创建认证服务器,和上面过程一致
add authentication vserver saml-sp-vserver SSL 192.168.202.18 443
bind ssl vserver saml-sp-vserver -certkeyName saml-sp-vserver-cert
# 创建saml认证动作,samlidp/<profile-name>
add authentication samlAction saml-sp-action -metadataUrl "https://192.168.202.17/metadata/samlidp/saml-idp-profile" -samlSigningCertName saml-sp-cert -samlIssuerName spissuser -metadataRefreshInterval 3600
# 添加策略
# add authentication samlPolicy saml-sp-policy ns_true saml-sp-action # 经典策略已过时,使用下面的方式添加高级策略
add authentication Policy saml-sp-policy -rule true -action saml-sp-action
# 将策略与虚拟认证服务器绑定
bind authentication vserver saml-sp-vserver -policy saml-sp-policy -priority 100
# 将sp与内容切换或负载均衡绑定...
add lb vserver lbserver HTTP 192.168.202.16 80 -persistenceType NONE -cltTimeout 180 -AuthenticationHost 192.168.202.18 -Authentication ON -authnVsName saml-sp-vserver # 添加lb虚拟服务,绑定认证到sp虚拟服务器
add service phpstudy 192.168.202.1 HTTP 80 -gslb NONE -maxClient 0 -maxReq 0 -cip DISABLED -usip NO -useproxyport YES -sp OFF -cltTimeout 180 -svrTimeout 360 -CKA NO -TCPB NO -CMP NO # 设置lb后端服务
bind lb vserver lbserver phpstudy # 为lb绑定后端服务,此时lb即可启动
漏洞挖掘
1.即然用的XML首先看是否有XXE啦
2.即然不是SP与idP直接交互,那就研究有没有必要加密(泄漏敏感信息了吗),有没有必要签名,验签的过程有问题吗?
3.除了XXE,还有种叫XML注入的攻击,如当存在有效身份信息时,可注入额外的信息让idP给它签名,从而越权,详见[1]
参考
[0] Security Assertion Markup Language (SAML) V2.0 Technical Overview
[1] SAML XML Injection -- Adam Roberts@nccgroup (2021)
LDAP
LDAP即轻量级目录访问协议(Light Directory Access Portocol),这里轻量是历史原因别管,目录是一种非关系型的数据库,用于存放树形结构的数据,可以和文件系统里的目录对应,其实像文件系统,组织人员等信息都很适合用树形来存储,这就是它的应用场景!访问指它主要用于读操作(读超快写很慢),协议就是说它不是某个具体的软件,它定义了访问目录数据的方法,数据模型的表示方式等内容。
本文只关注它在人员组织(用于认证与授权)上的用途,这在企业级应用上也炒鸡常见,就是把身份信息都放这上面实现统一密码认证啦。
基本概念
数据在LDAP中表现为树状结构,每个对象为entry,它们组成目录信息树(DIT),顶部为root,每个entry可以有0个或多个子entry,一个父entry与0个或多个兄弟entry,如下图左半部所示:

每个entry由一个或多个objectClass组成,所谓objectClass就是一组属性的集合(包含0个属性),有三种objectClass --Structural
/Abstract
/Auxiliary
类,每个entry有且只有一个Structural
来指定它的类型,其他随意,有一些公共的对象类可以直接使用,如要新增人员信息的entry则使用person
类,使用后就会根据schema填写必需的属性和可选的属性。
看完DIT组成结构后,在看其导航方式,即如何定位一个/组entry,当然是使用它定义的名称系统(Java反序列化内容开始打你了):
关键字 | 英文全称 | 含义 |
---|---|---|
dc | Domain Component | 域名组成部分,即完整的域名分成几部分后的形式,如域名为example.com变成dc=example,dc=com |
ou | Organization Unit | 组织单位,组织单位可以包含其他各种对象(包括其他组织单元) |
cn | Common Name | 公共名称 |
dn | Distinguished Name | 辨别名称,即唯一标识符,全路径名,用它唯一标识一个entry |
rdn | Relative dn | 相对辨别名,类似文件系统的相对路径,在指定父entry下使用rdn可确定子entry |
嗯,如下图:

最后一个概念就是操作,LDAP定义了多种操作类型,常用到的就是Search
/Update
/Bind
,如下表:

LDAP Server搭建
不像SAML那种SP不直接与idP交互,LDAP是CS架构,在测试时配好服务是有必要的,LDAP的实现有很多,例如windows的活动目录(AD),不过那太重了这里还是关注用OpenLDAP,它可以安装在windows/linux上,过程中可设置密码(默认secret
),之后可匿名读,要写的话需要用(User=cn=manager,dc=maxcrc,dc=com
与密码登录),若想使用命令行工具操作它,需要使用LDIF(LDAP Data Interchange Files)文件,不过这里直接使用图形化软件即可,可以用LdapAdminister/JXplorer/phpLdapadmin等...

漏洞挖掘
1.首先肯定是ldap注入啦
2.todo....
参考
[0] LDAP for Rocket Scientists
OAuth
TODO (PS:由于该协议在设备上很少见,先记两个参考链接,下次遇到再记..)
参考
[0] An offensive guide to the Authorization Code grant -- Rami McCarthy@nccgroup (2020)
[1] Creating a Safer OAuth User-Experience -- PaulYoun
[2] Hidden OAuth attack vectors -- Michael Stepankin (2021)
[3] OAuth 2.0 authentication vulnerabilities
OIDC
TODO (PS:由于该协议在设备上很少见,先记两个参考链接,下次遇到再记..)
参考
[0] Real-life OIDC Security (I): Overview -- lauritz (2020)
[1] Real-life OIDC Security (II): Login Confusion -- lauritz (2020)
[3] SSO Wars: The Token Menace -- Oleksandr Mirosh & Alvaro Muñoz (2019)
Radius
服务器搭建可使用FreeRadius,它需要运行在Linux下,可直接在ubuntu下安装二进制程序,安装后,根据文档配置:
# vim /etc/freeradius/mods-config/files/authorize
testing Cleartext-Password := "password"
# vim /etc/freeradius/clients.conf
client new {
ipaddr = 192.0.2.1 # 要使用radius的服务器(client)的地址
secret = testing123 # 密码
}
# service freeradius restart
其他鉴权问题
硬编码
硬编码是很常见的一类问题,主要是认证信息的硬编码。先把配置文件,代码等位置的字符串等数据研究一遍,是不是什么密钥,是不是每次启动/安装时随机生成的,是否可预测。硬编码是很容易出现的问题,这个也可以先通过经验制作一个关键词字典(如token/pass/secret/key...)用于自动化搜索,另外CICD等(甲方)也要注意,可能导致越权的代码泄漏
未配置
其实和硬编码类似,它是由于还没有配置该功能,但该功能又能使用,其实我还没见过但是KK说他遇到好几次了,记下来:
未配置SSO时,利用SSO -- by K4ng
失败后未终止
在判断没有权限的时候,没有正确的终止执行,如只是输出失败的信息/重定向到登录页的代码,但程序实际还在执行...
路由解析不一致
向node一般是路由和回调及中间件连接在一起的,就不容易出现这种问题,但其他语言,特别是Java中流行的模式,很容易出现功能与路由不强绑定,此时正向拦截未匹配(或匹配Public路由)并放通,则可能存在其他路由(如利用不一致问题)绕过鉴权...