Skip to content

Commit

Permalink
Log4shell scan improvements (#393)
Browse files Browse the repository at this point in the history
* add support for old spring archives to be scanned

* spring executable jars can be scanned now

* Fix windows output to not include excessive slashes #365

* add false positive tests
  • Loading branch information
breadchris authored Dec 31, 2021
1 parent fee19ab commit 2f14ea9
Show file tree
Hide file tree
Showing 22 changed files with 282,562 additions and 565 deletions.
3 changes: 3 additions & 0 deletions tools/log4shell/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ build: payload cli

clean:
go clean

setup-tests:
cd test/vulnerable-log4j2-versions && go run main.go
82 changes: 5 additions & 77 deletions tools/log4shell/analyze/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,87 +16,15 @@ package analyze

import (
"archive/zip"
"github.com/blang/semver/v4"
"github.com/lunasec-io/lunasec/tools/log4shell/constants"
"github.com/lunasec-io/lunasec/tools/log4shell/types"
"github.com/lunasec-io/lunasec/tools/log4shell/util"
"github.com/rs/zerolog/log"
"io"
"path"
"regexp"
"strings"
)

var alphaRegex = regexp.MustCompile("([a-z]+)")

func versionIsInRange(fileName string, semverVersion string, semverRange semver.Range) bool {
version, err := semver.Make(semverVersion)
if err != nil {
log.Warn().
Str("fileName", fileName).
Str("semverVersion", semverVersion).
Msg("Unable to parse semver version")
return false
}

if semverRange(version) {
return true
}
return false
}

func adjustMissingPatchVersion(semverVersion string) string {
if len(semverVersion) == 3 {
semverVersion += ".0"
}
if strings.HasPrefix(semverVersion, "2.0-") {
semverVersion = strings.Replace(semverVersion, "2.0-", "2.0.0-", -1)
}
if strings.HasPrefix(semverVersion, "1.0-") {
semverVersion = strings.Replace(semverVersion, "1.0-", "1.0.0-", -1)
}
return semverVersion
}

func fileNameToSemver(fileNameNoExt string) string {
fileNameParts := strings.Split(fileNameNoExt, "-")

var tag, semverVersion string
for i := len(fileNameParts) - 1; i >= 0; i-- {
fileNamePart := fileNameParts[i]
if (
strings.HasPrefix(fileNamePart, "1") ||
strings.HasPrefix(fileNamePart, "2")) &&
strings.Contains(fileNamePart, ".") {

tagPart := alphaRegex.FindString(fileNamePart)
if tagPart != "" {
fileNamePart = strings.Replace(fileNamePart, tagPart, "", 1)
if tag == "" {
tag = tagPart
} else {
tag = tagPart + "-" + tag
}
}

fileNamePart = adjustMissingPatchVersion(fileNamePart)

if tag == "" {
semverVersion = fileNamePart
break
}
semverVersion = fileNamePart + "-" + tag
break
}
if tag == "" {
tag = fileNamePart
continue
}
tag = fileNamePart + "-" + tag
}
return semverVersion
}

func GetJndiLookupHash(zipReader *zip.Reader, filePath string) (fileHash string) {
reader, err := zipReader.Open(constants.JndiLookupClasspath)
if err != nil {
Expand Down Expand Up @@ -126,16 +54,15 @@ func ProcessArchiveFile(zipReader *zip.Reader, reader io.Reader, filePath, fileN
jndiLookupFileHash string
)

_, file := path.Split(filePath)
fileNameNoExt := strings.TrimSuffix(file, path.Ext(file))
_, archiveName := path.Split(filePath)

// small adjustments to the version so that it can be parsed as semver
semverVersion := fileNameToSemver(fileNameNoExt)
semverVersion := ArchiveNameToSemverVersion(archiveName)

versionCve := ""

for _, fileVersionCheck := range constants.FileVersionChecks {
if versionIsInRange(fileNameNoExt, semverVersion, fileVersionCheck.SemverRange) {
if VersionIsInRange(archiveName, semverVersion, fileVersionCheck.SemverRange) {
if !strings.Contains(fileName, fileVersionCheck.LibraryFile) {
return
}
Expand Down Expand Up @@ -165,12 +92,13 @@ func ProcessArchiveFile(zipReader *zip.Reader, reader io.Reader, filePath, fileN
return
}

if versionIsInRange(fileNameNoExt, semverVersion, constants.JndiLookupPatchFileVersions) {
if VersionIsInRange(archiveName, semverVersion, constants.JndiLookupPatchFileVersions) {
jndiLookupFileHash = GetJndiLookupHash(zipReader, filePath)
}

log.Log().
Str("path", filePath).
Str("cve", versionCve).
Str("fileName", fileName).
Str("fileHash", fileHash).
Str("jndiLookupFileName", constants.JndiLookupClasspath).
Expand Down
94 changes: 94 additions & 0 deletions tools/log4shell/analyze/libraryversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2021 by LunaSec (owned by Refinery Labs, Inc)
//
// 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 analyze

import (
"github.com/blang/semver/v4"
"github.com/rs/zerolog/log"
"path"
"regexp"
"strings"
)

var alphaRegex = regexp.MustCompile("([a-z]+)")

func VersionIsInRange(fileName string, semverVersion string, semverRange semver.Range) bool {
version, err := semver.Make(semverVersion)
if err != nil {
log.Warn().
Str("fileName", fileName).
Str("semverVersion", semverVersion).
Msg("Unable to parse semver version")
return false
}

if semverRange(version) {
return true
}
return false
}

func adjustMissingPatchVersion(semverVersion string) string {
if len(semverVersion) == 3 {
semverVersion += ".0"
}
if strings.HasPrefix(semverVersion, "2.0-") {
semverVersion = strings.Replace(semverVersion, "2.0-", "2.0.0-", -1)
}
if strings.HasPrefix(semverVersion, "1.0-") {
semverVersion = strings.Replace(semverVersion, "1.0-", "1.0.0-", -1)
}
return semverVersion
}

func ArchiveNameToSemverVersion(filename string) string {
fileNameNoExt := strings.TrimSuffix(filename, path.Ext(filename))
fileNameParts := strings.Split(fileNameNoExt, "-")

var tag, semverVersion string
for i := len(fileNameParts) - 1; i >= 0; i-- {
fileNamePart := fileNameParts[i]
if (
strings.HasPrefix(fileNamePart, "1") ||
strings.HasPrefix(fileNamePart, "2")) &&
strings.Contains(fileNamePart, ".") {

tagPart := alphaRegex.FindString(fileNamePart)
if tagPart != "" {
fileNamePart = strings.Replace(fileNamePart, tagPart, "", 1)
if tag == "" {
tag = tagPart
} else {
tag = tagPart + "-" + tag
}
}

fileNamePart = adjustMissingPatchVersion(fileNamePart)

if tag == "" {
semverVersion = fileNamePart
break
}
semverVersion = fileNamePart + "-" + tag
break
}
if tag == "" {
tag = fileNamePart
continue
}
tag = fileNamePart + "-" + tag
}
return semverVersion
}
11 changes: 11 additions & 0 deletions tools/log4shell/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"os"
"strings"
)

func enableGlobalFlags(c *cli.Context, globalBoolFlags map[string]bool) {
Expand All @@ -48,6 +49,16 @@ func enableGlobalFlags(c *cli.Context, globalBoolFlags map[string]bool) {
consoleOutput.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("\n\t%s: ", util.Colorize(constants.ColorBlue, i))
}
consoleOutput.FormatFieldValue = func(i interface{}) string {
switch t := i.(type) {
case string:
return fmt.Sprintf("%s", util.FixStringSlashes(t))
case []string:
return fmt.Sprintf("[%s]", strings.Join(util.FixStringSliceSlashes(t), ", "))
default:
return fmt.Sprintf("%s", i)
}
}

consoleOutput.FormatLevel = func(i interface{}) string {
if i == nil {
Expand Down
Loading

0 comments on commit 2f14ea9

Please sign in to comment.