diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 7458386..3f5ce08 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - develop jobs: critic: diff --git a/.github/workflows/test-on-ubuntu.yml b/.github/workflows/sast.yml similarity index 58% rename from .github/workflows/test-on-ubuntu.yml rename to .github/workflows/sast.yml index f7e28aa..b4d9b25 100644 --- a/.github/workflows/test-on-ubuntu.yml +++ b/.github/workflows/sast.yml @@ -1,5 +1,10 @@ name: Testing on Ubuntu -on: [push] + +on: + pull_request: + branches: + - main + - develop jobs: build: @@ -13,4 +18,7 @@ jobs: sudo cpanm --installdeps . - name: Verify the basic usage run: | - perl zarn.pl --source . --ignore examples \ No newline at end of file + perl zarn.pl --source . --sarif zarn.sarif + - uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: zarn.sarif \ No newline at end of file diff --git a/.github/workflows/security-gate.yml b/.github/workflows/security-gate.yml index b883e0c..bfb5f12 100644 --- a/.github/workflows/security-gate.yml +++ b/.github/workflows/security-gate.yml @@ -1,12 +1,10 @@ name: Security Gate - Instriq on: - push: - branches: - - main pull_request: branches: - main + - develop jobs: build: diff --git a/.perltidyrc b/.perltidyrc new file mode 100644 index 0000000..b77f430 --- /dev/null +++ b/.perltidyrc @@ -0,0 +1,6 @@ +--maximum-line-length=120 +--indent-columns=4 +--continuation-indentation=4 +--square-bracket-tightness=2 +--tight-secret-operators +--maximum-consecutive-blank-lines=1 diff --git a/README.md b/README.md index 38952fc..3177652 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - +

