Java native agent学习笔记-从加载到log4j漏洞检测

news2025/1/19 17:12:53

记录一下java native agent的学习过程,也顺便造一个检测log4j漏洞的轮子:

java native agent相比java
agent最大的好处是快,C++写的,快的一笔,但是最大的坏处是非常麻烦,毕竟你拿个面过程的语言怼面对象的肯定是比较麻烦的。

本次学习的目的是做个加载器,动态加载agent,然后再实现检测log4j。

初始化JVM

首先我们需要给自己初始化一个jvm(进程附加到其他进程)或者用现成的jvm(dll注入到jvm里面),不管是加载jvm还是用现成的jvm,都需要获取jvm.dll的handle解析导出函数,只不过一个是”JNI_CreateJavaVM”另外一个是”JNI_GetCreatedJavaVMs”

//如果不指定路径,当是dll注入
            if (path_ == nullptr) {
                const auto jvmHandle = GetModuleHandleA("jvm.dll");
                if (jvmHandle == nullptr) {
                    isVm.store(false);
                    throw InitializationException("Could not load JVM library");
                }
                typedef jint(JNICALL* GetCreatedJavaVMs_t)(JavaVM**, jsize, jsize*);
                GetCreatedJavaVMs_t JNI_GetCreatedJavaVMs = (GetCreatedJavaVMs_t)GetProcAddress(jvmHandle, "JNI_GetCreatedJavaVMs");

                if (JNI_GetCreatedJavaVMs == NULL || JNI_GetCreatedJavaVMs(0, 0, &vmNums) != 0)
                {
                    isVm.store(false);
                    throw InitializationException("Java Virtual Machine failed during creation");
                }
                JavaVM** buffer = new JavaVM*[vmNums];
                if (JNI_GetCreatedJavaVMs(buffer, vmNums, &vmNums) != 0) {
                    isVm.store(false);
                    throw InitializationException("Java Virtual Machine failed during creation #2");
                }
                //windows每个线程只能有一个jvm
                javaVm = buffer[0];

                //delete buffer[vmNums];
            }
            else {
                HMODULE lib = ::LoadLibraryA(path_);

                if (lib == NULL)
                {
                    isVm.store(false);
                    throw InitializationException("Could not load JVM library");
                }

                CreateVm_t JNI_CreateJavaVM = (CreateVm_t) ::GetProcAddress(lib, "JNI_CreateJavaVM");

                /**
                    Is your debugger catching an error here?  This is normal.  Just continue. The JVM
                    intentionally does this to test how the OS handles memory-reference exceptions.
                 */
                if (JNI_CreateJavaVM == NULL || JNI_CreateJavaVM(&javaVm, (void**)&env, &args) != 0)
                {
                    isVm.store(false);
                    ::FreeLibrary(lib);
                    throw InitializationException("Java Virtual Machine failed during creation");
                }

一旦拿到javaVm 后,就可以拿到env了:

if (vm->GetEnv((void**)&_env, JNI_VERSION_1_2) != JNI_OK)
        {
#ifdef __ANDROID__
            if (vm->AttachCurrentThread(&_env, nullptr) != 0)
#else
            if (vm->AttachCurrentThread((void**)&_env, nullptr) != 0)
#endif
                throw InitializationException("Could not attach JNI to thread");

            _attached = true;
        }

这样我们就有当前进程的jvm的权限了

定位函数
然后我们要定位VirtualMachine这个类,用里面的attach方法加载我们的native

首先定位这个api的class:

static jclass findClass(const char* name)
{
    jclass ref = env()->FindClass(name);

    if (ref == nullptr)
    {
        env()->ExceptionClear();
        throw NameResolutionException(name);
    }

    return ref;
}

使用方法:

jni::Class virtualMachineClass = jni::Class("com/sun/tools/attach/VirtualMachine");

native
api使用java的API要预先设置所谓的signature,signature有一套命名规范,最简单的方法是用javap.exe看jar包的signature.比如VirtualMachine里面的attach就是(Ljava/lang/String;)Lcom/sun/tools/attach/VirtualMachine;

封装
简单的封装一下:

template <class TReturn, class... TArgs>
        TReturn cs_dynamic_call(const Object& obj, const char* name, const char* sig, const TArgs&... args) const {
            method_t method = getMethod(name, sig);
            return call<TReturn>(obj, method, args...);
        }

就可以随心所欲的call了

jni::Object virtualMachineObject = virtualMachineClass.cs_call<jni::Object>("attach","(Ljava/lang/String;)Lcom/sun/tools/attach/VirtualMachine;", argv[1]); //目标pid
    virtualMachineClass.cs_dynamic_call<void>(virtualMachineObject, "loadAgentPath", "(Ljava/lang/String;)V", "E:\\agent.dll");
    virtualMachineClass.cs_dynamic_call<void>(virtualMachineObject, "detach", "()V");

至此,就能动态附加一个agent了这是loader的完整代码:

#include "pch.h"
#include <assert.h>
auto main(int argc, char** argv) -> int {
    jni::Vm vm("C:\\Program Files\\Java\\jdk-16.0.2\\bin\\server\\jvm.dll");
    jni::Class virtualMachineClass = jni::Class("com/sun/tools/attach/VirtualMachine");
    jni::Object virtualMachineObject = virtualMachineClass.cs_call<jni::Object>("attach","(Ljava/lang/String;)Lcom/sun/tools/attach/VirtualMachine;", argv[1]); //目标pid
    virtualMachineClass.cs_dynamic_call<void>(virtualMachineObject, "loadAgentPath", "(Ljava/lang/String;)V", "E:\\agent.dll");
    virtualMachineClass.cs_dynamic_call<void>(virtualMachineObject, "detach", "()V");
    return 0;
}

Log4j检测

要让agent动态加载成功,需要给agent导出两个函数:

Agent_OnAttach
Agent_OnUnload

这样就行了:

JNIEXPORT auto __stdcall Agent_OnAttach(JavaVM* vm, char* options,
                                        void* reserved) -> jint {
    return Agent::Init(vm);
}
JNIEXPORT auto __stdcall Agent_OnUnload(JavaVM* vm) -> void {
    Tools::DbgPrint("Agent_OnUnload");
}

初始化回调
agent加载进来后咋办呢,看这个:

https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.htm

这里面是所有agent能触发的回调与设置列表,让我们把目光看到:

https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#ClassFileLoadHook

This event is sent when the VM obtains class file data, but before it
constructs the in-memory representation for that class. This event is also
sent when the class is being modified by the RetransformClasses function or
the RedefineClasses function, called in any JVM TI environment. The agent
can instrument the existing class file data sent by the VM to include
profiling/debugging hooks. See the description of bytecode instrumentation
for usage information.

按手册的说法,首先需要设置

Capability
jvmtiCapabilities capabilities = {0};
capabilities.can_generate_all_class_hook_events = 1;
jvmti->AddCapabilities(&capabilities);

然后设置

Event
jvmtiEventCallbacks callbacks = {0};
callbacks.ClassFileLoadHook = Callback::ClassFileLoadHook;

最后设置

EventNotificationMode
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);

