简介
NSA的项目 Emissary 中文"密使".
地址是 https://github.com/NationalSecurityAgency/emissary
Emissary是一个基于P2P的数据驱动工作流引擎,运行在异构的、可能广泛分散的、多层的P2P计算资源网络中。
工作流行程不像传统的工作流引擎那样预先计划,而是随着数据的更多信息被发现而被确定(发现)。在Emissary工作流中通常没有用户交互,而是以面向目标的方式处理数据,直到它达到完成状态。
Emissary 是高度可配置的,但在这个"基本实现"(base implementation)中几乎什么都不做。
这个框架的用户应提供扩展emissary.place.ServiceProviderPlace
的类,以在 emissary.core.IBaseDataObject
有效负载上执行工作。
可以做各种各样的事情,工作流是分阶段管理的,例如:STUDY, ID, COORDINATE, TRANSFORM, ANALYZE, IO, REVIEW.
emissary.core.MobileAgent
是负责指挥工作流的类、和从它派生的类,它们管理一组相关的负载对象的路径通过工作流。
通过工作流管理一组相关的"负载对象"(payload objects)的路径。
emissary.directory.DirectoryPlace
管理可用的"服务"(services)、它们的成本和质量,并保持 P2P 网络的连接。
参考https://securitylab.github.com/research/NSA-emissary/
不安全的反序列化(CVE-2021-32634) 漏洞1
第一个。
位于WorkSpaceClientEnqueueAction
REST端点中。
可见WorkSpaceClientEnqueueAction
类的实现:
public class WorkSpaceClientEnqueueAction {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/*
* <!-- Put a file on a work PickUpPlaceClient queue --> <Use-Case source="*" action="/WorkSpaceClientEnque.action">
* <Work type="Bean" target="emissary.comms.http.worker.LogWorker"/> <Work type="Bean"
* target="emissary.comms.http.worker.WorkSpaceClientEnqueWorker"/> <View status="0" view="/success.jsp"/> <View
* status="-1" view="/error.jsp"/> </Use-Case>
*/
// TODO This is an initial crack at the new endpoint, I haven't seen it called an am unsure when/if it does
@POST
@Path("/WorkSpaceClientEnqueue.action")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
public Response workspaceClientEnqueue(@FormParam(WorkSpaceAdapter.CLIENT_NAME) String clientName,
@FormParam(WorkSpaceAdapter.WORK_BUNDLE_OBJ) String workBundleString) {
logger.debug("TPWorker incoming execute! check prio={}", Thread.currentThread().getPriority());
// TODO Doesn't look like anything is actually calling this, should we remove this?
final boolean success;
try {
// Look up the place reference
final String nsName = KeyManipulator.getServiceLocation(clientName);
final IPickUpSpace place = (IPickUpSpace) Namespace.lookup(nsName);
if (place == null) {
throw new IllegalArgumentException("No client place found using name " + clientName);
}
final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(workBundleString.getBytes("8859_1")));
WorkBundle paths = (WorkBundle) ois.readObject();
success = place.enque(paths);
} catch (Exception e) {
logger.warn("WorkSpaceClientEnqueWorker exception", e);
return Response.serverError().entity("WorkSpaceClientEnqueWorker exception:\n" + e.getMessage()).build();
}
if (success) {
// old success from WorkSpaceClientEnqueWorker
// return WORKER_SUCCESS;
return Response.ok().entity("Successful add to the PickUpPlaceClient queue").build();
} else {
// old failure from WorkSpaceClientEnqueWorker
// return new WorkerStatus(WorkerStatus.FAILURE, "WorkSpaceClientEnqueWorker failed, queue full");
return Response.serverError().entity("WorkSpaceClientEnqueWorker failed, queue full").build();
}
}
可以通过对 的经过身份验证的 POST 请求访问此端点/WorkSpaceClientEnqueue.action
。
正如上面源代码中所看到的,表单参数WorkSpaceAdapterWORK_BUNDLE_OBJ
(即tpObj) 在第52行 被解码 并 反序列化。(可以看到readObject
)
也就是最关键的这一行final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(workBundleString.getBytes("8859_1")));
幸运的是,这是一个身份验证后的问题(post-authentication issue),由于SonarSource报告修复了跨站请求伪造(CSRF)漏洞,因此无法通过CSRF代表已登录用户利用该漏洞。
CodeQL还报告了另外2个不安全的反序列化操作,这些操作目前没有在代码中执行。然而,它们是定时炸弹,可能在未来的版本中启用,因此安全实验室团队也报告了它们。
第一个起源于MoveToAction
类,它没有被Jersey server公开。
正如在comment中描述的:
// TODO This is an initial crack at the new endpoint, I haven’t seen it called an am unsure when/if it does
public Response moveTo(@Context HttpServletRequest request)
final MoveToAdapter mt = new MoveToAdapter();
final boolean status = mt.inboundMoveTo(request);
...
MoveToAdapter:
public boolean inboundMoveTo(final HttpServletRequest req)
final MoveToRequestBean bean = new MoveToRequestBean(req);
MoveToRequestBean(final HttpServletRequest req)
final String agentData = RequestUtil.getParameter(req, AGENT_SERIAL);
setPayload(agentData);
this.payload = PayloadUtil.deserialize(s);
...
PayloadUtil:
ois = new ObjectInputStream(new ByteArrayInputStream(s.getBytes("8859_1")));
不安全的反序列化(CVE-2021-32634) 漏洞2
第二个方法来源于WorkSpaceAdapter
类的inboundEnque
方法。
该漏洞需要调用inboundEnque()
,但目前尚未执行该调用。
WorkspaceAdapter类:
见https://github.com/NationalSecurityAgency/emissary/blob/2e7939f3540f47f0374e77f083af8a657023de35/src/main/java/emissary/server/mvc/adapters/WorkSpaceAdapter.java
/**
* Process the enque coming remotely over HTTP request params onto the specified (local) pickup client place
*/
public boolean inboundEnque(final HttpServletRequest req) throws NamespaceException {
logger.debug("TPA incoming elements! check prio={}", Thread.currentThread().getPriority());
// Parse parameters
final EnqueRequestBean bean = new EnqueRequestBean(req);
// Look up the place reference
final String nsName = KeyManipulator.getServiceLocation(bean.getPlace());
final IPickUpSpace place = lookupPlace(nsName);
if (place == null) {
throw new IllegalArgumentException("No client place found using name " + bean.getPlace());
}
return place.enque(bean.getPaths());
}
WorkspaceAdapter:
EnqueRequestBean(final HttpServletRequest req) {
setPlace(RequestUtil.getParameter(req, CLIENT_NAME));
if (getPlace() == null) {
throw new IllegalArgumentException("No 'place' specified");
}
setPaths(RequestUtil.getParameter(req, WORK_BUNDLE_OBJ));
}
WorkspaceAdapter:
/**
* Sets the WorkBundle object from serialized data
*/
void setPaths(final String s) {
try {
final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(s.getBytes("8859_1")));
this.paths = (WorkBundle) ois.readObject();
} catch (Exception e) {
logger.error("Cannot deserialize WorkBundle using {} bytes", s.length(), e);
throw new IllegalArgumentException("Cannot deserialize WorkBundle");
}
}
可以看到readObject
修复与总结
不安全的反序列化会造成安全隐患。