GP-1314 added serialization filter to Ghidra Server

This commit is contained in:
ghidra1 2021-09-23 10:05:24 -04:00
parent 908135fa59
commit 89f123ab65
5 changed files with 281 additions and 2 deletions

View File

@ -3,5 +3,6 @@
##MODULE IP: LGPL 2.1
##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END|
data/serial.filter||GHIDRA||||END|
os/readme.txt||GHIDRA||||END|
src/main/java/ghidra/server/remote/ServerHelp.txt||GHIDRA||||END|

View File

@ -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;

View File

@ -66,7 +66,9 @@ import utility.application.ApplicationLayout;
*/
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 SslRMIClientSocketFactory clientSocketFactory;
@ -136,7 +138,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
* @param allowAnonymousAccess allow anonymous access if true
* @param autoProvisionAuthedUsers flag to turn on automatically adding successfully
* 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,
boolean allowUserToSpecifyName, boolean altSSHLoginAllowed,
@ -203,6 +207,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
GhidraServer.server = this;
// Establish serialization filter to address deserialization vulnerabity concerns
setGlobalSerializationFilter();
// Start block stream server - use RMI serverSocketFactory
blockStreamServer = BlockStreamServer.getBlockStreamServer();
ServerSocket streamServerSocket;
@ -855,4 +862,117 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
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);
}
}
}

View File

@ -17,12 +17,14 @@ package ghidra.server.remote;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import ghidra.framework.ApplicationProperties;
import ghidra.util.SystemUtilities;
import utility.application.ApplicationLayout;
import utility.application.ApplicationUtilities;
import utility.module.ModuleUtilities;
/**
* 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)
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
// Modules - required to find module data files
modules = ModuleUtilities.findModules(applicationRootDirs,
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>()));
}
}

View File

@ -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);
}
}
}