然后坑来了,你用vs的cl编译器会崩溃:

1665230251_634165ab40e2138b263eb.png!small?1665230250845

我这里是换clang编译器才通过的,原因不明.不理他了

别忘了回调的定义:

auto __stdcall ClassFileLoadHook(jvmtiEnv* jvmti_env, JNIEnv* jni_env,
                                 jclass class_being_redefined, jobject loader,
                                 const char* name, jobject protection_domain,
                                 jint class_data_len,
                                 const unsigned char* class_data,
                                 jint* new_class_data_len,
                                 unsigned char** new_class_data) -> void

检测log4j
为了实现log4j的检测,我们设置一些符合的条件:

  1. 被加载的class是不是不在项目文件中(内存加载)
  2. 加载者是不是可疑加载者比如反序化常用的那些库
  3. 栈上是否有可疑的库,比如log4j

让我们一步一步实现:
被加载的class是不是不在项目文件中(内存加载)

auto findJavaClass(JNIEnv* jni_env, const char* name) -> jclass {
    jclass ref = jni_env->FindClass(name);
    if (ref == nullptr) {
        jni_env->ExceptionClear();
    }
    return ref;
}

找到为nullptr就是内存加载的东西了

加载者是不是可疑加载者比如反序化常用的那些库
为了拿到加载者的信息,我封装了一个方法…

