Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Perl::Critic for Security Audits

Casey West
January 15, 2012

Perl::Critic for Security Audits

Use static analysis to find potential web security vulnerabilities in legacy Perl code bases. Presented at Perl Oasis in Orlando, FL. January 14, 2012.

Casey West

January 15, 2012
Tweet

More Decks by Casey West

Other Decks in Programming

Transcript

  1. CASE STUDY: WEB SECURITY • Cross Site Scripting (XSS) •

    Input isn’t validated. • Input ins’t sanitized. • Unscrutinized input used as output. • Successful attack! • SQL Injection Attack • Input isn’t validated. • Input isn’t sanitized. • Database queries are unprotected. • Successful attack! Sunday, January 15, 12
  2. CROSS SITE SCRIPTING (XSS) • Strategically formed HTML. • Injected

    JavaScript to control end-user’s experience. • Can allow account takeover; app takeover. • Can cripple your app. Sunday, January 15, 12
  3. XSS EXAMPLE use CGI; my $cgi = CGI->new; my $name

    = $cgi->param('name'); print "Hello, $name, how are you?"; Sunday, January 15, 12
  4. XSS EXAMPLE FIXED my $name = $cgi->escapeHTML( $cgi->param('name') ); print

    "Hello, $name, how are you?"; Sunday, January 15, 12
  5. SQL INJECTION ATTACK • Form/API user input with special SQL

    sequences. • Can cause data loss; data corruption. • Can cause data leaks. • Can cripple your app. Sunday, January 15, 12
  6. SQL INJECTION EXAMPLE <form> <input type="hidden" name="id" value="42"/> <label for="name">

    What's your name?</label> <input type="text" name="name" /> <input type="submit" /> </form> Sunday, January 15, 12
  7. SQL INJECTION EXAMPLE use CGI; use DBI; my $cgi =

    CGI->new; my $name = $cgi->param('name'); my $id = $cgi->param('id'); $dbh->do("UPDATE users SET name='$name' WHERE id=$id"); Sunday, January 15, 12
  8. SQL INJECTION EXAMPLE FIXED my $name = $dbh->quote( $cgi->param('name')); my

    $id = $dbh->quote( $cgi->param('id')); $dbh->do("UPDATE users SET name=$name WHERE id=$id"); Sunday, January 15, 12
  9. SQL INJECTION EXAMPLE FIXED my $id = $cgi->param('id'); if ($id

    =~ /\D+/) { die "Invalid input: go kiss a cactus."; } my $name = $dbh->quote( $cgi->param('name')); Sunday, January 15, 12
  10. THESE SECURITY VULNERABILITIES ARE SERIOUSLY BAD. Like, depending on what

    your app does you can get shamed, fired, sued, indicted, or thrown in Federal pound-me-in-the-ass prison. Sunday, January 15, 12
  11. YOU NEED TO FIND AND FIX THEM BEFORE AN ATTACKER

    DOES. Or your boss, a third-party auditing company, your partner’s boyfriend, or certain members of the Perl community who shall remain anonymous (no guessing; chuckle if you know). Sunday, January 15, 12
  12. THE PREAMBLE package Perl::Critic::Policy::For::Stuff; use strict; use warnings; use Readonly;

    use Perl::Critic::Utils qw[:all]; use base 'Perl::Critic::Policy'; Readonly::Scalar my $DESC => 'For Stuff'; Readonly::Scalar my $EXPL => [45]; Sunday, January 15, 12
  13. THE CONFIGURATION sub default_severity { $SEVERITY_HIGH } sub default_themes {

    return qw[custom] } sub applies_to { 'PPI::Token::Word' } Sunday, January 15, 12
  14. THE MONEY sub violates { my ($self, $elem) = @_;

    return unless $elem eq "print"; my @args = parse_arg_list($elem); return unless @args > 2; return $self->violation( $DESC, $EXPL, $elem ) }; Sunday, January 15, 12
  15. A SCRIPT TO BIND THEM #!/usr/bin/perl use Perl::Critic; my $critic

    = Perl::Critic->new( '-single-policy' => 'Perl::Critic::Policy::For::Stuff'); print $critic->critique($_) for @ARGV; Sunday, January 15, 12
  16. DECONSTRUCTING IT # print "two", "three", "and four."; PPI::Statement PPI::Token::Word

    'print' PPI::Token::Quote::Double '"two"' PPI::Token::Operator ',' PPI::Token::Quote::Double '"three"' PPI::Token::Operator ',' PPI::Token::Quote::Double '"and four."' PPI::Token::Structure ';' Sunday, January 15, 12
  17. RUN THE POLICY shell> perl our-policy.pl sample- program.pl For Stuff

    at line 3, column 1. See page 45 of PBP. Sunday, January 15, 12
  18. WRITING A POLICY • Come up with a clever name.

    • Subclass Perl::Critic::Policy. • Tell Parser when to notify you of interesting code. • Walk the parse tree to find violations. Sunday, January 15, 12
  19. THE PREAMBLE package Perl::Critic::Policy::XSS::Print; use Readonly; use Perl::Critic::Utils qw[:all]; use

    base 'Perl::Critic::Policy'; Readonly::Scalar my $DESC => 'Suspect output in CGI script'; Readonly::Scalar my $EXPL => 'Potential XSS attack vulnerability'; Sunday, January 15, 12
  20. THE CONFIGURATION sub default_severity { $SEVERITY_HIGH } sub default_themes {

    qw[custom] } sub applies_to { 'PPI::Token::Word' } Sunday, January 15, 12
  21. THE MONEY sub violates { my ($self, $elem) = @_;

    return unless $elem eq "print"; my @suspect_arguments; for my $arg (parse_arg_list($elem)) { # ... Find suspect arguments. } return unless @suspect_arguments; $self->violation($DESC, $EXPL, $elem); } Sunday, January 15, 12
  22. FIND SUSPECT ARGUMENTS for my $arg (parse_arg_list($elem)) { next if

    @{$arg} == 1 && $self->is_boring_string($arg->[0]); # ... } Sunday, January 15, 12
  23. FIND SUSPECT ARGUMENTS next if grep { is_method_call($_) && (

    $_ eq "redirect" || ( $_ eq "start_html" && $self- >is_boring_string(first_arg($_)) ) ) } @{$arg}; Sunday, January 15, 12
  24. FIND SUSPECT ARGUMENTS for my $arg (parse_arg_list($elem)) { # weed

    out the boring stuff... push @suspect_arguments, $arg; } Sunday, January 15, 12
  25. WHAT'S A BORING ARGUMENT? • A Number • A Single

    Quoted String • A String Literal • A Double Quoted String without Interpolations • An Quote-like operator (qq{}) without Interpolations • A Literal Here Doc Sunday, January 15, 12
  26. AVOID BORING ARGUMENTS sub is_boring_string { my ($self, $elem) =

    @_; my $PT = 'PPI::Token'; return 1 if $elem->isa("$PT::Number") || $elem->isa("$PT::Quote::Single") || $elem->isa("$PT::Quote::Literal"); } Sunday, January 15, 12
  27. AVOID BORING ARGUMENTS sub is_boring_string { my ($self, $elem) =

    @_; my $PT = 'PPI::Token'; # ... return 1 if $elem->isa("$PT::Quote::Double") && !$elem->interpolations; } Sunday, January 15, 12
  28. AVOID BORING ARGUMENTS sub is_boring_string { my ($self, $elem) =

    @_; my $PT = 'PPI::Token'; # ... return undef if $elem->isa("$PT::Quote::Interpolate") && $elem->content =~ /(?<!\\)(?:\\\\)*[\$\@]/ } Sunday, January 15, 12
  29. AVOID BORING ARGUMENTS sub is_boring_string { my ($self, $elem) =

    @_; my $PT = 'PPI::Token'; # ... return 1 if $elem->isa("$PT::HereDoc") && $elem->content =~ /'$/ } Sunday, January 15, 12
  30. RECAP XSS APPROACH • Find all print statements. • Inspect

    arguments to print for suspect content. • Filter out uninteresting strings, such as single quoted. • Report the rest. Sunday, January 15, 12
  31. APPROACH UPGRADE • For all interesting strings, find interpolations. •

    Walk up the source tree and find its origins. • Was the variable assignment interesting? Sunday, January 15, 12
  32. THE PREAMBLE package Perl::Critic::Policty::SQL::Inject; Readonly::Scalar my $DESC => 'Interpolated string

    as SQL expression'; Readonly::Scalar my $EXPL => 'Potential SQL injection vulnerability'; Sunday, January 15, 12
  33. THE CONFIGURATION sub default_security { $SEVERITY_HIGH } sub default_themes {

    qw[custom] } sub applies_to { 'PPI::Token::Word' } Sunday, January 15, 12
  34. THE MONEY sub violates { my ($self, $elem) = @_;

    return unless $self->method_is_suspect($elem); return unless $self->first_arg_is_interesting( $elem); # ... } Sunday, January 15, 12
  35. THE MONEY sub violates { my ($self, $elem) = @_;

    # ... my @vars = $self->find_variables_in_arg($elem); return unless @vars; $self->violation($DESC, $EXPL, $elem); } Sunday, January 15, 12
  36. IS THIS METHOD SUSPECT? sub method_is_suspect { my ($self, $elem)

    = @_; return is_method_call($elem) && grep { $elem eq $_ } qw[do prepare]; } Sunday, January 15, 12
  37. IS FIRST ARGUMENT INTERESTING? sub first_arg_is_interesting { my ($self, $elem)

    = @_; my $arg = first_arg($elem); my $pt = 'PPI::Token'; return $arg->isa("$PT::Quote::Interpolate") || ($arg->isa("$PT::Quote::Double") && $arg->interpolations) || $arg->isa("$PT::Symbol"); } Sunday, January 15, 12
  38. LOOK FOR VARIABLES IN FIRST ARGUMENT sub find_variables_in_arg { my

    ($self, $elem) = @_; $self->find_variables_in_string( $elem, first_arg($elem) ); } Sunday, January 15, 12
  39. FINDING VARIABLES IN A STRING sub find_variables_in_string { my ($self,

    $elem, $string) = @_; @words = words_from_string( $string->can('string') ? $string->string : "$string" ); my @vars; for my $word (@words) { ... } return @vars; } Sunday, January 15, 12
  40. FINDING VARIABLES IN A STRING for my $word (@words) {

    my $wdoc = PPI::Document->new(\$word); my ($first) = $wdoc-> find_first('PPI::Statement')-> schildren; if ($first && $first->isa('PPI::Token::Quote')) { # We should traverse this... } } Sunday, January 15, 12
  41. FINDING VARIABLES IN A STRING if ($first && $first->isa('PPI::Token::Quote')) {

    my @found = $self- >find_variables_in_string($elem, $first); push @vars, @found if @found; next; } Sunday, January 15, 12
  42. FINDING VARIABLES IN A STRING for my $word (@words) {

    my $wdoc = PPI::Document->new(\$word); # If this word is quoted, traverse. my $sym = $wdoc-> find_first('PPI::Token::Symbol'); next unless $sym; push @vars, $sym; } Sunday, January 15, 12
  43. APPROACH UPGRADE • Walk up the source tree looking for

    variable declarations. • Filter arguments with variables whose assignment isn't interesting. • Look for statements validating/sanitizing variables. • Look for $dbh->quote() assignments; filter. Sunday, January 15, 12
  44. USEFUL RESOURCES • Perl::Critic • Perl::Critic::DEVELOPER • PPI Read the

    parser sources! • PPI::Dumper / ppi_dumper -CWl • Perl::Critic::Utils Sunday, January 15, 12