@@ -37,7 +37,7 @@ $ sudo cpanm --installdeps . ### Example of use ```bash -$ perl zarn.pl --rules rules/quick-wins.yml --source ../nozaki +$ perl zarn.pl --rules rules/quick-wins.yml --source ../nozaki --sarif report.sarif [warn] - FILE:../nozaki/lib/Functions/Helper.pm Potential: Timing Attack. [vuln] - FILE:../nozaki/lib/Engine/Orchestrator.pm Potential: Path Traversal. diff --git a/cpanfile b/cpanfile index c70f0cd..2bf8227 100644 --- a/cpanfile +++ b/cpanfile @@ -1,4 +1,5 @@ requires "File::Find::Rule", "0.34"; requires "Getopt::Long", "2.54"; requires "YAML::Tiny", "1.73"; -requires "PPI::Document", "1.276"; \ No newline at end of file +requires "PPI::Document", "1.276"; +requires "JSON"; \ No newline at end of file diff --git a/lib/Zarn/AST.pm b/lib/Zarn/AST.pm index ef2214a..dba95d6 100644 --- a/lib/Zarn/AST.pm +++ b/lib/Zarn/AST.pm @@ -7,7 +7,7 @@ package Zarn::AST { sub new { my ($self, $parameters) = @_; - my ($file, $rules); + my ($file, $rules, @results); Getopt::Long::GetOptionsFromArray ( $parameters, @@ -27,50 +27,41 @@ package Zarn::AST { my $category = $rule -> {category}; my $title = $rule -> {name}; - if ($self -> matches_sample($token -> content(), \@sample)) { - $self -> process_sample_match($document, $category, $file, $title, $token); + if (grep {my $content = $_; scalar(grep {$content =~ m/$_/} @sample)} $token -> content()) { + my $next_element = $token -> snext_sibling; + + # this is a draft source-to-sink function + if (defined $next_element && ref $next_element && $next_element -> content() =~ /[\$\@\%](\w+)/) { + # perform taint analyis + my $var_token = $document -> find_first ( + sub { $_[1] -> isa("PPI::Token::Symbol") and $_[1] -> content eq "\$$1" } + ); + + if ($var_token && $var_token -> can("parent")) { + if (( + $var_token -> parent -> isa("PPI::Token::Operator") || + $var_token -> parent -> isa("PPI::Statement::Expression") + )) { + my ($line, $rowchar) = @{$var_token -> location}; + + push @results, { + category => $category, + file => $file, + title => $title, + line => $line, + rowchar => $rowchar + }; + } + } + } } } } - } - - return 1; - } - - sub matches_sample { - my ($self, $content, $sample) = @_; - - return grep { - my $sample_content = $_; - scalar(grep {$content =~ m/$_/} @$sample) - } @$sample; - } - - sub process_sample_match { - my ($self, $document, $category, $file, $title, $token) = @_; - my $next_element = $token -> snext_sibling; - - # this is a draft source-to-sink function - if (defined $next_element && ref $next_element && $next_element -> content() =~ /[\$\@\%](\w+)/) { - # perform taint analysis - $self -> perform_taint_analysis($document, $category, $file, $title, $next_element); + return @results; } - } - sub perform_taint_analysis { - my ($self, $document, $category, $file, $title, $next_element) = @_; - - my $var_token = $document -> find_first( - sub { $_[1 ] -> isa("PPI::Token::Symbol") and $_[1] -> content eq "\$$1" } - ); - - if ($var_token && $var_token -> can("parent")) { - if (($var_token -> parent -> isa("PPI::Token::Operator") || $var_token -> parent -> isa("PPI::Statement::Expression"))) { - my ($line, $rowchar) = @{ $var_token -> location }; - print "[$category] - FILE:$file \t Potential: $title. \t Line: $line:$rowchar.\n"; - } - } + return 0; } } diff --git a/lib/Zarn/Sarif.pm b/lib/Zarn/Sarif.pm new file mode 100644 index 0000000..64c6f14 --- /dev/null +++ b/lib/Zarn/Sarif.pm @@ -0,0 +1,49 @@ +package Zarn::Sarif { + use strict; + use warnings; + + sub new { + my ($self, @vulnerabilities) = @_; + + my $sarif_data = { + "\$schema" => "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + version => "2.1.0", + runs => [{ + tool => { + driver => { + name => "ZARN", + informationUri => "https://github.com/htrgouvea/zarn", + version => "0.0.8" + } + }, + results => [] + }] + }; + + foreach my $info (@vulnerabilities) { + my $result = { + ruleId => $info -> {title}, + message => { + text => $info -> {title} + }, + locations => [{ + physicalLocation => { + artifactLocation => { + uri => $info -> {file} + }, + region => { + startLine => $info -> {line}, + startColumn => $info -> {rowchar} + } + } + }] + }; + + push @{$sarif_data -> {runs}[0]{results}}, $result; + } + + return $sarif_data; + } +} + +1; \ No newline at end of file diff --git a/zarn.pl b/zarn.pl index 77b5b71..a66265a 100755 --- a/zarn.pl +++ b/zarn.pl @@ -4,25 +4,27 @@ use strict; use warnings; use lib "./lib/"; +use Getopt::Long; use Zarn::AST; use Zarn::Files; use Zarn::Rules; -use Getopt::Long; +use Zarn::Sarif; +use JSON; sub main { my $rules = "rules/default.yml"; - - my ($source, $ignore); + my ($source, $ignore, $sarif, @results); Getopt::Long::GetOptions ( - "r|rules=s" => \$rules, - "s|source=s" => \$source, - "i|ignore=s" => \$ignore + "r|rules=s" => \$rules, + "s|source=s" => \$source, + "i|ignore=s" => \$ignore, + "srf|sarif=s" => \$sarif ); if (!$source) { print " - \rZarn v0.0.5 + \rZarn v0.0.8 \rCore Commands \r============== \r\tCommand Description @@ -30,8 +32,9 @@ sub main { \r\t-s, --source Configure a source directory to do static analysis \r\t-r, --rules Define YAML file with rules \r\t-i, --ignore Define a file or directory to ignore + \r\t-srf, --sarif Define the SARIF output file \r\t-h, --help To see help menu of a module\n - "; + \r"; exit 1; } @@ -41,9 +44,34 @@ sub main { foreach my $file (@files) { if (@rules) { - my $analysis = Zarn::AST -> new (["--file" => $file, "--rules" => @rules]); + my @analysis = Zarn::AST -> new ([ + "--file" => $file, + "--rules" => @rules + ]); + + push @results, @analysis; } } + + foreach my $result (@results) { + my $category = $result -> {category}; + my $file = $result -> {file}; + my $title = $result -> {title}; + my $line = $result -> {line}; + my $rowchar = $result -> {rowchar}; + + print "[$category] - FILE:$file \t Potential: $title. \t Line: $line:$rowchar\n"; + } + + if ($sarif) { + my $sarif_data = Zarn::Sarif -> new (@results); + + open(my $output, '>', $sarif) or die "Cannot open file '$sarif': $!"; + print $output encode_json($sarif_data); + close($output); + } + + return 0; } exit main(); \ No newline at end of file