mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-30 04:09:40 +08:00
GP-1314 added serialization filter to Ghidra Server
This commit is contained in:
@@ -3,5 +3,6 @@
|
|||||||
##MODULE IP: LGPL 2.1
|
##MODULE IP: LGPL 2.1
|
||||||
##MODULE IP: Tango Icons - Public Domain
|
##MODULE IP: Tango Icons - Public Domain
|
||||||
Module.manifest||GHIDRA||||END|
|
Module.manifest||GHIDRA||||END|
|
||||||
|
data/serial.filter||GHIDRA||||END|
|
||||||
os/readme.txt||GHIDRA||||END|
|
os/readme.txt||GHIDRA||||END|
|
||||||
src/main/java/ghidra/server/remote/ServerHelp.txt||GHIDRA||||END|
|
src/main/java/ghidra/server/remote/ServerHelp.txt||GHIDRA||||END|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Ghidra Server serialization filter patterns
|
||||||
|
# See java.io.ObjectInputFilter.Config#createFilter(String)
|
||||||
|
#
|
||||||
|
# This file establishes allowed and disallowed inbound class de-serialization
|
||||||
|
# rules for the Ghidra Server. If not specifically allowed or disallowed a
|
||||||
|
# de-serialized class will be subject to an internal filter which allows all
|
||||||
|
# primitive and primitive array classes while rejecting all other classes.
|
||||||
|
#
|
||||||
|
|
||||||
|
java.base/java.lang.*;
|
||||||
|
java.base/java.security.**;
|
||||||
|
java.base/javax.security.**;
|
||||||
|
java.base/sun.security.**;
|
||||||
|
java.base/java.util.**;
|
||||||
|
|
||||||
|
ghidra.framework.remote.GhidraPrincipal;
|
||||||
|
ghidra.framework.remote.AnonymousCallback;
|
||||||
|
ghidra.framework.remote.SSHSignatureCallback;
|
||||||
|
ghidra.framework.remote.SignatureCallback;
|
||||||
|
ghidra.framework.remote.User;
|
||||||
|
ghidra.framework.remote.User[];
|
||||||
|
ghidra.framework.store.CheckoutType;
|
||||||
@@ -66,7 +66,9 @@ import utility.application.ApplicationLayout;
|
|||||||
*/
|
*/
|
||||||
public class GhidraServer extends UnicastRemoteObject implements GhidraServerHandle {
|
public class GhidraServer extends UnicastRemoteObject implements GhidraServerHandle {
|
||||||
|
|
||||||
private final static String TLS_SERVER_PROTOCOLS_PROPERTY = "ghidra.tls.server.protocols";
|
private static final String SERIAL_FILTER_FILE = "serial.filter";
|
||||||
|
|
||||||
|
private static final String TLS_SERVER_PROTOCOLS_PROPERTY = "ghidra.tls.server.protocols";
|
||||||
|
|
||||||
private static SslRMIServerSocketFactory serverSocketFactory;
|
private static SslRMIServerSocketFactory serverSocketFactory;
|
||||||
private static SslRMIClientSocketFactory clientSocketFactory;
|
private static SslRMIClientSocketFactory clientSocketFactory;
|
||||||
@@ -136,7 +138,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
|||||||
* @param allowAnonymousAccess allow anonymous access if true
|
* @param allowAnonymousAccess allow anonymous access if true
|
||||||
* @param autoProvisionAuthedUsers flag to turn on automatically adding successfully
|
* @param autoProvisionAuthedUsers flag to turn on automatically adding successfully
|
||||||
* authenticated users to the user manager if they don't already exist
|
* authenticated users to the user manager if they don't already exist
|
||||||
* @throws IOException
|
* @param jaasConfigFile JAAS configuration file
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
|
* @throws CertificateException if failed to parse CA certs file used for PKI authentication
|
||||||
*/
|
*/
|
||||||
GhidraServer(File rootDir, AuthMode authMode, String loginDomain,
|
GhidraServer(File rootDir, AuthMode authMode, String loginDomain,
|
||||||
boolean allowUserToSpecifyName, boolean altSSHLoginAllowed,
|
boolean allowUserToSpecifyName, boolean altSSHLoginAllowed,
|
||||||
@@ -203,6 +207,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
|||||||
|
|
||||||
GhidraServer.server = this;
|
GhidraServer.server = this;
|
||||||
|
|
||||||
|
// Establish serialization filter to address deserialization vulnerabity concerns
|
||||||
|
setGlobalSerializationFilter();
|
||||||
|
|
||||||
// Start block stream server - use RMI serverSocketFactory
|
// Start block stream server - use RMI serverSocketFactory
|
||||||
blockStreamServer = BlockStreamServer.getBlockStreamServer();
|
blockStreamServer = BlockStreamServer.getBlockStreamServer();
|
||||||
ServerSocket streamServerSocket;
|
ServerSocket streamServerSocket;
|
||||||
@@ -855,4 +862,117 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
|||||||
return clientSocketFactory;
|
return clientSocketFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setGlobalSerializationFilter() throws IOException {
|
||||||
|
|
||||||
|
ObjectInputFilter patternFilter = readSerialFilterPatternFile();
|
||||||
|
|
||||||
|
ObjectInputFilter filter = new ObjectInputFilter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Status checkInput(FilterInfo info) {
|
||||||
|
|
||||||
|
Class<?> clazz = info.serialClass();
|
||||||
|
|
||||||
|
// Give serial filter patterns first shot
|
||||||
|
Status status = patternFilter.checkInput(info);
|
||||||
|
if (status != Status.UNDECIDED) {
|
||||||
|
if (status == Status.REJECTED) {
|
||||||
|
return serialReject(info, "failed by serial.filter pattern");
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (clazz == null) {
|
||||||
|
return Status.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> componentType = clazz.getComponentType();
|
||||||
|
if (componentType != null && componentType.isPrimitive()) {
|
||||||
|
return Status.ALLOWED; // allow all primitive arrays
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialReject(info, "not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status serialReject(FilterInfo info, String reason) {
|
||||||
|
String clientHost = RepositoryManager.getRMIClient();
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append("Rejected class serialization");
|
||||||
|
if (clientHost != null) {
|
||||||
|
buf.append(" from ");
|
||||||
|
buf.append(clientHost);
|
||||||
|
}
|
||||||
|
buf.append("(");
|
||||||
|
buf.append(reason);
|
||||||
|
buf.append(")");
|
||||||
|
|
||||||
|
Class<?> serialClass = info.serialClass();
|
||||||
|
if (serialClass != null) {
|
||||||
|
buf.append(": ");
|
||||||
|
buf.append(serialClass.getCanonicalName());
|
||||||
|
buf.append(" ");
|
||||||
|
if (serialClass.getComponentType() != null) {
|
||||||
|
buf.append("(");
|
||||||
|
buf.append("array-length=");
|
||||||
|
buf.append(info.arrayLength());
|
||||||
|
buf.append(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error(buf.toString());
|
||||||
|
return Status.REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Install global serial class filter
|
||||||
|
ObjectInputFilter.Config.setSerialFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read serial.filter file content removing any comments and newlines and generate
|
||||||
|
* corresponding {@link ObjectInputFilter}. See {@link java.io.ObjectInputFilter.Config#createFilter(String)}
|
||||||
|
* for filter syntax.
|
||||||
|
* @return serial filter content
|
||||||
|
* @throws IOException if file error occurs
|
||||||
|
*/
|
||||||
|
private static ObjectInputFilter readSerialFilterPatternFile() throws IOException {
|
||||||
|
|
||||||
|
File serialFilterFile = Application.getModuleDataFile(SERIAL_FILTER_FILE).getFile(false);
|
||||||
|
if (serialFilterFile == null) {
|
||||||
|
// jar mode not supported
|
||||||
|
throw new FileNotFoundException(SERIAL_FILTER_FILE + " not found");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
try (FileReader fr = new FileReader(serialFilterFile);
|
||||||
|
BufferedReader r = new BufferedReader(fr)) {
|
||||||
|
|
||||||
|
for (String line = r.readLine(); line != null; line = r.readLine()) {
|
||||||
|
int ix = line.indexOf('#');
|
||||||
|
if (ix >= 0) {
|
||||||
|
// strip comment
|
||||||
|
line = line.substring(0, ix);
|
||||||
|
}
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!line.endsWith(";")) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"all filter statements must end with `;`");
|
||||||
|
}
|
||||||
|
if (line.length() != 0) {
|
||||||
|
buf.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ObjectInputFilter.Config.createFilter(buf.toString());
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new IOException("Failed to parse " + SERIAL_FILTER_FILE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -17,12 +17,14 @@ package ghidra.server.remote;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import ghidra.framework.ApplicationProperties;
|
import ghidra.framework.ApplicationProperties;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
import utility.application.ApplicationLayout;
|
import utility.application.ApplicationLayout;
|
||||||
import utility.application.ApplicationUtilities;
|
import utility.application.ApplicationUtilities;
|
||||||
|
import utility.module.ModuleUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Ghidra server application layout defines the customizable elements of the Ghidra
|
* The Ghidra server application layout defines the customizable elements of the Ghidra
|
||||||
@@ -56,5 +58,10 @@ public class GhidraServerApplicationLayout extends ApplicationLayout {
|
|||||||
|
|
||||||
// User directories (don't let anything use the user home directory...there may not be one)
|
// User directories (don't let anything use the user home directory...there may not be one)
|
||||||
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
|
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
|
||||||
|
|
||||||
|
// Modules - required to find module data files
|
||||||
|
modules = ModuleUtilities.findModules(applicationRootDirs,
|
||||||
|
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>()));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.client;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InvalidClassException;
|
||||||
|
import java.rmi.RemoteException;
|
||||||
|
import java.rmi.UnmarshalException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.Callback;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
import generic.test.category.PortSensitiveCategory;
|
||||||
|
import ghidra.framework.model.ServerInfo;
|
||||||
|
import ghidra.framework.remote.GhidraServerHandle;
|
||||||
|
import ghidra.net.ApplicationKeyManagerFactory;
|
||||||
|
import ghidra.server.remote.ServerTestUtil;
|
||||||
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
|
@Category(PortSensitiveCategory.class)
|
||||||
|
public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
|
private File serverRoot;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
System.clearProperty(ApplicationKeyManagerFactory.KEYSTORE_PATH_PROPERTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
closeAllWindows();
|
||||||
|
killServer();
|
||||||
|
|
||||||
|
ClientUtil.clearRepositoryAdapter("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void killServer() {
|
||||||
|
|
||||||
|
if (serverRoot == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerTestUtil.disposeServer();
|
||||||
|
|
||||||
|
FileUtilities.deleteDir(serverRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer(int authMode, boolean altLoginName, boolean enableSSH,
|
||||||
|
boolean enableAnonymous) throws Exception {
|
||||||
|
|
||||||
|
// Create server instance
|
||||||
|
serverRoot = new File(getTestDirectoryPath(), "TestServer");
|
||||||
|
|
||||||
|
ServerTestUtil.startServer(serverRoot.getAbsolutePath(),
|
||||||
|
ServerTestUtil.GHIDRA_TEST_SERVER_PORT, authMode, altLoginName, enableSSH,
|
||||||
|
enableAnonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class BogusPrincipal implements Principal, java.io.Serializable {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
public BogusPrincipal(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Subject getBogusUserSubject() {
|
||||||
|
String username = ClientUtil.getUserName();
|
||||||
|
HashSet<BogusPrincipal> pset = new HashSet<>();
|
||||||
|
HashSet<Object> emptySet = new HashSet<>();
|
||||||
|
pset.add(new BogusPrincipal(username));
|
||||||
|
Subject subj = new Subject(false, pset, emptySet, emptySet);
|
||||||
|
return subj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSerializationFailure() throws Exception {
|
||||||
|
|
||||||
|
ServerTestUtil.setLocalUser("test");
|
||||||
|
startServer(-1, false, false, false);
|
||||||
|
|
||||||
|
ServerInfo server = new ServerInfo("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||||
|
|
||||||
|
GhidraServerHandle serverHandle = ServerConnectTask.getGhidraServerHandle(server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverHandle.getRepositoryServer(getBogusUserSubject(), new Callback[0]);
|
||||||
|
fail("serial filter rejection failed to perform");
|
||||||
|
}
|
||||||
|
catch (RemoteException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
assertTrue("expected remote unmarshall exception", cause instanceof UnmarshalException);
|
||||||
|
cause = cause.getCause();
|
||||||
|
assertTrue("expected remote invalid class exceptionn",
|
||||||
|
cause instanceof InvalidClassException);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user