利用Sonatype API实现依赖检测工具

Msfcode

共 17414字,需浏览 35分钟

 ·

2023-05-06 20:36

背景

由于毕设答辩时间提前,导致迫不得已砍掉部分功能,而个人感觉功能有点少故添加了个依赖检测功能,大体原理利用Maven API解析提取上传的Pom文件中的依赖项,然后借助Sonatype进行安全检测。


依赖项提取

其实这里实现方式多种,模仿XmlDecoder的方式自定义DocumentHandler虽然可以(Tomcat内部解析web.xml就是这种方式)不过对于部分把版本号以变量形式约束定义在Properties标签的情况处理比较麻烦,故采用Maven API,它可以直接提取properties中的标签值等。

0604eda2ba7984d1afb650eabafed0f6.webp

依赖

      
        
          <dependency>
        
      
      
          <groupId>org.apache.maven</groupId>
      
      
          <artifactId>maven-model-builder</artifactId>
      
      
          <version>3.8.1</version>
      
      
        
          </dependency>
        
      
      
        
          <dependency>
        
      
      
          <groupId>org.apache.maven</groupId>
      
      
          <artifactId>maven-model</artifactId>
      
      
           <version>3.8.1</version>
      
      
        
          </dependency>
        
      
    

代码实现

      
        import java.io.File;
      
      
        import java.io.FileReader;
      
      
        import java.io.StringReader;
      
      
        import java.io.StringWriter;
      
      
        import java.util.ArrayList;
      
      
        import java.util.List;
      
      
        import java.util.Properties;
      
      
        import java.util.regex.Matcher;
      
      
        import java.util.regex.Pattern;
      
      
        
          
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.model.io.DefaultModelWriter; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList;
public class PomParser {     // 参数:Pom文件绝对路径 public static List<Dependency> parse(String pomPath) throws Exception { List<Dependency> result = new ArrayList<>(); MavenXpp3Reader reader = new MavenXpp3Reader(); Model model = reader.read(new FileReader(pomPath));
Properties properties = model.getProperties(); // 获取所有属性 // 将model对象转为xml字符串 DefaultModelWriter writer = new DefaultModelWriter(); StringWriter stringWriter = new StringWriter(); writer.write(stringWriter, null, model);
String xmlString = stringWriter.toString();
// 合并Dependencies和DependencyManagement List<org.apache.maven.model.Dependency> allDependencies=model.getDependencies(); DependencyManagement dependencyManagement = model.getDependencyManagement(); if (dependencyManagement!=null && dependencyManagement.getDependencies().size()>0){ allDependencies.addAll(dependencyManagement.getDependencies()); } for (org.apache.maven.model.Dependency dependency : allDependencies) { // 遍历每个依赖对象 String version = dependency.getVersion();
if (version!=null && version.startsWith("${") && version.endsWith("}")) { // 如果版本号以${}包裹,则需要进行替换 String propertyName = version.substring(2, version.length() - 1); String propertyValue = properties.getProperty(propertyName);
if (propertyValue == null) { // 如果属性不存在,抛出异常 throw new IllegalArgumentException("Property not found: " + propertyName); } dependency.setVersion(propertyValue); } }
for (org.apache.maven.model.Dependency dependency : allDependencies) { result.add(new Dependency(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())); } return result; } }


// Denpendency结构体 public static class Dependency { private final String groupId; private final String artifactId; private final String version;
public Dependency(String groupId, String artifactId, String version) { this.groupId = groupId; this.artifactId = artifactId; this.version = version; }
public String getGroupId() { return groupId; }
public String getArtifactId() { return artifactId; }
public String getVersion() { return version; } }



Https问题

由于Sonatype的API为HTTPS,导致测试时候获取不到数据,查阅网上方案得以解决

      
        package com.VulnScanner.DpendCheck;
      
      
        
          

