淺談被動式 IAST 產品與技術實現
作者:iiusky@墨雲科技VLab Team
筆者曾參與RASP研究與研發得到一些相關經驗,近兩年觀察到IAST發展勢頭明顯,但目前國內外對於IAST具體實現的細節相關文章較少,且筆者看到的開源IAST僅有洞態,故想通過筆者視角,對IAST的原理及技術實現進行探究及分享。
本文僅代表筆者個人觀點,歡迎大家進行技術交流及學習。
什麼是IAST
IAST是AST其中的一個類別,AST是Application Security Testing的簡稱,譯為應用安全測試,在其之下衍生出來以下幾種類型:
- SAST(Static Application Security Testing)靜態應用安全測試
- DAST(Dynamic Application Security Testing)動態應用安全測試
- MAST(Mobile Application Security Testing)移動應用安全測試
- IAST (Interactive Application Security Testing)交互式應用安全測試
對於IAST的定義我並沒有在Gartner找到其相關的術語表,但是在Gartner推薦的服務商中找到了一些關於IAST的定義,核心內容如下:
IAST使用運行時代理方法在測試階段分析與監控應用程序的行為。這種類型的測試也不測試整個應用程序或代碼,而只測試執行功能的部分。
有趣的是,多數人認為IAST是Gartner2012年提出來的術語,但我在Gartner的術語表內並沒有找到IAST的相關定義(可能由於Gartner之前改版,導致這個術語丟失),於是我在Gartner推薦的服務商中找到了IAST相關的標籤和簡單的介紹。
關於IAST的細分,可參考以下文章 http://www.freebuf.com/sectool/290671.html 這篇文章對IAST的分類有比較清晰的描述。本文以下內容主要圍繞被動式IAST進行分析介紹。
國內外IAST產品
筆者對國內外的IAST相關的產品公司進行了一些整理,內容如下(該數據不代表所有的IAST廠商,僅為筆者搜索到的部分廠商):
被動式IAST要想實現,那麼其實和RASP差別不大,區別主要集中在埋點檢測,從而達到對調用鏈的精準跟蹤,在這一細小部分,我個人的理解是,對所有有可能導致source獲取到的參數進行改變的方法進行埋點,包括但不限於類似以下幾種情況(下面僅是偽代碼,並不代表真實邏輯中的代碼,僅便於大家理解):
new String(....) "aa".replace(...) StringBuilder sb = new StringBuilder(); Base64.decode(...)
此鏈路需根據實際業務情況進行完善,例如實現某個加解密的類等,又或者是加入對souce進行安全過濾處理的方法,然後將所有經過預埋點的堆棧信息進行拼接,在這個過程中,可以去判斷這條鏈路經過了安全過濾處理方法,那麼或許可以粗暴的不上報這條調用鏈信息,認為這是一個安全的請求(當然這種情況還是要謹慎,畢竟研發中難免會犯一些錯誤,所以在情況允許的環境下,還是全部上報,交給人工進行復驗、排除是更為妥當的解決方式),然後將數據上報到服務端,到此完成一個IAST的技術理念邏輯。
那麼其實是不是可以使用一些APM的開源技術,對它進行改造,從而實現IAST的部分功能。如果想深度控制IAST的流程,更好的方式就是自己實現一套IAST埋點、檢測邏輯。
實現IAST所需要的一些技術
如果想要從零實現一個被動式的IAST,我們至少需要掌握關於字節碼操作的技術,例如ASM、Javassist等,若不想從零或底層的方式去實現,可以試試使用AspectJ技術,或結合使用開源APM框架進行改造,讓其成為一個簡單的被動IAST。
本次所涉及的Demo源碼已經公開,Github項目為: iiiusky/java_iast_example。
實驗環境搭建
這次IAST相關的環境其實和之前的RASP環境基本差不多。大家可以參照之前的淺談RASP技術攻防之實戰[環境配置篇]文章內容去搭建一個本地的實驗環境,唯一變的,可能就是包名了。
demo整體邏輯
這次實驗的整體邏輯如果相比真正的IAST,肯定會有很多缺少的細節部分完善,所以僅僅適合用來學習瞭解被動IAST實現的大致流程,整體邏輯圖如下:
從上圖可以看到,其實在這次demo實現的過程中,邏輯也並不是很複雜,大致文字版説明如下:
http->enterHttp->enterSource->leaveSource enterPropagator->leavePropagator(…………此過程重複n次…………) enterSink->leaveSink(可省略)->leaveHttp
以上大致完成了整個污點跟蹤鏈路流程,在初始化HTTP的時候,將新建一個 LinkedList
類型的對象,用來存儲線程鏈路調用的數據。
package cn.org.javaweb.iast.visitor; import org.objectweb.asm.MethodVisitor; /** * @author iiusky - 03sec.com */ public interface Handler { MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, String name, String desc, String signature, String[] exceptions); }
實現Http埋點
為了方便對不同類型的點進行適配,抽象了一個 Handler
出來,然後在根據不同的類型實現具體的 ClassVisitorHandler
內容, Handler.java
具體代碼如下:
在Java EE中通過劫持 javax.servlet.Servlet
的 service
方法和 javax.servlet.Filter
類的 doFilter
方法不但可以獲取到原始的 HttpServletRequest
和 HttpServletResponse
對象,還可以控制Servlet和Filter的程序執行邏輯。
可以將所有參數描述符為 (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V
的方法進行插入埋點,並緩存request、response對象。
實現的代碼如下(示例代碼為了便於理解未考慮異常處理):
package cn.org.javaweb.iast.visitor.handler; import cn.org.javaweb.iast.visitor.Handler; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import java.lang.reflect.Modifier; /** * @author iiusky - 03sec.com */ public class HttpClassVisitorHandler implements Handler { private static final String METHOD_DESC = "(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V"; public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, String name, String desc, String signature, String[] exceptions) { if ("service".equals(name) && METHOD_DESC.equals(desc)) { final boolean isStatic = Modifier.isStatic(access); final Type argsType = Type.getType(Object[].class); System.out.println( "HTTP Process 類名是: " + className + ",方法名是: " + name + "方法的描述符是:" + desc + ",簽名是:" + signature + ",exceptions:" + exceptions); return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { @Override protected void onMethodEnter() { loadArgArray(); int argsIndex = newLocal(argsType); storeLocal(argsIndex, argsType); loadLocal(argsIndex); if (isStatic) { push((Type) null); } else { loadThis(); } loadLocal(argsIndex); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Http", "enterHttp", "([Ljava/lang/Object;)V", false); } @Override protected void onMethodExit(int i) { super.onMethodExit(i); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Http", "leaveHttp", "()V", false); } }; } return mv; } }
上面的代碼將對所有實現 javax.servlet.Servlet#service 的方法進行了埋點處理(接口、抽象類除外),真正編譯到jvm中的類如下:
可以看到,在對進入方法的時候調用了IAST中的方法 cn.org.javaweb.iast.core.Http#enterHttp
,在離開方法的時候,調用了 cn.org.javaweb.iast.core.Http#leaveHttp
其中 enterHttp
具體代碼如下:
public static void enterHttp(Object[] objects) { if (!haveEnterHttp()) { IASTServletRequest request = new IASTServletRequest(objects[0]); IASTServletResponse response = new IASTServletResponse(objects[1]); RequestContext.setHttpRequestContextThreadLocal(request, response, null); } }
從上文中可以看到,傳入的 HttpServletRequest
和 HttpServletResponse
對象存到了當前線程的上下文中,方便後續對數據的調取使用。
leaveHttp
具體代碼如下:
public static void leaveHttp() { IASTServletRequest request = RequestContext.getHttpRequestContextThreadLocal() .getServletRequest(); System.out.printf("URL : %s \n", request.getRequestURL().toString()); System.out.printf("URI : %s \n", request.getRequestURI().toString()); System.out.printf("QueryString : %s \n", request.getQueryString().toString()); System.out.printf("HTTP Method : %s \n", request.getMethod()); RequestContext.getHttpRequestContextThreadLocal().getCallChain().forEach(item -> { if (item.getChainType().contains("leave")) { String returnData = null; if (item.getReturnObject().getClass().equals(byte[].class)) { returnData = new String((byte[]) item.getReturnObject()); } else if (item.getReturnObject().getClass().equals(char[].class)) { returnData = new String((char[]) item.getReturnObject()); } else { returnData = item.getReturnObject().toString(); } System.out .printf("Type: %s CALL Method Name: %s CALL Method Return: %s \n", item.getChainType(), item.getJavaClassName() + item.getJavaMethodName(), returnData); } else { System.out .printf("Type: %s CALL Method Name: %s CALL Method Args: %s \n", item.getChainType(), item.getJavaClassName() + item.getJavaMethodName(), Arrays.asList(item.getArgumentArray())); } }); }
從當前線程中獲取到在調用 enterHttp
時候存的數據,對其中的數據進行可視化的輸出打印。
實現Source埋點
在Java EE中通過可以劫持獲取輸入源的所有方法,比如常用的 getParameter
、 getHeader
等類似的方法,在這裏將對調用的方法、以及返回的參數進行跟蹤,這裏為真正污點跟蹤的起點。可以簡單的理解為就是http各個get方法即為來源,但這一結論不保證完全適配所有情況。對於Source相關的點處理的代碼如下(示例代碼為了便於理解未考慮異常處理):
package cn.org.javaweb.iast.visitor.handler; import cn.org.javaweb.iast.visitor.Handler; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import java.lang.reflect.Modifier; /** * @author iiusky - 03sec.com */ public class SourceClassVisitorHandler implements Handler { private static final String METHOD_DESC = "(Ljava/lang/String;)Ljava/lang/String;"; public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, final String name, final String desc, String signature, String[] exceptions) { if (METHOD_DESC.equals(desc) && "getParameter".equals(name)) { final boolean isStatic = Modifier.isStatic(access); System.out.println("Source Process 類名是: " + className + ",方法名是: " + name + "方法的描述符是:" + desc + ",簽名是:" + signature + ",exceptions:" + exceptions); return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { @Override protected void onMethodEnter() { loadArgArray(); int argsIndex = newLocal(Type.getType(Object[].class)); storeLocal(argsIndex, Type.getType(Object[].class)); loadLocal(argsIndex); push(className); push(name); push(desc); push(isStatic); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Source", "enterSource", "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false); super.onMethodEnter(); } @Override protected void onMethodExit(int opcode) { Type returnType = Type.getReturnType(desc); if (returnType == null || Type.VOID_TYPE.equals(returnType)) { push((Type) null); } else { mv.visitInsn(Opcodes.DUP); } push(className); push(name); push(desc); push(isStatic); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Source", "leaveSource", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false); super.onMethodExit(opcode); } }; } return mv; } }
以上代碼的邏輯,只是簡單的對於 getParameter
進行了埋點處理,讓其調用IAST的處理邏輯,編譯到JVM的Class內容如下:
可以看到,在進入方法後調用了 cn.org.javaweb.iast.core.Source#enterSource
,具體內容如下:
public static void enterSource(Object[] argumentArray, String javaClassName, String javaMethodName, String javaMethodDesc, boolean isStatic) { if (haveEnterHttp()) { CallChain callChain = new CallChain(); callChain.setChainType("enterSource"); callChain.setArgumentArray(argumentArray); callChain.setJavaClassName(javaClassName); callChain.setJavaMethodName(javaMethodName); callChain.setJavaMethodDesc(javaMethodDesc); callChain.setStatic(isStatic); RequestContext.getHttpRequestContextThreadLocal().addCallChain(callChain); } }
對參數、類名、方法名、描述符等信息添加到了callChain中. 在方法結束前獲取了返回值,並且調用了 cn.org.javaweb.iast.core.Source#leaveSource
方法,將返回值傳入了進去,那麼在處理的時候,就將其結果放到了 callChain.returnObject
。
實現Propagator埋點
傳播點的選擇是非常關鍵的,傳播點規則覆蓋的越廣得到的傳播鏈路就會更清晰。比如簡單粗暴的對 String
、 Byte
等類進行埋點,因為中間調用這些類的太多了,所以可能導致一個就是結果堆棧太長,不好對調用鏈進行分析,但是對於傳播點的選擇,可以更精細化一些去做選擇,比如 Base64
的 decode
、 encode
也可以作為傳播點進行埋點,以及執行命令的 java.lang.Runtime#exec
也是可以作為傳播點的,因為最終執行命令是最底層在不同系統封裝的調用執行命令JNI方法的類,如 java.lang.UNIXProcess
等,所以將 java.lang.Runtime#exec
作為傳播點也是一個選擇。為了方便演示污點傳播的效果,對 Base64
的 decode
以及 encode
和 java.lang.Runtime
進行了埋點處理,具體實現代碼如下(示例代碼為了便於理解未考慮異常處理):
package cn.org.javaweb.iast.visitor.handler; import cn.org.javaweb.iast.visitor.Handler; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import java.lang.reflect.Modifier; /** * @author iiusky - 03sec.com */ public class PropagatorClassVisitorHandler implements Handler { private static final String METHOD_DESC = "(Ljava/lang/String;)[B"; private static final String CLASS_NAME = "java.lang.Runtime"; @Override public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, final String name, final String desc, String signature, String[] exceptions) { if ((name.contains("decode") && METHOD_DESC.equals(desc)) || CLASS_NAME.equals(className)) { final boolean isStatic = Modifier.isStatic(access); final Type argsType = Type.getType(Object[].class); if (((access & Opcodes.ACC_NATIVE) == Opcodes.ACC_NATIVE) || className .contains("cn.org.javaweb.iast")) { System.out.println( "Propagator Process Skip 類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc); } else { System.out .println("Propagator Process 類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc); return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { @Override protected void onMethodEnter() { loadArgArray(); int argsIndex = newLocal(argsType); storeLocal(argsIndex, argsType); loadLocal(argsIndex); push(className); push(name); push(desc); push(isStatic); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Propagator", "enterPropagator", "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false); super.onMethodEnter(); } @Override protected void onMethodExit(int opcode) { Type returnType = Type.getReturnType(desc); if (returnType == null || Type.VOID_TYPE.equals(returnType)) { push((Type) null); } else { mv.visitInsn(Opcodes.DUP); } push(className); push(name); push(desc); push(isStatic); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Propagator", "leavePropagator", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false); super.onMethodExit(opcode); } }; } } return mv; } }
真正運行在JVM中的類如下:
java.util.Base64$Decoder#decode
java.lang.Runtime
可以看到其實也是在方法進入後和方法離開前插入了IAST的代碼邏輯,以便直觀的觀察到入參值以及返回值發生的變化。
實現Sink埋點
對於Sink點的選擇,其實和找RASP最終危險方法的思路一致,只限找到危險操作真正觸發的方法進行埋點即可,比如 java.lang.UNIXProcess#forkAndExec
方法,這種給 java.lang.UNIXProcess#forkAndExec
下點的方式太底層,如果不想這麼底層,也可以僅對 java.lang.ProcessBuilder#start
方法或者 java.lang.ProcessImpl#start
進行埋點處理。本次實驗選擇了對 java.lang.ProcessBuilder#start
進行埋點處理,具體實現代碼如下(示例代碼為了便於理解未考慮異常處理):
package cn.org.javaweb.iast.visitor.handler; import cn.org.javaweb.iast.visitor.Handler; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import java.lang.reflect.Modifier; /** * @author iiusky - 03sec.com */ public class SinkClassVisitorHandler implements Handler { private static final String METHOD_DESC = "()Ljava/lang/Process;"; @Override public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, final String name, final String desc, String signature, String[] exceptions) { if (("start".equals(name) && METHOD_DESC.equals(desc))) { final boolean isStatic = Modifier.isStatic(access); final Type argsType = Type.getType(Object[].class); System.out.println("Sink Process 類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc); return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { @Override protected void onMethodEnter() { loadArgArray(); int argsIndex = newLocal(argsType); storeLocal(argsIndex, argsType); loadThis(); loadLocal(argsIndex); push(className); push(name); push(desc); push(isStatic); mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Sink", "enterSink", "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false); super.onMethodEnter(); } }; } return mv; } }
在這次實驗中,選擇了對所有方法名為 start
且方法描述為 ()Ljava/lang/Process;
的類進行埋點,其實也就是對 java.lang.ProcessBuilder#start
進行埋點處理。最終運行在JVM中的class如下:
在方法進去後調用了IAST的 cn.org.javaweb.iast.core.Sink#enterSink
方法,以此來確定一個調用鏈是否已經到達危險函數執行點。對於Sink,除了整體處理邏輯與 Propagator
以及 Source
相似,多了一個 setStackTraceElement
的操作,目的是將在觸發 Sink
點的堆棧將其保存下來,方便後面使用分析。具體代碼如下:
public static void enterSink(Object[] argumentArray, String javaClassName, String javaMethodName, String javaMethodDesc, boolean isStatic) { if (haveEnterHttp()) { CallChain callChain = new CallChain(); callChain.setChainType("enterSink"); callChain.setArgumentArray(argumentArray); callChain.setJavaClassName(javaClassName); callChain.setJavaMethodName(javaMethodName); callChain.setJavaMethodDesc(javaMethodDesc); callChain.setStatic(isStatic); callChain.setStackTraceElement(Thread.currentThread().getStackTrace()); RequestContext.getHttpRequestContextThreadLocal().addCallChain(callChain); } }
結果驗證
全部實現完成後,寫一個jsp來執行命令試試看,代碼如下:
該JSP接收一個參數,然後對該參數進行base64解碼後傳入Runtime.exec中來執行命令,最後輸出執行結果。
<%@ page import="java.io.InputStream" %> <%@ page import="java.util.Base64" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <pre> <% String sb = request.getParameter("cmd"); byte[] decode = Base64.getDecoder().decode(sb); Process process = Runtime.getRuntime().exec(new String(decode)); InputStream in = process.getInputStream(); int a = 0; byte[] b = new byte[1024]; while ((a = in.read(b)) != -1) { out.println(new String(b, 0, a)); } in.close(); %> </pre>
接着編譯agent,將其加入到tomcat的啟動命令中,部署jsp頁面,訪問結果。
可見,首先觸發了 getParameter
方法中的Source埋點,傳入的參數為 cmd
,獲取到的結果為 CHdK
,接着連續觸發了5次Propagator點。
第一次觸發的Propagator點位於 Base64
類中 decode
方法,傳入的參數是 CHdK
,返回值為 pwd
(原始返回為[]byte,為了方便展示,對其轉為了字符串),這時候已經可以初步看到了參數的獲取到base64解碼,也就是原始source點已經發生了變化。
第二次觸發的埋點信息為獲取一個 Runtime
對象,調用的是 java.lang.Runtime#getRuntime
,傳入的參數為空,返回的結果為一個Runtime的對象信息,其實就是實例化了一個 java.lang.Runtime
對象,這次可以觀察到一個小細節,就是這個返回對象發生了變化,但是並沒有傳入任何參數。
第三次觸發的埋點信息為調用 java.lang.Runtime#exec
方法(接收參數類型為: String
),傳入的值是 pwn
,在這次調用中可以看到,第一次Propagator點的返回值作為了入參傳入了這次調用,但是緊接着並觸發沒有想象中的 leavePropagator
方法,而是調用了另一個 exec
方法。
第四次觸發的埋點信息為調用 java.lang.Runtime#exec
方法(接收參數類型為: String、String[]、File
),其中第一個參數的值為 pwn
,而其它參數為 null
(本文不討論如何確定第幾個參數是污染點的問題,這個可以通過加規則去逐步完善)。在這次調用中可以看到,第三次中傳遞過來的 pwn
沒有發生變化,然而也沒有觸發 leavePropagator
方法,由此可以推測出來這個方法內部繼續調用了在規則裏面預先匹配到的方法。
第五次觸發的埋點信息為調用 java.lang.Runtime#exec
方法(接收參數類型為: String[]、String[]、File
),傳入的值是 [[Ljava.lang.String;@58ed07d8, null, null]
,這時候就看到了在傳入的值由 pwn
變為了一個 String
數組類型的對象,返回到第四次觸發的埋點看,其實就可以看到 var
6其實是最開始是由 var1
,也就是入參值 pwn
轉換得到的。然後可以看到在當前調用的方法裏面,又調用了規則中的Sink點( java.lang.ProcessBuilder#start
)方法。
以上就是大概從Srouce點( getParameter
),經過中間的Propagator點( java.util.Base64$Decoder#decode、java.lang.Runtime#getRuntime、java.lang.Runtime#exec
)到最終Sink點( java.lang.ProcessBuilder#start
)的整體大概流程了。
總結
在本次實驗中,將 java.lang.Runtime
作為了傳播點,其實在整體流程訪問結束後,這個傳播點才會有返回值返回回來,他是在傳播的過程中調用到了Sink點。
那麼對於這種情況,是否應該摒棄將 java.lang.Runtime
作為傳播點呢?這其實應該就是仁者見仁智者見智了,對於整體IAST的流程,其實和RASP流程差不多,但是對於傳播點的選擇,目前大家更多的是基於規則(正則or繼承類)判斷去覆蓋其中的傳播鏈,或者更簡單粗暴的對 String
、 Byte
進行埋點,但是需要處理的細節也就更多了,以及對於在整條鏈路中的無用調用也需要處理。是否有一種一勞永逸的辦法可以完整的拿到整條污點傳播鏈路,從而拋棄基於規則的對傳播點進行人為覆蓋,這個可能就需要進行更加深入的研究了。
在這次實現的demo中,並沒有結合真正業務去實現,以及IAST的其它功能點去展開研究,比如流量重放、SCA、污點在方法中的參數位置等功能。如果僅僅是想融入DevSecOps中,可以基於開源的APM項目實現一個簡易的IAST,根據具體的一些公司開發規範,去定製一些規則點,來減少因為某些問題導致的誤報情況。
參考鏈接
- 機器學習——監督學習&無監督學習
- XAI/MLI 可解釋機器學習系列1- 開源&paper彙總
- Bumblebee 惡意軟件帶着新感染技術迴歸
- erlang-distribution-protocol 安全問題研究
- CVE-2022-34916 Apache Flume 遠程代碼執行漏洞分析
- 淺談被動式 IAST 產品與技術實現
- 404星鏈計劃 | 新收錄3個安全團隊的開源工具!速看!
- CVE-2021-34866 Linux 內核提權漏洞分析
- 從DynamoDB 2022 Paper回看Lindorm的一些設計
- 極智Paper | MAUM 直面跨模態行人re-ID
- 基於圖卷積神經網絡的 web 登錄入口識別方法
- Mirai 惡意軟件變體 MooBot 瞄準 D-Link 設備
- Xalan-J XSLT 整數截斷漏洞利用構造(CVE-2022-34169)
- 一種快速的無代替模型的黑盒對抗攻擊算法 SurFree
- 消息稱華為將推 MatePad Paper 典藏版和 MateStation S 十二代酷睿版
- 新款華為 MatePad Paper 墨水平板入網,有望與 Mate50 系列一同發佈
- 深度乾貨!一篇 Paper 帶您讀懂 HTAP | StoneDB 學術分享會第①期
- Ruby 安全漫談
- Chrome 沙箱繞過研究
- Paper2GUI - 開源免費的“AI 黑科技”工具箱!十幾款上手即用的人工智能軟件合集