From 9c9c22a29497721604f21e96c90773e0834eac3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heitor=20Gouv=C3=AAa?= Date: Mon, 20 Nov 2023 16:33:43 -0300 Subject: [PATCH] SARIF support and integration with Github Alerts (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat/add makefile (#21) * develop * release/0.0.6 * feat: add Dockerized build and run support - Added Makefile for Dockerized application - Defined Docker image and version variables - Included Docker commands for build, run, exec, stop, clean, and rebuild Why: Facilitate easy development, testing, and deployment using Docker. * test perl critic * test perl critic * fix * syntax * identation * add path * identation * test * test of rule * fix * remove examples * remove rules * fix code * new policies * fix linter errors * update version to 0.0.6 * Support SARIF output format #11 (#27) * feat!: support of SARIF output format when "--sarif" option is passed as a parameter BREAKING CHANGES: recieves "--sarif" as a parameter stores the subset of information present on ZARN to generate SARIF output function to generate SARIF output * feat!: support "--sarif" option for outputs of this type * fix: variables declaration, SARIF generation * update workflows * Support SARIF output format (#28) * Bump actions/checkout from 2 to 4 (#26) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * initializing sarif output format and some refactor on ast file * update the command description * commiting code changes to make checkpoint - most part of the implementation are correct now but still with some problems * support sarif output format * perlcritic update * solve merge problems * remove unused method * some changes now we maintain the actual output of the tool but in case the user pass --sarif with a string for filename we will create the corresponding file with the output at this file in sarif --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * added JSON as a dependencie * update version * starting refactoring * added example of sarif command * adjusts on style code * return to privsec version * fix linter warnings * rename * update version * fix syntax * rename SARIF to Sarif * fix * fixes to be a valid sarif file --------- Signed-off-by: dependabot[bot] Co-authored-by: Anderson Bosa Co-authored-by: Heitor GouvĂȘa Co-authored-by: priv <140729444+scriptprivate@users.noreply.github.com> Co-authored-by: Giovanni Martins Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linter.yml | 1 + .../{test-on-ubuntu.yml => sast.yml} | 12 +++- .github/workflows/security-gate.yml | 4 +- .perltidyrc | 6 ++ README.md | 4 +- cpanfile | 3 +- lib/Zarn/AST.pm | 69 ++++++++----------- lib/Zarn/Sarif.pm | 49 +++++++++++++ zarn.pl | 46 ++++++++++--- 9 files changed, 138 insertions(+), 56 deletions(-) rename .github/workflows/{test-on-ubuntu.yml => sast.yml} (58%) create mode 100644 .perltidyrc create mode 100644 lib/Zarn/Sarif.pm 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