原理跟java的

System.out.println(obj.getClass().getResource(obj.getClass().getSimpleName()+".class"));

相同,但是是面向过程

auto getClassFullPath(JNIEnv* jni_env, jobject javaObj) -> std::string {
    std::string result{};
    // getSimpleName
    jclass javaClass = jni_env->GetObjectClass(javaObj);
    jmethodID method_GetClass =
        jni_env->GetMethodID(javaClass, "getClass", "()Ljava/lang/Class;");
    jobject getClass_CallObj =
        jni_env->CallObjectMethod(javaObj, method_GetClass);
    jclass getClass_CallClass = jni_env->GetObjectClass(getClass_CallObj);
    jmethodID method_GetSimpleName = jni_env->GetMethodID(
        getClass_CallClass, "getSimpleName", "()Ljava/lang/String;");
    jstring jstring_GetSimpleName = (jstring)jni_env->CallObjectMethod(
        getClass_CallObj, method_GetSimpleName);
    const char* simpleName_StringBuffer =
        jni_env->GetStringUTFChars(jstring_GetSimpleName, nullptr);
    std::string simpleName = simpleName_StringBuffer;
    simpleName += ".class";
    jni_env->ReleaseStringUTFChars(jstring_GetSimpleName,
                                   simpleName_StringBuffer);
    // getResource
    jmethodID method_GetResource =
        jni_env->GetMethodID(getClass_CallClass, "getResource",
                             "(Ljava/lang/String;)Ljava/net/URL;");
    jstring jstring_SimpleNameBuffer =
        jni_env->NewStringUTF(simpleName.c_str());
    jobject urlObj = (jstring)jni_env->CallObjectMethod(
        getClass_CallObj, method_GetResource, jstring_SimpleNameBuffer);
    if (urlObj == nullptr) {
        // fix me
        return result;
    }
    // url_obj to string
    jclass javaUrlClass = jni_env->GetObjectClass(urlObj);
    jmethodID method_ToString =
        jni_env->GetMethodID(javaUrlClass, "toString", "()Ljava/lang/String;");
    jstring jstring_urlStringBuffer =
        (jstring)jni_env->CallObjectMethod(urlObj, method_ToString);
    const char* url =
        jni_env->GetStringUTFChars(jstring_urlStringBuffer, nullptr);
    result = url;
    jni_env->ReleaseStringUTFChars(jstring_urlStringBuffer, url);
    return result;
}

栈上是否有可疑的库
我这里给可疑库定的是:

org.apache.logging.log4j.core.lookup.JndiLookup.lookup

1665230415_6341664fbffd13b8d94e1.png!small?1665230415461

实现起来也是花了点功夫

