apt-sec.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #!/usr/bin/python3
  2. ## New implementation of TrustMiner using python and mongodb
  3. ## Nikos
  4. import sys
  5. import os
  6. from pymongo import MongoClient
  7. #mongodb assumes database at default path
  8. import logging, sys
  9. import configparser
  10. import json
  11. import urllib.request
  12. import datetime
  13. import debian_advisory
  14. logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
  15. #load config file as library
  16. config = configparser.ConfigParser()
  17. config.read('config_test')
  18. if config.sections == []:
  19. print('configuration file not found\n')
  20. sys.exit(1)
  21. #global variables
  22. secperday = 60*60*24
  23. now = datetime.datetime.now()
  24. verbosity = 1
  25. ###############################################################################
  26. ## logging
  27. # 1 fatal errors
  28. # 2 errors
  29. # 3 note
  30. # 4 trace
  31. # 5 debug
  32. def msg(lvl,msg):
  33. if lvl <= int(config['LOG']['loglevel']):
  34. print(msg)
  35. def debug(msg):
  36. msg(5, msg)
  37. # Need to see if this is necessary
  38. ## load state, different from DBs in that we always need it
  39. def load_state():
  40. cache = config['DIR']['cache_dir'] + 'state'
  41. err = 0
  42. state = dict()
  43. try:
  44. with open(cache) as json_data:
  45. state = json.load(json_data)
  46. except FileNotFoundError:
  47. # Load default state - start from the beginning
  48. state['cache_dir'] = cache
  49. state['next_adv'] = 0
  50. state['next_fsa'] = 0
  51. state['Packages'] = ''
  52. state['Sources'] = ''
  53. state['Sha1Sums'] = ''
  54. err += 1
  55. return (state, err)
  56. ###############################################################################
  57. ## save state, different from DBs in that we always need it
  58. def save_state(state):
  59. cache = config['DIR']['cache_dir'] + 'state'
  60. try:
  61. with open(cache, 'w') as fp:
  62. json.dump(state, fp)
  63. except IOError:
  64. print('write cache failed!! Fatal error')
  65. sys.exit(1)
  66. ###############################################################################
  67. ## load sha lists :TODO later
  68. def load_sha1lists():
  69. cache = config['DIR']['cache_dir'] + 'state'
  70. ###############################################################################
  71. ## save sha lists :TODO later
  72. def save_sha1lists():
  73. pass
  74. ###############################################################################
  75. ## load from files :TODO later
  76. def load_DBs():
  77. pass
  78. ###############################################################################
  79. ## save to files :TODO later
  80. def save_DBs():
  81. pass
  82. ###############################################################################
  83. ## Fetch current Packages, Sources and sha1sums files
  84. ## These are needed to find CVE stats by sha1sums/pkg-names
  85. ## Only Sha1Sums is custom generated, others are from Debian.
  86. ## FIXME: Server might do on-the-fly gzip (but should not for bzip2)
  87. ## Return: 1 on success, to signal that new parsing is needed.
  88. def fetchMeta(filename):
  89. urlbase = config['URL']['pkg_base_url']
  90. mydir = config['DIR']['cache_dir']
  91. bzFile = filename + '.bz2'
  92. url = urlbase + bzFile
  93. logging.info('Checking meta file from ' + url + '\n')
  94. # Download file
  95. urllib.request.urlretrieve(url, mydir + bzfile)
  96. # TODO catch exceptions like file not found
  97. # TODO check if file has changed, if it is new unpack
  98. ###############################################################################
  99. # Sources and Packages are not completely consistent, esp for debian-multimedia
  100. # He we store manual mappings for these..
  101. def addOrphanPkgs(pkg2src):
  102. pkg2src['liblame-dev'] = "lame";
  103. pkg2src['lame-extras'] = "lame";
  104. pkg2src['moonlight'] = "moon";
  105. pkg2src['libmoon0'] = "moon";
  106. pkg2src['xmms-mp4'] = "xmms2";
  107. pkg2src['xmms-mp4'] = "xmms2";
  108. pkg2src['lazarus-src-0.9.30'] = "lazarus";
  109. pkg2src['lazarus-ide-0.9.30'] = "lazarus";
  110. pkg2src['lcl-qt4-0.9.30'] = "lazarus";
  111. pkg2src['lazarus-ide-qt4-0.9.30'] = "lazarus";
  112. pkg2src['lcl-gtk2-0.9.30'] = "lazarus";
  113. pkg2src['lazarus-ide-gtk2-0.9.30'] = "lazarus";
  114. pkg2src['lcl-units-0.9.30'] = "lazarus";
  115. pkg2src['lazarus-0.9.30'] = "lazarus";
  116. pkg2src['lazarus-doc-0.9.30'] = "lazarus";
  117. pkg2src['lcl-0.9.30'] = "lazarus";
  118. pkg2src['lcl-utils-0.9.30'] = "lazarus";
  119. pkg2src['lcl-nogui-0.9.30'] = "lazarus";
  120. pkg2src['libx264-65'] = "x264";
  121. pkg2src['libx264-114'] = "x264";
  122. pkg2src['libx264-60'] = "x264";
  123. # pkg2src['libmlt3']
  124. # pkg2src['libgmerlin-avdec0']
  125. # pkg2src['libxul-dev']
  126. # pkg2src['libmyth-0.23.1-0']
  127. # pkg2src['libmpeg3hv']
  128. # pkg2src['libquicktimehv']
  129. # pkg2src['libxul0d']
  130. # pkg2src['acroread-fonts-kor']
  131. ###############################################################################
  132. ## Parse dpkg Packages file, create map deb-name->pkg-name
  133. def parsePackages(pkgfile):
  134. mydir = cache = config['DIR']['cache_dir']
  135. deb2pkg = dict()
  136. pkg2virt = dict()
  137. virt2pkg = ()
  138. logging.info('Parsing Packages file...\n')
  139. pkgfile = mydir + pkgfile
  140. #TODO open and parse pkg file
  141. ###############################################################################
  142. ## Parse dpkg Sources file, create map pkg-name->src-name
  143. def parseSources(srcfile):
  144. mydir = cache = config['DIR']['cache_dir']
  145. checklinecont = 0
  146. pkg2src = dict()
  147. logging.info('Parsing Sources file...\n')
  148. srcfile = mydir + srcfile
  149. #TODO open and parse sources file
  150. ###############################################################################
  151. def getSHA1(myhash, collection):
  152. return collection.find({"hash": myhash})
  153. ###############################################################################
  154. def addSHA1(myhash, deb, src):
  155. dic = getSHA1(myhash)
  156. thash = dic["hash"]
  157. tdeb = dic["deb"]
  158. tsrc = dic["src"]
  159. #TODO insert SHA to database
  160. ###############################################################################
  161. ## Parse Sha1Sums file. Format: "sha1sum::deb-name::unix-file-path"
  162. ## Create 2 maps: sha1sum->file, file->deb-name
  163. def parseSha1Sums(sha1file):
  164. pass
  165. ###############################################################################
  166. ## Parse local dpkg status, return list of debs
  167. def parseStatus(stsfile):
  168. pass
  169. ###############################################################################
  170. ## Parse Advisory (only Debian supported atm
  171. def parseAdvisory(adv):
  172. if state['vendor'] == 'debian':
  173. return parseDSAhtml(adv)
  174. else:
  175. print('Unsupported distribution. We only support Debian at the moment')
  176. system.exit(1)
  177. ###############################################################################
  178. ## Manually fix problems with Advisory entries
  179. def fixAdvisoryQuirks(arg, state):
  180. if state['vendor'] == 'debian':
  181. return fixDSAquirks(arg)
  182. else:
  183. print('Unsupported distribution. We only support Debian at the moment')
  184. system.exit(1)
  185. ###############################################################################
  186. ## Extract CVE ids from new advisories and print URL for mirror script
  187. def printCVEs(myid,adv):
  188. logging.info('Looking for CVEs in advisory...\n')
  189. dsastats = parseAdvisory(adv)
  190. if dsastats == []:
  191. return
  192. ## fix DSAs that don't contain correct CVE refs
  193. dsastats = fixAdvisoryQuirks(myid, dsastats);
  194. #TODO Fix this part
  195. ##for cve_id in dsastats
  196. ###############################################################################
  197. ## Update internal vuln. DB with new Advisory info
  198. ## Creates CVEtable for MTBF computation:
  199. ## ( cve-id => (date, delay, score1, score2, score3))
  200. def updateCVETables(myid, dsatable, state):
  201. logging.info('Updating vulnerability database with advisory ' + state['vendor'] + myid + ' \n')
  202. adv = dsatable[myid]
  203. dsastats = parseAdvisory(adv)
  204. if dsastats == []:
  205. return
  206. dsastats = fixAdvisoryQuirks(myid, dsastats)
  207. for srcpkg in dsastats[0]:
  208. src2dsa[srcpkg] = myid
  209. dsa2cve[myid] = dsastats[2]
  210. for cve_id in dsastats[2]:
  211. # No fetch CVE We use mongodb and cve-search
  212. cve = fetchCVE(cve_id)
  213. cvestats = parseCVE(cve_id, cve)
  214. if vestats[0] > dsastats[1] or cvestats[0] == 0:
  215. cvestats[0] = dsastats[1]
  216. cvedata = (cvestats[0], dsastats[1]-cvestats[0], cvestats[1], cvestats[2], cvestats[3])
  217. cvetable[cve_id] = cvedata
  218. return cvetable
  219. ###############################################################################
  220. ## Check for updates on Package information
  221. def aptsec_update(state, config):
  222. args = sys.argv
  223. if not('--offline' in args):
  224. fetchMeta('Packages')
  225. fetchMeta('Sources')
  226. fetchMeta('Sha1Sums')
  227. if not('--cves' in args):
  228. parsePackages('Packages')
  229. parseSources('Sources')
  230. if not('--nosha1' in args):
  231. parseSha1sums('Sha1Sums')
  232. if state['vendor'] == 'debian':
  233. newAdv = checkDSAs(state, config)
  234. else:
  235. print('Unsupported distribution. We only support Debian at the moment')
  236. system.exit(1)
  237. for myid in newAdv:
  238. if myid in dsatable:
  239. logging.info(state['vendor'] + ' advisory ' + myid + ' already known.\n')
  240. elif '--cves' in args:
  241. ## scan for CVE urls only?
  242. printCVEs(myid, newAdv[myid])
  243. else:
  244. ## store advisory and parse it
  245. dsatable[myid] = newAdv[myid]
  246. updateCVETables(myid)
  247. # recompute all pkg statistics
  248. for srcpkg in src2dsa:
  249. processCVEs(srcpkg)
  250. ###############################################################################
  251. ## find list of src pkgs from bin pkgs based on pkg2src
  252. def resolvePkg2Src(pkglist, pkg2src):
  253. srclist = []
  254. for pkg in pkglist:
  255. if pkg in pkg2src:
  256. srcpkg = pkg2src[pkg]
  257. srclist.append(srcpkg)
  258. else:
  259. logging.info('Could not find source package for: ' + pkg + ' .\n')
  260. return srclist
  261. ###############################################################################
  262. ## compute and store MTBF, MTBR and Scores of each src pkg
  263. ## output: %src2mtbf:
  264. ## (srcpkg=> (begin, num, delaysum, scoresum, maximpact, MTTF, MTTFl))
  265. def processCVEs(pkg, now, src2dsa, cvetable, config):
  266. stats = [now, 0, 0, 0, 0, 0, 0]
  267. mylambda = config['TRUST']['lambda']
  268. logging.info('Processing package: ' + pkg + ' .\n')
  269. ## @cvestats = (date base-score impact-score exploit-score)
  270. for dsa_id in src2dsa[pkg]:
  271. for cve_id in dsa2cve[dsa_id]:
  272. cvestats[cvetable[cve_id][0]] += 1
  273. stats[1] += 1
  274. stats[2] += cvetable[cve_id][1]
  275. stats[3] += cvetable[cve_id][2]
  276. if stats[4] < cvetable[cve_id][3]:
  277. stats[4] = cvetable[cve_id][3]
  278. # Ignore pkgs with less than one incident, should not happen..
  279. if stats[1] < 1:
  280. return
  281. prev_date = 0
  282. weight = 0
  283. dates = sorted(cvestats, key = cvestats.get)
  284. stats[0] = dates[0]
  285. for date in dates:
  286. pass
  287. ## Need to do compute value
  288. ##TODO Code to compute trust goes here
  289. ###############################################################################
  290. ## print some meta-info on internal data
  291. def aptsec_about(dsatable, cvetable, pkg2src, src2dsa):
  292. num_dsa = len(dsatable)
  293. num_cve = len(cvetable)
  294. num_pkg = len(pkg2src)
  295. num_src = len(src2dsa)
  296. print('\nThe current database records %d binary packages and %d DSAs.\n', num_pkg, num_src)
  297. print('%d CVEs are assiciated with %d source packages.\n', num_cve, num_src)
  298. ###############################################################################
  299. ## use scores to suggest alternative packages
  300. def aptsec_alternatives(pkg):
  301. pass
  302. ###############################################################################
  303. ## print overview for pkg high scores
  304. def aptsec_hitlist():
  305. pass
  306. ###############################################################################
  307. ## evaluation helper
  308. ## compute stats until date given in $2, then compute stats
  309. ## for the next year to check accuracy of the prediction.
  310. ## @cvestats = (date base-score impact-score exploit-score)
  311. def simulate_stats(pkg, year):
  312. pass
  313. ###############################################################################
  314. ##TODO Printing functions
  315. ###############################################################################
  316. ## show info on a single src pkg, resolv to src if needed
  317. def aptsec_show(pkg, state, pkg2src, src2dsa, src2mtbf, cvetable):
  318. if state['vendor'] == 'debian':
  319. ADV = 'DSA-'
  320. else:
  321. print('Unsupported distribution. We only support Debian at the moment')
  322. system.exit(1)
  323. if (not(pkg in src2dsa)) and (pkg in pkg2src):
  324. print('\nResolving ' + pkg + ' to ' + pkg2src[pkg] + '\n')
  325. pkg = pkg2src[pkg]
  326. print('\nThe following binary packages are created from ' + pkg + ' :\n\n')
  327. lines = 0
  328. for i in pkg2src:
  329. if pkg2src[i] == pkg:
  330. print(i + '\n')
  331. lines += 1
  332. if lines < 1:
  333. print('-\n')
  334. if not (pkg in src2dsa and pkg in src2mtbf):
  335. print('\nNo vulnerabilities recorded for source package ' + pkg + '.\n')
  336. return
  337. print('\nAdvisories on package ' + pkg + ':\n\n')
  338. for dsa_id in sorted(src2dsa[pkg], key = src2dsa[pkg].get):
  339. print(ADV + dsa_id + '\n')
  340. for cve_id in dsa2cve[dsa_id]:
  341. (sec, minut, hrs, day, mon, yr) = gmtime(cvetable[cve_id][0])
  342. print('%s: Base Score: %04.1f, %02d.%02d.%04d\n', cve_id, cvetable[cve_id][2], day, mon+1, yr+1900)
  343. stats = src2mtbf[pkg]
  344. (sec, minut, hrs, day, mon, yr) = gmtime(stats[0])
  345. print('Now we print various iformation \n')
  346. ###############################################################################
  347. ## print help text
  348. def aptsec_help():
  349. print('See manual for correct usage\n')
  350. ###############################################################################
  351. ## Print system status report from component(files) measurements (sha1sums)
  352. ## Expected input format is Linux IMA. We assume input was validated.
  353. ##
  354. ## Note: aptsec_status(), considers *reportedly installed* packages, while this
  355. ## one looks at *actually loaded* software that influenced the CPU since bootup.
  356. def aptsec_attest(sha1file):
  357. pass
  358. ## Main Program starts here!!
  359. try:
  360. action = sys.argv[1]
  361. except IndexError:
  362. # print('No argument given')
  363. # aptsec_help()
  364. # sys.exit(0)
  365. action = ''
  366. client = MongoClient()
  367. cve_db = client.cvedb
  368. (state, err) = load_state()
  369. #detect_distribution()
  370. #d = state['cache_dir']
  371. #if not os.path.exists(d):
  372. # os.makedirs(d)
  373. if action == 'update':
  374. load_DBs()
  375. # loadsha1lists()
  376. aptsec_update()
  377. # save_sha1lists()
  378. save_DBs()
  379. save_state()
  380. elif action == 'status':
  381. load_DBs or exit(1)
  382. #handle errors more gracefully
  383. aptsec_status(sys.argv[2])
  384. elif action == 'show':
  385. load_DBs or exit(1)
  386. #handle errors more gracefully
  387. aptsec_show(sys.argv[2])
  388. else:
  389. aptsec_help()
  390. print(state)
  391. save_state(state)
  392. #client = MongoClient()
  393. #cve_db = client.cvedb
  394. #collection = db.cves
  395. #testcvss = collection.find_one({"cvss": 9.3})
  396. #print(testcvss)