Skip to content

Commit

Permalink
support njsscan-ignore for templates (#64)
Browse files Browse the repository at this point in the history
* support njsscan-ignore for templates (#64)
* depricate `ignore:`
  • Loading branch information
ajinabraham authored Jun 9, 2021
1 parent fac7443 commit c6ea106
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 31 deletions.
2 changes: 1 addition & 1 deletion njsscan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
__title__ = 'njsscan'
__authors__ = 'Ajin Abraham'
__copyright__ = f'Copyright {datetime.now().year} Ajin Abraham, OpenSecurity'
__version__ = '0.2.7'
__version__ = '0.2.8'
__version_info__ = tuple(int(i) for i in __version__.split('.'))
__all__ = [
'__title__',
Expand Down
46 changes: 44 additions & 2 deletions njsscan/njsscan.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# -*- coding: utf_8 -*-
"""The nodejsscan cli: njsscan."""
from linecache import getline

from libsast import Scanner
from libsast import standards

from njsscan import settings
from njsscan.utils import (
get_config,
read_missing_controls,
)
from njsscan.patcher import patch_libsast


class NJSScan:
def __init__(self, paths, json, check_controls, config=False) -> None:
patch_libsast()
conf = get_config(paths, config)
self.check_controls = check_controls
self.options = {
Expand All @@ -30,6 +35,7 @@ def __init__(self, paths, json, check_controls, config=False) -> None:
'nodejs': {},
'errors': [],
}
self.standards = standards.get_standards()

def scan(self) -> dict:
"""Start Scan."""
Expand Down Expand Up @@ -60,6 +66,17 @@ def missing_controls(self, result):
else:
continue

def expand_mappings(self, meta):
"""Expand libsast standard mappings."""
meta_keys = meta['metadata'].keys()
for mkey in meta_keys:
if mkey not in self.standards.keys():
continue
to_expand = meta['metadata'][mkey]
expanded = self.standards[mkey].get(to_expand)
if expanded:
meta['metadata'][mkey] = expanded

def format_sgrep(self, sgrep_output):
"""Remove metavars from sgrep output."""
self.result['errors'] = sgrep_output['errors']
Expand All @@ -71,6 +88,9 @@ def format_sgrep(self, sgrep_output):

def format_matches(self, matcher_out):
"""Format Pattern Matcher output."""
for rule_id in matcher_out:
# TODO Remove after standards is handled in libsast
self.expand_mappings(matcher_out[rule_id])
self.result['templates'] = matcher_out

def post_ignore_rules(self):
Expand All @@ -81,6 +101,15 @@ def post_ignore_rules(self):
if rule_id in self.result['templates']:
del self.result['templates'][rule_id]

def suppress_pm_comments(self, obj, rule_id):
"""Suppress pattern matcher."""
file_path = obj['file_path']
lines = obj['match_lines']
match_line = getline(file_path, lines[0])
if 'njsscan-ignore:' in match_line and rule_id in match_line:
return True
return False

def post_ignore_files(self):
"""Ignore file by rule."""
del_keys = set()
Expand All @@ -91,8 +120,19 @@ def post_ignore_files(self):
tmp_files = files.copy()
for file in files:
mstr = file.get('match_string')
cmt = 'ignore:' in mstr or 'njsscan-ignore:' in mstr
if cmt and rule_id in mstr:
if 'njsscan-ignore:' in mstr and rule_id in mstr:
tmp_files.remove(file)
if len(tmp_files) == 0:
del_keys.add(rule_id)
details['files'] = tmp_files
for rule_id, details in self.result['templates'].items():
files = details.get('files')
if not files:
continue
tmp_files = files.copy()
for file in files:
mstr = file.get('match_string')
if self.suppress_pm_comments(file, rule_id):
tmp_files.remove(file)
if len(tmp_files) == 0:
del_keys.add(rule_id)
Expand All @@ -101,3 +141,5 @@ def post_ignore_files(self):
for rid in del_keys:
if rid in self.result['nodejs']:
del self.result['nodejs'][rid]
if rid in self.result['templates']:
del self.result['templates'][rid]
31 changes: 31 additions & 0 deletions njsscan/patcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Libsast Patcher for supporting rules with metadata."""
from copy import deepcopy

from libsast.core_matcher.pattern_matcher import PatternMatcher


def add_finding(self, file_path, rule, matches):
"""Add Code Analysis Findings."""
for match in matches:
crule = deepcopy(rule)
file_details = {
'file_path': file_path.as_posix(),
'match_string': match[0],
'match_position': match[1],
'match_lines': match[2],
}
if rule['id'] in self.findings:
self.findings[rule['id']]['files'].append(file_details)
else:
metadata = crule['metadata']
metadata['description'] = crule['message']
metadata['severity'] = crule['severity']
self.findings[rule['id']] = {
'files': [file_details],
'metadata': metadata,
}


def patch_libsast():
"""Patch Libsast."""
PatternMatcher.add_finding = add_finding
63 changes: 36 additions & 27 deletions njsscan/rules/pattern_matcher/template_rules.yaml
Original file line number Diff line number Diff line change
@@ -1,91 +1,100 @@
---
- id: handlebar_mustache_template
description: The Handlebar.js/Mustache.js template has an unescaped variable. Untrusted
message: The Handlebar.js/Mustache.js template has an unescaped variable. Untrusted
user input passed to this variable results in Cross Site Scripting (XSS).
type: Regex
pattern: '{{{(?!.*body).+}}}|{{[ ]*&[\w]+.*}}'
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: dust_template
description: The Dust.js template has an unescaped variable. Untrusted user input
message: The Dust.js template has an unescaped variable. Untrusted user input
passed to this variable results in Cross Site Scripting (XSS)
type: Regex
pattern: '{.+\|[ ]*s[ ]*}[^}]'
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: pug_jade_template
description: The Pug.js/Jade.js template has an unescaped variable. Untrusted user
message: The Pug.js/Jade.js template has an unescaped variable. Untrusted user
input passed to this variable results in Cross Site Scripting (XSS).
type: Regex
pattern: '!{.+}'
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: ejs_ect_template
description: The EJS/ECT template has an unescaped variable. Untrusted user input
message: The EJS/ECT template has an unescaped variable. Untrusted user input
passed to this variable results in Cross Site Scripting (XSS).
type: Regex
pattern: <%-(?![ ]*include\().*%>
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: vue_template
description: The Vue.js template has an unescaped variable. Untrusted user input
message: The Vue.js template has an unescaped variable. Untrusted user input
passed to this variable results in Cross Site Scripting (XSS).
type: Regex
pattern: v-html=[\'|"].+[\'|"]
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: underscore_template
description: The Underscore unescape function with untrusted user input results
message: The Underscore unescape function with untrusted user input results
in Cross Site Scripting (XSS).
type: Regex
pattern: '_.unescape\(.+\)'
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: squirrelly_template
description: The Squirrelly.js template has an unescaped variable. Untrusted user input
message: The Squirrelly.js template has an unescaped variable. Untrusted user input
passed to this variable results in Cross Site Scripting (XSS)
type: Regex
pattern: '{{.+\|.*safe.*}}'
severity: ERROR
input_case: exact
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
owasp: "A1: Injection"
metadata:
cwe: cwe-79
owasp: "A1: Injection"

- id: electronjs_node_integration
description: Node integration exposes node.js APIs to the electron app and this
message: Node integration exposes node.js APIs to the electron app and this
can introduce remote code execution vulnerabilities to the application if the
app is vulnerable to Cross Site Scripting (XSS).
type: Regex
pattern: <webview.+nodeIntegration(?!.*=.*['|"]false['|"])
severity: WARNING
input_case: exact
cwe: "CWE-272: Least Privilege Violation"
owasp: "A6: Security Misconfiguration"
metadata:
cwe: cwe-272
owasp: "A6: Security Misconfiguration"

- id: electronjs_disable_websecurity
description: Disabling webSecurity will disable the same-origin policy and
message: Disabling webSecurity will disable the same-origin policy and
allows the execution of insecure code from any domain.
type: Regex
pattern: <webview.+disablewebsecurity(?!.*=.*['|"]false['|"])
severity: ERROR
input_case: exact
cwe: "CWE-346: Origin Validation Error"
owasp: "A6: Security Misconfiguration"
metadata:
cwe: cwe-79
owasp: "A6: Security Misconfiguration"
3 changes: 3 additions & 0 deletions tests/assets/dot_njsscan/ignore_template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script>
window.MY_VAR = "{{{val}}}"; // njsscan-ignore: handlebar_mustache_template
</script>
2 changes: 1 addition & 1 deletion tests/assets/dot_njsscan/lorem_scan.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ app.get('/', function (req, res) {
app.get('/some/path', function (req, res) {
var target = req.param("target");
// BAD: sanitization doesn't apply here
res.redirect(target); //ignore: express_open_redirect
res.redirect(target); //njsscan-ignore: express_open_redirect
});

app.listen(8888);
Expand Down

0 comments on commit c6ea106

Please sign in to comment.