Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Java Meterpreter Symlink Handling on Windows #731

Merged
merged 6 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ protected CommandManager() throws Exception {
Class.forName("java.util.ServiceLoader");
apiVersion = ExtensionLoader.V1_6;

Class.forName("java.util.Objects");
apiVersion = ExtensionLoader.V1_7;

Class.forName("java.util.Optional");
apiVersion = ExtensionLoader.V1_8;

Class.forName("java.util.Optional").getMethod("stream");
apiVersion = ExtensionLoader.V1_9;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface ExtensionLoader {
public static final int V1_4 = 14;
public static final int V1_5 = 15;
public static final int V1_6 = 16;
public static final int V1_7 = 17;
public static final int V1_8 = 18;
public static final int V1_9 = 19;
public static final int V1_15 = 25;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.metasploit.meterpreter.stdapi;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;

public class FsUtils {
public static boolean isSymlink(File file) throws IOException {
String osName = System.getProperty("os.name");
if (osName != null && osName.toLowerCase().contains("windows") && isWindowsSymlink(file)) {
return true;
}

File canon;
if (file.getParent() == null) {
canon = file;
} else {
File canonDir = file.getParentFile().getCanonicalFile();
canon = new File(canonDir, file.getName());
}

return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
}

private static boolean isWindowsSymlink(File file) {
// this uses reflection to access the java.nio.file classes necessary that are available on Java 7+
try {
// first check using isSymbolicLink
Class<?> filesClass = Class.forName("java.nio.file.Files");
Class<?> pathClass = Class.forName("java.nio.file.Path");

Method isSymbolicLinkMethod = filesClass.getMethod("isSymbolicLink", pathClass);
Method toPathMethod = File.class.getMethod("toPath");

Object path = toPathMethod.invoke(file);
if ((Boolean)isSymbolicLinkMethod.invoke(null, path)) {
return true;
}

// next check if the target is a junction because isSymbolicLink doesn't handle that
Class<?> linkOptionClass = Class.forName("java.nio.file.LinkOption");
Object linkOptionArray = java.lang.reflect.Array.newInstance(linkOptionClass, 0);
Method toRealPath = pathClass.getMethod("toRealPath", linkOptionArray.getClass());
Object realPath = toRealPath.invoke(path, linkOptionArray);

// toRealPath resolves junctions so the result will be different
Method equalsMethod = pathClass.getMethod("equals", Object.class);
if (!(Boolean)equalsMethod.invoke(path, realPath)) {
return true;
}
} catch (ReflectiveOperationException e) {
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static void setCWD(File newCWD) {
public void load(CommandManager mgr) throws Exception {
mgr.registerCommand(CommandId.CORE_CHANNEL_OPEN, stdapi_channel_open.class, V1_2, V1_15);
mgr.registerCommand(CommandId.STDAPI_FS_CHDIR, stdapi_fs_chdir.class);
mgr.registerCommand(CommandId.STDAPI_FS_DELETE_DIR, stdapi_fs_delete_dir.class);
mgr.registerCommand(CommandId.STDAPI_FS_DELETE_DIR, stdapi_fs_delete_dir.class, V1_2, V1_7);
mgr.registerCommand(CommandId.STDAPI_FS_DELETE_FILE, stdapi_fs_delete_file.class);
mgr.registerCommand(CommandId.STDAPI_FS_FILE_EXPAND_PATH, stdapi_fs_file_expand_path.class, V1_2, V1_5); // %COMSPEC% only
mgr.registerCommand(CommandId.STDAPI_FS_FILE_MOVE, stdapi_fs_file_move.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class stdapi_fs_delete_dir implements Command {
public int execute(Meterpreter meterpreter, TLVPacket request, TLVPacket response) throws Exception {
String path = request.getStringValue(TLVType.TLV_TYPE_DIRECTORY_PATH);
File file = Loader.expand(path);
if (isSymlink(file)) {
if (!file.delete()) {
if (FsUtils.isSymlink(file)) {
if (!deleteSymlink(file)) {
throw new IOException("Cannot delete symbolic link " + file.getCanonicalPath());
}
} else if (file.isDirectory()) {
Expand All @@ -26,22 +26,15 @@ public int execute(Meterpreter meterpreter, TLVPacket request, TLVPacket respons
return ERROR_SUCCESS;
}

private static boolean isSymlink(File file) throws IOException {
File canon;
if (file.getParent() == null) {
canon = file;
} else {
File canonDir = file.getParentFile().getCanonicalFile();
canon = new File(canonDir, file.getName());
}
return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
protected boolean deleteSymlink(File file) throws IOException {
return file.delete();
}

private boolean rmtree(File file) throws IOException {
boolean ret = true;
for (File subFile : file.listFiles()) {
if (isSymlink(subFile)) {
ret = ret && subFile.delete();
if (FsUtils.isSymlink(subFile)) {
ret = ret && deleteSymlink(subFile);
} else if (subFile.isDirectory()) {
ret = ret && rmtree(subFile);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.metasploit.meterpreter.stdapi;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class stdapi_fs_delete_dir_V1_7 extends stdapi_fs_delete_dir {
@Override
protected boolean deleteSymlink(File file) throws IOException {
String osName = System.getProperty("os.name");
if (osName != null && osName.toLowerCase().contains("windows")) {
Files.delete(file.toPath());
return true;
}
return file.delete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public int execute(Meterpreter meterpreter, TLVPacket request, TLVPacket respons
}
}
File file = new File(path);
if (!file.exists()) {
if (!exists(file)) {
file = Loader.expand(path);
}
if (!file.exists()) {
if (!exists(file)) {
throw new IOException("File/directory does not exist: " + path);
}
response.add(TLVType.TLV_TYPE_STAT_BUF, stat(file));
Expand Down Expand Up @@ -76,6 +76,10 @@ protected boolean canExecute(File file) {
return false;
}

private boolean exists(File file) throws IOException {
return file.exists() || FsUtils.isSymlink(file);
}

/**
* Convert an integer to little endian.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ protected Process execute(String cmd, ArrayList<String> args) throws IOException
}

protected Process execute(String cmdstr) throws IOException {
Process proc = Runtime.getRuntime().exec(cmdstr);
Process proc = Runtime.getRuntime().exec(cmdstr, null, Loader.getCWD());
return proc;
}
}
2 changes: 1 addition & 1 deletion java/version-compatibility-check/java14/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<target>
<mkdir dir="${project.basedir}/target/generated-sources/copy/" />
<copy todir="${project.basedir}/target/generated-sources/copy">
<fileset dir="${project.basedir}/../java15/target/generated-sources/copy" includes="**/*.java" excludes="**/*_V1_5.java"/>
<fileset dir="${project.basedir}/../java15/target/generated-sources/copy" includes="**/*.java" excludes="**/*_V1_5.java" />
</copy>
</target>
</configuration>
Expand Down
2 changes: 1 addition & 1 deletion java/version-compatibility-check/java15/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<target>
<mkdir dir="${project.basedir}/target/generated-sources/copy/" />
<copy todir="${project.basedir}/target/generated-sources/copy">
<fileset dir="${project.basedir}/../java16/target/generated-sources/copy" includes="**/*.java" excludes="**/*_V1_6.java"/>
<fileset dir="${project.basedir}/../java16/target/generated-sources/copy" includes="**/*.java" excludes="**/*_V1_6.java" />
</copy>
</target>
</configuration>
Expand Down
19 changes: 15 additions & 4 deletions java/version-compatibility-check/java16/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,21 @@
<mkdir dir="${project.basedir}/target/generated-sources/copy/" />
<copy todir="${project.basedir}/target/generated-sources/copy">
<fileset dir="${project.basedir}/../../javapayload/src/main/java" includes="**/*.java" excludes="rmi/**" />
<fileset dir="${project.basedir}/../../meterpreter/meterpreter/src/main/java" includes="**/*.java"/>
<fileset dir="${project.basedir}/../../meterpreter/shared/src/main/java" includes="**/*.java"/>
<!-- Webcam_audio_record_V1_4 depends on Sun proprietary API -->
<fileset dir="${project.basedir}/../../meterpreter/meterpreter/target/extension-src" includes="**/*.java" excludes="**/stdapi_webcam_audio_record_V1_4.java" />
<fileset dir="${project.basedir}/../../meterpreter/meterpreter/src/main/java" includes="**/*.java" />
<fileset dir="${project.basedir}/../../meterpreter/shared/src/main/java" includes="**/*.java" />
<fileset dir="${project.basedir}/../../meterpreter/meterpreter/target/extension-src" includes="**/*.java">
<!-- stdapi_webcam_audio_record_V1_4 depends on Sun proprietary API -->
<exclude name="**/stdapi_webcam_audio_record_V1_4.java" />
<scriptselector language="javascript">
if (!(new RegExp('_V[0-9]_[0-9]+\.java')).test(filename)) {
self.setSelected(true);
} else if ((new RegExp('_V1_[0-6]\.java')).test(filename)) {
self.setSelected(true);
} else {
self.setSelected(false);
}
</scriptselector>
</fileset>
</copy>
</target>
</configuration>
Expand Down
Loading