mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-10-22 21:21:02 +00:00
GP-1314 added serialization filter to Ghidra Server
This commit is contained in:
parent
908135fa59
commit
89f123ab65
|
@ -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|
|
||||
|
|
22
Ghidra/Features/GhidraServer/data/serial.filter
Normal file
22
Ghidra/Features/GhidraServer/data/serial.filter
Normal 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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>()));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user