# frozen_string_literal: false require 'socket' module PWN module SAST # SAST Module used to identify banned function # calls in C & C++ code per: # https://msdn.microsoft.com/en-us/library/bb288454.aspx module BannedFunctionCallsC @@logger = PWN::Plugins::PWNLogger.create # Supported Method Parameters:: # PWN::SAST::BannedFunctionCallsC.scan( # :dir_path => 'optional path to dir defaults to .' # :git_repo_root_uri => 'optional http uri of git repo scanned' # ) public_class_method def self.scan(opts = {}) dir_path = opts[:dir_path] git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub result_arr = [] logger_results = '' PWN::Plugins::FileFu.recurse_dir(dir_path: dir_path) do |entry| if (File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/) && (File.extname(entry) == '.c' || File.extname(entry) == '.cpp' || File.extname(entry) == '.c++' || File.extname(entry) == '.cxx' || File.extname(entry) == '.h' || File.extname(entry) == '.hpp' || File.extname(entry) == '.h++' || File.extname(entry) == '.hh' || File.extname(entry) == '.hxx' || File.extname(entry) == '.ii' || File.extname(entry) == '.ixx' || File.extname(entry) == '.ipp' || File.extname(entry) == '.inl' || File.extname(entry) == '.txx' || File.extname(entry) == '.tpp' || File.extname(entry) == '.tpl') && entry !~ /test/i line_no_and_contents_arr = [] entry_beautified = false if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js')) js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED`.to_s.scrub entry = "#{entry}.JS-BEAUTIFIED" entry_beautified = true end test_case_filter = " grep -Fn \ -e 'strcpy' \ -e 'strcpyA' \ -e 'strcpyW' \ -e 'wcscpy' \ -e '_tcscpy' \ -e '_mbscpy' \ -e 'StrCpy' \ -e 'StrCpyA' \ -e 'StrCpyW' \ -e 'lstrcpy' \ -e 'lstrcpyA' \ -e 'lstrcpyW' \ -e '_tccpy' \ -e '_mbccpy' \ -e '_ftcscpy' \ -e 'strncpy' \ -e 'wcsncpy' \ -e '_tcsncpy' \ -e '_mbsncpy' \ -e '_mbsnbcpy' \ -e 'StrCpyN' \ -e 'StrCpyNA' \ -e 'StrCpyNW' \ -e 'StrNCpy' \ -e 'strcpynA' \ -e 'StrNCpyA' \ -e 'StrNCpyW' \ -e 'lstrcpyn' \ -e 'lstrcpynA' \ -e 'lstrcpynW' \ -e 'strcat' \ -e 'strcatA' \ -e 'strcatW' \ -e 'wcscat' \ -e '_tcscat' \ -e '_mbscat' \ -e 'StrCat' \ -e 'StrCatA' \ -e 'StrCatW' \ -e 'lstrcat' \ -e 'lstrcatA' \ -e 'lstrcatW' \ -e 'StrCatBuff' \ -e 'StrCatBuffA' \ -e 'StrCatBuffW' \ -e 'StrCatChainW' \ -e '_tccat' \ -e '_mbccat' \ -e '_ftcscat' \ -e 'strncat' \ -e 'wcsncat' \ -e '_tcsncat' \ -e '_mbsncat' \ -e '_mbsnbcat' \ -e 'StrCatN' \ -e 'StrCatNA' \ -e 'StrCatNW' \ -e 'StrNCat' \ -e 'StrNCatA' \ -e 'StrNCatW' \ -e 'lstrncat' \ -e 'lstrcatnA' \ -e 'lstrcatnW' \ -e 'lstrcatn' \ -e 'sprintfW' \ -e 'sprintfA' \ -e 'wsprintf' \ -e 'wsprintfW' \ -e 'wsprintfA' \ -e 'sprintf' \ -e 'swprintf' \ -e '_stprintf' \ -e 'wvsprintf' \ -e 'wvsprintfA' \ -e 'wvsprintfW' \ -e 'vsprintf' \ -e '_vstprintf' \ -e 'vswprintf' \ -e 'wvsprintf' \ -e 'wvsprintfA' \ -e 'wvsprintfW' \ -e 'vsprintf' \ -e '_vstprintf' \ -e 'vswprintf' \ -e 'strncpy' \ -e 'wcsncpy' \ -e '_tcsncpy' \ -e '_mbsncpy' \ -e '_mbsnbcpy' \ -e 'StrCpyN' \ -e 'StrCpyNA' \ -e 'StrCpyNW' \ -e 'StrNCpy' \ -e 'strcpynA' \ -e 'StrNCpyA' \ -e 'StrNCpyW' \ -e 'lstrcpyn' \ -e 'lstrcpynA' \ -e 'lstrcpynW' \ -e '_fstrncpy' \ -e 'strncat' \ -e 'wcsncat' \ -e '_tcsncat' \ -e '_mbsncat' \ -e '_mbsnbcat' \ -e 'StrCatN' \ -e 'StrCatNA' \ -e 'StrCatNW' \ -e 'StrNCat' \ -e 'StrNCatA' \ -e 'StrNCatW' \ -e 'lstrncat' \ -e 'lstrcatnA' \ -e 'lstrcatnW' \ -e 'lstrcatn' \ -e '_fstrncat' \ -e 'gets' \ -e '_getts' \ -e '_gettws' \ -e 'IsBadWritePtr' \ -e 'IsBadHugeWritePtr' \ -e 'IsBadReadPtr' \ -e 'IsBadHugeReadPtr' \ -e 'IsBadCodePtr' \ -e 'IsBadStringPtr' \ -e 'memcpy' \ -e 'RtlCopyMemory' \ -e 'CopyMemory' \ -e 'wmemcpy' #{entry} " str = `#{test_case_filter}`.to_s.scrub if str.to_s.empty? # If str length is >= 64 KB do not include results. (Due to Mongo Document Size Restrictions) logger_results = "#{logger_results}~" # Catching bugs is good :) else str = "1:Result larger than 64KB -> Size: #{str.to_s.length}. Please click the \"Path\" link for more details." if str.to_s.length >= 64_000 hash_line = { timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s, security_references: security_references, filename: { git_repo_root_uri: git_repo_root_uri, entry: entry }, line_no_and_contents: '', raw_content: str, test_case_filter: test_case_filter } # COMMMENT: Must be a better way to implement this (regex is kinda funky) line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1] line_no_count = line_contents_split.length # This should always be an even number current_count = 0 while line_no_count > current_count line_no = line_contents_split[current_count] contents = line_contents_split[current_count + 1] if Dir.exist?("#{dir_path}/.git") || Dir.exist?('.git') repo_root = dir_path repo_root = '.' if Dir.exist?('.git') author = PWN::Plugins::Git.get_author( repo_root: repo_root, from_line: line_no, to_line: line_no, target_file: entry, entry_beautified: entry_beautified ) else author = 'N/A' end hash_line[:line_no_and_contents] = line_no_and_contents_arr.push( line_no: line_no, contents: contents, author: author ) current_count += 2 end result_arr.push(hash_line) logger_results = "#{logger_results}x" # Seeing progress is good :) end end end logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html" if logger_results.empty? @@logger.info("#{logger_banner}: No files applicable to this test case.\n") else @@logger.info("#{logger_banner} => #{logger_results}complete.\n") end result_arr rescue StandardError => e raise e end # Used primarily to map NIST 800-53 Revision 4 Security Controls # https://web.nvd.nist.gov/view/800-53/Rev4/impact?impactName=HIGH # to PWN Exploit & Static Code Anti-Pattern Matching Modules to # Determine the level of Testing Coverage w/ PWN. public_class_method def self.security_references { sast_module: self, section: 'INFORMATION INPUT VALIDATION', nist_800_53_uri: 'https://csrc.nist.gov/Projects/risk-management/sp800-53-controls/release-search#/control/?version=5.1&number=SI-10', cwe_id: '676', cwe_uri: 'https://cwe.mitre.org/data/definitions/676.html' } rescue StandardError => e raise e end # Author(s):: 0day Inc. public_class_method def self.authors "AUTHOR(S): 0day Inc. " end # Display Usage for this Module public_class_method def self.help puts "USAGE: sast_arr = #{self}.scan( :dir_path => 'optional path to dir defaults to .', :git_repo_root_uri => 'optional http uri of git repo scanned' ) #{self}.authors " end end end end