mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-10-06 04:59:23 +08:00
Merge remote-tracking branch
'origin/GP-6796_ghidra1_GhidraURLAllowListInterface' into Ghidra_12.2
This commit is contained in:
@@ -122,6 +122,19 @@ localhost interface only.
|
||||
|
||||
See Ghidra GUI Help Content related to BSim Database Configuration and `bsim_ctl` for more details.
|
||||
|
||||
### Ghidra Client - Server Allow List
|
||||
|
||||
Ghidra client-side applications will now impose the use of a __Server Allow List__ mechanism to
|
||||
help mitigate unintended server access. This mechanism is currently used to restrict:
|
||||
|
||||
- Ghidra Server URL connections to unknown servers. Explicit repository access via a shared project
|
||||
will cause that server to be implicitly added to the __Server Allow List__, and
|
||||
- Clicking on URL links (e.g., http/https) within Ghidra listing comment annotations.
|
||||
|
||||
See analyzeHeadlessREADME.md for information related to use of __analyzeHeadless__ and the new
|
||||
__support/updateServerAllowList__ command which can be used to manage the __Server Allow List__
|
||||
entries.
|
||||
|
||||
## BSim PostgreSQL Deployment and Control (bsim_ctl)
|
||||
|
||||
Extensive changes have been made to the BSim PostgreSQL control script. New `bsim_ctl` commands
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
hyperlink.</P>
|
||||
<!-- Annotation Example -->
|
||||
|
||||
<P>The following text shows the syntax of a sample URL annotation:</P>
|
||||
<P>The following text shows the syntax of a sample HTTP URL annotation:</P>
|
||||
<pre><font size="4"><br>
|
||||
<b>{@<i>url</i></b> "<i>http://www.google.com</i>"</b> "Search Web"<b>}</b><br>
|
||||
<b>{@<i>url</i></b> "<i>https://www.google.com</i>"</b> "Search Web"<b>}</b><br>
|
||||
</font><br></pre>
|
||||
|
||||
<P>The bold text is required for all annotations. The italicized text is required but is
|
||||
@@ -38,7 +38,7 @@
|
||||
<!-- image -->
|
||||
|
||||
<P align="center"><IMG border="0" src="images/CommentDialogURLExample.png" alt=""><BR>
|
||||
<I>URL Annotation Example</I></P>
|
||||
<I>HTTP URL Annotation Example</I></P>
|
||||
|
||||
<P>The image above shows a URL annotation in its text form as entered into the EOL Comment
|
||||
tab of the Comments dialog.<BR>
|
||||
@@ -46,11 +46,13 @@
|
||||
The image below shows how the annotation is rendered in Ghidra.</P>
|
||||
|
||||
<P align="center"><IMG border="2" src="images/RenderedURLExample.png" alt=""><BR>
|
||||
<I>Rendered URL Annotation Example</I></P>
|
||||
<I>Rendered HTTP URL Annotation Example</I></P>
|
||||
|
||||
<P>When the URL text (e.g., "http://www.google.com") in the above image is clicked from within
|
||||
<P>When the URL text (e.g., "https://www.google.com") in the above image is clicked from within
|
||||
Ghidra, a web browser is launched and attempts to load the corresponding web page. </P>
|
||||
|
||||
<I>GHIDRA URL Annotation Example</I></P>
|
||||
|
||||
<P>If the URL text corresponds to a Ghidra URL and attempt will be made to open the referenced
|
||||
Program file within the Code Browser. Such a URL may refer to a Program file from a
|
||||
local project or Ghidra Server. The Ghidra URL forms supported include:</P>
|
||||
@@ -64,6 +66,14 @@
|
||||
<i>ghidra:/[<project-path>/]<project-name>?/<program-path>[#<address-or-symbol-ref>]</i><BR>
|
||||
Example: <i>ghidra:/share/MyProject?/notepad.exe#entry</i>
|
||||
</P>
|
||||
|
||||
<P><IMG border="0" src="images/warning.help.png" alt="Note"> Clicking on all remote URL annotations will be
|
||||
will be subject to the <b>Server Allow List</b> resulting in a possible confirmation dialog.
|
||||
This is independent of possible SSL/TLS server authentication which may be required.
|
||||
At anytime the <b>Server Allow List</b> may be cleared via the Project window
|
||||
<b>Edit->Clear Server Allow List...</b> or managed using the <I>support/updateServerAllowList</I> shell script.
|
||||
Execute this script without arguments to see usage information.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Valid Annotations</H2>
|
||||
@@ -197,7 +207,7 @@
|
||||
|
||||
<TD valign="top" width="12%">
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>URL</LI>
|
||||
<LI>HTTP/GHIDRA URL</LI>
|
||||
|
||||
<LI>[display text]<BR>
|
||||
</LI>
|
||||
@@ -219,11 +229,11 @@
|
||||
|
||||
<TD valign="top" width="25%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>{@url "http://www.google.com"}</LI>
|
||||
<LI>{@url "https://www.google.com"}</LI>
|
||||
|
||||
<LI>{@url "http://www.google.com" "google"}</LI>
|
||||
<LI>{@url "https://www.google.com" "google"}</LI>
|
||||
|
||||
<LI>{@url "http://www.google.com" "click here for google"}</LI>
|
||||
<LI>{@url "https://www.google.com" "click here for google"}</LI>
|
||||
|
||||
<LI>{@url "ghidra://myserver/Repo/notepad.exe"}</LI>
|
||||
|
||||
@@ -355,6 +365,10 @@
|
||||
quotes (") around content inside of the annotation tag, excluding the <B>@<I>name</I></B>
|
||||
part of the tag. Further, some annotations require quotes, as listed in the table above
|
||||
(e.g., the <B>Execute</B> annotation requires quotes). It is considered good practice to
|
||||
quote all annotation parameter values.</P><P><IMG border="0" src="images/warning.help.png" alt="Note"> All annotations support double
|
||||
quotes (") around content inside of the annotation tag, excluding the <B>@<I>name</I></B>
|
||||
part of the tag. Further, some annotations require quotes, as listed in the table above
|
||||
(e.g., the <B>Execute</B> annotation requires quotes). It is considered good practice to
|
||||
quote all annotation parameter values.</P>
|
||||
|
||||
|
||||
|
||||
+33
-9
@@ -98,8 +98,6 @@
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3> </H3>
|
||||
|
||||
<H3><A name="Manage_Certificates"></A>Manage Certificates (Windows and macOS only) </H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
@@ -125,18 +123,18 @@
|
||||
When you connect to the server the next time you run Ghidra, you will be prompted for the
|
||||
key-store password associated with this certificate key file. The path to your PKI
|
||||
certificate file is saved as part of your Ghidra preferences.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/note.png" border="0"> If the Ghidra Server, or other server,
|
||||
is not using PKI Certificates for user authentication, you can ignore this menu option
|
||||
since the certificate keystore will not be used.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/note.png" border="0"> Specifying the single user certificate
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> Setting your user PKI certificate key store
|
||||
may also be required if Ghidra communicates with other web services which rely on PKI user
|
||||
authentication.</P>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> Specifying the single user certificate
|
||||
keystore in this fashion will prevent the OS managed keystore from being used
|
||||
(applied to Windows and macOS only).</P>
|
||||
(applies to Windows and macOS only).</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Clear_PKI_Certificate"></A>Clear PKI Certificate </H3>
|
||||
@@ -149,6 +147,32 @@
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Clear_Server_Allow_List"></A>Server Allow List </H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>When attempting to communicate with various servers based upon a URL, you may be
|
||||
prompted to either allow or disallow the connection and all future connection attempts.
|
||||
The choice will be saved to a <I>Server Allow List</I>. A server entry is identified
|
||||
by the associated communication protocol (e.g., "https", "ghidra"), its host name
|
||||
as specified by a URL and the associated TCP port. The "ghidra" protocol is only
|
||||
identified with its base-port (e.g., 13100) and not the other two related ports.
|
||||
If you change your mind about a server connection it may be neccessary to revise this
|
||||
saved <I>Server Allow List</I>. Two options exist for altering this list:</P>
|
||||
|
||||
<UL>
|
||||
<LI>To clear the entire Server Allow List from the Ghidra GUI Project Window,
|
||||
choose <B>Edit<IMG src="help/shared/arrow.gif" border="0">Clear Server Allow List...</B>.</LI>
|
||||
|
||||
<LI>From a system command prompt, the <B>updateServerAllowList</B> command
|
||||
can be used to view and selectively modify the allow-list. This command can
|
||||
be found within the Ghidra installation <B>support</B> directory.
|
||||
Server Allow List entries may be displayed with the <I>-list</I>
|
||||
option, and added or modified using the <I>-allow or -disallow</I> options.
|
||||
Entries may also removed entirely by using the <I>-clear or -clearAll</I> option.</LI>
|
||||
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Exit_Ghidra"></A>Exiting Ghidra</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
@@ -25,6 +25,7 @@ import ghidra.*;
|
||||
import ghidra.app.util.importer.LibrarySearchPathManager;
|
||||
import ghidra.app.util.opinion.Loader;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.protocol.ghidra.Handler;
|
||||
import ghidra.util.Msg;
|
||||
@@ -65,6 +66,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
PASSWORD("-p", false),
|
||||
COMMIT("-commit", false, "[\"<comment>\"]]"),
|
||||
OK_TO_DELETE("-okToDelete", false),
|
||||
ALLOW_ALL_ACCESS("-allowAllAccess", false),
|
||||
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
|
||||
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
|
||||
LOADER(Loader.COMMAND_LINE_ARG_PREFIX, true, "<desired loader name>"),
|
||||
@@ -186,6 +188,16 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
HeadlessOptions options = analyzer.getOptions();
|
||||
parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport);
|
||||
|
||||
// Ensure that we do not rely on prompting user for allowing server access
|
||||
if (options.allowAllAccess) {
|
||||
Msg.warn(AnalyzeHeadless.class,
|
||||
"All remote server access is Allowed (" + Arg.ALLOW_ALL_ACCESS + ")");
|
||||
ClientUtil.setAllowListProvider(new AllowAllUrlAllowListProvider());
|
||||
}
|
||||
else {
|
||||
ClientUtil.setAllowListProvider(new DefaultlUrlAllowListProvider());
|
||||
}
|
||||
|
||||
Msg.info(AnalyzeHeadless.class,
|
||||
"Headless startup complete (" + GhidraLauncher.getMillisecondsFromLaunch() + " ms)");
|
||||
ClassSearcher.logStatistics();
|
||||
@@ -199,6 +211,11 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(HeadlessAnalyzer.class,
|
||||
"Abort due to error: " + e.getMessage());
|
||||
System.exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(HeadlessAnalyzer.class,
|
||||
"Abort due to Headless analyzer error: " + e.getMessage(), e);
|
||||
@@ -412,6 +429,9 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
else if (checkArgument(Arg.OK_TO_DELETE, args, argi)) {
|
||||
options.setOkToDelete(true);
|
||||
}
|
||||
else if (checkArgument(Arg.ALLOW_ALL_ACCESS, args, argi)) {
|
||||
options.setAllowAllAccess(true);
|
||||
}
|
||||
else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) {
|
||||
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
|
||||
}
|
||||
@@ -584,4 +604,24 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
private boolean isExistingArg(String s) {
|
||||
return Arrays.stream(Arg.values()).anyMatch(e -> e.matches(s));
|
||||
}
|
||||
|
||||
private static class AllowAllUrlAllowListProvider implements UrlAllowListProvider {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
return true; // do not cache decision
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultlUrlAllowListProvider extends AbstractUrlAllowListProvider {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
Boolean allowed = accessAllowed(url);
|
||||
if (allowed != null) {
|
||||
return allowed;
|
||||
}
|
||||
return false; // do not cache decision
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ import ghidra.app.util.importer.ProgramLoader;
|
||||
import ghidra.app.util.opinion.*;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
@@ -259,10 +258,8 @@ public class HeadlessAnalyzer {
|
||||
throws IOException, MalformedURLException, URISyntaxException {
|
||||
|
||||
if (options.readOnly && options.commit) {
|
||||
Msg.error(this,
|
||||
"Abort due to Headless analyzer error: The requested readOnly option is in conflict " +
|
||||
throw new IllegalArgumentException("The requested readOnly option is in conflict " +
|
||||
"with the commit option");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"ghidra".equals(ghidraURL.getProtocol())) {
|
||||
@@ -270,9 +267,8 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
|
||||
if (GhidraURL.isLocalURL(ghidraURL)) {
|
||||
Msg.error(this,
|
||||
throw new IllegalArgumentException(
|
||||
"Ghidra URL command form does not supported local project URLs (ghidra:/path...)");
|
||||
return;
|
||||
}
|
||||
|
||||
String path = ghidraURL.getPath();
|
||||
@@ -293,6 +289,22 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.allowAllAccess) {
|
||||
// Check Server Allow List - add access if not already blocked
|
||||
Boolean hasServerAccess = UrlAllowListManager.getAccess(ghidraURL);
|
||||
if (hasServerAccess == null) {
|
||||
ServerSpecification serverSpec = ServerSpecification.get(ghidraURL);
|
||||
Msg.info(HeadlessAnalyzer.class,
|
||||
"NOTICE: Adding server to allow list: " + serverSpec.toString());
|
||||
UrlAllowListManager.updateAccess(ghidraURL, true);
|
||||
}
|
||||
else if (!hasServerAccess) {
|
||||
ServerSpecification serverSpec = ServerSpecification.get(ghidraURL);
|
||||
throw new IOException(
|
||||
"Access denied by server allow list: " + serverSpec.toString());
|
||||
}
|
||||
}
|
||||
|
||||
BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
|
||||
bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
|
||||
try {
|
||||
|
||||
@@ -95,6 +95,10 @@ public class HeadlessOptions {
|
||||
// -p
|
||||
boolean allowPasswordPrompt;
|
||||
|
||||
// -allowAllAccess - Server Allow List will allow all remote server access, otherwise
|
||||
// access may be restricted based upon previously allowed server access.
|
||||
boolean allowAllAccess;
|
||||
|
||||
// -commit
|
||||
boolean commit;
|
||||
String commitComment;
|
||||
@@ -143,6 +147,7 @@ public class HeadlessOptions {
|
||||
keystore = null;
|
||||
connectUserID = null;
|
||||
allowPasswordPrompt = false;
|
||||
allowAllAccess = false;
|
||||
commit = false;
|
||||
commitComment = null;
|
||||
okToDelete = false;
|
||||
@@ -392,6 +397,17 @@ public class HeadlessOptions {
|
||||
this.analyze = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote Ghidra Server access relies on Server Allow List established by GUI application.
|
||||
* This method can be used to allow all access and ignore Server Allow List.
|
||||
*
|
||||
* @param allowAccess True if all server access should be allowed, otherwise rely on
|
||||
* Allow List previously cached by GUI application.
|
||||
*/
|
||||
public void setAllowAllAccess(boolean allowAccess) {
|
||||
this.allowAllAccess = allowAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language and compiler spec from the provided input. Any null value will attempt
|
||||
* a "best-guess" if possible.
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/* ###
|
||||
* 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.app.util.headless;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.protocol.ghidra.Handler;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* {@link UpdateServerAllowList} utility for managing the Server Allow List.
|
||||
* See {@link UrlAllowListManager}.
|
||||
*/
|
||||
public class UpdateServerAllowList implements GhidraLaunchable {
|
||||
|
||||
private static final String INVOCATION_NAME_PROPERTY = "UpdateServerAllowList.Name";
|
||||
|
||||
public UpdateServerAllowList() {
|
||||
// Required for GhidraLaunchable
|
||||
}
|
||||
|
||||
private URL parseURL(String urlString) throws MalformedURLException {
|
||||
URI uri = URI.create(urlString);
|
||||
return uri.toURL();
|
||||
}
|
||||
|
||||
private void checkMoreArgs(int currentArgIndex, String[] args) {
|
||||
if (currentArgIndex == args.length - 1) {
|
||||
usage(args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) throws IOException {
|
||||
Application.initializeApplication(layout, new ApplicationConfiguration());
|
||||
if (args.length == 0) {
|
||||
usage(args);
|
||||
}
|
||||
|
||||
// NOTE: May need other protocol handlers to be registered to avoid URL exceptions
|
||||
Handler.registerHandler();
|
||||
|
||||
try {
|
||||
boolean printList = false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
switch (arg) {
|
||||
|
||||
case "-allow":
|
||||
checkMoreArgs(i, args);
|
||||
URL url = parseURL(args[++i]);
|
||||
UrlAllowListManager.updateAccess(url, true);
|
||||
break;
|
||||
|
||||
case "-disallow":
|
||||
checkMoreArgs(i, args);
|
||||
url = parseURL(args[++i]);
|
||||
UrlAllowListManager.updateAccess(url, false);
|
||||
break;
|
||||
|
||||
case "-clear":
|
||||
checkMoreArgs(i, args);
|
||||
url = parseURL(args[++i]);
|
||||
UrlAllowListManager.clearAccessEntry(url);
|
||||
break;
|
||||
|
||||
case "-clearAll":
|
||||
UrlAllowListManager.clearAll();
|
||||
break;
|
||||
|
||||
case "-list":
|
||||
printList = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (printList) {
|
||||
Map<ServerSpecification, AccessRecord> accessMap =
|
||||
UrlAllowListManager.getAccessMap();
|
||||
List<ServerSpecification> servers = new ArrayList<>(accessMap.keySet());
|
||||
if (servers.isEmpty()) {
|
||||
System.out.println("Server Allow List is empty.");
|
||||
}
|
||||
else {
|
||||
Collections.sort(servers);
|
||||
System.out.println("Server Allow List:");
|
||||
for (ServerSpecification svr : servers) {
|
||||
|
||||
AccessRecord accessRecord = accessMap.get(svr);
|
||||
String access = accessRecord.accessAllowed() ? "ALLOW " : "DISALLOW";
|
||||
Date date = new Date(accessRecord.time());
|
||||
|
||||
System.out.println(
|
||||
" " + access + " " + svr.toUrlString() + " (" + date + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error("Exception processing Allow List updates", e);
|
||||
}
|
||||
System.out.println("Done");
|
||||
}
|
||||
|
||||
private static void usage(String[] args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.err.println("arg " + i + ": " + args[i]);
|
||||
}
|
||||
String invocationName = System.getProperty(INVOCATION_NAME_PROPERTY);
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("\nUsage: ");
|
||||
buf.append(
|
||||
invocationName != null ? invocationName : UpdateServerAllowList.class.getSimpleName());
|
||||
buf.append(
|
||||
" [-allow <protocol>://<hostname>:<port>] [-disallow <protocol>://<hostname>:<port>] [-clear <protocol>://<hostname>:<port>] [-clearAll] [-list]\n");
|
||||
System.err.println(buf.toString());
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package ghidra.app.util.task;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramLocator;
|
||||
@@ -177,16 +176,20 @@ public class ProgramOpener {
|
||||
msg += "Please contact the Ghidra team for assistance.";
|
||||
Msg.showError(this, null, "Error Opening " + filename, msg);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (domainFile.isInWritableProject() && (e instanceof IOException)) {
|
||||
RepositoryAdapter repo = domainFile.getParent().getProjectData().getRepository();
|
||||
catch (IOException e) {
|
||||
RepositoryAdapter repo = domainFile.getParent().getProjectData().getRepository();
|
||||
if (repo != null && domainFile.isInWritableProject()) {
|
||||
ClientUtil.handleException(repo, e, "Open File", null);
|
||||
}
|
||||
else {
|
||||
Msg.showError(this, null, "Error Opening " + filename,
|
||||
"Getting domain object failed.\n" + e.getMessage(), e);
|
||||
"Getting domain object failed.\n" + e.getMessage());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Error Opening " + filename,
|
||||
"Getting domain object failed.\n" + e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+47
-4
@@ -16,11 +16,14 @@
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -33,9 +36,24 @@ import ghidra.util.Msg;
|
||||
* The first string will be treated as a Java {@link URL} and the optional second string will
|
||||
* be treated as display text. If there is not display text, then the URL will be
|
||||
* displayed.
|
||||
* <p>
|
||||
* See {@link GhidraServerURLAnnotatedStringHandler} and {@link GhidraLocalURLAnnotatedStringHandler}
|
||||
* for GHIDRA URL dummy handlers that are used to facilitate supported Comment Editor annotation types.
|
||||
*/
|
||||
public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
|
||||
private static final Set<String> allowedProtocols = new TreeSet<>();
|
||||
static {
|
||||
// Set maintains alphabetical order or protocols
|
||||
// The 'ghidra' protocol must be included here since only one shared
|
||||
// annotation handler is used to process all supported URL protocols.
|
||||
allowedProtocols.add("ghidra");
|
||||
allowedProtocols.add("http");
|
||||
allowedProtocols.add("https");
|
||||
}
|
||||
|
||||
private static String allowedProtocolsStr = "ghidra, https or http";
|
||||
|
||||
private static final String INVALID_SYMBOL_TEXT =
|
||||
"@url annotation must have a URL string optionally followed by a display string";
|
||||
|
||||
@@ -53,7 +71,15 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
URL url = getURLForString(text[1]);
|
||||
|
||||
if (url == null) {
|
||||
return new AttributedString("Invalid URL annotations - not a URL: " + text[1],
|
||||
return new AttributedString("Invalid URL annotation - not a valid URL: " + text[1],
|
||||
Messages.ERROR, prototypeString.getFontMetrics(0), false, Messages.ERROR);
|
||||
}
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
if (!allowedProtocols.contains(protocol)) {
|
||||
return new AttributedString(
|
||||
"Unsupported URL annotation protocol - " + allowedProtocolsStr + " required:\n" +
|
||||
text[1],
|
||||
Messages.ERROR, prototypeString.getFontMetrics(0), false, Messages.ERROR);
|
||||
}
|
||||
|
||||
@@ -91,28 +117,45 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
String urlString = annotationParts[1];
|
||||
URL url = getURLForString(urlString);
|
||||
if (url != null) {
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
if (!allowedProtocols.contains(protocol)) {
|
||||
Msg.showError(this, null, "URL Access Not Allowed",
|
||||
"Unsupported URL annotation protocol - " + allowedProtocolsStr +
|
||||
" required:\n\n" +
|
||||
urlString);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ClientUtil.getAllowListProvider().isAllowed(url)) {
|
||||
Msg.showError(this, null, "URL Access Not Allowed",
|
||||
"Access denied by Server Allow List");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GhidraURL.PROTOCOL.equals(url.getProtocol())) {
|
||||
ProgramManager programManager = serviceProvider.getService(ProgramManager.class);
|
||||
return programManager.openProgram(url, ProgramManager.OPEN_CURRENT) != null;
|
||||
}
|
||||
|
||||
BrowserLoader.display(url, null, serviceProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
Msg.showError(this, null, "Invalid URL",
|
||||
"Unable to create a Java URL object from string: " + urlString);
|
||||
"Invalid URL annotation - not a valid URL: " + urlString);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "URL";
|
||||
return "HTTP-URL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@url http://www.example.com}";
|
||||
return "{@url https://www.example.com}";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,8 +21,7 @@ import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import docking.options.OptionsService;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
@@ -36,15 +35,6 @@ import ghidra.framework.plugintool.ServiceProvider;
|
||||
*/
|
||||
public class BrowserLoader {
|
||||
|
||||
/**
|
||||
* Display the content specified by url in a web browser window. This call will launch
|
||||
* a new thread and then immediately return.
|
||||
* @param url The URL to show.
|
||||
*/
|
||||
public static void display(URL url) {
|
||||
display(url, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the content specified by url in a web browser window. This call will launch
|
||||
* a new thread and then immediately return.
|
||||
@@ -57,6 +47,8 @@ public class BrowserLoader {
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(serviceProvider, "serviceProvider instance is required");
|
||||
|
||||
// open the browser in a new thread because the call may block
|
||||
(new Thread(new BrowserRunner(url, fileURL, serviceProvider))).start();
|
||||
@@ -65,12 +57,7 @@ public class BrowserLoader {
|
||||
private static void displayFromBrowserRunner(URL url, URL fileURL,
|
||||
ServiceProvider serviceProvider) {
|
||||
try {
|
||||
if (serviceProvider == null) {
|
||||
displayBrowserForExternalURL(url);
|
||||
}
|
||||
else {
|
||||
displayBrowser(url, fileURL, serviceProvider);
|
||||
}
|
||||
displayBrowser(url, fileURL, serviceProvider);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(BrowserLoader.class, null, "Error Loading Browser",
|
||||
@@ -78,15 +65,6 @@ public class BrowserLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static void displayBrowserForExternalURL(URL url) throws Exception {
|
||||
String[] arguments =
|
||||
generateCommandArguments(url, null,
|
||||
ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions());
|
||||
Process p = Runtime.getRuntime().exec(arguments);
|
||||
p.waitFor();
|
||||
p.exitValue(); // thought to help memory problems on some versions of windows
|
||||
}
|
||||
|
||||
private static void displayBrowser(URL url, URL fileURL, ServiceProvider serviceProvider) {
|
||||
OptionsService service = serviceProvider.getService(OptionsService.class);
|
||||
ToolOptions options =
|
||||
|
||||
+2
-2
@@ -135,8 +135,8 @@ public class ManualViewerCommandWrappedOption implements CustomOption {
|
||||
option.setFileFormat(DEFAULT_URL_REPLACEMENT_STRING);
|
||||
}
|
||||
else if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.MAC_OS_X) {
|
||||
option.setCommandString("open");
|
||||
option.setCommandArguments(new String[] {});
|
||||
option.setCommandString("anaconda-navigator");
|
||||
option.setCommandArguments(new String[] { "--url" });
|
||||
option.setFileFormat(DEFAULT_URL_REPLACEMENT_STRING);
|
||||
}
|
||||
else {
|
||||
|
||||
+39
-27
@@ -21,11 +21,13 @@ import java.net.URL;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.go.ipc.GhidraGoListener;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
@@ -50,49 +52,59 @@ public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
protected void dispose() {
|
||||
projectClosed();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
private void processUrl(URL url) {
|
||||
|
||||
URL projectUrl = GhidraURL.getProjectURL(url);
|
||||
Msg.info(this, "GhidraGo accepting the resource at " + projectUrl);
|
||||
FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
|
||||
|
||||
// Check for case where server access has already been blocked to
|
||||
// launching tool and then failing to access program.
|
||||
if (!ClientUtil.getAllowListProvider().isAllowed(url)) {
|
||||
Msg.showError(this, frontEndTool.getActiveWindow(), "URL Access Not Allowed",
|
||||
"Access denied by Server Allow List:\n" + projectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runLater(() -> {
|
||||
frontEndTool.toFront();
|
||||
frontEndTool.accept(url);
|
||||
});
|
||||
}
|
||||
|
||||
private void projectOpened() {
|
||||
projectClosed();
|
||||
try {
|
||||
listener = new GhidraGoListener((url) -> processUrl(url));
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "GhidraGoPlugin Exception",
|
||||
"Unable to create GhidraGoListener", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void projectClosed() {
|
||||
if (this.listener != null) {
|
||||
listener.dispose();
|
||||
listener = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof ProjectPluginEvent) {
|
||||
if (((ProjectPluginEvent) event).getProject() == null) {
|
||||
dispose();
|
||||
projectClosed();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
listener = new GhidraGoListener((url) -> {
|
||||
accept(url);
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "GhidraGoPlugin Exception",
|
||||
"Unable to create Listener", e);
|
||||
}
|
||||
projectOpened();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the given url, which is then passed to the FrontEndTool to process.
|
||||
* @param url a {@link GhidraURL}
|
||||
* @return true if handled successfully, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean accept(URL url) {
|
||||
Msg.info(this, "GhidraGo accepting the resource at " + GhidraURL.getProjectURL(url));
|
||||
FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
|
||||
frontEndTool.toFront();
|
||||
return frontEndTool.accept(url);
|
||||
}
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/* ###
|
||||
* 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 java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AbstractUrlAllowListProvider} provides the abstract URL allow list provider.
|
||||
* <p>
|
||||
* NOTE: See {@link UrlAllowListManager} for persistent allow list which will be consulted before
|
||||
* prompting user and updated if user allows access.
|
||||
*/
|
||||
public abstract class AbstractUrlAllowListProvider implements UrlAllowListProvider {
|
||||
|
||||
/**
|
||||
* Determine if access has previously been determined and return access state.
|
||||
* This method will return true for opaque or non-server URLs.
|
||||
*
|
||||
* @param url server URL
|
||||
* @return true if access allowed or URL type is not handled by allow list, false
|
||||
* if access is disallowed, or null if no allow list entry exists.
|
||||
*/
|
||||
protected static Boolean accessAllowed(URL url) {
|
||||
try {
|
||||
if (url.toURI().isOpaque() || StringUtils.isEmpty(url.getAuthority())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Unsupported URL: " + url, e);
|
||||
}
|
||||
|
||||
return UrlAllowListManager.getAccess(url.getProtocol(), url.getHost(), getPort(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port to be accessed by the specified URL. If not specified, the default port will
|
||||
* be returned.
|
||||
*
|
||||
* @param url server URL
|
||||
* @return URL port
|
||||
*/
|
||||
protected static int getPort(URL url) {
|
||||
int port = url.getPort();
|
||||
if (port < 0) {
|
||||
port = url.getDefaultPort();
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the base URL form which only includes protocol, server and port.
|
||||
*
|
||||
* @param url server URL
|
||||
* @return simplified base URL (e.g., ghidra://host/repo )
|
||||
* @throws IllegalArgumentException if URL does not specify an authority
|
||||
*/
|
||||
protected static String getBaseURL(URL url) throws IllegalArgumentException {
|
||||
|
||||
if (url.getAuthority() == null) {
|
||||
throw new IllegalArgumentException("Invalid remote server URL: " + url);
|
||||
}
|
||||
|
||||
int port = url.getPort();
|
||||
if (port < 0) {
|
||||
port = url.getDefaultPort();
|
||||
}
|
||||
|
||||
return url.getProtocol() + "://" + url.getHost() + ":" + port;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* ###
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@link AccessRecord} provides the URL Allow List access record used by
|
||||
* {@link UrlAllowListManager}.
|
||||
*
|
||||
* @param accessAllowed true if access is allowed, false if disallowed
|
||||
* @param time date and time when change was made (milliseconds since January 1, 1970, 00:00:00 GMT).
|
||||
* @see java.lang.System#currentTimeMillis()
|
||||
*/
|
||||
public record AccessRecord(boolean accessAllowed, long time) {}
|
||||
@@ -18,6 +18,7 @@ package ghidra.framework.client;
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.URL;
|
||||
import java.rmi.*;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
@@ -42,6 +43,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
public class ClientUtil {
|
||||
|
||||
private static UrlAllowListProvider allowListProvider;
|
||||
private static ClientAuthenticator clientAuthenticator;
|
||||
|
||||
private static Hashtable<ServerInfo, RepositoryServerAdapter> serverHandles = new Hashtable<>();
|
||||
@@ -49,6 +51,42 @@ public class ClientUtil {
|
||||
private ClientUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client allow list provider for GhidraURL connections.
|
||||
* @param provider allow list provider
|
||||
*/
|
||||
public static synchronized void setAllowListProvider(
|
||||
UrlAllowListProvider provider) {
|
||||
allowListProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently installed GhidraURL allow list provider. If one has not been
|
||||
* installed, this will trigger the installation of a default instance. The provider
|
||||
* facilitates prompting the user if supported and returning a final allow or deny
|
||||
* indication when accessing a URL.
|
||||
*
|
||||
* @return current allow list provider
|
||||
*/
|
||||
public static synchronized UrlAllowListProvider getAllowListProvider() {
|
||||
if (allowListProvider == null) {
|
||||
if (SystemUtilities.isInTestingMode()) {
|
||||
// When testing disable the use of allow list.
|
||||
// Specific tests can always set a specific provider if needed.
|
||||
setAllowListProvider(new AbstractUrlAllowListProvider() {
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
setAllowListProvider(new DefaultGhidraUrlAllowListProvider());
|
||||
}
|
||||
}
|
||||
return allowListProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client authenticator
|
||||
* @param authenticator client authenticator instance
|
||||
@@ -65,7 +103,7 @@ public class ClientUtil {
|
||||
* installed, this will trigger the installation of a default instance.
|
||||
* @return current client authenticator
|
||||
*/
|
||||
public static ClientAuthenticator getClientAuthenticator() {
|
||||
public static synchronized ClientAuthenticator getClientAuthenticator() {
|
||||
if (clientAuthenticator == null) {
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
setClientAuthenticator(new HeadlessClientAuthenticator());
|
||||
@@ -83,6 +121,10 @@ public class ClientUtil {
|
||||
* prompted for a password via a Swing dialog. If a previous connection
|
||||
* attempt to this server failed, the adapter may be returned in a
|
||||
* disconnected state.
|
||||
* <p>
|
||||
* NOTE: Requesting a Ghidra Server adapter using this method will automatically
|
||||
* add the server to the Server Allow List.
|
||||
*
|
||||
* @param host server name or address
|
||||
* @param port server port, 0 indicates that default port should be used.
|
||||
* @return repository server adapter
|
||||
@@ -95,6 +137,10 @@ public class ClientUtil {
|
||||
* Connect to a Repository Server and obtain a handle to it.
|
||||
* Based upon the server authentication requirements, the user may be
|
||||
* prompted for a password via a Swing dialog.
|
||||
* <p>
|
||||
* NOTE: Requesting a Ghidra Server adapter using this method will automatically
|
||||
* add the server to the Server Allow List.
|
||||
*
|
||||
* @param host server name or address
|
||||
* @param port server port, 0 indicates that default port should be used.
|
||||
* @param forceConnect if true and the server adapter is disconnected, an
|
||||
@@ -225,7 +271,7 @@ public class ClientUtil {
|
||||
excMsg = exc.toString();
|
||||
}
|
||||
if (exc instanceof IOException) {
|
||||
Msg.showError(ClientUtil.class, parent, title, excMsg, exc);
|
||||
Msg.showError(ClientUtil.class, parent, title, excMsg);
|
||||
}
|
||||
else {
|
||||
// show the stacktrace for non-IOException
|
||||
@@ -328,6 +374,13 @@ public class ClientUtil {
|
||||
static RemoteRepositoryServerHandle connect(ServerInfo server)
|
||||
throws LoginException, GeneralSecurityException, IOException, CancelledException {
|
||||
|
||||
// Check for explicitly denied server connections
|
||||
Boolean access =
|
||||
UrlAllowListManager.getAccess("ghidra", server.getServerName(), server.getPortNumber());
|
||||
if (access != null && !access) {
|
||||
throw new NotConnectedException("Access denied by Server Allow List");
|
||||
}
|
||||
|
||||
getClientAuthenticator();
|
||||
boolean allowLoginRetry = (clientAuthenticator instanceof DefaultClientAuthenticator);
|
||||
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
/**
|
||||
* {@link DefaultGhidraUrlAllowListProvider} is an allow list provider for cases where
|
||||
* the user should be prompted when access has not yet been decided.
|
||||
* <p>
|
||||
* Prompting uses {@code stdin/stdout} when {@link SystemUtilities#isInHeadlessMode()} returns true,
|
||||
* otherwise Swing GUI is used via {@link OptionDialog} popup.
|
||||
*/
|
||||
public class DefaultGhidraUrlAllowListProvider extends AbstractUrlAllowListProvider {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
|
||||
Boolean allowed = accessAllowed(url);
|
||||
if (allowed != null) {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
String prompt = "Allow server access to " + getBaseURL(url) + " (y|n): ";
|
||||
allowed = getConsoleYesNoUserResponse(prompt);
|
||||
}
|
||||
else {
|
||||
int resp = OptionDialog.showOptionDialog(null, "Verify Server Access",
|
||||
"Allow server access to " + getBaseURL(url) +
|
||||
"?\nThis decision will be retained for subsequent access decisions.",
|
||||
"&Yes", "&No", OptionDialog.QUESTION_MESSAGE);
|
||||
if (resp == OptionDialog.CANCEL_OPTION) {
|
||||
return false; // disallow connection without updating allow list
|
||||
}
|
||||
allowed = (resp == OptionDialog.OPTION_ONE);
|
||||
}
|
||||
|
||||
UrlAllowListManager.updateAccess(url, allowed);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
private boolean getConsoleYesNoUserResponse(String prompt) {
|
||||
|
||||
try {
|
||||
boolean closed = false;
|
||||
while (!closed) {
|
||||
|
||||
// Flush any stale stdin data before prompting for response
|
||||
while (System.in.available() > 0) {
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
// Prompt user on stdout
|
||||
System.out.print(prompt);
|
||||
|
||||
// Read response line from stdin
|
||||
StringBuilder buf = new StringBuilder();
|
||||
while (true) {
|
||||
int c = System.in.read();
|
||||
if (c <= 0) {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
if (c == '\r' || c == '\n') {
|
||||
break;
|
||||
}
|
||||
buf.append((char) c);
|
||||
}
|
||||
|
||||
// Check for yes/no response
|
||||
String resp = buf.toString();
|
||||
if ("y".equalsIgnoreCase(resp) || "yes".equalsIgnoreCase(resp)) {
|
||||
return true;
|
||||
}
|
||||
if ("n".equalsIgnoreCase(resp) || "no".equalsIgnoreCase(resp)) {
|
||||
return false;
|
||||
}
|
||||
System.out.println("Invalid response");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+10
@@ -60,6 +60,11 @@ public class RepositoryServerAdapter {
|
||||
|
||||
/**
|
||||
* Construct a repository server interface adapter.
|
||||
* <p>
|
||||
* NOTE: It is important that this method only be invoked for known/trusted Ghidra Servers.
|
||||
* This instantiation will add the specified server to the cached Allow List to facilitate
|
||||
* future access to the server via a Ghidra URL.
|
||||
*
|
||||
* @param server provides server connection data
|
||||
*/
|
||||
RepositoryServerAdapter(ServerInfo server) {
|
||||
@@ -70,6 +75,7 @@ public class RepositoryServerAdapter {
|
||||
/**
|
||||
* Construct a repository server interface adapter.
|
||||
* @param serverHandle associated server handle (reconnect not supported)
|
||||
* @param serverInfoString additional server information (e.g., URL)
|
||||
*/
|
||||
protected RepositoryServerAdapter(RepositoryServerHandle serverHandle,
|
||||
String serverInfoString) {
|
||||
@@ -146,6 +152,10 @@ public class RepositoryServerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Allow future server access when server is directly accessed
|
||||
UrlAllowListManager.updateAccess("ghidra", server.getServerName(), server.getPortNumber(),
|
||||
true);
|
||||
|
||||
lastConnectError = null;
|
||||
try {
|
||||
try {
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/* ###
|
||||
* 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 java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link ServerSpecification} URL-based server specification record
|
||||
*
|
||||
* @param protocol URL protocol (e.g., {@code https}, {@code ghidra}).
|
||||
* @param hostname host name or IP address
|
||||
* @param port connection port (positive value)
|
||||
*/
|
||||
public record ServerSpecification(String protocol, String hostname, int port)
|
||||
implements Comparable<ServerSpecification> {
|
||||
|
||||
public static ServerSpecification get(URL url) {
|
||||
return new ServerSpecification(url.getProtocol(), url.getHost(), getPort(url));
|
||||
}
|
||||
|
||||
static int getPort(URL url) {
|
||||
int port = url.getPort();
|
||||
if (port < 0) {
|
||||
port = url.getDefaultPort();
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
public ServerSpecification {
|
||||
Objects.requireNonNull(protocol, "Protocol may not be null");
|
||||
Objects.requireNonNull(hostname, "Hostname may not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ServerSpecification o) {
|
||||
int c = hostname.compareTo(o.hostname);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = Integer.compare(port, o.port);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
// NOTE: there should only be one protocol per server port
|
||||
return protocol.compareTo(o.protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return server info in URL form}
|
||||
*/
|
||||
public String toUrlString() {
|
||||
return protocol + "://" + hostname + ":" + port;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+440
File diff suppressed because it is too large
Load Diff
+34
@@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* 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 java.net.URL;
|
||||
|
||||
/**
|
||||
* {@link UrlAllowListProvider} provides the URL allow list provider interface which facilitates
|
||||
* obtaining an access decision for a specified server URL.
|
||||
*/
|
||||
public interface UrlAllowListProvider {
|
||||
|
||||
/**
|
||||
* Check if a server connection is permitted. If permitted, the server will be added
|
||||
* to the cached allow list. This method will return true for opaque or non-server URLs.
|
||||
*
|
||||
* @param url server URL
|
||||
* @return true if connection is allowed and may proceed, else false if denied
|
||||
*/
|
||||
public abstract boolean isAllowed(URL url);
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -17,6 +17,8 @@ package ghidra.framework.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Container for a host name and port number.
|
||||
*
|
||||
@@ -32,6 +34,12 @@ public class ServerInfo implements Serializable {
|
||||
* @param portNumber port number
|
||||
*/
|
||||
public ServerInfo(String host, int portNumber) {
|
||||
if (portNumber <= 0) {
|
||||
throw new IllegalArgumentException("Invalid port number specified: " + portNumber);
|
||||
}
|
||||
if (StringUtils.isBlank(host)) {
|
||||
throw new IllegalArgumentException("Invalid host specified: '" + host + "'");
|
||||
}
|
||||
this.host = host;
|
||||
this.portNumber = portNumber;
|
||||
}
|
||||
|
||||
+9
-3
@@ -26,6 +26,7 @@ import generic.timer.GhidraSwinglessTimer;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.GhidraServerHandle;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
@@ -111,8 +112,7 @@ public class DefaultProjectData implements ProjectData {
|
||||
* @throws FileNotFoundException if project directory not found
|
||||
*/
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
||||
boolean resetOwner)
|
||||
throws NotFoundException, NotOwnerException, IOException, LockException {
|
||||
boolean resetOwner) throws NotFoundException, NotOwnerException, IOException, LockException {
|
||||
localStorageLocator.checkProjectExistence();
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
boolean success = false;
|
||||
@@ -466,7 +466,13 @@ public class DefaultProjectData implements ProjectData {
|
||||
versionedFileSystemDir.getAbsolutePath(), create, true, !isInWritableProject, true);
|
||||
}
|
||||
else {
|
||||
int port = properties.getInt(PORT_NUMBER, -1);
|
||||
int port = properties.getInt(PORT_NUMBER, GhidraServerHandle.DEFAULT_PORT);
|
||||
|
||||
if (isInWritableProject) {
|
||||
// Ensure that future server access is allowed since it has been deliberately accessed
|
||||
UrlAllowListManager.updateAccess("ghidra", serverName, port, true);
|
||||
}
|
||||
|
||||
repository = getRepositoryAdapter(serverName, port, isInWritableProject);
|
||||
versionedFileSystem = new RemoteFileSystem(repository);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package ghidra.framework.main;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.tool.ToolConstants;
|
||||
@@ -24,6 +26,7 @@ import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.client.UrlAllowListManager;
|
||||
import ghidra.framework.main.certs.CertificateManagerLauncher;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.net.PKIUtils;
|
||||
@@ -44,12 +47,25 @@ class EditActionManager {
|
||||
private FrontEndPlugin plugin;
|
||||
private FrontEndTool tool;
|
||||
|
||||
private ChangeListener serverAllowListListener = e -> serverAllowListChanged();
|
||||
|
||||
private DockingAction clearServerAllowList;
|
||||
private DockingAction clearCertPathAction;
|
||||
|
||||
EditActionManager(FrontEndPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
tool = (FrontEndTool) plugin.getTool();
|
||||
createActions();
|
||||
|
||||
UrlAllowListManager.addChangeListener(serverAllowListListener);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
UrlAllowListManager.removeChangeListener(serverAllowListListener);
|
||||
}
|
||||
|
||||
private void serverAllowListChanged() {
|
||||
clearServerAllowList.setEnabled(!UrlAllowListManager.getAccessMap().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +112,22 @@ class EditActionManager {
|
||||
.build();
|
||||
tool.addAction(clearCertPathAction);
|
||||
|
||||
clearServerAllowList =
|
||||
new ActionBuilder("Clear Server Allow List", plugin.getName()).menuGroup("SvrAllowList")
|
||||
.menuPath(ToolConstants.MENU_EDIT, "Clear Server Allow List...")
|
||||
.helpLocation(new HelpLocation("FrontEndPlugin", "Clear_Server_Allow_List"))
|
||||
.onAction(c -> clearServerAllowList())
|
||||
.enabledWhen(c -> DefaultKeyManagerFactory.getKeyStore() != null)
|
||||
.enabled(true)
|
||||
.build();
|
||||
tool.addAction(clearServerAllowList);
|
||||
}
|
||||
|
||||
private void clearServerAllowList() {
|
||||
if (OptionDialog.YES_OPTION == OptionDialog.showYesNoDialog(tool.getToolFrame(),
|
||||
"Clear Server Allow List", "Clear all Server Allow List entries?\n")) {
|
||||
UrlAllowListManager.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,6 +129,8 @@ public class FrontEndPlugin extends Plugin
|
||||
|
||||
private FrontEndProvider frontEndProvider;
|
||||
|
||||
private EditActionManager editActionManager;
|
||||
|
||||
private ProjectRepoConnectAction repoConnectAction;
|
||||
private ProjectDataCutAction cutAction;
|
||||
private ClearCutAction clearCutAction;
|
||||
@@ -179,7 +181,7 @@ public class FrontEndPlugin extends Plugin
|
||||
tool.addComponentProvider(frontEndProvider, true);
|
||||
tool.setDefaultComponent(frontEndProvider);
|
||||
|
||||
new EditActionManager(this);
|
||||
editActionManager = new EditActionManager(this);
|
||||
buildGui();
|
||||
|
||||
toolChestChangeListener = new MyToolChestChangeListener();
|
||||
@@ -835,6 +837,7 @@ public class FrontEndPlugin extends Plugin
|
||||
dataTablePanel.dispose();
|
||||
dataTreePanel.dispose();
|
||||
projectActionManager.dispose();
|
||||
editActionManager.dispose();
|
||||
}
|
||||
|
||||
private void buildPanels() {
|
||||
|
||||
@@ -241,8 +241,11 @@ class RepositoryChooser extends ReusableDialogComponentProvider {
|
||||
|
||||
listModel.clear();
|
||||
|
||||
String serverName = serverInfoComponent.getServerName();
|
||||
int port = serverInfoComponent.getPortNumber();
|
||||
|
||||
RepositoryServerAdapter repositoryServer = ClientUtil.getRepositoryServer(
|
||||
serverInfoComponent.getServerName(), serverInfoComponent.getPortNumber(), true);
|
||||
serverName, port, true);
|
||||
|
||||
if (repositoryServer == null) {
|
||||
return;
|
||||
|
||||
+4
-4
@@ -190,14 +190,14 @@ public class ServerInfoComponent extends JPanel {
|
||||
}
|
||||
|
||||
private boolean checkPortNumber() {
|
||||
portNumber = -1;
|
||||
portNumber = GhidraServerHandle.DEFAULT_PORT;
|
||||
String portStr = portNumberField.getText();
|
||||
String msg = null;
|
||||
try {
|
||||
portNumber = Integer.parseInt(portStr);
|
||||
if (portNumber < 0 || portNumber > 65536) {
|
||||
portNumber = -1;
|
||||
msg = "Port number must in range of 0 to 65536";
|
||||
if (portNumber < 1 || portNumber > 65536) {
|
||||
portNumber = GhidraServerHandle.DEFAULT_PORT;
|
||||
msg = "Port number must in range of 1 to 65536";
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
|
||||
+23
-7
@@ -257,13 +257,29 @@ public class DefaultProject implements Project {
|
||||
c.setReadOnly(true);
|
||||
|
||||
StatusCode responseCode = c.getStatusCode();
|
||||
if (responseCode == StatusCode.NOT_FOUND) {
|
||||
throw new IOException(
|
||||
"Project/repository not found: " + GhidraURL.getDisplayString(url));
|
||||
}
|
||||
if (responseCode == StatusCode.UNAUTHORIZED) {
|
||||
// assume already informed
|
||||
return null;
|
||||
switch (responseCode) {
|
||||
case OK:
|
||||
break;
|
||||
|
||||
case UNAUTHORIZED:
|
||||
throw new IOException("Authorization failure");
|
||||
|
||||
case NOT_FOUND:
|
||||
throw new IOException("Project or repository not found");
|
||||
|
||||
case LOCKED:
|
||||
// Local projects are only accessed read-only, this condition should not occur
|
||||
throw new AssertionError("Unexpected local project lock condition");
|
||||
|
||||
case FORBIDDEN:
|
||||
throw new IOException("Access denied by Server Allow List");
|
||||
|
||||
case UNAVAILABLE:
|
||||
throw new IOException("Server connection error occured (see log files)");
|
||||
|
||||
default:
|
||||
throw new IOException("Server connection error occured: " + responseCode);
|
||||
|
||||
}
|
||||
|
||||
DefaultProjectData veiwedProjectData = (DefaultProjectData) c.getProjectData();
|
||||
|
||||
+8
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -61,8 +61,13 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
|
||||
|
||||
this.readOnly = readOnlyAccess;
|
||||
|
||||
statusCode = StatusCode.UNAVAILABLE; // if uncaught exception occurs
|
||||
// Check for explicitly denied server connections
|
||||
if (!ClientUtil.getAllowListProvider().isAllowed(url)) {
|
||||
statusCode = StatusCode.FORBIDDEN;
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
statusCode = StatusCode.UNAVAILABLE;
|
||||
repositoryServerAdapter =
|
||||
ClientUtil.getRepositoryServer(url.getHost(), url.getPort(), true);
|
||||
if (!repositoryServerAdapter.isConnected()) {
|
||||
|
||||
+17
-4
@@ -35,6 +35,11 @@ public class GhidraURLConnection extends URLConnection {
|
||||
* This status code occurs when repository access is denied.
|
||||
*/
|
||||
UNAUTHORIZED(401, "Unauthorized"),
|
||||
/**
|
||||
* Ghidra Status-Code 403: Forbidden by client allow list.
|
||||
* This status code occurs when repository access is denied.
|
||||
*/
|
||||
FORBIDDEN(403, "Forbidden"),
|
||||
/**
|
||||
* Ghidra Status-Code 404: Not Found.
|
||||
* This status code occurs when repository or project does not exist.
|
||||
@@ -49,6 +54,8 @@ public class GhidraURLConnection extends URLConnection {
|
||||
* Ghidra Status-Code 503: Unavailable.
|
||||
* This status code includes a variety of connection errors
|
||||
* which are reported/logged by the Ghidra Server support code.
|
||||
* This error may also occur when the connection is not allowed
|
||||
* by the user (see {@link GhidraURLAllowListProvider}).
|
||||
*/
|
||||
UNAVAILABLE(503, "Unavailable");
|
||||
|
||||
@@ -310,10 +317,16 @@ public class GhidraURLConnection extends URLConnection {
|
||||
// will be established with a RepositoryAdapter supplied by the connector.
|
||||
|
||||
// Obtain connected transient project for repository and complete connection
|
||||
TransientProjectManager transientProjectManager =
|
||||
TransientProjectManager.getTransientProjectManager();
|
||||
TransientProjectData transientProjectData =
|
||||
transientProjectManager.getTransientProject(protocolConnector, readOnly);
|
||||
TransientProjectData transientProjectData = null;
|
||||
try {
|
||||
TransientProjectManager transientProjectManager =
|
||||
TransientProjectManager.getTransientProjectManager();
|
||||
transientProjectData =
|
||||
transientProjectManager.getTransientProject(protocolConnector, readOnly);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore - rely on status code
|
||||
}
|
||||
|
||||
connected = true;
|
||||
statusCode = protocolConnector.getStatusCode();
|
||||
|
||||
+28
-5
@@ -167,6 +167,9 @@ public class GhidraURLQuery {
|
||||
status = c.getStatusCode();
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (status == null) {
|
||||
status = StatusCode.UNAVAILABLE;
|
||||
}
|
||||
resultHandler.handleError("URL Connection Error", e.getMessage(), ghidraUrl, e);
|
||||
}
|
||||
|
||||
@@ -183,24 +186,35 @@ public class GhidraURLQuery {
|
||||
return;
|
||||
|
||||
case NOT_FOUND:
|
||||
generatedErr = new IOException("Project or repository not found");
|
||||
generatedErr = new IOException(
|
||||
"Project or repository not found: " + getGhidraUrlDetail(ghidraUrl));
|
||||
break;
|
||||
|
||||
case LOCKED:
|
||||
// Local projects are only accessed read-only, this condition should not occur
|
||||
throw new AssertionError("Unexpected local project lock condition");
|
||||
throw new AssertionError("Unexpected local project lock condition: " +
|
||||
getGhidraUrlDetail(ghidraUrl));
|
||||
|
||||
case FORBIDDEN:
|
||||
generatedErr = new IOException(
|
||||
"Access denied by Server Allow List: " + getGhidraUrlDetail(ghidraUrl));
|
||||
break;
|
||||
|
||||
case UNAVAILABLE:
|
||||
generatedErr =
|
||||
new IOException("Server connection error occured (see log files)");
|
||||
new IOException("Server connection error occured (see log files): " +
|
||||
getGhidraUrlDetail(ghidraUrl));
|
||||
break;
|
||||
|
||||
default:
|
||||
generatedErr = new IOException("Server connection error occured (" + status +
|
||||
": " + getGhidraUrlDetail(ghidraUrl));
|
||||
break;
|
||||
}
|
||||
|
||||
if (generatedErr != null) {
|
||||
resultHandler.handleError("Content Not Found", generatedErr.getMessage(), ghidraUrl,
|
||||
generatedErr);
|
||||
resultHandler.handleError("Content Access Failure", generatedErr.getMessage(),
|
||||
ghidraUrl, generatedErr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,6 +248,15 @@ public class GhidraURLQuery {
|
||||
}
|
||||
}
|
||||
|
||||
private String getGhidraUrlDetail(URL ghidraUrl) {
|
||||
try {
|
||||
return GhidraURL.getProjectURL(ghidraUrl).toString();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return ghidraUrl.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private void processContent(Object content, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
if (content instanceof DomainFile file) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.remote.GhidraServerHandle;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
@@ -111,6 +112,11 @@ public class Handler extends URLStreamHandler {
|
||||
return protocolHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultPort() {
|
||||
return GhidraServerHandle.DEFAULT_PORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ support/jshellRun||GHIDRA||||END|
|
||||
support/launch.properties||GHIDRA||||END|
|
||||
support/pyghidraRun||GHIDRA||||END|
|
||||
support/sleigh||GHIDRA||||END|
|
||||
support/updateServerAllowList||GHIDRA||||END|
|
||||
|
||||
@@ -74,6 +74,7 @@ for common use cases.
|
||||
[<a href="#-processor-languageid">-processor <languageID></a>]
|
||||
[<a href="#-cspec-compilerspecid">-cspec <compilerSpecID></a>]
|
||||
[<a href="#-analysistimeoutperfile-timeout-in-seconds">-analysisTimeoutPerFile <timeout in seconds></a>]
|
||||
[<a href="#-allowAllAccess">-allowAllAccess</a>]
|
||||
[<a href="#-keystore-keystorepath">-keystore <KeystorePath></a>]
|
||||
[<a href="#-connect-userid">-connect [<userID>]</a>]
|
||||
[<a href="#-p">-p</a>]
|
||||
@@ -341,6 +342,19 @@ completed processing prior to timeout will still be saved with the program. Post
|
||||
to detect that analysis has timed out (in Headless processing ONLY) by calling the
|
||||
`getHeadlessAnalysisTimeoutStatus()` method.
|
||||
|
||||
### `-allowAllAccess`
|
||||
When using Ghidra URLs to access a GhidraServer and this option has been omitted, and if server
|
||||
access has not already disallowed, the stored `Server Allow List` will be updated to allow access.
|
||||
If access has previously been disallowed, the analyze headless process will fail.
|
||||
|
||||
The use of the `Server Allow List` can be bypassed by including the `-allowAllAccess` option.
|
||||
Alternatively, the `updateServerAllowList` command within the Ghidra installation may be used to examine
|
||||
and update the server allow list. Running the `updateServerAllowList` with no argument will provide
|
||||
usage details.
|
||||
|
||||
In general, there should be no need to deal with this server access concern unless access was
|
||||
previously disallowed or other Ghidra URLs are obtained and utilized from other sources.
|
||||
|
||||
### `-keystore <KeystorePath>`
|
||||
When connecting to a Ghidra Server using PKI or SSH authentication, this option allows
|
||||
specification of a suitable private keystore file. The keystore file should always be properly
|
||||
@@ -1033,6 +1047,7 @@ Below are some general guidelines for wildcard usage:
|
||||
[processor]: #-processor-languageid
|
||||
[cspec]: #-cspec-compilerspecid
|
||||
[timeout]: #-analysistimeoutperfile-timeout-in-seconds
|
||||
[-allowAllAccess]: #-allowAllAccess
|
||||
[keystore]: #-keystore-keystorepath
|
||||
[connect]: #-connect-userid
|
||||
[password]: #-p
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Update Server Allow List launch
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
MAXMEM=128M
|
||||
|
||||
# Launch mode can be changed to one of the following: fg, debug, debug-suspend
|
||||
LAUNCH_MODE=fg
|
||||
|
||||
# Set the debug address to listen on.
|
||||
# NOTE: This variable is ignored if not launching in a debugging mode.
|
||||
DEBUG_ADDRESS=127.0.0.1:13010
|
||||
|
||||
# Resolve symbolic link if present and get the directory this script lives in.
|
||||
# NOTE: "readlink -f" is best but works on Linux only, "readlink" will only work if your PWD
|
||||
# contains the link you are calling (which is the best we can do on macOS), and the "echo" is the
|
||||
# fallback, which doesn't attempt to do anything with links.
|
||||
SCRIPT_FILE="$(readlink -f "$0" 2>/dev/null || readlink "$0" 2>/dev/null || echo "$0")"
|
||||
SCRIPT_DIR="${SCRIPT_FILE%/*}"
|
||||
|
||||
# Set required VMARGS for jar builder application
|
||||
APP_VMARGS="-DUpdateServerAllowList.Name=$(basename "${SCRIPT_FILE}")"
|
||||
|
||||
# Launch UpdateServerAllowList.
|
||||
# DEBUG_ADDRESS set via environment for launch.sh
|
||||
DEBUG_ADDRESS=${DEBUG_ADDRESS} "${SCRIPT_DIR}"/launch.sh "${LAUNCH_MODE}" jdk updateServerAllowList "${MAXMEM}" "${APP_VMARGS}" ghidra.app.util.headless.UpdateServerAllowList "$@"
|
||||
@@ -0,0 +1,26 @@
|
||||
:: ###
|
||||
:: 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.
|
||||
:: ##
|
||||
:: Update Server Allow List launch
|
||||
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: maximum heap memory may be change if inadequate
|
||||
set MAXMEM=128M
|
||||
|
||||
set APP_VMARGS=-DUpdateServerAllowList.Name=%~n0
|
||||
|
||||
call "%~dp0launch.bat" fg jdk updateServerAllowList "%MAXMEM%" "%APP_VMARGS%" ghidra.app.util.headless.UpdateServerAllowList %*
|
||||
+71
-40
File diff suppressed because it is too large
Load Diff
+11
-42
@@ -15,33 +15,14 @@
|
||||
*/
|
||||
package ghidra.server.remote;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -53,38 +34,24 @@ import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import generic.hash.HashUtilities;
|
||||
import generic.test.AbstractGenericTest;
|
||||
import generic.test.ConcurrentTestExceptionHandler;
|
||||
import generic.test.TestUtils;
|
||||
import generic.test.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.client.RepositoryServerAdapter;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.ContentHandler;
|
||||
import ghidra.framework.data.DomainObjectAdapter;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.protocol.ghidra.Handler;
|
||||
import ghidra.framework.protocol.ghidra.*;
|
||||
import ghidra.framework.remote.GhidraServerHandle;
|
||||
import ghidra.framework.remote.RMIServerPortFactory;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.local.LocalFolderItem;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.net.DefaultSSLContextInitializer;
|
||||
import ghidra.net.DefaultTrustManagerFactory;
|
||||
import ghidra.net.PKITestUtils;
|
||||
import ghidra.net.PKIUtils;
|
||||
import ghidra.net.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.server.ServerAdmin;
|
||||
import ghidra.server.UserManager;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NamingUtilities;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.timer.GTimer;
|
||||
import utilities.util.FileUtilities;
|
||||
@@ -662,6 +629,8 @@ public class ServerTestUtil {
|
||||
|
||||
System.clearProperty(DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY);
|
||||
|
||||
TransientProjectManager.getTransientProjectManager().dispose();
|
||||
|
||||
if (serverProcess != null) {
|
||||
|
||||
cmdOut.dispose();
|
||||
|
||||
@@ -1297,10 +1297,10 @@ Before you can do anything else, you must first create a project. Projects are u
|
||||
<li>Types of annotations:</li>
|
||||
<ul>
|
||||
<li>Address</li>
|
||||
<li>Execute</li>
|
||||
<li>Program</li>
|
||||
<li>Symbol</li>
|
||||
<li>URL</li>
|
||||
<li>Program</li>
|
||||
<li>Symbol</li>
|
||||
<li>HTTP-URL</li>
|
||||
<li>GHIDRA-URL</li>
|
||||
</ul>
|
||||
</ul>
|
||||
<div role="note">
|
||||
@@ -1309,14 +1309,14 @@ Before you can do anything else, you must first create a project. Projects are u
|
||||
<ul>
|
||||
<li><b>Annotations</b> To add comment annotations using the comment editor, use the pull-down menu, choose an annotation type, and then click the <b>Add Annotation</b> button.</li>
|
||||
<ul>
|
||||
<li><b>Address</b> Fill in the interesting address. The comment will display the given address as a hyperlink. Double-click the link to navigate to that address.</li>
|
||||
<li><b>Execute</b> Fill in path to an executable. Double-click the resulting link to launch the specified executable in your OS with given optional parameters.</li>
|
||||
<li><b>Program</b> Fill in existing Ghidra program name (case-sensitive) to displays a hyperlink to the given Ghidra program name. Double-click the link to open the program in a new Listing tab. Optionally use the @symbol name after the program name to go to a specific symbol in the program.</li>
|
||||
<li><b>Symbol</b> Fill in the address of the interesting symbol. The comment will display the given symbol as a hyperlink. Double-click to navigate to the symbol. Changes to the symbol in the Listing will automatically change the comment to display the new symbol.</li>
|
||||
<li><b>URL</b> Displays the given URL as a hyperlink. Double-click to open what the link is pointing to. References to ghidra://, which refer to a program within a Ghidra Server repository, will be opened within the Listing display, while all other URL protocols (e.g., http://, https://, file://, etc.) will be launched via an external web browser. See HELP command configuration for Processor Manuals for more information.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<li><b>Address</b> Fill in the interesting address. The comment will display the given address as a hyperlink. Double-click the link to navigate to that address.</li>
|
||||
<li><b>Program</b> Fill in existing Ghidra program name (case-sensitive) to displays a hyperlink to the given Ghidra program name. Double-click the link to open the program in a new Listing tab. Optionally use the @symbol name after the program name to go to a specific symbol in the program.</li>
|
||||
<li><b>Symbol</b> Fill in the address of the interesting symbol. The comment will display the given symbol as a hyperlink. Double-click to navigate to the symbol. Changes to the symbol in the Listing will automatically change the comment to display the new symbol.</li>
|
||||
<li><b>HTTP-URL</b> Displays the given URL as a hyperlink. Double-click to open what the link is pointing to
|
||||
using your configured web browser. See HELP command configuration for Processor Manuals for more information.</li>
|
||||
<li><b>GHIDRA-URL</b> References a local Ghidra project file or a remote Ghidra Server repository file.
|
||||
The referenced Program file will be opened within the Listing display.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
[-processor <languageID>] [-cspec <compilerSpecID>]
|
||||
[-analysisTimeoutPerFile <timeout in seconds>]
|
||||
[-keystore <KeystorePath>] [-connect [<userID>]]
|
||||
[-p] [-commit ["<comment>"]] [-okToDelete]
|
||||
[-p] [-commit ["<comment>"]] [-okToDelete] [-allowAllAccess]
|
||||
[-max-cpu <max cpu cores to use>] [-loader <desired loader name>]
|
||||
</pre>
|
||||
</span>
|
||||
@@ -419,6 +419,22 @@
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header><span style="color:#FFD700">-allowAllAccess</span></header>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li>Access to remote servers may be subject to a <b>Server Allow List</b>. In order to avoid user
|
||||
prompting, connections to non-localhost servers, not previously added to the list via the GUI or
|
||||
<I>support/updateServerAllowList</I> shell script, will fail by default.
|
||||
Execute this script without arguments to see usage information.
|
||||
Access denial may be avoided by either updating the <b>Server Allow List</b> or including the
|
||||
<span style="color:#FFD700">-allowAllAccess</span> command line option.
|
||||
NOTE: <b>Server Allow List</b> screening is independent of possible SSL/TLS server authentication
|
||||
which may be required.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header><span style="font-size:40px;color:#FFD700">-max-cpu <max cpu cores to use></span></header>
|
||||
<br><br><br>
|
||||
|
||||
Reference in New Issue
Block a user