Practical Static Analysis for Continuous Application Security

Practical Static Analysis for Continuous Application Security

Static code analysis tools that attempt determine what code does without actually running the code provide an excellent opportunity to perform lightweight security checks as part of the software development lifecycle. Unfortunately, building generic static analysis tools, especially for security, is a costly, time-consuming effort. As a result very few tools exist and commercial tools are very expensive - if they even support your programming language.

The good news is building targeted static analysis tools for your own environment with rules specific to your needs is much easier! Since static analysis tools can be run at any point in the software development lifecycle, even simple tools enable powerful security assurance when added to continuous integration. This talk will go through straight-forward options for static analysis, from grep to writing rules for existing tools through writing static analysis tools from scratch.

711272a06d435ca5139b50874351cdbf?s=128

Justin Collins

October 13, 2016
Tweet

Transcript

  1. 13.

    @presidentbeef 1 - Identify a Problem Repeated incidents with the

    same root cause Opt-in security, e.g. in APIs Unsafe calls/settings no one should use, ever 13
  2. 14.

    @presidentbeef 2 - Identify a Solution Write a safer library

    Make security opt-out Detect unsafe calls/settings/API usage 14
  3. 16.

    @presidentbeef 4 - Automate Enforcement Part of continuous integration Part

    of code review Standalone, continuously-running process Local scripts/hooks 16
  4. 31.

    @presidentbeef Bash git checkout $@ --quiet files=$(git diff --name-status master

    HEAD | grep -E "^(A|M)" | cut -f 2) grep "get_survey([^,)]\+)" $files 31
  5. 33.

    @presidentbeef Bash git checkout $@ --quiet files=$(git diff --name-status master

    HEAD | grep -E "^(A|M)" | cut -f 2) grep "get_survey([^,)]\+)" $files 33
  6. 35.

    @presidentbeef Changed Files Rules file1 - warning X file2 -

    warning Y file3 - warning X ... Multiple Rules 35
  7. 36.

    @presidentbeef Create a Rule class CheckSurvey < Rule def run(file_name,

    code) if code =~ /get_survey\([^,)]+\)/ warn file_name, "get_survey without user ID" end end end 36
  8. 37.

    @presidentbeef Base Rule Class class Rule @rules = [] @warnings

    = [] def self.inherited klass @rules << klass end def self.run_rules files files.each do |name| code = File.read(name) @rules.each do |r| r.new.run(name, code) end end end def self.warnings @warnings end def warn file_name, msg Rule.warnings << [file_name, msg] end end 37
  9. 38.

    @presidentbeef Code to Run It system "git checkout #{ARGV[0]}" files

    = `git diff --name-status master HEAD | grep -E "^(A|M)" | cut -f 2` Rule.run_rules(files.split) Rule.warnings.each do |file_name, message| puts "#{message} in #{file_name}" end if Rule.warnings.any? exit 1 end 38
  10. 43.

    @presidentbeef Compilation vs. Static Analysis Lexical Analysis Parse into Abstract

    Syntax Tree Input Program Text Convert to Intermediate Form(s) Optimize Output Compiled Code Analyze! Output Finding Report Semantic Analysis 43
  11. 47.

    @presidentbeef Python (Astroid) Module() body = [ Discard() value =

    CallFunc() func = Name(get_survey) args = [ Name(survey_id) ] starargs = kwargs = ] AstroidBuilder().string_build("get_survey(survey_id)") 47
  12. 48.

    @presidentbeef JavaScript (Esprima) { "type": "Program", "body": [ { "type":

    "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "get_survey" }, "arguments": [ { "type": "Identifier", "name": "survey_id" } ] } } ], "sourceType": "script" } esprima.parse("get_survey(survey_id)") 48
  13. 51.

    @presidentbeef Bandit Custom Rule import bandit from bandit.core import test_properties

    as test @test.checks('Call') @test.test_id('B350') def unsafe_get_survey(context): if (context.call_function_name_qual == 'get_survey' and context.call_args_count < 2): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, text="Use of get_survey without user ID." ) 51
  14. 54.

    @presidentbeef Brakeman Custom Check require 'brakeman/checks/base_check' class Brakeman::CheckGetSurvey < Brakeman::BaseCheck

    Brakeman::Checks.add self Brakeman::WarningCodes::Codes[:get_survey] = 2001 @description = "Finds get_survey calls without a user_id" def run_check @tracker.find_call(target: false, method: :get_survey).each do |result| next if duplicate? result add_result result if result[:call].second_arg.nil? warn :result => result, :warning_type => "Direct Object Reference", :warning_code => :get_survey, :message => "Use of get_survey without user_id", :confidence => CONFIDENCE[:high] end end end end 54
  15. 59.

    @presidentbeef JavaScript (Esprima) { "type": "Program", "body": [ { "type":

    "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "get_survey" }, "arguments": [ { "type": "Identifier", "name": "survey_id" } ] } } ], "sourceType": "script" } esprima.parse("get_survey(survey_id)") 59
  16. 60.

    @presidentbeef Walking Esprima AST var check_get_survey = function(ast) { if(ast.type

    == "CallExpression" && ast.callee.type == "Identifier" && ast.callee.name == "get_survey" && ast.arguments.length == 1) { console.log("get_survey called without user_id at line ", ast.loc.start) } } var walk = function(ast) { if(Array.isArray(ast)) { ast.forEach(walk); } else if(ast.type) { check_get_survey(ast) for (key in ast) { walk(ast[key]) } } } var esprima = require('esprima'); walk(esprima.parse('get_survey(survey_id)', { loc: true })) 60
  17. 63.

    @presidentbeef Walking RubyParser AST require 'ruby_parser' require 'sexp_processor' class FindGetSurvey

    < SexpInterpreter def process_call exp if exp[1].nil? and exp[2] == :get_survey and exp[4].nil? puts "get_survey called without user_id at line #{exp.line}" end end end ast = RubyParser.new.parse "get_survey(survey_id)" FindGetSurvey.new.process ast 63
  18. 64.

    @presidentbeef Summary Start small: identify single issue to solve Tailor

    solution to your environment Automate enforcement 64