diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 3aa94e479..b3094af00 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/tools/log4shell/commands/patch.go b/tools/log4shell/commands/patch.go index 4df2ff882..e95b82300 100644 --- a/tools/log4shell/commands/patch.go +++ b/tools/log4shell/commands/patch.go @@ -23,6 +23,7 @@ import ( "github.com/lunasec-io/lunasec/tools/log4shell/util" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" + "io" "io/ioutil" "os" "strings" @@ -151,13 +152,160 @@ func getHashOfZipMember(member *zip.File) (hash string) { return } +func getNestedZipReader(zipReader *zip.Reader, zipPath string) (nestedZipReader *zip.Reader, err error) { + if zipPath == "" { + nestedZipReader = zipReader + return + } + + nestedZip, err := zipReader.Open(zipPath) + if err != nil { + log.Error().Err(err).Str("zipPath", zipPath).Msg("Unable to open nested zip path") + return + } + defer nestedZip.Close() + + info, err := nestedZip.Stat() + if err != nil { + log.Error().Err(err).Str("zipPath", zipPath).Msg("Unable to stat nested zip") + return + } + + nestedZipReader, err = util.NewZipFromReader(nestedZip, info.Size()) + if err != nil { + log.Error().Err(err).Str("zipPath", zipPath).Msg("Unable to create new zip reader") + return + } + return +} + +func head(s []string) string { + if len(s) > 0 { + return s[0] + } + return "" +} + +func tail(s []string) []string { + if len(s) > 1 { + return s[1:] + } + return []string{} +} + +func addFileToZip(zipWriter *zip.Writer, existingHeader zip.FileHeader, filename string) (err error) { + fileToZip, err := os.Open(filename) + if err != nil { + log.Error(). + Err(err). + Str("filename", filename). + Msg("Unable to open file") + return + } + defer fileToZip.Close() + + // Get the file information + info, err := fileToZip.Stat() + if err != nil { + log.Error(). + Err(err). + Str("filename", filename). + Msg("Unable to stat file") + return + } + + existingHeader.UncompressedSize64 = uint64(info.Size()) + + writer, err := zipWriter.CreateHeader(&existingHeader) + if err != nil { + log.Error(). + Err(err). + Str("filename", filename). + Msg("Unable to create zip header") + return + } + _, err = io.Copy(writer, fileToZip) + if err != nil { + log.Error(). + Err(err). + Str("filename", filename). + Msg("Unable to copy file contents to zip writer") + return + } + return +} + func filterOutJndiLookupFromZip( + finding types.Finding, + zipReader *zip.Reader, + nestedPaths []string, + zipWriter *zip.Writer, + existingHeader zip.FileHeader, +) (filename string, err error) { + validOutputFile := false + + outZip, err := ioutil.TempFile(os.TempDir(), "*.zip") + if err != nil { + log.Error(). + Str("tmpDir", os.TempDir()). + Err(err). + Msg("Unable to create temporary libraryFile") + return + } + defer func() { + outZip.Close() + if !validOutputFile { + os.Remove(outZip.Name()) + } + }() + + nestedZipWriter := zip.NewWriter(outZip) + defer nestedZipWriter.Close() + + err = copyAndFilterFilesFromZip(finding, zipReader, nestedZipWriter, nestedPaths) + if err != nil { + return + } + nestedZipWriter.Flush() + + if zipWriter == nil { + filename = outZip.Name() + validOutputFile = true + return + } + + err = addFileToZip(zipWriter, existingHeader, outZip.Name()) + if err != nil { + return + } + zipWriter.Flush() + return +} + +func copyAndFilterFilesFromZip( finding types.Finding, zipReader *zip.Reader, writer *zip.Writer, -) error { + nestedPaths []string, +) (err error) { + nestedPath := head(nestedPaths) for _, member := range zipReader.File { - if member.Name == finding.JndiLookupFileName { + if member.Name == nestedPath { + var nestedZipReader *zip.Reader + + nestedZipReader, err = getNestedZipReader(zipReader, nestedPath) + if err != nil { + return + } + + _, err = filterOutJndiLookupFromZip(finding, nestedZipReader, tail(nestedPaths), writer, member.FileHeader) + if err != nil { + return + } + continue + } + + if len(nestedPaths) == 0 && member.Name == finding.JndiLookupFileName { shouldSkip := false log.Debug(). @@ -185,19 +333,20 @@ func filterOutJndiLookupFromZip( } if member.FileInfo().IsDir() { - fmt.Println(member.Name, member.FileInfo().IsDir()) - fmt.Printf("%+v\n", member.FileHeader) + continue } - - if err := writer.Copy(member); err != nil { + err = writer.Copy(member) + if err != nil { log.Error(). Err(err). + Str("memberName", member.Name). + Str("member", fmt.Sprintf("%+v", member.FileHeader)). Msg("Error while copying zip file.") - return err + return } } - return nil + return } func patchJavaArchive(finding types.Finding, dryRun bool) (err error) { @@ -207,9 +356,10 @@ func patchJavaArchive(finding types.Finding, dryRun bool) (err error) { ) zipPaths := strings.Split(finding.Path, "::") - var zipReaders []*zip.Reader - libraryFile, err = os.Open(zipPaths[0]) + fsFile := head(zipPaths) + + libraryFile, err = os.Open(fsFile) if err != nil { log.Error(). Str("path", finding.Path). @@ -219,76 +369,43 @@ func patchJavaArchive(finding types.Finding, dryRun bool) (err error) { } defer libraryFile.Close() - info, _ := os.Stat(finding.Path) - zipSize := info.Size() - - for _, zipPath := range zipPaths { - nestedZip, err := zipReader.Open(zipPath) - - nestedZip. - - zipReader, err = zip.NewReader(libraryFile, zipSize) - if err != nil { - log.Error(). - Str("path", finding.Path). - Str("zipPath", zipPath). - Err(err). - Msg("Unable to open archive for patching") - return - } - zipReaders = append(zipReaders, zipReader) - } - - outZip, err := ioutil.TempFile(os.TempDir(), "*.zip") + info, err := os.Stat(fsFile) if err != nil { log.Error(). - Str("tmpDir", os.TempDir()). + Str("path", finding.Path). Err(err). - Msg("Unable to create temporary libraryFile") + Msg("Cannot stat file.") return } - defer os.Remove(outZip.Name()) - - writer := zip.NewWriter(outZip) - defer writer.Close() - err = filterOutJndiLookupFromZip(finding, zipReader, writer) + zipReader, err = zip.NewReader(libraryFile, info.Size()) if err != nil { - return - } - - writer.Close() - - if err = libraryFile.Close(); err != nil { log.Error(). - Str("outZipName", outZip.Name()). - Str("libraryFileName", finding.Path). + Str("path", finding.Path). Err(err). - Msg("Unable to close library file.") + Msg("Cannot create new zip reader for file.") return } - if err = outZip.Close(); err != nil { - log.Error(). - Str("outZipName", outZip.Name()). - Str("libraryFileName", finding.Path). - Err(err). - Msg("Unable to close output zip.") + filteredLibrary, err := filterOutJndiLookupFromZip(finding, zipReader, tail(zipPaths), nil, zip.FileHeader{}) + if err != nil { return } if dryRun { log.Info(). - Str("library", finding.Path). + Str("libraryFileName", fsFile). + Str("fullPathToLibrary", finding.Path). Msg("[Dry Run] Not completing patch process of overwriting existing library.") return } - _, err = util.CopyFile(outZip.Name(), finding.Path) + _, err = util.CopyFile(filteredLibrary, fsFile) if err != nil { log.Error(). - Str("outZipName", outZip.Name()). - Str("libraryFileName", finding.Path). + Str("outZipName", filteredLibrary). + Str("libraryFileName", fsFile). + Str("fullPathToLibrary", finding.Path). Err(err). Msg("Unable to replace library file with patched library file.") return @@ -342,6 +459,9 @@ func JavaArchivePatchCommand( err = patchJavaArchive(finding, dryRun) if err != nil { + log.Error(). + Str("path", finding.Path). + Msg("Unable to patch library successfully.") continue } patchedLibraries = append(patchedLibraries, finding.Path) @@ -349,6 +469,6 @@ func JavaArchivePatchCommand( log.Info(). Strs("patchedLibraries", patchedLibraries). - Msg("Successfully patched libraries.") + Msg("Completed patched libraries.") return nil } diff --git a/tools/log4shell/constants/version.go b/tools/log4shell/constants/version.go index 4d07c2f07..307ab23a9 100644 --- a/tools/log4shell/constants/version.go +++ b/tools/log4shell/constants/version.go @@ -14,4 +14,4 @@ // package constants -const Version = "1.4.1" +const Version = "1.5.0" diff --git a/tools/log4shell/main.go b/tools/log4shell/main.go index 2679ce7c3..a626ac734 100644 --- a/tools/log4shell/main.go +++ b/tools/log4shell/main.go @@ -185,6 +185,7 @@ func main() { Name: "patch", Aliases: []string{"p"}, Usage: "Patches findings of libraries vulnerable toLog4Shell by removing the JndiLookup.class file from each.", + Before: setGlobalBoolFlags, Flags: []cli.Flag{ &cli.StringSliceFlag{ Name: "exclude", @@ -206,6 +207,14 @@ func main() { Name: "findings", Usage: "Patches all vulnerable Java archives which have been identified.", }, + &cli.BoolFlag{ + Name: "json", + Usage: "Display findings in json format.", + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Display helpful information while debugging the CLI.", + }, }, Action: func(c *cli.Context) error { return commands.JavaArchivePatchCommand(c, globalBoolFlags, log4jLibraryHashes) diff --git a/tools/log4shell/test/vulnerable-apps/vulnerable.jar b/tools/log4shell/test/vulnerable-apps/vulnerable.jar new file mode 100755 index 000000000..214373a78 Binary files /dev/null and b/tools/log4shell/test/vulnerable-apps/vulnerable.jar differ diff --git a/tools/log4shell/util/fs.go b/tools/log4shell/util/fs.go index d58f7d0b1..050c6a3f4 100644 --- a/tools/log4shell/util/fs.go +++ b/tools/log4shell/util/fs.go @@ -15,8 +15,11 @@ package util import ( + "archive/zip" + "bytes" "github.com/rs/zerolog/log" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -76,8 +79,31 @@ func ResolveSymlinkFilePathAndInfo(symlinkPath string) (path string, info os.Fil return } +// NewZipFromReader ... +func NewZipFromReader(file io.ReadCloser, size int64) (*zip.Reader, error) { + in := file.(io.Reader) + + if _, ok := in.(io.ReaderAt); ok != true { + buffer, err := ioutil.ReadAll(in) + + if err != nil { + return nil, err + } + + in = bytes.NewReader(buffer) + size = int64(len(buffer)) + } + + reader, err := zip.NewReader(in.(io.ReaderAt), size) + if err != nil { + return nil, err + } + + return reader, nil +} + func CopyFile(in, out string) (int64, error) { - i, e := os.Open(in) + i, e := os.Open(in) if e != nil { return 0, e } defer i.Close() o, e := os.Create(out)