auto getStackPackageList(JNIEnv* jni_env) -> std::vector<std::string> {
    std::vector<std::string> packageList{};
    // get StackTraceElement by Thread.currentThread().getStackTrace()
    jclass javaThreadClass = findJavaClass(jni_env, "java/lang/Thread");
    jmethodID method_CurrentThread = jni_env->GetStaticMethodID(
        javaThreadClass, "currentThread", "()Ljava/lang/Thread;");
    jobject javaThreadObj =
        jni_env->CallStaticObjectMethod(javaThreadClass, method_CurrentThread);
    jclass javaThreadObjClass = jni_env->GetObjectClass(javaThreadObj);
    jmethodID method_GetStackTrace =
        jni_env->GetMethodID(javaThreadObjClass, "getStackTrace",
                             "()[Ljava/lang/StackTraceElement;");
    jobjectArray javaStackTraceElementArray =
        (jobjectArray)jni_env->CallObjectMethod(javaThreadObj,
                                                method_GetStackTrace);
    // get StackTraceElement
    jclass javaStackTraceElementClass =
        findJavaClass(jni_env, "java/lang/StackTraceElement");
    jmethodID method_GetClassName = jni_env->GetMethodID(
        javaStackTraceElementClass, "getClassName", "()Ljava/lang/String;");
    jmethodID method_GetMethodName = jni_env->GetMethodID(
        javaStackTraceElementClass, "getMethodName", "()Ljava/lang/String;");
    /*
    jmethodID method_GetLineNumber = jni_env->GetMethodID(
    javaStackTraceElementClass, "getLineNumber", "()I");
    */
    // get StackTraceElement
    const auto javaStackTraceElementArrayLength =
        jni_env->GetArrayLength(javaStackTraceElementArray);
    for (auto i = 0; i < javaStackTraceElementArrayLength; i++) {
        jobject javaStackTraceElementObj =
            jni_env->GetObjectArrayElement(javaStackTraceElementArray, i);
        jstring jstring_ClassNameBuffer = (jstring)jni_env->CallObjectMethod(
            javaStackTraceElementObj, method_GetClassName);
        const char* className =
            jni_env->GetStringUTFChars(jstring_ClassNameBuffer, nullptr);
        jstring jstring_MethodNameBuffer = (jstring)jni_env->CallObjectMethod(
            javaStackTraceElementObj, method_GetMethodName);
        const char* methodName =
            jni_env->GetStringUTFChars(jstring_MethodNameBuffer, nullptr);
        std::string fullPackageName{};
        fullPackageName += className;
        fullPackageName += ".";
        fullPackageName += methodName;
        packageList.push_back(fullPackageName);
        // Tools::DbgPrint("fullPackageName: %s \n", fullPackageName.c_str());
        /*
        jint lineNumber =
        jni_env->CallIntMethod(javaStackTraceElementObj,
                                                method_GetLineNumber);
        Tools::DbgPrint("StackTraceElement: %s.%s:%d \n", className,
        methodName, lineNumber);
        */

        jni_env->ReleaseStringUTFChars(jstring_ClassNameBuffer, className);
        jni_env->ReleaseStringUTFChars(jstring_MethodNameBuffer, methodName);
    }
    return packageList;
}

