File size: 22,816 Bytes
52b089b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
"""Tool for querying the National Vulnerability Database (NVD) of NIST.

This tool allows obtaining detailed information about specific vulnerabilities.

"""

from smolagents import Tool
import requests
import json
from urllib.parse import quote

class NvdTool(Tool):
    """

    Tool for querying the National Vulnerability Database (NVD) of NIST.

    """
    
    name = "nvd_search"
    description = """Tool for querying the National Vulnerability Database (NVD) of NIST.

    This tool allows searching for vulnerabilities in three ways:

    1. By CVE ID: nvd_search(search_type="cve", identifier="CVE-2021-44228")

    2. By keyword: nvd_search(search_type="keyword", identifier="log4j")

    3. By keyword with exact match: nvd_search(search_type="keyword", identifier="log4j", exact_match=True)

    

    CRITICAL - Product name format for keyword search:

    - ALWAYS use ONLY the base product name, NEVER include versions

    - CORRECT examples: "log4j", "apache", "microsoft", "chrome", "tomcat", "nginx"

    - INCORRECT examples: "log4j 2.14.1", "apache http server", "microsoft windows", "chrome 120.0.6099.109"

    - When searching, strip version numbers and descriptive words

    - Example: "Apache Tomcat 9.0.65" → search for "tomcat"

    - Example: "Google Chrome 120.0.6099.109" → search for "chrome"

    - Example: "Log4j 2.14.1" → search for "log4j"

    

    IMPORTANT: search_type must be EXACTLY 'cve' or 'keyword', not 'product' or any other value.

    CORRECT: nvd_search(search_type="keyword", identifier="mobaxterm")

    WRONG: nvd_search(search_type="product", identifier="mobaxterm")

    

    The exact_match parameter:

    - When True: Searches for CVEs that contain the EXACT phrase in the description

    - When False: Searches for CVEs that contain ANY of the words in the description

    """
    
    inputs = {
        "search_type": {
            "description": "Type of search to perform. Must be 'cve' for specific CVE ID or 'keyword' for keyword search.",
            "type": "string",
        },
        "identifier": {
            "description": "The CVE ID (e.g., 'CVE-2021-44228') or keyword (e.g., 'log4j'). For keywords, use ONLY base product names without versions (e.g., 'log4j' not 'log4j 2.14.1').",
            "type": "string",
        },
        "exact_match": {
            "description": "For keyword searches, whether to match the exact phrase (True) or any word (False).",
            "type": "boolean",
            "default": False,
            "nullable": True,
        },
    }
    
    output_type = "string"
    
    def __init__(self):
        super().__init__()
        self.base_url = "https://services.nvd.nist.gov/rest/json/cves/2.0"

    def forward(self, search_type: str, identifier: str, exact_match: bool = False) -> str:
        """Search for vulnerabilities in NVD."""
        try:
            if search_type == "cve":
                return self._search_by_cve(identifier)
            elif search_type == "keyword":
                return self._search_by_keyword(identifier, exact_match)
            else:
                return "Error: search_type must be 'cve' or 'keyword'"
                
        except Exception as e:
            return f"Error searching NVD: {str(e)}"

    def _search_by_cve(self, cve_id: str) -> str:
        """Search for a specific CVE."""
        url = f"{self.base_url}?cveId={cve_id}"
        
        try:
            response = requests.get(url)
            response.raise_for_status()
            data = response.json()
            
            if not data.get('vulnerabilities'):
                return f"No vulnerabilities found for {cve_id}"
            
            vuln_data = data['vulnerabilities'][0]['cve']
            return self._format_cve_data(vuln_data)
            
        except requests.exceptions.RequestException as e:
            return f"Error accessing NVD API: {str(e)}"

    def _search_by_keyword(self, keyword: str, exact_match: bool = False) -> str:
        """Search for vulnerabilities by keyword."""
        # Convert keyword to lowercase and encode spaces properly
        keyword = keyword.lower()
        encoded_keyword = quote(keyword)
        
        # Build the URL based on exact_match parameter
        if exact_match:
            # For exact match, use keywordExactMatch parameter
            url = f"{self.base_url}?keywordSearch={encoded_keyword}&keywordExactMatch"
        else:
            # For partial match, just use keywordSearch
            url = f"{self.base_url}?keywordSearch={encoded_keyword}"
        
        try:
            response = requests.get(url)
            response.raise_for_status()
            data = response.json()
            
            if not data.get('vulnerabilities'):
                return f"No vulnerabilities found for '{keyword}'"
            
            vulnerabilities = data['vulnerabilities']
            context = []
            context.append(f"Found {len(vulnerabilities)} vulnerabilities for search '{keyword}'")
            context.append("\nVulnerabilities found:")
            
            # Show all vulnerabilities
            for i, vuln in enumerate(vulnerabilities):
                vuln_data = vuln['cve']
                context.append(f"\n--- Vulnerability {i+1} ---")
                context.append(f"CVE ID: {vuln_data.get('id', 'Not available')}")
                context.append(f"Source Identifier: {vuln_data.get('sourceIdentifier', 'Not available')}")
                context.append(f"Vulnerability Status: {vuln_data.get('vulnStatus', 'Not available')}")
                
                # CVE Tags
                cve_tags = vuln_data.get('cveTags', [])
                if cve_tags:
                    context.append(f"CVE Tags: {', '.join(cve_tags)}")
                else:
                    context.append("CVE Tags: None")
                
                # Description - show all languages
                descriptions = vuln_data.get('descriptions', [])
                if descriptions:
                    for desc in descriptions:
                        lang = desc.get('lang', 'unknown')
                        value = desc.get('value', 'Not available')
                        context.append(f"Description ({lang}): {value}")
                else:
                    context.append("Description: Not available")
                
                # CVSS Metrics - show all available versions
                metrics = vuln_data.get('metrics', {})
                if metrics:
                    context.append("\nCVSS Metrics:")
                    
                    # CVSS V3.1
                    if 'cvssMetricV31' in metrics:
                        cvss_data = metrics['cvssMetricV31'][0]
                        cvss_info = cvss_data['cvssData']
                        context.append(f"CVSS V3.1:")
                        context.append(f"  Base Score: {cvss_info['baseScore']}")
                        context.append(f"  Base Severity: {cvss_info['baseSeverity']}")
                        context.append(f"  Vector String: {cvss_info['vectorString']}")
                        context.append(f"  Attack Vector: {cvss_info['attackVector']}")
                        context.append(f"  Attack Complexity: {cvss_info['attackComplexity']}")
                        context.append(f"  Privileges Required: {cvss_info['privilegesRequired']}")
                        context.append(f"  User Interaction: {cvss_info['userInteraction']}")
                        context.append(f"  Scope: {cvss_info['scope']}")
                        context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                        context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                        context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                        context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                        context.append(f"  Impact Score: {cvss_data['impactScore']}")
                    
                    # CVSS V3.0
                    if 'cvssMetricV30' in metrics:
                        cvss_data = metrics['cvssMetricV30'][0]
                        cvss_info = cvss_data['cvssData']
                        context.append(f"CVSS V3.0:")
                        context.append(f"  Base Score: {cvss_info['baseScore']}")
                        context.append(f"  Base Severity: {cvss_info['baseSeverity']}")
                        context.append(f"  Vector String: {cvss_info['vectorString']}")
                        context.append(f"  Attack Vector: {cvss_info['attackVector']}")
                        context.append(f"  Attack Complexity: {cvss_info['attackComplexity']}")
                        context.append(f"  Privileges Required: {cvss_info['privilegesRequired']}")
                        context.append(f"  User Interaction: {cvss_info['userInteraction']}")
                        context.append(f"  Scope: {cvss_info['scope']}")
                        context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                        context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                        context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                        context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                        context.append(f"  Impact Score: {cvss_data['impactScore']}")
                    
                    # CVSS V2
                    if 'cvssMetricV2' in metrics:
                        cvss_data = metrics['cvssMetricV2'][0]
                        cvss_info = cvss_data['cvssData']
                        context.append(f"CVSS V2:")
                        context.append(f"  Base Score: {cvss_info['baseScore']}")
                        context.append(f"  Base Severity: {cvss_data['baseSeverity']}")
                        context.append(f"  Vector String: {cvss_info['vectorString']}")
                        context.append(f"  Access Vector: {cvss_info['accessVector']}")
                        context.append(f"  Access Complexity: {cvss_info['accessComplexity']}")
                        context.append(f"  Authentication: {cvss_info['authentication']}")
                        context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                        context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                        context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                        context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                        context.append(f"  Impact Score: {cvss_data['impactScore']}")
                else:
                    context.append("CVSS Metrics: Not available")
                
                # Weaknesses - show all CWEs
                weaknesses = vuln_data.get('weaknesses', [])
                if weaknesses:
                    context.append("\nWeaknesses:")
                    for weakness in weaknesses:
                        source = weakness.get('source', 'Unknown')
                        w_type = weakness.get('type', 'Unknown')
                        context.append(f"  Source: {source}, Type: {w_type}")
                        descriptions = weakness.get('description', [])
                        for desc in descriptions:
                            lang = desc.get('lang', 'unknown')
                            value = desc.get('value', 'Not available')
                            context.append(f"    CWE ({lang}): {value}")
                else:
                    context.append("\nWeaknesses: Not available")
                
                # Configurations
                configurations = vuln_data.get('configurations', [])
                if configurations:
                    context.append("\nAffected Configurations:")
                    for config in configurations:
                        nodes = config.get('nodes', [])
                        for node in nodes:
                            operator = node.get('operator', 'Unknown')
                            negate = node.get('negate', False)
                            context.append(f"  Operator: {operator}, Negate: {negate}")
                            cpe_matches = node.get('cpeMatch', [])
                            for match in cpe_matches:
                                vulnerable = match.get('vulnerable', False)
                                criteria = match.get('criteria', 'Not available')
                                version_end = match.get('versionEndIncluding', 'Not specified')
                                context.append(f"    CPE: {criteria}")
                                context.append(f"    Vulnerable: {vulnerable}")
                                if version_end != 'Not specified':
                                    context.append(f"    Version End Including: {version_end}")
                else:
                    context.append("\nAffected Configurations: Not available")
                
                # Dates
                context.append(f"\nPublished: {vuln_data.get('published', 'Not available')}")
                context.append(f"Last Modified: {vuln_data.get('lastModified', 'Not available')}")
                
                # References - show all with tags
                references = vuln_data.get('references', [])
                if references:
                    context.append("\nReferences:")
                    for ref in references:
                        url = ref.get('url', 'Not available')
                        source = ref.get('source', 'Unknown')
                        tags = ref.get('tags', [])
                        context.append(f"  URL: {url}")
                        context.append(f"  Source: {source}")
                        if tags:
                            context.append(f"  Tags: {', '.join(tags)}")
                        context.append("")
                else:
                    context.append("\nReferences: Not available")
                
                context.append("-" * 50)  # Separator between vulnerabilities
            
            return "\n".join(context)
            
        except requests.exceptions.RequestException as e:
            return f"Error accessing NVD API: {str(e)}"

    def _format_cve_data(self, vuln_data: dict) -> str:
        """Format CVE data for display."""
        context = []
        
        context.append(f"CVE ID: {vuln_data.get('id', 'Not available')}")
        context.append(f"Source Identifier: {vuln_data.get('sourceIdentifier', 'Not available')}")
        context.append(f"Vulnerability Status: {vuln_data.get('vulnStatus', 'Not available')}")
        
        # CVE Tags
        cve_tags = vuln_data.get('cveTags', [])
        if cve_tags:
            context.append(f"CVE Tags: {', '.join(cve_tags)}")
        else:
            context.append("CVE Tags: None")
        
        # Description - show all languages
        descriptions = vuln_data.get('descriptions', [])
        if descriptions:
            for desc in descriptions:
                lang = desc.get('lang', 'unknown')
                value = desc.get('value', 'Not available')
                context.append(f"Description ({lang}): {value}")
        else:
            context.append("Description: Not available")
        
        # CVSS Metrics - show all available versions
        metrics = vuln_data.get('metrics', {})
        if metrics:
            context.append("\nCVSS Metrics:")
            
            # CVSS V3.1
            if 'cvssMetricV31' in metrics:
                cvss_data = metrics['cvssMetricV31'][0]
                cvss_info = cvss_data['cvssData']
                context.append(f"CVSS V3.1:")
                context.append(f"  Base Score: {cvss_info['baseScore']}")
                context.append(f"  Base Severity: {cvss_info['baseSeverity']}")
                context.append(f"  Vector String: {cvss_info['vectorString']}")
                context.append(f"  Attack Vector: {cvss_info['attackVector']}")
                context.append(f"  Attack Complexity: {cvss_info['attackComplexity']}")
                context.append(f"  Privileges Required: {cvss_info['privilegesRequired']}")
                context.append(f"  User Interaction: {cvss_info['userInteraction']}")
                context.append(f"  Scope: {cvss_info['scope']}")
                context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                context.append(f"  Impact Score: {cvss_data['impactScore']}")
            
            # CVSS V3.0
            if 'cvssMetricV30' in metrics:
                cvss_data = metrics['cvssMetricV30'][0]
                cvss_info = cvss_data['cvssData']
                context.append(f"CVSS V3.0:")
                context.append(f"  Base Score: {cvss_info['baseScore']}")
                context.append(f"  Base Severity: {cvss_info['baseSeverity']}")
                context.append(f"  Vector String: {cvss_info['vectorString']}")
                context.append(f"  Attack Vector: {cvss_info['attackVector']}")
                context.append(f"  Attack Complexity: {cvss_info['attackComplexity']}")
                context.append(f"  Privileges Required: {cvss_info['privilegesRequired']}")
                context.append(f"  User Interaction: {cvss_info['userInteraction']}")
                context.append(f"  Scope: {cvss_info['scope']}")
                context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                context.append(f"  Impact Score: {cvss_data['impactScore']}")
            
            # CVSS V2
            if 'cvssMetricV2' in metrics:
                cvss_data = metrics['cvssMetricV2'][0]
                cvss_info = cvss_data['cvssData']
                context.append(f"CVSS V2:")
                context.append(f"  Base Score: {cvss_info['baseScore']}")
                context.append(f"  Base Severity: {cvss_data['baseSeverity']}")
                context.append(f"  Vector String: {cvss_info['vectorString']}")
                context.append(f"  Access Vector: {cvss_info['accessVector']}")
                context.append(f"  Access Complexity: {cvss_info['accessComplexity']}")
                context.append(f"  Authentication: {cvss_info['authentication']}")
                context.append(f"  Confidentiality Impact: {cvss_info['confidentialityImpact']}")
                context.append(f"  Integrity Impact: {cvss_info['integrityImpact']}")
                context.append(f"  Availability Impact: {cvss_info['availabilityImpact']}")
                context.append(f"  Exploitability Score: {cvss_data['exploitabilityScore']}")
                context.append(f"  Impact Score: {cvss_data['impactScore']}")
        else:
            context.append("CVSS Metrics: Not available")
        
        # Weaknesses - show all CWEs
        weaknesses = vuln_data.get('weaknesses', [])
        if weaknesses:
            context.append("\nWeaknesses:")
            for weakness in weaknesses:
                source = weakness.get('source', 'Unknown')
                w_type = weakness.get('type', 'Unknown')
                context.append(f"  Source: {source}, Type: {w_type}")
                descriptions = weakness.get('description', [])
                for desc in descriptions:
                    lang = desc.get('lang', 'unknown')
                    value = desc.get('value', 'Not available')
                    context.append(f"    CWE ({lang}): {value}")
        else:
            context.append("\nWeaknesses: Not available")
        
        # Configurations
        configurations = vuln_data.get('configurations', [])
        if configurations:
            context.append("\nAffected Configurations:")
            for config in configurations:
                nodes = config.get('nodes', [])
                for node in nodes:
                    operator = node.get('operator', 'Unknown')
                    negate = node.get('negate', False)
                    context.append(f"  Operator: {operator}, Negate: {negate}")
                    cpe_matches = node.get('cpeMatch', [])
                    for match in cpe_matches:
                        vulnerable = match.get('vulnerable', False)
                        criteria = match.get('criteria', 'Not available')
                        version_end = match.get('versionEndIncluding', 'Not specified')
                        context.append(f"    CPE: {criteria}")
                        context.append(f"    Vulnerable: {vulnerable}")
                        if version_end != 'Not specified':
                            context.append(f"    Version End Including: {version_end}")
        else:
            context.append("\nAffected Configurations: Not available")
        
        # Dates
        context.append(f"\nPublished: {vuln_data.get('published', 'Not available')}")
        context.append(f"Last Modified: {vuln_data.get('lastModified', 'Not available')}")
        
        # References - show all with tags
        references = vuln_data.get('references', [])
        if references:
            context.append("\nReferences:")
            for ref in references:
                url = ref.get('url', 'Not available')
                source = ref.get('source', 'Unknown')
                tags = ref.get('tags', [])
                context.append(f"  URL: {url}")
                context.append(f"  Source: {source}")
                if tags:
                    context.append(f"  Tags: {', '.join(tags)}")
                context.append("")
        else:
            context.append("\nReferences: Not available")
        
        return "\n".join(context)