Skip navigation

It’s usually a good thing when my and infosec worlds collide. Unfortunately, this time it’s a script that R folk running on OS X can use to see if they are using a version of XQuartz that has a nasty vulnerability in the framework it uses to auto-update. If this test comes back with the warning, try to refrain from using XQuartz on insecure networks until the developers fix the issue.

**UPDATE**

Thanks to a gist prodding by @bearloga, here’s a script to scan all your applications for the vulnerability:

library(purrr)
library(dplyr)
library(XML)
 
read_plist <- safely(readKeyValueDB)
safe_compare <- safely(compareVersion)
 
apps <- list.dirs(c("/Applications", "/Applications/Utilities"), recursive=FALSE)
 
# if you have something further than this far down that's bad you're on your own
 
for (i in 1:4) {
  moar_dirs <- grep("app$", apps, value=TRUE, invert=TRUE)
  if (length(moar_dirs) > 0) { apps <- c(apps, list.dirs(moar_dirs, recursive=FALSE)) }
}
apps <- unique(grep("app$", apps, value=TRUE))
 
pb <- txtProgressBar(0, length(apps), style=3)
 
suppressWarnings(map_df(1:length(apps), function(i) {
 
  x <- apps[i]
 
  setTxtProgressBar(pb, i)
 
  is_vuln <- FALSE
  version <- ""
 
  app_name <- sub("\\.app$", "", basename(x))
  app_loc <- sub("^/", "", dirname(x))
 
  to_look <- c(sprintf("%s/Contents/Frameworks/Autoupdate.app/Contents/Info.plist", x),
               sprintf("%s/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist", x),
               sprintf("%s/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist", x))
 
  is_there <- map_lgl(c(sprintf("%s/Contents/Frameworks/Sparkle.framework/", x), to_look), file.exists)
 
  has_sparkle <- any(is_there)
 
  to_look <- to_look[which(is_there[-1])]
 
  discard(map_chr(to_look, function(x) {
    read_plist(x)$result$CFBundleShortVersionString %||% NA
  }), is.na) -> vs
 
  if (any(map_dbl(vs, function(v) { safe_compare(v, "1.16.1")$result %||% -1 }) < 0)) {
    is_vuln <- TRUE
    version <- vs[1]
  }
 
  data_frame(app_loc, app_name, has_sparkle, is_vuln, version)
 
})) -> app_scan_results
 
close(pb)
 
select(arrange(filter(app_scan_results, has_sparkle), app_loc, app_name), -has_sparkle)

3 Comments

  1. I’m trying to generalize your awesome vulnerability checker to check other applications too. Not sure where the “NAs introduced by coercion” errors/warnings are coming from :\

    Looks like you're safe with the version of /Applications/GPG Keychain.app
    Looks like you're safe with the version of /Applications/Sequel Pro.app
    Looks like you're safe with the version of /Applications/Telephone.app
    Looks like you're safe with the version of /Applications/Texpad.app
    Looks like you're safe with the version of /Applications/Utilities/NoSleep.app
    Looks like you're safe with the version of /Applications/TeX/BibDesk.app
    Looks like you're safe with the version of /Applications/TeX/LaTeXiT.app
    Looks like you're safe with the version of /Applications/TeX/TeX Live Utility.app
    Warning messages:
    1: In .f(.x[[i]], ...) : NAs introduced by coercion
    2: In FUN(X[[i]], ...) : /Applications/Manuscripts.app is vulnerable
    3: In FUN(X[[i]], ...) : /Applications/Papers.app is vulnerable
    4: In .f(.x[[i]], ...) : NAs introduced by coercion
    5: In FUN(X[[i]], ...) : /Applications/SourceTree.app is vulnerable
    6: In FUN(X[[i]], ...) : /Applications/VLC.app is vulnerable
    7: In FUN(X[[i]], ...) : /Applications/Utilities/XQuartz.app is vulnerable
    8: In .f(.x[[i]], ...) : NAs introduced by coercion
    9: In FUN(X[[i]], ...) : /Applications/TeX/TeXShop.app is vulnerable

    Code:

    # Original code by Bob Rudis (@hrbrmstr)
    # "OS X XQuartz Vulnerability Test Using R" (http://rud.is/b/2016/03/07/os-x-xquartz-vulnerability-test-using-r/)
    # Modified to check for Sparkle vulnerability in other applications the user has installed.
     
    library(XML)
    library(purrr)
     
    apps <- paste0("/Applications/", list.files("/Applications", pattern = ".*.app", include.dirs = TRUE))
    utils <- paste0("/Applications/Utilities/", list.files("/Applications/Utilities", pattern = ".*.app", include.dirs = TRUE))
    tex <- paste0("/Applications/TeX/", list.files("/Applications/TeX", pattern = ".*.app", include.dirs = TRUE))
     
    invisible(lapply(c(apps, utils, tex), function(app) {
      info_locations <- c(paste0(app, "/Contents/Frameworks/Autoupdate.app/Contents/Info.plist"),
                          paste0(app, "/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist"))
      if (!dir.exists(paste0(app, "/Contents/Frameworks/Sparkle.framework"))) {
        # message(app, " doesn't use Sparkle")
        return(invisible())
      }
      discard(map_chr(info_locations, function(x) { 
        if (file.exists(x)) {
          readKeyValueDB(x)$CFBundleShortVersionString
        } else {
          NA
        }
      }), is.na) -> vs
     
      if ((length(vs) > 0) & (any(map_int(vs, compareVersion, "1.16.1") < 0))) { 
        warning(app, " is vulnerable") 
      } else {
        message("Looks like you're safe with the version of ", app)
      }
    }))
  2. Reposted as a Gist (https://gist.github.com/bearloga/1a91911c30e289ccc4d6) because apparently WordPress comments section is not a good place for including chunks of code :P

  3. +1. The particular extension I use for formatting code will work in comments :-) All I had to do was wrap R code segments in <pre lang="rsplus"></pre> tags.


One Trackback/Pingback

  1. […] article was first published on R – rud.is, and kindly contributed to […]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.