让我们把这些组合在一起:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
namespace Agent {
namespace Callback {
auto findJavaClass(JNIEnv* jni_env, const char* name) -> jclass {
    jclass ref = jni_env->FindClass(name);
    if (ref == nullptr) {
        jni_env->ExceptionClear();
    }
    return ref;
}
auto getClassFullPath(JNIEnv* jni_env, jobject javaObj) -> std::string {
    std::string result{};
    // getSimpleName
    jclass javaClass = jni_env->GetObjectClass(javaObj);
    jmethodID method_GetClass =
        jni_env->GetMethodID(javaClass, "getClass", "()Ljava/lang/Class;");
    jobject getClass_CallObj =
        jni_env->CallObjectMethod(javaObj, method_GetClass);
    jclass getClass_CallClass = jni_env->GetObjectClass(getClass_CallObj);
    jmethodID method_GetSimpleName = jni_env->GetMethodID(
        getClass_CallClass, "getSimpleName", "()Ljava/lang/String;");
    jstring jstring_GetSimpleName = (jstring)jni_env->CallObjectMethod(
        getClass_CallObj, method_GetSimpleName);
    const char* simpleName_StringBuffer =
        jni_env->GetStringUTFChars(jstring_GetSimpleName, nullptr);
    std::string simpleName = simpleName_StringBuffer;
    simpleName += ".class";
    jni_env->ReleaseStringUTFChars(jstring_GetSimpleName,
                                   simpleName_StringBuffer);
    // getResource
    jmethodID method_GetResource =
        jni_env->GetMethodID(getClass_CallClass, "getResource",
                             "(Ljava/lang/String;)Ljava/net/URL;");
    jstring jstring_SimpleNameBuffer =
        jni_env->NewStringUTF(simpleName.c_str());
    jobject urlObj = (jstring)jni_env->CallObjectMethod(
        getClass_CallObj, method_GetResource, jstring_SimpleNameBuffer);
    if (urlObj == nullptr) {
        // fix me
        return result;
    }
    // url_obj to string
    jclass javaUrlClass = jni_env->GetObjectClass(urlObj);
    jmethodID method_ToString =
        jni_env->GetMethodID(javaUrlClass, "toString", "()Ljava/lang/String;");
    jstring jstring_urlStringBuffer =
        (jstring)jni_env->CallObjectMethod(urlObj, method_ToString);
    const char* url =
        jni_env->GetStringUTFChars(jstring_urlStringBuffer, nullptr);
    result = url;
    jni_env->ReleaseStringUTFChars(jstring_urlStringBuffer, url);
    return result;
}
auto getStackPackageList(JNIEnv* jni_env) -> std::vector<std::string> {
    std::vector<std::string> packageList{};
    // get StackTraceElement by Thread.currentThread().getStackTrace()
    jclass javaThreadClass = findJavaClass(jni_env, "java/lang/Thread");
    jmethodID method_CurrentThread = jni_env->GetStaticMethodID(
        javaThreadClass, "currentThread", "()Ljava/lang/Thread;");
    jobject javaThreadObj =
        jni_env->CallStaticObjectMethod(javaThreadClass, method_CurrentThread);
    jclass javaThreadObjClass = jni_env->GetObjectClass(javaThreadObj);
    jmethodID method_GetStackTrace =
        jni_env->GetMethodID(javaThreadObjClass, "getStackTrace",
                             "()[Ljava/lang/StackTraceElement;");
    jobjectArray javaStackTraceElementArray =
        (jobjectArray)jni_env->CallObjectMethod(javaThreadObj,
                                                method_GetStackTrace);
    // get StackTraceElement
    jclass javaStackTraceElementClass =
        findJavaClass(jni_env, "java/lang/StackTraceElement");
    jmethodID method_GetClassName = jni_env->GetMethodID(
        javaStackTraceElementClass, "getClassName", "()Ljava/lang/String;");
    jmethodID method_GetMethodName = jni_env->GetMethodID(
        javaStackTraceElementClass, "getMethodName", "()Ljava/lang/String;");
    /*
    jmethodID method_GetLineNumber = jni_env->GetMethodID(
    javaStackTraceElementClass, "getLineNumber", "()I");
    */
    // get StackTraceElement
    const auto javaStackTraceElementArrayLength =
        jni_env->GetArrayLength(javaStackTraceElementArray);
    for (auto i = 0; i < javaStackTraceElementArrayLength; i++) {
        jobject javaStackTraceElementObj =
            jni_env->GetObjectArrayElement(javaStackTraceElementArray, i);
        jstring jstring_ClassNameBuffer = (jstring)jni_env->CallObjectMethod(
            javaStackTraceElementObj, method_GetClassName);
        const char* className =
            jni_env->GetStringUTFChars(jstring_ClassNameBuffer, nullptr);
        jstring jstring_MethodNameBuffer = (jstring)jni_env->CallObjectMethod(
            javaStackTraceElementObj, method_GetMethodName);
        const char* methodName =
            jni_env->GetStringUTFChars(jstring_MethodNameBuffer, nullptr);
        std::string fullPackageName{};
        fullPackageName += className;
        fullPackageName += ".";
        fullPackageName += methodName;
        packageList.push_back(fullPackageName);
        // Tools::DbgPrint("fullPackageName: %s \n", fullPackageName.c_str());
        /*
        jint lineNumber =
        jni_env->CallIntMethod(javaStackTraceElementObj,
                                                method_GetLineNumber);
        Tools::DbgPrint("StackTraceElement: %s.%s:%d \n", className,
        methodName, lineNumber);
        */

        jni_env->ReleaseStringUTFChars(jstring_ClassNameBuffer, className);
        jni_env->ReleaseStringUTFChars(jstring_MethodNameBuffer, methodName);
    }
    return packageList;
}
auto __stdcall ClassFileLoadHook(jvmtiEnv* jvmti_env, JNIEnv* jni_env,
                                 jclass class_being_redefined, jobject loader,
                                 const char* name, jobject protection_domain,
                                 jint class_data_len,
                                 const unsigned char* class_data,
                                 jint* new_class_data_len,
                                 unsigned char** new_class_data) -> void {
    if (loader == nullptr || jni_env == nullptr) {
        return;
    }
    clock_t startTime, endTime;
    startTime = clock();
    // 是否在原有文件中
    const auto javaClass = findJavaClass(jni_env, name);

    // 检查loader的类 todo:黑名单类
    const auto loaderPath = getClassFullPath(jni_env, loader);
    if (loaderPath.size() == 0) {
        return;
    }
    // 检查栈
    const auto packageList = getStackPackageList(jni_env);
    static std::string log4jJniPackageName =
        "org.apache.logging.log4j.core.lookup.JndiLookup.lookup";
    bool lookUpJniManager = false;
    for (const auto& package : packageList) {
        if (package == log4jJniPackageName) {
            lookUpJniManager = true;
            break;
        }
    }
    endTime = clock();
    if (lookUpJniManager && javaClass == nullptr) {
        Tools::DbgPrint("suspicious package load : %s from %s by log4j \n",
                        name, loaderPath.c_str());
        Tools::DbgPrint("time: %lf ms \n",
                        (double)(endTime - startTime) / CLOCKS_PER_SEC);
    }
}
}  // namespace Callback
auto Init(JavaVM* vm) -> jint {
    jvmtiEnv* jvmti = nullptr;
    jint res = vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0);
    if (res != JNI_OK || jvmti == nullptr) {
        Tools::DbgPrint(
            "ERROR: Unable to access JVMTI Version 1, 2 or higher\n");
        return JNI_ERR;
    }
    // https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#ClassFileLoadHook
    jvmtiEventCallbacks callbacks = {0};
    callbacks.ClassFileLoadHook = Callback::ClassFileLoadHook;

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_all_class_hook_events = 1;

