1package scan 2 3import ( 4 "net" 5 "strings" 6 7 "github.com/future-architect/vuls/config" 8 "github.com/future-architect/vuls/models" 9 "github.com/future-architect/vuls/util" 10 "golang.org/x/xerrors" 11) 12 13// inherit OsTypeInterface 14type bsd struct { 15 base 16} 17 18// NewBSD constructor 19func newBsd(c config.ServerInfo) *bsd { 20 d := &bsd{ 21 base: base{ 22 osPackages: osPackages{ 23 Packages: models.Packages{}, 24 VulnInfos: models.VulnInfos{}, 25 }, 26 }, 27 } 28 d.log = util.NewCustomLogger(c) 29 d.setServerInfo(c) 30 return d 31} 32 33//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb 34func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) { 35 bsd = newBsd(c) 36 37 // Prevent from adding `set -o pipefail` option 38 c.Distro = config.Distro{Family: config.FreeBSD} 39 40 if r := exec(c, "uname", noSudo); r.isSuccess() { 41 if strings.Contains(strings.ToLower(r.Stdout), config.FreeBSD) == true { 42 if b := exec(c, "freebsd-version", noSudo); b.isSuccess() { 43 rel := strings.TrimSpace(b.Stdout) 44 bsd.setDistro(config.FreeBSD, rel) 45 return true, bsd 46 } 47 } 48 } 49 util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName) 50 return false, bsd 51} 52 53func (o *bsd) checkScanMode() error { 54 if o.getServerInfo().Mode.IsOffline() { 55 return xerrors.New("Remove offline scan mode, FreeBSD needs internet connection") 56 } 57 return nil 58} 59 60func (o *bsd) checkIfSudoNoPasswd() error { 61 // FreeBSD doesn't need root privilege 62 o.log.Infof("sudo ... No need") 63 return nil 64} 65 66func (o *bsd) checkDeps() error { 67 o.log.Infof("Dependencies... No need") 68 return nil 69} 70 71func (o *bsd) preCure() error { 72 o.log.Infof("Scanning in %s", o.getServerInfo().Mode) 73 if err := o.detectIPAddr(); err != nil { 74 o.log.Debugf("Failed to detect IP addresses: %s", err) 75 } 76 // Ignore this error as it just failed to detect the IP addresses 77 return nil 78} 79 80func (o *bsd) postScan() error { 81 return nil 82} 83 84func (o *bsd) detectIPAddr() (err error) { 85 r := o.exec("/sbin/ifconfig", noSudo) 86 if !r.isSuccess() { 87 return xerrors.Errorf("Failed to detect IP address: %v", r) 88 } 89 o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs = o.parseIfconfig(r.Stdout) 90 return nil 91} 92 93func (l *base) parseIfconfig(stdout string) (ipv4Addrs []string, ipv6Addrs []string) { 94 lines := strings.Split(stdout, "\n") 95 for _, line := range lines { 96 line = strings.TrimSpace(line) 97 fields := strings.Fields(line) 98 if len(fields) < 4 || !strings.HasPrefix(fields[0], "inet") { 99 continue 100 } 101 ip := net.ParseIP(fields[1]) 102 if ip == nil { 103 continue 104 } 105 if !ip.IsGlobalUnicast() { 106 continue 107 } 108 if ipv4 := ip.To4(); ipv4 != nil { 109 ipv4Addrs = append(ipv4Addrs, ipv4.String()) 110 } else { 111 ipv6Addrs = append(ipv6Addrs, ip.String()) 112 } 113 } 114 return 115} 116 117func (o *bsd) scanPackages() error { 118 // collect the running kernel information 119 release, version, err := o.runningKernel() 120 if err != nil { 121 o.log.Errorf("Failed to scan the running kernel version: %s", err) 122 return err 123 } 124 o.Kernel = models.Kernel{ 125 Release: release, 126 Version: version, 127 } 128 129 o.Kernel.RebootRequired, err = o.rebootRequired() 130 if err != nil { 131 err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err) 132 o.log.Warnf("err: %+v", err) 133 o.warns = append(o.warns, err) 134 // Only warning this error 135 } 136 137 packs, err := o.scanInstalledPackages() 138 if err != nil { 139 o.log.Errorf("Failed to scan installed packages: %s", err) 140 return err 141 } 142 o.Packages = packs 143 144 unsecures, err := o.scanUnsecurePackages() 145 if err != nil { 146 o.log.Errorf("Failed to scan vulnerable packages: %s", err) 147 return err 148 } 149 o.VulnInfos = unsecures 150 return nil 151} 152 153func (o *bsd) parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) { 154 return nil, nil, nil 155} 156 157func (o *bsd) rebootRequired() (bool, error) { 158 r := o.exec("freebsd-version -k", noSudo) 159 if !r.isSuccess() { 160 return false, xerrors.Errorf("Failed to SSH: %s", r) 161 } 162 return o.Kernel.Release != strings.TrimSpace(r.Stdout), nil 163} 164 165func (o *bsd) scanInstalledPackages() (models.Packages, error) { 166 // https://github.com/future-architect/vuls/issues/1042 167 cmd := util.PrependProxyEnv("pkg info") 168 r := o.exec(cmd, noSudo) 169 if !r.isSuccess() { 170 return nil, xerrors.Errorf("Failed to SSH: %s", r) 171 } 172 pkgs := o.parsePkgInfo(r.Stdout) 173 174 cmd = util.PrependProxyEnv("pkg version -v") 175 r = o.exec(cmd, noSudo) 176 if !r.isSuccess() { 177 return nil, xerrors.Errorf("Failed to SSH: %s", r) 178 } 179 // `pkg-audit` has a new version, overwrite it. 180 for name, p := range o.parsePkgVersion(r.Stdout) { 181 pkgs[name] = p 182 } 183 return pkgs, nil 184} 185 186func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) { 187 const vulndbPath = "/tmp/vuln.db" 188 cmd := "rm -f " + vulndbPath 189 r := o.exec(cmd, noSudo) 190 if !r.isSuccess(0) { 191 return nil, xerrors.Errorf("Failed to SSH: %s", r) 192 } 193 194 cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath) 195 r = o.exec(cmd, noSudo) 196 if !r.isSuccess(0, 1) { 197 return nil, xerrors.Errorf("Failed to SSH: %s", r) 198 } 199 if r.ExitStatus == 0 { 200 // no vulnerabilities 201 return nil, nil 202 } 203 204 packAdtRslt := []pkgAuditResult{} 205 blocks := o.splitIntoBlocks(r.Stdout) 206 for _, b := range blocks { 207 name, cveIDs, vulnID := o.parseBlock(b) 208 if len(cveIDs) == 0 { 209 continue 210 } 211 pack, found := o.Packages[name] 212 if !found { 213 return nil, xerrors.Errorf("Vulnerable package: %s is not found", name) 214 } 215 packAdtRslt = append(packAdtRslt, pkgAuditResult{ 216 pack: pack, 217 vulnIDCveIDs: vulnIDCveIDs{ 218 vulnID: vulnID, 219 cveIDs: cveIDs, 220 }, 221 }) 222 } 223 224 // { CVE ID: []pkgAuditResult } 225 cveIDAdtMap := make(map[string][]pkgAuditResult) 226 for _, p := range packAdtRslt { 227 for _, cid := range p.vulnIDCveIDs.cveIDs { 228 cveIDAdtMap[cid] = append(cveIDAdtMap[cid], p) 229 } 230 } 231 232 vinfos := models.VulnInfos{} 233 for cveID := range cveIDAdtMap { 234 packs := models.Packages{} 235 for _, r := range cveIDAdtMap[cveID] { 236 packs[r.pack.Name] = r.pack 237 } 238 239 disAdvs := []models.DistroAdvisory{} 240 for _, r := range cveIDAdtMap[cveID] { 241 disAdvs = append(disAdvs, models.DistroAdvisory{ 242 AdvisoryID: r.vulnIDCveIDs.vulnID, 243 }) 244 } 245 246 affected := models.PackageFixStatuses{} 247 for name := range packs { 248 affected = append(affected, models.PackageFixStatus{ 249 Name: name, 250 }) 251 } 252 vinfos[cveID] = models.VulnInfo{ 253 CveID: cveID, 254 AffectedPackages: affected, 255 DistroAdvisories: disAdvs, 256 Confidences: models.Confidences{models.PkgAuditMatch}, 257 } 258 } 259 return vinfos, nil 260} 261 262func (o *bsd) parsePkgInfo(stdout string) models.Packages { 263 packs := models.Packages{} 264 lines := strings.Split(stdout, "\n") 265 for _, l := range lines { 266 fields := strings.Fields(l) 267 if len(fields) < 2 { 268 continue 269 } 270 271 packVer := fields[0] 272 splitted := strings.Split(packVer, "-") 273 ver := splitted[len(splitted)-1] 274 name := strings.Join(splitted[:len(splitted)-1], "-") 275 packs[name] = models.Package{ 276 Name: name, 277 Version: ver, 278 } 279 } 280 return packs 281} 282 283func (o *bsd) parsePkgVersion(stdout string) models.Packages { 284 packs := models.Packages{} 285 lines := strings.Split(stdout, "\n") 286 for _, l := range lines { 287 fields := strings.Fields(l) 288 if len(fields) < 2 { 289 continue 290 } 291 292 packVer := fields[0] 293 splitted := strings.Split(packVer, "-") 294 ver := splitted[len(splitted)-1] 295 name := strings.Join(splitted[:len(splitted)-1], "-") 296 297 switch fields[1] { 298 case "?", "=": 299 packs[name] = models.Package{ 300 Name: name, 301 Version: ver, 302 } 303 case "<": 304 candidate := strings.TrimSuffix(fields[6], ")") 305 packs[name] = models.Package{ 306 Name: name, 307 Version: ver, 308 NewVersion: candidate, 309 } 310 case ">": 311 o.log.Warnf("The installed version of the %s is newer than the current version. *This situation can arise with an out of date index file, or when testing new ports.*", name) 312 packs[name] = models.Package{ 313 Name: name, 314 Version: ver, 315 } 316 } 317 } 318 return packs 319} 320 321type vulnIDCveIDs struct { 322 vulnID string 323 cveIDs []string 324} 325 326type pkgAuditResult struct { 327 pack models.Package 328 vulnIDCveIDs vulnIDCveIDs 329} 330 331func (o *bsd) splitIntoBlocks(stdout string) (blocks []string) { 332 lines := strings.Split(stdout, "\n") 333 block := []string{} 334 for _, l := range lines { 335 if len(strings.TrimSpace(l)) == 0 { 336 if 0 < len(block) { 337 blocks = append(blocks, strings.Join(block, "\n")) 338 block = []string{} 339 } 340 continue 341 } 342 block = append(block, strings.TrimSpace(l)) 343 } 344 if 0 < len(block) { 345 blocks = append(blocks, strings.Join(block, "\n")) 346 } 347 return 348} 349 350func (o *bsd) parseBlock(block string) (packName string, cveIDs []string, vulnID string) { 351 lines := strings.Split(block, "\n") 352 for _, l := range lines { 353 if strings.HasSuffix(l, " is vulnerable:") { 354 packVer := strings.Fields(l)[0] 355 splitted := strings.Split(packVer, "-") 356 packName = strings.Join(splitted[:len(splitted)-1], "-") 357 } else if strings.HasPrefix(l, "CVE:") { 358 cveIDs = append(cveIDs, strings.Fields(l)[1]) 359 } else if strings.HasPrefix(l, "WWW:") { 360 splitted := strings.Split(l, "/") 361 vulnID = strings.TrimSuffix(splitted[len(splitted)-1], ".html") 362 } 363 } 364 return 365} 366