All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.mirror.contents.advisory.GhsaAdvisorEntry Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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 com.metaeffekt.mirror.contents.advisory;

import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.artifact.analysis.version.curation.VersionContext;
import com.metaeffekt.mirror.contents.base.DescriptionParagraph;
import com.metaeffekt.mirror.contents.base.Reference;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeStore;
import com.metaeffekt.mirror.contents.store.OtherTypeStore;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeStore;
import com.metaeffekt.mirror.contents.vulnerability.VulnerableSoftwareVersionRange;
import com.metaeffekt.mirror.contents.vulnerability.VulnerableSoftwareVersionRangeEcosystem;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.inventory.processor.report.model.AdvisoryUtils;
import org.metaeffekt.core.security.cvss.CvssSource;
import org.metaeffekt.core.security.cvss.KnownCvssEntities;
import org.metaeffekt.core.security.cvss.v2.Cvss2;
import org.metaeffekt.core.security.cvss.v3.Cvss3P1;
import org.metaeffekt.core.security.cvss.v4P0.Cvss4P0;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class GhsaAdvisorEntry extends AdvisoryEntry {

    private final static Logger LOG = LoggerFactory.getLogger(GhsaAdvisorEntry.class);

    protected final static Set CONVERSION_KEYS_AMB = new HashSet(AdvisoryEntry.CONVERSION_KEYS_AMB) {{
    }};

    protected final static Set CONVERSION_KEYS_MAP = new HashSet(AdvisoryEntry.CONVERSION_KEYS_MAP) {{
        add("severity");
        add("githubReviewed");
        add("githubReviewedAt");
        add("nvdPublishedAt");
        add("vulnerableSoftware");
    }};


    private String severity;
    private boolean githubReviewed;

    private Date githubReviewedAt;
    private Date nvdPublishedAt;

    private final List vulnerableSoftwares = new ArrayList<>();

    public GhsaAdvisorEntry() {
        super(AdvisoryTypeStore.GHSA);
    }

    public GhsaAdvisorEntry(String id) {
        super(AdvisoryTypeStore.GHSA, id);
    }

    public String getSeverity() {
        return severity;
    }

    public void setSeverity(String severity) {
        this.severity = severity;
    }

    public boolean isGithubReviewed() {
        return githubReviewed;
    }

    public List getVulnerableSoftwares() {
        return vulnerableSoftwares;
    }

    public void setGithubReviewed(boolean githubReviewed) {
        this.githubReviewed = githubReviewed;
    }

    public Date getGithubReviewedAt() {
        return githubReviewedAt;
    }

    public void setGithubReviewedAt(Date githubReviewedAt) {
        this.githubReviewedAt = githubReviewedAt;
    }

    public Date getNvdPublishedAt() {
        return nvdPublishedAt;
    }

    public void setNvdPublishedAt(Date nvdPublishedAt) {
        this.nvdPublishedAt = nvdPublishedAt;
    }

    @Override
    public String getUrl() {
        return "https://github.com/advisories/" + getId();
    }

    @Override
    public String getType() {
        return AdvisoryUtils.normalizeType("alert");
    }

    /* TYPE CONVERSION METHODS */

    @Override
    protected Set conversionKeysAmb() {
        return CONVERSION_KEYS_AMB;
    }

    @Override
    protected Set conversionKeysMap() {
        return CONVERSION_KEYS_MAP;
    }

    @Override
    public GhsaAdvisorEntry constructDataClass() {
        return new GhsaAdvisorEntry();
    }

    public static GhsaAdvisorEntry fromAdvisoryMetaData(AdvisoryMetaData amd) {
        return AdvisoryEntry.fromAdvisoryMetaData(amd, GhsaAdvisorEntry::new);
    }

    public static GhsaAdvisorEntry fromInputMap(Map map) {
        return AdvisoryEntry.fromInputMap(map, GhsaAdvisorEntry::new);
    }

    public static GhsaAdvisorEntry fromJson(JSONObject json) {
        return AdvisoryEntry.fromJson(json, GhsaAdvisorEntry::new);
    }

    public static GhsaAdvisorEntry fromDocument(Document document) {
        return AdvisoryEntry.fromDocument(document, GhsaAdvisorEntry::new);
    }

    @Override
    public void appendFromBaseModel(AdvisoryMetaData amd) {
        super.appendFromBaseModel(amd);
    }

    @Override
    public void appendToBaseModel(AdvisoryMetaData amd) {
        super.appendToBaseModel(amd);
    }

    @Override
    public void appendFromMap(Map map) {
        super.appendFromMap(map);

        this.setSeverity((String) map.getOrDefault("severity", null));
        this.setGithubReviewed((boolean) map.getOrDefault("githubReviewed", false));
        this.setGithubReviewedAt(TimeUtils.tryParse(map.getOrDefault("githubReviewedAt", null)));
        this.setNvdPublishedAt(TimeUtils.tryParse(map.getOrDefault("nvdPublishedAt", null)));

        final List vulnerableSoftwareArray = (List) map.getOrDefault("vulnerableSoftware", null);
        this.vulnerableSoftwares.addAll(createVulnerableSoftwareConfigurationsFromJson(vulnerableSoftwareArray));
    }

    @Override
    public void appendToJson(JSONObject json) {
        super.appendToJson(json);

        json.put("severity", getSeverity());
        json.put("githubReviewed", isGithubReviewed());
        json.put("githubReviewedAt", ObjectUtils.defaultIfNull(githubReviewedAt == null ? null : githubReviewedAt.getTime(), JSONObject.NULL));
        json.put("nvdPublishedAt", ObjectUtils.defaultIfNull(nvdPublishedAt == null ? null : nvdPublishedAt.getTime(), JSONObject.NULL));

        final JSONArray vulnerableSoftwareArray = new JSONArray();
        for (VulnerableSoftwareVersionRange vulnerableSoftware : vulnerableSoftwares) {
            vulnerableSoftwareArray.put(vulnerableSoftware.toJson());
        }
        json.put("vulnerableSoftware", vulnerableSoftwareArray);
    }

    @Override
    public void appendFromDocument(Document document) {
        super.appendFromDocument(document);

        this.setSeverity(document.get("severity"));
        this.setGithubReviewed(Boolean.parseBoolean(document.get("githubReviewed")));
        this.setGithubReviewedAt(TimeUtils.tryParse(document.get("githubReviewedAt")));
        this.setNvdPublishedAt(TimeUtils.tryParse(document.get("nvdPublishedAt")));

        final String vulnerableSoftwareJson = document.get("vulnerableSoftware");
        if (vulnerableSoftwareJson != null) {
            this.vulnerableSoftwares.addAll(createVulnerableSoftwareConfigurationsFromJson(new JSONArray(vulnerableSoftwareJson)));
        }
    }

    @Override
    public void appendToDocument(Document document) {
        super.appendToDocument(document);

        super.addToDocumentAsTextFieldIfNotEmpty(document, "severity", getSeverity());
        document.add(new StringField("githubReviewed", String.valueOf(isGithubReviewed()), Field.Store.YES));
        super.addToDocumentAsTextFieldIfNotEmpty(document, "githubReviewedAt", githubReviewedAt == null ? null : Long.toString(githubReviewedAt.getTime()));
        super.addToDocumentAsTextFieldIfNotEmpty(document, "nvdPublishedAt", nvdPublishedAt == null ? null : Long.toString(nvdPublishedAt.getTime()));

        final JSONArray vulnerableSoftwareArray = new JSONArray();
        final List vulnerableSoftwareNamePhrases = new ArrayList<>();

        for (VulnerableSoftwareVersionRangeEcosystem vulnerableSoftware : vulnerableSoftwares) {
            vulnerableSoftwareArray.put(vulnerableSoftware.toJson());
            vulnerableSoftwareNamePhrases.addAll(Arrays.asList(vulnerableSoftware.getName().split("[.:-]")));
        }

        super.addToDocumentAsTextFieldIfNotEmpty(document, "vulnerableSoftware", vulnerableSoftwareArray.toString());
        super.addToDocumentAsTextFieldIfNotEmpty(document, "vulnerableSoftwareNamePhrases", String.join(" ", vulnerableSoftwareNamePhrases));
    }

    private static List createVulnerableSoftwareConfigurationsFromJson(JSONArray vulnerableSoftwareArray) {
        final List entries = new ArrayList<>();

        if (vulnerableSoftwareArray != null) {
            for (int i = 0; i < vulnerableSoftwareArray.length(); i++) {
                final JSONObject vulnerableSoftwareJson = vulnerableSoftwareArray.optJSONObject(i);
                if (vulnerableSoftwareJson != null) {
                    entries.add(VulnerableSoftwareVersionRangeEcosystem.fromJson(vulnerableSoftwareJson));
                }
            }
        }

        return entries;
    }

    private static List createVulnerableSoftwareConfigurationsFromJson(List vulnerableSoftwareArray) {
        final List entries = new ArrayList<>();

        if (vulnerableSoftwareArray != null) {
            for (Object vulnerableSoftwareJson : vulnerableSoftwareArray) {
                if (vulnerableSoftwareJson instanceof Map) {
                    entries.add(VulnerableSoftwareVersionRangeEcosystem.fromJson(new JSONObject(vulnerableSoftwareJson)));
                } else if (vulnerableSoftwareJson instanceof JSONObject) {
                    entries.add(VulnerableSoftwareVersionRangeEcosystem.fromJson((JSONObject) vulnerableSoftwareJson));
                } else {
                    LOG.warn("Unknown vulnerable software type: [{}]", vulnerableSoftwareJson.getClass());
                }
            }
        }

        return entries;
    }

    /**
     * Parses the JSON representation as provided from the git repository of a GHSA advisory.
* An example can be found * here. * * @param json The JSON object as provided from the git repository. * @return The parsed advisory entry. */ public static GhsaAdvisorEntry fromGitRepoJson(JSONObject json) { final GhsaAdvisorEntry entry = new GhsaAdvisorEntry(); entry.setId(json.getString("id")); entry.setUpdateDate(TimeUtils.tryParse(json.optString("modified", null))); entry.setCreateDate(TimeUtils.tryParse(json.optString("published", null))); entry.setSummary(json.optString("summary", null)); if (!json.isNull("details")) { entry.setDescription(DescriptionParagraph.fromTitleAndContent("details", json.getString("details"))); } final JSONArray aliases = json.optJSONArray("aliases"); if (aliases != null) { for (int i = 0; i < aliases.length(); i++) { final String alias = aliases.optString(i, null); if (alias == null) continue; if (alias.startsWith("CVE-")) { entry.addReferencedVulnerability(VulnerabilityTypeStore.CVE, alias); } } } final JSONArray ref = json.optJSONArray("references"); if (ref != null) { for (int i = 0; i < ref.length(); i++) { final JSONObject refEntry = ref.getJSONObject(i); final String type = refEntry.getString("type"); final String url = refEntry.getString("url"); entry.addReference(Reference.fromUrlAndTags(url, type)); } } final JSONObject dbSpecific = json.optJSONObject("database_specific"); if (dbSpecific != null) { final JSONArray cweIds = dbSpecific.getJSONArray("cwe_ids"); for (int i = 0; i < cweIds.length(); i++) { entry.addOtherReferencedId(OtherTypeStore.CWE, cweIds.getString(i)); } entry.setSeverity(dbSpecific.optString("severity", null)); entry.setGithubReviewed(dbSpecific.optBoolean("github_reviewed", false)); entry.setGithubReviewedAt(TimeUtils.tryParse(dbSpecific.optString("github_reviewed_at", null))); entry.setNvdPublishedAt(TimeUtils.tryParse(dbSpecific.optString("nvd_published_at", null))); } final JSONArray severity = json.optJSONArray("severity"); if (severity != null) { final CvssSource baseSourceV3 = new CvssSource(KnownCvssEntities.GHSA, Cvss3P1.class); for (int i = 0; i < severity.length(); i++) { final JSONObject severityEntry = severity.getJSONObject(i); final String type = severityEntry.getString("type"); final String value = ObjectUtils.firstNonNull(severityEntry.optString("value", null), severityEntry.optString("score", null)); if (value == null) { LOG.warn("Severity value is null for type: [{}]", type); continue; } switch (type) { case CVSS_3_KEY: entry.getCvssVectors().addCvssVector(new Cvss3P1(value, baseSourceV3)); break; case CVSS_2_KEY: entry.getCvssVectors().addCvssVector(new Cvss2(value, baseSourceV3.deriveSource(Cvss2.class))); break; case CVSS_4_KEY: entry.getCvssVectors().addCvssVector(new Cvss4P0(value, baseSourceV3.deriveSource(Cvss4P0.class))); break; default: LOG.warn("Unknown severity type: [{}] with value [{}]", type, value); break; } } } final JSONArray affected = json.optJSONArray("affected"); if (affected != null) { for (int i = 0; i < affected.length(); i++) { final JSONObject affectedEntry = affected.getJSONObject(i); final JSONObject packageJson = affectedEntry.getJSONObject("package"); final String name = packageJson.getString("name"); final String ecosystem = packageJson.getString("ecosystem"); final JSONArray ranges = affectedEntry.optJSONArray("ranges"); if (ranges != null) { for (int j = 0; j < ranges.length(); j++) { final JSONObject range = ranges.getJSONObject(j); final String type = range.getString("type"); if (type.equals("ECOSYSTEM")) { final JSONArray events = range.getJSONArray("events"); String introducedVersion = null; String fixedVersion = null; String lastAffected = null; for (int k = 0; k < events.length(); k++) { final JSONObject event = events.getJSONObject(k); if (event.has("introduced")) { introducedVersion = event.getString("introduced"); } else if (event.has("fixed")) { fixedVersion = event.getString("fixed"); } else if (event.has("last_affected")) { lastAffected = event.getString("last_affected"); } else { LOG.warn("Unknown event type: [{}] on [{}]", event.keySet(), entry.getId()); } } final String versionStartIncluding = introducedVersion; final String versionEndExcluding = fixedVersion; final String versionEndIncluding = lastAffected; final VulnerableSoftwareVersionRangeEcosystem vs = new VulnerableSoftwareVersionRangeEcosystem( ecosystem, name, null, null, null, versionStartIncluding, versionEndIncluding, versionEndExcluding, VersionContext.fromGhsaProduct(name), true); entry.vulnerableSoftwares.add(vs); } else { LOG.warn("Unknown range type: [{}]", type); } } } } } return entry; } // TODO: AEAA-356: actual CVSS 4.0 key is not known yet, this is just an assumption private final static String CVSS_4_KEY = "CVSS_V4"; private final static String CVSS_3_KEY = "CVSS_V3"; /** * Does not currently exist in the JSON structure, but will be checked for anyway. */ private final static String CVSS_2_KEY = "CVSS_V2"; }