    jvmti->AddCapabilities(&capabilities);
    jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks));
    jvmti->SetEventNotificationMode(JVMTI_ENABLE,
                                    JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
    Tools::DbgPrint("Agent::Init\n");
    return 0;
}
}  // namespace Agent
JNIEXPORT auto __stdcall Agent_OnAttach(JavaVM* vm, char* options,
                                        void* reserved) -> jint {
    return Agent::Init(vm);
}
JNIEXPORT auto __stdcall Agent_OnUnload(JavaVM* vm) -> void {
    Tools::DbgPrint("Agent_OnUnload");
}
auto __stdcall DllMain(HMODULE hModule, DWORD ul_reason_for_call,
                       LPVOID lpReserved) -> bool {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return true;
}

测试
整了个sprinboot:

package com.example.sprinboottest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.lang.management.ManagementFactory;

@RestController
public class webpage {
    private static final Logger logger = LogManager.getLogger(webpage.class);

    @RequestMapping("/")
    public String demo(@RequestParam("a") String s){
        logger.error("str={}",s);
        return "Hello World!";
    }
}

请注意sprinboot得设置iml文件把版本改成带漏洞的版本

<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-core:2.14.1" level="project" />
    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.14.1" level="project" />

然后启动sprinboot,结合之前的加载器附加进去,触发漏洞,看看效果:

1665230479_6341668f5413e50226698.png!small?1665230478520

看起来还不错哈…

项目github

https://github.com/huoji120/log4j_detect

;

    @RequestMapping("/")
    public String demo(@RequestParam("a") String s){
        logger.error("str={}",s);
        return "Hello World!";
    }
}

请注意sprinboot得设置iml文件把版本改成带漏洞的版本

<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-core:2.14.1" level="project" />
    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.14.1" level="project" />

