# HG changeset patch # User Benoît Allard # Date 1411478354 -7200 # Node ID e18b61a73a68e17b99c5db604f133eed4e20aa2f Initial Release diff -r 000000000000 -r e18b61a73a68 CHANGES --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CHANGES Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,14 @@ +FarolLuz 0.1 (2014-09-23) +========================= + +This is the first release of FarolLuz. FarolLuz is a set of library / +utilities to manipulate Security Advisories. It is part of Farol, a Security +Advisory Management Platform. + +It has the following features: + +- Parsing of CVRF Documents +- Generation of CVRF Documents +- Generation of RedHat LSC as NASL scripts +- Various utility methods on the CVRF objects +- Experimental Generation of RedHat LSC as OVAL scripts diff -r 000000000000 -r e18b61a73a68 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r 000000000000 -r e18b61a73a68 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,1 @@ +include farolluz/templates/*.j2 diff -r 000000000000 -r e18b61a73a68 README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.txt Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,33 @@ +========== + FarolLuz +========== + +------------------------------------------- +A Utility to manipulate Security Advisories +------------------------------------------- + +:version: 0.1 +:license: GPLv2+ + +Introduction +------------ + +FarolLuz is a set of library / utilities to manipulate security advisories. It +is part of Farol, a Security Advisory Management Platform. + +Main features +------------- + +* Read CVRF advisories +* Write CVRF Advisories +* Write NASL scripts +* Write OVAL Advisories (experimental) + +Installation +------------ + +To install ``farolluz``, use the pip package manager (preferably inside a +virtualenv):: + + pip install farolluz + diff -r 000000000000 -r e18b61a73a68 farolluz/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/__init__.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +# Description: +# A utility to manipulate Security Advisories +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Farol Luz: A utility to manipulate security advisories + +This is part of Farol: A Security Advisory Management Platform. +""" + +__version__ = '0.1' diff -r 000000000000 -r e18b61a73a68 farolluz/cvrf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/cvrf.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,1016 @@ +# -*- coding: utf-8 -*- +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Objects related to CVRF Documents +""" + +class ValidationError(Exception): pass + +class CVRFPublisher(object): + TYPES = ('Vendor', 'Discoverer', 'Coordinator', 'User', 'Other') + def __init__(self, _type, vendorid=None): + self._type = _type + self._vendorid = vendorid + self._contact = None + self._authority = None + + def setContact(self, contact): + self._contact = contact + + def setAuthority(self, authority): + self._authority = authority + + def validate(self): + if not self._type: + raise ValidationError('Document Publisher needs to have a type') + if self._type not in self.TYPES: + raise ValidationError('Document Publisher Type needs to be one of %s' % ', '.join(self.TYPES)) + + def __str__(self): + s = 'CVRFPublisher: %s' % self._type + if self._vendorid is not None: + s += ' ID: %s' % self._vendorid + if self._contact is not None: + s += ' Contact: "%s"' % self._contact + if self._authority is not None: + s += ' Authority: "%s"' % self._authority + return s + + +class CVRFTrackingID(object): + def __init__(self, _id): + self._id = _id + self._aliases = [] + + def addAlias(self, alias): + self._aliases.append(alias) + + def validate(self): + if not self._id: + raise ValidationError('Document ID cannot be left empty') + + def __str__(self): + if self._aliases: + return "%s (%s)" % (self._id, ', '.join(self._aliases)) + return self._id + + +class CVRFTracking(object): + STATUSES = ('Draft', 'Interim', 'Final') + def __init__(self, _id, status, version, initial, current): + self._identification = _id + self._status = status + self._version = version + self._history = [] + self._initialDate = initial + self._currentDate = current + self._generator = None + + def addRevision(self, revision): + self._history.append(revision) + + def setGenerator(self, generator): + self._generator = generator + + def validate(self): + if self._identification is None: + raise ValidationError('Document Tracking needs to have an Identification') + self._identification.validate() + if not self._status: + raise ValidationError('Document status must be set') + if self._status not in self.STATUSES: + raise ValidationError('Document Status must be one of %s' % ', '.join(self.STATUSES)) + if not self._version: + raise ValidationError('Document Version must be set') + if len(self._version) > 4: + raise ValidationError('Document Version must be comprised between `nn` and `nn.nn.nn.nn`') + if not self._history: + raise ValidationError('Document must have at least a revision') + if not self._initialDate: + raise ValidationError('Document must have an initial Release date set') + prev_date = self._initialDate + if self._history[0]._date < self._initialDate: + # Documents could have revisions before being released + prev_date = self._history[0]._date + prev = () + for revision in self._history: + revision.validate() + if revision._number <= prev: + raise ValidationError('Revision numbers must always be increasing') + if revision._date < prev_date: + raise ValidationError('Revision dates must always be increasing') + prev = revision._number + prev_date = revision._date + if not self._currentDate: + raise ValidationError('Document must have a Current Release Date set') + if self._currentDate != self._history[-1]._date: + raise ValidationError('Current Release Date must be the same as the Date from the last Revision') + if self._initialDate > self._currentDate: + raise ValidationError('Initial date must not be after current Date') + if self._version != self._history[-1]._number: + raise ValidationError('Document version must be the same as the number of the last Revision') + + def __str__(self): + s = "ID: %s" % self._identification + s += " Status: %s" % self._status + s += " v%s" % '.'.join('%d' % i for i in self._version) + s += " %d revisions" % len(self._history) + s += " Initial release: %s" % self._initialDate.isoformat() + return s + + +class CVRFRevision(object): + def __init__(self, number, date, description): + self._number = number + self._date = date + self._description = description + + def validate(self): + if not self._number: + raise ValidationError('A Revision must have a Number') + if not self._date: + raise ValidationError('A Revision must have a Date') + if not self._description: + raise ValidationError('A Revision must have a Description') + +class CVRFGenerator(object): + def __init__(self): + self._engine = None + self._date = None + + def setEngine(self, engine): + self._engine = engine + + def setDate(self, date): + self._date = date + + def validate(self): + if (not self._engine) and (not self._date): + raise ValidationError('The Generator must have at least an Engine or a Date') + + +class CVRFNote(object): + TYPES = ('General', 'Details', 'Description', 'Summary', 'FAQ', + 'Legal Disclaimer', 'Other') + def __init__(self, _type, ordinal, note, title=None, audience=None): + self._type = _type + self._ordinal = ordinal + self._note = note + self._title = title + self._audience = audience + + def getTitle(self): + """ returns something that can be used as a title """ + if self._title is None: + return "%s (#%d)" % (self._type, self._ordinal) + return "%s (%s)" % (self._title, self._type) + + def validate(self): + if not self._type: + raise ValidationError('A Note needs to have a Type set') + if self._type not in self.TYPES: + raise ValidationError('A Note Type needs to be one of %s' % ', '.join(self.TYPES)) + if self._ordinal < 0: + raise ValidationError('A Note ordinal must be a positive integer') + if not self._note: + raise ValidationError('A Note must contain some text') + + + def __str__(self): + return self._note + + +class CVRFReference(object): + TYPES = ('Self', 'External') + def __init__(self, url, description, _type=None): + self._url = url + self._description = description + self._type = _type + + def validate(self): + if (self._type is not None) and (self._type not in self.TYPES): + raise ValidationError('If a Reference type is set, it mist be one of %s' % ', '.join(self.TYPES)) + if not self._url: + raise ValidationError('A Reference must contain an URL') + if not self._description: + raise ValidationError('A Reference must contain a description') + + +class CVRFAcknowledgment(object): + def __init__(self, name=None, organization=None, description=None, + url=None): + self._name = name + self._organization = organization + self._description = description + self._url = url + + def getTitle(self): + return "%s - %s" % (self._name, self._organization) + + def validate(self): + if (not self._name) and (not self._organization) and (not self._description): + raise ValidationError('An Acknowledgment must have at least a Name, an Organization or a Description') + + +class CVRFProductTree(object): + def __init__(self): + # All the branches, they can be order with their `parent` attribute + self._branches = [] + self._groups = [] + self._relationships = [] + self._products = [] + self._groups = [] + + def addBranch(self, branch): + parent = self.getBranch(branch.getParent().getPath()) + if parent is self: + self._branches.append(branch) + else: + parent._childs.append(branch) + + def addProduct(self, product): + if product not in self._products: + self._products.append(product) + if product._parent is not self: + product._parent._product = product + + def addRelationship(self, rel): + self._relationships.append(rel) + + def addGroup(self, group): + self._groups.append(group) + + def getProductForID(self, productid): + for product in self._products: + if product._productid == productid: + return product + raise KeyError(productid) + + def getGroupForID(self, groupid): + for group in self._groups: + if group._groupid == groupid: + return group + raise KeyError(groupid) + + def decomposeProduct(self, productid): + """ In case of product defined as a relationship (product X installed + on OS Y), this gives us the following tuple: (OS, product). """ + product = self.getProductForID(productid) + parent = product._parent + if parent is None: + return (None, None) + if not isinstance(parent, CVRFRelationship): + return (None, None) + relationtype = parent._relationtype.replace(' ', '').lower() + if relationtype not in ('defaultcomponentof', 'installedon'): + return (None, None) + return ( + self.getProductForID(parent._relatestoproductreference), + self.getProductForID(parent._productreference) + ) + + def getBranch(self, path): + if len(path) == 0: + return self + branches = self._branches + node = None + for idx in path: + node = branches[idx] + branches = node._childs + return node + + def getBranches(self): + for branch in self._branches: + yield branch + for sub_branch in branch.getBranches(): + yield sub_branch + + def getPath(self): + return () + + def getNameOfRelationship(self, relationship): + if relationship is None: + return '' + return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', + relationship._relationtype.lower(), + self.getProductForID(relationship._relatestoproductreference)._name)) + + def getOrphanedBranches(self, product=None): + """ The branches that could accept `product` as Product Definition """ + white_list = [] + if product is not None: + white_list = [product._parent] + for branch in self.getBranches(): + if (branch in white_list) or branch.isOrphaned(): + yield branch + + def getNotTerminalBranches(self, b2=None): + """\ + The branches that could accept `b2` as new sub-branches + Note that b2 and all its sub-branches cannot be listed + """ + black_list = [] + if b2 is not None: + black_list = [b2] + list(b2.getBranches()) + for branch in self.getBranches(): + if branch in black_list: + continue + if branch._product is None: + yield branch + + def getOrphanedRelationships(self, product=None): + """ The relationships that need a product defninition """ + white_list = [] + if product is not None: + white_list = [product.getCurrentRelationship()] + for i, relationship in enumerate(self._relationships): + if (relationship in white_list) or relationship.isOrphaned(): + yield (i, relationship) + + def nbProducts(self): + """ Amount of 'raw' Products """ + return len([p for p in self._products if p._parent is self]) + + def validate(self): + for branch in self._branches: + branch.validate() + productids = set() + for product in self._products: + product.validate() + if product._productid in productids: + raise ValidationError('Each ProductID must be unique (%s)' % product._productid) + productids.add(product._productid) + for relationship in self._relationships: + relationship.validate() + for productid in (relationship._productreference, + relationship._relatestoproductreference): + if productid not in productids: + raise ValidationError('ProductID %s is unknown' % productid) + groupids = set() + for group in self._groups: + group.validate() + if group._groupid in groupids: + raise ValidationError('Duplicated GroupID: %s' % group._groupid) + groupids.add(group._groupid) + for productid in group._productids: + if productid not in productids: + raise ValidationError('ProductID %s is unknown' % productid) + return productids, groupids + + def __str__(self): + return 'Products: %s' % '\n'.join(str(p) for p in self._products) + + +class CVRFProductBranch(object): + TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', + 'Patch Level', 'Service Pack', 'Architecture', 'Language', + 'Legacy', 'Specification') + def __init__(self, _type, name, parentbranch): + self._type = _type + self._name = name + self._parentbranch = parentbranch + self._childs = [] + self._product = None + + def getParent(self): + return self._parentbranch + + def getPath(self, string=False): + """ return the path to that branch element as a tuple """ + if self.isRoot(): + for i, b in enumerate(self._parentbranch._branches): + if b is self: + if string: + return '%d' % i + return (i, ) + else: + for i, b in enumerate(self._parentbranch._childs): + if b is self: + if string: + return '/'.join([self._parentbranch.getPath(string), '%d' % i]) + return self._parentbranch.getPath(string) + (i,) + if string: + return '' + return () + + def getTree(self): + """ this returns a list of tuples (type, name) leading to here""" + if self.isRoot(): + return [(self._type, self._name)] + return self._parentbranch.getTree() + [(self._type, self._name)] + + def getName(self): + return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) + + def getParentPath(self): + """ return as string the path to the parent """ + return '/'.join('%s' % p for p in self.getPath()[:-1]) + + def isRoot(self): + return isinstance(self._parentbranch, CVRFProductTree) + + def isOrphaned(self): + """ Has no childs and no product """ + return len(self._childs) == 0 and (self._product is None) + + def getBranches(self): + for branch in self._childs: + yield branch + for sub_branch in branch.getBranches(): + yield sub_branch + + def unlink(self): + """ Unset our _parent, and remove us from the _parent._childs """ + if self.isRoot(): + self.getParent()._branches.remove(self) + else: + self.getParent()._childs.remove(self) + self._parentbranch = None + + def validate(self): + if not self._type: + raise ValidationError('A Branch must have a Type') + if self._type not in self.TYPES: + raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) + if not self._name: + raise ValidationError('A Branch must have a Name') + for branch in self._childs: + branch.validate() + if self.isOrphaned(): + raise ValidationError('A Branch must have at least a sub-product or sub-branches') + + def __str__(self): + return "%s: %s" % (self._type, self._name) + + +class CVRFFullProductName(object): + def __init__(self, productid, name, parent, cpe=None): + self._productid = productid + self._name = name + # Can be None (directly under the tree), a ProductBranch, or a + # Relationship + self._parent = parent + self._cpe = cpe + + def isRoot(self): + return isinstance(self._parent, CVRFProductTree) + + def isRelationship(self): + return isinstance(self._parent, CVRFRelationship) + + def getTree(self): + if not isinstance(self._parent, CVRFProductBranch): + return [] + return self._parent.getTree() + + def getParentPath(self): + if self.isRoot() or self.isRelationship(): + return '' + return self._parent.getPath(True) + + def getCurrentRelationship(self): + if self.isRelationship(): + return self._parent + return None + + def unlink(self): + """ Unset our _parent, and remove us from the _parent._childs """ + if self.isRoot(): + self._parent._products.remove(self) + else: + self._parent._product = None + self._parent = None + + def validate(self): + if not self._productid: + raise ValidationError('A Product must have a ProductID') + if not self._name: + raise ValidationError('A Product must have a Name') + + def __str__(self): + return "%s (%s)" % (self._productid, self._name) + + +class CVRFRelationship(object): + TYPES = ('Default Component Of', 'Optional Component Of', + 'External Component Of', 'Installed On', 'Installed With') + def __init__(self, productref, reltype, relatestoproductref): + self._productreference = productref + self._relationtype = reltype + self._relatestoproductreference = relatestoproductref + self._product = None + + def getParent(self): + """ All parent element of a FullProductName should implement that + method """ + return None + + def isOrphaned(self): + return self._product is None + + def validate(self): + if not self._productreference: + raise ValidationError('A Relationship must have a Product Reference') + if not self._relationtype: + raise ValidationError('A Relationship must have a Relation Type') + if self._relationtype not in self.TYPES: + raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) + if not self._relatestoproductreference: + raise ValidationError('A Relationship must have a "Relates To product Reference"') + if self._productreference == self._relatestoproductreference: + raise ValidationError('A Relationship cannot reference twice the same Product') + + +class CVRFGroup(object): + def __init__(self, groupid): + self._groupid = groupid + self._description = None + self._productids = [] + + def setDescription(self, description): + self._description = description + + def addProductID(self, productid): + self._productids.append(productid) + + def getTitle(self): + if self._description: + return "%s (%d products)" % (self._description, len(self._productids)) + return "#%s (%d products)" % (self._groupid, len(self._productids)) + + def validate(self): + if not self._groupid: + raise ValidationError('A Group must have a GroupID') + if not self._productids or len(self._productids) < 2: + raise ValidationError('A Group must contain at least two products') + + +class CVRFVulnerabilityID(object): + def __init__(self, systemname, value): + self._systemname = systemname + self._value = value + + def validate(self): + if not self._systemname: + raise ValidationError('A Vulnerability ID must have a System Name') + if not self._value: + raise ValidationError('A Vulnerability ID must have a value') + + +class CVRFVulnerability(object): + def __init__(self, ordinal): + self._ordinal = ordinal + self._title = None + self._id = None + self._notes = [] + self._discoverydate = None + self._releasedate = None + self._involvements = [] + self._cve = None + self._cwes = [] + self._productstatuses = [] + self._threats = [] + self._cvsss = [] + self._remediations = [] + self._references = [] + self._acknowledgments = [] + + def setTitle(self, title): + self._title = title + + def setID(self, _id): + self._id = _id + + def addNote(self, note): + self._notes.append(note) + + def setDiscoveryDate(self, date): + self._discoverydate = date + + def setReleaseDate(self, date): + self._releasedate = date + + def addInvolvement(self, involvement): + self._involvements.append(involvement) + + def setCVE(self, cve): + self._cve = cve + + def addCWE(self, cwe): + self._cwes.append(cwe) + + def addProductStatus(self, productstatus): + self._productstatuses.append(productstatus) + + def addThreat(self, threat): + self._threats.append(threat) + + def addCVSSSet(self, cvss_set): + self._cvsss.append(cvss_set) + + def addRemediation(self, remediation): + self._remediations.append(remediation) + + def addReference(self, ref): + self._references.append(ref) + + def addAcknowledgment(self, ack): + self._acknowledgments.append(ack) + + def getTitle(self): + """ return something that can be used as a title """ + if self._title: + if self._id: + return "%s (%s)" % (self._title, self._id._value) + return self._title + if self._id: + return self._id._value + return "#%d" % self._ordinal + + def validate(self, productids, groupids): + if not self._ordinal: + raise ValidationError('A Vulnerability must have an ordinal') + if self._id is not None: + self._id.validate() + ordinals = set() + for note in self._notes: + note.validate() + if note._ordinal in ordinals: + raise ValidationError('Vulnerability Note Ordinal %d duplicated' % note._ordinal) + ordinals.add(note._ordinal) + for involvement in self._involvements: + involvement.validate() + for cwe in self._cwes: + cwe.validate() + for status in self._productstatuses: + status.validate(productids) + for threat in self._threats: + threat.validate(productids, groupids) + for cvss in self._cvsss: + cvss.validate(productids) + for remediation in self._remediations: + remediation.validate(productids, groupids) + for reference in self._references: + reference.validate() + for acknowledgment in self._acknowledgments: + acknowledgment.validate() + + + +class CVRFInvolvement(object): + PARTIES = CVRFPublisher.TYPES + STATUSES = ('Open', 'Disputed', 'In Progress', 'Completed', + 'Contact Attempted', 'Not Contacted') + def __init__(self, party, status): + self._party = party + self._status = status + self._description = None + + def setDescription(self, description): + self._description = description + + def getTitle(self): + return "From %s: %s" % (self._party, self._status) + + def validate(self): + if not self._party: + raise ValidationError('An Involvement must have a Party') + if self._party not in self.PARTIES: + raise ValidationError("An Involvement's Party must be one of %s" % ', '.join(self.PARTIES)) + if not self._status: + raise ValidationError('An Involvement must have a Status') + if self._status not in self.STATUSES: + raise ValidationError("An Involvement's Status must be one of %s" % ', '.join(self.STATUSES)) + + +class CVRFCWE(object): + def __init__(self, _id, value): + self._id = _id + self._value = value + + def validate(self): + if not self._id: + raise ValidationError('A CWE must have an ID') + if not self._value: + raise ValidationError('A CWE must have a description') + + +class CVRFProductStatus(object): + TYPES = ('First Affected', 'Known Affected', 'Known Not Affected', + 'First Fixed', 'Fixed', 'Recommended', 'Last Affected') + def __init__(self, _type): + self._type = _type + self._productids = [] + + def addProductID(self, productid): + self._productids.append(productid) + + def getTitle(self): + return "%s: %d products" % (self._type, len(self._productids)) + + def validate(self, productids): + if not self._type: + raise ValidationError('A Product Status must have a Type') + if self._type not in self.TYPES: + raise ValidationError("A Product Status' Type must be one of %s" % ', '.join(self.TYPES)) + if len(self._productids) < 1: + raise ValidationError('A Product Status must mention at least one Product') + for productid in self._productids: + if productid not in productids: + raise ValidationError('Unknown ProductID: %s' % productid) + + +class CVRFThreat(object): + TYPES = ('Impact', 'Exploit Status', 'Target Set') + def __init__(self, _type, description): + self._type = _type + self._description = description + self._date = None + self._productids = [] + self._groupids = [] + + def setDate(self, date): + self._date = date + + def addProductID(self, productid): + self._productids.append(productid) + + def addGroupID(self, groupid): + self._groupids.append(groupid) + + def getTitle(self): + return self._type + + def validate(self, productids, groupids): + if not self._type: + raise ValidationError('A Threat must have a Type') + if self._type not in self.TYPES: + raise ValidationError("A Threat's Type must be one of %s" % ', '.join(self.TYPES)) + if not self._description: + raise ValidationError('A Threat must have a Description') + for productid in self._productids: + if productid not in productids: + raise ValidationError('Unknown ProductID: %s' % productid) + for groupid in self._groupids: + if groupid not in groupids: + raise ValidationError('Unknown GroupID: %s' % groupid) + + +class CVRFCVSSSet(object): + # To determine the base Score + VALUES = {'AV': {'L':0.395, 'A':0.646, 'N':1.0}, + 'AC': {'H':0.35, 'M':0.61 ,'L':0.71}, + 'Au': {'M':0.45, 'S':0.56, 'N':0.704}, + 'C': {'N':0.0, 'P':0.275, 'C':0.66}, + 'I': {'N':0.0, 'P':0.275, 'C':0.66}, + 'A': {'N':0.0, 'P':0.275, 'C':0.66}} + def __init__(self, basescore): + self._basescore = basescore + self._temporalscore = None + self._environmentalscore = None + self._vector = None + self.vector = None + self._productids = [] + + def setTemporalScore(self, tempscore): + self._temporalscore = tempscore + + def setEnvironmentalScore(self, envscore): + self._environmentalscore = envscore + + def setVector(self, vector): + self._vector = vector + if vector is None: + self.vector = vector + return + try: + self.vector = {} + for component in vector[:26].split('/'): + name, value = component.split(':') + self.vector[name] = self.VALUES[name][value] + except (KeyError, ValueError): + self.vector = None + + def addProductID(self, productid): + self._productids.append(productid) + + def baseScore(self): + v = self.vector # make an alias for shorter lines + exploitability = 20 * v['AV'] * v['AC'] * v['Au'] + impact = 10.41 * (1 - (1 - v['C']) * (1 - v['I']) * (1 - v['A'])) + def f(i): return 0 if i == 0 else 1.176 + return ((0.6 * impact) + (0.4 * exploitability) - 1.5) * f(impact) + + def validate(self, productids): + if not self._basescore: + raise ValidationError('A CVSS Score Set must have a Base Score') + if self._vector and not self.vector: + raise ValidationError('Syntax Error in CVSS Vector') + if abs(self._basescore - self.baseScore()) >= 0.05: + raise ValidationError('Inconsistency in CVSS Score Set between Vector (%f) and Base Score (%f)' % (self.baseScore(), self._basescore)) + for productid in self._productids: + if productid not in productids: + raise ValidationError('Unknown ProductID: %s' % productid) + + +class CVRFRemediation(object): + TYPES = ('Workaround', 'Mitigation', 'Vendor Fix', 'None Available', + 'Will Not Fix') + def __init__(self, _type, description): + self._type = _type + self._description = description + self._date = None + self._entitlement = None + self._url = None + self._productids = [] + self._groupids = [] + + def setDate(self, date): + self._date = date + + def setEntitlement(self, entitlement): + self._entitlement = entitlement + + def setURL(self, url): + self._url = url + + def addProductID(self, productid): + self._productids.append(productid) + + def addGroupID(self, groupid): + self._groupids.append(groupid) + + def getTitle(self): + return self._type + + def validate(self, productids, groupids): + if not self._type: + raise ValidationError('A Remediation must have a Type') + if self._type not in self.TYPES: + raise ValidationError("A Remediation's Type must be one of %s" % ', '.join(self.TYPES)) + if not self._description: + raise ValidationError('A Remediation must have a Description') + for productid in self._productids: + if productid not in productids: + raise ValidationError('Unknown ProductID: %s' % productid) + for groupid in self._groupids: + if groupid not in groupids: + raise ValidationError('Unknown GroupID: %s' % groupid) + +class CVRF(object): + def __init__(self, title, _type): + self._title = title + self._type = _type + self._publisher = None + self._tracking = None + self._notes = [] + self._distribution = None + self._references = [] + self._acknowledgments = [] + self._producttree = None + self._vulnerabilities = [] + + def setPublisher(self, publisher): + self._publisher = publisher + + def setTracking(self, tracking): + self._tracking = tracking + + def addNote(self, note): + self._notes.append(note) + + def setDistribution(self, distribution): + self._distribution = distribution + + def addReference(self, ref): + self._references.append(ref) + + def addAcknowledgment(self, ack): + self._acknowledgments.append(ack) + + def createProductTree(self): + """ only done if the element is there """ + self._producttree = CVRFProductTree() + return self._producttree + + def addVulnerability(self, vuln): + self._vulnerabilities.append(vuln) + + def getProductForID(self, productid): + if self._producttree is None: + raise ValueError('No ProductTree') + return self._producttree.getProductForID(productid) + + def getGroupForID(self, groupid): + if self._producttree is None: + raise ValueError('No ProductTree') + return self._producttree.getGroupForID(groupid) + + def getHighestCVSS(self): + highestBaseScore = 0 + highest = None + for vulnerability in self._vulnerabilities: + for cvss in vulnerability._cvsss: + if cvss._basescore <= highestBaseScore: + continue + highestBaseScore = cvss._basescore + highest = cvss + return highest + + def getProductList(self, type_='Fixed'): + products = set() + if type_ == 'Fixed': + # First try through the Remediation + for vulnerability in self._vulnerabilities: + for remediation in vulnerability._remediations: + if remediation._type != 'Vendor Fix': + continue + for productid in remediation._productids: + products.add(productid) + for groupid in remediation._groupids: + for productid in self.getGroupForID(groupid)._productids: + products.add(productid) + if not products: + # If nothing there, try through the productstatuses + for vulnerability in self._vulnerabilities: + for status in vulnerability._productstatuses: + if status._type != type_: + continue + for productid in status._productids: + products.add(productid) + return set(self.getProductForID(p) for p in products) + + def getNote(self, ordinal): + for note in self._notes: + if note._ordinal == ordinal: + return note + return None + + def validate(self): + if not self._title: + raise ValidationError('Document Title cannot be empty') + if not self._type: + raise ValidationError('Document Type cannot be empty') + if self._publisher is None: + raise ValidationError('Document Publisher needs to be set') + self._publisher.validate() + if self._tracking is None: + raise ValidationError('Document Tracking needs to be set') + self._tracking.validate() + ordinals = set() + for note in self._notes: + note.validate() + if note._ordinal in ordinals: + raise ValidationError('Document Note ordinal %d is issued twice' % note._ordinal) + ordinals.add(note._ordinal) + for reference in self._references: + reference.validate() + for acknowledgment in self._acknowledgments: + acknowledgment.validate() + productids = set() + groupids = set() + if self._producttree: + productids, groupids = self._producttree.validate() + ordinals = set() + for vulnerability in self._vulnerabilities: + vulnerability.validate(productids, groupids) + if vulnerability._ordinal in ordinals: + raise ValidationError('Vulnerability ordinal %d is issued twice' % vulnerability._ordinal) + ordinals.add(vulnerability._ordinal) + + def __str__(self): + s = [ + 'Title: %s' % self._title, + 'Type: %s' % self._type, + 'Publisher: %s' % self._publisher, + 'tracking: %s' % self._tracking, + '%d Notes: %s' % (len(self._notes), ', '.join( + str(n) for n in self._notes)) + ] + if self._distribution is not None: + s.append('Distribution: %s' % self._distribution) + s.extend([ + '%d Acknowledgments' % len(self._acknowledgments), + 'Products: %s' % self._producttree, + ]) + return '\n'.join(s) diff -r 000000000000 -r e18b61a73a68 farolluz/parsers/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/parsers/__init__.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +# Description: +# A utility to manipulate Security Advisories +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. diff -r 000000000000 -r e18b61a73a68 farolluz/parsers/cvrf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/parsers/cvrf.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- +# Description: +# Methods for parsing CVRF documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Methods for parsing of CVRF Documents +""" + +from __future__ import print_function + +import re +import textwrap +import xml.etree.ElementTree as ET +from datetime import datetime, timedelta + +try: + from datetime import timezone +except ImportError: + from ..py2 import FixedTimeZone as timezone + +from ..cvrf import (CVRF, CVRFPublisher, CVRFTracking, CVRFRevision, CVRFNote, + CVRFAcknowledgment, CVRFProductBranch, CVRFFullProductName, CVRFGenerator, + CVRFRelationship, CVRFVulnerability, CVRFVulnerabilityID, CVRFThreat, + CVRFProductStatus, CVRFCVSSSet, CVRFReference, CVRFRemediation, CVRFGroup, + CVRFInvolvement, CVRFCWE, CVRFTrackingID) + +NAMESPACES = { + 'cvrf': "http://www.icasi.org/CVRF/schema/cvrf/1.1", + 'prod': "http://www.icasi.org/CVRF/schema/prod/1.1", + 'vuln': "http://www.icasi.org/CVRF/schema/vuln/1.1", + 'xml': "http://www.w3.org/XML/1998/namespace", +} + + +def UN(ns, name): + """ UN for Universal Name """ + return "{%s}%s" % (NAMESPACES[ns], name) + + +def parseVersion(string): + return tuple(int(i) for i in string.split('.')) + + +def parseDate(string): + m = re.match('(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:([+-])(\d{2}):(\d{2})|(Z))?', string) + if (m.group(7) is None) or (m.group(7) == 'Z'): + tzhours = 0 + tzmin = 0 + else: + tzhours = int(m.group(8)) + if m.group(7) == '-': + tzhours = - tzhours + tzmin = int(m.group(9)) + return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6)), tzinfo=timezone(timedelta(hours=tzhours, minutes=tzmin))) + + +def parseNote(elem): + return CVRFNote( + elem.attrib['Type'], + int(elem.attrib['Ordinal']), + textwrap.dedent(elem.text).strip(), + elem.attrib.get('Title'), + elem.attrib.get('Audience') + ) + + +def parseReference(elem, ns='cvrf'): + """ ns is the current namespace """ + return CVRFReference( + elem.findtext(UN(ns, 'URL')).strip(), + textwrap.dedent(elem.findtext(UN(ns, 'Description'))).strip(), + elem.attrib.get('Type') + ) + + +def parseAcknowledgment(elem, ns='cvrf'): + return CVRFAcknowledgment( + elem.findtext(UN(ns, 'Name')), + elem.findtext(UN(ns, 'Organization')), + elem.findtext(UN(ns, 'Description')), + elem.findtext(UN(ns, 'URL')), + ) + + +def parseFullProductName(elem, parent): + return CVRFFullProductName( + elem.attrib['ProductID'], + elem.text.strip(), + parent, + cpe=elem.attrib.get('CPE') + ) + + +def parseProdBranch(elem, ptree, parentbranch=None): + """ Recursively parses the branches and the terminal productnames """ + fpncvrf = elem.find(UN('prod', 'FullProductName')) + if (parentbranch is not None) and (fpncvrf is not None): + # Don't process the products at the root of the tree + prod = parseFullProductName(fpncvrf, parentbranch) + ptree.addProduct(prod) + + if parentbranch is None: + parentbranch = ptree + for brcvrf in elem.findall(UN('prod', 'Branch')): + br = CVRFProductBranch(brcvrf.attrib['Type'], brcvrf.attrib['Name'], parentbranch) + # And go into recursion ... + br._childs = list(parseProdBranch(brcvrf, ptree, br)) + yield br + + +def parseVulnerability(elem): + vuln = CVRFVulnerability(int(elem.attrib['Ordinal'])) + + xmltitle = elem.findtext(UN('vuln', 'Title')) + if xmltitle is not None: + vuln.setTitle(xmltitle.strip()) + + xmlID = elem.find(UN('vuln', 'ID')) + if xmlID is not None: + vuln.setID(CVRFVulnerabilityID(xmlID.attrib['SystemName'], xmlID.text.strip())) + + for xmlnote in elem.findall('/'.join([UN('vuln', 'Notes'), UN('vuln', 'Note')])): + vuln.addNote(parseNote(xmlnote)) + + xmldiscoverydate = elem.findtext(UN('vuln', 'DiscoveryDate')) + if xmldiscoverydate is not None: + vuln.setDiscoveryDate(parseDate(xmldiscoverydate)) + xmlreleasedate = elem.findtext(UN('vuln', 'ReleaseDate')) + if xmlreleasedate is not None: + vuln.setReleaseDate(parseDate(xmlreleasedate)) + + for xmlinv in elem.findall('/'.join([UN('vuln', 'Involvements'), UN('vuln', 'Involvement')])): + involvement = CVRFInvolvement( + xmlinv.attrib['Party'], + xmlinv.attrib['Status'] + ) + xmldescr = xmlinv.findtext(UN('vuln', 'Description')) + if xmldescr is not None: + involvement.setDescription(textwrap.dedent(xmldescr).strip()) + vuln.addInvolvement(involvement) + + xmlcve = elem.findtext(UN('vuln', 'CVE')) + if xmlcve is not None: + vuln.setCVE(xmlcve.strip()) + + for xmlcwe in elem.findall(UN('vuln', 'CWE')): + vuln.addCWE(CVRFCWE( + xmlcwe.attrib['ID'], + xmlcwe.text.strip() + )) + + for xmlstatus in elem.findall('/'.join([UN('vuln', 'ProductStatuses'), UN('vuln', 'Status')])): + status = CVRFProductStatus(xmlstatus.attrib['Type']) + for xmlproductid in xmlstatus.findall(UN('vuln', 'ProductID')): + status.addProductID(xmlproductid.text.strip()) + + vuln.addProductStatus(status) + + for xmlthreat in elem.findall('/'.join([UN('vuln', 'Threats'), UN('vuln', 'Threat')])): + threat = CVRFThreat( + xmlthreat.attrib['Type'], + textwrap.dedent(xmlthreat.findtext(UN('vuln', 'Description'))).strip() + ) + xmldate = xmlthreat.findtext(UN('vuln', 'Date')) + if xmldate is not None: + threat.setDate(parseDate(xmldate)) + for xmlpid in xmlthreat.findall(UN('vuln', 'ProductID')): + threat.addProductID(xmlpid.text.strip()) + for xmlgid in xmlthreat.findall(UN('vuln', 'GroupID')): + threat.addGroupID(xmlgid.text.strip()) + + vuln.addThreat(threat) + + for xmlcvss in elem.findall('/'.join([UN('vuln', 'CVSSScoreSets'), UN('vuln', 'ScoreSet')])): + cvss_set = CVRFCVSSSet(float(xmlcvss.findtext(UN('vuln', 'BaseScore')).strip())) + xmltempscore = xmlcvss.findtext(UN('vuln', 'TemporalScore')) + if xmltempscore is not None: + cvss_set.setTemporalScore(float(xmltempscore.strip())) + xmlenvscore = xmlcvss.findtext(UN('vuln', 'EnvironmentalScore')) + if xmlenvscore is not None: + cvss_set.setEnvironmentalScore(float(xmlenvscore.strip())) + xmlvector = xmlcvss.findtext(UN('vuln', 'Vector')) + if xmlvector is not None: + cvss_set.setVector(xmlvector.strip()) + for xmlprodid in xmlcvss.findall(UN('vuln', 'ProductID')): + cvss_set.addProductID(xmlprodid.text.strip()) + + vuln.addCVSSSet(cvss_set) + + for xmlremediation in elem.findall('/'.join([UN('vuln', 'Remediations'), UN('vuln', 'Remediation')])): + remediation = CVRFRemediation( + xmlremediation.attrib['Type'], + textwrap.dedent(xmlremediation.findtext(UN('vuln', 'Description'))).strip() + ) + xmldate = xmlremediation.findtext(UN('vuln', 'Date')) + if xmldate is not None: + remediation.setDate(parseDate(xmldate)) + xmlentitlement = xmlremediation.findtext(UN('vuln', 'Entitlement')) + if xmlentitlement is not None: + remediation.setEntitlement(textwrap.dedent(xmlentitlement).strip()) + xmlurl = xmlremediation.findtext(UN('vuln', 'URL')) + if xmlurl is not None: + remediation.setURL(xmlurl.strip()) + for xmlpid in xmlremediation.findall(UN('vuln', 'ProductID')): + remediation.addProductID(xmlpid.text.strip()) + for xmlgid in xmlremediation.findall(UN('vuln', 'GroupID')): + remediation.addGroupID(xmlgid.text.strip()) + + vuln.addRemediation(remediation) + + for xmlref in elem.findall('/'.join([UN('vuln', 'References'), UN('vuln', 'Reference')])): + vuln.addReference(parseReference(xmlref, 'vuln')) + + for xmlack in elem.findall('/'.join([UN('vuln', 'Acknowledgments'), UN('vuln', 'Acknowledgment')])): + vuln.addAcknowledgment(parseAcknowledgment(xmlack, 'vuln')) + + return vuln + + +def parse(xml): + if hasattr(xml, 'read'): + xml = xml.read() + cvrfdoc = ET.fromstring(xml) + if cvrfdoc.tag != UN('cvrf', 'cvrfdoc'): + raise ValueError('Not a CVRF document !') + doc = CVRF( + cvrfdoc.findtext(UN('cvrf', 'DocumentTitle')).strip(), + cvrfdoc.findtext(UN('cvrf', 'DocumentType')).strip() + ) + cvrfpub = cvrfdoc.find(UN('cvrf', 'DocumentPublisher')) + pub = CVRFPublisher(cvrfpub.attrib['Type'], cvrfpub.attrib.get('VendorID')) + doc.setPublisher(pub) + contact = cvrfpub.find(UN('cvrf', 'ContactDetails')) + if contact is not None: + pub.setContact(contact.text.strip()) + authority = cvrfpub.find(UN('cvrf', 'IssuingAuthority')) + if authority is not None: + pub.setAuthority(authority.text.strip()) + cvrftracking = cvrfdoc.find(UN('cvrf', 'DocumentTracking')) + identification = CVRFTrackingID( + cvrftracking.findtext('/'.join([UN('cvrf', 'Identification'), UN('cvrf', 'ID')])).strip() + ) + for cvrfalias in cvrftracking.findall('/'.join([UN('cvrf', 'Identification'), UN('cvrf', 'Alias')])): + identification.addAlias(cvrfalias.text.strip()) + tracking = CVRFTracking( + identification, + cvrftracking.findtext(UN('cvrf', 'Status')).strip(), + parseVersion(cvrftracking.findtext(UN('cvrf', 'Version')).strip()), + parseDate(cvrftracking.findtext(UN('cvrf', 'InitialReleaseDate')).strip()), + parseDate(cvrftracking.findtext(UN('cvrf', 'CurrentReleaseDate')).strip()) + ) + doc.setTracking(tracking) + for cvrfrev in cvrftracking.findall('/'.join([UN('cvrf', 'RevisionHistory'), UN('cvrf', 'Revision')])): + rev = CVRFRevision( + parseVersion(cvrfrev.findtext(UN('cvrf', 'Number')).strip()), + parseDate(cvrfrev.findtext(UN('cvrf', 'Date')).strip()), + cvrfrev.findtext(UN('cvrf', 'Description')).strip(), + ) + tracking.addRevision(rev) + + xmlgenerator = cvrftracking.find(UN('cvrf', 'Generator')) + if xmlgenerator is not None: + generator = CVRFGenerator() + xmlengine = xmlgenerator.findtext(UN('cvrf', 'Engine')) + if xmlengine is not None: + generator.setEngine(xmlengine.strip()) + xmldate = xmlgenerator.findtext(UN('cvrf', 'Date')) + if xmldate is not None: + generator.setDate(parseDate(xmldate.strip())) + tracking.setGenerator(generator) + + for cvrfnote in cvrfdoc.findall('/'.join([UN('cvrf', 'DocumentNotes'), UN('cvrf', 'Note')])): + doc.addNote(parseNote(cvrfnote)) + + distr = cvrfdoc.findtext(UN('cvrf', 'DocumentDistribution')) + if distr is not None: + doc.setDistribution(textwrap.dedent(distr).strip()) + + # This is in a quite free format, not sure how to do something with it ... + xmlaggsev = cvrfdoc.find(UN('cvrf', 'AggregateSeverity')) + + for xmlref in cvrfdoc.findall('/'.join([UN('cvrf', 'DocumentReferences'), UN('cvrf', 'Reference')])): + doc.addReference(parseReference(xmlref)) + + for cvrfack in cvrfdoc.findall('/'.join([UN('cvrf', 'Acknowledgments'), UN('cvrf', 'Acknowledgment')])): + doc.addAcknowledgment(parseAcknowledgment(cvrfack)) + + # --- The ProductTree + + cvrfptree = cvrfdoc.find(UN('prod', 'ProductTree')) + if cvrfptree is not None: + producttree = doc.createProductTree() + for branch in parseProdBranch(cvrfptree, producttree): + producttree.addBranch(branch) + + for product in cvrfptree.findall(UN('prod', 'FullProductName')): + producttree.addProduct(parseFullProductName(product, producttree)) + + for cvrfrel in cvrfptree.findall(UN('prod', 'Relationship')): + rel = CVRFRelationship( + cvrfrel.attrib['ProductReference'], + cvrfrel.attrib['RelationType'], + cvrfrel.attrib['RelatesToProductReference'] + ) + producttree.addRelationship(rel) + producttree.addProduct(parseFullProductName(cvrfrel.find(UN('prod', 'FullProductName')), rel)) + + for xmlgroup in cvrfptree.findall('/'.join([UN('prod', 'ProductGroups'), UN('prod', 'Group')])): + group = CVRFGroup(xmlgroup.attrib['GroupID']) + xmldescr = xmlgroup.findtext(UN('prod', 'Description')) + if xmldescr is not None: + group.setDescription(textwrap.dedent(xmldescr).strip()) + for xmlpid in xmlgroup.findall(UN('prod', 'ProductID')): + group.addProductID(xmlpid.text.strip()) + producttree.addGroup(group) + + # --- The Vulnerabilities + + for cvrfvuln in cvrfdoc.findall(UN('vuln', 'Vulnerability')): + doc.addVulnerability(parseVulnerability(cvrfvuln)) + + return doc + + +if __name__ == "__main__": + import sys + with open(sys.argv[1], 'rt') as f: + cvrf = parse(f) + cvrf.validate() + print(cvrf) + print(cvrf.getHighestCVSS()._vector) + print(cvrf.getProductList()) + print(cvrf._producttree._branches) +# print(cvrf._producttree._branches[0]._childs) diff -r 000000000000 -r e18b61a73a68 farolluz/py2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/py2.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Description: +# Python2 Compatibility module +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Glue for missing features of python3 in python2 +""" + +from datetime import tzinfo, timedelta + +class FixedTimeZone(tzinfo): + def __init__(self, offset): + self.__offset = offset + + def utcoffset(self, dt): + return self.__offset + + def dst(self, dt): + return timedelta(0) + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen diff -r 000000000000 -r e18b61a73a68 farolluz/renderer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/renderer.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,153 @@ +# -*- encoding: utf-8 -*- +# Description: +# Module related to the rendering of a Security Advisory. +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import print_function + +import os +import sys +from datetime import datetime +import jinja2 + +from .parsers import cvrf +from .utils import utcnow + +# Supported Red Hat OSes for parsing. The value is as used in +# gather-package-list.nasl to set "ssh/login/release" +# Refer to that file, or the lsc_generator for a complete list. +OS_MAP = { + 'Red Hat Enterprise Linux Server (v. 7)' : 'RHENT_7', + 'Red Hat Enterprise Linux Server (v. 6)' : 'RHENT_6', + 'Red Hat Enterprise Linux Workstation (v. 6)' : 'RHENT_6', + 'Red Hat Enterprise Linux Desktop (v. 6)' : 'RHENT_6', + 'Red Hat Enterprise Linux Desktop 6' : 'RHENT_6', + 'Red Hat Enterprise Linux (v. 5 server)' : 'RHENT_5', + 'Red Hat Enterprise Linux ES version 2.1' : 'RHENT_2.1', + 'Red Hat Enterprise Linux WS version 2.1' : 'RHENT_2.1', + 'Red Hat Enterprise Linux AS version 3' : 'RHENT_3', + 'Red Hat Enterprise Linux ES version 3' : 'RHENT_3', + 'Red Hat Enterprise Linux WS version 3' : 'RHENT_3', + 'Red Hat Enterprise Linux AS version 4' : 'RHENT_4', + 'Red Hat Enterprise Linux ES version 4' : 'RHENT_4', + 'Red Hat Enterprise Linux WS version 4' : 'RHENT_4', + 'Red Hat Enterprise Linux AS (Advanced Server) version 2.1' : 'RHENT_2.1', +} + +def calculateRiskFactor(cvss_score, debug=0): + """ + Calculates and Return Risk Factor given CVSS Base Score + """ + cvss_score = float(cvss_score) + + for high, name in [ + (2, 'Low'), + (5, 'Medium'), + (8, 'High'), + (10, 'Critical')]: + if cvss_score <= high: + return name + + +def getReleaseName(os_name): + return OS_MAP.get(os_name, 'UNKNOWN') + +def getPackageName(rpm_name): + return rpm_name.split('-')[0] + +def PackageNameForrpmvuln(package_name): + package_name = package_name.rstrip('.src.rpm|.x86_64.rpm') + return (package_name.replace('-', '~')) + +def render(cvrf, templatepath, **kwargs): + + red_hat = False + script_family = "" + os_cpe = "" + ## check the platform + if "Red Hat Security Advisory" in cvrf._title: + red_hat = True + script_family = "Red Hat Local Security Checks" + os_cpe = "cpe:/o:redhat:enterprise_linux" + +# product_id = cvrf._vulnerabilities[0]._productstatuses[0]._productids +# print (product_id) +# for eachid in cvrf._vulnerabilities[0]._productstatuses[0]._productids: +# print(cvrf.getProductForID(eachid)._name) +# +# print("productnames") +# print(', '.join(p._name for p in cvrf.getProductList())) + + + + templatedir = os.path.join(os.path.dirname(__file__), 'templates') + templateLoader = jinja2.FileSystemLoader(searchpath=templatedir) + templateEnv = jinja2.Environment( + loader=templateLoader, + extensions=['jinja2.ext.with_'] + ) + + templateEnv.filters['risk_factor'] = calculateRiskFactor + templateEnv.filters['release_map'] = getReleaseName + templateEnv.filters['package_name'] = getPackageName + templateEnv.filters['for_rpmvuln'] = PackageNameForrpmvuln + + template = templateEnv.get_template(templatepath) + + + + templateVars = { + "cvrf": cvrf, + "script_id": 0, + "now" : utcnow(), + "red_hat": red_hat, + "script_family" : script_family, + "os_cpe" : os_cpe, + + } + + templateVars.update(kwargs) + + return template.render(templateVars) + +def main(cvrfpath, templatepath): + + with open(cvrfpath, 'rt') as f: + cvrfdoc = cvrf.parse(f) + + outputText = render(cvrfdoc, templatepath) + + adv_id = cvrfdoc._tracking._identification._id + if adv_id : + file_name = adv_id.replace(":", "_"); + file_name = "gb_" + file_name + "." + os.path.basename(templatepath).split('.')[0] + + with open(file_name, 'w') as file_handle: + file_handle.write(outputText) + print("file written to:", file_name) + +if __name__ == "__main__": + import sys + template = "nasl.j2" + if len(sys.argv) >= 3: + template = sys.argv[2] + main(sys.argv[1], template) diff -r 000000000000 -r e18b61a73a68 farolluz/templates/cvrf.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/templates/cvrf.j2 Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,258 @@ +{# +# Description: +# Template for generation of CVRF documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +-#} + + + +{#- Some macros for producttree generation #} +{%- macro FullProductNames(producttree, parent) %} + {%- for product in producttree._products %} + {%- if product._parent is sameas parent %} + + {{- product._name -}} + + {%- endif %} + {%- endfor %} +{%- endmacro %} + +{%- macro Note(note) -%} + + {{- note._note | escape -}} + +{%- endmacro %} + + {{ cvrf._title }} + {{ cvrf._type }} + {%- with publisher = cvrf._publisher %} + + {%- if publisher._contact %} + {{ publisher._contact }} + {%- endif %} + {%- if publisher._authority %} + {{ publisher._authority }} + {%- endif %} + + {%- endwith %} + {%- with tracking = cvrf._tracking %} + + + {{ tracking._identification._id }} + {%- for alias in tracking._identification._aliases %} + {{ alias }} + {%- endfor %} + + {{ tracking._status }} + {{ tracking._version | join('.') }} + + {%- for revision in tracking._history %} + + {{ revision._number | join('.') }} + {{ revision._date.isoformat() }} + {{ revision._description }} + + {%- endfor %} + + {{ tracking._initialDate.isoformat() }} + {{ tracking._currentDate.isoformat() }} + {%- if tracking._generator %} + + {%- with generator = tracking._generator %} + {%- if generator._engine %} + {{ generator._engine }} + {%- endif %} + {%- if generator._date %} + {{ generator._date.isoformat() }} + {%- endif %} + {%- endwith %} + + {%- endif %} + + {%- endwith %} + {%- if cvrf._notes %} + + {%- for note in cvrf._notes %} + {{ Note(note) }} + {%- endfor %} + + {%- endif %} + {%- if cvrf._distribution %} + {{ cvrf._distribution }} + {%- endif %} + {#- AggregateSeverity is missing #} + {%- if cvrf._references %} + + {%- for reference in cvrf._references %} + + {{ reference._url }} + {{ reference._description }} + + {%- endfor %} + + {%- endif %} + {%- if cvrf._acknowledgments %} + + {%- for acknowledgment in cvrf._acknowledgments %} + + {%- if acknowledgment._name %} + {{ acknowledgment._name }} + {%- endif %} + {%- if acknowledgment._organization %} + {{ acknowledgment._organization }} + {%- endif %} + {%- if acknowledgment._description %} + {{ acknowledgment._description }} + {%- endif %} + {%- if acknowledgment._url %} + {{ acknowledgment._url }} + {%- endif %} + + {%- endfor %} + + {%- endif %} + {%- if cvrf._producttree %} + + {%- with producttree = cvrf._producttree %} + {%- for branch in producttree._branches recursive %} + + {{- loop(branch._childs) }} + {{- FullProductNames(producttree, branch) }} + + {%- endfor %} + {{ FullProductNames(producttree, producttree) }} + {%- for relationship in producttree._relationships -%} + + {{- FullProductNames(producttree, relationship) }} + + {%- endfor %} + {%- if producttree._groups %} + + {%- for group in producttree._groups %} + + {%- if group._description %} + {{ group._description }} + {%- endif %} + {%- for productid in group._productids %} + {{ productid }} + {%- endfor %} + + {%- endfor %} + + {%- endif %} + {%- endwith %} + + {%- endif %} + {%- for vulnerability in cvrf._vulnerabilities %} + + {%- if vulnerability._title %} + {{ vulnerability._title }} + {%- endif %} + {%- if vulnerability._id %} + {{ vulnerability._id._value }} + {%- endif %} + {%- if vulnerability._notes %} + + {%- for note in vulnerability._notes %} + {{ Note(note) }} + {%- endfor %} + + {%- endif %} + {%- if vulnerability._discoverydate %} + {{ vulnerability._discoverydate.isoformat() }} + {%- endif %} + {%- if vulnerability._releasedate %} + {{ vulnerability._releasedate.isoformat() }} + {%- endif %} + {%- if vulnerability._involvements %} + + {%- for involvement in vulnerability._involvements %} + + {%- if involvement._description %} + {{ involvement._description }} + {%- endif %} + + {%- endfor %} + + {%- endif %} + {%- if vulnerability._cve %} + {{ vulnerability._cve }} + {%- endif %} + {%- for cwe in vulnerability._cwes %} + {{ cwe._value }} + {%- endfor %} + {%- if vulnerability._productstatuses %} + + {%- for status in vulnerability._productstatuses %} + + {%- for productid in status._productids %} + {{ productid }} + {%- endfor %} + + {%- endfor %} + + {%- endif %} + {%- if vulnerability._cvsss %} + + {%- for cvss in vulnerability._cvsss %} + + {{ cvss._basescore }} + {%- if cvss._temporalscore %} + {{ cvss._temporalscore }} + {%- endif %} + {%- if cvss._environmentalscore %} + {{ cvss._environmentalscore }} + {%- endif %} + {%- if cvss._vector %} + {{ cvss._vector }} + {%- endif %} + {%- for productid in cvss._productids %} + {{productid}} + {%- endfor %} + + {%- endfor %} + + {%- endif %} + {%- if vulnerability._remediations %} + + {%- for remediation in vulnerability._remediations %} + + {{ remediation._description }} + {%- if remediation._entitlement %} + {{ remediation._entitlement }} + {%- endif %} + {%- if remediation._url %} + {{ remediation._url }} + {%- endif %} + {%- for productid in remediation._productids %} + {{ productid }} + {%- endfor %} + {%- for groupid in remediation._groupids %} + {{ groupid }} + {%- endfor %} + + {%- endfor %} + + {%- endif %} + + {%- endfor %} + diff -r 000000000000 -r e18b61a73a68 farolluz/templates/nasl.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/templates/nasl.j2 Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,102 @@ +{# +# Description: +# Template for generation of NASL documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +-#} + +{% import "nasl_core.j2" as nasl_core -%} +############################################################################### +# OpenVAS Vulnerability Test +# +# {{ cvrf._title }} ({{ cvrf._tracking._identification._id }}) +# +# Authors: +# System Generated Check +# +# Copyright: +# Copyright (C) {{ now.strftime('%Y') }} Greenbone Networks GmbH, http://www.greenbone.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# (or any later version), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################### + +if(description) +{ + script_oid("1.3.6.1.4.1.25623.1.0.{{ script_id }}"); + script_version("$Revision$"); + script_tag(name:"check_type", value:"authenticated package test"); + script_tag(name:"last_modification", value:"$Date$"); + script_tag(name:"creation_date", value:"{{ now.strftime('%F %T %z (%a, %d %b %Y)') }}"); + script_cve_id({% for vuln in cvrf._vulnerabilities %}"{{ vuln._cve }}"{% if not loop.last %}, {% endif %}{% endfor %}); + script_tag(name:"cvss_base", value:"{{ cvrf.getHighestCVSS()._basescore }}"); + script_tag(name:"cvss_base_vector", value:"{{ cvrf.getHighestCVSS()._vector }}"); + script_name("{{ cvrf._title }} ({{ cvrf._tracking._identification._id }})"); + script_tag(name: "summary", value: "{{ nasl_core.notes(cvrf, 'Summary') }}"); + script_tag(name: "insight", value: "{{ nasl_core.notes(cvrf, 'Details') }}"); + script_tag(name: "affected" , value: " +{%- for product in cvrf.getProductList() %} + {{ product._name }} {% if not loop.last %}, {% endif %} + +{%- endfor %}"); + script_tag(name: "solution" , value:"Please Install the Updated Packages."); + + script_xref(name: "{{ cvrf._tracking._id }}", value: "{{ cvrf._tracking._id }}"); +{%- for ref in cvrf._references %} + script_xref(name: "URL" , value: "{{ ref._url }}"); +{%- endfor %} + script_summary("Check for the {{ cvrf._title }}"); + script_category(ACT_GATHER_INFO); + script_copyright("Copyright (C) {{ now.strftime('%Y') }} Greenbone Networks GmbH"); + script_family("{{ script_family }}"); + script_dependencies("gather-package-list.nasl"); + script_mandatory_keys({% if os_cpe %}"HostDetails/OS/{{ os_cpe }}", {% endif %}"login/SSH/success", "ssh/login/release"); + exit(0); +} + + +{% if red_hat %}include("pkg-lib-rpm.inc");{% endif %} + +release = get_kb_item("ssh/login/release"); + +res = ""; +if(release == NULL){ + exit(0); +} + +{%- if red_hat %} +{{ nasl_core.isrpmvuln(cvrf.getProductList(), cvrf._producttree) }} +{%- else %} +# Not red_hat +{%- endif %} + +if (__pkg_match) exit(99); # Not vulnerable. +exit(0); diff -r 000000000000 -r e18b61a73a68 farolluz/templates/nasl_core.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/templates/nasl_core.j2 Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,59 @@ +{# +# Description: +# Template macros for generation of NASL documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +-#} + +{% macro isrpmvuln(plist, ptree) %} +{%- set done = [] %} +{% for prod in plist %} + {%- set os, package = ptree.decomposeProduct(prod._productid) %} + {%- if os is not none %} + {%- set rls = os._name | release_map %} + {%- set rpm = package._name | for_rpmvuln %} + {%- if rls != 'UNKNOWN' %} + {%- if (rls, rpm) not in done %} +# {{ package._name }} on {{ os._name }} +if ((res = isrpmvuln(pkg:"{{ package._name | package_name }}", rpm:"{{ rpm }}", rls:"{{ rls }}")) != NULL) + { + security_message(data:res); + exit(0); + } + {{- done.append((rls, rpm)) or '' }} + {%- endif %} + {%- else %} +# Unknown release: {{ os._name }} + {%- endif %} + {%- endif %} +{%- endfor %} +{% endmacro %} + +{% macro notes(cvrf, name) %} +{%- for note in cvrf._notes %} + {%- if note._type == name or note._title == name %}{{ note._note.replace('"', "'") }}{% endif %} +{%- endfor %} +{%- for vulnerability in cvrf._vulnerabilities %} + {%- for note in vulnerability._notes %} + {%- if note._type == name or note._title == name %}{{ note._note.replace('"', "'") }}{% endif %} + {%- endfor %} +{%- endfor %} +{% endmacro %} diff -r 000000000000 -r e18b61a73a68 farolluz/templates/oval.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/templates/oval.j2 Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,154 @@ +{# +# Description: +# Template for generation of OVAL documents +# +# Authors: +# Antu Sanadi +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +-#} + + +{% import "oval_core.j2" as oval_core -%} + + + SecPod SCAP Repository + 5.10 + {{ now.isoformat() }} + + +{%- set done = [] %} +{% for prod in cvrf.getProductList() %} + {%- set os, package = cvrf._producttree.decomposeProduct(prod._productid) %} + {%- if os is not none %} + {%- set rls = os._name | release_map %} + {%- set rpm = package._name | for_rpmvuln %} + {%- if rls != 'UNKNOWN' %} + {%- if (rls, rpm) not in done %} + + + {{ os._name }} is installed + + {{ os._name }} + + + {{ os._name }} is installed + + + + SecPod Team + + + ACCEPTED + + + + + + + + + {{ package._name }} is installed + + {{ package._name }} + + + {{ package._name }} is installed + + + + SecPod Team + + + ACCEPTED + + + + + + + + + {{ cvrf._title }} ({{ cvrf._tracking._id }}) + + {{ os._name }} + {{ package._name }} + + + + {%- for vuln in cvrf._vulnerabilities %} + + {%- endfor %}) + {{ oval_core.notes(cvrf, 'Details') }} + + + + SecPod Team + + + INITIAL SUBMISSION + + + + + + + + + + + + + + + + + + + + + + + + + + + /etc + redhat-release + ^Red Hat Enterprise.*release.*$ + 1 + + + {{ package._name }} + + + + + ^Red Hat Enterprise.*release 7.*$ + + + 0:2.3.5-3.el7_0 + + + + + {{- done.append((rls, rpm)) or '' }} + {%- endif %} + {%- endif %} + {%- endif %} +{%- endfor %} diff -r 000000000000 -r e18b61a73a68 farolluz/templates/oval_core.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/templates/oval_core.j2 Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,35 @@ +{# +# Description: +# Template macros for generation of OVAL documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +-#} + +{% macro notes(cvrf, name) %} +{%- for note in cvrf._notes %} + {%- if note._type == name or note._title == name %}{{ note._note.replace('"', "'") }}{% endif %} +{%- endfor %} +{%- for vulnerability in cvrf._vulnerabilities %} + {%- for note in vulnerability._notes %} + {%- if note._type == name or note._title == name %}{{ note._note.replace('"', "'") }}{% endif %} + {%- endfor %} +{%- endfor %} +{% endmacro %} diff -r 000000000000 -r e18b61a73a68 farolluz/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/utils.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Description: +# Utilities used at multiple places. +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +from datetime import datetime + +from .parsers import cvrf + +def utcnow(): + """ This function returns the current date in a timezone aware fashion """ + return cvrf.parseDate(datetime.utcnow().isoformat()+'Z') + diff -r 000000000000 -r e18b61a73a68 parse_cvrf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parse_cvrf Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,36 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys + +from farolluz.parsers.cvrf import parse + +if __name__ == "__main__": + with open(sys.argv[1], 'rt') as f: + cvrf = parse(f) + cvrf.validate() + print(cvrf) + print(cvrf.getHighestCVSS()._vector) + print(cvrf.getProductList()) + print(cvrf._producttree._branches) +# print(cvrf._producttree._branches[0]._childs) diff -r 000000000000 -r e18b61a73a68 render --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,32 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys + +from farolluz.renderer import main + +if __name__ == "__main__": + template = "nasl.j2" + if len(sys.argv) >= 3: + template = sys.argv[2] + main(sys.argv[1], template) diff -r 000000000000 -r e18b61a73a68 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Tue Sep 23 15:19:14 2014 +0200 @@ -0,0 +1,45 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +from setuptools import setup + +from farolluz import __version__ + +with open('README.txt', 'rt') as f: + long_description = f.read() + +setup( + name='farolluz', + version=__version__, + description="Utilities to manipulate security advisories", + long_description=long_description, + author="Benoît Allard", + author_email='benoit.allard@greenbone.net', + license='GPLv2+', + packages=['farolluz', 'farolluz.parsers'], +# package_dir={'farolluz': 'farolluz'}, +# include_package_data=True, +# package_data={'farolluz': ['templates/*.j2']}, + scripts=['parse_cvrf', 'render'], + install_requires=['Jinja2'], +)