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

共 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






浏览 59
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报