然后启动sprinboot,结合之前的加载器附加进去,触发漏洞,看看效果:

[外链图片转存中…(img-367tsaoS-1675412915568)]

看起来还不错哈…

项目github

https://github.com/huoji120/log4j_detect

最后

分享一个快速学习【网络安全】的方法,「也许是」最全面的学习方法:
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

在这里插入图片描述

恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k。

到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?

想要入坑黑客&网络安全的朋友,给大家准备了一份:282G全网最全的网络安全资料包免费领取!
扫下方二维码,免费领取

有了这些基础,如果你要深入学习,可以参考下方这个超详细学习路线图,按照这个路线学习,完全够支撑你成为一名优秀的中高级网络安全工程师:

高清学习路线图或XMIND文件(点击下载原文件)

还有一些学习中收集的视频、文档资源,有需要的可以自取:
每个成长路线对应板块的配套视频:


当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

因篇幅有限,仅展示部分资料,需要的可以【扫下方二维码免费领取】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/197568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

研究人员发布 VMware vRealize Log RCE 漏洞,立即打补丁

Horizon3 攻击团队的安全研究人员将于下周发布一个针对漏洞链的漏洞利用程序&#xff0c;以在未打补丁的 VMware vRealize Log Insight 设备上获得远程代码执行。 vRealize Log Insight 现在称为 VMware Aria Operations for Logs&#xff0c;它使 VMware 管理员可以更轻松地分…

每日学术速递2.4

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.LG、cs.AI 1.Neuro Symbolic Continual Learning: Knowledge, Reasoning Shortcuts and Concept Rehearsal 标题&#xff1a;神经象征性的持续学习&#xff1a;知识、推理捷径和概念排练…

必须掌握的网络安全知识

没有网络安全&#xff0c;就没有国家安全。网络安全和保密防护&#xff0c;是机关单位日常工作中不可忽视的重要问题。尤其在涉密单位工作的人员&#xff0c;因工作性质特殊&#xff0c;不仅要了解非涉密网络的安全操作常识&#xff0c;更要重点了解涉密网络的规范行为要点&…

2021年上半年信息系统项目管理师真题与答案完整版(综合知识、案例分析、论文)

1、 国家信息化体系包括六个要素&#xff0c;其中&#xff08;&#xff09;是信息化体系六要素中的龙头&#xff0c;是国家信息化建设的主阵地&#xff0c;集中体现了国家信息化建设的需求和效益。A、信息资源 B、信息技术应用 C、信息网络 D、信息化政策法规和标准规范0参考答…

若依代码生成器------数据库篇

继上一篇《若依代码自动生成器研究-表查询篇》&#xff0c;我们继续来学习若依系统中的代码生成逻辑。 导入表之Sql查询 在菜单栏点击“代码生成”&#xff0c;在右侧栏中点击“导入”按钮&#xff0c;在文章若依中的代码自动生成器研究-表查询篇中&#xff0c;我们已经一直…

三十六、Kubernetes1.25中数据存储第一篇

1、概述在前面已经提到&#xff0c;容器的生命周期可能很短&#xff0c;会被频繁地创建和销毁。那么容器在销毁时&#xff0c;保存在容器中的数据也会被清除。这种结果对用户来说&#xff0c;在某些情况下是不乐意看到的。为了持久化保存容器的数据&#xff0c;kubernetes引入了…

【深度学习】对SSD与Retina的理解

SSD 正负样本选择 同YOLO 选择与GT IOU最大的anchor作为正样本。(此时正负样本很不平衡)对于剩余未匹配anchor,将与GT IOU超过0.5的作为正样本。这样一个GT就可以匹配多个anchor,增加正样本的数量。(此时负样本依然多于正样本)hard negative mining,难负样本挖掘。将所有…

重定向的概述和使用(基于web方面),很简单

大家好&#xff0c;今天分享一下重定向的概述以及使用 我们要知道&#xff0c;重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置 同时&#xff0c;重定向有好几类 1.网页重定向、 2.域名的重定向、 3.路由选择 4. Linux上的文件重定向操作 就是要知…

