VYPR
Moderate severityNVD Advisory· Published Jul 27, 2022· Updated Aug 3, 2024

CVE-2022-36910

CVE-2022-36910

Description

Jenkins Lucene-Search Plugin lacks permission checks in several HTTP endpoints, allowing attackers with Overall/Read to reindex the database and access restricted job information.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins Lucene-Search Plugin lacks permission checks in several HTTP endpoints, allowing attackers with Overall/Read to reindex the database and access restricted job information.

Vulnerability

Overview

CVE-2022-36910 affects the Jenkins Lucene-Search Plugin version 370.v62a5f618cd3a and earlier. The plugin fails to perform permission checks on several HTTP endpoints, meaning that any user with the Overall/Read permission can invoke these endpoints without proper authorization [1][3]. This is a missing authorization issue (CWE-862).

Attack

Vector and Exploitation

An attacker who already has Overall/Read access to the Jenkins instance can exploit these unprotected endpoints. The vulnerable endpoints allow the attacker to trigger a reindexing of the Lucene search database and retrieve information about jobs that would normally be inaccessible to them (e.g., jobs they are not allowed to view) [1][3]. No additional authentication or higher privileges are required beyond Overall/Read.

Impact

Successful exploitation could lead to two main consequences: first, the attacker can cause a reindex of the search database, which may be a resource-intensive operation impacting performance. Second, and more critically, the attacker can obtain sensitive information from jobs that are otherwise restricted from their view, leading to an information disclosure breach [3].

Mitigation

The fix is included in Lucene-Search Plugin version 371.v9e84c73fbe75 and later. The patch added permission checks (requiring Jenkins.ADMINISTER) to the previously unprotected endpoints [4]. Jenkins administrators should upgrade to the latest version of the plugin as soon as possible.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.jenkins-ci.plugins:lucene-searchMaven
< 387.v938a387.v938a

Affected products

2

Patches

1
b56e0aba81a3

Merge pull request #55 from tdraebing/fix-CVE-2022-36910