import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate;
public class HttpsUtils { static CloseableHttpClient httpClient; static CloseableHttpResponse httpResponse;
public static CloseableHttpClient createSSLClientDefault() { try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { // 信任所有 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); return HttpClients.custom().setSSLSocketFactory(sslsf).build(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } return HttpClients.createDefault();
}
/** * 发送https请求 * * @param content * @throws Exception */ public static String send(String content, String url) { try { HttpPost request = new HttpPost(url); StringEntity entity = new StringEntity(content, "UTF-8"); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); request.setEntity(entity); request.addHeader("Connection", "Keep-Alive"); request.addHeader("accept", "application/json"); request.addHeader("Content-Type", "application/json"); httpClient = HttpsUtils.createSSLClientDefault(); httpResponse = httpClient.execute(request); HttpEntity httpEntity = httpResponse.getEntity(); if (httpEntity != null) { String jsObject = EntityUtils.toString(httpEntity, "UTF-8"); return jsObject; } else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } finally { try { httpResponse.close(); httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } }



调用API检测

Sonatype API:https://ossindex.sonatype.org/rest

4d3cb9aab07d3f56f893c92764e7fee0.webp

可以看到参数主要是这个 coordinates 字段,关于它的格式定义(https://ossindex.sonatype.org/doc/coordinates)

aafc523109d4cd8b641517443a21b5b1.webp

如果是检测Java项目则格式如下

      
        maven:groupId/artifactId@Version
      
    

是以,我们可以进行组合实现检测依赖文件中的依赖项安全。

3ff840105d1077a1b2f56fc699a62abb.webp

c1c3844c65be0bb22ddcf27d3f6a843e.webp

      
        public class Checker {
      
      
            private static final String API_BASE_URL = "https://ossindex.sonatype.org/api/v3";
      
      
        
          

public List<ScanResult> getAllVulns(String filePath) throws Exception { List<ScanResult> scanResults=new ArrayList<ScanResult>(); try { List<PomParser.Dependency> dependencies = PomParser.parse(filePath); for (PomParser.Dependency dependency:dependencies){ scanResults.addAll(scanDependencies(dependency.getGroupId(),dependency.getArtifactId(),dependency.getVersion())); } }catch (Exception e){}
return scanResults; }



/** * 扫描指定 Maven 项目的所有依赖项,并返回包含 CVE 信息的扫描结果。 * * @param groupId Maven 项目的 Group ID * @param artifactId Maven 项目的 Artifact ID * @param version Maven 项目的版本号 * @return 包含 CVE 信息的扫描结果列表 */ public List<ScanResult> scanDependencies(String groupId, String artifactId, String version) throws IOException, JSONException { // 处理 CVE 数据并生成扫描结果 List<ScanResult> results = new ArrayList<>(); try{ // 构造 API 请求 URL String url = API_BASE_URL + "/component-report/"; String content="{ \"coordinates\":[\"maven:"+groupId+"/"+artifactId+"@"+version+"\"]}"; // 解析 JSON 响应 String data = HttpsUtils.send(content, url); int start = data.indexOf("[{"); data=data.substring(start+1,data.length()-1); JSONObject jsonResponse = new JSONObject(data); JSONArray vulnerabilities = jsonResponse.optJSONArray("vulnerabilities");
if (vulnerabilities != null) { for (int i = 0; i < vulnerabilities.length(); i++) { JSONObject vuln = vulnerabilities.getJSONObject(i); String cveId = vuln.optString("cve"); String description = vuln.optString("description"); results.add(new ScanResult(cveId, description,groupId,artifactId,version)); } } }catch (Exception e){
} return results;     } }


// ScanResult结构体(自行生成Getter/Setter) public class ScanResult { private String cveId; private String description; private String groupId; private String artifactId; private String version;
public ScanResult(String cveId, String description,String groupId,String artifactId,String version) { this.cveId = cveId; this.description = description; this.groupId=groupId; this.artifactId=artifactId; this.version=version;     } }

需要注意的是这里有点坑,那就获取到的数据包含了响应头数据,故这里利用响应体结构特性进行了较为暴力的处理,当然也可以用其他方式实现,这里个人偷懒。

      
        int start = data.indexOf("[{");
      
      
        data=data.substring(start+1,data.length()-1);
      
    



效果

以下为个人实现效果,这里没有给出我控制器这边代码,可自行琢磨封装使用。

fb8b899c52dfc2744bee088444b4b60f.webp



结语

注意一下的是Sonatype并非只能检测Java依赖项安全,它支持多种语言的依赖检测,只不过这里以Java为例。

a48ae6bd6a4fe7879563ae9276e95cf2.webp


浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报