QTransform的使用

目录引言基础知识缩放矩阵平移矩阵旋转矩阵矩阵乘法实际使用实现思路完整代码参考资料引言 A transformation specifies how to translate, scale, shear, rotate or project the coordinate system, and is typically used when rendering graphics. A QTransform object can …

6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals

6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals1、前言2、数据回顾3、计算候选框位置&#xff08;proposal coordinates&#xff09;4、筛选候选框&#xff08;filter proposals&#xff09;及相关处理1&#xff09;筛选出 预测概率 排前 2000 的proposals2&am…

TCP协议面试灵魂12 问(四)

005: 介绍一下 TCP 报文头部的字段 报文头部结构如下(单位为字节): 请大家牢记这张图&#xff01; 源端口、目标端口 如何标识唯一标识一个连接&#xff1f;答案是 TCP 连接的四元组——源 IP、源端口、目标 IP 和目标端口。 那 TCP 报文怎么没有源 IP 和目标 IP 呢&#x…

2021年下半年信息系统项目管理师《综合知识》《案例分析》《论文》真题与答案

1、“十四五”期间&#xff0c;我国关注推动政务信息化共建共用、推动构建网络空间命运共同体&#xff0c;属于()的建设内容.A、科技中国 B、数字中国 C、制造强国 D、创新强国0参考答案&#xff1a;B2、()关注的是业务&#xff0c;以业务驱动技术&#xff0c;强调IT与业务的对…

零基础学FPGA(八):可编程逻辑单元(基本结构,Xilinx+Altera)

目录日常唠嗑一、概述二、基于多路选择器的逻辑单元1、基于多路选择器的逻辑单元&#xff08;早期&#xff09;2、基于PLD结构的逻辑单元&#xff08;类CPLD&#xff09;3、基于查询表的逻辑单元&#xff08;目前主流&#xff09;三、Xilinx基本结构四、Altera 基本结构日常唠嗑…

Java语言还能火多久? 还能选择Java开发吗?

​​整个互联网行业“不进则退&#xff0c;慢进亦退”。对于用人要求持续增高的互联网企业来说&#xff0c;中高级Java程序员才是当下市场最紧缺的。 现在的你&#xff0c;是十年前你的决定&#xff0c;十年后的你&#xff0c;是现在你的决定。选择很重要 为什么选择Java开发…

代码随想录算法训练营第六十天_第九章_动态规划 | 647. 回文子串、516.最长回文子序列、动态规划总结篇

LeetCode 647. 回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 视频讲解https://www.bilibili.com/video/BV17G4y1y7z9/?spm_i…

Linux 进程知识总结

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Linux操作…

高阶数据结构之哈希的应用

文章目录位图&#xff08;bitMap&#xff09;位图的实现将数据添加到位图中检查数据是否在位图中存在将数据的对应位置置为0位图的应用布隆过滤器为什么会有误差布隆过滤器的实现布隆过滤器的删除使用Google下的guava组件操作布隆过滤器布隆过滤器的缺陷布隆过滤器的使用场景海…

分享69个ASP源码,总有一款适合您

分享69个ASP源码&#xff0c;总有一款适合您 69个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1XXwBL0Y0nOSel9xJVqz0Ow?pwdertw 提取码&#xff1a;ertw Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "https://d…

关于JavaScript编译原理以及作用域的深入探讨

前言 几乎所有编程语言最基本的功能之一&#xff0c;就是能够储存变量当中的值&#xff0c;并且能在之后对这个值进行访问或修改。事实上&#xff0c;正是这种储存和访问变量的值的能力将状态带给了程序。 若没有了状态这个概念&#xff0c;程序虽然也能够执行一些简单的任务…

高码率QPSK调制解调方案(2.1 QPSK调制)

2 全数字高码率QPSK调制解调软件设计 2.1 QPSK调制 2.1.1 QPSK调制原理 QPSK调制即正交相移键控信号,其信号表达式为 (1) 其中,和分别表示每比特的能量和持续