9 files changed · +78 56
  • pom.xml+2 2 modified
    @@ -101,14 +101,14 @@
     	<repositories>
     		<repository>
     			<id>repo.jenkins-ci.org</id>
    -			<url>http://repo.jenkins-ci.org/public/</url>
    +			<url>https://repo.jenkins-ci.org/public/</url>
     		</repository>
     	</repositories>
     
     	<pluginRepositories>
     		<pluginRepository>
     			<id>repo.jenkins-ci.org</id>
    -			<url>http://repo.jenkins-ci.org/public/</url>
    +			<url>https://repo.jenkins-ci.org/public/</url>
     		</pluginRepository>
     	</pluginRepositories>
     </project>
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/config/SearchBackendConfiguration.java+5 32 modified
    @@ -6,10 +6,7 @@
     
     import java.io.File;
     import java.io.IOException;
    -import java.io.InputStream;
    -import java.util.ArrayList;
     import java.util.HashMap;
    -import java.util.List;
     import java.util.Map;
     
     import javax.inject.Inject;
    @@ -18,11 +15,6 @@
     import jenkins.model.Jenkins;
     import net.sf.json.JSONObject;
     
    -import org.apache.commons.io.IOUtils;
    -import org.apache.http.HttpResponse;
    -import org.apache.http.client.HttpClient;
    -import org.apache.http.client.methods.HttpGet;
    -import org.apache.http.impl.client.HttpClientBuilder;
     import org.jenkinsci.plugins.lucene.search.databackend.SearchBackendManager;
     import org.kohsuke.stapler.DataBoundConstructor;
     import org.kohsuke.stapler.QueryParameter;
    @@ -38,7 +30,7 @@ public class SearchBackendConfiguration extends GlobalConfiguration {
         private transient SearchBackendManager backendManager;
     
         private File lucenePath = new File(Jenkins.getInstance().getRootDir(), "luceneIndex");
    -    private boolean useSecurity;
    +    private boolean useSecurity = true;
     
         @DataBoundConstructor
         public SearchBackendConfiguration(final String lucenePath,
    @@ -56,15 +48,13 @@ public SearchBackendConfiguration() {
             load();
         }
     
    -    public String getLucenePath() {
    -        return lucenePath.toString();
    -    }
    -
         public void setLucenePath(final File lucenePath) {
    +        Jenkins.get().getACL().checkPermission(Jenkins.ADMINISTER);
             this.lucenePath = lucenePath;
         }
     
         public FormValidation doCheckLucenePath(@QueryParameter final String lucenePath) {
    +        Jenkins.get().getACL().checkPermission(Jenkins.ADMINISTER);
             try {
                 new File(lucenePath);
                 return FormValidation.ok();
    @@ -73,25 +63,6 @@ public FormValidation doCheckLucenePath(@QueryParameter final String lucenePath)
             }
         }
     
    -    @SuppressWarnings({ "unchecked", "rawtypes" })
    -    private List<String> getCollections(String baseUrl) throws IOException {
    -        HttpClient httpClient = HttpClientBuilder.create().build();
    -        String url = baseUrl + "/admin/cores?wt=json";
    -        HttpGet get = new HttpGet(url);
    -        HttpResponse response = httpClient.execute(get);
    -        if (response.getStatusLine().getStatusCode() != 200) {
    -            throw new IOException("Failed to GET " + url);
    -        }
    -        InputStream content = response.getEntity().getContent();
    -        try {
    -            String jsonString = IOUtils.toString(content, "UTF-8");
    -            JSONObject json = JSONObject.fromObject(jsonString);
    -            return new ArrayList(json.getJSONObject("status").keySet());
    -        } finally {
    -            IOUtils.closeQuietly(content);
    -        }
    -    }
    -
         @Override
         public boolean configure(final StaplerRequest req, final JSONObject json) throws FormException {
             JSONObject selectedJson = json.getJSONObject("searchBackend");
    @@ -113,6 +84,7 @@ public boolean configure(final StaplerRequest req, final JSONObject json) throws
     
         @VisibleForTesting
         public void reconfigure() throws IOException {
    +        Jenkins.get().getACL().checkPermission(Jenkins.ADMINISTER);
             backendManager.reconfigure(getConfig());
             save();
         }
    @@ -134,6 +106,7 @@ public boolean isUseSecurity() {
         }
     
         public void setUseSecurity(boolean useSecurity) {
    +        Jenkins.get().getACL().checkPermission(Jenkins.ADMINISTER);
             this.useSecurity = useSecurity;
         }
     }
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/databackend/Progress.java+7 6 modified
    @@ -1,5 +1,7 @@
     package org.jenkinsci.plugins.lucene.search.databackend;
     
    +import java.util.concurrent.atomic.AtomicInteger;
    +
     public class Progress {
     
         public enum ProgressState {
    @@ -13,7 +15,7 @@ public enum ProgressState {
         private transient Exception reason;
         private String reasonMessage = "";
         private int max;
    -    private volatile int current;
    +    private AtomicInteger current = new AtomicInteger(0);
         private String name;
     
         public void assertNoErrors() throws Exception {
    @@ -29,7 +31,6 @@ public Progress() {
         public Progress(String name) {
             this.setName(name);
             startTime = System.currentTimeMillis();
    -        current = 0;
             max = 0;
         }
     
    @@ -78,15 +79,15 @@ public void setMax(int max) {
         }
     
         public int getCurrent() {
    -        return current;
    +        return current.get();
         }
     
         public void setCurrent(int current) {
    -        this.current = current;
    +        this.current.set(current);
         }
     
    -    public synchronized void incCurrent() {
    -        this.current++;
    +    public void incCurrent() {
    +        this.current.incrementAndGet();
         }
     
         public String getName() {
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/Field.java+5 0 modified
    @@ -10,6 +10,8 @@
     
     import org.apache.commons.io.output.ByteArrayOutputStream;
     
    +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    +
     public enum Field {
     
         PROJECT_NAME("j", Persist.TRUE) {
    @@ -55,6 +57,9 @@ public String getValue(Run<?, ?> build) {
         },
     
         CONSOLE("c", Persist.TRUE) {
    +        @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED",
    +            justification = "The offset returned by writeLogTo() can be ignored, " +
    +                "since no furtehr text is written to the output stream.")
             @Override
             public String getValue(Run<?, ?> build) {
                 try {
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/FreeTextSearchItemImplementation.java+5 4 modified
    @@ -4,6 +4,7 @@
     import hudson.search.SearchIndex;
     import hudson.search.SearchItem;
     
    +import java.util.ArrayList;
     import java.util.List;
     import java.util.regex.Pattern;
     
    @@ -13,7 +14,7 @@ public class FreeTextSearchItemImplementation extends FreeTextSearchItem {
     
         private final String projectName;
         private final boolean isShowConsole;
    -    private final String[] bestFragments;
    +    private final List<String> bestFragments;
         private final String url;
         private final String searchName;
     
    @@ -23,9 +24,9 @@ public FreeTextSearchItemImplementation(final String searchName, final String pr
             this.projectName = projectName;
             this.url = url;
             this.isShowConsole = isShowConsole;
    -        this.bestFragments = new String[bestFragments.length];
    +        this.bestFragments = new ArrayList<>(bestFragments.length);
             for (int i = 0; i < bestFragments.length; i++) {
    -            this.bestFragments[i] = LINE_ENDINGS.matcher(bestFragments[i]).replaceAll("<br/>");
    +            this.bestFragments.add(LINE_ENDINGS.matcher(bestFragments[i]).replaceAll("<br/>"));
             }
     
         }
    @@ -45,7 +46,7 @@ public String getProjectName() {
         }
     
         public String[] getBestFragments() {
    -        return bestFragments;
    +        return bestFragments.toArray(new String[bestFragments.size()]);
         }
     
         @Override
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/management/LuceneManager.java+46 6 modified
    @@ -46,6 +46,7 @@ public String getUrlName() {
     
         @JavaScriptMethod
         public JSReturnCollection rebuildDatabase(int workers, String jobNames, String overwrite) {
    +        Jenkins.get().getACL().checkPermission(getRequiredPermission());
             JSReturnCollection statement = verifyNotInProgress();
             this.workers = workers;
             if (this.workers <= 0) {
    @@ -99,6 +100,7 @@ private JSReturnCollection verifyNotInProgress() {
     
         @JavaScriptMethod
         public JSReturnCollection abort() {
    +        Jenkins.get().getACL().checkPermission(getRequiredPermission());
             JSReturnCollection statement = verifyNotInProgress();
             backendManager.abort();
             this.progress = null;
    @@ -107,6 +109,7 @@ public JSReturnCollection abort() {
     
         @JavaScriptMethod
         public JSReturnCollection clean() {
    +        Jenkins.get().getACL().checkPermission(getRequiredPermission());
             JSReturnCollection statement = verifyNotInProgress();
             if (statement.code == 0) {
                 progress = new ManagerProgress();
    @@ -119,6 +122,7 @@ public JSReturnCollection clean() {
     
         @JavaScriptMethod
         public JSReturnCollection getStatus() {
    +        Jenkins.get().getACL().checkPermission(getRequiredPermission());
             JSReturnCollection statement = new JSReturnCollection();
             if (progress != null) {
                 statement.progress = progress;
    @@ -157,11 +161,47 @@ public void writeStatus(StaplerResponse rsp, JSReturnCollection status) throws I
         }
     
         public static class JSReturnCollection {
    -        public int code;
    -        public String message = "";
    -        public boolean running;
    -        public ManagerProgress progress;
    -        public int workers;
    -        public boolean neverStarted;
    +        private int code;
    +        private String message = "";
    +        private boolean running;
    +        private ManagerProgress progress;
    +        private int workers;
    +        private boolean neverStarted;
    +		public int getCode() {
    +			return code;
    +		}
    +		public void setCode(int code) {
    +			this.code = code;
    +		}
    +		public String getMessage() {
    +			return message;
    +		}
    +		public void setMessage(String message) {
    +			this.message = message;
    +		}
    +		public boolean isRunning() {
    +			return running;
    +		}
    +		public void setRunning(boolean running) {
    +			this.running = running;
    +		}
    +		public ManagerProgress getProgress() {
    +			return progress;
    +		}
    +		public void setProgress(ManagerProgress progress) {
    +			this.progress = progress;
    +		}
    +		public int getWorkers() {
    +			return workers;
    +		}
    +		public void setWorkers(int workers) {
    +			this.workers = workers;
    +		}
    +		public boolean isNeverStarted() {
    +			return neverStarted;
    +		}
    +		public void setNeverStarted(boolean neverStarted) {
    +			this.neverStarted = neverStarted;
    +		}
         }
     }
    
  • src/main/java/org/jenkinsci/plugins/lucene/search/SearchResultImpl.java+1 1 modified
    @@ -9,7 +9,7 @@ public class SearchResultImpl extends ArrayList<SuggestedItem> implements Search
     
         private static final long serialVersionUID = 1L;
     
    -    private final boolean hasMoreResults = false;
    +    private static final boolean hasMoreResults = false;
     
         @Override
         public boolean hasMoreResults() {
    
  • src/main/resources/org/jenkinsci/plugins/lucene/search/config/SearchBackendConfiguration/config.jelly+3 0 modified
    @@ -11,5 +11,8 @@
                     </f:entry>
                 </f:dropdownListBlock>
             </f:dropdownList>
    +        <f:entry title="${%Use user security}" field="useSecurity" description="${%Only show search results the user may read. Will slow down searches _considerably_.}">
    +            <f:checkbox/>
    +        </f:entry>
         </f:section>
     </j:jelly>
    
  • src/test/java/org/jenkinsci/plugins/lucene/search/databackend/CommonTestCases.java+4 5 modified
    @@ -3,7 +3,6 @@
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertFalse;
     import static org.junit.Assert.assertNull;
    -import static org.junit.Assert.assertTrue;
     
     import com.google.gson.Gson;
     import com.google.gson.GsonBuilder;
    @@ -38,7 +37,7 @@ public static void rebuildDatabase(final JenkinsSearchBackend jenkinsSearchBacke
                 public Throwable call() throws Exception {
                     try {
                         LuceneManager.JSReturnCollection jsonObject = jenkinsSearchBackend.getRebuildStatus(rebuildUrl);
    -                    assertEquals(GSON.toJson(jsonObject), 0, jsonObject.code);
    +                    assertEquals(GSON.toJson(jsonObject), 0, jsonObject.getCode());
                         return null;
                     } catch (Exception e) {
                         return e;
    @@ -49,12 +48,12 @@ public Throwable call() throws Exception {
             assertNull(throwable);
             LuceneManager.JSReturnCollection jsonObject = jenkinsSearchBackend.getRebuildStatus(statusUrl);
             long started = System.currentTimeMillis();
    -        while ((jsonObject.running || jsonObject.neverStarted) && started + 10000 > System.currentTimeMillis()) {
    +        while ((jsonObject.isRunning() || jsonObject.isNeverStarted()) && started + 10000 > System.currentTimeMillis()) {
                 Thread.sleep(1000);
                 jsonObject = jenkinsSearchBackend.getRebuildStatus(statusUrl);
             }
    -        assertFalse("Test took too long", jsonObject.running || jsonObject.neverStarted);
    -        assertEquals(GSON.toJson(jsonObject), 0, jsonObject.code);
    +        assertFalse("Test took too long", jsonObject.isRunning() || jsonObject.isNeverStarted());
    +        assertEquals(GSON.toJson(jsonObject), 0, jsonObject.getCode());
         }
     
         public static void givenSearchWhenJobsWithBuildsAreExecutedThenTheyShouldBeSearchable(
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.